diff options
Diffstat (limited to 'foobar')
278 files changed, 79113 insertions, 0 deletions
diff --git a/foobar/.builds/freebsd.yml b/foobar/.builds/freebsd.yml new file mode 100644 index 00000000..51993106 --- /dev/null +++ b/foobar/.builds/freebsd.yml @@ -0,0 +1,23 @@ +image: freebsd/latest +packages: +- autoconf +- automake +- bison +- libevent +- libtool +- libressl +- libasr +- py37-ansible +- db6 +- python3 +- python37 +- python +sources: +- https://github.com/OpenSMTPD/OpenSMTPD +- https://github.com/OpenSMTPD/ci +tasks: +- ansible: | + ls -lah + BUILD_DIR=$(pwd)/OpenSMTPD + cd ci/ansible + ansible-playbook test.yml --inventory inventory/freebsd --skip-tags checkout --extra-vars "build_dir=$BUILD_DIR" diff --git a/foobar/.builds/openbsd.yml b/foobar/.builds/openbsd.yml new file mode 100644 index 00000000..a0557a99 --- /dev/null +++ b/foobar/.builds/openbsd.yml @@ -0,0 +1,16 @@ +image: openbsd/6.6 +packages: +- autoconf-2.69p2 +- automake-1.14.1p1 +- libtool +sources: +- https://github.com/OpenSMTPD/OpenSMTPD +tasks: +- configure: | + cd OpenSMTPD + export AUTOCONF_VERSION=2.69 + ./bootstrap + ./configure +- build: | + cd OpenSMTPD + make diff --git a/foobar/.gitattributes b/foobar/.gitattributes new file mode 100644 index 00000000..eaca2e77 --- /dev/null +++ b/foobar/.gitattributes @@ -0,0 +1 @@ +*/CVS export-ignore diff --git a/foobar/.github/FUNDING.yml b/foobar/.github/FUNDING.yml new file mode 100644 index 00000000..36cb5950 --- /dev/null +++ b/foobar/.github/FUNDING.yml @@ -0,0 +1,3 @@ +github: [poolpOrg] +patreon: gilles + diff --git a/foobar/.github/workflows/alpine.yml b/foobar/.github/workflows/alpine.yml new file mode 100644 index 00000000..6f2d29f6 --- /dev/null +++ b/foobar/.github/workflows/alpine.yml @@ -0,0 +1,15 @@ +name: Alpine Linux (amd64 musl openssl) +on: + push: + branches: + - portable + pull_request: + branches: + - portable +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Alpine Linux (amd64 musl openssl) + run: docker build . --file ci/docker/Dockerfile.alpine --tag opensmtpd:alpine diff --git a/foobar/.github/workflows/arch.yml b/foobar/.github/workflows/arch.yml new file mode 100644 index 00000000..a3528152 --- /dev/null +++ b/foobar/.github/workflows/arch.yml @@ -0,0 +1,15 @@ +name: Archlinux (amd64 glibc libressl) +on: + push: + branches: + - portable + pull_request: + branches: + - portable +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Archlinux (amd64 glibc libressl) + run: docker build . --file ci/docker/Dockerfile.archlinux --tag opensmtpd:archlinux diff --git a/foobar/.github/workflows/clang.yml b/foobar/.github/workflows/clang.yml new file mode 100644 index 00000000..15ccd31d --- /dev/null +++ b/foobar/.github/workflows/clang.yml @@ -0,0 +1,35 @@ +name: Clang Scan + +on: + schedule: + - cron: '0 23 * * *' # Daily at 23:00 UTC +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: install dependencies + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 9 + sudo apt install \ + clang-tools-9 \ + libasr-dev \ + libevent-dev \ + libssl-dev \ + bison + - uses: actions/checkout@v1 + - name: run scan-build + run: | + export PATH=$PATH:/usr/lib/llvm-9/bin/ + CLANG_SCAN_BADGE_REQUIRED=1 ci/scripts/clang_scan.sh + - name: publish results + run: | + aws s3 rm --recursive s3://opensmtpd/reports/clang/ + cd clang-report + cd "$( find . -type d | sort | tail -n1 )" + aws s3 sync . s3://opensmtpd/reports/clang + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} diff --git a/foobar/.github/workflows/coverity.yml b/foobar/.github/workflows/coverity.yml new file mode 100644 index 00000000..d25bb4f7 --- /dev/null +++ b/foobar/.github/workflows/coverity.yml @@ -0,0 +1,15 @@ +name: Coverity Scan +on: + schedule: + - cron: '0 23 * * *' # Daily at 23:00 UTC +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: dependencies + run: sudo apt install libasr-dev libevent-dev libssl-dev bison + - name: scan + run: sh ci/scripts/coverity_scan.sh + env: + token: ${{ secrets.COVERITY_SCAN_TOKEN }} diff --git a/foobar/.github/workflows/ubuntu-gcc10.yml b/foobar/.github/workflows/ubuntu-gcc10.yml new file mode 100644 index 00000000..9f70d7f0 --- /dev/null +++ b/foobar/.github/workflows/ubuntu-gcc10.yml @@ -0,0 +1,15 @@ +name: Ubuntu (amd64 glibc openssl gcc10) +on: + push: + branches: + - portable + pull_request: + branches: + - portable +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Ubuntu (amd64 glibc openssl gcc10) + run: docker build . --file ci/docker/Dockerfile.ubuntu-gcc10 --tag opensmtpd:ubuntu-gcc10 diff --git a/foobar/.github/workflows/ubuntu.yml b/foobar/.github/workflows/ubuntu.yml new file mode 100644 index 00000000..c068c6d5 --- /dev/null +++ b/foobar/.github/workflows/ubuntu.yml @@ -0,0 +1,15 @@ +name: Ubuntu (amd64 glibc openssl) +on: + push: + branches: + - portable + pull_request: + branches: + - portable +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Ubuntu (amd64 glibc openssl) + run: docker build . --file ci/docker/Dockerfile.ubuntu --tag opensmtpd:ubuntu diff --git a/foobar/.gitignore b/foobar/.gitignore new file mode 100644 index 00000000..f9f88aa5 --- /dev/null +++ b/foobar/.gitignore @@ -0,0 +1,56 @@ +*.d +*.o +*.a +*.in +*.out +*.log +.#* +.deps +m4 +aclocal.m4 +autom4te.cache +compile +config.guess +config.h +config.status +config.sub +configure +depcomp +install-sh +libtool +ltmain.sh +missing +parse.c +stamp-h1 +ylwrap +tags +obj + +#Other VCS files +CVS/ +smtpd/CVS +smtpd/smtpctl/CVS +smtpd/smtpd/CVS +smtpd/mail/CVS +smtpd/mail/CVS +smtpd/mail/*/CVS +smtpd/smtpctl/CVS/* + +#Editor temporary files +*~ +.idea + +# Coverity +cov-int/ +cov-analysis-linux64/ +cov-analysis-linux64.tgz +opensmtpd.tgz + +# Clang scan-build +clang-report/ + + +# TLS certs +open.smtpd.cert +open.smtpd.csr +open.smtpd.key diff --git a/foobar/portable/CHANGES.md b/foobar/portable/CHANGES.md new file mode 100644 index 00000000..db25a4db --- /dev/null +++ b/foobar/portable/CHANGES.md @@ -0,0 +1,67 @@ +# Release 6.6.3p1 (2020-02-10) + +Following the 6.6.2p1 release, various improvements were done in OpenBSD -current to mitigate the risk of similar bugs. + +This release back-ports them to the portable version of OpenSMTPD. + +# Release 6.6.2p1 (2020-01-28) + +This is CRITICAL security bugfix for +[CVE-2020-7247](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-7247) + +Read more details in +[this blog post](https://poolp.org/posts/2020-01-30/opensmtpd-advisory-dissected/) + +# Release 6.6.1p1 (2019-11-06) + +## Changes in this release (since 6.6.0p1) + +This is a bugfix release. No new features were added. + +- Fixed crash on recipient expansion [#968](https://github.com/OpenSMTPD/OpenSMTPD/issues/968) +- Fixed broken build with LibreSSL [#944](https://github.com/OpenSMTPD/OpenSMTPD/issues/944) +- Fixed crash in `arc4random` caused by differences in OpenSSL vs LibreSSL compatibility layer plumbing [#958](https://github.com/OpenSMTPD/OpenSMTPD/issues/958) +- Fixed issue where `from any` rules never matched by IPv6 sources [#969](https://github.com/OpenSMTPD/OpenSMTPD/issues/969) +- Fixed crash that happened during mail relay on musl distros [#929](https://github.com/OpenSMTPD/OpenSMTPD/issues/929) +- Added reference aliases file in `etc/aliases` +- Fixed multiple compilation warnings +[#965](https://github.com/OpenSMTPD/OpenSMTPD/issues/965) +[#966](https://github.com/OpenSMTPD/OpenSMTPD/issues/966) +[#967](https://github.com/OpenSMTPD/OpenSMTPD/issues/967) +[#978](https://github.com/OpenSMTPD/OpenSMTPD/issues/978) +[#977](https://github.com/OpenSMTPD/OpenSMTPD/issues/977) +[#975](https://github.com/OpenSMTPD/OpenSMTPD/issues/975) + + + +# Release 6.6.0p1 (2019-10-26) + +## Dependencies note: + +This release builds with LibreSSL > 3.0.2 or OpenSSL > 1.1.0. + +It's preferable to depend on LibreSSL as OpenSMTPD is written and tested +with that dependency. In addition, the features parity is not respected, +some features will not be available with OpenSSL, like ECDSA server-side +certificates support in this release. OpenSSL library is considered as a +best effort target TLS library and provided as a commodity, LibreSSL has +become our target TLS library. + + +## Changes in this release (since 6.4.0): + +- various improvements to documentation and code +- reverse dns session matching criteria added to smtpd.conf(5) +- regex table lookup support added to smtpd.conf(5) +- introduced support for ECDSA certificates with an ECDSA privsep engine +- introduced builtin filters for basic filtering of incoming sessions +- introduced option to deliver junk to a Junk folder in mail.maildir(8) +- fixed the smtp(1) client so it uses correct default port for SMTPS +- fixed an smtpd(8) crash on excessively large input +- ensured mail rejected by an LMTP server stay queued + + +## Experimental features: + +- introduced a filters API to allow writing standalone filters for smtpd +- introduced proxy-v2 support allowing smtpd to operate behind a proxy diff --git a/foobar/portable/INSTALL b/foobar/portable/INSTALL new file mode 100644 index 00000000..d3c5b40a --- /dev/null +++ b/foobar/portable/INSTALL @@ -0,0 +1,237 @@ +Installation Instructions +************************* + +Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005, +2006, 2007 Free Software Foundation, Inc. + +This file is free documentation; the Free Software Foundation gives +unlimited permission to copy, distribute and modify it. + +Basic Installation +================== + +Briefly, the shell commands `./configure; make; make install' should +configure, build, and install this package. The following +more-detailed instructions are generic; see the `README' file for +instructions specific to this package. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. Caching is +disabled by default to prevent problems with accidental use of stale +cache files. + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You need `configure.ac' if +you want to change it or regenerate `configure' using a newer version +of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. + + Running `configure' might take a while. While running, it prints + some messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package. + + 4. Type `make install' to install the programs and any data files and + documentation. + + 5. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + + 6. Often, you can also type `make uninstall' to remove the installed + files again. + +Compilers and Options +===================== + +Some systems require unusual options for compilation or linking that the +`configure' script does not know about. Run `./configure --help' for +details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c99 CFLAGS=-g LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + +You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you can use GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + With a non-GNU `make', it is safer to compile the package for one +architecture at a time in the source code directory. After you have +installed the package for one architecture, use `make distclean' before +reconfiguring for another architecture. + +Installation Names +================== + +By default, `make install' installs the package's commands under +`/usr/local/bin', include files under `/usr/local/include', etc. You +can specify an installation prefix other than `/usr/local' by giving +`configure' the option `--prefix=PREFIX'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +pass the option `--exec-prefix=PREFIX' to `configure', the package uses +PREFIX as the prefix for installing programs and libraries. +Documentation and other data files still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=DIR' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + +Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + +There may be some features `configure' cannot figure out automatically, +but needs to determine by the type of machine the package will run on. +Usually, assuming the package is built to be run on the _same_ +architectures, `configure' can figure that out, but if it prints a +message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the option `--target=TYPE' to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + +If you want to set default values for `configure' scripts to share, you +can create a site shell script called `config.site' that gives default +values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + +Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +causes the specified `gcc' to be used as the C compiler (unless it is +overridden in the site shell script). + +Unfortunately, this technique does not work for `CONFIG_SHELL' due to +an Autoconf bug. Until the bug is fixed you can use this workaround: + + CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash + +`configure' Invocation +====================== + +`configure' recognizes the following options to control how it operates. + +`--help' +`-h' + Print a summary of the options to `configure', and exit. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. + diff --git a/foobar/portable/LICENSE b/foobar/portable/LICENSE new file mode 100644 index 00000000..f1cea087 --- /dev/null +++ b/foobar/portable/LICENSE @@ -0,0 +1,342 @@ +This file is part of the OpenSMTPD software. + +The licences which components of this software fall under are as +follows. First, we will summarize and say that all components +are under a BSD licence, or a licence more free than that. + +OpenSMTPD contains no GPL code. + +Portable OpenSMTPD is divided in 4 parts: +- Original OpenSMTPD +- mail.local +- openbsd-compat +- smtpctl encrypt sub command + + + +OpenSMTPD +========= + + +1) Almost all code is licensed under an ISC-style license, to the following + copyright holders: + + Gilles Chehade + Eric Faurot + Jacek Masiulaniec + Pierre-Yves Ritschard + Henning Brauer + Esben Norby + Markus Friedl + Daniel Hartmeier + Theo de Raadt + Claudio Jeker + Reyk Floeter + Janne Johansson + Alexander Schrijver + Marc Balmer + Ashish Shukla + Ryan Kavanagh + Charles Longeau + + +2) ssl_privsep.c + + /* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * 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 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + + /* + * SSL operations needed when running in a privilege separated environment. + * Adapted from openssl's ssl_rsa.c by Pierre-Yves Ritschard . + */ + + + +mail.local +========== + + +1) mail.local is covered by a 3-clause BSD license, to the following + copyright holders: + + The Regents of the University of California. + David Mazieres + Theo de Raadt + + * 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. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + + + +openbsd-compat +============== + + +Most of the OpenBSD compatibility layer is based on the work by Damien Miller for +Portable OpenSSH. + +1) Almost all code is licensed under an ISC-style license, to the following + copyright holders: + + Internet Software Consortium. + David Mazieres + Damien Miller + Markus Friedl + Todd C. Miller + Henning Brauer + Pierre-Yves Ritschard + Reyk Floeter + Theo de Raadt + Ted Unangst + Charles Longeau + + * 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. + + +2) base64.{c,h} in addition to beeing covered by an ISC-style licence, is also + covered by this one: + + * Portions Copyright (c) 1995 by International Business Machines, Inc. + * + * International Business Machines, Inc. (hereinafter called IBM) grants + * permission under its copyrights to use, copy, modify, and distribute this + * Software with or without fee, provided that the above copyright notice and + * all paragraphs of this notice appear in all copies, and that the name of IBM + * not be used in connection with the marketing of any product incorporating + * the Software or modifications thereof, without specific, written prior + * permission. + * + * To the extent it has a right to do so, IBM grants an immunity from suit + * under its patents, if any, for the use, sale or manufacture of products to + * the extent that such products are used for performing Domain Name System + * dynamic updates in TCP/IP networks by means of the Software. No immunity is + * granted for any product per se or for any other function of any product. + * + * THE SOFTWARE IS PROVIDED "AS IS", AND IBM DISCLAIMS ALL WARRANTIES, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE. IN NO EVENT SHALL IBM BE LIABLE FOR ANY SPECIAL, + * DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE, EVEN + * IF IBM IS APPRISED OF THE POSSIBILITY OF SUCH DAMAGES. + + +3) Portable OpenSMTPD includes code under the 2-clause BSD license, from the + following copyright holders: + + Ben Lindstrom + Damien Miller + Marc Espie + Tim Rice + The NetBSD Foundation, Inc. + Jason R. Thorpe? + Niels Provos + + * 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 AUTHOR ``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 AUTHOR 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. + + +4) Some code is under a 3-clause BSD license, from the + following copyright holders: + + The Regents of the University of California. + Ian F. Darwin + Damien Miller + Eric P. Allman + + * 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. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + + +5) Some code is under a 4-clause BSD license, from the + following copyright holder: + + Christos Zoulas + + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Christos Zoulas. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + + +6) includes.h, log.h, setresguid.c, xmalloc.c, xmalloc.c + + * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + * All rights reserved + * + * As far as I am concerned, the code I have written for this software + * can be used freely for any purpose. Any derived versions of this + * software must be clearly marked as such, and if the derived work is + * incompatible with the protocol description in the RFC file, it must be + * called by a name other than "ssh" or "Secure Shell". + + +7) chacha_private.h + +D. J. Bernstein +Public domain. + + +8) bootstrap (only there in the git repository) + + # Copyright (c) 2002-2011 Sam Hocevar <sam@hocevar.net> + # + # This program is free software. It comes without any warranty, to + # the extent permitted by applicable law. You can redistribute it + # and/or modify it under the terms of the Do What The Fuck You Want + # To Public License, Version 2, as published by Sam Hocevar. See + # http://sam.zoy.org/wtfpl/COPYING for more details. + + + +smtpctl encrypt sub command +=========================== + + +smtpctl encrypt sub command is licensed under an ISC-style license, to the +following copyright holders: + + Sunil Nimmagadda + Gilles Chehade diff --git a/foobar/portable/Makefile.am b/foobar/portable/Makefile.am new file mode 100644 index 00000000..7d95a5b8 --- /dev/null +++ b/foobar/portable/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = openbsd-compat mk contrib + +ACLOCAL_AMFLAGS = -I m4 diff --git a/foobar/portable/README.md b/foobar/portable/README.md new file mode 100644 index 00000000..c00663cd --- /dev/null +++ b/foobar/portable/README.md @@ -0,0 +1,201 @@ +# OpenSMTPD + +[![Version](https://img.shields.io/badge/Version-6.6.3p1-brihtgreen.svg)](https://github.com/OpenSMTPD/OpenSMTPD/releases/tag/6.6.3p1) +[![Coverity Scan analysis](https://scan.coverity.com/projects/278/badge.svg)](https://scan.coverity.com/projects/opensmtpd-opensmtpd) +[![Packaging status](https://repology.org/badge/tiny-repos/opensmtpd.svg)](https://repology.org/project/opensmtpd/versions) +[![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://www.isc.org/licenses/) +[![Clang Analysis](https://opensmtpd.email/reports/clang/badge.svg)](https://opensmtpd.email/reports/clang/index.html) + + +OpenSMTPD is a FREE implementation of the server-side SMTP protocol as +defined by [RFC 5321](https://tools.ietf.org/html/rfc5321), with some +additional standard extensions. + +It allows ordinary machines to exchange e-mails with other systems +speaking the SMTP protocol. + +OpenSMTPD runs on top of the OpenBSD operating system but also has a +portable version that can build and run on several systems, including: + +* Linux +* FreeBSD +* NetBSD +* DragonFly + +For more information: http://www.opensmtpd.org/portable.html + +People interested about OpenSMTPD are encouraged to subscribe to our +mailing list: http://www.opensmtpd.org/list.html + +and to join the IRC channel: #OpenSMTPD @ irc.freenode.net + +Also note that we have a wiki at +https://github.com/OpenSMTPD/OpenSMTPD/wiki that you are encouraged to +contribute to. + +Cheers! + + +# How to build, configure and use Portable OpenSMTPD + +## Dependencies + +Portable OpenSMTPD relies on: + * autoconf (http://www.gnu.org/software/autoconf/) + * automake (http://www.gnu.org/software/automake/) + * bison (http://www.gnu.org/software/bison/) + or byacc (http://invisible-island.net/byacc/byacc.html) + * libevent (http://libevent.org/) + * libtool (http://www.gnu.org/software/libtool/) + * libressl (https://www.libressl.org/) + or OpenSSL (https://www.openssl.org/) + + +By default OpenSMTPD expects latest versions of all dependencies unless noted otherwise. + +Note that some distributions have different packages for a same library, you should always use the `-dev` or `-devel` package (for example, `libevent-dev` or `libevent-devel`) if you're going to build OpenSMTPD yourself. + + +## Get the source + + git clone -b portable git://github.com/OpenSMTPD/OpenSMTPD.git opensmtpd + + +## Build + + cd opensmtpd* + ./bootstrap # Only if you build from git sources + ./configure + make + sudo make install + +### Special notes for FreeBSD/DragonFlyBSD/Mac OS X: + +Please launch configure with special directive about libevent and +libasr directory: + +### FreeBSD / DragonFlyBSD: + + ./configure --with-libasr=/usr/local + +### Mac OS X: + + ./configure --with-libevent=/opt/local --with-libasr=/opt/local + + +## Install + + sudo make install + + +## Setup historical interface + +OpenSMTPD provides a single utility `smtpctl` to control the daemon and +the local submission subsystem. + +To accomodate systems that require historical interfaces such as `sendmail`, +`newaliases` or `makemap`, the `smtpctl` utility can operate in compatibility +mode if called with the historical name. + +On mailwrapper-enabled systems, this is achieved by editing `/etc/mailer.conf` +and adding the following lines: + + sendmail /usr/sbin/smtpctl + send-mail /usr/sbin/smtpctl + mailq /usr/sbin/smtpctl + makemap /usr/sbin/smtpctl + newaliases /usr/sbin/smtpctl + + +Whereas on systems that don't provide mailwrapper, it can be achieved by +setting the appropriate symbolic links: + + ln -s /usr/sbin/smtpctl sendmail + ln -s /usr/sbin/smtpctl send-mail + ln -s /usr/sbin/smtpctl mailq + ln -s /usr/sbin/smtpctl makemap + ln -s /usr/sbin/smtpctl newaliases + + +The OpenSMTPD project leaves it up to the package maintainers to setup the +links in their packages as it is very hard for us to accomodate all systems +with the prefered method in a clean way. + + +## Configure /etc/smtpd.conf + +Please have a look at the complete format description of smtpd.conf +configuration file (https://man.openbsd.org/smtpd.conf) + + +## Add OpenSMTPD users + +To operate, OpenSMTPD requires at least one user, by default `_smtpd`; and +preferably two users, by default `_smtpd` and `_smtpq`. + +Using two users instead of one will increase security by a large factor +so... if you want to voluntarily reduce security or you have absolute +more faith in our code than we do, by all means use one. + + +The instructions below assume the default users however, the configure +script allows overriding these using the options: +`--with-user-smtpd`, `--with-user-queue`, and `--with-group-queue`. + + +### NetBSD, Linux (Debian, Arch Linux, ...) + + mkdir /var/empty + useradd -c "SMTP Daemon" -d /var/empty -s /sbin/nologin _smtpd + useradd -c "SMTPD Queue" -d /var/empty -s /sbin/nologin _smtpq + +### DragonFlyBSD, FreeBSD + + pw useradd _smtpd -c "SMTP Daemon" -d /var/empty -s /sbin/nologin + pw useradd _smtpq -c "SMTPD Queue" -d /var/empty -s /sbin/nologin + +### Mac OS X + +First we need a group with an unused GID below `500`, list the current +ones used: + + /usr/bin/dscl . -list /Groups PrimaryGroupID | sort -n -k2,2 + +Add a group - here we have picked `444`: + + /usr/bin/sudo /usr/bin/dscl . -create /Groups/_smtpd + PrimaryGroupID 444 + +Then the user. Again we need an unused UID below `500`, list the current +ones used: + + /usr/bin/dscl . -list /Users UniqueID | sort -n -k2,2 + +Add a user - here we have picked `444`: + + /usr/bin/sudo /usr/bin/dscl . -create /Users/_smtpd UniqueID 444 + /usr/bin/sudo /usr/bin/dscl . -delete /Users/_smtpd AuthenticationAuthority + /usr/bin/sudo /usr/bin/dscl . -delete /Users/_smtpd PasswordPolicyOptions + /usr/bin/sudo /usr/bin/dscl . -delete /Users/_smtpd dsAttrTypeNative:KerberosKeys + /usr/bin/sudo /usr/bin/dscl . -delete /Users/_smtpd dsAttrTypeNative:ShadowHashData + /usr/bin/sudo /usr/bin/dscl . -create /Users/_smtpd RealName "SMTP Daemon" + /usr/bin/sudo /usr/bin/dscl . -create /Users/_smtpd Password "*" + /usr/bin/sudo /usr/bin/dscl . -create /Users/_smtpd PrimaryGroupID 444 + /usr/bin/sudo /usr/bin/dscl . -create /Users/_smtpd NFSHomeDirectory /var/empty + /usr/bin/sudo /usr/bin/dscl . -create /Users/_smtpd UserShell /usr/bin/false + +repeat for the `_smtpq` user. + + +## Launch smtpd + +First, kill any running sendmail/exim/qmail/postfix or other. + +Then: + + smtpd + +or in debug and verbose mode + + smtpd -dv + diff --git a/foobar/portable/bootstrap b/foobar/portable/bootstrap new file mode 100755 index 00000000..24e29440 --- /dev/null +++ b/foobar/portable/bootstrap @@ -0,0 +1,151 @@ +#! /bin/sh + +# bootstrap: generic bootstrap/autogen.sh script for autotools projects +# +# Copyright (c) 2002-2011 Sam Hocevar <sam@hocevar.net> +# +# This program is free software. It comes without any warranty, to +# the extent permitted by applicable law. You can redistribute it +# and/or modify it under the terms of the Do What The Fuck You Want +# To Public License, Version 2, as published by Sam Hocevar. See +# http://sam.zoy.org/wtfpl/COPYING for more details. +# +# The latest version of this script can be found at the following place: +# http://caca.zoy.org/wiki/build + +# Die if an error occurs +set -e + +# Guess whether we are using configure.ac or configure.in +if test -f configure.ac; then + conffile="configure.ac" +elif test -f configure.in; then + conffile="configure.in" +else + echo "$0: could not find configure.ac or configure.in" + exit 1 +fi + +# Check for needed features +auxdir="`sed -ne 's/^[ \t]*A._CONFIG_AUX_DIR *([[ ]*\([^] )]*\).*/\1/p' $conffile`" +pkgconfig="`grep '^[ \t]*PKG_PROG_PKG_CONFIG' $conffile >/dev/null 2>&1 && echo yes || echo no`" +libtool="`grep '^[ \t]*A._PROG_LIBTOOL' $conffile >/dev/null 2>&1 && echo yes || echo no`" +header="`grep '^[ \t]*A._CONFIG_HEADER' $conffile >/dev/null 2>&1 && echo yes || echo no`" +makefile="`[ -f Makefile.am ] && echo yes || echo no`" +aclocalflags="`sed -ne 's/^[ \t]*ACLOCAL_AMFLAGS[ \t]*=//p' Makefile.am 2>/dev/null || :`" + +# Check for automake +amvers="no" +for v in 16.1 16 15 14 13; do + if automake-1.${v} --version >/dev/null 2>&1; then + amvers="-1.${v}" + break + elif automake1.${v} --version >/dev/null 2>&1; then + amvers="1.${v}" + break + fi +done + +if test "${amvers}" = "no" && automake --version > /dev/null 2>&1; then + amvers="`automake --version | sed -e '1s/[^0-9]*//' -e q`" + if `echo "$amvers\n1.14" | sort -V | head -n 1 | grep -q "$amvers"`; then + amvers="no" + else + amvers="" + fi +fi + +if test "$amvers" = "no"; then + echo "$0: you need automake version 1.14 or later" + exit 1 +fi + +# Check for autoconf +acvers="no" +for v in "" "269" "-2.69"; do + if autoconf${v} --version >/dev/null 2>&1; then + acvers="${v}" + break + fi +done + +if test "$acvers" = "no"; then + echo "$0: you need autoconf" + exit 1 +fi + +# Check for libtool +if test "$libtool" = "yes"; then + libtoolize="no" + if glibtoolize --version >/dev/null 2>&1; then + libtoolize="glibtoolize" + else + for v in "16" "15" "" "14"; do + if libtoolize${v} --version >/dev/null 2>&1; then + libtoolize="libtoolize${v}" + break + fi + done + fi + + if test "$libtoolize" = "no"; then + echo "$0: you need libtool" + exit 1 + fi +fi + +# Check for pkg-config +if test "$pkgconfig" = "yes"; then + if ! pkg-config --version >/dev/null 2>&1; then + echo "$0: you need pkg-config" + exit 1 + fi +fi + +# Remove old cruft +for x in aclocal.m4 configure config.guess config.log config.sub config.cache config.h.in config.h compile libtool.m4 ltoptions.m4 ltsugar.m4 ltversion.m4 ltmain.sh libtool ltconfig missing mkinstalldirs depcomp install-sh; do rm -f $x autotools/$x; if test -n "$auxdir"; then rm -f "$auxdir/$x"; fi; done +rm -Rf autom4te.cache +if test -n "$auxdir"; then + if test ! -d "$auxdir"; then + mkdir "$auxdir" + fi + aclocalflags="${aclocalflags} -I $auxdir -I ." +fi + +# Honour M4PATH because sometimes M4 doesn't +save_IFS=$IFS +IFS=: +tmp="$M4PATH" +for x in $tmp; do + if test -n "$x"; then + aclocalflags="${aclocalflags} -I $x" + fi +done +IFS=$save_IFS + +# Explain what we are doing from now +set -x + +# Bootstrap package +if test "$libtool" = "yes"; then + ${libtoolize} --copy --force + if test -n "$auxdir" -a ! "$auxdir" = "." -a -f "ltmain.sh"; then + echo "$0: working around a minor libtool issue" + mv ltmain.sh "$auxdir/" + fi +fi + +aclocal${amvers} ${aclocalflags} +autoconf${acvers} +if test "$header" = "yes"; then + autoheader${acvers} +fi +if test "$makefile" = "yes"; then + #add --include-deps if you want to bootstrap with any other compiler than gcc + #automake${amvers} --add-missing --copy --include-deps + automake${amvers} --foreign --add-missing --copy +fi + +# Remove cruft that we no longer want +rm -Rf autom4te.cache + diff --git a/foobar/portable/ci/COVERITY.MD5SUM b/foobar/portable/ci/COVERITY.MD5SUM new file mode 100644 index 00000000..67b8a2de --- /dev/null +++ b/foobar/portable/ci/COVERITY.MD5SUM @@ -0,0 +1 @@ +d0d7d7df9d6609e578f85096a755fb8f ./cov-analysis-linux64.tgz diff --git a/foobar/portable/ci/README.md b/foobar/portable/ci/README.md new file mode 100644 index 00000000..2eee08f1 --- /dev/null +++ b/foobar/portable/ci/README.md @@ -0,0 +1,83 @@ +# Continuous Integration + +This directory contains CI/CD related scripts and resources + +CI/CD process leverages GitHub Actions as a primary automation platform since +up to 20 parallel workflows are available for opensource projects. + +On high level we have multiple dimensions to test: + + - OS Distribution + - TLS library implementation: libress, openssl + - libc implementations: glibc, musl + - compiler: gcc, clang (not yet tested) + +Within this matrix build tests, static code analysis, functional and +integration tests are planned. Currently only build tests and static code +analysis exist. Help is much needed with developing functional and integration +tests. + +## Directory strucuture + +- [docker](#dockerfiles) dockerfiles for various distributions +- [scripts](#scripts) useful scripts for ci/cd automation + + +## Design Considerations + +- Keep workflow yaml files and execution logic as separate as possible. + Reference ci scripts from workflow files to allow running same tests + locally, without depending on github. + + + +# Dockerfiles + +Dockerfiles in [docker](docker/) directory can be used for developing and +testing OpenSMTPD. These dockerfiles are intended to be used for dev/test +cycle ONLY and ARE NOT intended to be a delivery mechanism for end users and +should not be published on external resouces like DockerHub. Dockerfiles in +this folder can be used as a reference for package maintainers of various +distributions. + + +## Usage + +OpenSMTPD provides a set of dockerfiles for getting started with development +quickly locally or with GitHub's Actions. + +For each distribution there is a separate dockerfile with a distro name +suffixed. E.g. `Dockerfile.alpine` is a dockerfile that builds OpenSMTPD in +Alpine Linux environment. + +To build: + + docker build -f docker/Dockerfile.alpine -t opensmtpd-alpine + + +All configuration files that are in `/etc/mail` are taken from `etc/` directory. + + +To run the container that you've just built run: + + docker run --name smtpd_server -p 25:25 opensmtpd-alpine + + + +# Scripts + +Scripts to automate ci/cd actions: + +- [coverity_scan](scripts/coverity_scan.sh) - runs coverity scan and submits + the rusult to Coverity. See script contents for usage instructions. + +- [generate_certs](scripts/generate_certs.sh) - convenient script to quickly + generate some TLS certificates. Useful for testing. + +# Historical reference + +[Initial design discusstion](https://github.com/OpenSMTPD/OpenSMTPD/issues/947) + + + + diff --git a/foobar/portable/ci/docker/Dockerfile.alpine b/foobar/portable/ci/docker/Dockerfile.alpine new file mode 100644 index 00000000..2c7c66fc --- /dev/null +++ b/foobar/portable/ci/docker/Dockerfile.alpine @@ -0,0 +1,50 @@ +FROM alpine:3.11 as build + +# creates /opensmtpd dir and makes all following commands to run in it +# https://docs.docker.com/engine/reference/builder/#workdir +WORKDIR /opensmtpd + +# install necessary packages +RUN apk add --no-cache \ + autoconf \ + automake \ + bison \ + ca-certificates \ + fts-dev \ + gcc \ + fts \ + libevent-dev \ + libtool \ + libtool \ + linux-pam-dev \ + make \ + musl-dev \ + libressl \ + libressl-dev \ + zlib-dev + +# create users and directories +# note: alpine uses busybox and useradd is not available there +# also long flags are not available too, so sorry for the +RUN mkdir -p /var/lib/opensmtpd/empty \ + && adduser _smtpd -h /var/lib/opensmtpd/empty/ -D -H -s /bin/false \ + && adduser _smtpq -h /var/lib/opensmtpd/empty/ -D -H -s /bin/false \ + && mkdir -p /var/spool/smtpd \ + && mkdir -p /var/mail \ + && mkdir -p /etc/mail \ + && chmod 711 /var/spool/smtpd + +# Copy contentes of the repo inside the container +# https://docs.docker.com/engine/reference/builder/#copy +COPY . /opensmtpd + +# build opensmtpd +RUN ./bootstrap \ + && ./configure \ + --with-gnu-ld \ + --sysconfdir=/etc/mail \ + --with-auth-pam \ + && make \ + && make install \ + && cp etc/aliases /etc/mail/aliases + diff --git a/foobar/portable/ci/docker/Dockerfile.archlinux b/foobar/portable/ci/docker/Dockerfile.archlinux new file mode 100644 index 00000000..dcd46684 --- /dev/null +++ b/foobar/portable/ci/docker/Dockerfile.archlinux @@ -0,0 +1,62 @@ +FROM archlinux + +# Allow container to expose ports at runtime, if necessary +# https://docs.docker.com/engine/reference/#expose +EXPOSE 25 +EXPOSE 465 +EXPOSE 587 + +# creates /opensmtpd dir and makes all following commands to run in it +# https://docs.docker.com/engine/reference/builder/#workdir +WORKDIR /opensmtpd + +# install necessary packages +RUN pacman -Suy --noconfirm \ + base \ + make \ + m4 \ + grep \ + gcc \ + automake \ + autoconf \ + libtool \ + bison \ + gettext \ + libevent \ + libressl \ + pam \ + zlib + + +# create users and directories +RUN mkdir -p /var/lib/opensmtpd/empty \ + && useradd _smtpd \ + --home-dir /var/lib/opensmtpd/empty \ + --no-create-home \ + --shell /bin/false \ + && useradd _smtpq \ + --home-dir /var/lib/opensmtpd/empty \ + --no-create-home \ + --shell /bin/false \ + && mkdir -p /var/spool/smtpd \ + && mkdir -p /var/mail \ + && mkdir -p /etc/mail \ + && chmod 711 /var/spool/smtpd + + +# Copy contentes of the repo inside the container +# https://docs.docker.com/engine/reference/builder/#copy +COPY . /opensmtpd + + +# build opensmtpd +RUN ./bootstrap \ + && ./configure --with-gnu-ld \ + --sysconfdir=/etc/mail \ + --with-cflags='-I/usr/include/libressl -L/usr/lib/libressl -Wl,-rpath=/usr/lib/libressl' \ + --with-path-empty=/var/lib/opensmtpd/empty \ + --with-auth-pam \ + && make \ + && make install \ + && cp etc/aliases /etc/mail/aliases + diff --git a/foobar/portable/ci/docker/Dockerfile.ubuntu b/foobar/portable/ci/docker/Dockerfile.ubuntu new file mode 100644 index 00000000..6626033d --- /dev/null +++ b/foobar/portable/ci/docker/Dockerfile.ubuntu @@ -0,0 +1,52 @@ +FROM ubuntu:latest + +# Allow container to expose ports at runtime, if necessary +# https://docs.docker.com/engine/reference/#expose +EXPOSE 25 +EXPOSE 465 +EXPOSE 587 + +# creates /opensmtpd dir and makes all following commands to run in it +# https://docs.docker.com/engine/reference/builder/#workdir +WORKDIR /opensmtpd + +# install necessary packages +RUN apt update \ + && apt install -y --no-install-recommends \ + autoconf \ + automake \ + bison \ + build-essential \ + libevent-dev \ + libssl-dev \ + libtool \ + libpam0g-dev \ + zlib1g-dev + +# create users and directories +RUN mkdir -p /var/lib/opensmtpd/empty \ + && useradd _smtpd \ + --home-dir /var/lib/opensmtpd/empty \ + --no-create-home \ + --shell /bin/false \ + && useradd _smtpq \ + --home-dir /var/lib/opensmtpd/empty \ + --no-create-home \ + --shell /bin/false \ + && mkdir -p /var/spool/smtpd \ + && mkdir -p /var/mail \ + && mkdir -p /etc/mail \ + && chmod 711 /var/spool/smtpd + +# Copy contentes of the repo inside the container +# https://docs.docker.com/engine/reference/builder/#copy +COPY . /opensmtpd + +RUN ./bootstrap \ + && ./configure \ + --with-gnu-ld \ + --sysconfdir=/etc/mail \ + --with-auth-pam \ + && make \ + && make install \ + && cp etc/aliases /etc/mail/aliases diff --git a/foobar/portable/ci/docker/Dockerfile.ubuntu-gcc10 b/foobar/portable/ci/docker/Dockerfile.ubuntu-gcc10 new file mode 100644 index 00000000..2ebbdf58 --- /dev/null +++ b/foobar/portable/ci/docker/Dockerfile.ubuntu-gcc10 @@ -0,0 +1,54 @@ +FROM ubuntu:latest + +# Allow container to expose ports at runtime, if necessary +# https://docs.docker.com/engine/reference/#expose +EXPOSE 25 +EXPOSE 465 +EXPOSE 587 + +# creates /opensmtpd dir and makes all following commands to run in it +# https://docs.docker.com/engine/reference/builder/#workdir +WORKDIR /opensmtpd + +# install necessary packages +RUN apt update \ + && apt install -y --no-install-recommends \ + autoconf \ + automake \ + bison \ + build-essential \ + libevent-dev \ + libssl-dev \ + libtool \ + libpam0g-dev \ + zlib1g-dev \ + gcc-10 + +# create users and directories +RUN mkdir -p /var/lib/opensmtpd/empty \ + && useradd _smtpd \ + --home-dir /var/lib/opensmtpd/empty \ + --no-create-home \ + --shell /bin/false \ + && useradd _smtpq \ + --home-dir /var/lib/opensmtpd/empty \ + --no-create-home \ + --shell /bin/false \ + && mkdir -p /var/spool/smtpd \ + && mkdir -p /var/mail \ + && mkdir -p /etc/mail \ + && chmod 711 /var/spool/smtpd + +# Copy contentes of the repo inside the container +# https://docs.docker.com/engine/reference/builder/#copy +COPY . /opensmtpd + +RUN export CC=gcc-10 CXX=g++-10 +RUN ./bootstrap \ + && ./configure \ + --with-gnu-ld \ + --sysconfdir=/etc/mail \ + --with-auth-pam \ + && make \ + && make install \ + && cp etc/aliases /etc/mail/aliases diff --git a/foobar/portable/ci/scripts/clang_scan.sh b/foobar/portable/ci/scripts/clang_scan.sh new file mode 100755 index 00000000..714d55a7 --- /dev/null +++ b/foobar/portable/ci/scripts/clang_scan.sh @@ -0,0 +1,60 @@ +#!/bin/sh +set -eu + +# Unconditionally go to the root level of the git repo. +# If you invoke it from outside of the repo go to +# the script location first +cd "$(dirname "$0")" +cd "$(git rev-parse --show-toplevel)" + +# Clang Scan script +# +# USAGE: +# - clang must be installed +# - make sure you have clean repository, +# e.g. git clean -ffdx +# - if you want to download github badge set CLANG_SCAN_BADGE_REQUIRED variable +# - Run script from anywhere inside the repository +# ./ci/scripts/clang_scan.sh +# or +# CLANG_SCAN_BADGE_REQUIRED=1 ./ci/scripts/clang_scan.sh +# + +if ! type scan-build > /dev/null; then + echo "clang scan-build is missing" + exit 1 +fi + +# Unconditionally go to the root level of the git repo. +# If you invoke it from outside of the repo go to +# the script location first +cd "$(dirname "$0")" +# This moves us to the root of the repo +cd "$(git rev-parse --show-toplevel)" + +# Get short SHA of the HEAD +sha=$(git rev-parse --short HEAD) + +results_dir=${CLANG_SCAN_RESULTS_DIR:-clang-report} +mkdir -p "$results_dir" + +# Build with scan-build +./bootstrap +./configure +scan-build -o "$results_dir" \ + --keep-empty \ + --html-title="OpenSMTPD $sha" make + + +set -x +# conditionally generate badge +if [ -z "${CLANG_SCAN_BADGE_REQUIRED:-}" ]; then + echo "Skipping badge generation" +else + echo "Generating badge" + . ci/scripts/imports/badge.sh + cd "$results_dir" + cd "$( find . -type d | sort | tail -n1 )" + issues_nr="$( find . -name "report-*" | wc -l)" + download_badge "$issues_nr" "clang analysis" "$(pwd)" 30 +fi diff --git a/foobar/portable/ci/scripts/coverity_scan.sh b/foobar/portable/ci/scripts/coverity_scan.sh new file mode 100755 index 00000000..ab302767 --- /dev/null +++ b/foobar/portable/ci/scripts/coverity_scan.sh @@ -0,0 +1,77 @@ +#!/bin/sh +set -eu + +# Coverity Scan script +# Steps closely follow official documentation https://scan.coverity.com/download +# +# USAGE: provide coverity project token as 'token' environment variable and run +# token=abcdedf ./ci/scripts/coverity_scan.sh +# +# Or uncomment this line and put token here. But do not commit this to git. +# token="" +project_name="OpenSMTPD%2FOpenSMTPD" +cov_analysis_url="https://scan.coverity.com/download/cxx/linux64" +maintainer="ihor@antonovs.family" + +# Unconditionally go to the root level of the git repo. +# If you invoke it from outside of the repo go to +# the script location first +cd "$(dirname "$0")" +# This moves us to the root of the repo +cd "$(git rev-parse --show-toplevel)" + +# Get short SHA of the HEAD +sha=$(git rev-parse --short HEAD) + +# Download Coverity Build Tool if absent +set +x +# shellcheck disable=SC2154 +md5sum -c ./ci/COVERITY.MD5SUM || wget $cov_analysis_url \ + --post-data "token=$token&project=$project_name" \ + -O cov-analysis-linux64.tgz +set -x + +#Check MD5 +md5sum -c ./ci/COVERITY.MD5SUM + +# Extract Coverty Scan Tool +rm -rf ./cov-analysis-linux64 +mkdir -p cov-analysis-linux64 +tar xzf cov-analysis-linux64.tgz --strip 1 -C cov-analysis-linux64 + +# export PATH=$(pwd)/cov-analysis-linux64/bin:$PATH + +# Build with cov-build +./bootstrap +./configure +cov-analysis-linux64/bin/cov-build --dir cov-int make + +# Compress the rusults +tar czvf opensmtpd.tgz cov-int + + +# Submit the result to Coverity Scan +# Some parts are shamelessly taken from: +# https://scan.coverity.com/scripts/travisci_build_coverity_scan.sh +set +x +response=$(curl \ + --silent \ + --write-out "\n%{http_code}\n" \ + --form token="$token" \ + --form email="$maintainer" \ + --form file=@opensmtpd.tgz \ + --form version="portable-$sha" \ + --form description="daily scan" \ + "https://scan.coverity.com/builds?project=$project_name") +set -x + +status_code=$(echo "$response" | sed -n '$p') + +if [ "$status_code" != "200" ]; then + text=$(echo "$response" | sed '$d') + echo -e "Coverity Scan upload failed: $text" + exit 1 +fi + + + diff --git a/foobar/portable/ci/scripts/generate_certs.sh b/foobar/portable/ci/scripts/generate_certs.sh new file mode 100755 index 00000000..a9249c1e --- /dev/null +++ b/foobar/portable/ci/scripts/generate_certs.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +# Generate self-signed SSL certs +# Usage: ./generate_certs.sh + +days=3560 # 10 years +config="$(dirname "$0")/ssl.conf" +cert="open.smtpd.cert" +key="open.smtpd.key" +csr="open.smtpd.csr" + +# Key + CSR generation: +openssl req \ + -new \ + -x509 \ + -newkey rsa:2048 \ + -sha256 \ + -nodes \ + -keyout $key \ + -out $csr \ + -days $days \ + -config "$config" + +# Certificate generation: +openssl req \ + -new \ + -x509 \ + -newkey rsa:2048 \ + -days $days \ + -nodes \ + -config "$config" \ + -keyout $key \ + -out $cert diff --git a/foobar/portable/ci/scripts/imports/badge.sh b/foobar/portable/ci/scripts/imports/badge.sh new file mode 100644 index 00000000..d6cf0b3a --- /dev/null +++ b/foobar/portable/ci/scripts/imports/badge.sh @@ -0,0 +1,72 @@ +#!/bin/sh +# Copyright 2019 Neovim Project Contributors (https://neovim.io/) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Helper functions for getting badges. + +# Get code quality color. +# ${1}: Amount of bugs actually found. +# ${2}: Maximum number of bugs above which color will be red. Defaults to 20. +# ${3}: Maximum number of bugs above which color will be yellow. Defaults to +# $1 / 2. +# Output: 24-bit hexadecimal representation of the color (xxxxxx). +get_code_quality_color() { + bugs=$1 ; shift # shift will fail if there is no argument + max_bugs=${1:-20} + yellow_threshold=${2:-$(( max_bugs / 2 ))} + + red=255 + green=255 + blue=0 + + bugs=$(( bugs < max_bugs ? bugs : max_bugs)) + if test $bugs -ge "$yellow_threshold" ; then + green=$(( 255 - 255 * (bugs - yellow_threshold) / yellow_threshold )) + else + red=$(( 255 * bugs / yellow_threshold )) + fi + + printf "%02x%02x%02x" $red $green $blue +} + +# Get code quality badge. +# ${1}: Amount of bugs actually found. +# ${2}: Badge text. +# ${3}: Directory where to save badge to. +# ${3}: Maximum number of bugs above which color will be red. Defaults to 20. +# ${4}: Maximum number of bugs above which color will be yellow. Defaults to +# $1 / 2. +# Output: 24-bit hexadecimal representation of the color (xxxxxx). +download_badge() { + bugs=$1 ; shift + badge_text="$1" ; shift + reports_dir="$1" ; shift + max_bugs=${1:-20} + yellow_threshold=${2:-$(( max_bugs / 2 ))} + + code_quality_color="$( + get_code_quality_color $bugs $max_bugs $yellow_threshold)" + badge="${badge_text}-${bugs}-${code_quality_color}" + + rm -f "$reports_dir/badge.svg" + + response="$( + curl --tlsv1 "https://img.shields.io/badge/${badge}.svg" \ + -o"$reports_dir/badge.svg" 2>&1)" + + if ! grep -F 'xmlns="http://www.w3.org/2000/svg"' "$reports_dir/badge.svg" ; then + echo "Failed to download badge to $reports_dir: $response" + rm -f "$reports_dir/badge.svg" + fi +} diff --git a/foobar/portable/ci/scripts/ssl.conf b/foobar/portable/ci/scripts/ssl.conf new file mode 100644 index 00000000..eddfb7f8 --- /dev/null +++ b/foobar/portable/ci/scripts/ssl.conf @@ -0,0 +1,23 @@ +[req] +default_bits = 2048 +prompt = no +default_md = sha256 +x509_extensions = v3_req +distinguished_name = dn + + +# Puffy the pufferfish +# https://en.wikipedia.org/wiki/Tetraodontidae +[dn] +C = AZ +ST = Chordata +L = Actinopterygii_Tetraodontiformes +O = Tetraodontoidei_Tetraodontidae +CN = Puffy + +[v3_req] +subjectAltName = @alt_names + +[alt_names] +DNS.1 = puffy.bsd +DNS.2 = puffy.mail diff --git a/foobar/portable/configure.ac b/foobar/portable/configure.ac new file mode 100644 index 00000000..33b929c1 --- /dev/null +++ b/foobar/portable/configure.ac @@ -0,0 +1,2066 @@ +# $Id: configure.ac,v 1.519 2013/03/22 01:49:15 dtucker Exp $ +# +# Copyright (c) 2016 Gilles Chehade <gilles@poolp.org> +# Copyright (c) 1999-2004 Damien 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. + + +# +# WE NEED TO CLEANUP CONFIGURE.AC AND MAKE IT FOLLOW THE +# STANDARD LAYOUT ... +# +# 3.1.3 Standard configure.ac Layout +# +# https://www.gnu.org/software/autoconf/manual/autoconf-2.69/html_node/Autoconf-Input-Layout.html +# + + +# +# AUTOCONF REQUIREMENTS +# +AC_PREREQ(2.69) + + +# +# AC_INIT +# +AC_INIT([OpenSMTPD], + [portable], + [bugs@opensmtpd.org], + [opensmtpd], + [https://www.OpenSMTPD.org]) + +AM_INIT_AUTOMAKE([subdir-objects no-dependencies]) +LT_INIT + +# here we should test for variables set by libtool detection +if test "x$with_pic" != "xno"; then + CFLAGS="$CFLAGS ${pic_flag}" +fi + + +# +# PACKAGE INFORMATION +# +AC_LANG([C]) +AC_CONFIG_MACRO_DIR([m4]) +AC_CONFIG_HEADER([config.h]) +AC_PROG_CC +AC_CANONICAL_HOST +AC_C_BIGENDIAN + + +# +# CHECKS FOR PROGRAMS +# +AC_PROG_CPP +AC_PROG_INSTALL +AC_PROG_LIBTOOL +AC_PATH_PROG([AR], [ar]) +AC_PATH_PROG([CAT], [cat]) +AC_PATH_PROG([CHMOD], [chmod]) +AC_PATH_PROG([CHOWN], [chown]) +AC_PATH_PROG([ZCAT], [zcat]) +AC_PATH_PROG([SED], [sed]) +AC_PATH_PROG([TEST_MINUS_S_SH], [bash]) +AC_PATH_PROG([TEST_MINUS_S_SH], [ksh]) +AC_PATH_PROG([TEST_MINUS_S_SH], [sh]) +AC_PATH_PROG([SH], [sh]) +AC_PATH_PROG([GROFF], [groff]) +AC_PATH_PROG([NROFF], [nroff]) +AC_PATH_PROG([MANDOC], [mandoc]) +AC_PROG_YACC + +AC_SUBST([ZCAT]) + + +if test -z "$AR"; then + AC_MSG_ERROR([*** 'ar' missing, please install or fix your \$PATH ***]) +fi + +if test -z "$LD"; then + LD=$CC +fi +AC_SUBST([LD]) + +dnl select manpage formatter +if test -n "$MANDOC"; then + MANFMT="$MANDOC" +elif test -n "$NROFF"; then + MANFMT="$NROFF -mandoc" +elif test -n "$GROFF"; then + MANFMT="$GROFF -mandoc -Tascii" +else + AC_MSG_WARN([no manpage formatted found]) + MANFMT="false" +fi +AC_SUBST([MANFMT]) + + +# +# CHECKS FOR LIBRARIES +# + + + +# +# CHECKS FOR HEADERS +# +AC_CHECK_HEADERS([ \ + arpa/nameser_compat.h \ + crypt.h \ + dirent.h \ + err.h \ + fcntl.h \ + getopt.h \ + grp.h \ + libgen.h \ + limits.h \ + maillock.h \ + mach/mach_time.h \ + ndir.h \ + netdb.h \ + pam/pam_appl.h \ + paths.h \ + security/pam_appl.h \ + shadow.h \ + sys/cdefs.h \ + sys/dir.h \ + sys/file.h \ + sys/mount.h \ + sys/ndir.h \ + sys/pstat.h \ + sys/statfs.h \ + sys/time.h \ + sys/un.h \ + time.h \ + ucred.h \ + util.h \ + vis.h +]) + +AM_CONDITIONAL([NEED_ERR_H], [test x$HAVE_ERR_H = x1]) +AM_CONDITIONAL([SUPPORT_PATHS_H], [test x$HAVE_PATHS_H = x1]) + +# NetBSD requires sys/types.h before login_cap.h +AC_CHECK_HEADERS([login_cap.h], [], [], [ +#include <sys/types.h> +]) + +# older BSDs need sys/param.h before sys/mount.h +AC_CHECK_HEADERS([sys/mount.h], [], [], [ +#include <sys/param.h> +]) + +AC_CHECK_HEADERS([bsd/libutil.h libutil.h]) + +AC_CHECK_HEADER([fts.h], + [], + [AC_MSG_ERROR([*** fts.h missing - please install libfts ***])], + [ +#include <sys/types.h> +#include <sys/stat.h> +]) + +need_libasr=no +AC_CHECK_HEADER([asr.h], + [], + [need_libasr=yes], + [ +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +]) +AM_CONDITIONAL([NEED_LIBASR], [test x"$need_libasr" = x"yes"]) + +# +# CHECKS FOR TYPES +# +AC_CHECK_TYPES([long long, unsigned long long, long double, u_int, u_char]) +AC_CHECK_SIZEOF([short int], [2]) +AC_CHECK_SIZEOF([int], [4]) +AC_CHECK_SIZEOF([long int], [4]) +AC_CHECK_SIZEOF([long long int], [8]) + +AC_TYPE_INT8_T +AC_TYPE_INT16_T +AC_TYPE_INT32_T +AC_TYPE_INT64_T +AC_TYPE_UINT8_T +AC_TYPE_UINT16_T +AC_TYPE_UINT32_T +AC_TYPE_UINT64_T +AC_TYPE_INTPTR_T +AC_TYPE_INTMAX_T +AC_TYPE_UINTPTR_T +AC_TYPE_UINTMAX_T +AC_TYPE_SIZE_T +AC_TYPE_SSIZE_T +AC_TYPE_OFF_T +AC_TYPE_MODE_T +AC_TYPE_PID_T +AC_TYPE_UID_T + +TYPE_SOCKLEN_T + +AC_CHECK_TYPES([sig_atomic_t], [], [], [ +#include <signal.h> +]) +AC_CHECK_TYPES([fsblkcnt_t, fsfilcnt_t], [], [], [ +#include <sys/types.h> +#ifdef HAVE_SYS_BITYPES_H +#include <sys/bitypes.h> +#endif +#ifdef HAVE_SYS_STATFS_H +#include <sys/statfs.h> +#endif +#ifdef HAVE_SYS_STATVFS_H +#include <sys/statvfs.h> +#endif +]) + +AC_CHECK_TYPES([in_addr_t, in_port_t], [], [], [ +#include <sys/types.h> +#include <netinet/in.h> +]) + +AC_CHECK_TYPES([sa_family_t], [], [], [ +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +]) + +AC_CHECK_TYPES([struct timespec]) +AC_CHECK_TYPES([struct ifgroupreq]) +AC_CHECK_TYPES([struct sockaddr_storage], [], [], [ +#include <sys/types.h> +#include <sys/socket.h> +]) +AC_CHECK_TYPES([struct sockaddr_in6], [], [], [ +#include <sys/types.h> +#include <netinet/in.h> +]) +AC_CHECK_TYPES([struct in6_addr], [], [], [ +#include <sys/types.h> +#include <netinet/in.h> +]) +AC_CHECK_TYPES([struct addrinfo], [], [], [ +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +]) +AC_CHECK_TYPES([struct timeval], [], [], [ +#include <sys/time.h> +]) + +AC_CHECK_DECL([LLONG_MAX], [have_llong_max=1], , [#include <limits.h>]) + + +# +# CHECKS FOR STRUCTURES +# +AC_CHECK_MEMBERS([struct sockaddr_in6.sin6_scope_id], [], [], [ +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#include <netinet/in.h> +]) + +AC_CHECK_MEMBERS([struct passwd.pw_gecos, struct passwd.pw_class, +struct passwd.pw_change, struct passwd.pw_expire], +[], [], [ +#include <sys/types.h> +#include <pwd.h> +]) + +AC_CHECK_MEMBERS([struct stat.st_flags], , , + [ #include <sys/types.h> + #include <sys/stat.h> ] +) + +AC_CHECK_MEMBERS([struct stat.st_mtim], , , + [ #include <sys/types.h> + #include <sys/stat.h> ] +) + +AC_CHECK_MEMBERS([struct stat.st_mtimespec], , , + [ #include <sys/types.h> + #include <sys/stat.h> ] +) + +AC_CHECK_MEMBERS([struct sockaddr.sa_len], , , + [ #include <netdb.h> + #include <netinet/in.h> + #include <sys/socket.h> ] +) + +AC_CHECK_MEMBERS([struct sockaddr_storage.ss_len], , , + [ #include <netdb.h> + #include <netinet/in.h> + #include <sys/socket.h> ] +) + +AC_CHECK_MEMBERS([struct sockaddr_in.sin_len], , , + [ #include <netdb.h> + #include <netinet/in.h> + #include <sys/socket.h> ] +) + +AC_CHECK_MEMBERS([struct sockaddr_in6.sin6_len], , , + [ #include <netdb.h> + #include <netinet/in.h> + #include <sys/socket.h> ] +) + +AC_CHECK_MEMBERS([struct statfs.f_favail], , , + [ #include <sys/vfs.h> + #include <sys/statfs.h> ] +) + +AC_CHECK_MEMBERS([struct sockaddr_storage.ss_family], [], [], [ +#include <sys/types.h> +#include <sys/socket.h> +]) + +AC_CHECK_MEMBERS([struct sockaddr_storage.__ss_family], [], [], [ +#include <sys/types.h> +#include <sys/socket.h> +]) + +AC_CHECK_MEMBERS([struct tm.tm_gmtoff, struct tm.tm_zone], [], + [ + AC_CHECK_DECLS([timezone, altzone, tzname], + [], + [ AC_MSG_ERROR([cannot find timezone])], + [ #include <time.h> ] + ) + ], + [ #include <time.h> ] +) + +AC_CHECK_MEMBERS([struct DIR.d_type], [], [], [ +#include <sys/types.h> +#include <dirent.h> +]) + +# +# CHECKS FOR DECLARATIONS +# +AC_CHECK_DECLS([O_NONBLOCK], [], [], [ +#include <sys/types.h> +#ifdef HAVE_SYS_STAT_H +# include <sys/stat.h> +#endif +#ifdef HAVE_FCNTL_H +# include <fcntl.h> +#endif +]) + +AC_CHECK_DECLS([AF_LOCAL, PF_LOCAL], [], [], [ +#include <sys/socket.h> +]) + +AC_CHECK_DECLS([IPPORT_HILASTAUTO], [], [], [ +#include <netinet/in.h> +]) + +AC_CHECK_DECLS([WAIT_MYPGRP], [], [], [ +#include <sys/wait.h> +]) + +AC_CHECK_DECLS([writev], [], [], [ +#include <sys/types.h> +#include <sys/uio.h> +#include <unistd.h> +]) + +AC_CHECK_DECLS([LOG_PERROR], [], [], [ +#include <syslog.h> +]) + + +# +# CHECKS FOR COMPILER CHARACTERISTICS +# +AC_C_INLINE + + +AC_ARG_WITH([libs], + [ --with-libs Specify additional libraries to link with], + [ + if test -n "$withval" -a "$withval" != "no" -a "${withval}" != "yes"; then + LIBS="$LIBS $withval" + fi + ] +) +# +# CHECKS FOR LIBRARY FUNCTIONS +# +AC_SEARCH_LIBS([basename], + [gen], + [ + AC_DEFINE([HAVE_BASENAME], [1], + [Define if you have the basename() function.]) + ]) + +AC_SEARCH_LIBS([closefrom], + [gen], + [ + AC_DEFINE([HAVE_CLOSEFROM], [1], + [Define if you have the closefrom() function.]) + AC_COMPILE_IFELSE( + [ + AC_LANG_PROGRAM([AC_INCLUDES_DEFAULT], [[int res = closefrom(0);]]) + ], + AC_DEFINE(HAVE_CLOSEFROM_INT, 1, [closefrom return int]) + ) + ]) + +AC_SEARCH_LIBS([fmt_scaled], + [util bsd], + [ + AC_DEFINE([HAVE_FMT_SCALED], [1], + [Define if you have the fmt_scaled() function.]) + ]) + +AC_SEARCH_LIBS([dirname], + [gen], + [ + AC_DEFINE([HAVE_DIRNAME], [1], + [Define if you have the dirname() function.]) + ]) + +AC_SEARCH_LIBS([inet_net_pton], + [resolv bsd], + [ + AC_DEFINE([HAVE_INET_NET_PTON], [1], + [Define if you have the inet_net_pton() function.]) + ]) + +AC_SEARCH_LIBS([clock_gettime], + [rt], + [ + AC_DEFINE([HAVE_CLOCK_GETTIME], [1], + [Define if you have the clock_gettime() function.]) + ]) + +AC_SEARCH_LIBS([fts_open], + [fts], + [ + AC_DEFINE([HAVE_FTS_OPEN], [1], + [Define if you have the fts_open() function.]) + ]) + +AC_SEARCH_LIBS([daemon], + [bsd], + [ + AC_DEFINE([HAVE_DAEMON], [1], + [Define if you have the daemon() function.]) + ]) + +AC_SEARCH_LIBS([fparseln], + [util], + [ + AC_DEFINE([HAVE_FPARSELN], [1], + [Define if you have the fparseln() function.]) + ]) + +AC_SEARCH_LIBS([res_hnok], + [resolv], + [ + AC_DEFINE([HAVE_RES_HNOK], [1], + [Define if you have the res_hnok() function.]) + ]) + +AC_SEARCH_LIBS([res_randomid], + [resolv], + [ + AC_DEFINE([HAVE_RES_RANDOMID], [1], + [Define if you have the res_randomid() function.]) + ]) + +AC_SEARCH_LIBS([res_9_b64_ntop], + [resolv], + [ + AC_DEFINE([HAVE_RES_9_B64_NTOP], [1], + [Define if you have the res_9_b64_ntop() function.]) + ]) + +AC_SEARCH_LIBS([__b64_pton], + [resolv], + [ + AC_DEFINE([HAVE___B64_PTON], [1], + [Define if you have the __b64_pton() function.]) + ]) + +AC_SEARCH_LIBS([b64_pton], + [resolv], + [ + AC_DEFINE([HAVE_B64_PTON], [1], + [Define if you have the b64_pton() function.]) + ]) + +AC_SEARCH_LIBS([__b64_ntop], + [resolv], + [ + AC_DEFINE([HAVE___B64_NTOP], [1], + [Define if you have the b64_ntop() function.]) + ]) + +AC_SEARCH_LIBS([b64_ntop], + [resolv], + [ + AC_DEFINE([HAVE_B64_NTOP], [1], + [Define if you have the b64_ntop() function.]) + ]) + +AC_SEARCH_LIBS([setsockopt], + [socket], + [ + AC_DEFINE([HAVE_SETSOCKOPT], [1], + [Define if you have the setsockopt() function.]) + ]) + +AC_SEARCH_LIBS([crypt], + [crypt], + [ + AC_DEFINE([HAVE_CRYPT], [1], + [Define if you have the crypt() function.]) + ]) + +AC_SEARCH_LIBS([imsg_init], + [util], + [ + AC_DEFINE([HAVE_IMSG], [1], + [Define if you have the imsg framework.]) + ]) + +AC_SEARCH_LIBS([event_asr_run], + [event], + [ + AC_DEFINE([HAVE_EVENT_ASR_RUN], [1], + [Define if you have the event_asr_run() function.]) + ]) + +AC_CHECK_FUNCS([ \ + asprintf \ + arc4random \ + bcopy \ + calloc_conceal \ + chflags \ + crypt_checkpass \ + dirfd \ + err \ + errc \ + errx \ + explicit_bzero \ + fchflags \ + fgetln \ + flock \ + freeaddrinfo \ + freezero \ + getaddrinfo \ + getdtablesize \ + getdtablecount \ + getline \ + getnameinfo \ + gettimeofday \ + getopt \ + getpeereid \ + getpeerucred \ + getspnam \ + inet_aton \ + inet_ntoa \ + inet_ntop \ + malloc_conceal \ + memmove \ + nanosleep \ + nsleep \ + pidfile \ + pledge \ + reallocarray \ + recallocarray \ + res_hnok \ + res_randomid \ + setenv \ + seteuid \ + setegid \ + setproctitle \ + setregid \ + setreuid \ + setresuid \ + setresgid \ + setsid \ + signal \ + sigaction \ + snprintf \ + socketpair \ + strdup \ + strerror \ + strlcat \ + strlcpy \ + strmode \ + strndup \ + strnlen \ + strnvis \ + strtonum \ + sysconf \ + tcgetpgrp \ + time \ + usleep \ + vasprintf \ + vsnprintf \ + waitpid \ + warn \ + warnx \ +]) + +AC_CHECK_DECL([strsep], + [AC_CHECK_FUNCS([strsep])], + [], + [ +#ifdef HAVE_STRING_H +# include <string.h> +#endif + ]) + + +# These functions might be found in libressl +AC_CHECK_DECLS([strlcat, strlcpy], + [], + [], + []) + +# +# CHECKS FOR SYSTEM SERVICES +# +AC_MSG_CHECKING([for /proc/pid/fd directory]) +if test -d "/proc/$$/fd"; then + AC_DEFINE([HAVE_PROC_PID], [1], [Define if you have /proc/$pid/fd]) + AC_MSG_RESULT([yes]) +else + AC_MSG_RESULT([no]) +fi + + +# +# AC_CONFIG_FILES +# + +# +# AC_OUTPUT +# + + +### +### EVERYTHING BELOW MUST BE CLEANED AND MOVED ABOVE +### + +#l150 (without _FORTIFY_SOURCE=2) +if test "$GCC" = "yes" -o "$GCC" = "egcs"; then + OSSH_CHECK_CFLAG_COMPILE([-Qunused-arguments]) + OSSH_CHECK_CFLAG_COMPILE([-Wunknown-warning-option]) + OSSH_CHECK_CFLAG_COMPILE([-Wall]) + OSSH_CHECK_CFLAG_COMPILE([-Wpointer-arith]) + OSSH_CHECK_CFLAG_COMPILE([-Wuninitialized]) + OSSH_CHECK_CFLAG_COMPILE([-Wsign-compare]) + OSSH_CHECK_CFLAG_COMPILE([-Wformat-security]) + OSSH_CHECK_CFLAG_COMPILE([-Wsizeof-pointer-memaccess]) + OSSH_CHECK_CFLAG_COMPILE([-Wpointer-sign], [-Wno-pointer-sign]) + OSSH_CHECK_CFLAG_COMPILE([-Wunused-result], [-Wno-unused-result]) + OSSH_CHECK_CFLAG_COMPILE([-fno-strict-aliasing]) +# OSSH_CHECK_CFLAG_COMPILE([-D_FORTIFY_SOURCE=2]) + if test "x$use_toolchain_hardening" = "x1"; then + OSSH_CHECK_LDFLAG_LINK([-Wl,-z,relro]) + OSSH_CHECK_LDFLAG_LINK([-Wl,-z,now]) + OSSH_CHECK_LDFLAG_LINK([-Wl,-z,noexecstack]) + # NB. -ftrapv expects certain support functions to be present in + # the compiler library (libgcc or similar) to detect integer operations + # that can overflow. We must check that the result of enabling it + # actually links. The test program compiled/linked includes a number + # of integer operations that should exercise this. + OSSH_CHECK_CFLAG_LINK([-ftrapv]) + fi + AC_MSG_CHECKING([gcc version]) + GCC_VER=`$CC -v 2>&1 | $AWK '/gcc version /{print $3}'` + case $GCC_VER in + 1.*) no_attrib_nonnull=1 ;; + 2.8* | 2.9*) + no_attrib_nonnull=1 + ;; + 2.*) no_attrib_nonnull=1 ;; + *) ;; + esac + AC_MSG_RESULT([$GCC_VER]) + + AC_MSG_CHECKING([if $CC accepts -fno-builtin-memset]) + saved_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS -fno-builtin-memset" + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include <string.h> ]], + [[ char b[10]; memset(b, 0, sizeof(b)); ]])], + [ AC_MSG_RESULT([yes]) ], + [ AC_MSG_RESULT([no]) + CFLAGS="$saved_CFLAGS" ] + ) + + # -fstack-protector-all doesn't always work for some GCC versions + # and/or platforms, so we test if we can. If it's not supported + # on a given platform gcc will emit a warning so we use -Werror. + if test "x$use_stack_protector" = "x1"; then + for t in -fstack-protector-strong -fstack-protector-all \ + -fstack-protector; do + AC_MSG_CHECKING([if $CC supports $t]) + saved_CFLAGS="$CFLAGS" + saved_LDFLAGS="$LDFLAGS" + CFLAGS="$CFLAGS $t -Werror" + LDFLAGS="$LDFLAGS $t -Werror" + AC_LINK_IFELSE( + [AC_LANG_PROGRAM([[ #include <stdio.h> ]], + [[ + char x[256]; + snprintf(x, sizeof(x), "XXX"); + ]])], + [ AC_MSG_RESULT([yes]) + CFLAGS="$saved_CFLAGS $t" + LDFLAGS="$saved_LDFLAGS $t" + AC_MSG_CHECKING([if $t works]) + AC_RUN_IFELSE( + [AC_LANG_PROGRAM([[ #include <stdio.h> ]], + [[ + char x[256]; + snprintf(x, sizeof(x), "XXX"); + ]])], + [ AC_MSG_RESULT([yes]) + break ], + [ AC_MSG_RESULT([no]) ], + [ AC_MSG_WARN([cross compiling: cannot test]) + break ] + ) + ], + [ AC_MSG_RESULT([no]) ] + ) + CFLAGS="$saved_CFLAGS" + LDFLAGS="$saved_LDFLAGS" + done + fi + + if test -z "$have_llong_max"; then + # retry LLONG_MAX with -std=gnu99, needed on some Linuxes + unset ac_cv_have_decl_LLONG_MAX + saved_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS -std=gnu99" + AC_CHECK_DECL([LLONG_MAX], + [have_llong_max=1], + [CFLAGS="$saved_CFLAGS"], + [#include <limits.h>] + ) + fi +fi + +AC_MSG_CHECKING([if compiler allows __attribute__ on return types]) +AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[ +#include <stdlib.h> +__attribute__((__unused__)) static void foo(void){return;}]], + [[ exit(0); ]])], + [ AC_MSG_RESULT([yes]) ], + [ AC_MSG_RESULT([no]) + AC_DEFINE(NO_ATTRIBUTE_ON_RETURN_TYPE, 1, + [compiler does not accept __attribute__ on return types]) ] +) + +if test "x$no_attrib_nonnull" != "x1"; then + AC_DEFINE([HAVE_ATTRIBUTE__NONNULL__], [1], [Have attribute nonnull]) +fi + +AC_ARG_WITH([rpath], + [ --without-rpath Disable auto-added -R linker paths], + [ + if test "x$withval" = "xno"; then + need_dash_r="" + fi + if test "x$withval" = "xyes"; then + need_dash_r=1 + fi + ] +) + + +AC_ARG_WITH([cflags], + [ --with-cflags Specify additional flags to pass to compiler], + [ + if test -n "$withval" -a "$withval" != "no" -a "${withval}" != "yes"; then + CFLAGS="$CFLAGS $withval" + fi + ] +) +AC_ARG_WITH([cppflags], + [ --with-cppflags Specify additional flags to pass to preprocessor] , + [ + if test -n "$withval" -a "$withval" != "no" -a "${withval}" != "yes"; then + CPPFLAGS="$CPPFLAGS $withval" + fi + ] +) +AC_ARG_WITH([ldflags], + [ --with-ldflags Specify additional flags to pass to linker], + [ + if test -n "$withval" -a "$withval" != "xno" -a "${withval}" != "yes"; then + LDFLAGS="$LDFLAGS $withval" + fi + ] +) +AC_ARG_WITH([Werror], + [ --with-Werror Build main code with -Werror], + [ + if test -n "$withval" -a "$withval" != "no"; then + werror_flags="-Werror" + if test "${withval}" != "yes"; then + werror_flags="$withval" + fi + fi + ] +) + + + +AC_ARG_WITH([pie], + [ --with-pie Build Position Independent Executables if possible], [ + if test "x$withval" = "xno"; then + use_pie=no + fi + if test "x$withval" = "xyes"; then + use_pie=yes + fi + ] +) +if test -z "$use_pie"; then + use_pie=no +fi +if test "x$use_toolchain_hardening" != "x1" -a "x$use_pie" = "xauto"; then + # Turn off automatic PIE when toolchain hardening is off. + use_pie=no +fi +if test "x$use_pie" = "xauto"; then + # Automatic PIE requires gcc >= 4.x + AC_MSG_CHECKING([for gcc >= 4.x]) + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +#if !defined(__GNUC__) || __GNUC__ < 4 +#error gcc is too old +#endif +]])], + [ AC_MSG_RESULT([yes]) ], + [ AC_MSG_RESULT([no]) + use_pie=no ] +) +fi +if test "x$use_pie" != "xno"; then + SAVED_CFLAGS="$CFLAGS" + SAVED_LDFLAGS="$LDFLAGS" + OSSH_CHECK_CFLAG_COMPILE([-fPIE]) + OSSH_CHECK_LDFLAG_LINK([-pie]) + # We use both -fPIE and -pie or neither. + AC_MSG_CHECKING([whether both -fPIE and -pie are supported]) + if echo "x $CFLAGS" | grep ' -fPIE' >/dev/null 2>&1 && \ + echo "x $LDFLAGS" | grep ' -pie' >/dev/null 2>&1 ; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + CFLAGS="$SAVED_CFLAGS" + LDFLAGS="$SAVED_LDFLAGS" + fi +fi + + + + + + +#l432 (customized) +# Check for some target-specific stuff + +case "$host" in +*-*-darwin*) + use_pie=auto + AC_MSG_CHECKING([if we have working getaddrinfo]) + AC_RUN_IFELSE([AC_LANG_SOURCE([[ +#include <stdlib.h> +#include <mach-o/dyld.h> +main() { if (NSVersionOfRunTimeLibrary("System") >= (60 << 16)) + exit(0); + else + exit(1); +} + ]])], + [AC_MSG_RESULT([working])], + [AC_MSG_RESULT([buggy]) + AC_DEFINE([BROKEN_GETADDRINFO], [1], + [getaddrinfo is broken (if present)]) + ], + [AC_MSG_RESULT([assume it is working])]) + AC_DEFINE([SETEUID_BREAKS_SETUID], [1], [define if seteuid breaks setuid]) + AC_DEFINE([BROKEN_SETREUID], [1], [define if setreuid is broken]) + AC_DEFINE([BROKEN_SETREGID], [1], [define if setregid is broken]) + AC_DEFINE([BROKEN_GLOB], [1], [OS X glob does not do what we expect]) + AC_DEFINE([SPT_TYPE], [SPT_REUSEARGV], + [Define to a Set Process Title type if your system is + supported by bsd-setproctitle.c]) + AC_DEFINE([BROKEN_STRNVIS], [1], + [OSX strnvis argument order is swapped compared to OpenBSD]) + BROKEN_STRNVIS=1 + ;; +*-*-dragonfly*) + ;; +*-*-linux* | *-gnu* | *-k*bsd*-gnu* ) + use_pie=auto + CFLAGS="$CFLAGS -D_BSD_SOURCE -D_DEFAULT_SOURCE" + AC_DEFINE([SPT_TYPE], [SPT_REUSEARGV]) + ;; +*-*-netbsd*) + if test "x$withval" != "xno"; then + need_dash_r=1 + fi + AC_DEFINE([BROKEN_STRNVIS], [1], + [NetBSD strnvis argument order is swapped compared to OpenBSD]) + BROKEN_STRNVIS=1 + ;; +*-*-freebsd*) + AC_DEFINE([BROKEN_GLOB], [1], [FreeBSD glob does not do what we need]) + AC_DEFINE([BROKEN_STRNVIS], [1], + [FreeBSD strnvis argument order is swapped compared to OpenBSD]) + BROKEN_STRNVIS=1 + ;; +*-*-openbsd*) + use_pie=auto + AC_DEFINE([HAVE_ATTRIBUTE__SENTINEL__], [1], [OpenBSD's gcc has sentinel]) + AC_DEFINE([HAVE_ATTRIBUTE__BOUNDED__], [1], [OpenBSD's gcc has bounded]) + + AC_DEFINE([BROKEN_STRNVIS], [0], + [FreeBSD strnvis argument order is swapped compared to OpenBSD]) + BROKEN_STRNVIS=0 + YACC='yacc' + ASR_LIB= + AC_DEFINE([NOOP_ASR_FREEADDRINFO], [0], [OpenBSD doesn't need ASR_FREEADDRINFO]) + ;; +*-sun-solaris*) + AC_DEFINE([HAVE_M_DATA], [1], [M_DATA is defined in sys/stream.h included by netinet/in.h]) + ;; +esac +AC_SUBST([ASR_LIB]) + + +AC_MSG_CHECKING([compiler and flags for sanity]) +AC_RUN_IFELSE([AC_LANG_PROGRAM([[ +#include <stdio.h> +#include <stdlib.h> ]], [[ exit(0); ]])], + [ AC_MSG_RESULT([yes]) ], + [ + AC_MSG_RESULT([no]) + AC_MSG_ERROR([*** compiler cannot create working executables, check config.log ***]) + ], + [ AC_MSG_WARN([cross compiling: not checking compiler sanity]) ] +) + + + +#l1747 + + + + +# Check for missing getpeereid (or equiv) support +NO_PEERCHECK="" +if test "x$ac_cv_func_getpeereid" != "xyes" -a "x$ac_cv_func_getpeerucred" != "xyes"; then + AC_MSG_CHECKING([whether system supports SO_PEERCRED getsockopt]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#include <sys/types.h> +#include <sys/socket.h>]], [[int i = SO_PEERCRED;]])], + [ AC_MSG_RESULT([yes]) + AC_DEFINE([HAVE_SO_PEERCRED], [1], [Have PEERCRED socket option]) + ], [AC_MSG_RESULT([no]) + NO_PEERCHECK=1 + ]) +fi + +#l4176 (customized s/ssh.1/smtpd/smtpd.8/) +# Options from here on. Some of these are preset by platform above +AC_ARG_WITH([mantype], + [ --with-mantype=man|cat|doc Set man page type], + [ + case "$withval" in + man|cat|doc) + MANTYPE=$withval + ;; + *) + AC_MSG_ERROR([invalid man type: $withval]) + ;; + esac + ] +) +if test -z "$MANTYPE"; then + TestPath="/usr/bin${PATH_SEPARATOR}/usr/ucb" + AC_PATH_PROGS([NROFF], [nroff awf], [/bin/false], [$TestPath]) + if ${NROFF} -mdoc ${srcdir}/smtpd/smtpd.8 >/dev/null 2>&1; then + MANTYPE=doc + elif ${NROFF} -man ${srcdir}/smtpd/smtpd.8 >/dev/null 2>&1; then + MANTYPE=man + else + MANTYPE=cat + fi +fi +AC_SUBST([MANTYPE]) +if test "$MANTYPE" = "doc"; then + mansubdir=man; +else + mansubdir=$MANTYPE; +fi +AC_SUBST([mansubdir]) +#l4207 + + +#l4432 (customized s/pid/sock/) +# Whether to enable BSD auth support +BSD_AUTH_MSG=no +AC_ARG_WITH([auth-bsdauth], + [ --with-auth-bsdauth Enable bsd_auth(3) authentication support], + [ + if test "x$withval" != "xno"; then + AC_DEFINE([BSD_AUTH], [1], + [Define if you have BSD auth support]) + BSD_AUTH_MSG=yes + fi + ] +) + + +#l2757 +# Check for PAM libs +PAM_MSG="no" +USE_PAM_SERVICE=smtpd +AC_ARG_WITH([auth-pam], + [ --with-auth-pam=SERVICE Enable PAM authentication support (default=smtpd)], + [ + if test "x$withval" != "xno"; then + if test -n "$withval" -a "x${withval}" != "xyes"; then + USE_PAM_SERVICE=$withval + fi + + if test "x$ac_cv_header_security_pam_appl_h" != "xyes" -a \ + test "x$ac_cv_header_pam_pam_appl_h" != "xyes"; then + AC_MSG_ERROR([PAM headers not found]) + fi + + saved_LIBS="$LIBS" + AC_CHECK_LIB([dl], [dlopen], , ) + AC_CHECK_LIB([pam], [pam_set_item], , [AC_MSG_ERROR([*** libpam missing])]) + AC_CHECK_FUNCS([pam_getenvlist]) + AC_CHECK_FUNCS([pam_putenv]) + LIBS="$saved_LIBS" + + PAM_MSG="yes" + + SMTPDLIBS="$SMTPDLIBS -lpam" + AC_DEFINE([USE_PAM], [1], + [Define if you want to enable PAM support]) + + if test "x$ac_cv_lib_dl_dlopen" = "xyes"; then + case "$LIBS" in + *-ldl*) + # libdl already in LIBS + ;; + *) + SMTPDLIBS="$SMTPDLIBS -ldl" + ;; + esac + fi + fi + ] +) +AC_DEFINE_UNQUOTED([USE_PAM_SERVICE], ["$USE_PAM_SERVICE"], [pam service]) +AC_SUBST([USE_PAM_SERVICE]) + + +# Check for older PAM +if test "x$PAM_MSG" = "xyes"; then + # Check PAM strerror arguments (old PAM) + AC_MSG_CHECKING([whether pam_strerror takes only one argument]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#include <stdlib.h> +#if defined(HAVE_SECURITY_PAM_APPL_H) +#include <security/pam_appl.h> +#elif defined (HAVE_PAM_PAM_APPL_H) +#include <pam/pam_appl.h> +#endif + ]], [[ +(void)pam_strerror((pam_handle_t *)NULL, -1); + ]])], [AC_MSG_RESULT([no])], [ + AC_DEFINE([HAVE_OLD_PAM], [1], + [Define if you have an old version of PAM + which takes only one argument to pam_strerror]) + AC_MSG_RESULT([yes]) + PAM_MSG="yes (old library)" + + ]) +fi +#l2816 + + +##gilles + +SMTPD_USER=_smtpd +AC_ARG_WITH([user-smtpd], + [ --with-user-smtpd=user Specify non-privileged user for smtpd (default=_smtpd)], + [ + if test -n "$withval" -a "$withval" != "no" -a "${withval}" != "yes"; then + SMTPD_USER=$withval + fi + ] +) +AC_DEFINE_UNQUOTED([SMTPD_USER], ["$SMTPD_USER"], + [non-privileged user for privilege separation]) +AC_SUBST([SMTPD_USER]) + +SMTPD_QUEUE_USER=_smtpq +AC_ARG_WITH([user-queue], + [ --with-user-queue=user Specify non-privileged user for queue process (default=_smtpq)], + [ + if test -n "$withval" -a "$withval" != "no" -a "${withval}" != "yes"; then + SMTPD_QUEUE_USER=$withval + fi + ] +) +AC_DEFINE_UNQUOTED([SMTPD_QUEUE_USER], ["$SMTPD_QUEUE_USER"], + [non-privileged user for queue process]) +AC_SUBST([SMTPD_QUEUE_USER]) + +SMTPD_QUEUE_GROUP=_smtpq +AC_ARG_WITH([group-queue], + [ --with-group-queue=group Specify non-privileged group for offline queue (default=_smtpq)], + [ + if test -n "$withval" -a "$withval" != "no" -a "${withval}" != "yes"; then + SMTPD_QUEUE_GROUP=$withval + fi + ] +) +AC_DEFINE_UNQUOTED([SMTPD_QUEUE_GROUP], ["$SMTPD_QUEUE_GROUP"], + [non-privileged group for queue process]) +AC_SUBST([SMTPD_QUEUE_GROUP]) + +# Where to place spooler +spooldir=/var/spool/smtpd +AC_ARG_WITH([path-queue], + [ --with-path-queue=PATH Specify path to queue directory (default=/var/spool/smtpd)], + [ + if test -n "$withval" -a "$withval" != "no" -a "${withval}" != "yes"; then + spooldir=$withval + if test ! -d $spooldir; then + AC_MSG_WARN([** no $spooldir directory on this system **]) + fi + fi + ] +) + +AC_DEFINE_UNQUOTED([PATH_SPOOL], ["$spooldir"], + [Specify location of spooler]) +AC_SUBST([spooldir]) + + +PRIVSEP_PATH=/var/empty +AC_ARG_WITH([path-empty], + [ --with-path-empty=PATH Specify path to empty directory (default=/var/empty)], + [ + if test -n "$withval" -a "$withval" != "no" -a "${withval}" != "yes"; then + PRIVSEP_PATH=$withval + fi + ] +) +AC_SUBST([PRIVSEP_PATH]) +#l4022 + +#l4066 +dnl # --with-maildir=/path/to/mail gets top priority. +dnl # if maildir is set in the platform case statement above we use that. +dnl # Otherwise we run a program to get the dir from system headers. +dnl # We first look for _PATH_MAILDIR then MAILDIR then _PATH_MAIL +dnl # If we find _PATH_MAILDIR we do nothing because that is what +dnl # session.c expects anyway. Otherwise we set to the value found +dnl # stripping any trailing slash. If for some strage reason our program +dnl # does not find what it needs, we default to /var/spool/mail. +# Check for mail directory +AC_ARG_WITH([path-mbox], + [ --with-path-mbox=PATH Specify path to mbox directory (default=/var/spool/mail)], + [ + if test -n "$withval" -a "$withval" != "no" -a "${withval}" != "yes"; then + AC_DEFINE_UNQUOTED([MAIL_DIRECTORY], ["$withval"], + [Set this to your mail directory if you do not have _PATH_MAILDIR]) + fi + ],[ + if test -n "$maildir"; then + AC_DEFINE_UNQUOTED([MAIL_DIRECTORY], ["$maildir"]) + else + AC_MSG_CHECKING([system mail directory]) + AC_RUN_IFELSE( + [AC_LANG_PROGRAM([[ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#ifdef HAVE_PATHS_H +#include <paths.h> +#endif +#ifdef HAVE_MAILLOCK_H +#include <maillock.h> +#endif +#define DATA "conftest.maildir" + ]], [[ + FILE *fd; + int rc; + + fd = fopen(DATA,"w"); + if(fd == NULL) + exit(1); + +#if defined (_PATH_MAILDIR) + if ((rc = fprintf(fd ,"_PATH_MAILDIR:%s\n", _PATH_MAILDIR)) <0) + exit(1); +#elif defined (MAILDIR) + if ((rc = fprintf(fd ,"MAILDIR:%s\n", MAILDIR)) <0) + exit(1); +#elif defined (_PATH_MAIL) + if ((rc = fprintf(fd ,"_PATH_MAIL:%s\n", _PATH_MAIL)) <0) + exit(1); +#else + exit (2); +#endif + + exit(0); + ]])], + [ + maildir_what=`awk -F: '{print $1}' conftest.maildir` + maildir=`awk -F: '{print $2}' conftest.maildir \ + | sed 's|/$||'` + AC_MSG_RESULT([$maildir from $maildir_what]) + if test "x$maildir_what" != "x_PATH_MAILDIR"; then + AC_DEFINE_UNQUOTED([MAIL_DIRECTORY], ["$maildir"]) + fi + ], + [ + if test "X$ac_status" = "X2"; then +# our test program didn't find it. Default to /var/spool/mail + AC_MSG_RESULT([/var/spool/mail]) + AC_DEFINE_UNQUOTED([MAIL_DIRECTORY], ["/var/spool/mail"]) + else + AC_MSG_RESULT([*** not found ***]) + fi + ], + [ + AC_MSG_WARN([cross compiling: use --with-maildir=/path/to/mail]) + ] + ) + fi + ] +) # maildir +#l4146 + +# Where to place smtpd.sock +sockdir=/var/run +# make sure the directory exists +if test ! -d $sockdir; then + sockdir=`eval echo ${sysconfdir}` + case $sockdir in + NONE/*) sockdir=`echo $sockdir | sed "s~NONE~$ac_default_prefix~"` ;; + esac +fi + +AC_ARG_WITH([path-socket], + [ --with-path-socket=PATH Specify path to smtpd.sock directory (default=/var/run)], + [ + if test -n "$withval" -a "$withval" != "no" -a "${withval}" != "yes"; then + sockdir=$withval + if test ! -d $sockdir; then + AC_MSG_WARN([** no $sockdir directory on this system **]) + fi + fi + ] +) + +AC_DEFINE_UNQUOTED([SMTPD_SOCKDIR], ["$sockdir"], + [Specify location of smtpd.sock]) +AC_SUBST([sockdir]) +#l4470 + +# Where to place smtpd.pid +piddir=/var/run +AC_MSG_CHECKING([system pid directory]) +AC_RUN_IFELSE( + [ + AC_LANG_PROGRAM([[ +#include <stdio.h> +#include <stdlib.h> +#ifdef HAVE_PATHS_H +#include <paths.h> +#endif +#define DATA "conftest.piddir" + ]], + [[ +#ifdef _PATH_VARRUN +FILE *fd; +int rc; + +if ((fd = fopen(DATA,"w")) == NULL) { exit(1); } +if ((rc = fprintf(fd ,"%s\n", _PATH_VARRUN)) < 0) { exit(2); } +exit(0); +#else +exit(-1); +#endif + ]]) + ], [ + piddir=`cat conftest.piddir` + AC_MSG_RESULT([$piddir from paths.h]) + ], + [ + AC_MSG_RESULT([$piddir from default value]) + ], + [ + AC_MSG_RESULT([$piddir from default value]) + ] +) + +AC_ARG_WITH([path-pidfile], + [ --with-path-pidfile=PATH Specify path to smtpd.pid directory (default=/var/run)], + [ + if test -n "$withval" -a "$withval" != "no" -a "${withval}" != "yes"; then + piddir=$withval + fi + ] +) + +AC_DEFINE_UNQUOTED([SMTPD_PIDDIR], ["$piddir"], [Specify location of smtpd.pid]) +AC_SUBST([piddir]) + +CA_FILE=/etc/ssl/cert.pem +AC_ARG_WITH([path-CAfile], + [ --with-path-CAfile=FILE Specify path to CA certificate (default=/etc/ssl/cert.pem)], + [ + if test -n "$withval" -a "$withval" != "no" -a "${withval}" != "yes"; then + CA_FILE=$withval + fi + ] +) +AC_SUBST([CA_FILE]) + + + + + + +# compute LLONG_MIN and LLONG_MAX if we don't know them. +if test -z "$have_llong_max"; then + AC_MSG_CHECKING([for max value of long long]) + AC_RUN_IFELSE( + [AC_LANG_PROGRAM([[ +#include <stdio.h> +#include <stdlib.h> +/* Why is this so damn hard? */ +#ifdef __GNUC__ +# undef __GNUC__ +#endif +#define __USE_ISOC99 +#include <limits.h> +#define DATA "conftest.llminmax" +#define my_abs(a) ((a) < 0 ? ((a) * -1) : (a)) + +/* + * printf in libc on some platforms (eg old Tru64) does not understand %lld so + * we do this the hard way. + */ +static int +fprint_ll(FILE *f, long long n) +{ + unsigned int i; + int l[sizeof(long long) * 8]; + + if (n < 0) + if (fprintf(f, "-") < 0) + return -1; + for (i = 0; n != 0; i++) { + l[i] = my_abs(n % 10); + n /= 10; + } + do { + if (fprintf(f, "%d", l[--i]) < 0) + return -1; + } while (i != 0); + if (fprintf(f, " ") < 0) + return -1; + return 0; +} + ]], [[ + FILE *f; + long long i, llmin, llmax = 0; + + if((f = fopen(DATA,"w")) == NULL) + exit(1); + +#if defined(LLONG_MIN) && defined(LLONG_MAX) + fprintf(stderr, "Using system header for LLONG_MIN and LLONG_MAX\n"); + llmin = LLONG_MIN; + llmax = LLONG_MAX; +#else + fprintf(stderr, "Calculating LLONG_MIN and LLONG_MAX\n"); + /* This will work on one's complement and two's complement */ + for (i = 1; i > llmax; i <<= 1, i++) + llmax = i; + llmin = llmax + 1LL; /* wrap */ +#endif + + /* Sanity check */ + if (llmin + 1 < llmin || llmin - 1 < llmin || llmax + 1 > llmax + || llmax - 1 > llmax || llmin == llmax || llmin == 0 + || llmax == 0 || llmax < LONG_MAX || llmin > LONG_MIN) { + fprintf(f, "unknown unknown\n"); + exit(2); + } + + if (fprint_ll(f, llmin) < 0) + exit(3); + if (fprint_ll(f, llmax) < 0) + exit(4); + if (fclose(f) < 0) + exit(5); + exit(0); + ]])], + [ + llong_min=`$AWK '{print $1}' conftest.llminmax` + llong_max=`$AWK '{print $2}' conftest.llminmax` + + AC_MSG_RESULT([$llong_max]) + AC_DEFINE_UNQUOTED([LLONG_MAX], [${llong_max}LL], + [max value of long long calculated by configure]) + AC_MSG_CHECKING([for min value of long long]) + AC_MSG_RESULT([$llong_min]) + AC_DEFINE_UNQUOTED([LLONG_MIN], [${llong_min}LL], + [min value of long long calculated by configure]) + ], + [ + AC_MSG_RESULT([not found]) + ], + [ + AC_MSG_WARN([cross compiling: not checking]) + ] + ) +fi + + + + + +#l3561 + + +dnl make sure we're using the real structure members and not defines +AC_CACHE_CHECK([for msg_accrights field in struct msghdr], + ac_cv_have_accrights_in_msghdr, [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <stdlib.h> + ]], [[ +#ifdef msg_accrights +#error "msg_accrights is a macro" +exit(1); +#endif +struct msghdr m; +m.msg_accrights = 0; +exit(0); + ]])], + [ ac_cv_have_accrights_in_msghdr="yes" ], + [ ac_cv_have_accrights_in_msghdr="no" ] + ) +]) +if test "x$ac_cv_have_accrights_in_msghdr" = "xyes"; then + AC_DEFINE([HAVE_ACCRIGHTS_IN_MSGHDR], [1], + [Define if your system uses access rights style + file descriptor passing]) +fi + + +AC_CACHE_CHECK([for msg_control field in struct msghdr], + ac_cv_have_control_in_msghdr, [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <stdlib.h> + ]], [[ +#ifdef msg_control +#error "msg_control is a macro" +exit(1); +#endif +struct msghdr m; +m.msg_control = 0; +exit(0); + ]])], + [ ac_cv_have_control_in_msghdr="yes" ], + [ ac_cv_have_control_in_msghdr="no" ] + ) +]) +if test "x$ac_cv_have_control_in_msghdr" = "xyes"; then + AC_DEFINE([HAVE_CONTROL_IN_MSGHDR], [1], + [Define if your system uses ancillary data style + file descriptor passing]) +fi + +AC_CACHE_CHECK([if libc defines __progname], ac_cv_libc_defines___progname, [ + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include <stdio.h> ]], + [[ extern char *__progname; printf("%s", __progname); ]])], + [ ac_cv_libc_defines___progname="yes" ], + [ ac_cv_libc_defines___progname="no" + ]) +]) +if test "x$ac_cv_libc_defines___progname" = "xyes"; then + AC_DEFINE([HAVE___PROGNAME], [1], [Define if libc defines __progname]) +fi + +AC_CACHE_CHECK([whether $CC implements __FUNCTION__], ac_cv_cc_implements___FUNCTION__, [ + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include <stdio.h> ]], + [[ printf("%s", __FUNCTION__); ]])], + [ ac_cv_cc_implements___FUNCTION__="yes" ], + [ ac_cv_cc_implements___FUNCTION__="no" + ]) +]) +if test "x$ac_cv_cc_implements___FUNCTION__" = "xyes"; then + AC_DEFINE([HAVE___FUNCTION__], [1], + [Define if compiler implements __FUNCTION__]) +fi + +AC_CACHE_CHECK([whether $CC implements __func__], ac_cv_cc_implements___func__, [ + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include <stdio.h> ]], + [[ printf("%s", __func__); ]])], + [ ac_cv_cc_implements___func__="yes" ], + [ ac_cv_cc_implements___func__="no" + ]) +]) +if test "x$ac_cv_cc_implements___func__" = "xyes"; then + AC_DEFINE([HAVE___func__], [1], [Define if compiler implements __func__]) +fi + +AC_CACHE_CHECK([whether va_copy exists], ac_cv_have_va_copy, [ + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ +#include <stdarg.h> +va_list x,y; + ]], [[ va_copy(x,y); ]])], + [ ac_cv_have_va_copy="yes" ], + [ ac_cv_have_va_copy="no" + ]) +]) +if test "x$ac_cv_have_va_copy" = "xyes"; then + AC_DEFINE([HAVE_VA_COPY], [1], [Define if va_copy exists]) +fi + +AC_CACHE_CHECK([whether __va_copy exists], ac_cv_have___va_copy, [ + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ +#include <stdarg.h> +va_list x,y; + ]], [[ __va_copy(x,y); ]])], + [ ac_cv_have___va_copy="yes" ], [ ac_cv_have___va_copy="no" + ]) +]) +if test "x$ac_cv_have___va_copy" = "xyes"; then + AC_DEFINE([HAVE___VA_COPY], [1], [Define if __va_copy exists]) +fi + +AC_CACHE_CHECK([whether getopt has optreset support], + ac_cv_have_getopt_optreset, [ + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include <getopt.h> ]], + [[ extern int optreset; optreset = 0; ]])], + [ ac_cv_have_getopt_optreset="yes" ], + [ ac_cv_have_getopt_optreset="no" + ]) +]) +if test "x$ac_cv_have_getopt_optreset" = "xyes"; then + AC_DEFINE([HAVE_GETOPT_OPTRESET], [1], + [Define if your getopt(3) defines and uses optreset]) +fi +#l3765 + + + + +#l4045 +STRIP_OPT=-s +AC_ARG_ENABLE([strip], + [ --disable-strip Disable calling strip(1) on install], + [ + if test "x$enableval" = "xno"; then + STRIP_OPT= + fi + ] +) +AC_SUBST([STRIP_OPT]) +#l4054 + + + +case "$host" in +*-*-openbsd*) + pkglibexecdir="$libexecdir/smtpd" + ;; +*) + pkglibexecdir="$libexecdir/opensmtpd" + ;; +esac +AC_SUBST([pkglibexecdir]) + + + + + + + + + +#l4742 +dnl Adding -Werror to CFLAGS early prevents configure tests from running. +dnl Add now. +CFLAGS="$CFLAGS $werror_flags" + +AC_SUBST([TEST_MALLOC_OPTIONS], [$TEST_MALLOC_OPTIONS]) + + +AC_EXEEXT +#l4757 + + +# Search for fts +AC_ARG_WITH([libfts], + [ --with-libfts=PATH Specify path to libfts installation (default: none, part of libc)], + [ if test "x$withval" = "xno"; then + AC_MSG_ERROR([*** fts is required ***]) + elif test "x$withval" != "xyes"; then + if test -d "$withval/lib"; then + if test -n "${need_dash_r}"; then + LDFLAGS="-L${withval}/lib -R${withval}/lib ${LDFLAGS}" + else + LDFLAGS="-L${withval}/lib ${LDFLAGS}" + fi + else + if test -n "${need_dash_r}"; then + LDFLAGS="-L${withval} -R${withval} ${LDFLAGS}" + else + LDFLAGS="-L${withval} ${LDFLAGS}" + fi + fi + if test -d "$withval/include"; then + CPPFLAGS="-I${withval}/include ${CPPFLAGS}" + else + CPPFLAGS="-I${withval} ${CPPFLAGS}" + fi + LIBS="-lfts $LIBS" + fi + ] +) + + + +##chl (based on OpenSSL checks, see above) +# Search for libevent +saved_CPPFLAGS="$CPPFLAGS" +saved_LDFLAGS="$LDFLAGS" +AC_ARG_WITH([libevent], + [ --with-libevent=PATH Specify path to libevent installation ], + [ + if test "x$withval" != "xno"; then + case "$withval" in + # Relative paths + ./*|../*) withval="`pwd`/$withval" + esac + if test -d "$withval/lib"; then + if test -n "${need_dash_r}"; then + LDFLAGS="-L${withval}/lib -R${withval}/lib ${LDFLAGS}" + else + LDFLAGS="-L${withval}/lib ${LDFLAGS}" + fi + elif test -d "$withval/lib64"; then + if test -n "${need_dash_r}"; then + LDFLAGS="-L${withval}/lib64 -R${withval}/lib64 ${LDFLAGS}" + else + LDFLAGS="-L${withval}/lib64 ${LDFLAGS}" + fi + else + if test -n "${need_dash_r}"; then + LDFLAGS="-L${withval} -R${withval} ${LDFLAGS}" + else + LDFLAGS="-L${withval} ${LDFLAGS}" + fi + fi + if test -d "$withval/include"; then + CPPFLAGS="-I${withval}/include ${CPPFLAGS}" + else + CPPFLAGS="-I${withval} ${CPPFLAGS}" + fi + need_libevent_autodetect=no + fi + ] +) + +if test "x${need_libevent_autodetect}" != "xno"; then + for path in /usr/local /usr; do + if test -f "${path}/include/event.h"; then + CPPFLAGS="-I${path}/include ${CPPFLAGS}" + LDFLAGS="-L${path}/lib ${LDFLAGS}" + fi + done +fi + +AC_CHECK_HEADER([event.h], ,[AC_MSG_ERROR([*** event.h missing - please install libevent ***])], +[#include <sys/types.h>]) +LIBS="-levent $LIBS" +AC_MSG_CHECKING([if programs using libevent functions will link]) +AC_LINK_IFELSE( + [AC_LANG_PROGRAM([[ + #include <event.h> + ]], [[ + event_base_new(); + ]])], + [ + AC_MSG_RESULT([yes]) + ], + [ + AC_MSG_RESULT([no]) + ] +) + + +#l2174 (customized, bu adding -lssl to LIBS) +# Search for OpenSSL +saved_CPPFLAGS="$CPPFLAGS" +saved_LDFLAGS="$LDFLAGS" +AC_ARG_WITH([libssl], + [ --with-libssl=PATH Specify path to libssl installation ], + [ + if test "x$withval" != "xno"; then + case "$withval" in + # Relative paths + ./*|../*) withval="`pwd`/$withval" + esac + if test -d "$withval/lib"; then + if test -n "${need_dash_r}"; then + LDFLAGS="-L${withval}/lib -R${withval}/lib ${LDFLAGS}" + else + LDFLAGS="-L${withval}/lib ${LDFLAGS}" + fi + elif test -d "$withval/lib64"; then + if test -n "${need_dash_r}"; then + LDFLAGS="-L${withval}/lib64 -R${withval}/lib64 ${LDFLAGS}" + else + LDFLAGS="-L${withval}/lib64 ${LDFLAGS}" + fi + else + if test -n "${need_dash_r}"; then + LDFLAGS="-L${withval} -R${withval} ${LDFLAGS}" + else + LDFLAGS="-L${withval} ${LDFLAGS}" + fi + fi + if test -d "$withval/include"; then + CPPFLAGS="-I${withval}/include ${CPPFLAGS}" + else + CPPFLAGS="-I${withval} ${CPPFLAGS}" + fi + fi + ] +) +## XXX chl -lssl manually added +LIBS="-lcrypto -lssl $LIBS" +AC_TRY_LINK_FUNC([RAND_add], [AC_DEFINE([HAVE_OPENSSL], [1], + [Define if your ssl headers are included + with #include <openssl/header.h>])], + [ + dnl Check default openssl install dir + if test -n "${need_dash_r}"; then + LDFLAGS="-L/usr/local/ssl/lib -R/usr/local/ssl/lib ${saved_LDFLAGS}" + else + LDFLAGS="-L/usr/local/ssl/lib ${saved_LDFLAGS}" + fi + CPPFLAGS="-I/usr/local/ssl/include ${saved_CPPFLAGS}" + AC_CHECK_HEADER([openssl/opensslv.h], , + [AC_MSG_ERROR([*** LibreSSL headers missing - please install first or check config.log ***])]) + AC_TRY_LINK_FUNC([RAND_add], [AC_DEFINE([HAVE_OPENSSL])], + [ + AC_MSG_ERROR([*** Can't find recent LibreSSL libcrypto (see config.log for details) ***]) + ] + ) + ] +) + + +LIBS="-lcrypto -lssl $LIBS" +AC_MSG_CHECKING([whether SSL_CTX_use_certificate_chain_mem is available]) +AC_TRY_LINK_FUNC([SSL_CTX_use_certificate_chain_mem], + [ + AC_DEFINE([HAVE_SSL_CTX_USE_CERTIFICATE_CHAIN_MEM], [1], + [Define if SSL_CTX_use_certificate_chain_mem exists in libssl]) + AC_MSG_RESULT([yes]) + ], + [ AC_MSG_RESULT([no])] +) + +LIBS="-lcrypto -lssl $LIBS" +AC_MSG_CHECKING([whether ECDSA is available]) +AC_TRY_LINK_FUNC([ENGINE_get_ECDSA], + [ + AC_DEFINE([SUPPORT_ECDSA], [1], + [Define if ECDSA is supported]) + AC_MSG_RESULT([yes]) + ], + [ AC_MSG_RESULT([no])] +) + +# Sanity check OpenSSL headers +AC_MSG_CHECKING([whether LibreSSL's headers match the library]) +AC_RUN_IFELSE( + [AC_LANG_PROGRAM([[ +#include <stdlib.h> +#include <string.h> +#include <openssl/opensslv.h> +#include <openssl/crypto.h> + ]], [[ + exit(SSLeay() == OPENSSL_VERSION_NUMBER ? 0 : 1); + ]])], + [ + AC_MSG_RESULT([yes]) + ], + [ + AC_MSG_RESULT([no]) + AC_MSG_ERROR([Your LibreSSL headers do not match your library.]) + ], + [ + AC_MSG_WARN([cross compiling: not checking]) + ] +) + +AC_MSG_CHECKING([if programs using LibreSSL functions will link]) +AC_LINK_IFELSE( + [AC_LANG_PROGRAM([[ #include <openssl/evp.h> ]], + [[ SSLeay_add_all_algorithms(); ]])], + [ + AC_MSG_RESULT([yes]) + ], + [ + AC_MSG_RESULT([no]) + saved_LIBS="$LIBS" + LIBS="$LIBS -ldl" + AC_MSG_CHECKING([if programs using LibreSSL need -ldl]) + AC_LINK_IFELSE( + [AC_LANG_PROGRAM([[ #include <openssl/evp.h> ]], + [[ SSLeay_add_all_algorithms(); ]])], + [ + AC_MSG_RESULT([yes]) + ], + [ + AC_MSG_RESULT([no]) + LIBS="$saved_LIBS" + ] + ) + ] +) + +AC_CHECK_DECL([LIBRESSL_VERSION_NUMBER], , , [#include <openssl/ssl.h>]) + +#l2371 + + +dnl zlib is required +AC_ARG_WITH([libz], + [ --with-libz=PATH Specify path to libz installation], + [ if test "x$withval" = "xno"; then + AC_MSG_ERROR([*** zlib is required ***]) + elif test "x$withval" != "xyes"; then + if test -d "$withval/lib"; then + if test -n "${need_dash_r}"; then + LDFLAGS="-L${withval}/lib -R${withval}/lib ${LDFLAGS}" + else + LDFLAGS="-L${withval}/lib ${LDFLAGS}" + fi + else + if test -n "${need_dash_r}"; then + LDFLAGS="-L${withval} -R${withval} ${LDFLAGS}" + else + LDFLAGS="-L${withval} ${LDFLAGS}" + fi + fi + if test -d "$withval/include"; then + CPPFLAGS="-I${withval}/include ${CPPFLAGS}" + else + CPPFLAGS="-I${withval} ${CPPFLAGS}" + fi + fi ] +) + +AC_CHECK_HEADER([zlib.h], ,[AC_MSG_ERROR([*** zlib.h missing - please install first or check config.log ***])]) +AC_CHECK_LIB([z], [deflate], , + [ + saved_CPPFLAGS="$CPPFLAGS" + saved_LDFLAGS="$LDFLAGS" + save_LIBS="$LIBS" + dnl Check default zlib install dir + if test -n "${need_dash_r}"; then + LDFLAGS="-L/usr/local/lib -R/usr/local/lib ${saved_LDFLAGS}" + else + LDFLAGS="-L/usr/local/lib ${saved_LDFLAGS}" + fi + CPPFLAGS="-I/usr/local/include ${saved_CPPFLAGS}" + LIBS="$LIBS -lz" + AC_TRY_LINK_FUNC([deflate], [AC_DEFINE([HAVE_LIBZ])], + [ + AC_MSG_ERROR([*** zlib missing - please install first or check config.log ***]) + ] + ) + ] +) + + + + + +AC_ARG_WITH([table-db], + [ --with-table-db Enable building of table-db backend (default=no)], + [ + if test "x$withval" = "xyes"; then + use_db_api=1 + else + use_db_api=0 + fi + ] +) + +if test "x$use_db_api" = "x1"; then +# Search for libdb +AC_CHECK_HEADER(db_185.h, [AC_DEFINE([HAVE_DB_185_H], [], [if you have the <db_185.h> header file]) ] , [ +AC_CHECK_HEADER(db.h, [AC_DEFINE([HAVE_DB_H], [], [if you have the <db.h> header file]) ] , [ +AC_CHECK_HEADER(db1/db.h, [AC_DEFINE([HAVE_DB1_DB_H], [], [if you have the <db1/db.h> header file]) ] , [ + AC_MSG_ERROR([*** Can't find Berkeley DB headers (see config.log for details) ***]) +])])]) +fi + +save_LIBS="$LIBS" + +if test "x$use_db_api" = "x1"; then +DB_LIB= + +for libdb in db db1 c; do + AC_CHECK_LIB($libdb, dbopen, [ DB_LIB="$libdb"; break; ], + AC_CHECK_LIB($libdb, __db185_open, [ DB_LIB="$libdb"; break; ])) +done + +if test -z "$DB_LIB"; then + AC_MSG_ERROR([Berkeley DB not found or not built with --enable-185]) +fi + +DB_LIB="-l$DB_LIB" +AC_SUBST([DB_LIB]) +fi + +LIBS="$save_LIBS" + + +AM_CONDITIONAL([HAVE_DB_API], [test "x$use_db_api" = "x1"]) +AM_COND_IF([HAVE_DB_API], [AC_DEFINE([HAVE_DB_API], [1], [Define to 1 if HAVE_DB_API])]) + + + +if test "$need_libasr" = "no" -a "x$ac_cv_search_event_asr_run" = "xno"; then + LIBS="$LIBS -lasr" +fi + + +LIBS="$LIBS ${SMTPDLIBS}" +##end of chl + + +## +#AM_CONDITIONAL([NEED_BASENAME], [test "x$ac_cv_have_basename" != "xyes"]) +AM_CONDITIONAL([NEED_BASE64], [test "x$ac_cv_search_b64_ntop" = "xno" -a "x$ac_cv_search___b64_ntop" = "xno" ]) +AM_CONDITIONAL([NEED_BASENAME], [test "x$ac_cv_search_basename" = "xno"]) +AM_CONDITIONAL([NEED_CLOCK_GETTIME], [test "x$ac_cv_search_clock_gettime" = "xno"]) +AM_CONDITIONAL([NEED_CLOSEFROM], [test "x$ac_cv_search_closefrom" = "xno"]) +AM_CONDITIONAL([NEED_DAEMON], [test "x$ac_cv_search_daemon" = "xno"]) +AM_CONDITIONAL([NEED_DIRNAME], [test "x$ac_cv_search_dirname" = "xno"]) +AM_CONDITIONAL([NEED_EVENT_ASR_RUN], [test "x$ac_cv_search_event_asr_run" = "xno"]) +AM_CONDITIONAL([NEED_FMT_SCALED], [test "x$ac_cv_search_fmt_scaled" = "xno"]) +AM_CONDITIONAL([NEED_FPARSELN], [test "x$ac_cv_search_fparseln" = "xno"]) +AM_CONDITIONAL([NEED_IMSG], [test "x$ac_cv_search_imsg_init" = "xno"]) +AM_CONDITIONAL([NEED_INET_NET_PTON], [test "x$ac_cv_search_inet_net_pton" = "xno"]) + +AM_CONDITIONAL([NEED_ERR], [test "x$ac_cv_func_err" != "xyes"]) +AM_CONDITIONAL([NEED_ERRC], [test "x$ac_cv_func_errc" != "xyes"]) +AM_CONDITIONAL([NEED_CRYPT_CHECKPASS], [test "x$ac_cv_func_crypt_checkpass" != "xyes"]) +AM_CONDITIONAL([NEED_EXPLICIT_BZERO], [test "x$ac_cv_func_explicit_bzero" != "xyes"]) +AM_CONDITIONAL([NEED_FGETLN], [test "x$ac_cv_func_fgetln" != "xyes"]) +AM_CONDITIONAL([NEED_FREEZERO], [test "x$ac_cv_func_freezero" != "xyes"]) +AM_CONDITIONAL([NEED_GETOPT], [test "x$ac_cv_func_getopt" != "xyes"]) +AM_CONDITIONAL([NEED_GETPEEREID], [test "x$ac_cv_func_getpeereid" != "xyes"]) +AM_CONDITIONAL([NEED_NANOSLEEP], [test "x$ac_cv_func_nanosleep" != "xyes"]) +AM_CONDITIONAL([NEED_PIDFILE], [test "x$ac_cv_func_pidfile" != "xyes"]) +AM_CONDITIONAL([NEED_REALLOCARRAY], [test "x$ac_cv_func_reallocarray" != "xyes"]) +AM_CONDITIONAL([NEED_RECALLOCARRAY], [test "x$ac_cv_func_recallocarray" != "xyes"]) +AM_CONDITIONAL([NEED_SETPROCTITLE], [test "x$ac_cv_func_setproctitle" != "xyes"]) +AM_CONDITIONAL([NEED_SETEGID], [test "x$ac_cv_func_setegid" != "xyes"]) +AM_CONDITIONAL([NEED_SETEUID], [test "x$ac_cv_func_seteuid" != "xyes"]) +AM_CONDITIONAL([NEED_SETRESGID], [test "x$ac_cv_func_setresgid" != "xyes"]) +AM_CONDITIONAL([NEED_SETRESUID], [test "x$ac_cv_func_setresuid" != "xyes"]) +AM_CONDITIONAL([NEED_SIGNAL], [test "x$ac_cv_func_signal" != "xyes"]) +AM_CONDITIONAL([NEED_STRERROR], [test "x$ac_cv_func_strerror" != "xyes"]) +AM_CONDITIONAL([NEED_STRLCAT], [test "x$ac_cv_func_strlcat" != "xyes"]) +AM_CONDITIONAL([NEED_STRLCPY], [test "x$ac_cv_func_strlcpy" != "xyes"]) +AM_CONDITIONAL([NEED_STRMODE], [test "x$ac_cv_func_strmode" != "xyes"]) +AM_CONDITIONAL([NEED_STRSEP], [test "x$ac_cv_func_strsep" != "xyes"]) +AM_CONDITIONAL([NEED_STRTONUM], [test "x$ac_cv_func_strtonum" != "xyes"]) +AM_CONDITIONAL([NEED_STRNDUP], [test "x$ac_cv_func_strndup" != "xyes"]) +AM_CONDITIONAL([NEED_STRNLEN], [test "x$ac_cv_func_strnlen" != "xyes"]) +AM_CONDITIONAL([NEED_WAITPID], [test "x$ac_cv_func_waitpid" != "xyes"]) +AM_CONDITIONAL([NEED_VIS], [test "x$ac_cv_func_strnvis" != "xyes" -o "x$BROKEN_STRNVIS" = "x1"]) +AM_CONDITIONAL([NEED_USLEEP], [test "x$ac_cv_func_usleep" != "xyes"]) + +AM_CONDITIONAL([NEED_RES_HNOK], [test "x$ac_cv_search_res_hnok" = "xno" -a x"$ac_cv_func_res_hnok" != "xyes" -a x"$need_libasr" = x"yes"]) +AM_CONDITIONAL([NEED_RES_RANDOMID], [test "x$ac_cv_search_res_randomid" = "xno" -a x"$ac_cv_func_res_randomid" != "xyes" -a x"$need_libasr" = x"yes"]) + +AM_CONDITIONAL([NEED_ARC4RANDOM], [test "x$ac_cv_func_arc4random" != "xyes" -a "x$ac_cv_have_decl_LIBRESSL_VERSION_NUMBER" != "xyes"]) +AM_CONDITIONAL([NEED_SSL_CTX_USE_CERTIFICATE_CHAIN_MEM], [test "x$ac_cv_have_decl_LIBRESSL_VERSION_NUMBER" != "xyes"]) + +AM_CONDITIONAL([NEED_PROGNAME], [test "x$ac_cv_libc_defines___progname" != "xyes"]) +## + + +AC_CONFIG_FILES([Makefile + openbsd-compat/Makefile + mk/Makefile + mk/mail/Makefile + mk/mail/mail.lmtp/Makefile + mk/mail/mail.maildir/Makefile + mk/mail/mail.mboxfile/Makefile + mk/mail/mail.mda/Makefile + mk/smtpd/Makefile + mk/smtpctl/Makefile + mk/smtp/Makefile + contrib/Makefile + contrib/libexec/Makefile + contrib/libexec/mail.local/Makefile + contrib/libexec/lockspool/Makefile + contrib/libexec/encrypt/Makefile + ]) + +#l4761 +AC_OUTPUT diff --git a/foobar/portable/contrib/Makefile.am b/foobar/portable/contrib/Makefile.am new file mode 100644 index 00000000..37a8e73a --- /dev/null +++ b/foobar/portable/contrib/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = libexec diff --git a/foobar/portable/contrib/libexec/Makefile.am b/foobar/portable/contrib/libexec/Makefile.am new file mode 100644 index 00000000..0e3a271f --- /dev/null +++ b/foobar/portable/contrib/libexec/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = mail.local lockspool encrypt diff --git a/foobar/portable/contrib/libexec/encrypt/Makefile.am b/foobar/portable/contrib/libexec/encrypt/Makefile.am new file mode 100644 index 00000000..6ad7b82d --- /dev/null +++ b/foobar/portable/contrib/libexec/encrypt/Makefile.am @@ -0,0 +1,13 @@ +pkglibexec_PROGRAMS = encrypt + +encrypt_SOURCES = encrypt.c +encrypt_SOURCES += $(top_srcdir)/smtpd/log.c + +AM_CPPFLAGS = -I$(top_srcdir)/openbsd-compat + +LIBCOMPAT = $(top_builddir)/openbsd-compat/libopenbsd.a + +LDADD = $(LIBCOMPAT) + +uninstall-hook: + rmdir $(DESTDIR)$(pkglibexecdir) 2> /dev/null || /bin/true diff --git a/foobar/portable/contrib/libexec/encrypt/encrypt.c b/foobar/portable/contrib/libexec/encrypt/encrypt.c new file mode 100644 index 00000000..80275921 --- /dev/null +++ b/foobar/portable/contrib/libexec/encrypt/encrypt.c @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2013 Sunil Nimmagadda <sunil@sunilnimmagadda.com> + * Copyright (c) 2013 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#ifdef HAVE_CRYPT_H +#include <crypt.h> /* needed for crypt() */ +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define PASSWORD_LEN 128 +#define SALT_LEN 16 + +static unsigned char itoa64[] = /* 0 ... 63 => ascii - 64 */ + "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +static void to64(char *, long int, int); +static void print_passwd(const char *); + +int +main(int argc, char *argv[]) +{ + char *line; + size_t linesz; + ssize_t linelen; + + if (argc > 2) { + fprintf(stderr, "usage: encrypt <string>\n"); + return (1); + } + + if (argc == 2) { + print_passwd(argv[1]); + return (0); + } + + line = NULL; + linesz = 0; + while ((linelen = getline(&line, &linesz, stdin)) != -1) { + if (line[linelen - 1] == '\n') + line[linelen - 1] = '\0'; + print_passwd(line); + } + free(line); + + return (0); +} + +void +print_passwd(const char *string) +{ + const char *ids[] = { "2a", "6", "5", "3", "2", "1", NULL }; + const char *id; + char salt[SALT_LEN+1]; + char buffer[PASSWORD_LEN]; + int n; + const char *p; + + for (n = 0; n < SALT_LEN; ++n) + to64(&salt[n], arc4random_uniform(0xff), 1); + salt[SALT_LEN] = '\0'; + + for (n = 0; ids[n]; n++) { + id = ids[n]; + (void)snprintf(buffer, sizeof buffer, "$%s$%s$", id, salt); + if ((p = crypt(string, buffer)) == NULL) + continue; + if (strncmp(p, buffer, strlen(buffer)) != 0) + continue; + printf("%s\n", p); + return; + } + + salt[2] = 0; + printf("%s\n", crypt(string, salt)); +} + +void +to64(char *s, long int v, int n) +{ + while (--n >= 0) { + *s++ = itoa64[v & 0x3f]; + v >>= 6; + } +} diff --git a/foobar/portable/contrib/libexec/lockspool/Makefile.am b/foobar/portable/contrib/libexec/lockspool/Makefile.am new file mode 100644 index 00000000..dacf5386 --- /dev/null +++ b/foobar/portable/contrib/libexec/lockspool/Makefile.am @@ -0,0 +1,20 @@ +pkglibexec_PROGRAMS = lockspool + +lockspool_SOURCES = lockspool.c +lockspool_SOURCES += locking.c +lockspool_SOURCES += $(top_srcdir)/smtpd/log.c + +EXTRA_DIST = mail.local.h pathnames.h + +AM_CPPFLAGS = -I$(top_srcdir)/openbsd-compat -I$(top_srcdir)/mail.local + +LIBCOMPAT = $(top_builddir)/openbsd-compat/libopenbsd.a + +LDADD = $(LIBCOMPAT) + +install-exec-hook: $(CONFIGFILES) $(MANPAGES) + chown root $(DESTDIR)$(pkglibexecdir)/lockspool || true + chmod 4555 $(DESTDIR)$(pkglibexecdir)/lockspool || true + +uninstall-hook: + rmdir $(DESTDIR)$(pkglibexecdir) 2> /dev/null || /bin/true diff --git a/foobar/portable/contrib/libexec/lockspool/locking.c b/foobar/portable/contrib/libexec/lockspool/locking.c new file mode 100644 index 00000000..e4922dd6 --- /dev/null +++ b/foobar/portable/contrib/libexec/lockspool/locking.c @@ -0,0 +1,181 @@ +/* $OpenBSD: locking.c,v 1.14 2020/02/09 14:59:20 millert Exp $ */ + +/* + * Copyright (c) 1996-1998 Theo de Raadt <deraadt@theos.com> + * Copyright (c) 1996-1998 David Mazieres <dm@lcs.mit.edu> + * 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. + * 3. The name of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``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 AUTHORS 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 "includes.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <pwd.h> +#include <syslog.h> +#include <time.h> +#include <unistd.h> +#include <limits.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include "pathnames.h" +#include "mail.local.h" + +static char lpath[PATH_MAX]; + +void +rellock(void) +{ + + if (lpath[0]) + unlink(lpath); +} + +int +getlock(const char *name, struct passwd *pw) +{ + struct stat sb, fsb; + int lfd=-1; + char buf[8*1024]; + int tries = 0; + + (void)snprintf(lpath, sizeof lpath, "%s/%s.lock", + _PATH_MAILDIR, name); + + if (stat(_PATH_MAILDIR, &sb) != -1 && + (sb.st_mode & S_IWOTH) == S_IWOTH) { + /* + * We have a writeable spool, deal with it as + * securely as possible. + */ + time_t ctim = -1; + + seteuid(pw->pw_uid); + if (lstat(lpath, &sb) != -1) + ctim = sb.st_ctime; + while (1) { + /* + * Deal with existing user.lock files + * or directories or symbolic links that + * should not be here. + */ + if (readlink(lpath, buf, sizeof buf-1) != -1) { + if (lstat(lpath, &sb) != -1 && + S_ISLNK(sb.st_mode)) { + seteuid(sb.st_uid); + unlink(lpath); + seteuid(pw->pw_uid); + } + goto again; + } + if ((lfd = open(lpath, O_CREAT|O_WRONLY|O_EXCL|O_EXLOCK, + S_IRUSR|S_IWUSR)) != -1) + break; +again: + if (tries > 10) { + mwarn("%s: %s", lpath, strerror(errno)); + seteuid(0); + return(-1); + } + if (tries > 9 && + (lfd = open(lpath, O_WRONLY|O_EXLOCK, 0)) != -1) { + if (fstat(lfd, &fsb) != -1 && + lstat(lpath, &sb) != -1) { + if (fsb.st_dev == sb.st_dev && + fsb.st_ino == sb.st_ino && + ctim == fsb.st_ctime ) { + seteuid(fsb.st_uid); + baditem(lpath); + seteuid(pw->pw_uid); + } + } + close(lfd); + } + sleep(1U << tries); + tries++; + continue; + } + seteuid(0); + } else { + /* + * Only root can write the spool directory. + */ + while (1) { + if ((lfd = open(lpath, O_CREAT|O_WRONLY|O_EXCL, + S_IRUSR|S_IWUSR)) != -1) + break; + if (tries > 9) { + mwarn("%s: %s", lpath, strerror(errno)); + return(-1); + } + sleep(1U << tries); + tries++; + } + } + return(lfd); +} + +void +baditem(char *path) +{ + char npath[PATH_MAX]; + int fd; + + if (unlink(path) == 0) + return; + snprintf(npath, sizeof npath, "%s/mailXXXXXXXXXX", _PATH_MAILDIR); + if ((fd = mkstemp(npath)) == -1) + return; + close(fd); + if (rename(path, npath) == -1) + unlink(npath); + else + mwarn("nasty spool item %s renamed to %s", path, npath); + /* XXX if we fail to rename, another attempt will happen later */ +} + +void +mwarn(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vsyslog(LOG_ERR, fmt, ap); + va_end(ap); +} + +void +merr(int eval, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vsyslog(LOG_ERR, fmt, ap); + va_end(ap); + exit(eval); +} diff --git a/foobar/portable/contrib/libexec/lockspool/lockspool.1 b/foobar/portable/contrib/libexec/lockspool/lockspool.1 new file mode 100644 index 00000000..ea5524bf --- /dev/null +++ b/foobar/portable/contrib/libexec/lockspool/lockspool.1 @@ -0,0 +1,77 @@ +.\" $OpenBSD: lockspool.1,v 1.14 2019/01/25 00:19:26 millert Exp $ +.\" +.\" Copyright (c) 1998 Todd C. Miller <millert@openbsd.org> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: January 25 2019 $ +.Dt LOCKSPOOL 1 +.Os +.Sh NAME +.Nm lockspool +.Nd lock user's system mailbox +.Sh SYNOPSIS +.Nm lockspool +.Op Ar username +.Sh DESCRIPTION +.Nm +is useful for a client mail program to attain proper locking. +.Nm +obtains a +.Pa username.lock +for the calling user and retains it until stdin is closed or a signal like +.Dv SIGINT , +.Dv SIGTERM , +or +.Dv SIGHUP +is received. +Additionally, the superuser may specify the name of a user in order +to lock a different mailbox. +.Pp +If +.Nm +is able to create the lock file, +.Dq 1 +is written to stdout, otherwise +.Dq 0 +is written and an error message is written to stderr. +.Nm +will try up to 10 times to get the lock (sleeping +for a short period in between tries). +.Pp +Typical usage is for a user mail agent (such as +.Xr mail 1 ) +to open a pipe to +.Nm +when it needs to lock the user's mail spool. +Closing the pipe will cause +.Nm +to release the lock. +.Sh FILES +.Bl -tag -width /var/mail/username.lock -compact +.It Pa /var/mail/username.lock +user's mail lock file +.El +.Sh EXIT STATUS +The +.Nm +utility exits 0 on success, and 1 if an error occurs. +.Sh SEE ALSO +.Xr mail 1 , +.Xr mail.local 8 , +.Xr smtpd 8 +.Sh HISTORY +The +.Nm +program appeared in +.Ox 2.4 . diff --git a/foobar/portable/contrib/libexec/lockspool/lockspool.c b/foobar/portable/contrib/libexec/lockspool/lockspool.c new file mode 100644 index 00000000..9277241b --- /dev/null +++ b/foobar/portable/contrib/libexec/lockspool/lockspool.c @@ -0,0 +1,124 @@ +/* $OpenBSD: lockspool.c,v 1.21 2020/02/09 14:59:20 millert Exp $ */ + +/* + * Copyright (c) 1998 Theo de Raadt <deraadt@theos.com> + * Copyright (c) 1998 Todd C. Miller <millert@openbsd.org> + * 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 ``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 AUTHORS 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 "includes.h" + +#include <signal.h> +#include <pwd.h> +#include <syslog.h> +#include <unistd.h> +#include <errno.h> +#include <stdio.h> +#include <paths.h> +#include <stdlib.h> +#include <poll.h> +#include <err.h> + +#include "mail.local.h" + +void unhold(int); +void usage(void); + +extern char *__progname; + +int +main(int argc, char *argv[]) +{ + struct passwd *pw; + struct pollfd pfd; + ssize_t nread; + char *from, c; + int holdfd; + +#if HAVE_UNVEIL + if (unveil(_PATH_MAILDIR, "rwc") == -1) + err(1, "unveil"); +#endif +#if HAVE_PLEDGE + if (pledge("stdio rpath wpath getpw cpath fattr", NULL) == -1) + err(1, "pledge"); +#endif + + openlog(__progname, LOG_PERROR, LOG_MAIL); + + if (argc != 1 && argc != 2) + usage(); + if (argc == 2 && getuid() != 0) + merr(1, "you must be root to lock someone else's spool"); + + signal(SIGTERM, unhold); + signal(SIGINT, unhold); + signal(SIGHUP, unhold); + signal(SIGPIPE, unhold); + + if (argc == 2) + pw = getpwnam(argv[1]); + else + pw = getpwuid(getuid()); + if (pw == NULL) + exit (1); + from = pw->pw_name; + + holdfd = getlock(from, pw); + if (holdfd == -1) { + write(STDOUT_FILENO, "0\n", 2); + exit (1); + } + write(STDOUT_FILENO, "1\n", 2); + + /* wait for the other end of the pipe to close, then release the lock */ + pfd.fd = STDIN_FILENO; + pfd.events = POLLIN; + do { + if (poll(&pfd, 1, INFTIM) == -1) { + if (errno != EINTR) + break; + } + do { + nread = read(STDIN_FILENO, &c, 1); + } while (nread == 1 || (nread == -1 && errno == EINTR)); + } while (nread == -1 && errno == EAGAIN); + rellock(); + exit (0); +} + +/*ARGSUSED*/ +void +unhold(int signo) +{ + + rellock(); + _exit(0); +} + +void +usage(void) +{ + + merr(1, "usage: %s [username]", __progname); +} diff --git a/foobar/portable/contrib/libexec/lockspool/mail.local.h b/foobar/portable/contrib/libexec/lockspool/mail.local.h new file mode 100644 index 00000000..bc3137cb --- /dev/null +++ b/foobar/portable/contrib/libexec/lockspool/mail.local.h @@ -0,0 +1,42 @@ +/* $OpenBSD: mail.local.h,v 1.7 2020/02/09 14:59:21 millert Exp $ */ + +/*- + * Copyright (c) 1990 The Regents of the University of California. + * 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. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + */ + +void baditem(char *); +int deliver(int, char *, int); +void merr(int, const char *, ...); +void mwarn(const char *, ...); +int getlock(const char *, struct passwd *); +void notifybiff(char *); +void rellock(void); +int storemail(char *); +int lockspool(const char *, struct passwd *); +void unlockspool(void); +void usage(void); diff --git a/foobar/portable/contrib/libexec/lockspool/pathnames.h b/foobar/portable/contrib/libexec/lockspool/pathnames.h new file mode 100644 index 00000000..0a2c2731 --- /dev/null +++ b/foobar/portable/contrib/libexec/lockspool/pathnames.h @@ -0,0 +1,38 @@ +/* $OpenBSD: pathnames.h,v 1.5 2003/06/02 19:38:24 millert Exp $*/ + +/*- + * Copyright (c) 1990 The Regents of the University of California. + * 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. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * from: @(#)pathnames.h 5.3 (Berkeley) 1/17/91 + */ +#ifdef HAVE_PATHS_H +#include <paths.h> +#endif + +#define _PATH_LOCTMP "/tmp/local.XXXXXXXXXX" +#define _PATH_LOCKSPOOL PATH_LIBEXEC"/lockspool" diff --git a/foobar/portable/contrib/libexec/mail.local/Makefile.am b/foobar/portable/contrib/libexec/mail.local/Makefile.am new file mode 100644 index 00000000..bd5211a2 --- /dev/null +++ b/foobar/portable/contrib/libexec/mail.local/Makefile.am @@ -0,0 +1,22 @@ +pkglibexec_PROGRAMS = mail.local + +mail_local_SOURCES = mail.local.c +mail_local_SOURCES += locking.c +mail_local_SOURCES += $(top_srcdir)/smtpd/log.c + +EXTRA_DIST = mail.local.h pathnames.h + +AM_CPPFLAGS = -I$(top_srcdir)/openbsd-compat -DPATH_LIBEXEC=\"$(pkglibexecdir)\" + +LIBCOMPAT = $(top_builddir)/openbsd-compat/libopenbsd.a + +LDADD = $(LIBCOMPAT) + +# need to define _GNU_SOURCE to get: +# EAI_NODATA defined +# {v,}asprintf +# setres{g,u}id +#CFLAGS += -D_GNU_SOURCE + +uninstall-hook: + rmdir $(DESTDIR)$(pkglibexecdir) 2> /dev/null || /bin/true diff --git a/foobar/portable/contrib/libexec/mail.local/locking.c b/foobar/portable/contrib/libexec/mail.local/locking.c new file mode 100644 index 00000000..85a48d5e --- /dev/null +++ b/foobar/portable/contrib/libexec/mail.local/locking.c @@ -0,0 +1,182 @@ +/* $OpenBSD: locking.c,v 1.14 2020/02/09 14:59:20 millert Exp $ */ + +/* + * Copyright (c) 1996-1998 Theo de Raadt <deraadt@theos.com> + * Copyright (c) 1996-1998 David Mazieres <dm@lcs.mit.edu> + * 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. + * 3. The name of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``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 AUTHORS 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 "includes.h" + +#include <sys/types.h> + +#include <sys/stat.h> +#include <fcntl.h> +#include <pwd.h> +#include <syslog.h> +#include <time.h> +#include <unistd.h> +#include <limits.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include "pathnames.h" +#include "mail.local.h" + +static char lpath[PATH_MAX]; + +void +rellock(void) +{ + + if (lpath[0]) + unlink(lpath); +} + +int +getlock(const char *name, struct passwd *pw) +{ + struct stat sb, fsb; + int lfd=-1; + char buf[8*1024]; + int tries = 0; + + (void)snprintf(lpath, sizeof lpath, "%s/%s.lock", + _PATH_MAILDIR, name); + + if (stat(_PATH_MAILDIR, &sb) != -1 && + (sb.st_mode & S_IWOTH) == S_IWOTH) { + /* + * We have a writeable spool, deal with it as + * securely as possible. + */ + time_t ctim = -1; + + seteuid(pw->pw_uid); + if (lstat(lpath, &sb) != -1) + ctim = sb.st_ctime; + while (1) { + /* + * Deal with existing user.lock files + * or directories or symbolic links that + * should not be here. + */ + if (readlink(lpath, buf, sizeof buf-1) != -1) { + if (lstat(lpath, &sb) != -1 && + S_ISLNK(sb.st_mode)) { + seteuid(sb.st_uid); + unlink(lpath); + seteuid(pw->pw_uid); + } + goto again; + } + if ((lfd = open(lpath, O_CREAT|O_WRONLY|O_EXCL|O_EXLOCK, + S_IRUSR|S_IWUSR)) != -1) + break; +again: + if (tries > 10) { + mwarn("%s: %s", lpath, strerror(errno)); + seteuid(0); + return(-1); + } + if (tries > 9 && + (lfd = open(lpath, O_WRONLY|O_EXLOCK, 0)) != -1) { + if (fstat(lfd, &fsb) != -1 && + lstat(lpath, &sb) != -1) { + if (fsb.st_dev == sb.st_dev && + fsb.st_ino == sb.st_ino && + ctim == fsb.st_ctime ) { + seteuid(fsb.st_uid); + baditem(lpath); + seteuid(pw->pw_uid); + } + } + close(lfd); + } + sleep(1U << tries); + tries++; + continue; + } + seteuid(0); + } else { + /* + * Only root can write the spool directory. + */ + while (1) { + if ((lfd = open(lpath, O_CREAT|O_WRONLY|O_EXCL, + S_IRUSR|S_IWUSR)) != -1) + break; + if (tries > 9) { + mwarn("%s: %s", lpath, strerror(errno)); + return(-1); + } + sleep(1U << tries); + tries++; + } + } + return(lfd); +} + +void +baditem(char *path) +{ + char npath[PATH_MAX]; + int fd; + + if (unlink(path) == 0) + return; + snprintf(npath, sizeof npath, "%s/mailXXXXXXXXXX", _PATH_MAILDIR); + if ((fd = mkstemp(npath)) == -1) + return; + close(fd); + if (rename(path, npath) == -1) + unlink(npath); + else + mwarn("nasty spool item %s renamed to %s", path, npath); + /* XXX if we fail to rename, another attempt will happen later */ +} + +void +mwarn(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vsyslog(LOG_ERR, fmt, ap); + va_end(ap); +} + +void +merr(int eval, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vsyslog(LOG_ERR, fmt, ap); + va_end(ap); + exit(eval); +} diff --git a/foobar/portable/contrib/libexec/mail.local/mail.local.8 b/foobar/portable/contrib/libexec/mail.local/mail.local.8 new file mode 100644 index 00000000..330a4473 --- /dev/null +++ b/foobar/portable/contrib/libexec/mail.local/mail.local.8 @@ -0,0 +1,183 @@ +.\" $OpenBSD: mail.local.8,v 1.31 2014/09/16 21:28:51 jmc Exp $ +.\" Copyright (c) 1990 The Regents of the University of California. +.\" 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. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. +.\" +.\" from: @(#)mail.local.8 6.8 (Berkeley) 4/27/91 +.\" +.Dd $Mdocdate: September 16 2014 $ +.Dt MAIL.LOCAL 8 +.Os +.Sh NAME +.Nm mail.local +.Nd store mail in a mailbox +.Sh SYNOPSIS +.Nm mail.local +.Op Fl Ll +.Op Fl f Ar from +.Ar user ... +.Sh DESCRIPTION +.Nm +reads the standard input up to an end-of-file and appends it to each +.Ar user Ns 's +.Pa mail +file. +The +.Ar user +must be a valid user name. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl f Ar from +Specify the sender's name. +.It Fl L +Don't create a +.Pa username.lock +file while locking the spool. +.It Fl l +For compatibility, request that files named +.Pa username.lock +be used for locking. +(This is the default behavior.) +.El +.Pp +Individual mail messages in the mailbox are delimited by an empty +line followed by a line beginning with the string +.Dq "From\&\ " . +A line containing the string +.Dq "From\&\ " , +the sender's name and a timestamp is prepended to each delivered mail message. +A blank line is appended to each message. +A greater-than character +.Pq Ql > +is prepended to any line in the message which could be mistaken for a +.Dq "From\&\ " +delimiter line. +.Pp +Significant efforts have been made to ensure that +.Nm +acts as securely as possible if the spool directory is mode 1777 or 755. +The default of mode 755 is more secure, but it prevents mail clients from using +.Pa username.lock +style locking. +The use of 1777 is more flexible in an NFS shared-spool environment, +so many sites use it. +However, it does carry some risks, such as attackers filling the spool disk. +Some of these problems may be alleviated +by making the spool a separate filesystem, and placing quotas on it. +The use of any mode other than 1777 and 755 for the spool directory is +recommended against but may work properly. +.Pp +The mailbox is always locked using +.Xr flock 2 +while mail is appended. +Unless the +.Fl L +flag is specified, a +.Pa username.lock +file is also used. +.Pp +If the +.Xr biff 1 +service is returned by +.Xr getservbyname 3 , +the biff server is notified of delivered mail. +.Sh ENVIRONMENT +.Bl -tag -width indent +.It Ev TZ +Used to set the appropriate time zone on the timestamp. +.El +.Sh FILES +.Bl -tag -width /tmp/local.XXXXXXXXXX -compact +.It Pa /tmp/local.XXXXXXXXXX +temporary files +.It Pa /var/mail/user +user's mailbox directory +.El +.Sh EXIT STATUS +.Ex -std mail.local +.Sh SEE ALSO +.Xr biff 1 , +.Xr mail 1 , +.Xr flock 2 , +.Xr getservbyname 3 , +.Xr comsat 8 , +.Xr smtpd 8 +.Sh HISTORY +A superset of +.Nm +(handling mailbox reading as well as mail delivery) appeared in +.At v7 +as the program +.Xr mail 1 . +.Sh BUGS +Using quotas in +.Pa /var/mail +can be problematic if using +.Xr sendmail 8 +as an MTA, +since it asks +.Nm +to deliver a message to multiple recipients if possible. +This causes problems in a quota environment since a message may be +delivered to some users but not others due to disk quotas. +Even though the message was delivered to some of the recipients, +.Nm +will exit with an exit code > 0, causing +.Xr sendmail 8 +to attempt redelivery later. +That means that some users will keep getting the same message every time +.Xr sendmail 8 +runs its queue. +This problem does not exist for +.Xr smtpd 8 +users. +.Pp +If you are running +.Xr sendmail 8 +and have disk quotas on +.Pa /var/mail +it is imperative that you unset the +.Dq m +mailer flag for the +.Sq local +mailer. +To do this, locate the line beginning with +.Dq Mlocal +in +.Pa /etc/mail/sendmail.cf +and remove the +.Dq m +from the flags section, denoted by +.Dq F= . +Alternately, you can override the default mailer flags by adding the line: +.Pp +.Dl define(`LOCAL_MAILER_FLAGS', `rn9S')dnl +.Pp +to your +.Dq \.mc +file (this is the source file that is used to generate +.Pa /etc/mail/sendmail.cf ) . diff --git a/foobar/portable/contrib/libexec/mail.local/mail.local.c b/foobar/portable/contrib/libexec/mail.local/mail.local.c new file mode 100644 index 00000000..a574b3fe --- /dev/null +++ b/foobar/portable/contrib/libexec/mail.local/mail.local.c @@ -0,0 +1,392 @@ +/* $OpenBSD: mail.local.c,v 1.39 2020/02/09 14:59:20 millert Exp $ */ + +/*- + * Copyright (c) 1996-1998 Theo de Raadt <deraadt@theos.com> + * Copyright (c) 1996-1998 David Mazieres <dm@lcs.mit.edu> + * Copyright (c) 1990 The Regents of the University of California. + * 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. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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 "includes.h" + +#include <sys/types.h> + +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <netinet/in.h> +#include <sysexits.h> +#include <syslog.h> +#include <fcntl.h> +#include <netdb.h> +#include <pwd.h> +#include <time.h> +#include <unistd.h> +#include <limits.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include "pathnames.h" +#include "mail.local.h" + +int +main(int argc, char *argv[]) +{ + struct passwd *pw; + int ch, fd, eval, lockfile=1; + uid_t uid; + char *from; + + openlog("mail.local", LOG_PERROR, LOG_MAIL); + + from = NULL; + while ((ch = getopt(argc, argv, "lLdf:r:")) != -1) + switch (ch) { + case 'd': /* backward compatible */ + break; + case 'f': + case 'r': /* backward compatible */ + if (from) + merr(EX_USAGE, "multiple -f options"); + from = optarg; + break; + case 'l': + lockfile=1; + break; + case 'L': + lockfile=0; + break; + default: + usage(); + } + argc -= optind; + argv += optind; + + if (!*argv) + usage(); + + /* + * If from not specified, use the name from getlogin() if the + * uid matches, otherwise, use the name from the password file + * corresponding to the uid. + */ + uid = getuid(); + if (!from && (!(from = getlogin()) || + !(pw = getpwnam(from)) || pw->pw_uid != uid)) + from = (pw = getpwuid(uid)) ? pw->pw_name : "???"; + + fd = storemail(from); + for (eval = 0; *argv; ++argv) { + if ((ch = deliver(fd, *argv, lockfile)) != 0) + eval = ch; + } + exit(eval); +} + +int +storemail(char *from) +{ + FILE *fp = NULL; + time_t tval; + int fd, eline; + size_t len; + char *line, *tbuf; + + if ((tbuf = strdup(_PATH_LOCTMP)) == NULL) + merr(EX_OSERR, "unable to allocate memory"); + if ((fd = mkstemp(tbuf)) == -1 || !(fp = fdopen(fd, "w+"))) + merr(EX_OSERR, "unable to open temporary file"); + (void)unlink(tbuf); + free(tbuf); + + (void)time(&tval); + (void)fprintf(fp, "From %s %s", from, ctime(&tval)); + + for (eline = 1, tbuf = NULL; (line = fgetln(stdin, &len));) { + /* We have to NUL-terminate the line since fgetln does not */ + if (line[len - 1] == '\n') + line[len - 1] = '\0'; + else { + /* No trailing newline, so alloc space and copy */ + if ((tbuf = malloc(len + 1)) == NULL) + merr(EX_OSERR, "unable to allocate memory"); + memcpy(tbuf, line, len); + tbuf[len] = '\0'; + line = tbuf; + } + if (line[0] == '\0') + eline = 1; + else { + if (eline && line[0] == 'F' && len > 5 && + !memcmp(line, "From ", 5)) + (void)putc('>', fp); + eline = 0; + } + (void)fprintf(fp, "%s\n", line); + if (ferror(fp)) + break; + } + free(tbuf); + + /* Output a newline; note, empty messages are allowed. */ + (void)putc('\n', fp); + (void)fflush(fp); + if (ferror(fp)) + merr(EX_OSERR, "temporary file write error"); + return(fd); +} + +int +deliver(int fd, char *name, int lockfile) +{ + struct stat sb, fsb; + struct passwd *pw; + int mbfd=-1, lfd=-1, rval=EX_OSERR; + char biffmsg[100], buf[8*1024], path[PATH_MAX]; + off_t curoff; + size_t off; + ssize_t nr, nw; + + /* + * Disallow delivery to unknown names -- special mailboxes can be + * handled in the sendmail aliases file. + */ + if (!(pw = getpwnam(name))) { + mwarn("unknown name: %s", name); + return(EX_NOUSER); + } + + (void)snprintf(path, sizeof path, "%s/%s", _PATH_MAILDIR, name); + + if (lockfile) { + lfd = lockspool(name, pw); + if (lfd == -1) + return(EX_OSERR); + } + + /* after this point, always exit via bad to remove lockfile */ +retry: + if (lstat(path, &sb)) { + if (errno != ENOENT) { + mwarn("%s: %s", path, strerror(errno)); + goto bad; + } + if ((mbfd = open(path, O_APPEND|O_CREAT|O_EXCL|O_WRONLY|O_EXLOCK, + S_IRUSR|S_IWUSR)) == -1) { +#ifndef HAVE_O_EXLOCK + /* XXX : do something! */ +#endif + if (errno == EEXIST) { + /* file appeared since lstat */ + goto retry; + } else { + mwarn("%s: %s", path, strerror(errno)); + rval = EX_CANTCREAT; + goto bad; + } + } + /* + * Set the owner and group. Historically, binmail repeated + * this at each mail delivery. We no longer do this, assuming + * that if the ownership or permissions were changed there + * was a reason for doing so. + */ + if (fchown(mbfd, pw->pw_uid, pw->pw_gid) == -1) { + mwarn("chown %u:%u: %s", pw->pw_uid, pw->pw_gid, name); + goto bad; + } + } else { + if (sb.st_nlink != 1 || !S_ISREG(sb.st_mode)) { + mwarn("%s: linked or special file", path); + goto bad; + } + if ((mbfd = open(path, O_APPEND|O_WRONLY|O_EXLOCK, + S_IRUSR|S_IWUSR)) == -1) { + mwarn("%s: %s", path, strerror(errno)); + goto bad; + } + if (fstat(mbfd, &fsb) == -1) { + /* relating error to path may be bad style */ + mwarn("%s: %s", path, strerror(errno)); + goto bad; + } + if (sb.st_dev != fsb.st_dev || sb.st_ino != fsb.st_ino) { + mwarn("%s: changed after open", path); + goto bad; + } + /* paranoia? */ + if (fsb.st_nlink != 1 || !S_ISREG(fsb.st_mode)) { + mwarn("%s: linked or special file", path); + rval = EX_CANTCREAT; + goto bad; + } + } + + curoff = lseek(mbfd, 0, SEEK_END); + (void)snprintf(biffmsg, sizeof biffmsg, "%s@%lld\n", name, curoff); + if (lseek(fd, 0, SEEK_SET) == (off_t)-1) { + mwarn("temporary file: %s", strerror(errno)); + goto bad; + } + + while ((nr = read(fd, buf, sizeof(buf))) > 0) + for (off = 0; off < nr; off += nw) + if ((nw = write(mbfd, buf + off, nr - off)) == -1) { + mwarn("%s: %s", path, strerror(errno)); + (void)ftruncate(mbfd, curoff); + goto bad; + } + + if (nr == 0) { + rval = 0; + } else { + (void)ftruncate(mbfd, curoff); + mwarn("temporary file: %s", strerror(errno)); + } + +bad: + if (lfd != -1) + unlockspool(); + + if (mbfd != -1) { + (void)fsync(mbfd); /* Don't wait for update. */ + (void)close(mbfd); /* Implicit unlock. */ + } + + if (!rval) + notifybiff(biffmsg); + return(rval); +} + +void +notifybiff(char *msg) +{ + static struct addrinfo *res0; + struct addrinfo hints, *res; + static int f = -1; + size_t len; + int error; + + if (res0 == NULL) { + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + + error = getaddrinfo("localhost", "biff", &hints, &res0); + if (error) { + /* Be silent if biff service not available. */ + if (error != EAI_SERVICE) { + mwarn("localhost: %s", gai_strerror(error)); + } + return; + } + } + + if (f == -1) { + for (res = res0; res != NULL; res = res->ai_next) { + f = socket(res->ai_family, res->ai_socktype, + res->ai_protocol); + if (f != -1) + break; + } + } + if (f == -1) { + mwarn("socket: %s", strerror(errno)); + return; + } + + len = strlen(msg) + 1; /* XXX */ + if (sendto(f, msg, len, 0, res->ai_addr, res->ai_addrlen) != len) + mwarn("sendto biff: %s", strerror(errno)); +} + +static int lockfd = -1; +static pid_t lockpid = -1; + +int +lockspool(const char *name, struct passwd *pw) +{ + int pfd[2]; + char ch; + + if (geteuid() == 0) + return getlock(name, pw); + + /* If not privileged, open pipe to lockspool(1) instead */ + if (pipe2(pfd, O_CLOEXEC) == -1) { + merr(EX_OSERR, "pipe: %s", strerror(errno)); + return -1; + } + + signal(SIGPIPE, SIG_IGN); + switch ((lockpid = fork())) { + case -1: + merr(EX_OSERR, "fork: %s", strerror(errno)); + return -1; + case 0: + /* child */ + close(pfd[0]); + dup2(pfd[1], STDOUT_FILENO); + execl(_PATH_LOCKSPOOL, "lockspool", (char *)NULL); + merr(EX_OSERR, "execl: lockspool: %s", strerror(errno)); + /* NOTREACHED */ + break; + default: + /* parent */ + close(pfd[1]); + lockfd = pfd[0]; + break; + } + + if (read(lockfd, &ch, 1) != 1 || ch != '1') { + unlockspool(); + merr(EX_OSERR, "lockspool: unable to get lock"); + } + + return lockfd; +} + +void +unlockspool(void) +{ + if (lockpid != -1) { + waitpid(lockpid, NULL, 0); + lockpid = -1; + } else { + rellock(); + } + close(lockfd); + lockfd = -1; +} + +void +usage(void) +{ + merr(EX_USAGE, "usage: mail.local [-Ll] [-f from] user ..."); +} diff --git a/foobar/portable/contrib/libexec/mail.local/mail.local.h b/foobar/portable/contrib/libexec/mail.local/mail.local.h new file mode 100644 index 00000000..bc3137cb --- /dev/null +++ b/foobar/portable/contrib/libexec/mail.local/mail.local.h @@ -0,0 +1,42 @@ +/* $OpenBSD: mail.local.h,v 1.7 2020/02/09 14:59:21 millert Exp $ */ + +/*- + * Copyright (c) 1990 The Regents of the University of California. + * 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. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + */ + +void baditem(char *); +int deliver(int, char *, int); +void merr(int, const char *, ...); +void mwarn(const char *, ...); +int getlock(const char *, struct passwd *); +void notifybiff(char *); +void rellock(void); +int storemail(char *); +int lockspool(const char *, struct passwd *); +void unlockspool(void); +void usage(void); diff --git a/foobar/portable/contrib/libexec/mail.local/pathnames.h b/foobar/portable/contrib/libexec/mail.local/pathnames.h new file mode 100644 index 00000000..0a2c2731 --- /dev/null +++ b/foobar/portable/contrib/libexec/mail.local/pathnames.h @@ -0,0 +1,38 @@ +/* $OpenBSD: pathnames.h,v 1.5 2003/06/02 19:38:24 millert Exp $*/ + +/*- + * Copyright (c) 1990 The Regents of the University of California. + * 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. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * from: @(#)pathnames.h 5.3 (Berkeley) 1/17/91 + */ +#ifdef HAVE_PATHS_H +#include <paths.h> +#endif + +#define _PATH_LOCTMP "/tmp/local.XXXXXXXXXX" +#define _PATH_LOCKSPOOL PATH_LIBEXEC"/lockspool" diff --git a/foobar/portable/etc/README.md b/foobar/portable/etc/README.md new file mode 100644 index 00000000..0c53cc6b --- /dev/null +++ b/foobar/portable/etc/README.md @@ -0,0 +1,6 @@ +This directory will contain example OpenSMTPD config files that can be used as +a reference or for testing specific usecases. Tests that are run as part of +CI/CD process in docker containers will utilize these files. + + +* `aliases` file - default aliases map that is referenced by default OpenSMTPD config. diff --git a/foobar/portable/etc/aliases b/foobar/portable/etc/aliases new file mode 100644 index 00000000..09fb6cf5 --- /dev/null +++ b/foobar/portable/etc/aliases @@ -0,0 +1,100 @@ +# +# $OpenBSD: aliases,v 1.67 2019/01/26 10:58:05 florian Exp $ +# +# Aliases in this file will NOT be expanded in the header from +# Mail, but WILL be visible over networks or from /usr/libexec/mail.local. +# +# >>>>>>>>>> The program "newaliases" must be run after +# >> NOTE >> this file is updated for any changes to +# >>>>>>>>>> show through to smtpd. +# + +# Basic system aliases -- these MUST be present +MAILER-DAEMON: postmaster +postmaster: root + +# General redirections for important pseudo accounts +daemon: root +ftp-bugs: root +operator: root +www: root + +# Redirections for pseudo accounts that should not receive mail +_bgpd: /dev/null +_dhcp: /dev/null +_dpb: /dev/null +_dvmrpd: /dev/null +_eigrpd: /dev/null +_file: /dev/null +_fingerd: /dev/null +_ftp: /dev/null +_hostapd: /dev/null +_identd: /dev/null +_iked: /dev/null +_isakmpd: /dev/null +_iscsid: /dev/null +_ldapd: /dev/null +_ldpd: /dev/null +_mopd: /dev/null +_nsd: /dev/null +_ntp: /dev/null +_ospfd: /dev/null +_ospf6d: /dev/null +_pbuild: /dev/null +_pfetch: /dev/null +_pflogd: /dev/null +_ping: /dev/null +_pkgfetch: /dev/null +_pkguntar: /dev/null +_portmap: /dev/null +_ppp: /dev/null +_rad: /dev/null +_radiusd: /dev/null +_rbootd: /dev/null +_relayd: /dev/null +_rebound: /dev/null +_ripd: /dev/null +_rstatd: /dev/null +_rusersd: /dev/null +_rwalld: /dev/null +_smtpd: /dev/null +_smtpq: /dev/null +_sndio: /dev/null +_snmpd: /dev/null +_spamd: /dev/null +_switchd: /dev/null +_syslogd: /dev/null +_tcpdump: /dev/null +_traceroute: /dev/null +_tftpd: /dev/null +_unbound: /dev/null +_unwind: /dev/null +_vmd: /dev/null +_x11: /dev/null +_ypldap: /dev/null +bin: /dev/null +build: /dev/null +nobody: /dev/null +_tftp_proxy: /dev/null +_ftp_proxy: /dev/null +_sndiop: /dev/null +_syspatch: /dev/null +_slaacd: /dev/null +sshd: /dev/null + +# Well-known aliases -- these should be filled in! +# root: +# manager: +# dumper: + +# RFC 2142: NETWORK OPERATIONS MAILBOX NAMES +abuse: root +# noc: root +security: root + +# RFC 2142: SUPPORT MAILBOX NAMES FOR SPECIFIC INTERNET SERVICES +# hostmaster: root +# usenet: root +# news: usenet +# webmaster: root +# ftp: root diff --git a/foobar/portable/mk/Makefile.am b/foobar/portable/mk/Makefile.am new file mode 100644 index 00000000..f49c5289 --- /dev/null +++ b/foobar/portable/mk/Makefile.am @@ -0,0 +1,4 @@ +SUBDIRS = smtpd +SUBDIRS += smtpctl +SUBDIRS += mail +SUBDIRS += smtp diff --git a/foobar/portable/mk/mail/Makefile.am b/foobar/portable/mk/mail/Makefile.am new file mode 100644 index 00000000..cc6d96cb --- /dev/null +++ b/foobar/portable/mk/mail/Makefile.am @@ -0,0 +1,5 @@ +SUBDIRS = mail.lmtp +SUBDIRS += mail.maildir +SUBDIRS += mail.mboxfile +SUBDIRS += mail.mda + diff --git a/foobar/portable/mk/mail/mail.lmtp/Makefile.am b/foobar/portable/mk/mail/mail.lmtp/Makefile.am new file mode 100644 index 00000000..9847dfdf --- /dev/null +++ b/foobar/portable/mk/mail/mail.lmtp/Makefile.am @@ -0,0 +1,22 @@ +include $(top_srcdir)/mk/pathnames + +pkglibexec_PROGRAMS = mail.lmtp + +mail_lmtp_SOURCES = $(smtpd_srcdir)/mail.lmtp.c +mail_lmtp_SOURCES+= $(smtpd_srcdir)/log.c + +AM_CPPFLAGS= -I$(top_srcdir)/smtpd \ + -I$(top_srcdir)/openbsd-compat +if !NEED_ERR_H +AM_CPPFLAGS += -I$(top_srcdir)/openbsd-compat/err_h +endif + +LIBCOMPAT = $(top_builddir)/openbsd-compat/libopenbsd.a + +LDADD = $(LIBCOMPAT) + + + + +uninstall-hook: + rmdir $(DESTDIR)$(pkglibexecdir) 2> /dev/null || /bin/true diff --git a/foobar/portable/mk/mail/mail.maildir/Makefile.am b/foobar/portable/mk/mail/mail.maildir/Makefile.am new file mode 100644 index 00000000..d8f696ee --- /dev/null +++ b/foobar/portable/mk/mail/mail.maildir/Makefile.am @@ -0,0 +1,22 @@ +include $(top_srcdir)/mk/pathnames + +pkglibexec_PROGRAMS = mail.maildir + +mail_maildir_SOURCES = $(smtpd_srcdir)/mail.maildir.c +mail_maildir_SOURCES+= $(smtpd_srcdir)/log.c + +AM_CPPFLAGS= -I$(top_srcdir)/smtpd \ + -I$(top_srcdir)/openbsd-compat +if !NEED_ERR_H +AM_CPPFLAGS += -I$(top_srcdir)/openbsd-compat/err_h +endif + +LIBCOMPAT = $(top_builddir)/openbsd-compat/libopenbsd.a + +LDADD = $(LIBCOMPAT) + + + + +uninstall-hook: + rmdir $(DESTDIR)$(pkglibexecdir) 2> /dev/null || /bin/true diff --git a/foobar/portable/mk/mail/mail.mboxfile/Makefile.am b/foobar/portable/mk/mail/mail.mboxfile/Makefile.am new file mode 100644 index 00000000..d57362c0 --- /dev/null +++ b/foobar/portable/mk/mail/mail.mboxfile/Makefile.am @@ -0,0 +1,22 @@ +include $(top_srcdir)/mk/pathnames + +pkglibexec_PROGRAMS = mail.mboxfile + +mail_mboxfile_SOURCES = $(smtpd_srcdir)/mail.mboxfile.c +mail_mboxfile_SOURCES+= $(smtpd_srcdir)/log.c + +AM_CPPFLAGS= -I$(top_srcdir)/smtpd \ + -I$(top_srcdir)/openbsd-compat +if !NEED_ERR_H +AM_CPPFLAGS += -I$(top_srcdir)/openbsd-compat/err_h +endif + +LIBCOMPAT = $(top_builddir)/openbsd-compat/libopenbsd.a + +LDADD = $(LIBCOMPAT) + + + + +uninstall-hook: + rmdir $(DESTDIR)$(pkglibexecdir) 2> /dev/null || /bin/true diff --git a/foobar/portable/mk/mail/mail.mda/Makefile.am b/foobar/portable/mk/mail/mail.mda/Makefile.am new file mode 100644 index 00000000..b04aefda --- /dev/null +++ b/foobar/portable/mk/mail/mail.mda/Makefile.am @@ -0,0 +1,22 @@ +include $(top_srcdir)/mk/pathnames + +pkglibexec_PROGRAMS = mail.mda + +mail_mda_SOURCES = $(smtpd_srcdir)/mail.mda.c +mail_mda_SOURCES+= $(smtpd_srcdir)/log.c + +AM_CPPFLAGS= -I$(top_srcdir)/smtpd \ + -I$(top_srcdir)/openbsd-compat +if !NEED_ERR_H +AM_CPPFLAGS += -I$(top_srcdir)/openbsd-compat/err_h +endif + +LIBCOMPAT = $(top_builddir)/openbsd-compat/libopenbsd.a + +LDADD = $(LIBCOMPAT) + + + + +uninstall-hook: + rmdir $(DESTDIR)$(pkglibexecdir) 2> /dev/null || /bin/true diff --git a/foobar/portable/mk/mdoc2man.awk b/foobar/portable/mk/mdoc2man.awk new file mode 100644 index 00000000..726f628c --- /dev/null +++ b/foobar/portable/mk/mdoc2man.awk @@ -0,0 +1,391 @@ +#!/usr/bin/awk +# +# Copyright (c) 2003 Peter Stuge <stuge-mdoc2man@cdy.org> +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# Dramatically overhauled by Tim Kientzle. This version almost +# handles library-style pages with Fn, Ft, etc commands. Still +# a lot of problems... + +BEGIN { + displaylines = 0 + trailer = "" + out = "" + sep = "" + nextsep = " " +} + +# Add a word with appropriate preceding whitespace +# Maintain a short queue of the expected upcoming word separators. +function add(str) { + out=out sep str + sep = nextsep + nextsep = " " +} + +# Add a word with no following whitespace +# Use for opening punctuation such as '(' +function addopen(str) { + add(str) + sep = "" +} + +# Add a word with no preceding whitespace +# Use for closing punctuation such as ')' or '.' +function addclose(str) { + sep = "" + add(str) +} + +# Add a word with no space before or after +# Use for separating punctuation such as '=' +function addpunct(str) { + sep = "" + add(str) + sep = "" +} + +# Emit the current line so far +function endline() { + addclose(trailer) + trailer = "" + if(length(out) > 0) { + print out + out="" + } + if(displaylines > 0) { + displaylines = displaylines - 1 + if (displaylines == 0) + dispend() + } + # First word on next line has no preceding whitespace + sep = "" +} + +function linecmd(cmd) { + endline() + add(cmd) + endline() +} + +function breakline() { + linecmd(".br") +} + +# Start an indented display +function dispstart() { + linecmd(".RS 4") +} + +# End an indented display +function dispend() { + linecmd(".RE") +} + +# Collect rest of input line +function wtail() { + retval="" + while(w<nwords) { + if(length(retval)) + retval=retval " " + retval=retval words[++w] + } + return retval +} + +function splitwords(l, dest, n, o, w) { + n = 1 + delete dest + while (length(l) > 0) { + sub("^[ \t]*", "", l) + if (match(l, "^\"")) { + l = substr(l, 2) + o = index(l, "\"") + if (o > 0) { + w = substr(l, 1, o-1) + l = substr(l, o+1) + dest[n++] = w + } else { + dest[n++] = l + l = "" + } + } else { + o = match(l, "[ \t]") + if (o > 0) { + w = substr(l, 1, o-1) + l = substr(l, o+1) + dest[n++] = w + } else { + dest[n++] = l + l = "" + } + } + } + return n-1 +} + +! /^\./ { + out = $0 + endline() + next +} + +/^\.\\"/ { next } + +{ + sub("^\\.","") + nwords=splitwords($0, words) + # TODO: Instead of iterating 'w' over the array, have a separate + # function that returns 'next word' and use that. This will allow + # proper handling of double-quoted arguments as well. + for(w=1;w<=nwords;w++) { + if(match(words[w],"^Li$")) { # Literal; rest of line is unformatted + dispstart() + displaylines = 1 + } else if(match(words[w],"^Dl$")) { # Display literal + dispstart() + displaylines = 1 + } else if(match(words[w],"^Bd$")) { # Begin display + if(match(words[w+1],"-literal")) { + dispstart() + linecmd(".nf") + displaylines=10000 + w=nwords + } + } else if(match(words[w],"^Ed$")) { # End display + displaylines = 0 + dispend() + } else if(match(words[w],"^Ns$")) { # Suppress space after next word + nextsep = "" + } else if(match(words[w],"^No$")) { # Normal text + add(words[++w]) + } else if(match(words[w],"^Dq$")) { # Quote + addopen("``") + add(words[++w]) + while(w<nwords&&!match(words[w+1],"^[\\.,]")) + add(words[++w]) + addclose("''") + } else if(match(words[w],"^Do$")) { + addopen("``") + } else if(match(words[w],"^Dc$")) { + addclose("''") + } else if(match(words[w],"^Oo$")) { + addopen("[") + } else if(match(words[w],"^Oc$")) { + addclose("]") + } else if(match(words[w],"^Ao$")) { + addopen("<") + } else if(match(words[w],"^Ac$")) { + addclose(">") + } else if(match(words[w],"^Dd$")) { + date=wtail() + next + } else if(match(words[w],"^Dt$")) { + id=wtail() + next + } else if(match(words[w],"^Ox$")) { + add("OpenBSD") + } else if(match(words[w],"^Fx$")) { + add("FreeBSD") + } else if(match(words[w],"^Nx$")) { + add("NetBSD") + } else if(match(words[w],"^St$")) { + if (match(words[w+1], "^-p1003.1$")) { + w++ + add("IEEE Std 1003.1 (``POSIX.1'')") + } else if(match(words[w+1], "^-p1003.1-96$")) { + w++ + add("ISO/IEC 9945-1:1996 (``POSIX.1'')") + } else if(match(words[w+1], "^-p1003.1-88$")) { + w++ + add("IEEE Std 1003.1-1988 (``POSIX.1'')") + } else if(match(words[w+1], "^-p1003.1-2001$")) { + w++ + add("IEEE Std 1003.1-2001 (``POSIX.1'')") + } else if(match(words[w+1], "^-susv2$")) { + w++ + add("Version 2 of the Single UNIX Specification (``SUSv2'')") + } + } else if(match(words[w],"^Ex$")) { + if (match(words[w+1], "^-std$")) { + w++ + add("The \\fB" name "\\fP utility exits 0 on success, and >0 if an error occurs.") + } + } else if(match(words[w],"^Os$")) { + add(".TH " id " \"" date "\" \"" wtail() "\"") + } else if(match(words[w],"^Sh$")) { + section=wtail() + add(".SH " section) + linecmd(".ad l") + } else if(match(words[w],"^Xr$")) { + add("\\fB" words[++w] "\\fP(" words[++w] ")" words[++w]) + } else if(match(words[w],"^Nm$")) { + if(match(section,"SYNOPSIS")) + breakline() + if(w >= nwords) + n=name + else if (match(words[w+1], "^[A-Z][a-z]$")) + n=name + else if (match(words[w+1], "^[.,;:]$")) + n=name + else { + n=words[++w] + if(!length(name)) + name=n + } + if(!length(n)) + n=name + add("\\fB\\%" n "\\fP") + } else if(match(words[w],"^Nd$")) { + add("\\- " wtail()) + } else if(match(words[w],"^Fl$")) { + add("\\fB\\-" words[++w] "\\fP") + } else if(match(words[w],"^Ar$")) { + addopen("\\fI") + if(w==nwords) + add("file ...\\fP") + else + add(words[++w] "\\fP") + } else if(match(words[w],"^Cm$")) { + add("\\fB" words[++w] "\\fP") + } else if(match(words[w],"^Op$")) { + addopen("[") + option=1 + trailer="]" trailer + } else if(match(words[w],"^Pp$")) { + linecmd(".PP") + } else if(match(words[w],"^An$")) { + endline() + } else if(match(words[w],"^Ss$")) { + add(".SS") + } else if(match(words[w],"^Ft$")) { + if (match(section, "SYNOPSIS")) { + breakline() + } + add("\\fI" wtail() "\\fP") + if (match(section, "SYNOPSIS")) { + breakline() + } + } else if(match(words[w],"^Fn$")) { + ++w + F = "\\fB\\%" words[w] "\\fP(" + Fsep = "" + while(w<nwords) { + ++w + if (match(words[w], "^[.,:]$")) { + --w + break + } + gsub(" ", "\\ ", words[w]) + F = F Fsep "\\fI\\%" words[w] "\\fP" + Fsep = ", " + } + add(F ")") + if (match(section, "SYNOPSIS")) { + addclose(";") + } + } else if(match(words[w],"^Fo$")) { + w++ + F = "\\fB\\%" words[w] "\\fP(" + Fsep = "" + } else if(match(words[w],"^Fa$")) { + w++ + gsub(" ", "\\ ", words[w]) + F = F Fsep "\\fI\\%" words[w] "\\fP" + Fsep = ", " + } else if(match(words[w],"^Fc$")) { + add(F ")") + if (match(section, "SYNOPSIS")) { + addclose(";") + } + } else if(match(words[w],"^Va$")) { + w++ + add("\\fI" words[w] "\\fP") + } else if(match(words[w],"^In$")) { + w++ + add("\\fB#include <" words[w] ">\\fP") + } else if(match(words[w],"^Pa$")) { + addopen("\\fI") + w++ + if(match(words[w],"^\\.")) + add("\\&") + add(words[w] "\\fP") + } else if(match(words[w],"^Dv$")) { + add(".BR") + } else if(match(words[w],"^Em|Ev$")) { + add(".IR") + } else if(match(words[w],"^Pq$")) { + addopen("(") + trailer=")" trailer + } else if(match(words[w],"^Aq$")) { + addopen("\\%<") + trailer=">" trailer + } else if(match(words[w],"^Brq$")) { + addopen("{") + trailer="}" trailer + } else if(match(words[w],"^S[xy]$")) { + add(".B " wtail()) + } else if(match(words[w],"^Ic$")) { + add("\\fB") + trailer="\\fP" trailer + } else if(match(words[w],"^Bl$")) { + oldoptlist=optlist + linecmd(".RS 5") + if(match(words[w+1],"-bullet")) + optlist=1 + else if(match(words[w+1],"-enum")) { + optlist=2 + enum=0 + } else if(match(words[w+1],"-tag")) + optlist=3 + else if(match(words[w+1],"-item")) + optlist=4 + else if(match(words[w+1],"-bullet")) + optlist=1 + w=nwords + } else if(match(words[w],"^El$")) { + linecmd(".RE") + optlist=oldoptlist + } else if(match(words[w],"^It$")&&optlist) { + if(optlist==1) + add(".IP \\(bu") + else if(optlist==2) + add(".IP " ++enum ".") + else if(optlist==3) { + add(".TP") + endline() + if(match(words[w+1],"^Pa$|^Ev$")) { + add(".B") + w++ + } + } else if(optlist==4) + add(".IP") + } else if(match(words[w],"^Xo$")) { + # TODO: Figure out how to handle this + } else if(match(words[w],"^Xc$")) { + # TODO: Figure out how to handle this + } else if(match(words[w],"^[=]$")) { + addpunct(words[w]) + } else if(match(words[w],"^[[{(]$")) { + addopen(words[w]) + } else if(match(words[w],"^[\\])}.,;:]$")) { + addclose(words[w]) + } else { + add(words[w]) + } + } + if(match(out,"^\\.[^a-zA-Z]")) + sub("^\\.","",out) + endline() +} diff --git a/foobar/portable/mk/pathnames b/foobar/portable/mk/pathnames new file mode 100644 index 00000000..b233ec33 --- /dev/null +++ b/foobar/portable/mk/pathnames @@ -0,0 +1,10 @@ +smtpd_srcdir = $(top_srcdir)/smtpd +compat_srcdir = $(top_srcdir)/openbsd-compat +regress_srcdir = $(top_srcdir)/regress/bin + +PATHS= -DSMTPD_CONFDIR=\"$(sysconfdir)\" \ + -DPATH_CHROOT=\"$(PRIVSEP_PATH)\" \ + -DPATH_SMTPCTL=\"$(sbindir)/smtpctl\" \ + -DPATH_MAILLOCAL=\"$(pkglibexecdir)/mail.local\" \ + -DPATH_MAKEMAP=\"$(sbindir)/makemap\" \ + -DPATH_LIBEXEC=\"$(pkglibexecdir)\" diff --git a/foobar/portable/mk/smtp/Makefile.am b/foobar/portable/mk/smtp/Makefile.am new file mode 100644 index 00000000..e955a271 --- /dev/null +++ b/foobar/portable/mk/smtp/Makefile.am @@ -0,0 +1,59 @@ +include $(top_srcdir)/mk/pathnames + +bin_PROGRAMS= smtp + +smtp_SOURCES= $(smtpd_srcdir)/iobuf.c +smtp_SOURCES+= $(smtpd_srcdir)/ioev.c +smtp_SOURCES+= $(smtpd_srcdir)/log.c +smtp_SOURCES+= $(smtpd_srcdir)/smtp_client.c +smtp_SOURCES+= $(smtpd_srcdir)/smtpc.c +smtp_SOURCES+= $(smtpd_srcdir)/ssl.c +smtp_SOURCES+= $(smtpd_srcdir)/ssl_verify.c + +smtp_CFLAGS= -DIO_TLS + +AM_CPPFLAGS= -I$(top_srcdir)/smtpd \ + -I$(top_srcdir)/openbsd-compat +if !NEED_ERR_H +AM_CPPFLAGS += -I$(top_srcdir)/openbsd-compat/err_h +endif + +LIBCOMPAT= $(top_builddir)/openbsd-compat/libopenbsd.a + +LDADD= $(LIBCOMPAT) + +# need to define _GNU_SOURCE to get: +# EAI_NODATA defined +# {v,}asprintf +# setres{g,u}id +CFLAGS+= -D_GNU_SOURCE +CPPFLAGS= -I$(srcdir) @CPPFLAGS@ $(PATHS) @DEFS@ + +MANPAGES= smtp.1.out +MANPAGES_IN= $(smtpd_srcdir)/smtp.1 + +EXTRA_DIST= $(MANPAGES_IN) + +PATHSUBS= -e 's|/var/run/smtpd.sock|$(sockdir)/smtpd.sock|g' \ + -e 's|/usr/libexec|$(libexecdir)|g' \ + -e 's|/etc/mail/|$(sysconfdir)/|g' + +FIXPATHSCMD= $(SED) $(PATHSUBS) + + +$(MANPAGES): $(MANPAGES_IN) + manpage=$(smtpd_srcdir)/`echo $@ | sed 's/\.out$$//'`; \ + if test "$(MANTYPE)" = "man"; then \ + $(FIXPATHSCMD) $${manpage} | $(AWK) -f $(srcdir)/../mdoc2man.awk > $@; \ + else \ + $(FIXPATHSCMD) $${manpage} > $@; \ + fi + +install-exec-hook: $(CONFIGFILES) $(MANPAGES) + $(MKDIR_P) $(DESTDIR)$(mandir)/$(mansubdir)1 + $(INSTALL) -m 644 smtp.1.out $(DESTDIR)$(mandir)/$(mansubdir)1/smtp.1 + rm smtp.1.out + +uninstall-hook: + rm -f $(DESTDIR)$(mandir)/$(mansubdir)1/smtp.1 + rmdir $(DESTDIR)$(mandir)/$(mansubdir)1 2> /dev/null || /bin/true diff --git a/foobar/portable/mk/smtpctl/Makefile.am b/foobar/portable/mk/smtpctl/Makefile.am new file mode 100644 index 00000000..3aac9bd7 --- /dev/null +++ b/foobar/portable/mk/smtpctl/Makefile.am @@ -0,0 +1,99 @@ +include $(top_srcdir)/mk/pathnames + +sbin_PROGRAMS= smtpctl + +smtpctl_SOURCES= $(smtpd_srcdir)/enqueue.c +smtpctl_SOURCES+= $(smtpd_srcdir)/parser.c +smtpctl_SOURCES+= $(smtpd_srcdir)/log.c +smtpctl_SOURCES+= $(smtpd_srcdir)/envelope.c +smtpctl_SOURCES+= $(smtpd_srcdir)/queue_backend.c +smtpctl_SOURCES+= $(smtpd_srcdir)/queue_fs.c +smtpctl_SOURCES+= $(smtpd_srcdir)/smtpctl.c +smtpctl_SOURCES+= $(smtpd_srcdir)/spfwalk.c +smtpctl_SOURCES+= $(smtpd_srcdir)/util.c +smtpctl_SOURCES+= $(smtpd_srcdir)/unpack_dns.c +smtpctl_SOURCES+= $(smtpd_srcdir)/compress_backend.c +smtpctl_SOURCES+= $(smtpd_srcdir)/compress_gzip.c +smtpctl_SOURCES+= $(smtpd_srcdir)/to.c +smtpctl_SOURCES+= $(smtpd_srcdir)/expand.c +smtpctl_SOURCES+= $(smtpd_srcdir)/tree.c +smtpctl_SOURCES+= $(smtpd_srcdir)/dict.c + +if HAVE_DB_API +smtpctl_SOURCES+= $(smtpd_srcdir)/config.c +smtpctl_SOURCES+= $(smtpd_srcdir)/parse.y +smtpctl_SOURCES+= $(smtpd_srcdir)/limit.c +smtpctl_SOURCES+= $(smtpd_srcdir)/table.c +smtpctl_SOURCES+= $(smtpd_srcdir)/table_static.c +smtpctl_SOURCES+= $(smtpd_srcdir)/table_db.c +smtpctl_SOURCES+= $(smtpd_srcdir)/table_getpwnam.c +smtpctl_SOURCES+= $(smtpd_srcdir)/table_proc.c +smtpctl_SOURCES+= $(smtpd_srcdir)/mailaddr.c +smtpctl_SOURCES+= $(smtpd_srcdir)/makemap.c +endif + +smtpctl_SOURCES+= $(smtpd_srcdir)/crypto.c + +smtpctl_CFLAGS= -DNO_IO -DCONFIG_MINIMUM +smtpctl_CFLAGS+= -DPATH_GZCAT=\"$(ZCAT)\" \ + -DPATH_ENCRYPT=\"$(pkglibexecdir)/encrypt\" + +AM_CPPFLAGS= -I$(top_srcdir)/smtpd \ + -I$(top_srcdir)/openbsd-compat +if !NEED_ERR_H +AM_CPPFLAGS += -I$(top_srcdir)/openbsd-compat/err_h +endif + +LIBCOMPAT= $(top_builddir)/openbsd-compat/libopenbsd.a + +LDADD= $(LIBCOMPAT) +if HAVE_DB_API +LDADD+= $(DB_LIB) +endif + +# need to define _GNU_SOURCE to get: +# EAI_NODATA defined +# {v,}asprintf +# setres{g,u}id +CFLAGS+= -D_GNU_SOURCE +CPPFLAGS= -I$(srcdir) @CPPFLAGS@ $(PATHS) @DEFS@ + +MANPAGES= smtpctl.8.out sendmail.8.out makemap.8.out newaliases.8.out +MANPAGES_IN= $(smtpd_srcdir)/smtpctl.8 $(smtpd_srcdir)/sendmail.8 $(smtpd_srcdir)/makemap.8 $(smtpd_srcdir)/newaliases.8 + +EXTRA_DIST= $(MANPAGES_IN) + +PATHSUBS= -e 's|/var/run/smtpd.sock|$(sockdir)/smtpd.sock|g' \ + -e 's|/usr/libexec|$(libexecdir)|g' \ + -e 's|/etc/mail/|$(sysconfdir)/|g' + +FIXPATHSCMD= $(SED) $(PATHSUBS) + +if NEED_LIBASR +AM_CPPFLAGS+= -I$(top_srcdir)/openbsd-compat/libasr +endif + +$(MANPAGES): $(MANPAGES_IN) + manpage=$(smtpd_srcdir)/`echo $@ | sed 's/\.out$$//'`; \ + if test "$(MANTYPE)" = "man"; then \ + $(FIXPATHSCMD) $${manpage} | $(AWK) -f $(srcdir)/../mdoc2man.awk > $@; \ + else \ + $(FIXPATHSCMD) $${manpage} > $@; \ + fi + +install-exec-hook: $(CONFIGFILES) $(MANPAGES) + $(MKDIR_P) $(DESTDIR)$(mandir)/$(mansubdir)8 + chgrp $(SMTPD_QUEUE_USER) $(DESTDIR)$(sbindir)/smtpctl || true + chmod 2555 $(DESTDIR)$(sbindir)/smtpctl || true + $(INSTALL) -m 644 smtpctl.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/smtpctl.8 + $(INSTALL) -m 644 sendmail.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/sendmail.8 + $(INSTALL) -m 644 makemap.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/makemap.8 + $(INSTALL) -m 644 newaliases.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/newaliases.8 + rm smtpctl.8.out sendmail.8.out makemap.8.out newaliases.8.out + +uninstall-hook: + rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/smtpctl.8 + rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/sendmail.8 + rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/makemap.8 + rm -f $(DESTDIR)$(mandir)/$(mansubdir)8/newaliases.8 + rmdir $(DESTDIR)$(mandir)/$(mansubdir)8 2> /dev/null || /bin/true diff --git a/foobar/portable/mk/smtpd/Makefile.am b/foobar/portable/mk/smtpd/Makefile.am new file mode 100644 index 00000000..bf7a483b --- /dev/null +++ b/foobar/portable/mk/smtpd/Makefile.am @@ -0,0 +1,194 @@ +# In OpenBSD, smtpd's files are installed this way: +# +# /etc/mail/smtpd.conf +# /usr/sbin/smtpd +# +# +# For OpenSMTPD portable, here's where files are installed: +# (assuming PREFIX=/usr/local) +# +# /usr/local/etc/smtpd.conf +# /usr/local/sbin/smtpd + +include $(top_srcdir)/mk/pathnames + +sbin_PROGRAMS= smtpd + +smtpd_SOURCES= $(smtpd_srcdir)/aliases.c +smtpd_SOURCES+= $(smtpd_srcdir)/bounce.c +smtpd_SOURCES+= $(smtpd_srcdir)/ca.c +smtpd_SOURCES+= $(smtpd_srcdir)/cert.c +smtpd_SOURCES+= $(smtpd_srcdir)/compress_backend.c +smtpd_SOURCES+= $(smtpd_srcdir)/config.c +smtpd_SOURCES+= $(smtpd_srcdir)/control.c +smtpd_SOURCES+= $(smtpd_srcdir)/dict.c +smtpd_SOURCES+= $(smtpd_srcdir)/dns.c +smtpd_SOURCES+= $(smtpd_srcdir)/esc.c +smtpd_SOURCES+= $(smtpd_srcdir)/envelope.c +smtpd_SOURCES+= $(smtpd_srcdir)/expand.c +smtpd_SOURCES+= $(smtpd_srcdir)/forward.c +smtpd_SOURCES+= $(smtpd_srcdir)/iobuf.c +smtpd_SOURCES+= $(smtpd_srcdir)/ioev.c +smtpd_SOURCES+= $(smtpd_srcdir)/limit.c +smtpd_SOURCES+= $(smtpd_srcdir)/lka.c +smtpd_SOURCES+= $(smtpd_srcdir)/lka_filter.c +smtpd_SOURCES+= $(smtpd_srcdir)/lka_session.c +smtpd_SOURCES+= $(smtpd_srcdir)/log.c +smtpd_SOURCES+= $(smtpd_srcdir)/mda.c +smtpd_SOURCES+= $(smtpd_srcdir)/mda_mbox.c +smtpd_SOURCES+= $(smtpd_srcdir)/mda_unpriv.c +smtpd_SOURCES+= $(smtpd_srcdir)/mda_variables.c +smtpd_SOURCES+= $(smtpd_srcdir)/mproc.c +smtpd_SOURCES+= $(smtpd_srcdir)/mailaddr.c +smtpd_SOURCES+= $(smtpd_srcdir)/mta.c +smtpd_SOURCES+= $(smtpd_srcdir)/mta_session.c +smtpd_SOURCES+= $(smtpd_srcdir)/parse.y +smtpd_SOURCES+= $(smtpd_srcdir)/pony.c +smtpd_SOURCES+= $(smtpd_srcdir)/proxy.c +smtpd_SOURCES+= $(smtpd_srcdir)/queue.c +smtpd_SOURCES+= $(smtpd_srcdir)/queue_backend.c +smtpd_SOURCES+= $(smtpd_srcdir)/report_smtp.c +smtpd_SOURCES+= $(smtpd_srcdir)/resolver.c +smtpd_SOURCES+= $(smtpd_srcdir)/rfc5322.c +smtpd_SOURCES+= $(smtpd_srcdir)/ruleset.c +smtpd_SOURCES+= $(smtpd_srcdir)/runq.c +smtpd_SOURCES+= $(smtpd_srcdir)/scheduler.c +smtpd_SOURCES+= $(smtpd_srcdir)/scheduler_backend.c +smtpd_SOURCES+= $(smtpd_srcdir)/smtp.c +smtpd_SOURCES+= $(smtpd_srcdir)/smtp_session.c +smtpd_SOURCES+= $(smtpd_srcdir)/smtpd.c +smtpd_SOURCES+= $(smtpd_srcdir)/srs.c +smtpd_SOURCES+= $(smtpd_srcdir)/ssl.c +smtpd_SOURCES+= $(smtpd_srcdir)/ssl_smtpd.c +smtpd_SOURCES+= $(smtpd_srcdir)/ssl_verify.c +smtpd_SOURCES+= $(smtpd_srcdir)/stat_backend.c +smtpd_SOURCES+= $(smtpd_srcdir)/table.c +smtpd_SOURCES+= $(smtpd_srcdir)/to.c +smtpd_SOURCES+= $(smtpd_srcdir)/tree.c +smtpd_SOURCES+= $(smtpd_srcdir)/unpack_dns.c +smtpd_SOURCES+= $(smtpd_srcdir)/util.c +smtpd_SOURCES+= $(smtpd_srcdir)/waitq.c + +# backends +smtpd_SOURCES+= $(smtpd_srcdir)/crypto.c +smtpd_SOURCES+= $(smtpd_srcdir)/compress_gzip.c +if HAVE_DB_API +smtpd_SOURCES+= $(smtpd_srcdir)/table_db.c +endif +smtpd_SOURCES+= $(smtpd_srcdir)/table_getpwnam.c +smtpd_SOURCES+= $(smtpd_srcdir)/table_proc.c +smtpd_SOURCES+= $(smtpd_srcdir)/table_static.c +smtpd_SOURCES+= $(smtpd_srcdir)/queue_fs.c +smtpd_SOURCES+= $(smtpd_srcdir)/queue_null.c +smtpd_SOURCES+= $(smtpd_srcdir)/queue_proc.c +smtpd_SOURCES+= $(smtpd_srcdir)/queue_ram.c +smtpd_SOURCES+= $(smtpd_srcdir)/scheduler_null.c +smtpd_SOURCES+= $(smtpd_srcdir)/scheduler_proc.c +smtpd_SOURCES+= $(smtpd_srcdir)/scheduler_ramqueue.c +smtpd_SOURCES+= $(smtpd_srcdir)/stat_ramstat.c + + +smtpd_CFLAGS= -DIO_TLS +smtpd_CFLAGS+= -DCA_FILE=\"$(CA_FILE)\" + +AM_CPPFLAGS= -I$(smtpd_srcdir) \ + -I$(compat_srcdir) +if !NEED_ERR_H +AM_CPPFLAGS += -I$(top_srcdir)/openbsd-compat/err_h +endif +if !SUPPORT_PATHS_H +AM_CPPFLAGS += -I$(top_srcdir)/openbsd-compat/paths_h +endif + +LIBCOMPAT= $(top_builddir)/openbsd-compat/libopenbsd.a +if NEED_LIBASR +AM_CPPFLAGS+= -I$(top_srcdir)/openbsd-compat/libasr +endif + +LDADD= $(LIBCOMPAT) $(DB_LIB) $(ASR_LIB) + +# need to define _GNU_SOURCE to get: +# EAI_NODATA defined +# {v,}asprintf +# setres{g,u}id +CFLAGS+= -D_GNU_SOURCE -DNEED_EVENT_ASR_RUN +CPPFLAGS= -I$(srcdir) @CPPFLAGS@ $(PATHS) @DEFS@ + +MANPAGES= aliases.5.out forward.5.out smtpd.8.out \ + smtpd.conf.5.out table.5.out + +MANPAGES_IN= $(smtpd_srcdir)/aliases.5 +MANPAGES_IN+= $(smtpd_srcdir)/forward.5 +MANPAGES_IN+= $(smtpd_srcdir)/smtpd.8 +MANPAGES_IN+= $(smtpd_srcdir)/smtpd.conf.5 +MANPAGES_IN+= $(smtpd_srcdir)/table.5 + +CONFIGFILES= smtpd.conf.out +CONFIGFILES_IN= $(smtpd_srcdir)/smtpd.conf + +EXTRA_DIST= $(CONFIGFILES_IN) $(MANPAGES_IN) + + +EXTRA_DIST+= $(smtpd_srcdir)/smtpd.h +EXTRA_DIST+= $(smtpd_srcdir)/smtpd-api.h +EXTRA_DIST+= $(smtpd_srcdir)/smtpd-defines.h +EXTRA_DIST+= $(smtpd_srcdir)/ioev.h +EXTRA_DIST+= $(smtpd_srcdir)/iobuf.h +EXTRA_DIST+= $(smtpd_srcdir)/log.h +EXTRA_DIST+= $(smtpd_srcdir)/ssl.h +EXTRA_DIST+= $(smtpd_srcdir)/parser.h + +EXTRA_DIST+= $(backends_srcdir)/queue_utils.h +EXTRA_DIST+= $(filters_srcdir)/asr_event.h + +PATHSUBS= -e 's|/etc/mail/|$(sysconfdir)/|g' \ + -e 's|/var/run/smtpd.sock|$(sockdir)/smtpd.sock|g' \ + -e 's|/usr/local/libexec/smtpd/|$(pkglibexecdir)|g' + +FIXPATHSCMD= $(SED) $(PATHSUBS) + +$(MANPAGES): $(MANPAGES_IN) + manpage=$(smtpd_srcdir)/`echo $@ | sed 's/\.out$$//'`; \ + if test "$(MANTYPE)" = "man"; then \ + $(FIXPATHSCMD) $${manpage} | $(AWK) -f $(srcdir)/../mdoc2man.awk > $@; \ + else \ + $(FIXPATHSCMD) $${manpage} > $@; \ + fi + +$(CONFIGFILES): $(CONFIGFILES_IN) + conffile=$(smtpd_srcdir)/`echo $@ | sed 's/.out$$//'`; \ + $(CAT) $(srcdir)/$${conffile} > $@ + + +# smtpd.conf +# newaliases makemap +install-exec-hook: $(CONFIGFILES) $(MANPAGES) + $(MKDIR_P) $(DESTDIR)$(sysconfdir) + $(MKDIR_P) $(DESTDIR)$(bindir) + $(MKDIR_P) $(DESTDIR)$(mandir)/$(mansubdir)5 + $(MKDIR_P) $(DESTDIR)$(mandir)/$(mansubdir)8 + + @if [ ! -f $(DESTDIR)$(sysconfdir)/smtpd.conf ]; then \ + $(INSTALL) -m 644 smtpd.conf.out $(DESTDIR)$(sysconfdir)/smtpd.conf; \ + else \ + echo "$(DESTDIR)$(sysconfdir)/smtpd.conf already exists, install will not overwrite"; \ + fi + + $(INSTALL) -m 644 aliases.5.out $(DESTDIR)$(mandir)/$(mansubdir)5/aliases.5 + $(INSTALL) -m 644 forward.5.out $(DESTDIR)$(mandir)/$(mansubdir)5/forward.5 + $(INSTALL) -m 644 table.5.out $(DESTDIR)$(mandir)/$(mansubdir)5/table.5 + $(INSTALL) -m 644 smtpd.8.out $(DESTDIR)$(mandir)/$(mansubdir)8/smtpd.8 + $(INSTALL) -m 644 smtpd.conf.5.out $(DESTDIR)$(mandir)/$(mansubdir)5/smtpd.conf.5 + rm aliases.5.out forward.5.out table.5.out \ + smtpd.8.out smtpd.conf.5.out smtpd.conf.out + +uninstall-hook: +# XXX to make "make distcheck" happy we need to rm smtpd.conf +# rm $(DESTDIR)$(sysconfdir)/smtpd.conf + rm -f $(DESTDIR)$(mandir)/$(mansubdir)5/aliases.5 \ + $(DESTDIR)$(mandir)/$(mansubdir)5/forward.5 \ + $(DESTDIR)$(mandir)/$(mansubdir)5/table.5 \ + $(DESTDIR)$(mandir)/$(mansubdir)5/smtpd.conf.5 \ + $(DESTDIR)$(mandir)/$(mansubdir)8/smtpd.8 + rmdir $(DESTDIR)$(mandir)/$(mansubdir)5 \ + $(DESTDIR)$(mandir)/$(mansubdir)8 2> /dev/null || /bin/true diff --git a/foobar/portable/openbsd-compat/Makefile.am b/foobar/portable/openbsd-compat/Makefile.am new file mode 100644 index 00000000..db1e93fa --- /dev/null +++ b/foobar/portable/openbsd-compat/Makefile.am @@ -0,0 +1,231 @@ +noinst_LIBRARIES = libopenbsd.a + +AM_CPPFLAGS = -I$(top_srcdir) -I$(top_srcdir)/smtpd -I$(top_srcdir)/openbsd-compat + +libopenbsd_a_SOURCES = empty.c + +if NEED_LIBASR +AM_CPPFLAGS += -I$(top_srcdir)/openbsd-compat/libasr + +libopenbsd_a_SOURCES += libasr/asr.c +libopenbsd_a_SOURCES += libasr/asr_debug.c +libopenbsd_a_SOURCES += libasr/asr_compat.c +libopenbsd_a_SOURCES += libasr/asr_utils.c +libopenbsd_a_SOURCES += libasr/getaddrinfo_async.c +libopenbsd_a_SOURCES += libasr/gethostnamadr_async.c +libopenbsd_a_SOURCES += libasr/getnameinfo_async.c +libopenbsd_a_SOURCES += libasr/getnetnamadr_async.c +libopenbsd_a_SOURCES += libasr/res_search_async.c +libopenbsd_a_SOURCES += libasr/res_send_async.c + +include_HEADERS = libasr/asr.h +endif + + + +if NEED_PROGNAME +libopenbsd_a_SOURCES += progname.c +endif + +if NEED_ARC4RANDOM +libopenbsd_a_SOURCES += arc4random.c +endif + +if NEED_BASE64 +libopenbsd_a_SOURCES += base64.c +endif + +if NEED_BASENAME +libopenbsd_a_SOURCES += basename.c +endif + +if NEED_CLOCK_GETTIME +libopenbsd_a_SOURCES += clock_gettime.c +endif + +if NEED_CLOSEFROM +libopenbsd_a_SOURCES += closefrom.c +endif + +if NEED_CRYPT_CHECKPASS +libopenbsd_a_SOURCES += crypt_checkpass.c +endif + +if NEED_DAEMON +libopenbsd_a_SOURCES += daemon.c +endif + +if NEED_DIRNAME +libopenbsd_a_SOURCES += dirname.c +endif + +if NEED_ERR +libopenbsd_a_SOURCES += bsd-err.c +endif + +if NEED_ERRC +libopenbsd_a_SOURCES += errc.c +endif + +if NEED_EVENT_ASR_RUN +libopenbsd_a_SOURCES += event_asr_run.c +endif + +if NEED_EXPLICIT_BZERO +libopenbsd_a_SOURCES += explicit_bzero.c +endif + +if NEED_FGETLN +libopenbsd_a_SOURCES += fgetln.c +endif + +if NEED_FMT_SCALED +libopenbsd_a_SOURCES += fmt_scaled.c +endif + +if NEED_FPARSELN +libopenbsd_a_SOURCES += fparseln.c +endif + +if NEED_FREEZERO +libopenbsd_a_SOURCES += freezero.c +endif + +if NEED_GETOPT +libopenbsd_a_SOURCES += getopt.c +endif + +if NEED_GETPEEREID +libopenbsd_a_SOURCES += getpeereid.c +endif + +if NEED_IMSG +libopenbsd_a_SOURCES += imsg.c +libopenbsd_a_SOURCES += imsg-buffer.c +endif + +if NEED_INET_NET_PTON +libopenbsd_a_SOURCES += inet_net_pton.c +endif + +if NEED_NANOSLEEP +libopenbsd_a_SOURCES += nanosleep.c +endif + +if NEED_PIDFILE +libopenbsd_a_SOURCES += pidfile.c +endif + +if NEED_REALLOCARRAY +libopenbsd_a_SOURCES += reallocarray.c +endif + +if NEED_RECALLOCARRAY +libopenbsd_a_SOURCES += recallocarray.c +endif + +if NEED_RES_HNOK +libopenbsd_a_SOURCES += res_hnok.c +endif + +if NEED_RES_RANDOMID +libopenbsd_a_SOURCES += res_randomid.c +endif + +if NEED_SETPROCTITLE +libopenbsd_a_SOURCES += setproctitle.c +endif + +if NEED_SETRESGID +libopenbsd_a_SOURCES += setresgid.c +endif + +if NEED_SETRESUID +libopenbsd_a_SOURCES += setresuid.c +endif + +if NEED_SETEGID +libopenbsd_a_SOURCES += setegid.c +endif + +if NEED_SETEUID +libopenbsd_a_SOURCES += seteuid.c +endif + +if NEED_SIGNAL +libopenbsd_a_SOURCES += signal.c +endif + +if NEED_SSL_CTX_USE_CERTIFICATE_CHAIN_MEM +libopenbsd_a_SOURCES += SSL_CTX_use_certificate_chain_mem.c +endif + +if NEED_STRERROR +libopenbsd_a_SOURCES += strerror.c +endif + +if NEED_STRLCAT +libopenbsd_a_SOURCES += strlcat.c +endif + +if NEED_STRLCPY +libopenbsd_a_SOURCES += strlcpy.c +endif + +if NEED_STRMODE +libopenbsd_a_SOURCES += strmode.c +endif + +if NEED_STRSEP +libopenbsd_a_SOURCES += strsep.c +endif + +if NEED_STRTONUM +libopenbsd_a_SOURCES += strtonum.c +endif + +if NEED_STRNDUP +libopenbsd_a_SOURCES += strndup.c +endif + +if NEED_STRNLEN +libopenbsd_a_SOURCES += strnlen.c +endif + +if NEED_USLEEP +libopenbsd_a_SOURCES += usleep.c +endif + +if NEED_VIS +libopenbsd_a_SOURCES += vis.c +endif + +if NEED_WAITPID +libopenbsd_a_SOURCES += bsd-waitpid.c +endif + + + +EXTRA_DIST = base64.h +EXTRA_DIST += bsd-misc.h +EXTRA_DIST += bsd-waitpid.h +EXTRA_DIST += chacha_private.h +EXTRA_DIST += defines.h +EXTRA_DIST += entropy.h +EXTRA_DIST += imsg.h +EXTRA_DIST += includes.h +EXTRA_DIST += log.h +EXTRA_DIST += openbsd-compat.h +EXTRA_DIST += sys/queue.h +EXTRA_DIST += sys/tree.h +EXTRA_DIST += bsd-vis.h + +if NEED_LIBASR +EXTRA_DIST += libasr/asr_compat.h +EXTRA_DIST += libasr/asr_private.h +endif + + +if !NEED_ERR_H +AM_CPPFLAGS += -I$(top_srcdir)/openbsd-compat/err_h +endif diff --git a/foobar/portable/openbsd-compat/NOTES b/foobar/portable/openbsd-compat/NOTES new file mode 100644 index 00000000..42aefc7d --- /dev/null +++ b/foobar/portable/openbsd-compat/NOTES @@ -0,0 +1,37 @@ +List of files and where they come from + +arc4random.c portable openssh +base64.{c,h} portable openssh +basename.c portable openssh +bsd-closefrom.c portable openssh +bsd-getpeereid.c portable openssh +bsd-waitpid.{c,h} portable openssh +clock_gettime.c handmade +daemon.c portable openssh +defines.h portable openssh +dirname.c portable openssh +entropy.{c,h} portable openssh +event_asr_run.c end of /usr/src/lib/libevent/event.c +fgetln.c part of /usr/src/usr.bin/make/util.c +fmt_scaled.c portable openssh +fparseln.c part of /usr/src/lib/libutil/fparseln.c +getopt.c portable openssh +imsg-buffer.c part of /usr/src/libutil/imsg-buffer.c +imsg.{c,h} part of /usr/src/libutil/imsg.c +includes.h portable openssh +log.h portable openssh +mktemp.c portable openssh +openbsd-compat.h portable openssh +pidfile.c /usr/src/lib/libutil/pidfile.c +pw_dup.c /usr/src/lib/libc/gen/pw_dup.c +reallocarray.c /usr/src/lib/libc/stdlib/reallocarray.c +setproctitle.c portable openssh +strlcat.c portable openssh +strlcpy.c portable openssh +strmode.c portable openssh +strsep.c portable openssh +strtonum.c portable openssh +sys/queue.h portable openssh +sys/tree.h portable openssh +vis.{c,h} portable openssh +xmalloc.{c,h} portable openssh diff --git a/foobar/portable/openbsd-compat/SSL_CTX_use_certificate_chain_mem.c b/foobar/portable/openbsd-compat/SSL_CTX_use_certificate_chain_mem.c new file mode 100644 index 00000000..3a47ff0e --- /dev/null +++ b/foobar/portable/openbsd-compat/SSL_CTX_use_certificate_chain_mem.c @@ -0,0 +1,174 @@ +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * 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 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + +/* + * SSL operations needed when running in a privilege separated environment. + * Adapted from openssl's ssl_rsa.c by Pierre-Yves Ritschard . + */ + +#include "includes.h" + +#include <sys/types.h> + +#include <limits.h> +#include <unistd.h> +#include <stdio.h> + +#include <openssl/err.h> +#include <openssl/bio.h> +#include <openssl/objects.h> +#include <openssl/evp.h> +#include <openssl/x509.h> +#include <openssl/pem.h> +#include <openssl/ssl.h> + +#include "log.h" +#include "ssl.h" + +#define SSL_ECDH_CURVE "prime256v1" + +/* + * Read a bio that contains our certificate in "PEM" format, + * possibly followed by a sequence of CA certificates that should be + * sent to the peer in the Certificate message. + */ +static int +ssl_ctx_use_certificate_chain_bio(SSL_CTX *ctx, BIO *in) +{ + int ret = 0; + X509 *x = NULL; + + ERR_clear_error(); /* clear error stack for SSL_CTX_use_certificate() */ + + x = PEM_read_bio_X509_AUX(in, NULL, SSL_CTX_get_default_passwd_cb(ctx), + SSL_CTX_get_default_passwd_cb_userdata(ctx)); + if (x == NULL) { + SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_PEM_LIB); + goto end; + } + + ret = SSL_CTX_use_certificate(ctx, x); + + if (ERR_peek_error() != 0) + ret = 0; + /* Key/certificate mismatch doesn't imply ret==0 ... */ + if (ret) { + /* + * If we could set up our certificate, now proceed to + * the CA certificates. + */ + X509 *ca; + STACK_OF(X509) *chain; + int r; + unsigned long err; + + SSL_CTX_get_extra_chain_certs_only(ctx, &chain); + if (chain != NULL) { + sk_X509_pop_free(chain, X509_free); + SSL_CTX_clear_extra_chain_certs(ctx); + } + + while ((ca = PEM_read_bio_X509(in, NULL, + SSL_CTX_get_default_passwd_cb(ctx), + SSL_CTX_get_default_passwd_cb_userdata(ctx))) != NULL) { + r = SSL_CTX_add_extra_chain_cert(ctx, ca); + if (!r) { + X509_free(ca); + ret = 0; + goto end; + } + /* + * Note that we must not free r if it was successfully + * added to the chain (while we must free the main + * certificate, since its reference count is increased + * by SSL_CTX_use_certificate). + */ + } + + /* When the while loop ends, it's usually just EOF. */ + err = ERR_peek_last_error(); + if (ERR_GET_LIB(err) == ERR_LIB_PEM && + ERR_GET_REASON(err) == PEM_R_NO_START_LINE) + ERR_clear_error(); + else + ret = 0; /* some real error */ + } + +end: + if (x != NULL) + X509_free(x); + return (ret); +} + +int +SSL_CTX_use_certificate_chain_mem(SSL_CTX *ctx, void *buf, int len) +{ + BIO *in; + int ret = 0; + + in = BIO_new_mem_buf(buf, len); + if (in == NULL) { + SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_BUF_LIB); + goto end; + } + + ret = ssl_ctx_use_certificate_chain_bio(ctx, in); + +end: + BIO_free(in); + return (ret); +} diff --git a/foobar/portable/openbsd-compat/arc4random.c b/foobar/portable/openbsd-compat/arc4random.c new file mode 100644 index 00000000..f5cda877 --- /dev/null +++ b/foobar/portable/openbsd-compat/arc4random.c @@ -0,0 +1,246 @@ +/* OPENBSD ORIGINAL: lib/libc/crypto/arc4random.c */ + +/* $OpenBSD: arc4random.c,v 1.25 2013/10/01 18:34:57 markus Exp $ */ + +/* + * Copyright (c) 1996, David Mazieres <dm@uun.org> + * Copyright (c) 2008, Damien Miller <djm@openbsd.org> + * Copyright (c) 2013, Markus Friedl <markus@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * ChaCha based random number generator for OpenBSD. + */ + +#include "includes.h" + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> + +#include <openssl/rand.h> +#include <openssl/err.h> + +#include "log.h" + +#define KEYSTREAM_ONLY +#include "chacha_private.h" + +#ifdef __GNUC__ +#define inline __inline +#else /* !__GNUC__ */ +#define inline +#endif /* !__GNUC__ */ + +/* OpenSSH isn't multithreaded */ +#define _ARC4_LOCK() +#define _ARC4_UNLOCK() + +#define KEYSZ 32 +#define IVSZ 8 +#define BLOCKSZ 64 +#define RSBUFSZ (16*BLOCKSZ) +static int rs_initialized; +static pid_t rs_stir_pid; +static chacha_ctx rs; /* chacha context for random keystream */ +static u_char rs_buf[RSBUFSZ]; /* keystream blocks */ +static size_t rs_have; /* valid bytes at end of rs_buf */ +static size_t rs_count; /* bytes till reseed */ + +static inline void _rs_rekey(u_char *dat, size_t datlen); + +static inline void +_rs_init(u_char *buf, size_t n) +{ + if (n < KEYSZ + IVSZ) + return; + chacha_keysetup(&rs, buf, KEYSZ * 8, 0); + chacha_ivsetup(&rs, buf + KEYSZ); +} + +static void +_rs_stir(void) +{ + u_char rnd[KEYSZ + IVSZ]; + + if (RAND_bytes(rnd, sizeof(rnd)) <= 0) + fatal("Couldn't obtain random bytes (error %ld)", + ERR_get_error()); + + if (!rs_initialized) { + rs_initialized = 1; + _rs_init(rnd, sizeof(rnd)); + } else + _rs_rekey(rnd, sizeof(rnd)); + memset(rnd, 0, sizeof(rnd)); + + /* invalidate rs_buf */ + rs_have = 0; + memset(rs_buf, 0, RSBUFSZ); + + rs_count = 1600000; +} + +static inline void +_rs_stir_if_needed(size_t len) +{ + pid_t pid = getpid(); + + if (rs_count <= len || !rs_initialized || rs_stir_pid != pid) { + rs_stir_pid = pid; + _rs_stir(); + } else + rs_count -= len; +} + +static inline void +_rs_rekey(u_char *dat, size_t datlen) +{ +#ifndef KEYSTREAM_ONLY + memset(rs_buf, 0,RSBUFSZ); +#endif + /* fill rs_buf with the keystream */ + chacha_encrypt_bytes(&rs, rs_buf, rs_buf, RSBUFSZ); + /* mix in optional user provided data */ + if (dat) { + size_t i, m; + + m = MIN(datlen, KEYSZ + IVSZ); + for (i = 0; i < m; i++) + rs_buf[i] ^= dat[i]; + } + /* immediately reinit for backtracking resistance */ + _rs_init(rs_buf, KEYSZ + IVSZ); + memset(rs_buf, 0, KEYSZ + IVSZ); + rs_have = RSBUFSZ - KEYSZ - IVSZ; +} + +static inline void +_rs_random_buf(void *_buf, size_t n) +{ + u_char *buf = (u_char *)_buf; + size_t m; + + _rs_stir_if_needed(n); + while (n > 0) { + if (rs_have > 0) { + m = MIN(n, rs_have); + memcpy(buf, rs_buf + RSBUFSZ - rs_have, m); + memset(rs_buf + RSBUFSZ - rs_have, 0, m); + buf += m; + n -= m; + rs_have -= m; + } + if (rs_have == 0) + _rs_rekey(NULL, 0); + } +} + +static inline void +_rs_random_u32(uint32_t *val) +{ + _rs_stir_if_needed(sizeof(*val)); + if (rs_have < sizeof(*val)) + _rs_rekey(NULL, 0); + memcpy(val, rs_buf + RSBUFSZ - rs_have, sizeof(*val)); + memset(rs_buf + RSBUFSZ - rs_have, 0, sizeof(*val)); + rs_have -= sizeof(*val); + return; +} + +void +arc4random_stir(void) +{ + _ARC4_LOCK(); + _rs_stir(); + _ARC4_UNLOCK(); +} + +void +arc4random_addrandom(u_char *dat, int datlen) +{ + int m; + + _ARC4_LOCK(); + if (!rs_initialized) + _rs_stir(); + while (datlen > 0) { + m = MIN(datlen, KEYSZ + IVSZ); + _rs_rekey(dat, m); + dat += m; + datlen -= m; + } + _ARC4_UNLOCK(); +} + +uint32_t +arc4random(void) +{ + uint32_t val; + + _ARC4_LOCK(); + _rs_random_u32(&val); + _ARC4_UNLOCK(); + return val; +} + +/* + * If we are providing arc4random, then we can provide a more efficient + * arc4random_buf(). + */ +void +arc4random_buf(void *buf, size_t n) +{ + _ARC4_LOCK(); + _rs_random_buf(buf, n); + _ARC4_UNLOCK(); +} + +/* + * Calculate a uniformly distributed random number less than upper_bound + * avoiding "modulo bias". + * + * Uniformity is achieved by generating new random numbers until the one + * returned is outside the range [0, 2**32 % upper_bound). This + * guarantees the selected random number will be inside + * [2**32 % upper_bound, 2**32) which maps back to [0, upper_bound) + * after reduction modulo upper_bound. + */ +uint32_t +arc4random_uniform(uint32_t upper_bound) +{ + uint32_t r, min; + + if (upper_bound < 2) + return 0; + + /* 2**32 % x == (2**32 - x) % x */ + min = -upper_bound % upper_bound; + + /* + * This could theoretically loop forever but each retry has + * p > 0.5 (worst case, usually far better) of selecting a + * number inside the range we need, so it should rarely need + * to re-roll. + */ + for (;;) { + r = arc4random(); + if (r >= min) + break; + } + + return r % upper_bound; +} diff --git a/foobar/portable/openbsd-compat/base64.c b/foobar/portable/openbsd-compat/base64.c new file mode 100644 index 00000000..a3c5782b --- /dev/null +++ b/foobar/portable/openbsd-compat/base64.c @@ -0,0 +1,306 @@ +/* $OpenBSD: base64.c,v 1.5 2006/10/21 09:55:03 otto Exp $ */ + +/* + * Copyright (c) 1996 by Internet Software Consortium. + * + * 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 INTERNET SOFTWARE CONSORTIUM DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE + * CONSORTIUM 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. + */ + +/* + * Portions Copyright (c) 1995 by International Business Machines, Inc. + * + * International Business Machines, Inc. (hereinafter called IBM) grants + * permission under its copyrights to use, copy, modify, and distribute this + * Software with or without fee, provided that the above copyright notice and + * all paragraphs of this notice appear in all copies, and that the name of IBM + * not be used in connection with the marketing of any product incorporating + * the Software or modifications thereof, without specific, written prior + * permission. + * + * To the extent it has a right to do so, IBM grants an immunity from suit + * under its patents, if any, for the use, sale or manufacture of products to + * the extent that such products are used for performing Domain Name System + * dynamic updates in TCP/IP networks by means of the Software. No immunity is + * granted for any product per se or for any other function of any product. + * + * THE SOFTWARE IS PROVIDED "AS IS", AND IBM DISCLAIMS ALL WARRANTIES, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE. IN NO EVENT SHALL IBM BE LIABLE FOR ANY SPECIAL, + * DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE, EVEN + * IF IBM IS APPRISED OF THE POSSIBILITY OF SUCH DAMAGES. + */ + +/* OPENBSD ORIGINAL: lib/libc/net/base64.c */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <ctype.h> +#include <stdio.h> + +#include <stdlib.h> +#include <string.h> + +#include "base64.h" + +static const char Base64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char Pad64 = '='; + +/* (From RFC1521 and draft-ietf-dnssec-secext-03.txt) + The following encoding technique is taken from RFC 1521 by Borenstein + and Freed. It is reproduced here in a slightly edited form for + convenience. + + A 65-character subset of US-ASCII is used, enabling 6 bits to be + represented per printable character. (The extra 65th character, "=", + is used to signify a special processing function.) + + The encoding process represents 24-bit groups of input bits as output + strings of 4 encoded characters. Proceeding from left to right, a + 24-bit input group is formed by concatenating 3 8-bit input groups. + These 24 bits are then treated as 4 concatenated 6-bit groups, each + of which is translated into a single digit in the base64 alphabet. + + Each 6-bit group is used as an index into an array of 64 printable + characters. The character referenced by the index is placed in the + output string. + + Table 1: The Base64 Alphabet + + Value Encoding Value Encoding Value Encoding Value Encoding + 0 A 17 R 34 i 51 z + 1 B 18 S 35 j 52 0 + 2 C 19 T 36 k 53 1 + 3 D 20 U 37 l 54 2 + 4 E 21 V 38 m 55 3 + 5 F 22 W 39 n 56 4 + 6 G 23 X 40 o 57 5 + 7 H 24 Y 41 p 58 6 + 8 I 25 Z 42 q 59 7 + 9 J 26 a 43 r 60 8 + 10 K 27 b 44 s 61 9 + 11 L 28 c 45 t 62 + + 12 M 29 d 46 u 63 / + 13 N 30 e 47 v + 14 O 31 f 48 w (pad) = + 15 P 32 g 49 x + 16 Q 33 h 50 y + + Special processing is performed if fewer than 24 bits are available + at the end of the data being encoded. A full encoding quantum is + always completed at the end of a quantity. When fewer than 24 input + bits are available in an input group, zero bits are added (on the + right) to form an integral number of 6-bit groups. Padding at the + end of the data is performed using the '=' character. + + Since all base64 input is an integral number of octets, only the + ------------------------------------------------- + following cases can arise: + + (1) the final quantum of encoding input is an integral + multiple of 24 bits; here, the final unit of encoded + output will be an integral multiple of 4 characters + with no "=" padding, + (2) the final quantum of encoding input is exactly 8 bits; + here, the final unit of encoded output will be two + characters followed by two "=" padding characters, or + (3) the final quantum of encoding input is exactly 16 bits; + here, the final unit of encoded output will be three + characters followed by one "=" padding character. + */ + +int +b64_ntop(u_char const *src, size_t srclength, char *target, size_t targsize) +{ + size_t datalength = 0; + u_char input[3]; + u_char output[4]; + u_int i; + + while (2 < srclength) { + input[0] = *src++; + input[1] = *src++; + input[2] = *src++; + srclength -= 3; + + output[0] = input[0] >> 2; + output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4); + output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6); + output[3] = input[2] & 0x3f; + + if (datalength + 4 > targsize) + return (-1); + target[datalength++] = Base64[output[0]]; + target[datalength++] = Base64[output[1]]; + target[datalength++] = Base64[output[2]]; + target[datalength++] = Base64[output[3]]; + } + + /* Now we worry about padding. */ + if (0 != srclength) { + /* Get what's left. */ + input[0] = input[1] = input[2] = '\0'; + for (i = 0; i < srclength; i++) + input[i] = *src++; + + output[0] = input[0] >> 2; + output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4); + output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6); + + if (datalength + 4 > targsize) + return (-1); + target[datalength++] = Base64[output[0]]; + target[datalength++] = Base64[output[1]]; + if (srclength == 1) + target[datalength++] = Pad64; + else + target[datalength++] = Base64[output[2]]; + target[datalength++] = Pad64; + } + if (datalength >= targsize) + return (-1); + target[datalength] = '\0'; /* Returned value doesn't count \0. */ + return (datalength); +} + +/* skips all whitespace anywhere. + converts characters, four at a time, starting at (or after) + src from base - 64 numbers into three 8 bit bytes in the target area. + it returns the number of data bytes stored at the target, or -1 on error. + */ + +int +b64_pton(char const *src, u_char *target, size_t targsize) +{ + u_int tarindex, state; + int ch; + char *pos; + + state = 0; + tarindex = 0; + + while ((ch = *src++) != '\0') { + if (isspace(ch)) /* Skip whitespace anywhere. */ + continue; + + if (ch == Pad64) + break; + + pos = strchr(Base64, ch); + if (pos == 0) /* A non-base64 character. */ + return (-1); + + switch (state) { + case 0: + if (target) { + if (tarindex >= targsize) + return (-1); + target[tarindex] = (pos - Base64) << 2; + } + state = 1; + break; + case 1: + if (target) { + if (tarindex + 1 >= targsize) + return (-1); + target[tarindex] |= (pos - Base64) >> 4; + target[tarindex+1] = ((pos - Base64) & 0x0f) + << 4 ; + } + tarindex++; + state = 2; + break; + case 2: + if (target) { + if (tarindex + 1 >= targsize) + return (-1); + target[tarindex] |= (pos - Base64) >> 2; + target[tarindex+1] = ((pos - Base64) & 0x03) + << 6; + } + tarindex++; + state = 3; + break; + case 3: + if (target) { + if (tarindex >= targsize) + return (-1); + target[tarindex] |= (pos - Base64); + } + tarindex++; + state = 0; + break; + } + } + + /* + * We are done decoding Base-64 chars. Let's see if we ended + * on a byte boundary, and/or with erroneous trailing characters. + */ + + if (ch == Pad64) { /* We got a pad char. */ + ch = *src++; /* Skip it, get next. */ + switch (state) { + case 0: /* Invalid = in first position */ + case 1: /* Invalid = in second position */ + return (-1); + + case 2: /* Valid, means one byte of info */ + /* Skip any number of spaces. */ + for (; ch != '\0'; ch = *src++) + if (!isspace(ch)) + break; + /* Make sure there is another trailing = sign. */ + if (ch != Pad64) + return (-1); + ch = *src++; /* Skip the = */ + /* Fall through to "single trailing =" case. */ + /* FALLTHROUGH */ + + case 3: /* Valid, means two bytes of info */ + /* + * We know this char is an =. Is there anything but + * whitespace after it? + */ + for (; ch != '\0'; ch = *src++) + if (!isspace(ch)) + return (-1); + + /* + * Now make sure for cases 2 and 3 that the "extra" + * bits that slopped past the last full byte were + * zeros. If we don't check them, they become a + * subliminal channel. + */ + if (target && target[tarindex] != 0) + return (-1); + } + } else { + /* + * We ended by seeing the end of the string. Make sure we + * have no partial bytes lying around. + */ + if (state != 0) + return (-1); + } + + return (tarindex); +} diff --git a/foobar/portable/openbsd-compat/base64.h b/foobar/portable/openbsd-compat/base64.h new file mode 100644 index 00000000..732c6b3f --- /dev/null +++ b/foobar/portable/openbsd-compat/base64.h @@ -0,0 +1,65 @@ +/* $Id: base64.h,v 1.6 2003/08/29 16:59:52 mouring Exp $ */ + +/* + * Copyright (c) 1996 by Internet Software Consortium. + * + * 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 INTERNET SOFTWARE CONSORTIUM DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE + * CONSORTIUM 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. + */ + +/* + * Portions Copyright (c) 1995 by International Business Machines, Inc. + * + * International Business Machines, Inc. (hereinafter called IBM) grants + * permission under its copyrights to use, copy, modify, and distribute this + * Software with or without fee, provided that the above copyright notice and + * all paragraphs of this notice appear in all copies, and that the name of IBM + * not be used in connection with the marketing of any product incorporating + * the Software or modifications thereof, without specific, written prior + * permission. + * + * To the extent it has a right to do so, IBM grants an immunity from suit + * under its patents, if any, for the use, sale or manufacture of products to + * the extent that such products are used for performing Domain Name System + * dynamic updates in TCP/IP networks by means of the Software. No immunity is + * granted for any product per se or for any other function of any product. + * + * THE SOFTWARE IS PROVIDED "AS IS", AND IBM DISCLAIMS ALL WARRANTIES, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE. IN NO EVENT SHALL IBM BE LIABLE FOR ANY SPECIAL, + * DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE, EVEN + * IF IBM IS APPRISED OF THE POSSIBILITY OF SUCH DAMAGES. + */ + +#ifndef _BSD_BASE64_H +#define _BSD_BASE64_H + +#include "includes.h" + +#ifndef HAVE___B64_NTOP +# ifndef HAVE_B64_NTOP +int b64_ntop(u_char const *src, size_t srclength, char *target, + size_t targsize); +# endif /* !HAVE_B64_NTOP */ +# define __b64_ntop(a,b,c,d) b64_ntop(a,b,c,d) +#endif /* HAVE___B64_NTOP */ + +#ifndef HAVE___B64_PTON +# ifndef HAVE_B64_PTON +int b64_pton(char const *src, u_char *target, size_t targsize); +# endif /* !HAVE_B64_PTON */ +# define __b64_pton(a,b,c) b64_pton(a,b,c) +#endif /* HAVE___B64_PTON */ + +#endif /* _BSD_BASE64_H */ diff --git a/foobar/portable/openbsd-compat/basename.c b/foobar/portable/openbsd-compat/basename.c new file mode 100644 index 00000000..ffa5c898 --- /dev/null +++ b/foobar/portable/openbsd-compat/basename.c @@ -0,0 +1,67 @@ +/* $OpenBSD: basename.c,v 1.14 2005/08/08 08:05:33 espie Exp $ */ + +/* + * Copyright (c) 1997, 2004 Todd C. Miller <Todd.Miller@courtesan.com> + * + * 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. + */ + +/* OPENBSD ORIGINAL: lib/libc/gen/basename.c */ + +#include "includes.h" +#ifndef HAVE_BASENAME +#include <errno.h> +#include <string.h> + +char * +basename(const char *path) +{ + static char bname[MAXPATHLEN]; + size_t len; + const char *endp, *startp; + + /* Empty or NULL string gets treated as "." */ + if (path == NULL || *path == '\0') { + bname[0] = '.'; + bname[1] = '\0'; + return (bname); + } + + /* Strip any trailing slashes */ + endp = path + strlen(path) - 1; + while (endp > path && *endp == '/') + endp--; + + /* All slashes becomes "/" */ + if (endp == path && *endp == '/') { + bname[0] = '/'; + bname[1] = '\0'; + return (bname); + } + + /* Find the start of the base */ + startp = endp; + while (startp > path && *(startp - 1) != '/') + startp--; + + len = endp - startp + 1; + if (len >= sizeof(bname)) { + errno = ENAMETOOLONG; + return (NULL); + } + memcpy(bname, startp, len); + bname[len] = '\0'; + return (bname); +} + +#endif /* !defined(HAVE_BASENAME) */ diff --git a/foobar/portable/openbsd-compat/bsd-err.c b/foobar/portable/openbsd-compat/bsd-err.c new file mode 100644 index 00000000..a7823fad --- /dev/null +++ b/foobar/portable/openbsd-compat/bsd-err.c @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2015 Tim Rice <tim@multitalents.net> + * + * 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 "includes.h" + +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +extern char *__progname; + +void +err(int r, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + fprintf(stderr, "%s: ", __progname); + if (fmt != NULL) { + vfprintf(stderr, fmt, args); + fprintf(stderr, ": "); + } + fprintf(stderr, "%s\n", strerror(errno)); + va_end(args); + exit(r); +} + +void +errx(int r, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + fprintf(stderr, "%s: ", __progname); + if (fmt != NULL) + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); + va_end(args); + exit(r); +} + +void +warn(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + fprintf(stderr, "%s: ", __progname); + if (fmt != NULL) { + vfprintf(stderr, fmt, args); + fprintf(stderr, ": "); + } + fprintf(stderr, "%s\n", strerror(errno)); + va_end(args); +} + +void +warnx(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + fprintf(stderr, "%s: ", __progname); + if (fmt != NULL) + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); + va_end(args); +} diff --git a/foobar/portable/openbsd-compat/bsd-err.h b/foobar/portable/openbsd-compat/bsd-err.h new file mode 100644 index 00000000..f75d0eb4 --- /dev/null +++ b/foobar/portable/openbsd-compat/bsd-err.h @@ -0,0 +1,29 @@ +/* + * Public domain + * err.h compatibility shim + */ + +#ifndef HAVE_ERR_H + +#ifndef LIBCRYPTOCOMPAT_ERR_H +#define LIBCRYPTOCOMPAT_ERR_H + +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#define err(exitcode, format, args...) \ + errx(exitcode, format ": %s", ## args, strerror(errno)) + +#define errx(exitcode, format, args...) \ + do { warnx(format, ## args); exit(exitcode); } while (0) + +#define warn(format, args...) \ + warnx(format ": %s", ## args, strerror(errno)) + +#define warnx(format, args...) \ + fprintf(stderr, format "\n", ## args) + +#endif + +#endif diff --git a/foobar/portable/openbsd-compat/bsd-misc.h b/foobar/portable/openbsd-compat/bsd-misc.h new file mode 100644 index 00000000..c638462d --- /dev/null +++ b/foobar/portable/openbsd-compat/bsd-misc.h @@ -0,0 +1,24 @@ +/* $Id: bsd-misc.h,v 1.25 2013/08/04 11:48:41 dtucker Exp $ */ + +/* + * Copyright (c) 1999-2004 Damien Miller <djm@mindrot.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _BSD_MISC_H +#define _BSD_MISC_H + +#include "includes.h" + +#endif /* _BSD_MISC_H */ diff --git a/foobar/portable/openbsd-compat/bsd-vis.h b/foobar/portable/openbsd-compat/bsd-vis.h new file mode 100644 index 00000000..d1286c99 --- /dev/null +++ b/foobar/portable/openbsd-compat/bsd-vis.h @@ -0,0 +1,95 @@ +/* $OpenBSD: vis.h,v 1.11 2005/08/09 19:38:31 millert Exp $ */ +/* $NetBSD: vis.h,v 1.4 1994/10/26 00:56:41 cgd Exp $ */ + +/*- + * Copyright (c) 1990 The Regents of the University of California. + * 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. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * @(#)vis.h 5.9 (Berkeley) 4/3/91 + */ + +/* OPENBSD ORIGINAL: include/vis.h */ + +#include "includes.h" +#if !defined(HAVE_STRNVIS) || defined(BROKEN_STRNVIS) + +#ifndef _VIS_H_ +#define _VIS_H_ + +#include <sys/types.h> +#include <limits.h> + +/* + * to select alternate encoding format + */ +#define VIS_OCTAL 0x01 /* use octal \ddd format */ +#define VIS_CSTYLE 0x02 /* use \[nrft0..] where appropriate */ + +/* + * to alter set of characters encoded (default is to encode all + * non-graphic except space, tab, and newline). + */ +#define VIS_SP 0x04 /* also encode space */ +#define VIS_TAB 0x08 /* also encode tab */ +#define VIS_NL 0x10 /* also encode newline */ +#define VIS_WHITE (VIS_SP | VIS_TAB | VIS_NL) +#define VIS_SAFE 0x20 /* only encode "unsafe" characters */ + +/* + * other + */ +#define VIS_NOSLASH 0x40 /* inhibit printing '\' */ +#define VIS_GLOB 0x100 /* encode glob(3) magics and '#' */ + +/* + * unvis return codes + */ +#define UNVIS_VALID 1 /* character valid */ +#define UNVIS_VALIDPUSH 2 /* character valid, push back passed char */ +#define UNVIS_NOCHAR 3 /* valid sequence, no character produced */ +#define UNVIS_SYNBAD -1 /* unrecognized escape sequence */ +#define UNVIS_ERROR -2 /* decoder in unknown state (unrecoverable) */ + +/* + * unvis flags + */ +#define UNVIS_END 1 /* no more characters */ + +char *vis(char *, int, int, int); +int strvis(char *, const char *, int); +int strnvis(char *, const char *, size_t, int) + __attribute__ ((__bounded__(__string__,1,3))); +int strvisx(char *, const char *, size_t, int) + __attribute__ ((__bounded__(__string__,1,3))); +int strunvis(char *, const char *); +int unvis(char *, char, int *, int); +ssize_t strnunvis(char *, const char *, size_t) + __attribute__ ((__bounded__(__string__,1,3))); + +#endif /* !_VIS_H_ */ + +#endif /* !HAVE_STRNVIS || BROKEN_STRNVIS */ diff --git a/foobar/portable/openbsd-compat/bsd-waitpid.c b/foobar/portable/openbsd-compat/bsd-waitpid.c new file mode 100644 index 00000000..3ef68a53 --- /dev/null +++ b/foobar/portable/openbsd-compat/bsd-waitpid.c @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000 Ben Lindstrom. 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 AUTHOR ``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 AUTHOR 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 "includes.h" + +#include <errno.h> +#include <sys/wait.h> +#include "bsd-waitpid.h" + +pid_t +waitpid(int pid, int *stat_loc, int options) +{ + union wait statusp; + pid_t wait_pid; + + if (pid <= 0) { + if (pid != -1) { + errno = EINVAL; + return (-1); + } + /* wait4() wants pid=0 for indiscriminate wait. */ + pid = 0; + } + wait_pid = wait4(pid, &statusp, options, NULL); + if (stat_loc) + *stat_loc = (int) statusp.w_status; + + return (wait_pid); +} diff --git a/foobar/portable/openbsd-compat/bsd-waitpid.h b/foobar/portable/openbsd-compat/bsd-waitpid.h new file mode 100644 index 00000000..2d853db6 --- /dev/null +++ b/foobar/portable/openbsd-compat/bsd-waitpid.h @@ -0,0 +1,51 @@ +/* $Id: bsd-waitpid.h,v 1.5 2003/08/29 16:59:52 mouring Exp $ */ + +/* + * Copyright (c) 2000 Ben Lindstrom. 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 AUTHOR ``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 AUTHOR 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 _BSD_WAITPID_H +#define _BSD_WAITPID_H + +#ifndef HAVE_WAITPID +/* Clean out any potental issues */ +#undef WIFEXITED +#undef WIFSTOPPED +#undef WIFSIGNALED + +/* Define required functions to mimic a POSIX look and feel */ +#define _W_INT(w) (*(int*)&(w)) /* convert union wait to int */ +#define WIFEXITED(w) (!((_W_INT(w)) & 0377)) +#define WIFSTOPPED(w) ((_W_INT(w)) & 0100) +#define WIFSIGNALED(w) (!WIFEXITED(w) && !WIFSTOPPED(w)) +#define WEXITSTATUS(w) (int)(WIFEXITED(w) ? ((_W_INT(w) >> 8) & 0377) : -1) +#define WTERMSIG(w) (int)(WIFSIGNALED(w) ? (_W_INT(w) & 0177) : -1) +#define WCOREFLAG 0x80 +#define WCOREDUMP(w) ((_W_INT(w)) & WCOREFLAG) + +/* Prototype */ +pid_t waitpid(int, int *, int); + +#endif /* !HAVE_WAITPID */ +#endif /* _BSD_WAITPID_H */ diff --git a/foobar/portable/openbsd-compat/chacha_private.h b/foobar/portable/openbsd-compat/chacha_private.h new file mode 100644 index 00000000..46613646 --- /dev/null +++ b/foobar/portable/openbsd-compat/chacha_private.h @@ -0,0 +1,224 @@ +/* +chacha-merged.c version 20080118 +D. J. Bernstein +Public domain. +*/ + +/* $OpenBSD: chacha_private.h,v 1.2 2013/10/04 07:02:27 djm Exp $ */ + +#include <sys/types.h> + +typedef unsigned char u8; +typedef unsigned int u32; + +typedef struct +{ + u32 input[16]; /* could be compressed */ +} chacha_ctx; + +#define U8C(v) (v##U) +#define U32C(v) (v##U) + +#define U8V(v) ((u8)(v) & U8C(0xFF)) +#define U32V(v) ((u32)(v) & U32C(0xFFFFFFFF)) + +#define ROTL32(v, n) \ + (U32V((v) << (n)) | ((v) >> (32 - (n)))) + +#define U8TO32_LITTLE(p) \ + (((u32)((p)[0]) ) | \ + ((u32)((p)[1]) << 8) | \ + ((u32)((p)[2]) << 16) | \ + ((u32)((p)[3]) << 24)) + +#define U32TO8_LITTLE(p, v) \ + do { \ + (p)[0] = U8V((v) ); \ + (p)[1] = U8V((v) >> 8); \ + (p)[2] = U8V((v) >> 16); \ + (p)[3] = U8V((v) >> 24); \ + } while (0) + +#define ROTATE(v,c) (ROTL32(v,c)) +#define XOR(v,w) ((v) ^ (w)) +#define PLUS(v,w) (U32V((v) + (w))) +#define PLUSONE(v) (PLUS((v),1)) + +#define QUARTERROUND(a,b,c,d) \ + a = PLUS(a,b); d = ROTATE(XOR(d,a),16); \ + c = PLUS(c,d); b = ROTATE(XOR(b,c),12); \ + a = PLUS(a,b); d = ROTATE(XOR(d,a), 8); \ + c = PLUS(c,d); b = ROTATE(XOR(b,c), 7); + +static const char sigma[16] = "expand 32-byte k"; +static const char tau[16] = "expand 16-byte k"; + +static void +chacha_keysetup(chacha_ctx *x,const u8 *k,u32 kbits,u32 ivbits) +{ + const char *constants; + + x->input[4] = U8TO32_LITTLE(k + 0); + x->input[5] = U8TO32_LITTLE(k + 4); + x->input[6] = U8TO32_LITTLE(k + 8); + x->input[7] = U8TO32_LITTLE(k + 12); + if (kbits == 256) { /* recommended */ + k += 16; + constants = sigma; + } else { /* kbits == 128 */ + constants = tau; + } + x->input[8] = U8TO32_LITTLE(k + 0); + x->input[9] = U8TO32_LITTLE(k + 4); + x->input[10] = U8TO32_LITTLE(k + 8); + x->input[11] = U8TO32_LITTLE(k + 12); + x->input[0] = U8TO32_LITTLE(constants + 0); + x->input[1] = U8TO32_LITTLE(constants + 4); + x->input[2] = U8TO32_LITTLE(constants + 8); + x->input[3] = U8TO32_LITTLE(constants + 12); +} + +static void +chacha_ivsetup(chacha_ctx *x,const u8 *iv) +{ + x->input[12] = 0; + x->input[13] = 0; + x->input[14] = U8TO32_LITTLE(iv + 0); + x->input[15] = U8TO32_LITTLE(iv + 4); +} + +static void +chacha_encrypt_bytes(chacha_ctx *x,const u8 *m,u8 *c,u32 bytes) +{ + u32 x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; + u32 j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; + u8 *ctarget = NULL; + u8 tmp[64]; + u_int i; + + if (!bytes) return; + + j0 = x->input[0]; + j1 = x->input[1]; + j2 = x->input[2]; + j3 = x->input[3]; + j4 = x->input[4]; + j5 = x->input[5]; + j6 = x->input[6]; + j7 = x->input[7]; + j8 = x->input[8]; + j9 = x->input[9]; + j10 = x->input[10]; + j11 = x->input[11]; + j12 = x->input[12]; + j13 = x->input[13]; + j14 = x->input[14]; + j15 = x->input[15]; + + for (;;) { + if (bytes < 64) { + for (i = 0;i < bytes;++i) tmp[i] = m[i]; + m = tmp; + ctarget = c; + c = tmp; + } + x0 = j0; + x1 = j1; + x2 = j2; + x3 = j3; + x4 = j4; + x5 = j5; + x6 = j6; + x7 = j7; + x8 = j8; + x9 = j9; + x10 = j10; + x11 = j11; + x12 = j12; + x13 = j13; + x14 = j14; + x15 = j15; + for (i = 20;i > 0;i -= 2) { + QUARTERROUND( x0, x4, x8,x12) + QUARTERROUND( x1, x5, x9,x13) + QUARTERROUND( x2, x6,x10,x14) + QUARTERROUND( x3, x7,x11,x15) + QUARTERROUND( x0, x5,x10,x15) + QUARTERROUND( x1, x6,x11,x12) + QUARTERROUND( x2, x7, x8,x13) + QUARTERROUND( x3, x4, x9,x14) + } + x0 = PLUS(x0,j0); + x1 = PLUS(x1,j1); + x2 = PLUS(x2,j2); + x3 = PLUS(x3,j3); + x4 = PLUS(x4,j4); + x5 = PLUS(x5,j5); + x6 = PLUS(x6,j6); + x7 = PLUS(x7,j7); + x8 = PLUS(x8,j8); + x9 = PLUS(x9,j9); + x10 = PLUS(x10,j10); + x11 = PLUS(x11,j11); + x12 = PLUS(x12,j12); + x13 = PLUS(x13,j13); + x14 = PLUS(x14,j14); + x15 = PLUS(x15,j15); + +#ifndef KEYSTREAM_ONLY + x0 = XOR(x0,U8TO32_LITTLE(m + 0)); + x1 = XOR(x1,U8TO32_LITTLE(m + 4)); + x2 = XOR(x2,U8TO32_LITTLE(m + 8)); + x3 = XOR(x3,U8TO32_LITTLE(m + 12)); + x4 = XOR(x4,U8TO32_LITTLE(m + 16)); + x5 = XOR(x5,U8TO32_LITTLE(m + 20)); + x6 = XOR(x6,U8TO32_LITTLE(m + 24)); + x7 = XOR(x7,U8TO32_LITTLE(m + 28)); + x8 = XOR(x8,U8TO32_LITTLE(m + 32)); + x9 = XOR(x9,U8TO32_LITTLE(m + 36)); + x10 = XOR(x10,U8TO32_LITTLE(m + 40)); + x11 = XOR(x11,U8TO32_LITTLE(m + 44)); + x12 = XOR(x12,U8TO32_LITTLE(m + 48)); + x13 = XOR(x13,U8TO32_LITTLE(m + 52)); + x14 = XOR(x14,U8TO32_LITTLE(m + 56)); + x15 = XOR(x15,U8TO32_LITTLE(m + 60)); +#endif + + j12 = PLUSONE(j12); + if (!j12) { + j13 = PLUSONE(j13); + /* stopping at 2^70 bytes per nonce is user's responsibility */ + } + + U32TO8_LITTLE(c + 0,x0); + U32TO8_LITTLE(c + 4,x1); + U32TO8_LITTLE(c + 8,x2); + U32TO8_LITTLE(c + 12,x3); + U32TO8_LITTLE(c + 16,x4); + U32TO8_LITTLE(c + 20,x5); + U32TO8_LITTLE(c + 24,x6); + U32TO8_LITTLE(c + 28,x7); + U32TO8_LITTLE(c + 32,x8); + U32TO8_LITTLE(c + 36,x9); + U32TO8_LITTLE(c + 40,x10); + U32TO8_LITTLE(c + 44,x11); + U32TO8_LITTLE(c + 48,x12); + U32TO8_LITTLE(c + 52,x13); + U32TO8_LITTLE(c + 56,x14); + U32TO8_LITTLE(c + 60,x15); + + if (bytes <= 64) { + if (bytes < 64) { + for (i = 0;i < bytes;++i) ctarget[i] = c[i]; + } + x->input[12] = j12; + x->input[13] = j13; + return; + } + bytes -= 64; + c += 64; +#ifndef KEYSTREAM_ONLY + m += 64; +#endif + } +} diff --git a/foobar/portable/openbsd-compat/clock_gettime.c b/foobar/portable/openbsd-compat/clock_gettime.c new file mode 100644 index 00000000..6c1ef0d4 --- /dev/null +++ b/foobar/portable/openbsd-compat/clock_gettime.c @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2012 Charles Longeau <chl@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#ifdef HAVE_MACH_MACH_TIME_H +#include <mach/mach_time.h> +#endif +#include <sys/time.h> +#include <time.h> + +#if !defined(HAVE_CLOCK_GETTIME) +int +clock_gettime(int clock_id, struct timespec *tp) +{ + int ret = 0; + uint64_t time; + mach_timebase_info_data_t info; + static double scaling_factor = 0; + +#if 0 + struct timeval tv; + + ret = gettimeofday(&tv, NULL); + TIMEVAL_TO_TIMESPEC(&tv, tp); +#endif + +/* based on http://code-factor.blogspot.fr/2009/11/monotonic-timers.html */ + + time = mach_absolute_time(); + + if (scaling_factor == 0) { + ret = (int) mach_timebase_info(&info); + if (ret != 0) + fatal("mach_timebase_info failed"); + scaling_factor = info.numer/info.denom; + } + + time *= scaling_factor; + + tp->tv_sec = time / 1000000000; + tp->tv_nsec = time % 1000000000; + + return (ret); +} +#endif diff --git a/foobar/portable/openbsd-compat/closefrom.c b/foobar/portable/openbsd-compat/closefrom.c new file mode 100644 index 00000000..528949a6 --- /dev/null +++ b/foobar/portable/openbsd-compat/closefrom.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2004-2005 Todd C. Miller <Todd.Miller@courtesan.com> + * + * 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 "includes.h" + +#include <sys/types.h> +#include <sys/param.h> +#include <unistd.h> +#include <stdio.h> +#ifdef HAVE_FCNTL_H +# include <fcntl.h> +#endif +#include <limits.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <unistd.h> +#ifdef HAVE_DIRENT_H +# include <dirent.h> +# define NAMLEN(dirent) strlen((dirent)->d_name) +#else +# define dirent direct +# define NAMLEN(dirent) (dirent)->d_namlen +# ifdef HAVE_SYS_NDIR_H +# include <sys/ndir.h> +# endif +# ifdef HAVE_SYS_DIR_H +# include <sys/dir.h> +# endif +# ifdef HAVE_NDIR_H +# include <ndir.h> +# endif +#endif + +#ifndef OPEN_MAX +# define OPEN_MAX 256 +#endif + +#if 0 +__unused static const char rcsid[] = "$Sudo: closefrom.c,v 1.11 2006/08/17 15:26:54 millert Exp $"; +#endif /* lint */ + +/* + * Close all file descriptors greater than or equal to lowfd. + */ +#ifdef HAVE_FCNTL_CLOSEM +void +closefrom(int lowfd) +{ + (void) fcntl(lowfd, F_CLOSEM, 0); +} +#else +void +closefrom(int lowfd) +{ + long fd, maxfd; +#if defined(HAVE_DIRFD) && defined(HAVE_PROC_PID) + char fdpath[PATH_MAX], *endp; + struct dirent *dent; + DIR *dirp; + int len; + + /* Check for a /proc/$$/fd directory. */ + len = snprintf(fdpath, sizeof(fdpath), "/proc/%ld/fd", (long)getpid()); + if (len > 0 && (size_t)len <= sizeof(fdpath) && (dirp = opendir(fdpath))) { + while ((dent = readdir(dirp)) != NULL) { + fd = strtol(dent->d_name, &endp, 10); + if (dent->d_name != endp && *endp == '\0' && + fd >= 0 && fd < INT_MAX && fd >= lowfd && fd != dirfd(dirp)) + (void) close((int) fd); + } + (void) closedir(dirp); + } else +#endif + { + /* + * Fall back on sysconf() or getdtablesize(). We avoid checking + * resource limits since it is possible to open a file descriptor + * and then drop the rlimit such that it is below the open fd. + */ +#ifdef HAVE_SYSCONF + maxfd = sysconf(_SC_OPEN_MAX); +#else + maxfd = getdtablesize(); +#endif /* HAVE_SYSCONF */ + if (maxfd < 0) + maxfd = OPEN_MAX; + + for (fd = lowfd; fd < maxfd; fd++) + (void) close((int) fd); + } +} +#endif /* !HAVE_FCNTL_CLOSEM */ + diff --git a/foobar/portable/openbsd-compat/crypt_checkpass.c b/foobar/portable/openbsd-compat/crypt_checkpass.c new file mode 100644 index 00000000..d10b3a57 --- /dev/null +++ b/foobar/portable/openbsd-compat/crypt_checkpass.c @@ -0,0 +1,33 @@ +/* OPENBSD ORIGINAL: lib/libc/crypt/cryptutil.c */ + +#include "includes.h" +#include <errno.h> +#ifdef HAVE_CRYPT_H +#include <crypt.h> +#endif +#include <string.h> +#include <unistd.h> + +int +crypt_checkpass(const char *pass, const char *goodhash) +{ + char *c; + + if (goodhash == NULL) + goto fail; + + /* empty password */ + if (strlen(goodhash) == 0 && strlen(pass) == 0) + return 0; + + c = crypt(pass, goodhash); + if (c == NULL) + goto fail; + + if (strcmp(c, goodhash) == 0) + return 0; + +fail: + errno = EACCES; + return -1; +} diff --git a/foobar/portable/openbsd-compat/daemon.c b/foobar/portable/openbsd-compat/daemon.c new file mode 100644 index 00000000..3efe14c6 --- /dev/null +++ b/foobar/portable/openbsd-compat/daemon.c @@ -0,0 +1,82 @@ +/* $OpenBSD: daemon.c,v 1.6 2005/08/08 08:05:33 espie Exp $ */ +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. 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. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + */ + +/* OPENBSD ORIGINAL: lib/libc/gen/daemon.c */ + +#include "includes.h" + +#ifndef HAVE_DAEMON + +#include <sys/types.h> + +#ifdef HAVE_SYS_STAT_H +# include <sys/stat.h> +#endif + +#ifdef HAVE_FCNTL_H +# include <fcntl.h> +#endif + +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif + +int +daemon(int nochdir, int noclose) +{ + int fd; + + switch (fork()) { + case -1: + return (-1); + case 0: + break; + default: + _exit(0); + } + + if (setsid() == -1) + return (-1); + + if (!nochdir) + (void)chdir("/"); + + if (!noclose && (fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) { + (void)dup2(fd, STDIN_FILENO); + (void)dup2(fd, STDOUT_FILENO); + (void)dup2(fd, STDERR_FILENO); + if (fd > 2) + (void)close (fd); + } + return (0); +} + +#endif /* !HAVE_DAEMON */ + diff --git a/foobar/portable/openbsd-compat/defines.h b/foobar/portable/openbsd-compat/defines.h new file mode 100644 index 00000000..a6e528eb --- /dev/null +++ b/foobar/portable/openbsd-compat/defines.h @@ -0,0 +1,510 @@ +/* + * Copyright (c) 2016 Gilles Chehade <gilles@poolp.org>. All rights reserved. + * Copyright (c) 1999-2003 Damien Miller. 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 AUTHOR ``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 AUTHOR 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 _DEFINES_H +#define _DEFINES_H + +/* $Id: defines.h,v 1.181 2014/06/11 19:22:50 dtucker Exp $ */ + + +/* Constants */ +#ifndef EAUTH +# define EAUTH 80 +#endif + +#ifndef INFTIM +#define INFTIM (-1) +#endif + +#ifndef HOST_NAME_MAX +# ifdef _POSIX_HOST_NAME_MAX +# define HOST_NAME_MAX _POSIX_HOST_NAME_MAX +# endif +#endif + +#ifndef PATH_MAX +# ifdef _POSIX_PATH_MAX +# define PATH_MAX _POSIX_PATH_MAX +# endif +#endif + +#ifndef MAXPATHLEN +# ifdef PATH_MAX +# define MAXPATHLEN PATH_MAX +# else /* PATH_MAX */ +# define MAXPATHLEN 64 +# define PATH_MAX 64 +/* realpath uses a fixed buffer of size MAXPATHLEN, so force use of ours */ +# ifndef BROKEN_REALPATH +# define BROKEN_REALPATH 1 +# endif /* BROKEN_REALPATH */ +# endif /* PATH_MAX */ +#endif /* MAXPATHLEN */ + +#ifndef MAXHOSTNAMELEN +# define MAXHOSTNAMELEN 64 +#endif + +#ifndef LOGIN_NAME_MAX +# define LOGIN_NAME_MAX 32 +#endif + +#ifndef MAXLOGNAME +#define MAXLOGNAME LOGIN_NAME_MAX +#endif + +#ifndef UID_MAX +#define UID_MAX UINT_MAX +#endif +#ifndef GID_MAX +#define GID_MAX UINT_MAX +#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 + +#if !HAVE_DECL_O_NONBLOCK +# define O_NONBLOCK 00004 /* Non Blocking Open */ +#endif + +#ifndef O_EXLOCK +#define O_EXLOCK 0 +#endif + +#ifndef S_ISDIR +# define S_ISDIR(mode) (((mode) & (_S_IFMT)) == (_S_IFDIR)) +#endif /* S_ISDIR */ + +#ifndef S_ISREG +# define S_ISREG(mode) (((mode) & (_S_IFMT)) == (_S_IFREG)) +#endif /* S_ISREG */ + +#ifndef S_ISLNK +# define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK) +#endif /* S_ISLNK */ + +#ifndef S_IXUSR +# define S_ISUID 0004000 /* set-uid */ +# define S_ISGID 0002000 /* set-gid */ +# define S_ISVTX 0001000 /* sticky */ +# define S_IXUSR 0000100 /* execute/search permission, */ +# define S_IXGRP 0000010 /* execute/search permission, */ +# define S_IXOTH 0000001 /* execute/search permission, */ +# define _S_IWUSR 0000200 /* write permission, */ +# define S_IWUSR _S_IWUSR /* write permission, owner */ +# define S_IWGRP 0000020 /* write permission, group */ +# define S_IWOTH 0000002 /* write permission, other */ +# define S_IRUSR 0000400 /* read permission, owner */ +# define S_IRGRP 0000040 /* read permission, group */ +# define S_IROTH 0000004 /* read permission, other */ +# define S_IRWXU 0000700 /* read, write, execute */ +# define S_IRWXG 0000070 /* read, write, execute */ +# define S_IRWXO 0000007 /* read, write, execute */ +#endif /* S_IXUSR */ + +#if !defined(MAP_ANON) && defined(MAP_ANONYMOUS) +#define MAP_ANON MAP_ANONYMOUS +#endif + +#ifndef MAP_FAILED +# define MAP_FAILED ((void *)-1) +#endif + +/* +SCO Open Server 3 has INADDR_LOOPBACK defined in rpc/rpc.h but +including rpc/rpc.h breaks Solaris 6 +*/ +#ifndef INADDR_LOOPBACK +#define INADDR_LOOPBACK ((u_long)0x7f000001) +#endif + + +/* Types */ +#ifndef HAVE_U_CHAR +typedef unsigned char u_char; +# define HAVE_U_CHAR +#endif /* HAVE_U_CHAR */ + +#ifndef HAVE_U_INT +typedef unsigned int u_int; +# define HAVE_U_INT +#endif + +#ifndef HAVE_INTMAX_T +typedef long long intmax_t; +# define HAVE_INTMAX_T +#endif + +#ifndef HAVE_UINTMAX_T +typedef unsigned long long uintmax_t; +# define HAVE_UINTMAX_T +#endif + +#ifndef HAVE_SA_FAMILY_T +typedef int sa_family_t; +# define HAVE_SA_FAMILY_T +#endif /* HAVE_SA_FAMILY_T */ + +#ifndef HAVE_SIG_ATOMIC_T +typedef int sig_atomic_t; +# define HAVE_SIG_ATOMIC_T +#endif /* HAVE_SIG_ATOMIC_T */ + + +#ifndef ULLONG_MAX +# define ULLONG_MAX ((unsigned long long)-1) +#endif + +#ifndef SIZE_T_MAX +#define SIZE_T_MAX ULONG_MAX +#endif /* SIZE_T_MAX */ + +#ifndef SIZE_MAX +#define SIZE_MAX SIZE_T_MAX +#endif + + + +#if !defined(HAVE_SS_FAMILY_IN_SS) && defined(HAVE___SS_FAMILY_IN_SS) +# define ss_family __ss_family +#endif /* !defined(HAVE_SS_FAMILY_IN_SS) && defined(HAVE_SA_FAMILY_IN_SS) */ + +#ifndef HAVE_SYS_UN_H +struct sockaddr_un { + short sun_family; /* AF_UNIX */ + char sun_path[108]; /* path name (gag) */ +}; +#endif /* HAVE_SYS_UN_H */ + +#ifndef HAVE_IN_ADDR_T +typedef uint32_t in_addr_t; +#endif + +#ifndef HAVE_IN_PORT_T +typedef uint16_t in_port_t; +#endif + + +/* Paths */ + +/* needed by compat/daemon.c */ +#ifndef _PATH_DEVNULL +# define _PATH_DEVNULL "/dev/null" +#endif + +/* user may have set a different path */ +#if !defined(_PATH_MAILDIR) +# define _PATH_MAILDIR "/var/spool/mail" +#endif + +#if defined(_PATH_MAILDIR) && defined(MAIL_DIRECTORY) +# undef _PATH_MAILDIR +#endif /* defined(_PATH_MAILDIR) && defined(MAIL_DIRECTORY) */ + +#ifdef MAIL_DIRECTORY +# define _PATH_MAILDIR MAIL_DIRECTORY +#endif + +#ifdef MAILDIR +# undef MAILDIR +#endif + + + +/* Macros */ + +/* needed by compat */ +#ifndef MAX +# define MAX(a,b) (((a)>(b))?(a):(b)) +#endif +#ifndef MIN +# define MIN(a,b) (((a)<(b))?(a):(b)) +#endif + +/* needed by smtpd */ +#ifndef timespeccmp +#define timespeccmp(a, b, cmp) \ + (((a)->tv_sec == (b)->tv_sec) ? \ + ((a)->tv_nsec cmp (b)->tv_nsec) : \ + ((a)->tv_sec cmp (b)->tv_sec)) +#endif + +/* needed by smtpd */ +#ifndef timespecsub +#define timespecsub(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ + (result)->tv_nsec = (a)->tv_nsec - (b)->tv_nsec; \ + if ((result)->tv_nsec < 0) { \ + --(result)->tv_sec; \ + (result)->tv_nsec += 1000000000L; \ + } \ + } while (0) +#endif + +/* needed by smtpd */ +#ifndef TIMEVAL_TO_TIMESPEC +#define TIMEVAL_TO_TIMESPEC(tv, ts) { \ + (ts)->tv_sec = (tv)->tv_sec; \ + (ts)->tv_nsec = (tv)->tv_usec * 1000; \ +} +#endif + +/* needed by compat */ +#ifndef TIMESPEC_TO_TIMEVAL +#define TIMESPEC_TO_TIMEVAL(tv, ts) { \ + (tv)->tv_sec = (ts)->tv_sec; \ + (tv)->tv_usec = (ts)->tv_nsec / 1000; \ +} +#endif + +#ifndef __P +# define __P(x) x +#endif + +#if !defined(IN6_IS_ADDR_V4MAPPED) +# define IN6_IS_ADDR_V4MAPPED(a) \ + ((((uint32_t *) (a))[0] == 0) && (((uint32_t *) (a))[1] == 0) && \ + (((uint32_t *) (a))[2] == htonl (0xffff))) +#endif /* !defined(IN6_IS_ADDR_V4MAPPED) */ + +#if !defined(__GNUC__) || (__GNUC__ < 2) +# define __attribute__(x) +#endif /* !defined(__GNUC__) || (__GNUC__ < 2) */ + +#ifndef __dead +# define __dead __attribute__((noreturn)) +#endif + +#if !defined(HAVE_ATTRIBUTE__SENTINEL__) && !defined(__sentinel__) +# define __sentinel__ +#endif + +#if !defined(HAVE_ATTRIBUTE__BOUNDED__) && !defined(__bounded__) +# define __bounded__(x, y, z) +#endif + +#if !defined(HAVE_ATTRIBUTE__NONNULL__) && !defined(__nonnull__) +# define __nonnull__(x) +#endif + +#ifndef OSSH_ALIGNBYTES +#define OSSH_ALIGNBYTES (sizeof(int) - 1) +#endif +#ifndef __CMSG_ALIGN +#define __CMSG_ALIGN(p) (((u_int)(p) + OSSH_ALIGNBYTES) &~ OSSH_ALIGNBYTES) +#endif + +/* Length of the contents of a control message of length len */ +#ifndef CMSG_LEN +#define CMSG_LEN(len) (__CMSG_ALIGN(sizeof(struct cmsghdr)) + (len)) +#endif + +/* Length of the space taken up by a padded control message of length len */ +#ifndef CMSG_SPACE +#define CMSG_SPACE(len) (__CMSG_ALIGN(sizeof(struct cmsghdr)) + __CMSG_ALIGN(len)) +#endif + +/* given pointer to struct cmsghdr, return pointer to data */ +#ifndef CMSG_DATA +#define CMSG_DATA(cmsg) ((u_char *)(cmsg) + __CMSG_ALIGN(sizeof(struct cmsghdr))) +#endif /* CMSG_DATA */ + +/* + * RFC 2292 requires to check msg_controllen, in case that the kernel returns + * an empty list for some reasons. + */ +#ifndef CMSG_FIRSTHDR +#define CMSG_FIRSTHDR(mhdr) \ + ((mhdr)->msg_controllen >= sizeof(struct cmsghdr) ? \ + (struct cmsghdr *)(mhdr)->msg_control : \ + (struct cmsghdr *)NULL) +#endif /* CMSG_FIRSTHDR */ + + +/* Set up BSD-style BYTE_ORDER definition if it isn't there already */ +/* XXX: doesn't try to cope with strange byte orders (PDP_ENDIAN) */ +#ifndef BYTE_ORDER +# ifndef LITTLE_ENDIAN +# define LITTLE_ENDIAN 1234 +# endif /* LITTLE_ENDIAN */ +# ifndef BIG_ENDIAN +# define BIG_ENDIAN 4321 +# endif /* BIG_ENDIAN */ +# ifdef WORDS_BIGENDIAN +# define BYTE_ORDER BIG_ENDIAN +# else /* WORDS_BIGENDIAN */ +# define BYTE_ORDER LITTLE_ENDIAN +# endif /* WORDS_BIGENDIAN */ +#endif /* BYTE_ORDER */ + +/* Function replacement / compatibility hacks */ + +#if defined(BROKEN_GETADDRINFO) && defined(HAVE_GETADDRINFO) +# undef HAVE_GETADDRINFO +#endif +#if defined(BROKEN_GETADDRINFO) && defined(HAVE_FREEADDRINFO) +# undef HAVE_FREEADDRINFO +#endif +#if defined(BROKEN_GETADDRINFO) && defined(HAVE_GAI_STRERROR) +# undef HAVE_GAI_STRERROR +#endif + +#if !defined(HAVE_MEMMOVE) && defined(HAVE_BCOPY) +# define memmove(s1, s2, n) bcopy((s2), (s1), (n)) +#endif /* !defined(HAVE_MEMMOVE) && defined(HAVE_BCOPY) */ + +#if !defined(HAVE___func__) && defined(HAVE___FUNCTION__) +# define __func__ __FUNCTION__ +#elif !defined(HAVE___func__) +# define __func__ "" +#endif + + +/* Maximum number of file descriptors available */ +/* needed by compat/bsd-closefrom.c */ +#ifndef OPEN_MAX +# ifdef HAVE_SYSCONF +# define OPEN_MAX sysconf(_SC_OPEN_MAX) +# else +# define OPEN_MAX 256 +# endif +#endif + + + +/** end of login recorder definitions */ + +#ifndef IOV_MAX +# if defined(_XOPEN_IOV_MAX) +# define IOV_MAX _XOPEN_IOV_MAX +# elif defined(DEF_IOV_MAX) +# define IOV_MAX DEF_IOV_MAX +# else +# define IOV_MAX 16 +# endif +#endif + +#ifndef EWOULDBLOCK +# define EWOULDBLOCK EAGAIN +#endif + +#ifndef INET6_ADDRSTRLEN /* for non IPv6 machines */ +#define INET6_ADDRSTRLEN 46 +#endif + +#ifndef HAVE_VA_COPY +# ifdef HAVE___VA_COPY +# define va_copy(dest, src) __va_copy(dest, src) +# else +# define va_copy(dest, src) (dest) = (src) +# endif +#endif + +/* OpenSMTPD-portable specific entries */ + +/* From OpenNTPD portable */ +#if !defined(SA_LEN) +# if defined(HAVE_STRUCT_SOCKADDR_SA_LEN) +# define SA_LEN(x) ((x)->sa_len) +# else +# define SA_LEN(x) ((x)->sa_family == AF_INET6 ? \ + sizeof(struct sockaddr_in6) : \ + sizeof(struct sockaddr_in)) +# endif +#endif + +/* From OpenBGPD portable */ +#if !defined(SS_LEN) +# if defined(HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN) +# define SS_LEN(x) ((x)->ss_len) +# else +# define SS_LEN(x) SA_LEN((struct sockaddr *)(x)) +# endif +#endif + +#ifdef HAVE_SS_LEN +# define STORAGE_LEN(X) ((X).ss_len) +# define SET_STORAGE_LEN(X, Y) do { STORAGE_LEN(X) = (Y); } while(0) +#elif defined(HAVE___SS_LEN) +# define STORAGE_LEN(X) ((X).__ss_len) +# define SET_STORAGE_LEN(X, Y) do { STORAGE_LEN(X) = (Y); } while(0) +#else +# define STORAGE_LEN(X) (STORAGE_FAMILY(X) == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6)) +# define SET_STORAGE_LEN(X, Y) (void) 0 +#endif + +/* chl parts */ +#ifndef EAI_NODATA +# ifdef EAI_NONAME +# define EAI_NODATA EAI_NONAME +# else +# error "Neither EAI_NODATA and EAI_NONAME are defined! :(" +# endif +#endif +/* end of chl */ + +#ifndef HAVE_FPARSELN +/* + * fparseln() specific operation flags. + */ +#define FPARSELN_UNESCESC 0x01 +#define FPARSELN_UNESCCONT 0x02 +#define FPARSELN_UNESCCOMM 0x04 +#define FPARSELN_UNESCREST 0x08 +#define FPARSELN_UNESCALL 0x0f +#endif + +#ifdef HAVE_M_DATA +#undef M_DATA +#endif + +#ifndef SCOPE_DELIMITER +#define SCOPE_DELIMITER '%' +#endif + +#ifndef HAVE_FLOCK +#define LOCK_SH 0x01 /* shared file lock */ +#define LOCK_EX 0x02 /* exclusive file lock */ +#define LOCK_NB 0x04 /* don't block when locking */ +#define LOCK_UN 0x08 /* unlock file */ +#endif + +#if !HAVE_DECL_LOG_PERROR +#define LOG_PERROR 0 +#endif + +#ifndef MAXDNAME +#define MAXDNAME 1025 +#endif + +#endif /* _DEFINES_H */ diff --git a/foobar/portable/openbsd-compat/dirname.c b/foobar/portable/openbsd-compat/dirname.c new file mode 100644 index 00000000..30fcb496 --- /dev/null +++ b/foobar/portable/openbsd-compat/dirname.c @@ -0,0 +1,72 @@ +/* $OpenBSD: dirname.c,v 1.13 2005/08/08 08:05:33 espie Exp $ */ + +/* + * Copyright (c) 1997, 2004 Todd C. Miller <Todd.Miller@courtesan.com> + * + * 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. + */ + +/* OPENBSD ORIGINAL: lib/libc/gen/dirname.c */ + +#include "includes.h" +#ifndef HAVE_DIRNAME + +#include <errno.h> +#include <string.h> +#include <sys/param.h> + +char * +dirname(const char *path) +{ + static char dname[MAXPATHLEN]; + size_t len; + const char *endp; + + /* Empty or NULL string gets treated as "." */ + if (path == NULL || *path == '\0') { + dname[0] = '.'; + dname[1] = '\0'; + return (dname); + } + + /* Strip any trailing slashes */ + endp = path + strlen(path) - 1; + while (endp > path && *endp == '/') + endp--; + + /* Find the start of the dir */ + while (endp > path && *endp != '/') + endp--; + + /* Either the dir is "/" or there are no slashes */ + if (endp == path) { + dname[0] = *endp == '/' ? '/' : '.'; + dname[1] = '\0'; + return (dname); + } else { + /* Move forward past the separating slashes */ + do { + endp--; + } while (endp > path && *endp == '/'); + } + + len = endp - path + 1; + if (len >= sizeof(dname)) { + errno = ENAMETOOLONG; + return (NULL); + } + memcpy(dname, path, len); + dname[len] = '\0'; + return (dname); +} +#endif diff --git a/foobar/portable/openbsd-compat/empty.c b/foobar/portable/openbsd-compat/empty.c new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/foobar/portable/openbsd-compat/empty.c diff --git a/foobar/portable/openbsd-compat/entropy.c b/foobar/portable/openbsd-compat/entropy.c new file mode 100644 index 00000000..367d7135 --- /dev/null +++ b/foobar/portable/openbsd-compat/entropy.c @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2001 Damien Miller. 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 AUTHOR ``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 AUTHOR 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 "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> +#ifdef HAVE_SYS_UN_H +# include <sys/un.h> +#endif + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <errno.h> +#include <signal.h> +#include <string.h> +#include <unistd.h> + +#include <openssl/rand.h> +#include <openssl/crypto.h> +#include <openssl/err.h> + +#include "smtpd/log.h" + +void +seed_rng(void) +{ +#ifndef LIBRESSL_VERSION + u_long mask; + + /* + * OpenSSL version numbers: MNNFFPPS: major minor fix patch status + * We match major, minor, fix and status (not patch) for <1.0.0. + * After that, we acceptable compatible fix versions (so we + * allow 1.0.1 to work with 1.0.0). Going backwards is only allowed + * within a patch series. + */ + mask = SSLeay() >= 0x1000000f ? 0xfff00000L : 0xfffff00fL; + if ((SSLeay() & mask) < (OPENSSL_VERSION_NUMBER & mask)) { + fatalx("OpenSSL version mismatch. Built against %lx, you have %lx\n", + (u_long)OPENSSL_VERSION_NUMBER, SSLeay()); + } +#endif + + if (RAND_status() != 1) + fatal("PRNG is not seeded"); +} diff --git a/foobar/portable/openbsd-compat/entropy.h b/foobar/portable/openbsd-compat/entropy.h new file mode 100644 index 00000000..496bed66 --- /dev/null +++ b/foobar/portable/openbsd-compat/entropy.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 1999-2000 Damien Miller. 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 AUTHOR ``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 AUTHOR 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. + */ + +/* $Id: entropy.h,v 1.6 2011/09/09 01:29:41 dtucker Exp $ */ + +#ifndef _RANDOMS_H +#define _RANDOMS_H + +/* #include "buffer.h" */ + +void seed_rng(void); + +/* void rexec_send_rng_seed(Buffer *); */ +/* void rexec_recv_rng_seed(Buffer *); */ + +#endif /* _RANDOMS_H */ diff --git a/foobar/portable/openbsd-compat/err_h/err.h b/foobar/portable/openbsd-compat/err_h/err.h new file mode 100644 index 00000000..a56b6188 --- /dev/null +++ b/foobar/portable/openbsd-compat/err_h/err.h @@ -0,0 +1,18 @@ +#ifndef ERR_H +#define ERR_H + +#ifndef LIBCRYPTOCOMPAT_ERR_H +#define LIBCRYPTOCOMPAT_ERR_H + +__attribute__ ((noreturn)) +void err(int, const char *, ...); + +__attribute__ ((noreturn)) +void errx(int, const char *, ...); + +void warn(const char *, ...); +void warnx(const char *, ...); + +#endif + +#endif diff --git a/foobar/portable/openbsd-compat/errc.c b/foobar/portable/openbsd-compat/errc.c new file mode 100644 index 00000000..658a55b4 --- /dev/null +++ b/foobar/portable/openbsd-compat/errc.c @@ -0,0 +1,56 @@ +/* $OpenBSD: basename.c,v 1.14 2005/08/08 08:05:33 espie Exp $ */ + +/* + * Copyright (c) 1997, 2004 Todd C. Miller <Todd.Miller@courtesan.com> + * + * 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. + */ + +/* OPENBSD ORIGINAL: lib/libc/gen/errc.c */ + +#include "includes.h" + +#ifndef HAVE_ERRC + +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +extern char *__progname; + +__attribute__((noreturn)) +static void +_verrc(int eval, int code, const char *fmt, va_list ap) +{ + (void)fprintf(stderr, "%s: ", __progname); + if (fmt != NULL) { + (void)vfprintf(stderr, fmt, ap); + (void)fprintf(stderr, ": "); + } + (void)fprintf(stderr, "%s\n", strerror(code)); + exit(eval); +} + +void +errc(int eval, int code, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + _verrc(eval, code, fmt, ap); + va_end(ap); +} + +#endif diff --git a/foobar/portable/openbsd-compat/event_asr_run.c b/foobar/portable/openbsd-compat/event_asr_run.c new file mode 100644 index 00000000..aef86154 --- /dev/null +++ b/foobar/portable/openbsd-compat/event_asr_run.c @@ -0,0 +1,88 @@ +/* $OpenBSD$ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> + +#include <asr.h> +#include <event.h> +#include <stdlib.h> + +struct event_asr { + struct event ev; + struct asr_query *async; + void (*cb)(struct asr_result *, void *); + void *arg; +}; + +struct event_asr * event_asr_run(struct asr_query *, + void (*)(struct asr_result *, void *), void *); +void event_asr_abort(struct event_asr *); + +static void +event_asr_dispatch(int fd __attribute__((__unused__)), + short ev __attribute__((__unused__)), void *arg) +{ + struct event_asr *eva = arg; + struct asr_result ar; + struct timeval tv; + + event_del(&eva->ev); + + if (asr_run(eva->async, &ar)) { + eva->cb(&ar, eva->arg); + free(eva); + } else { + event_set(&eva->ev, ar.ar_fd, + ar.ar_cond == ASR_WANT_READ ? EV_READ : EV_WRITE, + event_asr_dispatch, eva); + tv.tv_sec = ar.ar_timeout / 1000; + tv.tv_usec = (ar.ar_timeout % 1000) * 1000; + event_add(&eva->ev, &tv); + } +} + +struct event_asr * +event_asr_run(struct asr_query *async, void (*cb)(struct asr_result *, void *), + void *arg) +{ + struct event_asr *eva; + struct timeval tv; + + eva = calloc(1, sizeof *eva); + if (eva == NULL) + return (NULL); + eva->async = async; + eva->cb = cb; + eva->arg = arg; + tv.tv_sec = 0; + tv.tv_usec = 0; + evtimer_set(&eva->ev, event_asr_dispatch, eva); + evtimer_add(&eva->ev, &tv); + return (eva); +} + +void +event_asr_abort(struct event_asr *eva) +{ + asr_abort(eva->async); + event_del(&eva->ev); + free(eva); +} diff --git a/foobar/portable/openbsd-compat/explicit_bzero.c b/foobar/portable/openbsd-compat/explicit_bzero.c new file mode 100644 index 00000000..d9f4abf5 --- /dev/null +++ b/foobar/portable/openbsd-compat/explicit_bzero.c @@ -0,0 +1,15 @@ +/* $OpenBSD: explicit_bzero.c,v 1.4 2015/08/31 02:53:57 guenther Exp $ */ +/* + * Public domain. + * Written by Matthew Dempsky. + */ + +#include "includes.h" + +#include <string.h> + +void +explicit_bzero(void *buf, size_t len) +{ + memset(buf, 0, len); +} diff --git a/foobar/portable/openbsd-compat/fgetln.c b/foobar/portable/openbsd-compat/fgetln.c new file mode 100644 index 00000000..1c51ff78 --- /dev/null +++ b/foobar/portable/openbsd-compat/fgetln.c @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2015 Joerg Jung <jung@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * portable fgetln() version, NOT reentrant + */ + +#include "includes.h" + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + +void *reallocarray(void *, size_t, size_t); + +char * +fgetln(FILE *fp, size_t *len) +{ + static char *buf = NULL; + static size_t bufsz = 0; + size_t r = 0; + char *p; + int c, e; + + if (buf == NULL) { + if ((buf = calloc(1, BUFSIZ)) == NULL) + return NULL; + bufsz = BUFSIZ; + } + + while ((c = getc(fp)) != EOF) { + buf[r++] = c; + if (r == bufsz) { + if (!(p = reallocarray(buf, 2, bufsz))) { + e = errno; + free(buf); + errno = e; + buf = NULL, bufsz = 0; + return NULL; + } + buf = p, bufsz = 2 * bufsz; + } + if (c == '\n') + break; + } + return (*len = r) ? buf : NULL; +} + diff --git a/foobar/portable/openbsd-compat/fmt_scaled.c b/foobar/portable/openbsd-compat/fmt_scaled.c new file mode 100644 index 00000000..edd682a4 --- /dev/null +++ b/foobar/portable/openbsd-compat/fmt_scaled.c @@ -0,0 +1,274 @@ +/* $OpenBSD: fmt_scaled.c,v 1.9 2007/03/20 03:42:52 tedu Exp $ */ + +/* + * Copyright (c) 2001, 2002, 2003 Ian F. Darwin. 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + */ + +/* OPENBSD ORIGINAL: lib/libutil/fmt_scaled.c */ + +/* + * fmt_scaled: Format numbers scaled for human comprehension + * scan_scaled: Scan numbers in this format. + * + * "Human-readable" output uses 4 digits max, and puts a unit suffix at + * the end. Makes output compact and easy-to-read esp. on huge disks. + * Formatting code was originally in OpenBSD "df", converted to library routine. + * Scanning code written for OpenBSD libutil. + */ + +#include "includes.h" + +#ifndef HAVE_FMT_SCALED + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <ctype.h> +#include <limits.h> + +typedef enum { + NONE = 0, KILO = 1, MEGA = 2, GIGA = 3, TERA = 4, PETA = 5, EXA = 6 +} unit_type; + +/* These three arrays MUST be in sync! XXX make a struct */ +static unit_type units[] = { NONE, KILO, MEGA, GIGA, TERA, PETA, EXA }; +static char scale_chars[] = "BKMGTPE"; +static long long scale_factors[] = { + 1LL, + 1024LL, + 1024LL*1024, + 1024LL*1024*1024, + 1024LL*1024*1024*1024, + 1024LL*1024*1024*1024*1024, + 1024LL*1024*1024*1024*1024*1024, +}; +#define SCALE_LENGTH (sizeof(units)/sizeof(units[0])) + +#define MAX_DIGITS (SCALE_LENGTH * 3) /* XXX strlen(sprintf("%lld", -1)? */ + +/** Convert the given input string "scaled" into numeric in "result". + * Return 0 on success, -1 and errno set on error. + */ +int +scan_scaled(char *scaled, long long *result) +{ + char *p = scaled; + int sign = 0; + unsigned int i, ndigits = 0, fract_digits = 0; + long long scale_fact = 1, whole = 0, fpart = 0; + + /* Skip leading whitespace */ + while (isascii(*p) && isspace(*p)) + ++p; + + /* Then at most one leading + or - */ + while (*p == '-' || *p == '+') { + if (*p == '-') { + if (sign) { + errno = EINVAL; + return -1; + } + sign = -1; + ++p; + } else if (*p == '+') { + if (sign) { + errno = EINVAL; + return -1; + } + sign = +1; + ++p; + } + } + + /* Main loop: Scan digits, find decimal point, if present. + * We don't allow exponentials, so no scientific notation + * (but note that E for Exa might look like e to some!). + * Advance 'p' to end, to get scale factor. + */ + for (; isascii(*p) && (isdigit(*p) || *p=='.'); ++p) { + if (*p == '.') { + if (fract_digits > 0) { /* oops, more than one '.' */ + errno = EINVAL; + return -1; + } + fract_digits = 1; + continue; + } + + i = (*p) - '0'; /* whew! finally a digit we can use */ + if (fract_digits > 0) { + if (fract_digits >= MAX_DIGITS-1) + /* ignore extra fractional digits */ + continue; + fract_digits++; /* for later scaling */ + fpart *= 10; + fpart += i; + } else { /* normal digit */ + if (++ndigits >= MAX_DIGITS) { + errno = ERANGE; + return -1; + } + whole *= 10; + whole += i; + } + } + + if (sign) { + whole *= sign; + fpart *= sign; + } + + /* If no scale factor given, we're done. fraction is discarded. */ + if (!*p) { + *result = whole; + return 0; + } + + /* Validate scale factor, and scale whole and fraction by it. */ + for (i = 0; i < SCALE_LENGTH; i++) { + + /** Are we there yet? */ + if (*p == scale_chars[i] || + *p == tolower(scale_chars[i])) { + + /* If it ends with alphanumerics after the scale char, bad. */ + if (isalnum(*(p+1))) { + errno = EINVAL; + return -1; + } + scale_fact = scale_factors[i]; + + /* scale whole part */ + whole *= scale_fact; + + /* truncate fpart so it does't overflow. + * then scale fractional part. + */ + while (fpart >= LLONG_MAX / scale_fact) { + fpart /= 10; + fract_digits--; + } + fpart *= scale_fact; + if (fract_digits > 0) { + for (i = 0; i < fract_digits -1; i++) + fpart /= 10; + } + whole += fpart; + *result = whole; + return 0; + } + } + errno = ERANGE; + return -1; +} + +/* Format the given "number" into human-readable form in "result". + * Result must point to an allocated buffer of length FMT_SCALED_STRSIZE. + * Return 0 on success, -1 and errno set if error. + */ +int +fmt_scaled(long long number, char *result) +{ + long long abval, fract = 0; + unsigned int i; + unit_type unit = NONE; + + abval = (number < 0LL) ? -number : number; /* no long long_abs yet */ + + /* Not every negative long long has a positive representation. + * Also check for numbers that are just too darned big to format + */ + if (abval < 0 || abval / 1024 >= scale_factors[SCALE_LENGTH-1]) { + errno = ERANGE; + return -1; + } + + /* scale whole part; get unscaled fraction */ + for (i = 0; i < SCALE_LENGTH; i++) { + if (abval/1024 < scale_factors[i]) { + unit = units[i]; + fract = (i == 0) ? 0 : abval % scale_factors[i]; + number /= scale_factors[i]; + if (i > 0) + fract /= scale_factors[i - 1]; + break; + } + } + + fract = (10 * fract + 512) / 1024; + /* if the result would be >= 10, round main number */ + if (fract == 10) { + if (number >= 0) + number++; + else + number--; + fract = 0; + } + + if (number == 0) + strlcpy(result, "0B", FMT_SCALED_STRSIZE); + else if (unit == NONE || number >= 100 || number <= -100) { + if (fract >= 5) { + if (number >= 0) + number++; + else + number--; + } + (void)snprintf(result, FMT_SCALED_STRSIZE, "%lld%c", + number, scale_chars[unit]); + } else + (void)snprintf(result, FMT_SCALED_STRSIZE, "%lld.%1lld%c", + number, fract, scale_chars[unit]); + + return 0; +} + +#ifdef MAIN +/* + * This is the original version of the program in the man page. + * Copy-and-paste whatever you need from it. + */ +int +main(int argc, char **argv) +{ + char *cinput = "1.5K", buf[FMT_SCALED_STRSIZE]; + long long ninput = 10483892, result; + + if (scan_scaled(cinput, &result) == 0) + printf("\"%s\" -> %lld\n", cinput, result); + else + perror(cinput); + + if (fmt_scaled(ninput, buf) == 0) + printf("%lld -> \"%s\"\n", ninput, buf); + else + fprintf(stderr, "%lld invalid (%s)\n", ninput, strerror(errno)); + + return 0; +} +#endif + +#endif /* HAVE_FMT_SCALED */ diff --git a/foobar/portable/openbsd-compat/fparseln.c b/foobar/portable/openbsd-compat/fparseln.c new file mode 100644 index 00000000..dfa9093c --- /dev/null +++ b/foobar/portable/openbsd-compat/fparseln.c @@ -0,0 +1,179 @@ +/* $OpenBSD: fparseln.c,v 1.6 2005/08/02 21:46:23 espie Exp $ */ +/* $NetBSD: fparseln.c,v 1.7 1999/07/02 15:49:12 simonb Exp $ */ + +/* + * Copyright (c) 1997 Christos Zoulas. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Christos Zoulas. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + */ + +/* OPENBSD ORIGINAL: lib/libutil/fparseln.c */ + +#include "includes.h" + +#ifdef HAVE_SYS_CDEFS +#include <sys/cdefs.h> +#endif + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +static int isescaped(const char *, const char *, int); + +/* isescaped(): + * Return true if the character in *p that belongs to a string + * that starts in *sp, is escaped by the escape character esc. + */ +static int +isescaped(const char *sp, const char *p, int esc) +{ + const char *cp; + size_t ne; + + /* No escape character */ + if (esc == '\0') + return 1; + + /* Count the number of escape characters that precede ours */ + for (ne = 0, cp = p; --cp >= sp && *cp == esc; ne++) + continue; + + /* Return true if odd number of escape characters */ + return (ne & 1) != 0; +} + + +/* fparseln(): + * Read a line from a file parsing continuations ending in \ + * and eliminating trailing newlines, or comments starting with + * the comment char. + */ +char * +fparseln(FILE *fp, size_t *size, size_t *lineno, const char str[3], + int flags) +{ + static const char dstr[3] = { '\\', '\\', '#' }; + char *buf = NULL, *ptr, *cp, esc, con, nl, com; + size_t s, len = 0; + int cnt = 1; + + if (str == NULL) + str = dstr; + + esc = str[0]; + con = str[1]; + com = str[2]; + + /* + * XXX: it would be cool to be able to specify the newline character, + * but unfortunately, fgetln does not let us + */ + nl = '\n'; + + while (cnt) { + cnt = 0; + + if (lineno) + (*lineno)++; + + if ((ptr = fgetln(fp, &s)) == NULL) + break; + + if (s && com) { /* Check and eliminate comments */ + for (cp = ptr; cp < ptr + s; cp++) + if (*cp == com && !isescaped(ptr, cp, esc)) { + s = cp - ptr; + cnt = s == 0 && buf == NULL; + break; + } + } + + if (s && nl) { /* Check and eliminate newlines */ + cp = &ptr[s - 1]; + + if (*cp == nl) + s--; /* forget newline */ + } + + if (s && con) { /* Check and eliminate continuations */ + cp = &ptr[s - 1]; + + if (*cp == con && !isescaped(ptr, cp, esc)) { + s--; /* forget escape */ + cnt = 1; + } + } + + if (s == 0 && buf != NULL) + continue; + + if ((cp = realloc(buf, len + s + 1)) == NULL) { + free(buf); + return NULL; + } + buf = cp; + + (void) memcpy(buf + len, ptr, s); + len += s; + buf[len] = '\0'; + } + + if ((flags & FPARSELN_UNESCALL) != 0 && esc && buf != NULL && + strchr(buf, esc) != NULL) { + ptr = cp = buf; + while (cp[0] != '\0') { + int skipesc; + + while (cp[0] != '\0' && cp[0] != esc) + *ptr++ = *cp++; + if (cp[0] == '\0' || cp[1] == '\0') + break; + + skipesc = 0; + if (cp[1] == com) + skipesc += (flags & FPARSELN_UNESCCOMM); + if (cp[1] == con) + skipesc += (flags & FPARSELN_UNESCCONT); + if (cp[1] == esc) + skipesc += (flags & FPARSELN_UNESCESC); + if (cp[1] != com && cp[1] != con && cp[1] != esc) + skipesc = (flags & FPARSELN_UNESCREST); + + if (skipesc) + cp++; + else + *ptr++ = *cp++; + *ptr++ = *cp++; + } + *ptr = '\0'; + len = strlen(buf); + } + + if (size) + *size = len; + return buf; +} diff --git a/foobar/portable/openbsd-compat/freezero.c b/foobar/portable/openbsd-compat/freezero.c new file mode 100644 index 00000000..da20d132 --- /dev/null +++ b/foobar/portable/openbsd-compat/freezero.c @@ -0,0 +1,34 @@ +/* $OpenBSD: strtonum.c,v 1.6 2004/08/03 19:38:01 millert Exp $ */ + +/* + * Copyright (c) 2004 Ted Unangst and Todd Miller + * All rights reserved. + * + * 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. + */ + +/* OPENBSD ORIGINAL: lib/libc/stdlib/malloc.c */ + +#include "includes.h" + +#include <stdlib.h> +#include <strings.h> + +void +freezero(void *ptr, size_t sz) +{ + if (ptr == NULL) + return; + explicit_bzero(ptr, sz); + free(ptr); +} diff --git a/foobar/portable/openbsd-compat/getopt.c b/foobar/portable/openbsd-compat/getopt.c new file mode 100644 index 00000000..5450e43d --- /dev/null +++ b/foobar/portable/openbsd-compat/getopt.c @@ -0,0 +1,123 @@ +/* + * Copyright (c) 1987, 1993, 1994 + * The Regents of the University of California. 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. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + */ + +/* OPENBSD ORIGINAL: lib/libc/stdlib/getopt.c */ + +#include "includes.h" +#if !defined(HAVE_GETOPT) || !defined(HAVE_GETOPT_OPTRESET) + +#if defined(LIBC_SCCS) && !defined(lint) +static char *rcsid = "$OpenBSD: getopt.c,v 1.5 2003/06/02 20:18:37 millert Exp $"; +#endif /* LIBC_SCCS and not lint */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +int BSDopterr = 1, /* if error message should be printed */ + BSDoptind = 1, /* index into parent argv vector */ + BSDoptopt, /* character checked for validity */ + BSDoptreset; /* reset getopt */ +char *BSDoptarg; /* argument associated with option */ + +#define BADCH (int)'?' +#define BADARG (int)':' +#define EMSG "" + +/* + * getopt -- + * Parse argc/argv argument vector. + */ +int +BSDgetopt(nargc, nargv, ostr) + int nargc; + char * const *nargv; + const char *ostr; +{ + extern char *__progname; + static char *place = EMSG; /* option letter processing */ + char *oli; /* option letter list index */ + + if (ostr == NULL) + return (-1); + + if (BSDoptreset || !*place) { /* update scanning pointer */ + BSDoptreset = 0; + if (BSDoptind >= nargc || *(place = nargv[BSDoptind]) != '-') { + place = EMSG; + return (-1); + } + if (place[1] && *++place == '-') { /* found "--" */ + ++BSDoptind; + place = EMSG; + return (-1); + } + } /* option letter okay? */ + if ((BSDoptopt = (int)*place++) == (int)':' || + !(oli = strchr(ostr, BSDoptopt))) { + /* + * if the user didn't specify '-' as an option, + * assume it means -1. + */ + if (BSDoptopt == (int)'-') + return (-1); + if (!*place) + ++BSDoptind; + if (BSDopterr && *ostr != ':') + (void)fprintf(stderr, + "%s: illegal option -- %c\n", __progname, BSDoptopt); + return (BADCH); + } + if (*++oli != ':') { /* don't need argument */ + BSDoptarg = NULL; + if (!*place) + ++BSDoptind; + } + else { /* need an argument */ + if (*place) /* no white space */ + BSDoptarg = place; + else if (nargc <= ++BSDoptind) { /* no arg */ + place = EMSG; + if (*ostr == ':') + return (BADARG); + if (BSDopterr) + (void)fprintf(stderr, + "%s: option requires an argument -- %c\n", + __progname, BSDoptopt); + return (BADCH); + } + else /* white space */ + BSDoptarg = nargv[BSDoptind]; + place = EMSG; + ++BSDoptind; + } + return (BSDoptopt); /* dump back option letter */ +} + +#endif /* !defined(HAVE_GETOPT) || !defined(HAVE_OPTRESET) */ diff --git a/foobar/portable/openbsd-compat/getpeereid.c b/foobar/portable/openbsd-compat/getpeereid.c new file mode 100644 index 00000000..c8ce808f --- /dev/null +++ b/foobar/portable/openbsd-compat/getpeereid.c @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2002,2004 Damien Miller <djm@mindrot.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> + +#include <unistd.h> + +#if defined(SO_PEERCRED) +int +getpeereid(int s, uid_t *euid, gid_t *gid) +{ + struct ucred cred; + socklen_t len = sizeof(cred); + + if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0) + return (-1); + *euid = cred.uid; + *gid = cred.gid; + + return (0); +} +#elif defined(HAVE_GETPEERUCRED) + +#ifdef HAVE_UCRED_H +# include <ucred.h> +#endif + +int +getpeereid(int s, uid_t *euid, gid_t *gid) +{ + ucred_t *ucred = NULL; + + if (getpeerucred(s, &ucred) == -1) + return (-1); + if ((*euid = ucred_geteuid(ucred)) == -1) + return (-1); + if ((*gid = ucred_getrgid(ucred)) == -1) + return (-1); + + ucred_free(ucred); + + return (0); +} +#else +int +getpeereid(int s, uid_t *euid, gid_t *gid) +{ + *euid = geteuid(); + *gid = getgid(); + + return (0); +} +#endif /* defined(SO_PEERCRED) */ diff --git a/foobar/portable/openbsd-compat/imsg-buffer.c b/foobar/portable/openbsd-compat/imsg-buffer.c new file mode 100644 index 00000000..e3762092 --- /dev/null +++ b/foobar/portable/openbsd-compat/imsg-buffer.c @@ -0,0 +1,310 @@ +/* $OpenBSD: imsg-buffer.c,v 1.3 2013/11/13 20:40:24 benno Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/param.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/uio.h> + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#ifndef HAVE_EXPLICIT_BZERO +#include <strings.h> +#endif +#include <unistd.h> + +#include "imsg.h" + +int ibuf_realloc(struct ibuf *, size_t); +void ibuf_enqueue(struct msgbuf *, struct ibuf *); +void ibuf_dequeue(struct msgbuf *, struct ibuf *); + +struct ibuf * +ibuf_open(size_t len) +{ + struct ibuf *buf; + + if ((buf = calloc(1, sizeof(struct ibuf))) == NULL) + return (NULL); + if ((buf->buf = malloc(len)) == NULL) { + free(buf); + return (NULL); + } + buf->size = buf->max = len; + buf->fd = -1; + + return (buf); +} + +struct ibuf * +ibuf_dynamic(size_t len, size_t max) +{ + struct ibuf *buf; + + if (max < len) + return (NULL); + + if ((buf = ibuf_open(len)) == NULL) + return (NULL); + + if (max > 0) + buf->max = max; + + return (buf); +} + +int +ibuf_realloc(struct ibuf *buf, size_t len) +{ + u_char *b; + + /* on static buffers max is eq size and so the following fails */ + if (buf->wpos + len > buf->max) { + errno = ENOMEM; + return (-1); + } + + b = realloc(buf->buf, buf->wpos + len); + if (b == NULL) + return (-1); + buf->buf = b; + buf->size = buf->wpos + len; + + return (0); +} + +int +ibuf_add(struct ibuf *buf, const void *data, size_t len) +{ + if (buf->wpos + len > buf->size) + if (ibuf_realloc(buf, len) == -1) + return (-1); + + memcpy(buf->buf + buf->wpos, data, len); + buf->wpos += len; + return (0); +} + +void * +ibuf_reserve(struct ibuf *buf, size_t len) +{ + void *b; + + if (buf->wpos + len > buf->size) + if (ibuf_realloc(buf, len) == -1) + return (NULL); + + b = buf->buf + buf->wpos; + buf->wpos += len; + return (b); +} + +void * +ibuf_seek(struct ibuf *buf, size_t pos, size_t len) +{ + /* only allowed to seek in already written parts */ + if (pos + len > buf->wpos) + return (NULL); + + return (buf->buf + pos); +} + +size_t +ibuf_size(struct ibuf *buf) +{ + return (buf->wpos); +} + +size_t +ibuf_left(struct ibuf *buf) +{ + return (buf->max - buf->wpos); +} + +void +ibuf_close(struct msgbuf *msgbuf, struct ibuf *buf) +{ + ibuf_enqueue(msgbuf, buf); +} + +int +ibuf_write(struct msgbuf *msgbuf) +{ + struct iovec iov[IOV_MAX]; + struct ibuf *buf; + unsigned int i = 0; + ssize_t n; + + bzero(&iov, sizeof(iov)); + TAILQ_FOREACH(buf, &msgbuf->bufs, entry) { + if (i >= IOV_MAX) + break; + iov[i].iov_base = buf->buf + buf->rpos; + iov[i].iov_len = buf->wpos - buf->rpos; + i++; + } + +again: + if ((n = writev(msgbuf->fd, iov, i)) == -1) { + if (errno == EINTR) + goto again; + if (errno == ENOBUFS) + errno = EAGAIN; + return (-1); + } + + if (n == 0) { /* connection closed */ + errno = 0; + return (0); + } + + msgbuf_drain(msgbuf, n); + + return (1); +} + +void +ibuf_free(struct ibuf *buf) +{ + free(buf->buf); + free(buf); +} + +void +msgbuf_init(struct msgbuf *msgbuf) +{ + msgbuf->queued = 0; + msgbuf->fd = -1; + TAILQ_INIT(&msgbuf->bufs); +} + +void +msgbuf_drain(struct msgbuf *msgbuf, size_t n) +{ + struct ibuf *buf, *next; + + for (buf = TAILQ_FIRST(&msgbuf->bufs); buf != NULL && n > 0; + buf = next) { + next = TAILQ_NEXT(buf, entry); + if (buf->rpos + n >= buf->wpos) { + n -= buf->wpos - buf->rpos; + ibuf_dequeue(msgbuf, buf); + } else { + buf->rpos += n; + n = 0; + } + } +} + +void +msgbuf_clear(struct msgbuf *msgbuf) +{ + struct ibuf *buf; + + while ((buf = TAILQ_FIRST(&msgbuf->bufs)) != NULL) + ibuf_dequeue(msgbuf, buf); +} + +int +msgbuf_write(struct msgbuf *msgbuf) +{ + struct iovec iov[IOV_MAX]; + struct ibuf *buf; + unsigned int i = 0; + ssize_t n; + struct msghdr msg; + struct cmsghdr *cmsg; + union { + struct cmsghdr hdr; + char buf[CMSG_SPACE(sizeof(int))]; + } cmsgbuf; + + bzero(&iov, sizeof(iov)); + bzero(&msg, sizeof(msg)); + TAILQ_FOREACH(buf, &msgbuf->bufs, entry) { + if (i >= IOV_MAX) + break; + iov[i].iov_base = buf->buf + buf->rpos; + iov[i].iov_len = buf->wpos - buf->rpos; + i++; + if (buf->fd != -1) + break; + } + + msg.msg_iov = iov; + msg.msg_iovlen = i; + + if (buf != NULL && buf->fd != -1) { + msg.msg_control = (caddr_t)&cmsgbuf.buf; + msg.msg_controllen = sizeof(cmsgbuf.buf); + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + *(int *)CMSG_DATA(cmsg) = buf->fd; + } + +again: + if ((n = sendmsg(msgbuf->fd, &msg, 0)) == -1) { + if (errno == EINTR) + goto again; + if (errno == ENOBUFS) + errno = EAGAIN; + return (-1); + } + + if (n == 0) { /* connection closed */ + errno = 0; + return (0); + } + + /* + * assumption: fd got sent if sendmsg sent anything + * this works because fds are passed one at a time + */ + if (buf != NULL && buf->fd != -1) { + close(buf->fd); + buf->fd = -1; + } + + msgbuf_drain(msgbuf, n); + + return (1); +} + +void +ibuf_enqueue(struct msgbuf *msgbuf, struct ibuf *buf) +{ + TAILQ_INSERT_TAIL(&msgbuf->bufs, buf, entry); + msgbuf->queued++; +} + +void +ibuf_dequeue(struct msgbuf *msgbuf, struct ibuf *buf) +{ + TAILQ_REMOVE(&msgbuf->bufs, buf, entry); + + if (buf->fd != -1) + close(buf->fd); + + msgbuf->queued--; + ibuf_free(buf); +} diff --git a/foobar/portable/openbsd-compat/imsg.c b/foobar/portable/openbsd-compat/imsg.c new file mode 100644 index 00000000..a5900a05 --- /dev/null +++ b/foobar/portable/openbsd-compat/imsg.c @@ -0,0 +1,330 @@ +/* $OpenBSD: imsg.c,v 1.5 2013/12/26 17:32:33 eric Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/param.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/uio.h> + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#ifndef HAVE_EXPLICIT_BZERO +#include <strings.h> +#endif +#include <unistd.h> + +#include "imsg.h" + +int imsg_fd_overhead = 0; + +int imsg_get_fd(struct imsgbuf *); + +int +available_fds(unsigned int n) +{ + unsigned int i; + int ret, fds[256]; + + if (n > (sizeof(fds)/sizeof(fds[0]))) + return (1); + + ret = 0; + for (i = 0; i < n; i++) { + fds[i] = -1; + if ((fds[i] = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + ret = 1; + break; + } + } + + for (i = 0; i < n && fds[i] >= 0; i++) + close(fds[i]); + + return (ret); +} + +void +imsg_init(struct imsgbuf *ibuf, int fd) +{ + msgbuf_init(&ibuf->w); + bzero(&ibuf->r, sizeof(ibuf->r)); + ibuf->fd = fd; + ibuf->w.fd = fd; + ibuf->pid = getpid(); + TAILQ_INIT(&ibuf->fds); +} + +ssize_t +imsg_read(struct imsgbuf *ibuf) +{ + struct msghdr msg; + struct cmsghdr *cmsg; + union { + struct cmsghdr hdr; + char buf[CMSG_SPACE(sizeof(int) * 1)]; + } cmsgbuf; + struct iovec iov; + ssize_t n = -1; + int fd; + struct imsg_fd *ifd; + + bzero(&msg, sizeof(msg)); + + iov.iov_base = ibuf->r.buf + ibuf->r.wpos; + iov.iov_len = sizeof(ibuf->r.buf) - ibuf->r.wpos; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = &cmsgbuf.buf; + msg.msg_controllen = sizeof(cmsgbuf.buf); + + if ((ifd = calloc(1, sizeof(struct imsg_fd))) == NULL) + return (-1); + +again: + if (available_fds(imsg_fd_overhead + + (CMSG_SPACE(sizeof(int))-CMSG_SPACE(0))/sizeof(int))) { + errno = EAGAIN; + free(ifd); + return (-1); + } + + if ((n = recvmsg(ibuf->fd, &msg, 0)) == -1) { + if (errno == EMSGSIZE) + goto fail; + if (errno != EINTR && errno != EAGAIN) + goto fail; + goto again; + } + + ibuf->r.wpos += n; + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS) { + int i; + int j; + + /* + * We only accept one file descriptor. Due to C + * padding rules, our control buffer might contain + * more than one fd, and we must close them. + */ + j = ((char *)cmsg + cmsg->cmsg_len - + (char *)CMSG_DATA(cmsg)) / sizeof(int); + for (i = 0; i < j; i++) { + fd = ((int *)CMSG_DATA(cmsg))[i]; + if (ifd != NULL) { + ifd->fd = fd; + TAILQ_INSERT_TAIL(&ibuf->fds, ifd, + entry); + ifd = NULL; + } else + close(fd); + } + } + /* we do not handle other ctl data level */ + } + +fail: + if (ifd) + free(ifd); + return (n); +} + +ssize_t +imsg_get(struct imsgbuf *ibuf, struct imsg *imsg) +{ + size_t av, left, datalen; + + av = ibuf->r.wpos; + + if (IMSG_HEADER_SIZE > av) + return (0); + + memcpy(&imsg->hdr, ibuf->r.buf, sizeof(imsg->hdr)); + if (imsg->hdr.len < IMSG_HEADER_SIZE || + imsg->hdr.len > MAX_IMSGSIZE) { + errno = ERANGE; + return (-1); + } + if (imsg->hdr.len > av) + return (0); + datalen = imsg->hdr.len - IMSG_HEADER_SIZE; + ibuf->r.rptr = ibuf->r.buf + IMSG_HEADER_SIZE; + if ((imsg->data = malloc(datalen)) == NULL) + return (-1); + + if (imsg->hdr.flags & IMSGF_HASFD) + imsg->fd = imsg_get_fd(ibuf); + else + imsg->fd = -1; + + memcpy(imsg->data, ibuf->r.rptr, datalen); + + if (imsg->hdr.len < av) { + left = av - imsg->hdr.len; + memmove(&ibuf->r.buf, ibuf->r.buf + imsg->hdr.len, left); + ibuf->r.wpos = left; + } else + ibuf->r.wpos = 0; + + return (datalen + IMSG_HEADER_SIZE); +} + +int +imsg_compose(struct imsgbuf *ibuf, uint32_t type, uint32_t peerid, + pid_t pid, int fd, const void *data, uint16_t datalen) +{ + struct ibuf *wbuf; + + if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL) + return (-1); + + if (imsg_add(wbuf, data, datalen) == -1) + return (-1); + + wbuf->fd = fd; + + imsg_close(ibuf, wbuf); + + return (1); +} + +int +imsg_composev(struct imsgbuf *ibuf, uint32_t type, uint32_t peerid, + pid_t pid, int fd, const struct iovec *iov, int iovcnt) +{ + struct ibuf *wbuf; + int i, datalen = 0; + + for (i = 0; i < iovcnt; i++) + datalen += iov[i].iov_len; + + if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL) + return (-1); + + for (i = 0; i < iovcnt; i++) + if (imsg_add(wbuf, iov[i].iov_base, iov[i].iov_len) == -1) + return (-1); + + wbuf->fd = fd; + + imsg_close(ibuf, wbuf); + + return (1); +} + +/* ARGSUSED */ +struct ibuf * +imsg_create(struct imsgbuf *ibuf, uint32_t type, uint32_t peerid, + pid_t pid, uint16_t datalen) +{ + struct ibuf *wbuf; + struct imsg_hdr hdr; + + datalen += IMSG_HEADER_SIZE; + if (datalen > MAX_IMSGSIZE) { + errno = ERANGE; + return (NULL); + } + + hdr.type = type; + hdr.flags = 0; + hdr.peerid = peerid; + if ((hdr.pid = pid) == 0) + hdr.pid = ibuf->pid; + if ((wbuf = ibuf_dynamic(datalen, MAX_IMSGSIZE)) == NULL) { + return (NULL); + } + if (imsg_add(wbuf, &hdr, sizeof(hdr)) == -1) + return (NULL); + + return (wbuf); +} + +int +imsg_add(struct ibuf *msg, const void *data, uint16_t datalen) +{ + if (datalen) + if (ibuf_add(msg, data, datalen) == -1) { + ibuf_free(msg); + return (-1); + } + return (datalen); +} + +void +imsg_close(struct imsgbuf *ibuf, struct ibuf *msg) +{ + struct imsg_hdr *hdr; + + hdr = (struct imsg_hdr *)msg->buf; + + hdr->flags &= ~IMSGF_HASFD; + if (msg->fd != -1) + hdr->flags |= IMSGF_HASFD; + + hdr->len = (uint16_t)msg->wpos; + + ibuf_close(&ibuf->w, msg); +} + +void +imsg_free(struct imsg *imsg) +{ + free(imsg->data); +} + +int +imsg_get_fd(struct imsgbuf *ibuf) +{ + int fd; + struct imsg_fd *ifd; + + if ((ifd = TAILQ_FIRST(&ibuf->fds)) == NULL) + return (-1); + + fd = ifd->fd; + TAILQ_REMOVE(&ibuf->fds, ifd, entry); + free(ifd); + + return (fd); +} + +int +imsg_flush(struct imsgbuf *ibuf) +{ + while (ibuf->w.queued) + if (msgbuf_write(&ibuf->w) <= 0) + return (-1); + return (0); +} + +void +imsg_clear(struct imsgbuf *ibuf) +{ + int fd; + + msgbuf_clear(&ibuf->w); + while ((fd = imsg_get_fd(ibuf)) != -1) + close(fd); +} diff --git a/foobar/portable/openbsd-compat/imsg.h b/foobar/portable/openbsd-compat/imsg.h new file mode 100644 index 00000000..3757c8b9 --- /dev/null +++ b/foobar/portable/openbsd-compat/imsg.h @@ -0,0 +1,115 @@ +/* $OpenBSD: imsg.h,v 1.3 2013/12/26 17:32:33 eric Exp $ */ + +/* + * Copyright (c) 2006, 2007 Pierre-Yves Ritschard <pyr@openbsd.org> + * Copyright (c) 2006, 2007, 2008 Reyk Floeter <reyk@openbsd.org> + * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _IMSG_H_ +#define _IMSG_H_ + +#define IBUF_READ_SIZE 65535 +#define IMSG_HEADER_SIZE sizeof(struct imsg_hdr) +#define MAX_IMSGSIZE 16384 + +#include "defines.h" + +struct ibuf { + TAILQ_ENTRY(ibuf) entry; + u_char *buf; + size_t size; + size_t max; + size_t wpos; + size_t rpos; + int fd; +}; + +struct msgbuf { + TAILQ_HEAD(, ibuf) bufs; + uint32_t queued; + int fd; +}; + +struct ibuf_read { + u_char buf[IBUF_READ_SIZE]; + u_char *rptr; + size_t wpos; +}; + +struct imsg_fd { + TAILQ_ENTRY(imsg_fd) entry; + int fd; +}; + +struct imsgbuf { + TAILQ_HEAD(, imsg_fd) fds; + struct ibuf_read r; + struct msgbuf w; + int fd; + pid_t pid; +}; + +#define IMSGF_HASFD 1 + +struct imsg_hdr { + uint32_t type; + uint16_t len; + uint16_t flags; + uint32_t peerid; + uint32_t pid; +}; + +struct imsg { + struct imsg_hdr hdr; + int fd; + void *data; +}; + + +/* buffer.c */ +struct ibuf *ibuf_open(size_t); +struct ibuf *ibuf_dynamic(size_t, size_t); +int ibuf_add(struct ibuf *, const void *, size_t); +void *ibuf_reserve(struct ibuf *, size_t); +void *ibuf_seek(struct ibuf *, size_t, size_t); +size_t ibuf_size(struct ibuf *); +size_t ibuf_left(struct ibuf *); +void ibuf_close(struct msgbuf *, struct ibuf *); +int ibuf_write(struct msgbuf *); +void ibuf_free(struct ibuf *); +void msgbuf_init(struct msgbuf *); +void msgbuf_clear(struct msgbuf *); +int msgbuf_write(struct msgbuf *); +void msgbuf_drain(struct msgbuf *, size_t); + +/* imsg.c */ +int available_fds(unsigned int); +void imsg_init(struct imsgbuf *, int); +ssize_t imsg_read(struct imsgbuf *); +ssize_t imsg_get(struct imsgbuf *, struct imsg *); +int imsg_compose(struct imsgbuf *, uint32_t, uint32_t, pid_t, + int, const void *, uint16_t); +int imsg_composev(struct imsgbuf *, uint32_t, uint32_t, pid_t, + int, const struct iovec *, int); +struct ibuf *imsg_create(struct imsgbuf *, uint32_t, uint32_t, pid_t, + uint16_t); +int imsg_add(struct ibuf *, const void *, uint16_t); +void imsg_close(struct imsgbuf *, struct ibuf *); +void imsg_free(struct imsg *); +int imsg_flush(struct imsgbuf *); +void imsg_clear(struct imsgbuf *); + +#endif diff --git a/foobar/portable/openbsd-compat/includes.h b/foobar/portable/openbsd-compat/includes.h new file mode 100644 index 00000000..cd044a20 --- /dev/null +++ b/foobar/portable/openbsd-compat/includes.h @@ -0,0 +1,75 @@ +/* $OpenBSD: includes.h,v 1.54 2006/07/22 20:48:23 stevesk Exp $ */ + +/* + * Author: Tatu Ylonen <ylo@cs.hut.fi> + * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + * All rights reserved + * This file includes most of the needed system headers. + * + * As far as I am concerned, the code I have written for this software + * can be used freely for any purpose. Any derived versions of this + * software must be clearly marked as such, and if the derived work is + * incompatible with the protocol description in the RFC file, it must be + * called by a name other than "ssh" or "Secure Shell". + */ + +#ifndef INCLUDES_H +#define INCLUDES_H + +#include "config.h" + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE /* activate extra prototypes for glibc */ +#endif + +#include <sys/types.h> +#include <sys/socket.h> /* For CMSG_* */ + +#ifdef HAVE_LIMITS_H +# include <limits.h> /* For PATH_MAX */ +#endif +#ifdef HAVE_BSTRING_H +# include <bstring.h> +#endif + +#ifdef HAVE_ENDIAN_H +# include <endian.h> +#endif +#ifdef HAVE_MAILLOCK_H +# include <maillock.h> /* For _PATH_MAILDIR */ +#endif +#ifdef HAVE_PATHS_H +# include <paths.h> +#endif + +#ifdef HAVE_RPC_TYPES_H +# include <rpc/types.h> /* For INADDR_LOOPBACK */ +#endif +#ifdef USE_PAM +#if defined(HAVE_SECURITY_PAM_APPL_H) +# include <security/pam_appl.h> +#elif defined (HAVE_PAM_PAM_APPL_H) +# include <pam/pam_appl.h> +#endif +#endif +#include <errno.h> + +/* chl */ +#ifdef HAVE_NETDB_H +# include <netdb.h> +#endif +/* end of chl*/ + +#if !defined(NETDB_INTERNAL) +# define NETDB_INTERNAL (-1) +#endif + +#include <openssl/opensslv.h> /* For OPENSSL_VERSION_NUMBER */ + +#include "defines.h" + +#include "openbsd-compat.h" + +#include "entropy.h" + +#endif /* INCLUDES_H */ diff --git a/foobar/portable/openbsd-compat/inet_net_pton.c b/foobar/portable/openbsd-compat/inet_net_pton.c new file mode 100644 index 00000000..b65cb76f --- /dev/null +++ b/foobar/portable/openbsd-compat/inet_net_pton.c @@ -0,0 +1,236 @@ +/* $OpenBSD: inet_net_pton.c,v 1.8 2013/11/25 18:23:51 deraadt Exp $ */ + +/* + * Copyright (c) 2012 by Gilles Chehade <gilles@openbsd.org> + * Copyright (c) 1996 by Internet Software Consortium. + * + * 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 INTERNET SOFTWARE CONSORTIUM DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE + * CONSORTIUM 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 "includes.h" +#ifndef HAVE_INET_NET_PTON + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +static int inet_net_pton_ipv4(const char *, u_char *, size_t); +static int inet_net_pton_ipv6(const char *, u_char *, size_t); + +/* + * static int + * inet_net_pton(af, src, dst, size) + * convert network number from presentation to network format. + * accepts hex octets, hex strings, decimal octets, and /CIDR. + * "size" is in bytes and describes "dst". + * return: + * number of bits, either imputed classfully or specified with /CIDR, + * or -1 if some failure occurred (check errno). ENOENT means it was + * not a valid network specification. + * author: + * Paul Vixie (ISC), June 1996 + */ +int +inet_net_pton(int af, const char *src, void *dst, size_t size) +{ + switch (af) { + case AF_INET: + return (inet_net_pton_ipv4(src, dst, size)); + case AF_INET6: + return (inet_net_pton_ipv6(src, dst, size)); + default: + errno = EAFNOSUPPORT; + return (-1); + } +} + +/* + * static int + * inet_net_pton_ipv4(src, dst, size) + * convert IPv4 network number from presentation to network format. + * accepts hex octets, hex strings, decimal octets, and /CIDR. + * "size" is in bytes and describes "dst". + * return: + * number of bits, either imputed classfully or specified with /CIDR, + * or -1 if some failure occurred (check errno). ENOENT means it was + * not an IPv4 network specification. + * note: + * network byte order assumed. this means 192.5.5.240/28 has + * 0x11110000 in its fourth octet. + * author: + * Paul Vixie (ISC), June 1996 + */ +static int +inet_net_pton_ipv4(const char *src, u_char *dst, size_t size) +{ + static const char + xdigits[] = "0123456789abcdef", + digits[] = "0123456789"; + int n, ch, tmp, dirty, bits; + const u_char *odst = dst; + + ch = (unsigned char)*src++; + if (ch == '0' && (src[0] == 'x' || src[0] == 'X') + && isascii((unsigned char)src[1]) && isxdigit((unsigned char)src[1])) { + /* Hexadecimal: Eat nybble string. */ + if (size <= 0) + goto emsgsize; + *dst = 0, dirty = 0; + src++; /* skip x or X. */ + while ((ch = (unsigned char)*src++) != '\0' && + isascii(ch) && isxdigit(ch)) { + if (isupper(ch)) + ch = tolower(ch); + n = strchr(xdigits, ch) - xdigits; + assert(n >= 0 && n <= 15); + *dst |= n; + if (!dirty++) + *dst <<= 4; + else if (size-- > 0) + *++dst = 0, dirty = 0; + else + goto emsgsize; + } + if (dirty) + size--; + } else if (isascii(ch) && isdigit(ch)) { + /* Decimal: eat dotted digit string. */ + for (;;) { + tmp = 0; + do { + n = strchr(digits, ch) - digits; + assert(n >= 0 && n <= 9); + tmp *= 10; + tmp += n; + if (tmp > 255) + goto enoent; + } while ((ch = (unsigned char)*src++) != '\0' && + isascii(ch) && isdigit(ch)); + if (size-- <= 0) + goto emsgsize; + *dst++ = (u_char) tmp; + if (ch == '\0' || ch == '/') + break; + if (ch != '.') + goto enoent; + ch = (unsigned char)*src++; + if (!isascii(ch) || !isdigit(ch)) + goto enoent; + } + } else + goto enoent; + + bits = -1; + if (ch == '/' && isascii((unsigned char)src[0]) && + isdigit((unsigned char)src[0]) && dst > odst) { + /* CIDR width specifier. Nothing can follow it. */ + ch = (unsigned char)*src++; /* Skip over the /. */ + bits = 0; + do { + n = strchr(digits, ch) - digits; + assert(n >= 0 && n <= 9); + bits *= 10; + bits += n; + if (bits > 32) + goto emsgsize; + } while ((ch = (unsigned char)*src++) != '\0' && + isascii(ch) && isdigit(ch)); + if (ch != '\0') + goto enoent; + } + + /* Firey death and destruction unless we prefetched EOS. */ + if (ch != '\0') + goto enoent; + + /* If nothing was written to the destination, we found no address. */ + if (dst == odst) + goto enoent; + /* If no CIDR spec was given, infer width from net class. */ + if (bits == -1) { + if (*odst >= 240) /* Class E */ + bits = 32; + else if (*odst >= 224) /* Class D */ + bits = 4; + else if (*odst >= 192) /* Class C */ + bits = 24; + else if (*odst >= 128) /* Class B */ + bits = 16; + else /* Class A */ + bits = 8; + /* If imputed mask is narrower than specified octets, widen. */ + if (bits < ((dst - odst) * 8)) + bits = (dst - odst) * 8; + } + /* Extend network to cover the actual mask. */ + while (bits > ((dst - odst) * 8)) { + if (size-- <= 0) + goto emsgsize; + *dst++ = '\0'; + } + return (bits); + + enoent: + errno = ENOENT; + return (-1); + + emsgsize: + errno = EMSGSIZE; + return (-1); +} + + +static int +inet_net_pton_ipv6(const char *src, u_char *dst, size_t size) +{ + int ret; + int bits; + char buf[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255:255:255:255/128")]; + char *sep; + const char *errstr; + + if (strlcpy(buf, src, sizeof buf) >= sizeof buf) { + errno = EMSGSIZE; + return (-1); + } + + sep = strchr(buf, '/'); + if (sep != NULL) + *sep++ = '\0'; + + ret = inet_pton(AF_INET6, buf, dst); + if (ret != 1) + return (-1); + + if (sep == NULL) + return 128; + + bits = strtonum(sep, 0, 128, &errstr); + if (errstr) { + errno = EINVAL; + return (-1); + } + + return bits; +} + +#endif diff --git a/foobar/portable/openbsd-compat/libasr/asr.c b/foobar/portable/openbsd-compat/libasr/asr.c new file mode 100644 index 00000000..90bc59b4 --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/asr.c @@ -0,0 +1,867 @@ +/* $OpenBSD: asr.c,v 1.61 2018/10/22 17:31:24 krw Exp $ */ +/* + * Copyright (c) 2010-2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <fcntl.h> +#include <resolv.h> +#include <poll.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <limits.h> + +#include "asr_private.h" + +#include "thread_private.h" + +#define DEFAULT_CONF "lookup file\n" +#define DEFAULT_LOOKUP "lookup bind file" + +#define RELOAD_DELAY 15 /* seconds */ + +static void asr_check_reload(struct asr *); +static struct asr_ctx *asr_ctx_create(void); +static void asr_ctx_ref(struct asr_ctx *); +static void asr_ctx_free(struct asr_ctx *); +static int asr_ctx_add_searchdomain(struct asr_ctx *, const char *); +static int asr_ctx_from_file(struct asr_ctx *, const char *); +static int asr_ctx_from_string(struct asr_ctx *, const char *); +static int asr_ctx_parse(struct asr_ctx *, const char *); +static int asr_parse_nameserver(struct sockaddr *, const char *); +static int asr_ndots(const char *); +static void pass0(char **, int, struct asr_ctx *); +static int strsplit(char *, char **, int); +static void asr_ctx_envopts(struct asr_ctx *); +static void *__THREAD_NAME(_asr); + +static struct asr *_asr = NULL; + +#ifndef HAVE_ISSETUGID +#define issetugid() ((getuid() != geteuid())) +#endif + +/* Allocate and configure an async "resolver". */ +static void * +_asr_resolver(void) +{ + static int init = 0; + struct asr *asr; + + if (init == 0) { +#ifdef DEBUG + if (getenv("ASR_DEBUG")) + _asr_debug = stderr; +#endif + init = 1; + } + + if ((asr = calloc(1, sizeof(*asr))) == NULL) + goto fail; + + asr_check_reload(asr); + if (asr->a_ctx == NULL) { + if ((asr->a_ctx = asr_ctx_create()) == NULL) + goto fail; + if (asr_ctx_from_string(asr->a_ctx, DEFAULT_CONF) == -1) + goto fail; + asr_ctx_envopts(asr->a_ctx); + } + +#ifdef DEBUG + _asr_dump_config(_asr_debug, asr); +#endif + return (asr); + + fail: + if (asr) { + if (asr->a_ctx) + asr_ctx_free(asr->a_ctx); + free(asr); + } + + return (NULL); +} + +/* + * Free the "asr" async resolver (or the thread-local resolver if NULL). + * Drop the reference to the current context. + */ +void +_asr_resolver_done(void *arg) +{ + struct asr *asr = arg; + struct asr **priv; + + if (asr == NULL) { + priv = _THREAD_PRIVATE(_asr, _asr, &_asr); + if (*priv == NULL) + return; + asr = *priv; + *priv = NULL; + } + + _asr_ctx_unref(asr->a_ctx); + free(asr); +} + +/* + * Cancel an async query. + */ +void +asr_abort(struct asr_query *as) +{ + _asr_async_free(as); +} + +/* + * Resume the "as" async query resolution. Return one of ASYNC_COND, + * or ASYNC_DONE and put query-specific return values in the user-allocated + * memory at "ar". + */ +int +asr_run(struct asr_query *as, struct asr_result *ar) +{ + int r, saved_errno = errno; + + DPRINT("asr: asr_run(%p, %p) %s ctx=[%p]\n", as, ar, + _asr_querystr(as->as_type), as->as_ctx); + r = as->as_run(as, ar); + + DPRINT("asr: asr_run(%p, %p) -> %s", as, ar, _asr_transitionstr(r)); +#ifdef DEBUG + if (r == ASYNC_COND) +#endif + DPRINT(" fd=%i timeout=%i", ar->ar_fd, ar->ar_timeout); + DPRINT("\n"); + if (r == ASYNC_DONE) + _asr_async_free(as); + + errno = saved_errno; + + return (r); +} +DEF_WEAK(asr_run); + +static int +poll_intrsafe(struct pollfd *fds, nfds_t nfds, int timeout) +{ + struct timespec pollstart, pollend, elapsed; + int r; + + if (clock_gettime(CLOCK_MONOTONIC, &pollstart)) + return -1; + + while ((r = poll(fds, 1, timeout)) == -1 && errno == EINTR) { + if (clock_gettime(CLOCK_MONOTONIC, &pollend)) + return -1; + timespecsub(&pollend, &pollstart, &elapsed); + timeout -= elapsed.tv_sec * 1000 + elapsed.tv_nsec / 1000000; + if (timeout < 1) + return 0; + } + + return r; +} + +/* + * Same as asr_run, but run in a loop that handles the fd conditions result. + */ +int +asr_run_sync(struct asr_query *as, struct asr_result *ar) +{ + struct pollfd fds[1]; + int r, saved_errno = errno; + + while ((r = asr_run(as, ar)) == ASYNC_COND) { + fds[0].fd = ar->ar_fd; + fds[0].events = (ar->ar_cond == ASR_WANT_READ) ? POLLIN:POLLOUT; + + if (poll_intrsafe(fds, 1, ar->ar_timeout) == -1) { + memset(ar, 0, sizeof(*ar)); + ar->ar_errno = errno; + ar->ar_h_errno = NETDB_INTERNAL; + ar->ar_gai_errno = EAI_SYSTEM; + ar->ar_rrset_errno = NETDB_INTERNAL; + _asr_async_free(as); + errno = saved_errno; + return ASYNC_DONE; + } + + /* + * Otherwise, just ignore the error and let asr_run() + * catch the failure. + */ + } + + errno = saved_errno; + + return (r); +} +DEF_WEAK(asr_run_sync); + +/* + * Create a new async request of the given "type" on the async context "ac". + * Take a reference on it so it does not get deleted while the async query + * is running. + */ +struct asr_query * +_asr_async_new(struct asr_ctx *ac, int type) +{ + struct asr_query *as; + + DPRINT("asr: asr_async_new(ctx=%p) type=%i refcount=%i\n", ac, type, + ac ? ac->ac_refcount : 0); + if (ac == NULL || (as = calloc(1, sizeof(*as))) == NULL) + return (NULL); + + ac->ac_refcount += 1; + as->as_ctx = ac; + as->as_fd = -1; + as->as_type = type; + as->as_state = ASR_STATE_INIT; + + return (as); +} + +/* + * Free an async query and unref the associated context. + */ +void +_asr_async_free(struct asr_query *as) +{ + DPRINT("asr: asr_async_free(%p)\n", as); + + if (as->as_subq) + _asr_async_free(as->as_subq); + + switch (as->as_type) { + case ASR_SEND: + if (as->as_fd != -1) + close(as->as_fd); + if (as->as.dns.obuf && !(as->as_flags & ASYNC_EXTOBUF)) + free(as->as.dns.obuf); + if (as->as.dns.ibuf) + free(as->as.dns.ibuf); + if (as->as.dns.dname) + free(as->as.dns.dname); + break; + + case ASR_SEARCH: + if (as->as.search.name) + free(as->as.search.name); + break; + + case ASR_GETRRSETBYNAME: + if (as->as.rrset.name) + free(as->as.rrset.name); + break; + + case ASR_GETHOSTBYNAME: + case ASR_GETHOSTBYADDR: + if (as->as.hostnamadr.name) + free(as->as.hostnamadr.name); + break; + + case ASR_GETADDRINFO: + if (as->as.ai.aifirst) + freeaddrinfo(as->as.ai.aifirst); + if (as->as.ai.hostname) + free(as->as.ai.hostname); + if (as->as.ai.servname) + free(as->as.ai.servname); + if (as->as.ai.fqdn) + free(as->as.ai.fqdn); + break; + + case ASR_GETNAMEINFO: + break; + } + + _asr_ctx_unref(as->as_ctx); + free(as); +} + +/* + * Get a context from the given resolver. This takes a new reference to + * the returned context, which *must* be explicitly dropped when done + * using this context. + */ +struct asr_ctx * +_asr_use_resolver(void *arg) +{ + struct asr *asr = arg; + struct asr **priv; + + if (asr == NULL) { + DPRINT("using thread-local resolver\n"); + priv = _THREAD_PRIVATE(_asr, _asr, &_asr); + if (*priv == NULL) { + DPRINT("setting up thread-local resolver\n"); + *priv = _asr_resolver(); + } + asr = *priv; + } + if (asr != NULL) { + asr_check_reload(asr); + asr_ctx_ref(asr->a_ctx); + return (asr->a_ctx); + } + return (NULL); +} + +static void +asr_ctx_ref(struct asr_ctx *ac) +{ + DPRINT("asr: asr_ctx_ref(ctx=%p) refcount=%i\n", ac, ac->ac_refcount); + ac->ac_refcount += 1; +} + +/* + * Drop a reference to an async context, freeing it if the reference + * count drops to 0. + */ +void +_asr_ctx_unref(struct asr_ctx *ac) +{ + DPRINT("asr: asr_ctx_unref(ctx=%p) refcount=%i\n", ac, + ac ? ac->ac_refcount : 0); + if (ac == NULL) + return; + if (--ac->ac_refcount) + return; + + asr_ctx_free(ac); +} + +static void +asr_ctx_free(struct asr_ctx *ac) +{ + int i; + + if (ac->ac_domain) + free(ac->ac_domain); + for (i = 0; i < ASR_MAXNS; i++) + free(ac->ac_ns[i]); + for (i = 0; i < ASR_MAXDOM; i++) + free(ac->ac_dom[i]); + + free(ac); +} + +/* + * Reload the configuration file if it has changed on disk. + */ +static void +asr_check_reload(struct asr *asr) +{ + struct asr_ctx *ac; + struct stat st; + struct timespec ts; + pid_t pid; + + pid = getpid(); + if (pid != asr->a_pid) { + asr->a_pid = pid; + asr->a_rtime = 0; + } + + if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1) + return; + + if ((ts.tv_sec - asr->a_rtime) < RELOAD_DELAY && asr->a_rtime != 0) + return; + asr->a_rtime = ts.tv_sec; + + DPRINT("asr: checking for update of \"%s\"\n", _PATH_RESCONF); + if (stat(_PATH_RESCONF, &st) == -1 || + asr->a_mtime == st.st_mtime || + (ac = asr_ctx_create()) == NULL) + return; + asr->a_mtime = st.st_mtime; + + DPRINT("asr: reloading config file\n"); + if (asr_ctx_from_file(ac, _PATH_RESCONF) == -1) { + asr_ctx_free(ac); + return; + } + + asr_ctx_envopts(ac); + if (asr->a_ctx) + _asr_ctx_unref(asr->a_ctx); + asr->a_ctx = ac; +} + +/* + * Construct a fully-qualified domain name for the given name and domain. + * If "name" ends with a '.' it is considered as a FQDN by itself. + * Otherwise, the domain, which must be a FQDN, is appended to "name" (it + * may have a leading dot which would be ignored). If the domain is null, + * then "." is used. Return the length of the constructed FQDN or (0) on + * error. + */ +size_t +_asr_make_fqdn(const char *name, const char *domain, char *buf, size_t buflen) +{ + size_t len; + + if (domain == NULL) + domain = "."; + else if ((len = strlen(domain)) == 0) + return (0); + else if (domain[len -1] != '.') + return (0); + + len = strlen(name); + if (len == 0) { + if (strlcpy(buf, domain, buflen) >= buflen) + return (0); + } else if (name[len - 1] != '.') { + if (domain[0] == '.') + domain += 1; + if (strlcpy(buf, name, buflen) >= buflen || + strlcat(buf, ".", buflen) >= buflen || + strlcat(buf, domain, buflen) >= buflen) + return (0); + } else { + if (strlcpy(buf, name, buflen) >= buflen) + return (0); + } + + return (strlen(buf)); +} + +/* + * Count the dots in a string. + */ +static int +asr_ndots(const char *s) +{ + int n; + + for (n = 0; *s; s++) + if (*s == '.') + n += 1; + + return (n); +} + +/* + * Allocate a new empty context. + */ +static struct asr_ctx * +asr_ctx_create(void) +{ + struct asr_ctx *ac; + + if ((ac = calloc(1, sizeof(*ac))) == NULL) + return (NULL); + + ac->ac_options = RES_RECURSE | RES_DEFNAMES | RES_DNSRCH; + ac->ac_refcount = 1; + ac->ac_ndots = 1; +#ifndef ASR_IPV4_BEFORE_IPV6 + ac->ac_family[0] = AF_INET6; + ac->ac_family[1] = AF_INET; +#else + ac->ac_family[0] = AF_INET; + ac->ac_family[1] = AF_INET6; +#endif + ac->ac_family[2] = -1; + + ac->ac_nscount = 0; + ac->ac_nstimeout = 5; + ac->ac_nsretries = 4; + + return (ac); +} + +struct asr_ctx * +_asr_no_resolver(void) +{ + return asr_ctx_create(); +} + +/* + * Add a search domain to the async context. + */ +static int +asr_ctx_add_searchdomain(struct asr_ctx *ac, const char *domain) +{ + char buf[MAXDNAME]; + + if (ac->ac_domcount == ASR_MAXDOM) + return (-1); + + if (_asr_make_fqdn(domain, NULL, buf, sizeof(buf)) == 0) + return (-1); + + if ((ac->ac_dom[ac->ac_domcount] = strdup(buf)) == NULL) + return (0); + + ac->ac_domcount += 1; + + return (1); +} + +static int +strsplit(char *line, char **tokens, int ntokens) +{ + int ntok; + char *cp, **tp; + + for (cp = line, tp = tokens, ntok = 0; + ntok < ntokens && (*tp = strsep(&cp, " \t")) != NULL; ) + if (**tp != '\0') { + tp++; + ntok++; + } + + return (ntok); +} + +/* + * Pass on a split config line. + */ +static void +pass0(char **tok, int n, struct asr_ctx *ac) +{ + int i, j, d; + const char *e; + struct sockaddr_storage ss; + + if (!strcmp(tok[0], "nameserver")) { + if (ac->ac_nscount == ASR_MAXNS) + return; + if (n != 2) + return; + if (asr_parse_nameserver((struct sockaddr *)&ss, tok[1])) + return; + if ((ac->ac_ns[ac->ac_nscount] = calloc(1, SS_LEN(&ss))) == NULL) + return; + memmove(ac->ac_ns[ac->ac_nscount], &ss, SS_LEN(&ss)); + ac->ac_nscount += 1; + + } else if (!strcmp(tok[0], "domain")) { + if (n != 2) + return; + if (ac->ac_domain) + return; + ac->ac_domain = strdup(tok[1]); + + } else if (!strcmp(tok[0], "lookup")) { + /* ensure that each lookup is only given once */ + for (i = 1; i < n; i++) + for (j = i + 1; j < n; j++) + if (!strcmp(tok[i], tok[j])) + return; + ac->ac_dbcount = 0; + for (i = 1; i < n && ac->ac_dbcount < ASR_MAXDB; i++) { + if (!strcmp(tok[i], "yp")) { + /* silently deprecated */ + } else if (!strcmp(tok[i], "bind")) + ac->ac_db[ac->ac_dbcount++] = ASR_DB_DNS; + else if (!strcmp(tok[i], "file")) + ac->ac_db[ac->ac_dbcount++] = ASR_DB_FILE; + } + } else if (!strcmp(tok[0], "search")) { + /* resolv.conf says the last line wins */ + for (i = 0; i < ASR_MAXDOM; i++) { + free(ac->ac_dom[i]); + ac->ac_dom[i] = NULL; + } + ac->ac_domcount = 0; + for (i = 1; i < n; i++) + asr_ctx_add_searchdomain(ac, tok[i]); + + } else if (!strcmp(tok[0], "family")) { + if (n == 1 || n > 3) + return; + for (i = 1; i < n; i++) + if (strcmp(tok[i], "inet4") && strcmp(tok[i], "inet6")) + return; + for (i = 1; i < n; i++) + ac->ac_family[i - 1] = strcmp(tok[i], "inet4") ? \ + AF_INET6 : AF_INET; + ac->ac_family[i - 1] = -1; + + } else if (!strcmp(tok[0], "options")) { + for (i = 1; i < n; i++) { + if (!strcmp(tok[i], "tcp")) + ac->ac_options |= RES_USEVC; + else if (!strcmp(tok[i], "edns0")) + ac->ac_options |= RES_USE_EDNS0; + else if ((!strncmp(tok[i], "ndots:", 6))) { + e = NULL; + d = strtonum(tok[i] + 6, 1, 16, &e); + if (e == NULL) + ac->ac_ndots = d; + } + } + } +} + +/* + * Setup an async context with the config specified in the string "str". + */ +static int +asr_ctx_from_string(struct asr_ctx *ac, const char *str) +{ + char buf[512], *ch; + + asr_ctx_parse(ac, str); + + if (ac->ac_dbcount == 0) { + /* No lookup directive */ + asr_ctx_parse(ac, DEFAULT_LOOKUP); + } + + if (ac->ac_nscount == 0) + asr_ctx_parse(ac, "nameserver 127.0.0.1"); + + if (ac->ac_domain == NULL) + if (gethostname(buf, sizeof buf) == 0) { + ch = strchr(buf, '.'); + if (ch) + ac->ac_domain = strdup(ch + 1); + else /* Assume root. see resolv.conf(5) */ + ac->ac_domain = strdup(""); + } + + /* If no search domain was specified, use the local subdomains */ + if (ac->ac_domcount == 0) + for (ch = ac->ac_domain; ch; ) { + asr_ctx_add_searchdomain(ac, ch); + ch = strchr(ch, '.'); + if (ch && asr_ndots(++ch) == 0) + break; + } + + return (0); +} + +/* + * Setup the "ac" async context from the file at location "path". + */ +static int +asr_ctx_from_file(struct asr_ctx *ac, const char *path) +{ + FILE *cf; + char buf[4096]; + ssize_t r; + + cf = fopen(path, "re"); + if (cf == NULL) + return (-1); + + r = fread(buf, 1, sizeof buf - 1, cf); + if (feof(cf) == 0) { + DPRINT("asr: config file too long: \"%s\"\n", path); + r = -1; + } + fclose(cf); + if (r == -1) + return (-1); + buf[r] = '\0'; + + return asr_ctx_from_string(ac, buf); +} + +/* + * Parse lines in the configuration string. For each one, split it into + * tokens and pass them to "pass0" for processing. + */ +static int +asr_ctx_parse(struct asr_ctx *ac, const char *str) +{ + size_t len; + const char *line; + char buf[1024]; + char *tok[10]; + int ntok; + + line = str; + while (*line) { + len = strcspn(line, "\n\0"); + if (len < sizeof buf) { + memmove(buf, line, len); + buf[len] = '\0'; + } else + buf[0] = '\0'; + line += len; + if (*line == '\n') + line++; + buf[strcspn(buf, ";#")] = '\0'; + if ((ntok = strsplit(buf, tok, 10)) == 0) + continue; + + pass0(tok, ntok, ac); + } + + return (0); +} + +/* + * Check for environment variables altering the configuration as described + * in resolv.conf(5). Although not documented there, this feature is disabled + * for setuid/setgid programs. + */ +static void +asr_ctx_envopts(struct asr_ctx *ac) +{ + char buf[4096], *e; + size_t s; + + if (issetugid()) { + ac->ac_options |= RES_NOALIASES; + return; + } + + if ((e = getenv("RES_OPTIONS")) != NULL) { + strlcpy(buf, "options ", sizeof buf); + strlcat(buf, e, sizeof buf); + s = strlcat(buf, "\n", sizeof buf); + if (s < sizeof buf) + asr_ctx_parse(ac, buf); + } + + if ((e = getenv("LOCALDOMAIN")) != NULL) { + strlcpy(buf, "search ", sizeof buf); + strlcat(buf, e, sizeof buf); + s = strlcat(buf, "\n", sizeof buf); + if (s < sizeof buf) + asr_ctx_parse(ac, buf); + } +} + +/* + * Parse a resolv.conf(5) nameserver string into a sockaddr. + */ +static int +asr_parse_nameserver(struct sockaddr *sa, const char *s) +{ + in_port_t portno = 53; + + if (_asr_sockaddr_from_str(sa, PF_UNSPEC, s) == -1) + return (-1); + + if (sa->sa_family == PF_INET) + ((struct sockaddr_in *)sa)->sin_port = htons(portno); + else if (sa->sa_family == PF_INET6) + ((struct sockaddr_in6 *)sa)->sin6_port = htons(portno); + + return (0); +} + +/* + * Turn a (uncompressed) DNS domain name into a regular nul-terminated string + * where labels are separated by dots. The result is put into the "buf" buffer, + * truncated if it exceeds "max" chars. The function returns "buf". + */ +char * +_asr_strdname(const char *_dname, char *buf, size_t max) +{ + const unsigned char *dname = _dname; + char *res; + size_t left, n, count; + + if (_dname[0] == 0) { + strlcpy(buf, ".", max); + return buf; + } + + res = buf; + left = max - 1; + for (n = 0; dname[0] && left; n += dname[0]) { + count = (dname[0] < (left - 1)) ? dname[0] : (left - 1); + memmove(buf, dname + 1, count); + dname += dname[0] + 1; + left -= count; + buf += count; + if (left) { + left -= 1; + *buf++ = '.'; + } + } + buf[0] = 0; + + return (res); +} + +/* + * Read and split the next line from the given namedb file. + * Return -1 on error, or put the result in the "tokens" array of + * size "ntoken" and returns the number of token on the line. + */ +int +_asr_parse_namedb_line(FILE *file, char **tokens, int ntoken, char *lbuf, size_t sz) +{ + size_t len; + char *buf; + int ntok; + + again: + if ((buf = fgetln(file, &len)) == NULL) + return (-1); + + if (len >= sz) + goto again; + + if (buf[len - 1] == '\n') + len--; + else { + memcpy(lbuf, buf, len); + buf = lbuf; + } + + buf[len] = '\0'; + buf[strcspn(buf, "#")] = '\0'; + if ((ntok = strsplit(buf, tokens, ntoken)) == 0) + goto again; + + return (ntok); +} + +/* + * Update the async context so that it uses the next configured DB. + * Return 0 on success, or -1 if no more DBs is available. + */ +int +_asr_iter_db(struct asr_query *as) +{ + if (as->as_db_idx >= as->as_ctx->ac_dbcount) { + DPRINT("asr_iter_db: done\n"); + return (-1); + } + + as->as_db_idx += 1; + DPRINT("asr_iter_db: %i\n", as->as_db_idx); + + return (0); +} diff --git a/foobar/portable/openbsd-compat/libasr/asr.h b/foobar/portable/openbsd-compat/libasr/asr.h new file mode 100644 index 00000000..e9725e6b --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/asr.h @@ -0,0 +1,95 @@ +/* $OpenBSD: asr.h,v 1.1 2014/03/26 18:13:15 eric Exp $ */ +/* + * Copyright (c) 2012-2014 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Expected fd conditions + */ +#define ASR_WANT_READ 1 +#define ASR_WANT_WRITE 2 + +/* + * Structure through which asynchronous query results are returned when + * calling asr_run(). + */ +struct asr_result { + /* Fields set if the query is not done yet (asr_run returns 0) */ + int ar_cond; /* ASR_WANT_READ or ASR_WANT_WRITE */ + int ar_fd; /* the fd waiting for io condition */ + int ar_timeout; /* time to wait for in milliseconds */ + + /* Error fields. Depends on the query type. */ + int ar_errno; + int ar_h_errno; + int ar_gai_errno; + int ar_rrset_errno; + + /* Result for res_*_async() calls */ + int ar_count; /* number of answers in the dns reply */ + int ar_rcode; /* response code in the dns reply */ + void *ar_data; /* raw reply packet (must be freed) */ + int ar_datalen; /* reply packet length */ + struct sockaddr_storage ar_ns; /* nameserver that responded */ + + /* Result for other calls. Must be freed properly. */ + struct addrinfo *ar_addrinfo; + struct rrsetinfo *ar_rrsetinfo; + struct hostent *ar_hostent; + struct netent *ar_netent; +}; + +/* + * Asynchronous query management. + */ + +/* Forward declaration. The API uses opaque pointers as query handles. */ +struct asr_query; + +int asr_run(struct asr_query *, struct asr_result *); +int asr_run_sync(struct asr_query *, struct asr_result *); +void asr_abort(struct asr_query *); + +/* + * Asynchronous version of the resolver functions. Similar prototypes, with + * an extra context parameter at the end which must currently be set to NULL. + * All functions return a handle suitable for use with the management functions + * above. + */ +struct asr_query *res_send_async(const unsigned char *, int, void *); +struct asr_query *res_query_async(const char *, int, int, void *); +struct asr_query *res_search_async(const char *, int, int, void *); + +struct asr_query *getrrsetbyname_async(const char *, unsigned int, unsigned int, + unsigned int, void *); + +struct asr_query *gethostbyname_async(const char *, void *); +struct asr_query *gethostbyname2_async(const char *, int, void *); +struct asr_query *gethostbyaddr_async(const void *, socklen_t, int, void *); + +struct asr_query *getnetbyname_async(const char *, void *); +struct asr_query *getnetbyaddr_async(in_addr_t, int, void *); + +struct asr_query *getaddrinfo_async(const char *, const char *, + const struct addrinfo *, void *); +struct asr_query *getnameinfo_async(const struct sockaddr *, socklen_t, char *, + size_t, char *, size_t, int, void *); + +/* only there for -portable */ +void asr_freeaddrinfo(struct addrinfo *); + +/* from in event.h */ +struct event_asr * event_asr_run(struct asr_query *, + void (*)(struct asr_result *, void *), void *); diff --git a/foobar/portable/openbsd-compat/libasr/asr_compat.c b/foobar/portable/openbsd-compat/libasr/asr_compat.c new file mode 100644 index 00000000..ee958357 --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/asr_compat.c @@ -0,0 +1,102 @@ +/* $OpenBSD: asr_debug.c,v 1.25 2018/04/28 15:16:49 schwarze Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <arpa/nameser.h> +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +#include <arpa/nameser_compat.h> +#endif + +#include "asr_compat.h" + +#ifndef HAVE___P_CLASS +const char * +__p_class(int c) +{ + switch(c) { + case C_IN: return "IN"; + case C_CHAOS: return "CHAOS"; + case C_HS: return "HESIOD"; + case C_ANY: return "ANY"; + default: return "?"; + } +}; +#endif /* !HAVE___P_CLASS */ + +#ifndef HAVE___P_TYPE +const char * +__p_type(int t) +{ + switch(t) { + case T_A: return "A"; + case T_NS: return "NS"; + case T_MD: return "MD"; + case T_MF: return "MF"; + case T_CNAME: return "CNAME"; + case T_SOA: return "SOA"; + case T_MB: return "MB"; + case T_MG: return "MG"; + case T_MR: return "MR"; + case T_NULL: return "NULL"; + case T_WKS: return "WKS"; + case T_PTR: return "PTR"; + case T_HINFO: return "HINFO"; + case T_MINFO: return "MINFO"; + case T_MX: return "MX"; + case T_TXT: return "TXT"; + case T_RP: return "RP"; + case T_AFSDB: return "AFSDB"; + case T_X25: return "X25"; + case T_ISDN: return "ISDN"; + case T_RT: return "RT"; + case T_NSAP: return "NSAP"; + case T_NSAP_PTR:return"NSAP_PTR"; + case T_SIG: return "SIG"; + case T_KEY: return "KEY"; + case T_PX: return "PX"; + case T_GPOS: return "GPOS"; + case T_AAAA: return "AAAA"; + case T_LOC: return "LOC"; + case T_NXT: return "NXT"; + case T_EID: return "EID"; + case T_NIMLOC: return "NIMLOC"; + case T_SRV: return "SRV"; + case T_ATMA: return "ATMA"; + case T_OPT: return "OPT"; + case T_IXFR: return "IXFR"; + case T_AXFR: return "AXFR"; + case T_MAILB: return "MAILB"; + case T_MAILA: return "MAILA"; +#ifdef T_UINFO + case T_UINFO: return "UINFO"; +#endif +#ifdef T_UID + case T_UID: return "UID"; +#endif +#ifdef T_GID + case T_GID: return "GID"; +#endif + case T_NAPTR: return "NAPTR"; +#ifdef T_UNSPEC + case T_UNSPEC: return "UNSPEC"; +#endif + case T_ANY: return "ANY"; + default: return "?"; + } +} +#endif /* !HAVE___P_TYPE */ diff --git a/foobar/portable/openbsd-compat/libasr/asr_compat.h b/foobar/portable/openbsd-compat/libasr/asr_compat.h new file mode 100644 index 00000000..2c7686a4 --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/asr_compat.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2018 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +/* source compat */ +#define ASR_BUFSIZ 1024 + +#define DEF_WEAK(x) +#define __THREAD_NAME(x) __thread_name_ ## x + +#ifndef __BEGIN_HIDDEN_DECLS +#define __BEGIN_HIDDEN_DECLS +#endif +#ifndef __END_HIDDEN_DECLS +#define __END_HIDDEN_DECLS +#endif + +/* + * netdb.h + */ +#ifndef NETDB_SUCCESS +#define NETDB_SUCCESS 0 +#endif + +#ifndef NETDB_INTERNAL +#define NETDB_INTERNAL -1 +#endif + +#ifndef AI_FQDN +#define AI_FQDN AI_CANONNAME +#endif + +#ifndef AI_MASK +#define AI_MASK \ + (AI_PASSIVE | AI_CANONNAME | AI_NUMERICHOST | AI_NUMERICSERV | AI_ADDRCONFIG | AI_FQDN) +#endif + +#ifndef SCOPE_DELIMITER +#define SCOPE_DELIMITER '%' +#endif + +#ifndef _PATH_HOSTS +#define _PATH_HOSTS "/etc/hosts" +#endif + +#ifndef _PATH_NETWORKS +#define _PATH_NETWORKS "/etc/networks" +#endif + +/* + * arpa/nameserv.h + */ +#ifndef T_OPT +#define T_OPT 41 +#endif + +#ifndef DNS_MESSAGEEXTFLAG_DO +#define DNS_MESSAGEEXTFLAG_DO 0x8000U +#endif + +#ifndef HAVE___P_CLASS +const char * __p_class(int); +#endif + +#ifndef HAVE___P_TYPE +const char * __p_type(int); +#endif diff --git a/foobar/portable/openbsd-compat/libasr/asr_debug.c b/foobar/portable/openbsd-compat/libasr/asr_debug.c new file mode 100644 index 00000000..be80436a --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/asr_debug.c @@ -0,0 +1,362 @@ +/* $OpenBSD: asr_debug.c,v 1.26 2019/07/03 03:24:03 deraadt Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/nameser.h> +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +#include <arpa/nameser_compat.h> +#endif +#include <arpa/inet.h> +#include <netdb.h> + +#include <asr.h> +#include <resolv.h> +#include <string.h> + +#include "asr_private.h" + +static const char *rcodetostr(uint16_t); +static const char *print_dname(const char *, char *, size_t); +static const char *print_header(const struct asr_dns_header *, char *, size_t); +static const char *print_query(const struct asr_dns_query *, char *, size_t); +static const char *print_rr(const struct asr_dns_rr *, char *, size_t); + +FILE *_asr_debug = NULL; + +#define OPCODE_SHIFT 11 + +static const char * +rcodetostr(uint16_t v) +{ + switch (v) { + case NOERROR: return "NOERROR"; + case FORMERR: return "FORMERR"; + case SERVFAIL: return "SERVFAIL"; + case NXDOMAIN: return "NXDOMAIN"; + case NOTIMP: return "NOTIMP"; + case REFUSED: return "REFUSED"; + default: return "?"; + } +} + +static const char * +print_dname(const char *_dname, char *buf, size_t max) +{ + return (_asr_strdname(_dname, buf, max)); +} + +static const char * +print_rr(const struct asr_dns_rr *rr, char *buf, size_t max) +{ + char *res; + char tmp[256]; + char tmp2[256]; + int r; + + res = buf; + + r = snprintf(buf, max, "%s %u %s %s ", + print_dname(rr->rr_dname, tmp, sizeof tmp), + rr->rr_ttl, + __p_class(rr->rr_class), + __p_type(rr->rr_type)); + if (r < 0 || (size_t)r >= max) { + buf[0] = '\0'; + return (buf); + } + + if ((size_t)r >= max) + return (buf); + + max -= r; + buf += r; + + switch (rr->rr_type) { + case T_CNAME: + print_dname(rr->rr.cname.cname, buf, max); + break; + case T_MX: + snprintf(buf, max, "%lu %s", + (unsigned long)rr->rr.mx.preference, + print_dname(rr->rr.mx.exchange, tmp, sizeof tmp)); + break; + case T_NS: + print_dname(rr->rr.ns.nsname, buf, max); + break; + case T_PTR: + print_dname(rr->rr.ptr.ptrname, buf, max); + break; + case T_SOA: + snprintf(buf, max, "%s %s %lu %lu %lu %lu %lu", + print_dname(rr->rr.soa.rname, tmp, sizeof tmp), + print_dname(rr->rr.soa.mname, tmp2, sizeof tmp2), + (unsigned long)rr->rr.soa.serial, + (unsigned long)rr->rr.soa.refresh, + (unsigned long)rr->rr.soa.retry, + (unsigned long)rr->rr.soa.expire, + (unsigned long)rr->rr.soa.minimum); + break; + case T_A: + if (rr->rr_class != C_IN) + goto other; + snprintf(buf, max, "%s", inet_ntop(AF_INET, + &rr->rr.in_a.addr, tmp, sizeof tmp)); + break; + case T_AAAA: + if (rr->rr_class != C_IN) + goto other; + snprintf(buf, max, "%s", inet_ntop(AF_INET6, + &rr->rr.in_aaaa.addr6, tmp, sizeof tmp)); + break; + default: + other: + snprintf(buf, max, "(rdlen=%i)", (int)rr->rr.other.rdlen); + break; + } + + return (res); +} + +static const char * +print_query(const struct asr_dns_query *q, char *buf, size_t max) +{ + char b[256]; + + snprintf(buf, max, "%s %s %s", + print_dname(q->q_dname, b, sizeof b), + __p_class(q->q_class), __p_type(q->q_type)); + + return (buf); +} + +static const char * +print_header(const struct asr_dns_header *h, char *buf, size_t max) +{ + snprintf(buf, max, + "id:0x%04x %s op:%i %s %s %s %s z:%i %s %s r:%s qd:%i an:%i ns:%i ar:%i", + ((int)h->id), + (h->flags & QR_MASK) ? "QR":" ", + (int)(OPCODE(h->flags) >> OPCODE_SHIFT), + (h->flags & AA_MASK) ? "AA":" ", + (h->flags & TC_MASK) ? "TC":" ", + (h->flags & RD_MASK) ? "RD":" ", + (h->flags & RA_MASK) ? "RA":" ", + (h->flags & Z_MASK), + (h->flags & AD_MASK) ? "AD":" ", + (h->flags & CD_MASK) ? "CD":" ", + rcodetostr(RCODE(h->flags)), + h->qdcount, h->ancount, h->nscount, h->arcount); + + return (buf); +} + +void +_asr_dump_packet(FILE *f, const void *data, size_t len) +{ + char buf[1024]; + struct asr_unpack p; + struct asr_dns_header h; + struct asr_dns_query q; + struct asr_dns_rr rr; + int i, an, ns, ar, n; + + if (f == NULL) + return; + + _asr_unpack_init(&p, data, len); + + if (_asr_unpack_header(&p, &h) == -1) { + fprintf(f, ";; BAD PACKET: %s\n", strerror(p.err)); + return; + } + + fprintf(f, ";; HEADER %s\n", print_header(&h, buf, sizeof buf)); + + if (h.qdcount) + fprintf(f, ";; QUERY SECTION:\n"); + for (i = 0; i < h.qdcount; i++) { + if (_asr_unpack_query(&p, &q) == -1) + goto error; + fprintf(f, "%s\n", print_query(&q, buf, sizeof buf)); + } + + an = 0; + ns = an + h.ancount; + ar = ns + h.nscount; + n = ar + h.arcount; + + for (i = 0; i < n; i++) { + if (i == an) + fprintf(f, "\n;; ANSWER SECTION:\n"); + if (i == ns) + fprintf(f, "\n;; AUTHORITY SECTION:\n"); + if (i == ar) + fprintf(f, "\n;; ADDITIONAL SECTION:\n"); + + if (_asr_unpack_rr(&p, &rr) == -1) + goto error; + fprintf(f, "%s\n", print_rr(&rr, buf, sizeof buf)); + } + + if (p.offset != len) + fprintf(f, ";; REMAINING GARBAGE %zu\n", len - p.offset); + + error: + if (p.err) + fprintf(f, ";; ERROR AT OFFSET %zu/%zu: %s\n", p.offset, p.len, + strerror(p.err)); +} + +const char * +_asr_print_sockaddr(const struct sockaddr *sa, char *buf, size_t len) +{ + char h[256]; + int portno; + union { + const struct sockaddr *sa; + const struct sockaddr_in *sin; + const struct sockaddr_in6 *sin6; + } s; + + s.sa = sa; + + switch (sa->sa_family) { + case AF_INET: + inet_ntop(AF_INET, &s.sin->sin_addr, h, sizeof h); + portno = ntohs(s.sin->sin_port); + break; + case AF_INET6: + inet_ntop(AF_INET6, &s.sin6->sin6_addr, h, sizeof h); + portno = ntohs(s.sin6->sin6_port); + break; + default: + snprintf(buf, len, "?"); + return (buf); + } + + snprintf(buf, len, "%s:%i", h, portno); + return (buf); +} + +void +_asr_dump_config(FILE *f, struct asr *a) +{ + char buf[256]; + int i; + struct asr_ctx *ac; + unsigned int o; + + if (f == NULL) + return; + + ac = a->a_ctx; + + fprintf(f, "--------- ASR CONFIG ---------------\n"); + fprintf(f, "DOMAIN \"%s\"\n", ac->ac_domain); + fprintf(f, "SEARCH\n"); + for (i = 0; i < ac->ac_domcount; i++) + fprintf(f, " \"%s\"\n", ac->ac_dom[i]); + fprintf(f, "OPTIONS\n"); + fprintf(f, " options:"); + o = ac->ac_options; + +#define PRINTOPT(flag, n) if (o & (flag)) { fprintf(f, " " n); o &= ~(flag); } + PRINTOPT(RES_INIT, "INIT"); + PRINTOPT(RES_DEBUG, "DEBUG"); + PRINTOPT(RES_USEVC, "USEVC"); + PRINTOPT(RES_IGNTC, "IGNTC"); + PRINTOPT(RES_RECURSE, "RECURSE"); + PRINTOPT(RES_DEFNAMES, "DEFNAMES"); + PRINTOPT(RES_STAYOPEN, "STAYOPEN"); + PRINTOPT(RES_DNSRCH, "DNSRCH"); + PRINTOPT(RES_NOALIASES, "NOALIASES"); + PRINTOPT(RES_USE_EDNS0, "USE_EDNS0"); + PRINTOPT(RES_USE_DNSSEC, "USE_DNSSEC"); + if (o) + fprintf(f, " 0x%08x", o); + fprintf(f, "\n"); + + fprintf(f, " ndots: %i\n", ac->ac_ndots); + fprintf(f, " family:"); + for (i = 0; ac->ac_family[i] != -1; i++) + fprintf(f, " %s", (ac->ac_family[i] == AF_INET)?"inet4":"inet6"); + fprintf(f, "\n"); + fprintf(f, "NAMESERVERS timeout=%i retry=%i\n", + ac->ac_nstimeout, + ac->ac_nsretries); + for (i = 0; i < ac->ac_nscount; i++) + fprintf(f, " %s\n", _asr_print_sockaddr(ac->ac_ns[i], buf, + sizeof buf)); + fprintf(f, "LOOKUP %s", ac->ac_db); + fprintf(f, "\n------------------------------------\n"); +} + +#define CASE(n) case n: return #n + +const char * +_asr_statestr(int state) +{ + switch (state) { + CASE(ASR_STATE_INIT); + CASE(ASR_STATE_NEXT_DOMAIN); + CASE(ASR_STATE_NEXT_DB); + CASE(ASR_STATE_SAME_DB); + CASE(ASR_STATE_NEXT_FAMILY); + CASE(ASR_STATE_NEXT_NS); + CASE(ASR_STATE_UDP_SEND); + CASE(ASR_STATE_UDP_RECV); + CASE(ASR_STATE_TCP_WRITE); + CASE(ASR_STATE_TCP_READ); + CASE(ASR_STATE_PACKET); + CASE(ASR_STATE_SUBQUERY); + CASE(ASR_STATE_NOT_FOUND); + CASE(ASR_STATE_HALT); + default: + return "?"; + } +}; + +const char * +_asr_querystr(int type) +{ + switch (type) { + CASE(ASR_SEND); + CASE(ASR_SEARCH); + CASE(ASR_GETRRSETBYNAME); + CASE(ASR_GETHOSTBYNAME); + CASE(ASR_GETHOSTBYADDR); + CASE(ASR_GETADDRINFO); + CASE(ASR_GETNAMEINFO); + default: + return "?"; + } +} + +const char * +_asr_transitionstr(int type) +{ + switch (type) { + CASE(ASYNC_COND); + CASE(ASYNC_DONE); + default: + return "?"; + } +} diff --git a/foobar/portable/openbsd-compat/libasr/asr_private.h b/foobar/portable/openbsd-compat/libasr/asr_private.h new file mode 100644 index 00000000..acf0e874 --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/asr_private.h @@ -0,0 +1,359 @@ +/* $OpenBSD: asr_private.h,v 1.47 2018/04/28 15:16:49 schwarze Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdio.h> + +#include "asr_compat.h" + +#define QR_MASK (0x1 << 15) +#define OPCODE_MASK (0xf << 11) +#define AA_MASK (0x1 << 10) +#define TC_MASK (0x1 << 9) +#define RD_MASK (0x1 << 8) +#define RA_MASK (0x1 << 7) +#define Z_MASK (0x1 << 6) +#define AD_MASK (0x1 << 5) +#define CD_MASK (0x1 << 4) +#define RCODE_MASK (0xf) + +#define OPCODE(v) ((v) & OPCODE_MASK) +#define RCODE(v) ((v) & RCODE_MASK) + + +struct asr_pack { + char *buf; + size_t len; + size_t offset; + int err; +}; + +struct asr_unpack { + const char *buf; + size_t len; + size_t offset; + int err; +}; + +struct asr_dns_header { + uint16_t id; + uint16_t flags; + uint16_t qdcount; + uint16_t ancount; + uint16_t nscount; + uint16_t arcount; +}; + +struct asr_dns_query { + char q_dname[MAXDNAME]; + uint16_t q_type; + uint16_t q_class; +}; + +struct asr_dns_rr { + char rr_dname[MAXDNAME]; + uint16_t rr_type; + uint16_t rr_class; + uint32_t rr_ttl; + union { + struct { + char cname[MAXDNAME]; + } cname; + struct { + uint16_t preference; + char exchange[MAXDNAME]; + } mx; + struct { + char nsname[MAXDNAME]; + } ns; + struct { + char ptrname[MAXDNAME]; + } ptr; + struct { + char mname[MAXDNAME]; + char rname[MAXDNAME]; + uint32_t serial; + uint32_t refresh; + uint32_t retry; + uint32_t expire; + uint32_t minimum; + } soa; + struct { + struct in_addr addr; + } in_a; + struct { + struct in6_addr addr6; + } in_aaaa; + struct { + uint16_t rdlen; + const void *rdata; + } other; + } rr; +}; + + +#define ASR_MAXNS 5 +#define ASR_MAXDB 3 +#define ASR_MAXDOM 10 + +enum async_type { + ASR_SEND, + ASR_SEARCH, + ASR_GETRRSETBYNAME, + ASR_GETHOSTBYNAME, + ASR_GETHOSTBYADDR, + ASR_GETADDRINFO, + ASR_GETNAMEINFO, +}; + +#define ASR_DB_FILE 'f' +#define ASR_DB_DNS 'b' + +struct asr_ctx { + int ac_refcount; + int ac_options; + int ac_ndots; + char *ac_domain; + int ac_domcount; + char *ac_dom[ASR_MAXDOM]; + int ac_dbcount; + char ac_db[ASR_MAXDB + 1]; + int ac_family[3]; + + int ac_nscount; + int ac_nstimeout; + int ac_nsretries; + struct sockaddr *ac_ns[ASR_MAXNS]; + +}; + +struct asr { + pid_t a_pid; + time_t a_mtime; + time_t a_rtime; + struct asr_ctx *a_ctx; +}; + +#define ASYNC_COND 0 +#define ASYNC_DONE 1 + +#define ASYNC_DOM_FQDN 0x00000001 +#define ASYNC_DOM_NDOTS 0x00000002 +#define ASYNC_DOM_DOMAIN 0x00000004 +#define ASYNC_DOM_ASIS 0x00000008 + +#define ASYNC_NODATA 0x00000100 +#define ASYNC_AGAIN 0x00000200 + +#define ASYNC_GETNET 0x00001000 +#define ASYNC_EXTOBUF 0x00002000 + +#define ASYNC_NO_INET 0x00010000 +#define ASYNC_NO_INET6 0x00020000 + +struct asr_query { + int (*as_run)(struct asr_query *, struct asr_result *); + struct asr_ctx *as_ctx; + int as_type; + int as_flags; + int as_state; + + /* cond */ + int as_timeout; + int as_fd; + struct asr_query *as_subq; + + /* loop indices in ctx */ + int as_dom_step; + int as_dom_idx; + int as_dom_flags; + int as_family_idx; + int as_db_idx; + + int as_count; + + union { + struct { + uint16_t reqid; + int class; + int type; + char *dname; /* not fqdn! */ + int rcode; /* response code */ + int ancount; /* answer count */ + + int nsidx; + int nsloop; + + /* io buffers for query/response */ + unsigned char *obuf; + size_t obuflen; + size_t obufsize; + unsigned char *ibuf; + size_t ibuflen; + size_t ibufsize; + size_t datalen; /* for tcp io */ + uint16_t pktlen; + } dns; + + struct { + int class; + int type; + char *name; + int saved_h_errno; + } search; + + struct { + int flags; + int class; + int type; + char *name; + } rrset; + + struct { + char *name; + int family; + char addr[16]; + int addrlen; + int subq_h_errno; + } hostnamadr; + + struct { + char *hostname; + char *servname; + int port_tcp; + int port_udp; + union { + struct sockaddr sa; + struct sockaddr_in sain; + struct sockaddr_in6 sain6; + } sa; + + struct addrinfo hints; + char *fqdn; + struct addrinfo *aifirst; + struct addrinfo *ailast; + } ai; + + struct { + char *hostname; + char *servname; + size_t hostnamelen; + size_t servnamelen; + union { + struct sockaddr sa; + struct sockaddr_in sain; + struct sockaddr_in6 sain6; + } sa; + int flags; + } ni; +#define MAXTOKEN 10 + } as; + +}; + +#define AS_DB(p) ((p)->as_ctx->ac_db[(p)->as_db_idx - 1]) +#define AS_FAMILY(p) ((p)->as_ctx->ac_family[(p)->as_family_idx]) + +enum asr_state { + ASR_STATE_INIT, + ASR_STATE_NEXT_DOMAIN, + ASR_STATE_NEXT_DB, + ASR_STATE_SAME_DB, + ASR_STATE_NEXT_FAMILY, + ASR_STATE_NEXT_NS, + ASR_STATE_UDP_SEND, + ASR_STATE_UDP_RECV, + ASR_STATE_TCP_WRITE, + ASR_STATE_TCP_READ, + ASR_STATE_PACKET, + ASR_STATE_SUBQUERY, + ASR_STATE_NOT_FOUND, + ASR_STATE_HALT, +}; + +#define MAXPACKETSZ 4096 + +__BEGIN_HIDDEN_DECLS + +/* asr_utils.c */ +void _asr_pack_init(struct asr_pack *, char *, size_t); +int _asr_pack_header(struct asr_pack *, const struct asr_dns_header *); +int _asr_pack_query(struct asr_pack *, uint16_t, uint16_t, const char *); +int _asr_pack_edns0(struct asr_pack *, uint16_t, int); +void _asr_unpack_init(struct asr_unpack *, const char *, size_t); +int _asr_unpack_header(struct asr_unpack *, struct asr_dns_header *); +int _asr_unpack_query(struct asr_unpack *, struct asr_dns_query *); +int _asr_unpack_rr(struct asr_unpack *, struct asr_dns_rr *); +int _asr_sockaddr_from_str(struct sockaddr *, int, const char *); +ssize_t _asr_dname_from_fqdn(const char *, char *, size_t); +ssize_t _asr_addr_as_fqdn(const char *, int, char *, size_t); + +/* asr.c */ +void _asr_resolver_done(void *); +struct asr_ctx *_asr_use_resolver(void *); +struct asr_ctx *_asr_no_resolver(void); +void _asr_ctx_unref(struct asr_ctx *); +struct asr_query *_asr_async_new(struct asr_ctx *, int); +void _asr_async_free(struct asr_query *); +size_t _asr_make_fqdn(const char *, const char *, char *, size_t); +char *_asr_strdname(const char *, char *, size_t); +int _asr_iter_db(struct asr_query *); +int _asr_parse_namedb_line(FILE *, char **, int, char *, size_t); + +/* *_async.c */ +struct asr_query *_res_query_async_ctx(const char *, int, int, struct asr_ctx *); +struct asr_query *_res_search_async_ctx(const char *, int, int, struct asr_ctx *); +struct asr_query *_gethostbyaddr_async_ctx(const void *, socklen_t, int, + struct asr_ctx *); + +int _asr_iter_domain(struct asr_query *, const char *, char *, size_t); + +#ifdef DEBUG + +#define DPRINT(...) do { if(_asr_debug) { \ + fprintf(_asr_debug, __VA_ARGS__); \ + } } while (0) +#define DPRINT_PACKET(n, p, s) do { if(_asr_debug) { \ + fprintf(_asr_debug, "----- %s -----\n", n); \ + _asr_dump_packet(_asr_debug, (p), (s)); \ + fprintf(_asr_debug, "--------------\n"); \ + } } while (0) + +#else /* DEBUG */ + +#define DPRINT(...) +#define DPRINT_PACKET(...) + +#endif /* DEBUG */ + +const char *_asr_querystr(int); +const char *_asr_statestr(int); +const char *_asr_transitionstr(int); +const char *_asr_print_sockaddr(const struct sockaddr *, char *, size_t); +void _asr_dump_config(FILE *, struct asr *); +void _asr_dump_packet(FILE *, const void *, size_t); + +extern FILE *_asr_debug; + +#define async_set_state(a, s) do { \ + DPRINT("asr: [%s@%p] %s -> %s\n", \ + _asr_querystr((a)->as_type), \ + as, \ + _asr_statestr((a)->as_state), \ + _asr_statestr((s))); \ + (a)->as_state = (s); } while (0) + +__END_HIDDEN_DECLS diff --git a/foobar/portable/openbsd-compat/libasr/asr_run.3 b/foobar/portable/openbsd-compat/libasr/asr_run.3 new file mode 100644 index 00000000..61c1b02c --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/asr_run.3 @@ -0,0 +1,316 @@ +.\" $OpenBSD: asr_run.3,v 1.3 2017/02/18 19:23:05 jca Exp $ +.\" +.\" Copyright (c) 2012-2014, Eric Faurot <eric@openbsd.org> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: February 18 2017 $ +.Dt ASR_RUN 3 +.Os +.Sh NAME +.Nm asr_run , +.Nm asr_run_sync , +.Nm asr_abort , +.Nm res_send_async , +.Nm res_query_async , +.Nm res_search_async , +.Nm getrrsetbyname_async , +.Nm gethostbyname_async , +.Nm gethostbyname2_async , +.Nm gethostbyaddr_async , +.Nm getnetbyname_async , +.Nm getnetbyaddr_async , +.Nm getaddrinfo_async , +.Nm getnameinfo_async +.Nd asynchronous resolver functions +.Sh SYNOPSIS +.In sys/types.h +.In sys/socket.h +.In netdb.h +.In asr.h +.Ft int +.Fn asr_run "struct asr_query *aq" "struct asr_result *ar" +.Ft int +.Fn asr_run_sync "struct asr_query *aq" "struct asr_result *ar" +.Ft void +.Fn asr_abort "struct asr_query *aq" +.Ft struct asr_query * +.Fn res_send_async "const unsigned char *pkt" "int pktlen" "void *asr" +.Ft struct asr_query * +.Fn res_query_async "const char *name" "int class" "int type" "void *asr" +.Ft struct asr_query * +.Fn res_search_async "const char *name" "int class" "int type" "void *asr" +.Ft struct asr_query * +.Fn getrrsetbyname_async "const char *hostname" "unsigned int rdclass" "unsigned int rdtype" "unsigned int flags" "void *asr" +.Ft struct asr_query * +.Fn gethostbyname_async "const char *name" "void *asr" +.Ft struct asr_query * +.Fn gethostbyname2_async "const char *name" "int af" "void *asr" +.Ft struct asr_query * +.Fn gethostbyaddr_async "const void *addr" "socklen_t len" "int af" "void *asr" +.Ft struct asr_query * +.Fn getnetbyname_async "const char *name" "void *asr" +.Ft struct asr_query * +.Fn getnetbyaddr_async "in_addr_t net" "int type" "void *asr" +.Ft struct asr_query * +.Fn getaddrinfo_async "const char *hostname" "const char *servname" "const struct addrinfo *hints" "void *asr" +.Ft struct asr_query * +.Fn getnameinfo_async "const struct sockaddr *sa" "socklen_t salen" "char *host" "size_t hostlen" "char *serv" "size_t servlen" "int flags" "void *asr" +.Sh DESCRIPTION +The +.Nm asr +functions provide a simple interface for asynchronous address +resolution and nameserver querying. +They should be used in place of the classical resolver functions +of libc when blocking is not desirable. +.Pp +The principle of operation is as follows: +All async requests are made against an +.Nm asr +context which basically defines a list of sources to query and a +strategy to do so. +The user creates a query through one of the dedicated functions, and +gets a handle representing the internal query. +A query is a state-machine that can be run to try to fulfill a +particular request. +This is done by calling in a generic API that performs the state +transitions until it needs to give the control back to the user, +either because a result is available, or because the next transition +implies a blocking call (a file descriptor needs to be read from or +written to). +The user is responsible for dealing with the situation: either get +the result, or wait until the fd conditions are met, and then call +back into the resolving machinery when it is ready to proceed. +.Pp +The +.Fn asr_run +function drives the resolving process. +It runs the asynchronous query represented by the +.Fa aq +handle until a result is available, or until it cannot continue +without blocking. +The results are returned to the user through the +.Fa ar +parameter, which must be a valid pointer to user allocated memory. +.Fa ar +is defined as: +.Bd -literal +struct asr_result { + + /* Fields set if the query is not done yet (asr_run returns 0) */ + int ar_cond; /* ASR_WANT_READ or ASR_WANT_WRITE */ + int ar_fd; /* the fd waiting for io condition */ + int ar_timeout; /* time to wait for in milliseconds */ + + /* Error fields. Depends on the query type. */ + int ar_errno; + int ar_h_errno; + int ar_gai_errno; + int ar_rrset_errno; + + /* Result for res_*_async() calls */ + int ar_count; /* number of answers in the dns reply */ + int ar_rcode; /* response code in the dns reply */ + void *ar_data; /* raw reply packet (must be freed) */ + int ar_datalen; /* reply packet length */ + struct sockaddr_storage ar_ns; /* nameserver that responded */ + + /* Result for other calls. Must be freed properly. */ + struct addrinfo *ar_addrinfo; + struct rrsetinfo *ar_rrsetinfo; + struct hostent *ar_hostent; + struct netent *ar_netent; +}; +.Ed +.Pp +The function returns one of the following values: +.Bl -tag -width "0 " -offset indent +.It 0 +The query cannot be processed further until a specific condition on a +file descriptor becomes true. +The following members of the +.Fa ar +structure are filled: +.Pp +.Bl -tag -width "ar_timeout " -compact +.It Fa ar_cond +one of ASR_WANT_READ or ASR_WANT_WRITE, +.It Fa ar_fd +the file descriptor waiting for an IO operation, +.It Fa ar_timeout +the amount of time to wait for in milliseconds. +.El +.Pp +The caller is expected to call +.Fn asr_run +again once the condition holds or the timeout expires. +.It 1 +The query is completed. +The members relevant to the actual async query type are set accordingly, +including error conditions. +In any case, the query is cleared and its handle is invalidated. +.El +.Pp +Note that although the query itself may fail (the error being properly reported +in the +.Fa ar +structure), the +.Fn asr_run +function itself cannot fail and it always preserves errno. +.Pp +The +.Fn asr_run_sync +function is a wrapper around +.Fn asr_run +that handles the read/write conditions, thus falling back to a blocking +interface. +It only returns 1. +It also preserves errno. +.Pp +The +.Fn asr_abort +function clears a running query. +It can be called when the query is waiting on a file descriptor. +Note that a completed query is already cleared when +.Fn asr_run +returns, so +.Fn asr_abort +must not be called in this case. +.Pp +The remaining functions are used to initiate different kinds of query +on the +.Fa asr +resolver context. +The specific operational details for each of them are described below. +All functions return a handle to an internal query, or NULL if they could +not allocate the necessary resources to initiate the query. +All other errors (especially invalid parameters) are reported when calling +.Fn asr_run . +They usually have the same interface as an existing resolver function, with +an additional +.Ar asr +argument, which specifies the context to use for this request. +For now, the argument must always be NULL, which will use the default +context for the current thread. +.Pp +The +.Fn res_send_async , +.Fn res_query_async +and +.Fn res_search_async +functions are asynchronous versions of the standard libc resolver routines. +Their interface is very similar, except that the response buffer is always +allocated internally. +The return value is found upon completion in the +.Fa ar_datalen +member of the response structure. +In addition, the +.Fa ar_ns +structure contains the address of the DNS server that sent the response, +.Fa ar_rcode +contains the code returned by the server in the DNS response packet, and +.Fa ar_count +contains the number of answers in the packet. +If a response is received it is placed in a newly allocated buffer +and returned as +.Fa ar_data +member. +This buffer must be freed by the caller. +On error, the +.Fa ar_errno +and +.Fa ar_h_errno +members are set accordingly. +.Pp +The +.Fn getrrsetbyname_async +function is an asynchronous version of +.Xr getrrsetbyname 3 . +Upon completion, the return code is found in +.Fa ar_rrset_errno +and the address to the newly allocated result set is set in +.Fa ar_rrsetinfo . +As for the blocking function, it must be freed by calling +.Xr freerrset 3 . +.Pp +The +.Fn gethostbyname_async , +.Fn gethostbyname2_async +and +.Fn gethostbyaddr_async +functions provide an asynchronous version of the network host entry functions. +Upon completion, +.Ar ar_h_errno +is set and the resulting hostent address, if found, is set +in the +.Ar ar_hostent +field. +Note that unlike their blocking counterparts, these functions always return a +pointer to newly allocated memory, which must be released by the caller using +.Xr free 3 . +.Pp +Similarly, the +.Fn getnetbyname_async +and +.Fn getnetbyaddr_async +functions provide an asynchronous version of the network entry functions. +Upon completion, +.Ar ar_h_errno +is set and the resulting netent address, if found, is set +in the +.Ar ar_netent +field. +The memory there is also allocated for the request, and it must be freed by +.Xr free 3 . +.Pp +The +.Fn getaddrinfo_async +function is an asynchronous version of the +.Xr getaddrinfo 3 +call. +It provides a chain of addrinfo structures with all valid combinations of +socket address for the given +.Fa hostname , +.Fa servname +and +.Fa hints . +Those three parameters have the same meaning as for the blocking counterpart. +Upon completion the return code is set in +.Fa ar_gai_errno . +The +.Fa ar_errno +member may also be set. +On success, the +.Fa ar_addrinfo +member points to a newly allocated list of addrinfo. +This list must be freed with +.Xr freeaddrinfo 3 . +.Sh WORKING WITH THREADS +This implementation of the asynchronous resolver interface is thread-safe +and lock-free internally, but the following restriction applies: +Two different threads must not create queries on the same context or +run queries originating from the same context at the same time. +If they want to do that, all calls must be protected by a mutex around +that context. +.Pp +It is generally not a problem since the main point of the asynchronous +resolver is to multiplex queries within a single thread of control, +so sharing a resolver among threads is not useful. +.Sh SEE ALSO +.Xr getaddrinfo 3 , +.Xr gethostbyname 3 , +.Xr getnameinfo 3 , +.Xr getnetbyname 3 , +.Xr getrrsetbyname 3 , +.Xr res_send 3 , +.Xr resolv.conf 5 diff --git a/foobar/portable/openbsd-compat/libasr/asr_utils.c b/foobar/portable/openbsd-compat/libasr/asr_utils.c new file mode 100644 index 00000000..e3d24c93 --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/asr_utils.c @@ -0,0 +1,574 @@ +/* $OpenBSD: asr_utils.c,v 1.18 2017/09/23 20:55:06 jca Exp $ */ +/* + * Copyright (c) 2009-2012 Eric Faurot <eric@faurot.net> + * + * 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 "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <net/if.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +#include <arpa/nameser_compat.h> +#endif +#include <netdb.h> + +#include <asr.h> +#include <ctype.h> +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "asr_private.h" + +static int dname_check_label(const char *, size_t); +static ssize_t dname_expand(const unsigned char *, size_t, size_t, size_t *, + char *, size_t); + +static int unpack_data(struct asr_unpack *, void *, size_t); +static int unpack_u16(struct asr_unpack *, uint16_t *); +static int unpack_u32(struct asr_unpack *, uint32_t *); +static int unpack_inaddr(struct asr_unpack *, struct in_addr *); +static int unpack_in6addr(struct asr_unpack *, struct in6_addr *); +static int unpack_dname(struct asr_unpack *, char *, size_t); + +static int pack_data(struct asr_pack *, const void *, size_t); +static int pack_u16(struct asr_pack *, uint16_t); +static int pack_dname(struct asr_pack *, const char *); + +static int +dname_check_label(const char *s, size_t l) +{ + if (l == 0 || l > 63) + return (-1); + + return (0); +} + +ssize_t +_asr_dname_from_fqdn(const char *str, char *dst, size_t max) +{ + ssize_t res; + size_t l, n; + char *d; + + res = 0; + + /* special case: the root domain */ + if (str[0] == '.') { + if (str[1] != '\0') + return (-1); + if (dst && max >= 1) + *dst = '\0'; + return (1); + } + + for (; *str; str = d + 1) { + + d = strchr(str, '.'); + if (d == NULL || d == str) + return (-1); + + l = (d - str); + + if (dname_check_label(str, l) == -1) + return (-1); + + res += l + 1; + + if (dst) { + *dst++ = l; + max -= 1; + n = (l > max) ? max : l; + memmove(dst, str, n); + max -= n; + if (max == 0) + dst = NULL; + else + dst += n; + } + } + + if (dst) + *dst++ = '\0'; + + return (res + 1); +} + +static ssize_t +dname_expand(const unsigned char *data, size_t len, size_t offset, + size_t *newoffset, char *dst, size_t max) +{ + size_t n, count, end, ptr, start; + ssize_t res; + + if (offset >= len) + return (-1); + + res = 0; + end = start = offset; + + for (; (n = data[offset]); ) { + if ((n & 0xc0) == 0xc0) { + if (offset + 2 > len) + return (-1); + ptr = 256 * (n & ~0xc0) + data[offset + 1]; + if (ptr >= start) + return (-1); + if (end < offset + 2) + end = offset + 2; + offset = start = ptr; + continue; + } + if (offset + n + 1 > len) + return (-1); + + if (dname_check_label(data + offset + 1, n) == -1) + return (-1); + + /* copy n + at offset+1 */ + if (dst != NULL && max != 0) { + count = (max < n + 1) ? (max) : (n + 1); + memmove(dst, data + offset, count); + dst += count; + max -= count; + } + res += n + 1; + offset += n + 1; + if (end < offset) + end = offset; + } + if (end < offset + 1) + end = offset + 1; + + if (dst != NULL && max != 0) + dst[0] = 0; + if (newoffset) + *newoffset = end; + return (res + 1); +} + +void +_asr_pack_init(struct asr_pack *pack, char *buf, size_t len) +{ + pack->buf = buf; + pack->len = len; + pack->offset = 0; + pack->err = 0; +} + +void +_asr_unpack_init(struct asr_unpack *unpack, const char *buf, size_t len) +{ + unpack->buf = buf; + unpack->len = len; + unpack->offset = 0; + unpack->err = 0; +} + +static int +unpack_data(struct asr_unpack *p, void *data, size_t len) +{ + if (p->err) + return (-1); + + if (p->len - p->offset < len) { + p->err = EOVERFLOW; + return (-1); + } + + memmove(data, p->buf + p->offset, len); + p->offset += len; + + return (0); +} + +static int +unpack_u16(struct asr_unpack *p, uint16_t *u16) +{ + if (unpack_data(p, u16, 2) == -1) + return (-1); + + *u16 = ntohs(*u16); + + return (0); +} + +static int +unpack_u32(struct asr_unpack *p, uint32_t *u32) +{ + if (unpack_data(p, u32, 4) == -1) + return (-1); + + *u32 = ntohl(*u32); + + return (0); +} + +static int +unpack_inaddr(struct asr_unpack *p, struct in_addr *a) +{ + return (unpack_data(p, a, 4)); +} + +static int +unpack_in6addr(struct asr_unpack *p, struct in6_addr *a6) +{ + return (unpack_data(p, a6, 16)); +} + +static int +unpack_dname(struct asr_unpack *p, char *dst, size_t max) +{ + ssize_t e; + + if (p->err) + return (-1); + + e = dname_expand(p->buf, p->len, p->offset, &p->offset, dst, max); + if (e == -1) { + p->err = EINVAL; + return (-1); + } + if (e < 0 || e > MAXDNAME) { + p->err = ERANGE; + return (-1); + } + + return (0); +} + +int +_asr_unpack_header(struct asr_unpack *p, struct asr_dns_header *h) +{ + if (unpack_data(p, h, HFIXEDSZ) == -1) + return (-1); + + h->flags = ntohs(h->flags); + h->qdcount = ntohs(h->qdcount); + h->ancount = ntohs(h->ancount); + h->nscount = ntohs(h->nscount); + h->arcount = ntohs(h->arcount); + + return (0); +} + +int +_asr_unpack_query(struct asr_unpack *p, struct asr_dns_query *q) +{ + unpack_dname(p, q->q_dname, sizeof(q->q_dname)); + unpack_u16(p, &q->q_type); + unpack_u16(p, &q->q_class); + + return (p->err) ? (-1) : (0); +} + +int +_asr_unpack_rr(struct asr_unpack *p, struct asr_dns_rr *rr) +{ + uint16_t rdlen; + size_t save_offset; + + unpack_dname(p, rr->rr_dname, sizeof(rr->rr_dname)); + unpack_u16(p, &rr->rr_type); + unpack_u16(p, &rr->rr_class); + unpack_u32(p, &rr->rr_ttl); + unpack_u16(p, &rdlen); + + if (p->err) + return (-1); + + if (p->len - p->offset < rdlen) { + p->err = EOVERFLOW; + return (-1); + } + + save_offset = p->offset; + + switch (rr->rr_type) { + + case T_CNAME: + unpack_dname(p, rr->rr.cname.cname, sizeof(rr->rr.cname.cname)); + break; + + case T_MX: + unpack_u16(p, &rr->rr.mx.preference); + unpack_dname(p, rr->rr.mx.exchange, sizeof(rr->rr.mx.exchange)); + break; + + case T_NS: + unpack_dname(p, rr->rr.ns.nsname, sizeof(rr->rr.ns.nsname)); + break; + + case T_PTR: + unpack_dname(p, rr->rr.ptr.ptrname, sizeof(rr->rr.ptr.ptrname)); + break; + + case T_SOA: + unpack_dname(p, rr->rr.soa.mname, sizeof(rr->rr.soa.mname)); + unpack_dname(p, rr->rr.soa.rname, sizeof(rr->rr.soa.rname)); + unpack_u32(p, &rr->rr.soa.serial); + unpack_u32(p, &rr->rr.soa.refresh); + unpack_u32(p, &rr->rr.soa.retry); + unpack_u32(p, &rr->rr.soa.expire); + unpack_u32(p, &rr->rr.soa.minimum); + break; + + case T_A: + if (rr->rr_class != C_IN) + goto other; + unpack_inaddr(p, &rr->rr.in_a.addr); + break; + + case T_AAAA: + if (rr->rr_class != C_IN) + goto other; + unpack_in6addr(p, &rr->rr.in_aaaa.addr6); + break; + default: + other: + rr->rr.other.rdata = p->buf + p->offset; + rr->rr.other.rdlen = rdlen; + p->offset += rdlen; + } + + if (p->err) + return (-1); + + /* make sure that the advertised rdlen is really ok */ + if (p->offset - save_offset != rdlen) + p->err = EINVAL; + + return (p->err) ? (-1) : (0); +} + +static int +pack_data(struct asr_pack *p, const void *data, size_t len) +{ + if (p->err) + return (-1); + + if (p->len < p->offset + len) { + p->err = EOVERFLOW; + return (-1); + } + + memmove(p->buf + p->offset, data, len); + p->offset += len; + + return (0); +} + +static int +pack_u16(struct asr_pack *p, uint16_t v) +{ + v = htons(v); + + return (pack_data(p, &v, 2)); +} + +static int +pack_dname(struct asr_pack *p, const char *dname) +{ + /* dname compression would be nice to have here. + * need additionnal context. + */ + return (pack_data(p, dname, strlen(dname) + 1)); +} + +int +_asr_pack_header(struct asr_pack *p, const struct asr_dns_header *h) +{ + struct asr_dns_header c; + + c.id = h->id; + c.flags = htons(h->flags); + c.qdcount = htons(h->qdcount); + c.ancount = htons(h->ancount); + c.nscount = htons(h->nscount); + c.arcount = htons(h->arcount); + + return (pack_data(p, &c, HFIXEDSZ)); +} + +int +_asr_pack_query(struct asr_pack *p, uint16_t type, uint16_t class, const char *dname) +{ + pack_dname(p, dname); + pack_u16(p, type); + pack_u16(p, class); + + return (p->err) ? (-1) : (0); +} + +int +_asr_pack_edns0(struct asr_pack *p, uint16_t pktsz, int dnssec_do) +{ + DPRINT("asr EDNS0 pktsz:%hu dnssec:%s\n", pktsz, + dnssec_do ? "yes" : "no"); + + pack_dname(p, ""); /* root */ + pack_u16(p, T_OPT); /* OPT */ + pack_u16(p, pktsz); /* UDP payload size */ + + /* extended RCODE and flags */ + pack_u16(p, 0); + pack_u16(p, dnssec_do ? DNS_MESSAGEEXTFLAG_DO : 0); + + pack_u16(p, 0); /* RDATA len */ + + return (p->err) ? (-1) : (0); +} + +int +_asr_sockaddr_from_str(struct sockaddr *sa, int family, const char *str) +{ + struct in_addr ina; + struct in6_addr in6a; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + char *cp, *str2; + const char *errstr; + + switch (family) { + case PF_UNSPEC: + if (_asr_sockaddr_from_str(sa, PF_INET, str) == 0) + return (0); + return _asr_sockaddr_from_str(sa, PF_INET6, str); + + case PF_INET: + if (inet_pton(PF_INET, str, &ina) != 1) + return (-1); + + sin = (struct sockaddr_in *)sa; + memset(sin, 0, sizeof *sin); +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + sin->sin_len = sizeof(struct sockaddr_in); +#endif + sin->sin_family = PF_INET; + sin->sin_addr.s_addr = ina.s_addr; + return (0); + + case PF_INET6: + cp = strchr(str, SCOPE_DELIMITER); + if (cp) { + str2 = strdup(str); + if (str2 == NULL) + return (-1); + str2[cp - str] = '\0'; + if (inet_pton(PF_INET6, str2, &in6a) != 1) { + free(str2); + return (-1); + } + cp++; + free(str2); + } else if (inet_pton(PF_INET6, str, &in6a) != 1) + return (-1); + + sin6 = (struct sockaddr_in6 *)sa; + memset(sin6, 0, sizeof *sin6); +#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN + sin6->sin6_len = sizeof(struct sockaddr_in6); +#endif + sin6->sin6_family = PF_INET6; + sin6->sin6_addr = in6a; + + if (cp == NULL) + return (0); + + if (IN6_IS_ADDR_LINKLOCAL(&in6a) || + IN6_IS_ADDR_MC_LINKLOCAL(&in6a) || + IN6_IS_ADDR_MC_NODELOCAL(&in6a)) + if ((sin6->sin6_scope_id = if_nametoindex(cp))) + return (0); + + sin6->sin6_scope_id = strtonum(cp, 0, UINT32_MAX, &errstr); + if (errstr) + return (-1); + return (0); + + default: + break; + } + + return (-1); +} + +ssize_t +_asr_addr_as_fqdn(const char *addr, int family, char *dst, size_t max) +{ + const struct in6_addr *in6_addr; + in_addr_t in_addr; + + switch (family) { + case AF_INET: + in_addr = ntohl(*((const in_addr_t *)addr)); + snprintf(dst, max, + "%d.%d.%d.%d.in-addr.arpa.", + in_addr & 0xff, + (in_addr >> 8) & 0xff, + (in_addr >> 16) & 0xff, + (in_addr >> 24) & 0xff); + break; + case AF_INET6: + in6_addr = (const struct in6_addr *)addr; + snprintf(dst, max, + "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." + "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." + "ip6.arpa.", + in6_addr->s6_addr[15] & 0xf, + (in6_addr->s6_addr[15] >> 4) & 0xf, + in6_addr->s6_addr[14] & 0xf, + (in6_addr->s6_addr[14] >> 4) & 0xf, + in6_addr->s6_addr[13] & 0xf, + (in6_addr->s6_addr[13] >> 4) & 0xf, + in6_addr->s6_addr[12] & 0xf, + (in6_addr->s6_addr[12] >> 4) & 0xf, + in6_addr->s6_addr[11] & 0xf, + (in6_addr->s6_addr[11] >> 4) & 0xf, + in6_addr->s6_addr[10] & 0xf, + (in6_addr->s6_addr[10] >> 4) & 0xf, + in6_addr->s6_addr[9] & 0xf, + (in6_addr->s6_addr[9] >> 4) & 0xf, + in6_addr->s6_addr[8] & 0xf, + (in6_addr->s6_addr[8] >> 4) & 0xf, + in6_addr->s6_addr[7] & 0xf, + (in6_addr->s6_addr[7] >> 4) & 0xf, + in6_addr->s6_addr[6] & 0xf, + (in6_addr->s6_addr[6] >> 4) & 0xf, + in6_addr->s6_addr[5] & 0xf, + (in6_addr->s6_addr[5] >> 4) & 0xf, + in6_addr->s6_addr[4] & 0xf, + (in6_addr->s6_addr[4] >> 4) & 0xf, + in6_addr->s6_addr[3] & 0xf, + (in6_addr->s6_addr[3] >> 4) & 0xf, + in6_addr->s6_addr[2] & 0xf, + (in6_addr->s6_addr[2] >> 4) & 0xf, + in6_addr->s6_addr[1] & 0xf, + (in6_addr->s6_addr[1] >> 4) & 0xf, + in6_addr->s6_addr[0] & 0xf, + (in6_addr->s6_addr[0] >> 4) & 0xf); + break; + default: + return (-1); + } + return (0); +} diff --git a/foobar/portable/openbsd-compat/libasr/getaddrinfo.c b/foobar/portable/openbsd-compat/libasr/getaddrinfo.c new file mode 100644 index 00000000..37fe2d4d --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/getaddrinfo.c @@ -0,0 +1,55 @@ +/* $OpenBSD: getaddrinfo.c,v 1.9 2015/10/08 14:08:44 eric Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> + +int +getaddrinfo(const char *hostname, const char *servname, + const struct addrinfo *hints, struct addrinfo **res) +{ + struct asr_query *as; + struct asr_result ar; + int saved_errno = errno; + + if (hints == NULL || (hints->ai_flags & AI_NUMERICHOST) == 0) + res_init(); + + as = getaddrinfo_async(hostname, servname, hints, NULL); + if (as == NULL) { + if (errno == ENOMEM) { + errno = saved_errno; + return (EAI_MEMORY); + } + return (EAI_SYSTEM); + } + + asr_run_sync(as, &ar); + + *res = ar.ar_addrinfo; + if (ar.ar_gai_errno == EAI_SYSTEM) + errno = ar.ar_errno; + + return (ar.ar_gai_errno); +} +DEF_WEAK(getaddrinfo); diff --git a/foobar/portable/openbsd-compat/libasr/getaddrinfo_async.c b/foobar/portable/openbsd-compat/libasr/getaddrinfo_async.c new file mode 100644 index 00000000..1fd44ff7 --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/getaddrinfo_async.c @@ -0,0 +1,756 @@ +/* $OpenBSD: getaddrinfo_async.c,v 1.56 2018/11/03 09:13:24 eric Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <netinet/in.h> +#include <arpa/nameser.h> +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +#include <arpa/nameser_compat.h> +#endif +#include <net/if.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <ifaddrs.h> +#include <resolv.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> + +#include "asr_private.h" + +struct match { + int family; + int socktype; + int protocol; +}; + +static int getaddrinfo_async_run(struct asr_query *, struct asr_result *); +static int get_port(const char *, const char *, int); +static int iter_family(struct asr_query *, int); +static int addrinfo_add(struct asr_query *, const struct sockaddr *, const char *); +static int addrinfo_from_file(struct asr_query *, int, FILE *); +static int addrinfo_from_pkt(struct asr_query *, char *, size_t); +static int addrconfig_setup(struct asr_query *); + +static const struct match matches[] = { + { PF_INET, SOCK_DGRAM, IPPROTO_UDP }, + { PF_INET, SOCK_STREAM, IPPROTO_TCP }, + { PF_INET, SOCK_RAW, 0 }, + { PF_INET6, SOCK_DGRAM, IPPROTO_UDP }, + { PF_INET6, SOCK_STREAM, IPPROTO_TCP }, + { PF_INET6, SOCK_RAW, 0 }, + { -1, 0, 0, }, +}; + +#define MATCH_FAMILY(a, b) ((a) == matches[(b)].family || (a) == PF_UNSPEC) +#define MATCH_PROTO(a, b) ((a) == matches[(b)].protocol || (a) == 0 || matches[(b)].protocol == 0) +/* Do not match SOCK_RAW unless explicitly specified */ +#define MATCH_SOCKTYPE(a, b) ((a) == matches[(b)].socktype || ((a) == 0 && \ + matches[(b)].socktype != SOCK_RAW)) + +enum { + DOM_INIT, + DOM_DOMAIN, + DOM_DONE +}; + +struct asr_query * +getaddrinfo_async(const char *hostname, const char *servname, + const struct addrinfo *hints, void *asr) +{ + struct asr_ctx *ac; + struct asr_query *as; + + if (hints == NULL || (hints->ai_flags & AI_NUMERICHOST) == 0) + ac = _asr_use_resolver(asr); + else + ac = _asr_no_resolver(); + if ((as = _asr_async_new(ac, ASR_GETADDRINFO)) == NULL) + goto abort; /* errno set */ + as->as_run = getaddrinfo_async_run; + + if (hostname) { + if ((as->as.ai.hostname = strdup(hostname)) == NULL) + goto abort; /* errno set */ + } + if (servname && (as->as.ai.servname = strdup(servname)) == NULL) + goto abort; /* errno set */ + if (hints) + memmove(&as->as.ai.hints, hints, sizeof *hints); + else { + memset(&as->as.ai.hints, 0, sizeof as->as.ai.hints); + as->as.ai.hints.ai_family = PF_UNSPEC; + as->as.ai.hints.ai_flags = AI_ADDRCONFIG; + } + + _asr_ctx_unref(ac); + return (as); + abort: + if (as) + _asr_async_free(as); + _asr_ctx_unref(ac); + return (NULL); +} +DEF_WEAK(getaddrinfo_async); + +static int +getaddrinfo_async_run(struct asr_query *as, struct asr_result *ar) +{ + char fqdn[MAXDNAME]; + const char *str; + struct addrinfo *ai; + int i, family, r; + FILE *f; + union { + struct sockaddr sa; + struct sockaddr_in sain; + struct sockaddr_in6 sain6; + } sa; + + next: + switch (as->as_state) { + + case ASR_STATE_INIT: + + /* + * First, make sure the parameters are valid. + */ + + as->as_count = 0; + + if (as->as.ai.hostname == NULL && + as->as.ai.servname == NULL) { + ar->ar_gai_errno = EAI_NONAME; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (as->as.ai.hostname && as->as.ai.hostname[0] == '\0') { + ar->ar_gai_errno = EAI_NODATA; + async_set_state(as, ASR_STATE_HALT); + break; + } + + ai = &as->as.ai.hints; + +#ifdef EAI_BADHINTS + if (ai->ai_addrlen || + ai->ai_canonname || + ai->ai_addr || + ai->ai_next) { + ar->ar_gai_errno = EAI_BADHINTS; + async_set_state(as, ASR_STATE_HALT); + break; + } +#endif + + if (ai->ai_flags & ~AI_MASK || + (ai->ai_flags & AI_CANONNAME && ai->ai_flags & AI_FQDN)) { + ar->ar_gai_errno = EAI_BADFLAGS; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (ai->ai_family != PF_UNSPEC && + ai->ai_family != PF_INET && + ai->ai_family != PF_INET6) { + ar->ar_gai_errno = EAI_FAMILY; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (ai->ai_socktype && + ai->ai_socktype != SOCK_DGRAM && + ai->ai_socktype != SOCK_STREAM && + ai->ai_socktype != SOCK_RAW) { + ar->ar_gai_errno = EAI_SOCKTYPE; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (ai->ai_socktype == SOCK_RAW && + get_port(as->as.ai.servname, NULL, 1) != 0) { + ar->ar_gai_errno = EAI_SERVICE; + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* Restrict result set to configured address families */ + if (ai->ai_flags & AI_ADDRCONFIG) { + if (addrconfig_setup(as) == -1) { + ar->ar_errno = errno; + ar->ar_gai_errno = EAI_SYSTEM; + async_set_state(as, ASR_STATE_HALT); + break; + } + } + + /* Make sure there is at least a valid combination */ + for (i = 0; matches[i].family != -1; i++) + if (MATCH_FAMILY(ai->ai_family, i) && + MATCH_SOCKTYPE(ai->ai_socktype, i) && + MATCH_PROTO(ai->ai_protocol, i)) + break; + if (matches[i].family == -1) { +#ifdef EAI_BADHINTS + ar->ar_gai_errno = EAI_BADHINTS; +#else + ar->ar_gai_errno = EAI_FAIL; +#endif + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (ai->ai_protocol == 0 || ai->ai_protocol == IPPROTO_UDP) + as->as.ai.port_udp = get_port(as->as.ai.servname, "udp", + as->as.ai.hints.ai_flags & AI_NUMERICSERV); + if (ai->ai_protocol == 0 || ai->ai_protocol == IPPROTO_TCP) + as->as.ai.port_tcp = get_port(as->as.ai.servname, "tcp", + as->as.ai.hints.ai_flags & AI_NUMERICSERV); + if (as->as.ai.port_tcp == -2 || as->as.ai.port_udp == -2 || + (as->as.ai.port_tcp == -1 && as->as.ai.port_udp == -1) || + (ai->ai_protocol && (as->as.ai.port_udp == -1 || + as->as.ai.port_tcp == -1))) { + ar->ar_gai_errno = EAI_SERVICE; + async_set_state(as, ASR_STATE_HALT); + break; + } + + ar->ar_gai_errno = 0; + + /* If hostname is NULL, use local address */ + if (as->as.ai.hostname == NULL) { + for (family = iter_family(as, 1); + family != -1; + family = iter_family(as, 0)) { + /* + * We could use statically built sockaddrs for + * those, rather than parsing over and over. + */ + if (family == PF_INET) + str = (ai->ai_flags & AI_PASSIVE) ? \ + "0.0.0.0" : "127.0.0.1"; + else /* PF_INET6 */ + str = (ai->ai_flags & AI_PASSIVE) ? \ + "::" : "::1"; + /* This can't fail */ + _asr_sockaddr_from_str(&sa.sa, family, str); + if ((r = addrinfo_add(as, &sa.sa, NULL))) { + ar->ar_gai_errno = r; + break; + } + } + if (ar->ar_gai_errno == 0 && as->as_count == 0) { + ar->ar_gai_errno = EAI_NODATA; + } + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* Try numeric addresses first */ + for (family = iter_family(as, 1); + family != -1; + family = iter_family(as, 0)) { + + if (_asr_sockaddr_from_str(&sa.sa, family, + as->as.ai.hostname) == -1) + continue; + + if ((r = addrinfo_add(as, &sa.sa, NULL))) + ar->ar_gai_errno = r; + break; + } + if (ar->ar_gai_errno || as->as_count) { + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (ai->ai_flags & AI_NUMERICHOST) { + ar->ar_gai_errno = EAI_NONAME; + async_set_state(as, ASR_STATE_HALT); + break; + } + + async_set_state(as, ASR_STATE_NEXT_DB); + break; + + case ASR_STATE_NEXT_DB: + if (_asr_iter_db(as) == -1) { + async_set_state(as, ASR_STATE_NOT_FOUND); + break; + } + as->as_family_idx = 0; + async_set_state(as, ASR_STATE_SAME_DB); + break; + + case ASR_STATE_NEXT_FAMILY: + as->as_family_idx += 1; + if (as->as.ai.hints.ai_family != AF_UNSPEC || + AS_FAMILY(as) == -1) { + /* The family was specified, or we have tried all + * families with this DB. + */ + if (as->as_count) { + ar->ar_gai_errno = 0; + async_set_state(as, ASR_STATE_HALT); + } else + async_set_state(as, ASR_STATE_NEXT_DOMAIN); + break; + } + async_set_state(as, ASR_STATE_SAME_DB); + break; + + case ASR_STATE_NEXT_DOMAIN: + /* domain search is only for dns */ + if (AS_DB(as) != ASR_DB_DNS) { + async_set_state(as, ASR_STATE_NEXT_DB); + break; + } + as->as_family_idx = 0; + + free(as->as.ai.fqdn); + as->as.ai.fqdn = NULL; + r = _asr_iter_domain(as, as->as.ai.hostname, fqdn, sizeof(fqdn)); + if (r == -1) { + async_set_state(as, ASR_STATE_NEXT_DB); + break; + } + if (r == 0) { + ar->ar_gai_errno = EAI_FAIL; + async_set_state(as, ASR_STATE_HALT); + break; + } + as->as.ai.fqdn = strdup(fqdn); + if (as->as.ai.fqdn == NULL) { + ar->ar_gai_errno = EAI_MEMORY; + async_set_state(as, ASR_STATE_HALT); + break; + } + + async_set_state(as, ASR_STATE_SAME_DB); + break; + + case ASR_STATE_SAME_DB: + /* query the current DB again */ + switch (AS_DB(as)) { + case ASR_DB_DNS: + if (as->as.ai.fqdn == NULL) { + /* First try, initialize domain iteration */ + as->as_dom_flags = 0; + as->as_dom_step = DOM_INIT; + async_set_state(as, ASR_STATE_NEXT_DOMAIN); + break; + } + + family = (as->as.ai.hints.ai_family == AF_UNSPEC) ? + AS_FAMILY(as) : as->as.ai.hints.ai_family; + + if (family == AF_INET && + as->as_flags & ASYNC_NO_INET) { + async_set_state(as, ASR_STATE_NEXT_FAMILY); + break; + } else if (family == AF_INET6 && + as->as_flags & ASYNC_NO_INET6) { + async_set_state(as, ASR_STATE_NEXT_FAMILY); + break; + } + + as->as_subq = _res_query_async_ctx(as->as.ai.fqdn, + C_IN, (family == AF_INET6) ? T_AAAA : T_A, + as->as_ctx); + + if (as->as_subq == NULL) { + if (errno == ENOMEM) + ar->ar_gai_errno = EAI_MEMORY; + else + ar->ar_gai_errno = EAI_FAIL; + async_set_state(as, ASR_STATE_HALT); + break; + } + async_set_state(as, ASR_STATE_SUBQUERY); + break; + + case ASR_DB_FILE: + f = fopen(_PATH_HOSTS, "re"); + if (f == NULL) { + async_set_state(as, ASR_STATE_NEXT_DB); + break; + } + family = (as->as.ai.hints.ai_family == AF_UNSPEC) ? + AS_FAMILY(as) : as->as.ai.hints.ai_family; + + r = addrinfo_from_file(as, family, f); + if (r == -1) { + if (errno == ENOMEM) + ar->ar_gai_errno = EAI_MEMORY; + else + ar->ar_gai_errno = EAI_FAIL; + async_set_state(as, ASR_STATE_HALT); + } else + async_set_state(as, ASR_STATE_NEXT_FAMILY); + fclose(f); + break; + + default: + async_set_state(as, ASR_STATE_NEXT_DB); + } + break; + + case ASR_STATE_SUBQUERY: + if ((r = asr_run(as->as_subq, ar)) == ASYNC_COND) + return (ASYNC_COND); + + as->as_subq = NULL; + + if (ar->ar_datalen == -1) { + async_set_state(as, ASR_STATE_NEXT_FAMILY); + break; + } + + r = addrinfo_from_pkt(as, ar->ar_data, ar->ar_datalen); + if (r == -1) { + if (errno == ENOMEM) + ar->ar_gai_errno = EAI_MEMORY; + else + ar->ar_gai_errno = EAI_FAIL; + async_set_state(as, ASR_STATE_HALT); + } else + async_set_state(as, ASR_STATE_NEXT_FAMILY); + free(ar->ar_data); + break; + + case ASR_STATE_NOT_FOUND: + /* No result found. Maybe we can try again. */ + if (as->as_flags & ASYNC_AGAIN) + ar->ar_gai_errno = EAI_AGAIN; + else + ar->ar_gai_errno = EAI_NODATA; + async_set_state(as, ASR_STATE_HALT); + break; + + case ASR_STATE_HALT: + if (ar->ar_gai_errno == 0) { + ar->ar_count = as->as_count; + ar->ar_addrinfo = as->as.ai.aifirst; + as->as.ai.aifirst = NULL; + } else { + ar->ar_count = 0; + ar->ar_addrinfo = NULL; + } + return (ASYNC_DONE); + + default: + ar->ar_errno = EOPNOTSUPP; + ar->ar_gai_errno = EAI_SYSTEM; + async_set_state(as, ASR_STATE_HALT); + break; + } + goto next; +} + +/* + * Retreive the port number for the service name "servname" and + * the protocol "proto". + */ +static int +get_port(const char *servname, const char *proto, int numonly) +{ +#ifdef HAVE_GETSERVBYNAME_R_4_ARGS + struct servent se; +#endif +#ifdef HAVE_STRUCT_SERVENT_DATA + struct servent_data sed; +#endif + int port; + const char *e; + + if (servname == NULL) + return (0); + + e = NULL; + port = strtonum(servname, 0, USHRT_MAX, &e); + if (e == NULL) + return (port); + if (errno == ERANGE) + return (-2); /* invalid */ + if (numonly) + return (-2); + + port = -1; +#ifdef HAVE_STRUCT_SERVENT_DATA + memset(&sed, 0, sizeof(sed)); +#endif +#ifdef HAVE_GETSERVBYNAME_R_4_ARGS + if (getservbyname_r(servname, proto, &se, &sed) != -1) + port = ntohs(se.s_port); +#endif +#ifdef HAVE_ENDSERVENT_R + endservent_r(&sed); +#endif + + return (port); +} + +/* + * Iterate over the address families that are to be queried. Use the + * list on the async context, unless a specific family was given in hints. + */ +static int +iter_family(struct asr_query *as, int first) +{ + if (first) { + as->as_family_idx = 0; + if (as->as.ai.hints.ai_family != PF_UNSPEC) + return as->as.ai.hints.ai_family; + return AS_FAMILY(as); + } + + if (as->as.ai.hints.ai_family != PF_UNSPEC) + return (-1); + + as->as_family_idx++; + + return AS_FAMILY(as); +} + +/* + * Use the sockaddr at "sa" to extend the result list on the "as" context, + * with the specified canonical name "cname". This function adds one + * entry per protocol/socktype match. + */ +static int +addrinfo_add(struct asr_query *as, const struct sockaddr *sa, const char *cname) +{ + struct addrinfo *ai; + int i, port, proto; + + for (i = 0; matches[i].family != -1; i++) { + if (matches[i].family != sa->sa_family || + !MATCH_SOCKTYPE(as->as.ai.hints.ai_socktype, i) || + !MATCH_PROTO(as->as.ai.hints.ai_protocol, i)) + continue; + + proto = as->as.ai.hints.ai_protocol; + if (!proto) + proto = matches[i].protocol; + + if (proto == IPPROTO_TCP) + port = as->as.ai.port_tcp; + else if (proto == IPPROTO_UDP) + port = as->as.ai.port_udp; + else + port = 0; + + /* servname specified, but not defined for this protocol */ + if (port == -1) + continue; + + ai = calloc(1, sizeof(*ai) + SA_LEN(sa)); + if (ai == NULL) + return (EAI_MEMORY); + ai->ai_family = sa->sa_family; + ai->ai_socktype = matches[i].socktype; + ai->ai_protocol = proto; + ai->ai_flags = as->as.ai.hints.ai_flags; + ai->ai_addrlen = SA_LEN(sa); + ai->ai_addr = (void *)(ai + 1); + if (cname && + as->as.ai.hints.ai_flags & (AI_CANONNAME | AI_FQDN)) { + if ((ai->ai_canonname = strdup(cname)) == NULL) { + free(ai); + return (EAI_MEMORY); + } + } + memmove(ai->ai_addr, sa, SA_LEN(sa)); + if (sa->sa_family == PF_INET) + ((struct sockaddr_in *)ai->ai_addr)->sin_port = + htons(port); + else if (sa->sa_family == PF_INET6) + ((struct sockaddr_in6 *)ai->ai_addr)->sin6_port = + htons(port); + + if (as->as.ai.aifirst == NULL) + as->as.ai.aifirst = ai; + if (as->as.ai.ailast) + as->as.ai.ailast->ai_next = ai; + as->as.ai.ailast = ai; + as->as_count += 1; + } + + return (0); +} + +void +asr_freeaddrinfo(struct addrinfo *ai) +{ + struct addrinfo *ai_next; + + while (ai) { + ai_next = ai->ai_next; + if (ai->ai_canonname) + free(ai->ai_canonname); + free(ai); + ai = ai_next; + } +} + +static int +addrinfo_from_file(struct asr_query *as, int family, FILE *f) +{ + char *tokens[MAXTOKEN], *c, buf[ASR_BUFSIZ + 1]; + int n, i; + union { + struct sockaddr sa; + struct sockaddr_in sain; + struct sockaddr_in6 sain6; + } u; + + for (;;) { + n = _asr_parse_namedb_line(f, tokens, MAXTOKEN, buf, sizeof(buf)); + if (n == -1) + break; /* ignore errors reading the file */ + + for (i = 1; i < n; i++) { + if (strcasecmp(as->as.ai.hostname, tokens[i])) + continue; + if (_asr_sockaddr_from_str(&u.sa, family, tokens[0]) == -1) + continue; + break; + } + if (i == n) + continue; + + if (as->as.ai.hints.ai_flags & (AI_CANONNAME | AI_FQDN)) + c = tokens[1]; + else + c = NULL; + + if (addrinfo_add(as, &u.sa, c)) + return (-1); /* errno set */ + } + return (0); +} + +static int +addrinfo_from_pkt(struct asr_query *as, char *pkt, size_t pktlen) +{ + struct asr_unpack p; + struct asr_dns_header h; + struct asr_dns_query q; + struct asr_dns_rr rr; + int i; + union { + struct sockaddr sa; + struct sockaddr_in sain; + struct sockaddr_in6 sain6; + } u; + char buf[MAXDNAME], *c; + + _asr_unpack_init(&p, pkt, pktlen); + _asr_unpack_header(&p, &h); + for (; h.qdcount; h.qdcount--) + _asr_unpack_query(&p, &q); + + for (i = 0; i < h.ancount; i++) { + _asr_unpack_rr(&p, &rr); + if (rr.rr_type != q.q_type || + rr.rr_class != q.q_class) + continue; + + memset(&u, 0, sizeof u); + if (rr.rr_type == T_A) { +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + u.sain.sin_len = sizeof u.sain; +#endif + u.sain.sin_family = AF_INET; + u.sain.sin_addr = rr.rr.in_a.addr; + u.sain.sin_port = 0; + } else if (rr.rr_type == T_AAAA) { +#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN + u.sain6.sin6_len = sizeof u.sain6; +#endif + u.sain6.sin6_family = AF_INET6; + u.sain6.sin6_addr = rr.rr.in_aaaa.addr6; + u.sain6.sin6_port = 0; + } else + continue; + + if (as->as.ai.hints.ai_flags & AI_CANONNAME) { + _asr_strdname(rr.rr_dname, buf, sizeof buf); + buf[strlen(buf) - 1] = '\0'; + c = res_hnok(buf) ? buf : NULL; + } else if (as->as.ai.hints.ai_flags & AI_FQDN) + c = as->as.ai.fqdn; + else + c = NULL; + + if (addrinfo_add(as, &u.sa, c)) + return (-1); /* errno set */ + } + return (0); +} + +static int +addrconfig_setup(struct asr_query *as) +{ + struct ifaddrs *ifa, *ifa0; + struct sockaddr_in *sinp; + struct sockaddr_in6 *sin6p; + + if (getifaddrs(&ifa0) == -1) + return (-1); + + as->as_flags |= ASYNC_NO_INET | ASYNC_NO_INET6; + + for (ifa = ifa0; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) + continue; + + switch (ifa->ifa_addr->sa_family) { + case PF_INET: + sinp = (struct sockaddr_in *)ifa->ifa_addr; + + if (sinp->sin_addr.s_addr == htonl(INADDR_LOOPBACK)) + continue; + + as->as_flags &= ~ASYNC_NO_INET; + break; + case PF_INET6: + sin6p = (struct sockaddr_in6 *)ifa->ifa_addr; + + if (IN6_IS_ADDR_LOOPBACK(&sin6p->sin6_addr)) + continue; + + if (IN6_IS_ADDR_LINKLOCAL(&sin6p->sin6_addr)) + continue; + + as->as_flags &= ~ASYNC_NO_INET6; + break; + } + } + + freeifaddrs(ifa0); + + return (0); +} diff --git a/foobar/portable/openbsd-compat/libasr/gethostnamadr.c b/foobar/portable/openbsd-compat/libasr/gethostnamadr.c new file mode 100644 index 00000000..2fce46b3 --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/gethostnamadr.c @@ -0,0 +1,200 @@ +/* $OpenBSD: gethostnamadr.c,v 1.13 2015/09/14 07:38:37 guenther Exp $ */ +/* + * Copyright (c) 2012,2013 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/param.h> /* ALIGN */ +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +static int _gethostbyname(const char *, int, struct hostent *, char *, size_t, + int *); +static int _fillhostent(const struct hostent *, struct hostent *, char *, + size_t); + +static struct hostent _hostent; +static char _entbuf[4096]; + +static char *_empty[] = { NULL, }; + +static int +_fillhostent(const struct hostent *h, struct hostent *r, char *buf, size_t len) +{ + char **ptr, *end, *pos; + size_t n, i; + int naliases, naddrs; + + bzero(buf, len); + bzero(r, sizeof(*r)); + r->h_aliases = _empty; + r->h_addr_list = _empty; + + end = buf + len; + ptr = (char **)ALIGN(buf); + + if ((char *)ptr >= end) + return (ERANGE); + + for (naliases = 0; h->h_aliases[naliases]; naliases++) + ; + for (naddrs = 0; h->h_addr_list[naddrs]; naddrs++) + ; + + pos = (char *)(ptr + (naliases + 1) + (naddrs + 1)); + if (pos >= end) + return (ERANGE); + + r->h_name = NULL; + r->h_addrtype = h->h_addrtype; + r->h_length = h->h_length; + r->h_aliases = ptr; + r->h_addr_list = ptr + naliases + 1; + + n = strlcpy(pos, h->h_name, end - pos); + if (n >= end - pos) + return (ERANGE); + r->h_name = pos; + pos += n + 1; + + for (i = 0; i < naliases; i++) { + n = strlcpy(pos, h->h_aliases[i], end - pos); + if (n >= end - pos) + return (ERANGE); + r->h_aliases[i] = pos; + pos += n + 1; + } + + pos = (char *)ALIGN(pos); + if (pos >= end) + return (ERANGE); + + for (i = 0; i < naddrs; i++) { + if (r->h_length > end - pos) + return (ERANGE); + memmove(pos, h->h_addr_list[i], r->h_length); + r->h_addr_list[i] = pos; + pos += r->h_length; + } + + return (0); +} + +static int +_gethostbyname(const char *name, int af, struct hostent *ret, char *buf, + size_t buflen, int *h_errnop) +{ + struct asr_query *as; + struct asr_result ar; + int r; + + if (af == -1) + as = gethostbyname_async(name, NULL); + else + as = gethostbyname2_async(name, af, NULL); + + if (as == NULL) + return (errno); + + asr_run_sync(as, &ar); + + errno = ar.ar_errno; + *h_errnop = ar.ar_h_errno; + if (ar.ar_hostent == NULL) + return (0); + + r = _fillhostent(ar.ar_hostent, ret, buf, buflen); + free(ar.ar_hostent); + + return (r); +} + +struct hostent * +gethostbyname(const char *name) +{ + struct hostent *h; + + res_init(); + + if (_res.options & RES_USE_INET6 && + (h = gethostbyname2(name, AF_INET6))) + return (h); + + return gethostbyname2(name, AF_INET); +} +DEF_WEAK(gethostbyname); + +struct hostent * +gethostbyname2(const char *name, int af) +{ + int r; + + res_init(); + + r = _gethostbyname(name, af, &_hostent, _entbuf, sizeof(_entbuf), + &h_errno); + if (r) { + h_errno = NETDB_INTERNAL; + errno = r; + } + + if (h_errno) + return (NULL); + + return (&_hostent); +} +DEF_WEAK(gethostbyname2); + +struct hostent * +gethostbyaddr(const void *addr, socklen_t len, int af) +{ + struct asr_query *as; + struct asr_result ar; + int r; + + res_init(); + + as = gethostbyaddr_async(addr, len, af, NULL); + if (as == NULL) { + h_errno = NETDB_INTERNAL; + return (NULL); + } + + asr_run_sync(as, &ar); + + errno = ar.ar_errno; + h_errno = ar.ar_h_errno; + if (ar.ar_hostent == NULL) + return (NULL); + + r = _fillhostent(ar.ar_hostent, &_hostent, _entbuf, sizeof(_entbuf)); + free(ar.ar_hostent); + + if (r) { + h_errno = NETDB_INTERNAL; + errno = r; + return (NULL); + } + + return (&_hostent); +} diff --git a/foobar/portable/openbsd-compat/libasr/gethostnamadr_async.c b/foobar/portable/openbsd-compat/libasr/gethostnamadr_async.c new file mode 100644 index 00000000..2636e848 --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/gethostnamadr_async.c @@ -0,0 +1,676 @@ +/* $OpenBSD: gethostnamadr_async.c,v 1.45 2019/06/27 05:26:37 martijn Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +#include <arpa/nameser_compat.h> +#endif +#include <netdb.h> + +#include <asr.h> +#include <ctype.h> +#include <errno.h> +#include <resolv.h> /* for res_hnok */ +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> + +#include "asr_private.h" + +#define MAXALIASES 35 +#define MAXADDRS 35 + +struct hostent_ext { + struct hostent h; + char *aliases[MAXALIASES + 1]; + char *addrs[MAXADDRS + 1]; + char *end; + char *pos; +}; + +struct netent_ext { + struct netent n; + char *aliases[MAXALIASES + 1]; + char *end; + char *pos; +}; + +static int gethostnamadr_async_run(struct asr_query *, struct asr_result *); +static struct hostent_ext *hostent_alloc(int); +static int hostent_set_cname(struct hostent_ext *, const char *, int); +static int hostent_add_alias(struct hostent_ext *, const char *, int); +static int hostent_add_addr(struct hostent_ext *, const void *, size_t); +static struct hostent_ext *hostent_from_addr(int, const char *, const char *); +static struct hostent_ext *hostent_file_match(FILE *, int, int, const char *, + int); +static struct hostent_ext *hostent_from_packet(int, int, char *, size_t); +static void netent_from_hostent(struct asr_result *ar); + +struct asr_query * +gethostbyname_async(const char *name, void *asr) +{ + return gethostbyname2_async(name, AF_INET, asr); +} +DEF_WEAK(gethostbyname_async); + +struct asr_query * +gethostbyname2_async(const char *name, int af, void *asr) +{ + struct asr_ctx *ac; + struct asr_query *as; + + /* the original segfaults */ + if (name == NULL) { + errno = EINVAL; + return (NULL); + } + + ac = _asr_use_resolver(asr); + if ((as = _asr_async_new(ac, ASR_GETHOSTBYNAME)) == NULL) + goto abort; /* errno set */ + as->as_run = gethostnamadr_async_run; + + as->as.hostnamadr.family = af; + if (af == AF_INET) + as->as.hostnamadr.addrlen = INADDRSZ; + else if (af == AF_INET6) + as->as.hostnamadr.addrlen = IN6ADDRSZ; + as->as.hostnamadr.name = strdup(name); + if (as->as.hostnamadr.name == NULL) + goto abort; /* errno set */ + + _asr_ctx_unref(ac); + return (as); + + abort: + if (as) + _asr_async_free(as); + _asr_ctx_unref(ac); + return (NULL); +} +DEF_WEAK(gethostbyname2_async); + +struct asr_query * +gethostbyaddr_async(const void *addr, socklen_t len, int af, void *asr) +{ + struct asr_ctx *ac; + struct asr_query *as; + + ac = _asr_use_resolver(asr); + as = _gethostbyaddr_async_ctx(addr, len, af, ac); + _asr_ctx_unref(ac); + + return (as); +} +DEF_WEAK(gethostbyaddr_async); + +struct asr_query * +_gethostbyaddr_async_ctx(const void *addr, socklen_t len, int af, + struct asr_ctx *ac) +{ + struct asr_query *as; + + if ((as = _asr_async_new(ac, ASR_GETHOSTBYADDR)) == NULL) + goto abort; /* errno set */ + as->as_run = gethostnamadr_async_run; + + as->as.hostnamadr.family = af; + as->as.hostnamadr.addrlen = len; + if (len > 0) + memmove(as->as.hostnamadr.addr, addr, (len > 16) ? 16 : len); + + return (as); + + abort: + if (as) + _asr_async_free(as); + return (NULL); +} + +static int +gethostnamadr_async_run(struct asr_query *as, struct asr_result *ar) +{ + struct hostent_ext *h; + int r, type, saved_errno; + FILE *f; + char name[MAXDNAME], *data, addr[16], *c; + + next: + switch (as->as_state) { + + case ASR_STATE_INIT: + + if (as->as.hostnamadr.family != AF_INET && + as->as.hostnamadr.family != AF_INET6) { + ar->ar_h_errno = NETDB_INTERNAL; + ar->ar_errno = EAFNOSUPPORT; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if ((as->as.hostnamadr.family == AF_INET && + as->as.hostnamadr.addrlen != INADDRSZ) || + (as->as.hostnamadr.family == AF_INET6 && + as->as.hostnamadr.addrlen != IN6ADDRSZ)) { + ar->ar_h_errno = NETDB_INTERNAL; + ar->ar_errno = EINVAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (as->as_type == ASR_GETHOSTBYNAME) { + + if (as->as.hostnamadr.name[0] == '\0') { + ar->ar_h_errno = NO_DATA; + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* Name might be an IP address string */ + for (c = as->as.hostnamadr.name; *c; c++) + if (!isdigit((unsigned char)*c) && + *c != '.' && *c != ':') + break; + if (*c == 0 && + inet_pton(as->as.hostnamadr.family, + as->as.hostnamadr.name, addr) == 1) { + h = hostent_from_addr(as->as.hostnamadr.family, + as->as.hostnamadr.name, addr); + if (h == NULL) { + ar->ar_errno = errno; + ar->ar_h_errno = NETDB_INTERNAL; + } + else { + ar->ar_hostent = &h->h; + ar->ar_h_errno = NETDB_SUCCESS; + } + async_set_state(as, ASR_STATE_HALT); + break; + } + } + async_set_state(as, ASR_STATE_NEXT_DB); + break; + + case ASR_STATE_NEXT_DB: + + if (_asr_iter_db(as) == -1) { + async_set_state(as, ASR_STATE_NOT_FOUND); + break; + } + + switch (AS_DB(as)) { + + case ASR_DB_DNS: + + /* Create a subquery to do the DNS lookup */ + + if (as->as_type == ASR_GETHOSTBYNAME) { + type = (as->as.hostnamadr.family == AF_INET) ? + T_A : T_AAAA; + as->as_subq = _res_search_async_ctx( + as->as.hostnamadr.name, + C_IN, type, as->as_ctx); + } else { + _asr_addr_as_fqdn(as->as.hostnamadr.addr, + as->as.hostnamadr.family, + name, sizeof(name)); + as->as_subq = _res_query_async_ctx( + name, C_IN, T_PTR, as->as_ctx); + } + + if (as->as_subq == NULL) { + ar->ar_errno = errno; + ar->ar_h_errno = NETDB_INTERNAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + + async_set_state(as, ASR_STATE_SUBQUERY); + break; + + case ASR_DB_FILE: + + /* Try to find a match in the host file */ + + if ((f = fopen(_PATH_HOSTS, "re")) == NULL) + break; + + if (as->as_type == ASR_GETHOSTBYNAME) + data = as->as.hostnamadr.name; + else + data = as->as.hostnamadr.addr; + + h = hostent_file_match(f, as->as_type, + as->as.hostnamadr.family, data, + as->as.hostnamadr.addrlen); + saved_errno = errno; + fclose(f); + errno = saved_errno; + + if (h == NULL) { + if (errno) { + ar->ar_errno = errno; + ar->ar_h_errno = NETDB_INTERNAL; + async_set_state(as, ASR_STATE_HALT); + } + /* otherwise not found */ + break; + } + ar->ar_hostent = &h->h; + ar->ar_h_errno = NETDB_SUCCESS; + async_set_state(as, ASR_STATE_HALT); + break; + } + break; + + case ASR_STATE_SUBQUERY: + + /* Run the DNS subquery. */ + + if ((r = asr_run(as->as_subq, ar)) == ASYNC_COND) + return (ASYNC_COND); + + /* Done. */ + as->as_subq = NULL; + + /* + * We either got no packet or a packet without an answer. + * Saveguard the h_errno and use the next DB. + */ + if (ar->ar_count == 0) { + free(ar->ar_data); + as->as.hostnamadr.subq_h_errno = ar->ar_h_errno; + async_set_state(as, ASR_STATE_NEXT_DB); + break; + } + + /* Read the hostent from the packet. */ + + h = hostent_from_packet(as->as_type, + as->as.hostnamadr.family, ar->ar_data, ar->ar_datalen); + free(ar->ar_data); + if (h == NULL) { + ar->ar_errno = errno; + ar->ar_h_errno = NETDB_INTERNAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (as->as_type == ASR_GETHOSTBYADDR) { + if (hostent_add_addr(h, as->as.hostnamadr.addr, + as->as.hostnamadr.addrlen) == -1) { + free(h); + ar->ar_errno = errno; + ar->ar_h_errno = NETDB_INTERNAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + } + + /* + * No valid hostname or address found in the dns packet. + * Ignore it. + */ + if ((as->as_type == ASR_GETHOSTBYNAME && + h->h.h_addr_list[0] == NULL) || + h->h.h_name == NULL) { + free(h); + async_set_state(as, ASR_STATE_NEXT_DB); + break; + } + + ar->ar_hostent = &h->h; + ar->ar_h_errno = NETDB_SUCCESS; + async_set_state(as, ASR_STATE_HALT); + break; + + case ASR_STATE_NOT_FOUND: + ar->ar_errno = 0; + if (as->as.hostnamadr.subq_h_errno) + ar->ar_h_errno = as->as.hostnamadr.subq_h_errno; + else + ar->ar_h_errno = HOST_NOT_FOUND; + async_set_state(as, ASR_STATE_HALT); + break; + + case ASR_STATE_HALT: + if (ar->ar_h_errno == NETDB_SUCCESS && + as->as_flags & ASYNC_GETNET) + netent_from_hostent(ar); + if (ar->ar_h_errno) { + ar->ar_hostent = NULL; + ar->ar_netent = NULL; + } else + ar->ar_errno = 0; + return (ASYNC_DONE); + + default: + ar->ar_errno = EOPNOTSUPP; + ar->ar_h_errno = NETDB_INTERNAL; + ar->ar_gai_errno = EAI_SYSTEM; + async_set_state(as, ASR_STATE_HALT); + break; + } + goto next; +} + +/* + * Create a hostent from a numeric address string. + */ +static struct hostent_ext * +hostent_from_addr(int family, const char *name, const char *addr) +{ + struct hostent_ext *h; + + if ((h = hostent_alloc(family)) == NULL) + return (NULL); + if (hostent_set_cname(h, name, 0) == -1) + goto fail; + if (hostent_add_addr(h, addr, h->h.h_length) == -1) + goto fail; + return (h); +fail: + free(h); + return (NULL); +} + +/* + * Lookup the first matching entry in the hostfile, either by address or by + * name depending on reqtype, and build a hostent from the line. + */ +static struct hostent_ext * +hostent_file_match(FILE *f, int reqtype, int family, const char *data, + int datalen) +{ + char *tokens[MAXTOKEN], addr[16], buf[ASR_BUFSIZ + 1]; + struct hostent_ext *h; + int n, i; + + for (;;) { + n = _asr_parse_namedb_line(f, tokens, MAXTOKEN, buf, sizeof(buf)); + if (n == -1) { + errno = 0; /* ignore errors reading the file */ + return (NULL); + } + + /* there must be an address and at least one name */ + if (n < 2) + continue; + + if (reqtype == ASR_GETHOSTBYNAME) { + for (i = 1; i < n; i++) { + if (strcasecmp(data, tokens[i])) + continue; + if (inet_pton(family, tokens[0], addr) == 1) + goto found; + } + } else { + if (inet_pton(family, tokens[0], addr) == 1 && + memcmp(addr, data, datalen) == 0) + goto found; + } + } + +found: + if ((h = hostent_alloc(family)) == NULL) + return (NULL); + if (hostent_set_cname(h, tokens[1], 0) == -1) + goto fail; + for (i = 2; i < n; i ++) + if (hostent_add_alias(h, tokens[i], 0) == -1) + goto fail; + if (hostent_add_addr(h, addr, h->h.h_length) == -1) + goto fail; + return (h); +fail: + free(h); + return (NULL); +} + +/* + * Fill the hostent from the given DNS packet. + */ +static struct hostent_ext * +hostent_from_packet(int reqtype, int family, char *pkt, size_t pktlen) +{ + struct hostent_ext *h; + struct asr_unpack p; + struct asr_dns_header hdr; + struct asr_dns_query q; + struct asr_dns_rr rr; + char dname[MAXDNAME]; + + if ((h = hostent_alloc(family)) == NULL) + return (NULL); + + _asr_unpack_init(&p, pkt, pktlen); + _asr_unpack_header(&p, &hdr); + for (; hdr.qdcount; hdr.qdcount--) + _asr_unpack_query(&p, &q); + strlcpy(dname, q.q_dname, sizeof(dname)); + + for (; hdr.ancount; hdr.ancount--) { + _asr_unpack_rr(&p, &rr); + if (rr.rr_class != C_IN) + continue; + switch (rr.rr_type) { + + case T_CNAME: + if (reqtype == ASR_GETHOSTBYNAME) { + if (hostent_add_alias(h, rr.rr_dname, 1) == -1) + goto fail; + } else { + if (strcasecmp(rr.rr_dname, dname) == 0) + strlcpy(dname, rr.rr.cname.cname, + sizeof(dname)); + } + break; + + case T_PTR: + if (reqtype != ASR_GETHOSTBYADDR) + break; + if (strcasecmp(rr.rr_dname, dname) != 0) + continue; + if (hostent_set_cname(h, rr.rr.ptr.ptrname, 1) == -1) + hostent_add_alias(h, rr.rr.ptr.ptrname, 1); + break; + + case T_A: + if (reqtype != ASR_GETHOSTBYNAME) + break; + if (family != AF_INET) + break; + if (hostent_set_cname(h, rr.rr_dname, 1) == -1) + ; + if (hostent_add_addr(h, &rr.rr.in_a.addr, 4) == -1) + goto fail; + break; + + case T_AAAA: + if (reqtype != ASR_GETHOSTBYNAME) + break; + if (family != AF_INET6) + break; + if (hostent_set_cname(h, rr.rr_dname, 1) == -1) + ; + if (hostent_add_addr(h, &rr.rr.in_aaaa.addr6, 16) == -1) + goto fail; + break; + } + } + + return (h); +fail: + free(h); + return (NULL); +} + +static struct hostent_ext * +hostent_alloc(int family) +{ + struct hostent_ext *h; + size_t alloc; + + alloc = sizeof(*h) + 1024; + if ((h = calloc(1, alloc)) == NULL) + return (NULL); + + h->h.h_addrtype = family; + h->h.h_length = (family == AF_INET) ? 4 : 16; + h->h.h_aliases = h->aliases; + h->h.h_addr_list = h->addrs; + h->pos = (char *)(h) + sizeof(*h); + h->end = h->pos + 1024; + + return (h); +} + +static int +hostent_set_cname(struct hostent_ext *h, const char *name, int isdname) +{ + char buf[MAXDNAME]; + size_t n; + + if (h->h.h_name) + return (-1); + + if (isdname) { + _asr_strdname(name, buf, sizeof buf); + buf[strlen(buf) - 1] = '\0'; + if (!res_hnok(buf)) + return (-1); + name = buf; + } + + n = strlen(name) + 1; + if (h->pos + n >= h->end) + return (-1); + + h->h.h_name = h->pos; + memmove(h->pos, name, n); + h->pos += n; + return (0); +} + +static int +hostent_add_alias(struct hostent_ext *h, const char *name, int isdname) +{ + char buf[MAXDNAME]; + size_t i, n; + + for (i = 0; i < MAXALIASES; i++) + if (h->aliases[i] == NULL) + break; + if (i == MAXALIASES) + return (0); + + if (isdname) { + _asr_strdname(name, buf, sizeof buf); + buf[strlen(buf)-1] = '\0'; + if (!res_hnok(buf)) + return (-1); + name = buf; + } + + n = strlen(name) + 1; + if (h->pos + n >= h->end) + return (0); + + h->aliases[i] = h->pos; + memmove(h->pos, name, n); + h->pos += n; + return (0); +} + +static int +hostent_add_addr(struct hostent_ext *h, const void *addr, size_t size) +{ + int i; + + for (i = 0; i < MAXADDRS; i++) + if (h->addrs[i] == NULL) + break; + if (i == MAXADDRS) + return (0); + + if (h->pos + size >= h->end) + return (0); + + h->addrs[i] = h->pos; + memmove(h->pos, addr, size); + h->pos += size; + return (0); +} + +static void +netent_from_hostent(struct asr_result *ar) +{ + struct in_addr *addr; + struct netent_ext *n; + struct hostent_ext *h; + char **na, **ha; + size_t sz; + + /* Allocate and initialize the output. */ + if ((n = calloc(1, sizeof(*n) + 1024)) == NULL) { + ar->ar_h_errno = NETDB_INTERNAL; + ar->ar_errno = errno; + goto out; + } + n->pos = (char *)(n) + sizeof(*n); + n->end = n->pos + 1024; + n->n.n_name = n->pos; + n->n.n_aliases = n->aliases; + + /* Copy the fixed-size data. */ + h = (struct hostent_ext *)ar->ar_hostent; + addr = (struct in_addr *)h->h.h_addr; + n->n.n_net = ntohl(addr->s_addr); + n->n.n_addrtype = h->h.h_addrtype; + + /* Copy the network name. */ + sz = strlen(h->h.h_name) + 1; + memcpy(n->pos, h->h.h_name, sz); + n->pos += sz; + + /* + * Copy the aliases. + * No overflow check is needed because we are merely copying + * a part of the data from a structure of the same size. + */ + na = n->aliases; + for (ha = h->aliases; *ha != NULL; ha++) { + sz = strlen(*ha) + 1; + memcpy(n->pos, *ha, sz); + *na++ = n->pos; + n->pos += sz; + } + *na = NULL; + + /* Handle the return values. */ + ar->ar_netent = &n->n; +out: + free(ar->ar_hostent); + ar->ar_hostent = NULL; +} diff --git a/foobar/portable/openbsd-compat/libasr/getnameinfo.c b/foobar/portable/openbsd-compat/libasr/getnameinfo.c new file mode 100644 index 00000000..7bee468d --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/getnameinfo.c @@ -0,0 +1,205 @@ +/* $OpenBSD: getnameinfo.c,v 1.9 2019/07/03 03:24:03 deraadt Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <net/if.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> +#include <string.h> + +static size_t asr_print_addr(const struct sockaddr *, char *, size_t); +static size_t asr_print_port(const struct sockaddr *, const char *, char *, size_t); + +#define SA_IN(sa) ((struct sockaddr_in*)(sa)) +#define SA_IN6(sa) ((struct sockaddr_in6*)(sa)) + +/* + * Print the textual representation (as given by inet_ntop(3)) of the address + * set in "sa". + * + * Return the total length of the string it tried to create or 0 if an error + * occured, in which case errno is set. On success, the constructed string + * is guaranteed to be NUL-terminated. Overflow must be detected by checking + * the returned size against buflen. + * + */ +static size_t +asr_print_addr(const struct sockaddr *sa, char *buf, size_t buflen) +{ + unsigned int ifidx; + char tmp[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; + char scope[IF_NAMESIZE + 1], *ifname; + const void *addr; + size_t s; + + switch(sa->sa_family) { + case AF_INET: + addr = &SA_IN(sa)->sin_addr; + break; + case AF_INET6: + addr = &SA_IN6(sa)->sin6_addr; + break; + default: + errno = EINVAL; + return (0); + } + + if (inet_ntop(sa->sa_family, addr, tmp, sizeof(tmp)) == NULL) + return (0); /* errno set */ + + s = strlcpy(buf, tmp, buflen); + + if (sa->sa_family == AF_INET6 && SA_IN6(sa)->sin6_scope_id) { + + scope[0] = SCOPE_DELIMITER; + scope[1] = '\0'; + + ifidx = SA_IN6(sa)->sin6_scope_id; + ifname = NULL; + + if (IN6_IS_ADDR_LINKLOCAL(&(SA_IN6(sa)->sin6_addr)) || + IN6_IS_ADDR_MC_LINKLOCAL(&(SA_IN6(sa)->sin6_addr)) || + IN6_IS_ADDR_MC_INTFACELOCAL(&(SA_IN6(sa)->sin6_addr))) + ifname = if_indextoname(ifidx, scope + 1); + + if (ifname == NULL) + (void)snprintf(scope + 1, sizeof(scope) - 1, "%u", ifidx); + + if (s < buflen) + (void)strlcat(buf, scope, buflen); + + s += strlen(scope); + } + + return (s); +} + +/* + * Print the textual representation of the port set on "sa". + * + * If proto is not NULL, it is used as parameter to "getservbyport_r(3)" to + * return a service name. If it's not set, or if no matching service is found, + * it prints the portno. + * + * Return the total length of the string it tried to create or 0 if an error + * occured, in which case errno is set. On success, the constructed string + * is guaranteed to be NUL-terminated. Overflow must be detected by checking + * the returned size against buflen. + */ +static size_t +asr_print_port(const struct sockaddr *sa, const char *proto, char *buf, size_t buflen) +{ + struct servent s; + struct servent_data sd; + int port, r, saved_errno; + size_t n; + + switch(sa->sa_family) { + case AF_INET: + port = SA_IN(sa)->sin_port; + break; + case AF_INET6: + port = SA_IN6(sa)->sin6_port; + break; + default: + errno = EINVAL; + return (0); + } + + if (proto) { + memset(&sd, 0, sizeof (sd)); + saved_errno = errno; + if (getservbyport_r(port, proto, &s, &sd) != -1) { + n = strlcpy(buf, s.s_name, buflen); + endservent_r(&sd); + return (n); + } + errno = saved_errno; + } + + r = snprintf(buf, buflen, "%u", ntohs(port)); + if (r < 0 || r >= buflen) /* Actually, this can not happen */ + return (0); + + return (r); +} + +int +getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, + size_t hostlen, char *serv, size_t servlen, int flags) +{ + struct asr_query *as; + struct asr_result ar; + int saved_errno = errno; + const char *proto; + size_t r; + + /* + * Take a shortcut if we don't care about hostname, + * or if NI_NUMERICHOST is set. + */ + if (host == NULL || hostlen == 0 || + (host && hostlen && (flags & NI_NUMERICHOST))) { + if (host) { + r = asr_print_addr(sa, host, hostlen); + if (r == 0) + return (EAI_SYSTEM); /* errno set */ + if (r >= hostlen) + return (EAI_OVERFLOW); + } + + if (serv && servlen) { + if (flags & NI_NUMERICSERV) + proto = NULL; + else + proto = (flags & NI_DGRAM) ? "udp" : "tcp"; + r = asr_print_port(sa, proto, serv, servlen); + if (r == 0) + return (EAI_SYSTEM); /* errno set */ + if (r >= servlen) + return (EAI_OVERFLOW); + } + + errno = saved_errno; + return (0); + } + + res_init(); + + as = getnameinfo_async(sa, salen, host, hostlen, serv, servlen, flags, + NULL); + if (as == NULL) { + if (errno == ENOMEM) { + errno = saved_errno; + return (EAI_MEMORY); + } + return (EAI_SYSTEM); + } + + asr_run_sync(as, &ar); + if (ar.ar_gai_errno == EAI_SYSTEM) + errno = ar.ar_errno; + + return (ar.ar_gai_errno); +} +DEF_WEAK(getnameinfo); diff --git a/foobar/portable/openbsd-compat/libasr/getnameinfo_async.c b/foobar/portable/openbsd-compat/libasr/getnameinfo_async.c new file mode 100644 index 00000000..faba8860 --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/getnameinfo_async.c @@ -0,0 +1,300 @@ +/* $OpenBSD: getnameinfo_async.c,v 1.14 2019/07/03 03:24:03 deraadt Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <net/if.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "asr_private.h" + +static int getnameinfo_async_run(struct asr_query *, struct asr_result *); +static int _servname(struct asr_query *); +static int _numerichost(struct asr_query *); + +struct asr_query * +getnameinfo_async(const struct sockaddr *sa, socklen_t slen, char *host, + size_t hostlen, char *serv, size_t servlen, int flags, void *asr) +{ + struct asr_ctx *ac; + struct asr_query *as; + + ac = _asr_use_resolver(asr); + if ((as = _asr_async_new(ac, ASR_GETNAMEINFO)) == NULL) + goto abort; /* errno set */ + as->as_run = getnameinfo_async_run; + + if (sa->sa_family == AF_INET) + memmove(&as->as.ni.sa.sa, sa, sizeof (as->as.ni.sa.sain)); + else if (sa->sa_family == AF_INET6) + memmove(&as->as.ni.sa.sa, sa, sizeof (as->as.ni.sa.sain6)); + +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + as->as.ni.sa.sa.sa_len = slen; +#endif + as->as.ni.hostname = host; + as->as.ni.hostnamelen = hostlen; + as->as.ni.servname = serv; + as->as.ni.servnamelen = servlen; + as->as.ni.flags = flags; + + _asr_ctx_unref(ac); + return (as); + + abort: + if (as) + _asr_async_free(as); + _asr_ctx_unref(ac); + return (NULL); +} +DEF_WEAK(getnameinfo_async); + +static int +getnameinfo_async_run(struct asr_query *as, struct asr_result *ar) +{ + void *addr; + socklen_t addrlen; + int r; + + next: + switch (as->as_state) { + + case ASR_STATE_INIT: + + /* Make sure the parameters are all valid. */ + + if (as->as.ni.sa.sa.sa_family != AF_INET && + as->as.ni.sa.sa.sa_family != AF_INET6) { + ar->ar_gai_errno = EAI_FAMILY; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if ((as->as.ni.sa.sa.sa_family == AF_INET && + (SA_LEN(&as->as.ni.sa.sa) != sizeof (as->as.ni.sa.sain))) || + (as->as.ni.sa.sa.sa_family == AF_INET6 && + (SA_LEN(&as->as.ni.sa.sa) != sizeof (as->as.ni.sa.sain6)))) { + ar->ar_gai_errno = EAI_FAIL; + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* Set the service name first, if needed. */ + if (_servname(as) == -1) { + ar->ar_gai_errno = EAI_OVERFLOW; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (as->as.ni.hostname == NULL || as->as.ni.hostnamelen == 0) { + ar->ar_gai_errno = 0; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (as->as.ni.flags & NI_NUMERICHOST) { + if (_numerichost(as) == -1) { + if (errno == ENOMEM) + ar->ar_gai_errno = EAI_MEMORY; + else if (errno == ENOSPC) + ar->ar_gai_errno = EAI_OVERFLOW; + else { + ar->ar_errno = errno; + ar->ar_gai_errno = EAI_SYSTEM; + } + } else + ar->ar_gai_errno = 0; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (as->as.ni.sa.sa.sa_family == AF_INET) { + addrlen = sizeof(as->as.ni.sa.sain.sin_addr); + addr = &as->as.ni.sa.sain.sin_addr; + } else { + addrlen = sizeof(as->as.ni.sa.sain6.sin6_addr); + addr = &as->as.ni.sa.sain6.sin6_addr; + } + + /* + * Create a subquery to lookup the address. + */ + as->as_subq = _gethostbyaddr_async_ctx(addr, addrlen, + as->as.ni.sa.sa.sa_family, + as->as_ctx); + if (as->as_subq == NULL) { + ar->ar_gai_errno = EAI_MEMORY; + async_set_state(as, ASR_STATE_HALT); + break; + } + + async_set_state(as, ASR_STATE_SUBQUERY); + break; + + case ASR_STATE_SUBQUERY: + + if ((r = asr_run(as->as_subq, ar)) == ASYNC_COND) + return (ASYNC_COND); + + /* + * Request done. + */ + as->as_subq = NULL; + + if (ar->ar_hostent == NULL) { + if (as->as.ni.flags & NI_NAMEREQD) { + ar->ar_gai_errno = EAI_NONAME; + } else if (_numerichost(as) == -1) { + if (errno == ENOMEM) + ar->ar_gai_errno = EAI_MEMORY; + else if (errno == ENOSPC) + ar->ar_gai_errno = EAI_OVERFLOW; + else { + ar->ar_errno = errno; + ar->ar_gai_errno = EAI_SYSTEM; + } + } else + ar->ar_gai_errno = 0; + } else { + if (strlcpy(as->as.ni.hostname, + ar->ar_hostent->h_name, + as->as.ni.hostnamelen) >= as->as.ni.hostnamelen) + ar->ar_gai_errno = EAI_OVERFLOW; + else + ar->ar_gai_errno = 0; + free(ar->ar_hostent); + } + + async_set_state(as, ASR_STATE_HALT); + break; + + case ASR_STATE_HALT: + return (ASYNC_DONE); + + default: + ar->ar_errno = EOPNOTSUPP; + ar->ar_gai_errno = EAI_SYSTEM; + async_set_state(as, ASR_STATE_HALT); + break; + } + goto next; +} + + +/* + * Set the service name on the result buffer is not NULL. + * return (-1) if the buffer is too small. + */ +static int +_servname(struct asr_query *as) +{ + struct servent s; +#ifdef HAVE_STRUCT_SERVENT_DATA + struct servent_data sd; +#endif + int port, r; + char *buf = as->as.ni.servname; + size_t buflen = as->as.ni.servnamelen; + + if (as->as.ni.servname == NULL || as->as.ni.servnamelen == 0) + return (0); + + if (as->as.ni.sa.sa.sa_family == AF_INET) + port = as->as.ni.sa.sain.sin_port; + else + port = as->as.ni.sa.sain6.sin6_port; + + if (!(as->as.ni.flags & NI_NUMERICSERV)) { +#ifdef HAVE_STRUCT_SERVENT_DATA + memset(&sd, 0, sizeof (sd)); +#endif +#ifdef HAVE_GETSERVBYPORT_R_4_ARGS + r = getservbyport_r(port, + (as->as.ni.flags & NI_DGRAM) ? "udp" : "tcp", + &s, &sd); +#else + r = -1; +#endif + if (r != -1) { + r = strlcpy(buf, s.s_name, buflen) >= buflen; +#ifdef HAVE_ENDSERVENT_R + endservent_r(&sd); +#endif + return (r ? -1 : 0); + } + } + + r = snprintf(buf, buflen, "%u", ntohs(port)); + if (r < 0 || (size_t)r >= buflen) + return (-1); + + return (0); +} + +/* + * Write the numeric address + */ +static int +_numerichost(struct asr_query *as) +{ + unsigned int ifidx; + char scope[IF_NAMESIZE + 1], *ifname; + void *addr; + char *buf = as->as.ni.hostname; + size_t buflen = as->as.ni.hostnamelen; + + if (as->as.ni.sa.sa.sa_family == AF_INET) + addr = &as->as.ni.sa.sain.sin_addr; + else + addr = &as->as.ni.sa.sain6.sin6_addr; + + if (inet_ntop(as->as.ni.sa.sa.sa_family, addr, buf, buflen) == NULL) + return (-1); /* errno set */ + + if (as->as.ni.sa.sa.sa_family == AF_INET6 && + as->as.ni.sa.sain6.sin6_scope_id) { + + scope[0] = SCOPE_DELIMITER; + scope[1] = '\0'; + + ifidx = as->as.ni.sa.sain6.sin6_scope_id; + ifname = NULL; + + if (IN6_IS_ADDR_LINKLOCAL(&as->as.ni.sa.sain6.sin6_addr) || + IN6_IS_ADDR_MC_LINKLOCAL(&as->as.ni.sa.sain6.sin6_addr) || + IN6_IS_ADDR_MC_NODELOCAL(&as->as.ni.sa.sain6.sin6_addr)) + ifname = if_indextoname(ifidx, scope + 1); + + if (ifname == NULL) + snprintf(scope + 1, sizeof(scope) - 1, "%u", ifidx); + + strlcat(buf, scope, buflen); + } + + return (0); +} diff --git a/foobar/portable/openbsd-compat/libasr/getnetnamadr.c b/foobar/portable/openbsd-compat/libasr/getnetnamadr.c new file mode 100644 index 00000000..141b4a9a --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/getnetnamadr.c @@ -0,0 +1,134 @@ +/* $OpenBSD: getnetnamadr.c,v 1.9 2015/01/16 16:48:51 deraadt Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/param.h> /* ALIGN */ +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> +#include <stdlib.h> +#include <string.h> + +static void _fillnetent(const struct netent *, struct netent *, char *buf, + size_t); + +static struct netent _netent; +static char _entbuf[4096]; + +static char *_empty[] = { NULL, }; + +static void +_fillnetent(const struct netent *e, struct netent *r, char *buf, size_t len) +{ + char **ptr, *end, *pos; + size_t n, i; + int naliases; + + bzero(buf, len); + bzero(r, sizeof(*r)); + r->n_aliases = _empty; + + end = buf + len; + ptr = (char **)ALIGN(buf); + + if ((char *)ptr >= end) + return; + + for (naliases = 0; e->n_aliases[naliases]; naliases++) + ; + + r->n_name = NULL; + r->n_addrtype = e->n_addrtype; + r->n_net = e->n_net; + r->n_aliases = ptr; + + pos = (char *)(ptr + (naliases + 1)); + if (pos > end) + r->n_aliases = _empty; + + n = strlcpy(pos, e->n_name, end - pos); + if (n >= end - pos) + return; + r->n_name = pos; + pos += n + 1; + + for (i = 0; i < naliases; i++) { + n = strlcpy(pos, e->n_aliases[i], end - pos); + if (n >= end - pos) + return; + r->n_aliases[i] = pos; + pos += n + 1; + } +} + +struct netent * +getnetbyname(const char *name) +{ + struct asr_query *as; + struct asr_result ar; + + res_init(); + + as = getnetbyname_async(name, NULL); + if (as == NULL) { + h_errno = NETDB_INTERNAL; + return (NULL); + } + + asr_run_sync(as, &ar); + + errno = ar.ar_errno; + h_errno = ar.ar_h_errno; + if (ar.ar_netent == NULL) + return (NULL); + + _fillnetent(ar.ar_netent, &_netent, _entbuf, sizeof(_entbuf)); + free(ar.ar_netent); + + return (&_netent); +} + +struct netent * +getnetbyaddr(in_addr_t net, int type) +{ + struct asr_query *as; + struct asr_result ar; + + res_init(); + + as = getnetbyaddr_async(net, type, NULL); + if (as == NULL) { + h_errno = NETDB_INTERNAL; + return (NULL); + } + + asr_run_sync(as, &ar); + + errno = ar.ar_errno; + h_errno = ar.ar_h_errno; + if (ar.ar_netent == NULL) + return (NULL); + + _fillnetent(ar.ar_netent, &_netent, _entbuf, sizeof(_entbuf)); + free(ar.ar_netent); + + return (&_netent); +} diff --git a/foobar/portable/openbsd-compat/libasr/getnetnamadr_async.c b/foobar/portable/openbsd-compat/libasr/getnetnamadr_async.c new file mode 100644 index 00000000..1e02d008 --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/getnetnamadr_async.c @@ -0,0 +1,52 @@ +/* $OpenBSD: getnetnamadr_async.c,v 1.26 2018/04/28 15:16:49 schwarze Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> +#include <netdb.h> +#include <asr.h> + +#include "asr_private.h" + +struct asr_query * +getnetbyname_async(const char *name, void *asr) +{ + struct asr_query *as; + + if ((as = gethostbyname_async(name, asr)) != NULL) + as->as_flags |= ASYNC_GETNET; + return (as); +} +DEF_WEAK(getnetbyname_async); + +struct asr_query * +getnetbyaddr_async(in_addr_t net, int family, void *asr) +{ + struct in_addr in; + struct asr_query *as; + + in.s_addr = htonl(net); + as = gethostbyaddr_async(&in, sizeof(in), family, asr); + if (as != NULL) + as->as_flags |= ASYNC_GETNET; + return (as); +} +DEF_WEAK(getnetbyaddr_async); diff --git a/foobar/portable/openbsd-compat/libasr/getrrsetbyname.c b/foobar/portable/openbsd-compat/libasr/getrrsetbyname.c new file mode 100644 index 00000000..24df2c8b --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/getrrsetbyname.c @@ -0,0 +1,83 @@ +/* $OpenBSD: getrrsetbyname.c,v 1.6 2015/09/14 07:38:37 guenther Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> +#include <stdlib.h> + +int +getrrsetbyname(const char *name, unsigned int class, unsigned int type, + unsigned int flags, struct rrsetinfo **res) +{ + struct asr_query *as; + struct asr_result ar; + int r, saved_errno = errno; + + res_init(); + + as = getrrsetbyname_async(name, class, type, flags, NULL); + if (as == NULL) { + r = (errno == ENOMEM) ? ERRSET_NOMEMORY : ERRSET_FAIL; + errno = saved_errno; + return (r); + } + + asr_run_sync(as, &ar); + + *res = ar.ar_rrsetinfo; + + return (ar.ar_rrset_errno); +} + +/* from net/getrrsetbyname.c */ +void +freerrset(struct rrsetinfo *rrset) +{ + u_int16_t i; + + if (rrset == NULL) + return; + + if (rrset->rri_rdatas) { + for (i = 0; i < rrset->rri_nrdatas; i++) { + if (rrset->rri_rdatas[i].rdi_data == NULL) + break; + free(rrset->rri_rdatas[i].rdi_data); + } + free(rrset->rri_rdatas); + } + + if (rrset->rri_sigs) { + for (i = 0; i < rrset->rri_nsigs; i++) { + if (rrset->rri_sigs[i].rdi_data == NULL) + break; + free(rrset->rri_sigs[i].rdi_data); + } + free(rrset->rri_sigs); + } + + if (rrset->rri_name) + free(rrset->rri_name); + free(rrset); +} +DEF_WEAK(freerrset); diff --git a/foobar/portable/openbsd-compat/libasr/getrrsetbyname_async.c b/foobar/portable/openbsd-compat/libasr/getrrsetbyname_async.c new file mode 100644 index 00000000..9ff7f6c7 --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/getrrsetbyname_async.c @@ -0,0 +1,590 @@ +/* $OpenBSD: getrrsetbyname_async.c,v 1.11 2017/02/23 17:04:02 eric Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <netinet/in.h> +#include <arpa/nameser.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "asr_private.h" + +static int getrrsetbyname_async_run(struct asr_query *, struct asr_result *); +static void get_response(struct asr_result *, const char *, int); + +struct asr_query * +getrrsetbyname_async(const char *hostname, unsigned int rdclass, + unsigned int rdtype, unsigned int flags, void *asr) +{ + struct asr_ctx *ac; + struct asr_query *as; + + ac = _asr_use_resolver(asr); + if ((as = _asr_async_new(ac, ASR_GETRRSETBYNAME)) == NULL) + goto abort; /* errno set */ + as->as_run = getrrsetbyname_async_run; + + as->as.rrset.flags = flags; + as->as.rrset.class = rdclass; + as->as.rrset.type = rdtype; + as->as.rrset.name = strdup(hostname); + if (as->as.rrset.name == NULL) + goto abort; /* errno set */ + + _asr_ctx_unref(ac); + return (as); + abort: + if (as) + _asr_async_free(as); + + _asr_ctx_unref(ac); + return (NULL); +} +DEF_WEAK(getrrsetbyname_async); + +static int +getrrsetbyname_async_run(struct asr_query *as, struct asr_result *ar) +{ + next: + switch (as->as_state) { + + case ASR_STATE_INIT: + + /* Check for invalid class and type. */ + if (as->as.rrset.class > 0xffff || as->as.rrset.type > 0xffff) { + ar->ar_rrset_errno = ERRSET_INVAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* Do not allow queries of class or type ANY. */ + if (as->as.rrset.class == 0xff || as->as.rrset.type == 0xff) { + ar->ar_rrset_errno = ERRSET_INVAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* Do not allow flags yet, unimplemented. */ + if (as->as.rrset.flags) { + ar->ar_rrset_errno = ERRSET_INVAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* Create a delegate the lookup to a subquery. */ + as->as_subq = _res_query_async_ctx( + as->as.rrset.name, + as->as.rrset.class, + as->as.rrset.type, + as->as_ctx); + if (as->as_subq == NULL) { + ar->ar_rrset_errno = ERRSET_FAIL; + async_set_state(as, ASR_STATE_HALT); + break; + } + + async_set_state(as, ASR_STATE_SUBQUERY); + break; + + case ASR_STATE_SUBQUERY: + + if ((asr_run(as->as_subq, ar)) == ASYNC_COND) + return (ASYNC_COND); + + as->as_subq = NULL; + + /* No packet received.*/ + if (ar->ar_datalen == -1) { + switch (ar->ar_h_errno) { + case HOST_NOT_FOUND: + ar->ar_rrset_errno = ERRSET_NONAME; + break; + case NO_DATA: + ar->ar_rrset_errno = ERRSET_NODATA; + break; + default: + ar->ar_rrset_errno = ERRSET_FAIL; + break; + } + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* Got a packet but no answer. */ + if (ar->ar_count == 0) { + free(ar->ar_data); + switch (ar->ar_rcode) { + case NXDOMAIN: + ar->ar_rrset_errno = ERRSET_NONAME; + break; + case NOERROR: + ar->ar_rrset_errno = ERRSET_NODATA; + break; + default: + ar->ar_rrset_errno = ERRSET_FAIL; + break; + } + async_set_state(as, ASR_STATE_HALT); + break; + } + + get_response(ar, ar->ar_data, ar->ar_datalen); + free(ar->ar_data); + async_set_state(as, ASR_STATE_HALT); + break; + + case ASR_STATE_HALT: + if (ar->ar_rrset_errno) + ar->ar_rrsetinfo = NULL; + return (ASYNC_DONE); + + default: + ar->ar_rrset_errno = ERRSET_FAIL; + async_set_state(as, ASR_STATE_HALT); + break; + } + goto next; +} + +/* The rest of this file is taken from the orignal implementation. */ + +/* $OpenBSD: getrrsetbyname_async.c,v 1.11 2017/02/23 17:04:02 eric Exp $ */ + +/* + * Copyright (c) 2001 Jakob Schlyter. 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 AUTHOR ``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 AUTHOR 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. + */ + +/* + * Portions Copyright (c) 1999-2001 Internet Software Consortium. + * + * 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 INTERNET SOFTWARE CONSORTIUM + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * INTERNET SOFTWARE CONSORTIUM 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 MAXPACKET 1024*64 + +struct dns_query { + char *name; + u_int16_t type; + u_int16_t class; + struct dns_query *next; +}; + +struct dns_rr { + char *name; + u_int16_t type; + u_int16_t class; + u_int16_t ttl; + u_int16_t size; + void *rdata; + struct dns_rr *next; +}; + +struct dns_response { + HEADER header; + struct dns_query *query; + struct dns_rr *answer; + struct dns_rr *authority; + struct dns_rr *additional; +}; + +static struct dns_response *parse_dns_response(const u_char *, int); +static struct dns_query *parse_dns_qsection(const u_char *, int, + const u_char **, int); +static struct dns_rr *parse_dns_rrsection(const u_char *, int, const u_char **, + int); + +static void free_dns_query(struct dns_query *); +static void free_dns_rr(struct dns_rr *); +static void free_dns_response(struct dns_response *); + +static int count_dns_rr(struct dns_rr *, u_int16_t, u_int16_t); + +static void +get_response(struct asr_result *ar, const char *pkt, int pktlen) +{ + struct rrsetinfo *rrset = NULL; + struct dns_response *response = NULL; + struct dns_rr *rr; + struct rdatainfo *rdata; + unsigned int index_ans, index_sig; + + /* parse result */ + response = parse_dns_response(pkt, pktlen); + if (response == NULL) { + ar->ar_rrset_errno = ERRSET_FAIL; + goto fail; + } + + if (response->header.qdcount != 1) { + ar->ar_rrset_errno = ERRSET_FAIL; + goto fail; + } + + /* initialize rrset */ + rrset = calloc(1, sizeof(struct rrsetinfo)); + if (rrset == NULL) { + ar->ar_rrset_errno = ERRSET_NOMEMORY; + goto fail; + } + rrset->rri_rdclass = response->query->class; + rrset->rri_rdtype = response->query->type; + rrset->rri_ttl = response->answer->ttl; + rrset->rri_nrdatas = response->header.ancount; + + /* check for authenticated data */ + if (response->header.ad == 1) + rrset->rri_flags |= RRSET_VALIDATED; + + /* copy name from answer section */ + rrset->rri_name = strdup(response->answer->name); + if (rrset->rri_name == NULL) { + ar->ar_rrset_errno = ERRSET_NOMEMORY; + goto fail; + } + + /* count answers */ + rrset->rri_nrdatas = count_dns_rr(response->answer, rrset->rri_rdclass, + rrset->rri_rdtype); + rrset->rri_nsigs = count_dns_rr(response->answer, rrset->rri_rdclass, + T_RRSIG); + + /* allocate memory for answers */ + rrset->rri_rdatas = calloc(rrset->rri_nrdatas, + sizeof(struct rdatainfo)); + if (rrset->rri_rdatas == NULL) { + ar->ar_rrset_errno = ERRSET_NOMEMORY; + goto fail; + } + + /* allocate memory for signatures */ + rrset->rri_sigs = calloc(rrset->rri_nsigs, sizeof(struct rdatainfo)); + if (rrset->rri_sigs == NULL) { + ar->ar_rrset_errno = ERRSET_NOMEMORY; + goto fail; + } + + /* copy answers & signatures */ + for (rr = response->answer, index_ans = 0, index_sig = 0; + rr; rr = rr->next) { + + rdata = NULL; + + if (rr->class == rrset->rri_rdclass && + rr->type == rrset->rri_rdtype) + rdata = &rrset->rri_rdatas[index_ans++]; + + if (rr->class == rrset->rri_rdclass && + rr->type == T_RRSIG) + rdata = &rrset->rri_sigs[index_sig++]; + + if (rdata) { + rdata->rdi_length = rr->size; + rdata->rdi_data = malloc(rr->size); + + if (rdata->rdi_data == NULL) { + ar->ar_rrset_errno = ERRSET_NOMEMORY; + goto fail; + } + memcpy(rdata->rdi_data, rr->rdata, rr->size); + } + } + free_dns_response(response); + + ar->ar_rrsetinfo = rrset; + ar->ar_rrset_errno = ERRSET_SUCCESS; + return; + +fail: + if (rrset != NULL) + freerrset(rrset); + if (response != NULL) + free_dns_response(response); +} + +/* + * DNS response parsing routines + */ +static struct dns_response * +parse_dns_response(const u_char *answer, int size) +{ + struct dns_response *resp; + const u_char *cp; + + /* allocate memory for the response */ + resp = calloc(1, sizeof(*resp)); + if (resp == NULL) + return (NULL); + + /* initialize current pointer */ + cp = answer; + + /* copy header */ + memcpy(&resp->header, cp, HFIXEDSZ); + cp += HFIXEDSZ; + + /* fix header byte order */ + resp->header.qdcount = ntohs(resp->header.qdcount); + resp->header.ancount = ntohs(resp->header.ancount); + resp->header.nscount = ntohs(resp->header.nscount); + resp->header.arcount = ntohs(resp->header.arcount); + + /* there must be at least one query */ + if (resp->header.qdcount < 1) { + free_dns_response(resp); + return (NULL); + } + + /* parse query section */ + resp->query = parse_dns_qsection(answer, size, &cp, + resp->header.qdcount); + if (resp->header.qdcount && resp->query == NULL) { + free_dns_response(resp); + return (NULL); + } + + /* parse answer section */ + resp->answer = parse_dns_rrsection(answer, size, &cp, + resp->header.ancount); + if (resp->header.ancount && resp->answer == NULL) { + free_dns_response(resp); + return (NULL); + } + + /* parse authority section */ + resp->authority = parse_dns_rrsection(answer, size, &cp, + resp->header.nscount); + if (resp->header.nscount && resp->authority == NULL) { + free_dns_response(resp); + return (NULL); + } + + /* parse additional section */ + resp->additional = parse_dns_rrsection(answer, size, &cp, + resp->header.arcount); + if (resp->header.arcount && resp->additional == NULL) { + free_dns_response(resp); + return (NULL); + } + + return (resp); +} + +static struct dns_query * +parse_dns_qsection(const u_char *answer, int size, const u_char **cp, int count) +{ + struct dns_query *head, *curr, *prev; + int i, length; + char name[MAXDNAME]; + + for (i = 1, head = NULL, prev = NULL; i <= count; i++, prev = curr) { + + /* allocate and initialize struct */ + curr = calloc(1, sizeof(struct dns_query)); + if (curr == NULL) { + free_dns_query(head); + return (NULL); + } + if (head == NULL) + head = curr; + if (prev != NULL) + prev->next = curr; + + /* name */ + length = dn_expand(answer, answer + size, *cp, name, + sizeof(name)); + if (length < 0) { + free_dns_query(head); + return (NULL); + } + curr->name = strdup(name); + if (curr->name == NULL) { + free_dns_query(head); + return (NULL); + } + *cp += length; + + /* type */ + curr->type = _getshort(*cp); + *cp += INT16SZ; + + /* class */ + curr->class = _getshort(*cp); + *cp += INT16SZ; + } + + return (head); +} + +static struct dns_rr * +parse_dns_rrsection(const u_char *answer, int size, const u_char **cp, + int count) +{ + struct dns_rr *head, *curr, *prev; + int i, length; + char name[MAXDNAME]; + + for (i = 1, head = NULL, prev = NULL; i <= count; i++, prev = curr) { + + /* allocate and initialize struct */ + curr = calloc(1, sizeof(struct dns_rr)); + if (curr == NULL) { + free_dns_rr(head); + return (NULL); + } + if (head == NULL) + head = curr; + if (prev != NULL) + prev->next = curr; + + /* name */ + length = dn_expand(answer, answer + size, *cp, name, + sizeof(name)); + if (length < 0) { + free_dns_rr(head); + return (NULL); + } + curr->name = strdup(name); + if (curr->name == NULL) { + free_dns_rr(head); + return (NULL); + } + *cp += length; + + /* type */ + curr->type = _getshort(*cp); + *cp += INT16SZ; + + /* class */ + curr->class = _getshort(*cp); + *cp += INT16SZ; + + /* ttl */ + curr->ttl = _getlong(*cp); + *cp += INT32SZ; + + /* rdata size */ + curr->size = _getshort(*cp); + *cp += INT16SZ; + + /* rdata itself */ + curr->rdata = malloc(curr->size); + if (curr->rdata == NULL) { + free_dns_rr(head); + return (NULL); + } + memcpy(curr->rdata, *cp, curr->size); + *cp += curr->size; + } + + return (head); +} + +static void +free_dns_query(struct dns_query *p) +{ + if (p == NULL) + return; + + if (p->name) + free(p->name); + free_dns_query(p->next); + free(p); +} + +static void +free_dns_rr(struct dns_rr *p) +{ + if (p == NULL) + return; + + if (p->name) + free(p->name); + if (p->rdata) + free(p->rdata); + free_dns_rr(p->next); + free(p); +} + +static void +free_dns_response(struct dns_response *p) +{ + if (p == NULL) + return; + + free_dns_query(p->query); + free_dns_rr(p->answer); + free_dns_rr(p->authority); + free_dns_rr(p->additional); + free(p); +} + +static int +count_dns_rr(struct dns_rr *p, u_int16_t class, u_int16_t type) +{ + int n = 0; + + while (p) { + if (p->class == class && p->type == type) + n++; + p = p->next; + } + + return (n); +} diff --git a/foobar/portable/openbsd-compat/libasr/libasr.la b/foobar/portable/openbsd-compat/libasr/libasr.la new file mode 100644 index 00000000..71346e8a --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/libasr.la @@ -0,0 +1,41 @@ +# libasr.la - a libtool library file +# Generated by libtool (GNU libtool) 2.4.6 +# +# Please DO NOT delete this file! +# It is necessary for linking the library. + +# The name that we can dlopen(3). +dlname='libasr.0.dylib' + +# Names of this library. +library_names='libasr.0.dylib libasr.dylib' + +# The name of the static archive. +old_library='libasr.a' + +# Linker flags that cannot go in dependency_libs. +inherited_linker_flags=' ' + +# Libraries that this one depends upon. +dependency_libs=' -L/usr/local/Cellar/openssl@1.1/1.1.1d//lib -L/usr/local/lib -lz -lcrypto -lssl -levent -lresolv' + +# Names of additional weak libraries provided by this library +weak_library_names='' + +# Version information for libasr. +current=0 +age=0 +revision=3 + +# Is this an already installed library? +installed=no + +# Should we warn about portability when linking against -modules? +shouldnotlink=no + +# Files to dlopen/dlpreopen +dlopen='' +dlpreopen='' + +# Directory that this library needs to be installed in: +libdir='/tmp/lib' diff --git a/foobar/portable/openbsd-compat/libasr/res_debug.c b/foobar/portable/openbsd-compat/libasr/res_debug.c new file mode 100644 index 00000000..ca9c5ee0 --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/res_debug.c @@ -0,0 +1,2 @@ +/* $OpenBSD: res_debug.c,v 1.1 2012/09/08 11:08:21 eric Exp $ */ +/* NOTHING */ diff --git a/foobar/portable/openbsd-compat/libasr/res_init.c b/foobar/portable/openbsd-compat/libasr/res_init.c new file mode 100644 index 00000000..04243c47 --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/res_init.c @@ -0,0 +1,103 @@ +/* $OpenBSD: res_init.c,v 1.11 2019/06/17 05:54:45 otto Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/nameser.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <asr.h> +#include <resolv.h> +#include <string.h> + +#include "asr_private.h" +#include "thread_private.h" + + +struct __res_state _res; +struct __res_state_ext _res_ext; + +int h_errno; + +int +res_init(void) +{ + static void *resinit_mutex; + struct asr_ctx *ac; + int i; + + ac = _asr_use_resolver(NULL); + + /* + * The first thread to call res_init() will setup the global _res + * structure from the async context, not overriding fields set early + * by the user. + */ + _MUTEX_LOCK(&resinit_mutex); + if (!(_res.options & RES_INIT)) { + if (_res.retry == 0) + _res.retry = ac->ac_nsretries; + if (_res.retrans == 0) + _res.retrans = ac->ac_nstimeout; + if (_res.options == 0) + _res.options = ac->ac_options; + if (_res.lookups[0] == '\0') + strlcpy(_res.lookups, ac->ac_db, sizeof(_res.lookups)); + + for (i = 0; i < ac->ac_nscount && i < MAXNS; i++) { + /* + * No need to check for length since we copy to a + * struct sockaddr_storage with a size of 256 bytes + * and sa_len has only 8 bits. + */ + memcpy(&_res_ext.nsaddr_list[i], ac->ac_ns[i], + ac->ac_ns[i]->sa_len); + if (ac->ac_ns[i]->sa_len <= sizeof(_res.nsaddr_list[i])) + memcpy(&_res.nsaddr_list[i], ac->ac_ns[i], + ac->ac_ns[i]->sa_len); + else + memset(&_res.nsaddr_list[i], 0, + sizeof(_res.nsaddr_list[i])); + } + _res.nscount = i; + _res.options |= RES_INIT; + } + _MUTEX_UNLOCK(&resinit_mutex); + + /* + * If the program is not threaded, we want to reflect (some) changes + * made by the user to the global _res structure. + * This is a bit of a hack: if there is already an async query on + * this context, it might change things in its back. It is ok + * as long as the user only uses the blocking resolver API. + * If needed we could consider cloning the context if there is + * a running query. + */ + if (!__isthreaded) { + ac->ac_nsretries = _res.retry; + ac->ac_nstimeout = _res.retrans; + ac->ac_options = _res.options; + strlcpy(ac->ac_db, _res.lookups, sizeof(ac->ac_db)); + ac->ac_dbcount = strlen(ac->ac_db); + } + + _asr_ctx_unref(ac); + + return (0); +} +DEF_WEAK(res_init); diff --git a/foobar/portable/openbsd-compat/libasr/res_mkquery.c b/foobar/portable/openbsd-compat/libasr/res_mkquery.c new file mode 100644 index 00000000..959ecc47 --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/res_mkquery.c @@ -0,0 +1,119 @@ +/* $OpenBSD: res_mkquery.c,v 1.13 2019/01/14 06:49:42 otto Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/nameser.h> /* for MAXDNAME */ +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> +#include <string.h> + +#include "asr_private.h" + +/* This function is apparently needed by some ports. */ +int +res_mkquery(int op, const char *dname, int class, int type, + const unsigned char *data, int datalen, const unsigned char *newrr, + unsigned char *buf, int buflen) +{ + struct asr_ctx *ac; + struct asr_pack p; + struct asr_dns_header h; + char fqdn[MAXDNAME]; + char dn[MAXDNAME]; + + /* we currently only support QUERY */ + if (op != QUERY || data) + return (-1); + + if (dname[0] == '\0' || dname[strlen(dname) - 1] != '.') { + if (strlcpy(fqdn, dname, sizeof(fqdn)) >= sizeof(fqdn) || + strlcat(fqdn, ".", sizeof(fqdn)) >= sizeof(fqdn)) + return (-1); + dname = fqdn; + } + + if (_asr_dname_from_fqdn(dname, dn, sizeof(dn)) == -1) + return (-1); + + ac = _asr_use_resolver(NULL); + + memset(&h, 0, sizeof h); + h.id = res_randomid(); + if (ac->ac_options & RES_RECURSE) + h.flags |= RD_MASK; +#ifdef RES_USE_CD + if (ac->ac_options & RES_USE_CD) + h.flags |= CD_MASK; +#endif + h.qdcount = 1; + if (ac->ac_options & (RES_USE_EDNS0 | RES_USE_DNSSEC)) + h.arcount = 1; + + _asr_pack_init(&p, buf, buflen); + _asr_pack_header(&p, &h); + _asr_pack_query(&p, type, class, dn); + if (ac->ac_options & (RES_USE_EDNS0 | RES_USE_DNSSEC)) + _asr_pack_edns0(&p, MAXPACKETSZ, + ac->ac_options & RES_USE_DNSSEC); + + _asr_ctx_unref(ac); + + if (p.err) + return (-1); + + return (p.offset); +} + +/* + * This function is not documented, but used by sendmail. + * Put here because it uses asr_private.h too. + */ +int +res_querydomain(const char *name, + const char *domain, + int class, + int type, + u_char *answer, + int anslen) +{ + char fqdn[MAXDNAME], ndom[MAXDNAME]; + size_t n; + + /* we really want domain to end with a dot for now */ + if (domain && ((n = strlen(domain)) == 0 || domain[n - 1 ] != '.')) { + if (strlcpy(ndom, domain, sizeof(ndom)) >= sizeof(ndom) || + strlcat(ndom, ".", sizeof(ndom)) >= sizeof(ndom)) { + h_errno = NETDB_INTERNAL; + errno = EINVAL; + return (-1); + } + domain = ndom; + } + + if (_asr_make_fqdn(name, domain, fqdn, sizeof fqdn) == 0) { + h_errno = NETDB_INTERNAL; + errno = EINVAL; + return (-1); + } + + return (res_query(fqdn, class, type, answer, anslen)); +} diff --git a/foobar/portable/openbsd-compat/libasr/res_query.c b/foobar/portable/openbsd-compat/libasr/res_query.c new file mode 100644 index 00000000..3f891416 --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/res_query.c @@ -0,0 +1,112 @@ +/* $OpenBSD: res_query.c,v 1.9 2015/10/05 02:57:16 guenther Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> +#include <string.h> +#include <stdlib.h> + +int +res_query(const char *name, int class, int type, u_char *ans, int anslen) +{ + struct asr_query *as; + struct asr_result ar; + size_t len; + + res_init(); + + if (ans == NULL || anslen <= 0) { + h_errno = NO_RECOVERY; + errno = EINVAL; + return (-1); + } + + as = res_query_async(name, class, type, NULL); + if (as == NULL) { + if (errno == EINVAL) + h_errno = NO_RECOVERY; + else + h_errno = NETDB_INTERNAL; + return (-1); /* errno set */ + } + + asr_run_sync(as, &ar); + + if (ar.ar_errno) + errno = ar.ar_errno; + h_errno = ar.ar_h_errno; + + if (ar.ar_h_errno != NETDB_SUCCESS) + return (-1); + + len = anslen; + if (ar.ar_datalen < len) + len = ar.ar_datalen; + memmove(ans, ar.ar_data, len); + free(ar.ar_data); + + return (ar.ar_datalen); +} +DEF_WEAK(res_query); + +int +res_search(const char *name, int class, int type, u_char *ans, int anslen) +{ + struct asr_query *as; + struct asr_result ar; + size_t len; + + res_init(); + + if (ans == NULL || anslen <= 0) { + h_errno = NO_RECOVERY; + errno = EINVAL; + return (-1); + } + + as = res_search_async(name, class, type, NULL); + if (as == NULL) { + if (errno == EINVAL) + h_errno = NO_RECOVERY; + else + h_errno = NETDB_INTERNAL; + return (-1); /* errno set */ + } + + asr_run_sync(as, &ar); + + if (ar.ar_errno) + errno = ar.ar_errno; + h_errno = ar.ar_h_errno; + + if (ar.ar_h_errno != NETDB_SUCCESS) + return (-1); + + len = anslen; + if (ar.ar_datalen < len) + len = ar.ar_datalen; + memmove(ans, ar.ar_data, len); + free(ar.ar_data); + + return (ar.ar_datalen); +} diff --git a/foobar/portable/openbsd-compat/libasr/res_search_async.c b/foobar/portable/openbsd-compat/libasr/res_search_async.c new file mode 100644 index 00000000..6436ab85 --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/res_search_async.c @@ -0,0 +1,327 @@ +/* $OpenBSD: res_search_async.c,v 1.21 2017/02/27 10:44:46 jca Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <arpa/nameser.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "asr_private.h" + +static int res_search_async_run(struct asr_query *, struct asr_result *); +static size_t domcat(const char *, const char *, char *, size_t); + +/* + * Unlike res_query_async(), this function returns a valid packet only if + * h_errno is NETDB_SUCCESS. + */ +struct asr_query * +res_search_async(const char *name, int class, int type, void *asr) +{ + struct asr_ctx *ac; + struct asr_query *as; + + DPRINT("asr: res_search_async(\"%s\", %i, %i)\n", name, class, type); + + ac = _asr_use_resolver(asr); + as = _res_search_async_ctx(name, class, type, ac); + _asr_ctx_unref(ac); + + return (as); +} +DEF_WEAK(res_search_async); + +struct asr_query * +_res_search_async_ctx(const char *name, int class, int type, struct asr_ctx *ac) +{ + struct asr_query *as; + + DPRINT("asr: res_search_async_ctx(\"%s\", %i, %i)\n", name, class, + type); + + if ((as = _asr_async_new(ac, ASR_SEARCH)) == NULL) + goto err; /* errno set */ + as->as_run = res_search_async_run; + if ((as->as.search.name = strdup(name)) == NULL) + goto err; /* errno set */ + + as->as.search.class = class; + as->as.search.type = type; + + return (as); + err: + if (as) + _asr_async_free(as); + return (NULL); +} + +#define HERRNO_UNSET -2 + +static int +res_search_async_run(struct asr_query *as, struct asr_result *ar) +{ + int r; + char fqdn[MAXDNAME]; + + next: + switch (as->as_state) { + + case ASR_STATE_INIT: + + if (as->as.search.name[0] == '\0') { + ar->ar_h_errno = NO_DATA; + async_set_state(as, ASR_STATE_HALT); + break; + } + + as->as.search.saved_h_errno = HERRNO_UNSET; + async_set_state(as, ASR_STATE_NEXT_DOMAIN); + break; + + case ASR_STATE_NEXT_DOMAIN: + /* + * Reset flags to be able to identify the case in + * STATE_SUBQUERY. + */ + as->as_dom_flags = 0; + + r = _asr_iter_domain(as, as->as.search.name, fqdn, sizeof(fqdn)); + if (r == -1) { + async_set_state(as, ASR_STATE_NOT_FOUND); + break; + } + if (r == 0) { + ar->ar_errno = EINVAL; + ar->ar_h_errno = NO_RECOVERY; + ar->ar_datalen = -1; + ar->ar_data = NULL; + async_set_state(as, ASR_STATE_HALT); + break; + } + as->as_subq = _res_query_async_ctx(fqdn, + as->as.search.class, as->as.search.type, as->as_ctx); + if (as->as_subq == NULL) { + ar->ar_errno = errno; + if (errno == EINVAL) + ar->ar_h_errno = NO_RECOVERY; + else + ar->ar_h_errno = NETDB_INTERNAL; + ar->ar_datalen = -1; + ar->ar_data = NULL; + async_set_state(as, ASR_STATE_HALT); + break; + } + async_set_state(as, ASR_STATE_SUBQUERY); + break; + + case ASR_STATE_SUBQUERY: + + if ((r = asr_run(as->as_subq, ar)) == ASYNC_COND) + return (ASYNC_COND); + as->as_subq = NULL; + + if (ar->ar_h_errno == NETDB_SUCCESS) { + async_set_state(as, ASR_STATE_HALT); + break; + } + + /* + * The original res_search() does this in the domain search + * loop, but only for ECONNREFUSED. I think we can do better + * because technically if we get an errno, it means + * we couldn't reach any nameserver, so there is no point + * in trying further. + */ + if (ar->ar_errno) { + async_set_state(as, ASR_STATE_HALT); + break; + } + + free(ar->ar_data); + + /* + * The original resolver does something like this. + */ + if (as->as_dom_flags & ASYNC_DOM_NDOTS) + as->as.search.saved_h_errno = ar->ar_h_errno; + + if (as->as_dom_flags & ASYNC_DOM_DOMAIN) { + if (ar->ar_h_errno == NO_DATA) + as->as_flags |= ASYNC_NODATA; + else if (ar->ar_h_errno == TRY_AGAIN) + as->as_flags |= ASYNC_AGAIN; + } + + async_set_state(as, ASR_STATE_NEXT_DOMAIN); + break; + + case ASR_STATE_NOT_FOUND: + + if (as->as.search.saved_h_errno != HERRNO_UNSET) + ar->ar_h_errno = as->as.search.saved_h_errno; + else if (as->as_flags & ASYNC_NODATA) + ar->ar_h_errno = NO_DATA; + else if (as->as_flags & ASYNC_AGAIN) + ar->ar_h_errno = TRY_AGAIN; + /* + * Else, we got the ar_h_errno value set by res_query_async() + * for the last domain. + */ + ar->ar_datalen = -1; + ar->ar_data = NULL; + async_set_state(as, ASR_STATE_HALT); + break; + + case ASR_STATE_HALT: + + return (ASYNC_DONE); + + default: + ar->ar_errno = EOPNOTSUPP; + ar->ar_h_errno = NETDB_INTERNAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + goto next; +} + +/* + * Concatenate a name and a domain name. The result has no trailing dot. + * Return the resulting string length, or 0 in case of error. + */ +static size_t +domcat(const char *name, const char *domain, char *buf, size_t buflen) +{ + size_t r; + + r = _asr_make_fqdn(name, domain, buf, buflen); + if (r == 0) + return (0); + buf[r - 1] = '\0'; + + return (r - 1); +} + +enum { + DOM_INIT, + DOM_DOMAIN, + DOM_DONE +}; + +/* + * Implement the search domain strategy. + * + * This function works as a generator that constructs complete domains in + * buffer "buf" of size "len" for the given host name "name", according to the + * search rules defined by the resolving context. It is supposed to be called + * multiple times (with the same name) to generate the next possible domain + * name, if any. + * + * It returns -1 if all possibilities have been exhausted, 0 if there was an + * error generating the next name, or the resulting name length. + */ +int +_asr_iter_domain(struct asr_query *as, const char *name, char * buf, size_t len) +{ + const char *c; + int dots; + + switch (as->as_dom_step) { + + case DOM_INIT: + /* First call */ + + /* + * If "name" is an FQDN, that's the only result and we + * don't try anything else. + */ + if (strlen(name) && name[strlen(name) - 1] == '.') { + DPRINT("asr: iter_domain(\"%s\") fqdn\n", name); + as->as_dom_flags |= ASYNC_DOM_FQDN; + as->as_dom_step = DOM_DONE; + return (domcat(name, NULL, buf, len)); + } + + /* + * Otherwise, we iterate through the specified search domains. + */ + as->as_dom_step = DOM_DOMAIN; + as->as_dom_idx = 0; + + /* + * If "name" as enough dots, use it as-is first, as indicated + * in resolv.conf(5). + */ + dots = 0; + for (c = name; *c; c++) + dots += (*c == '.'); + if (dots >= as->as_ctx->ac_ndots) { + DPRINT("asr: iter_domain(\"%s\") ndots\n", name); + as->as_dom_flags |= ASYNC_DOM_NDOTS; + if (strlcpy(buf, name, len) >= len) + return (0); + return (strlen(buf)); + } + /* Otherwise, starts using the search domains */ + /* FALLTHROUGH */ + + case DOM_DOMAIN: + if (as->as_dom_idx < as->as_ctx->ac_domcount && + (as->as_ctx->ac_options & RES_DNSRCH || ( + as->as_ctx->ac_options & RES_DEFNAMES && + as->as_dom_idx == 0 && + strchr(name, '.') == NULL))) { + DPRINT("asr: iter_domain(\"%s\") domain \"%s\"\n", + name, as->as_ctx->ac_dom[as->as_dom_idx]); + as->as_dom_flags |= ASYNC_DOM_DOMAIN; + return (domcat(name, + as->as_ctx->ac_dom[as->as_dom_idx++], buf, len)); + } + + /* No more domain to try. */ + + as->as_dom_step = DOM_DONE; + + /* + * If the name was not tried as an absolute name before, + * do it now. + */ + if (!(as->as_dom_flags & ASYNC_DOM_NDOTS)) { + DPRINT("asr: iter_domain(\"%s\") as is\n", name); + as->as_dom_flags |= ASYNC_DOM_ASIS; + if (strlcpy(buf, name, len) >= len) + return (0); + return (strlen(buf)); + } + /* Otherwise, we are done. */ + + case DOM_DONE: + default: + DPRINT("asr: iter_domain(\"%s\") done\n", name); + return (-1); + } +} diff --git a/foobar/portable/openbsd-compat/libasr/res_send.c b/foobar/portable/openbsd-compat/libasr/res_send.c new file mode 100644 index 00000000..32c94081 --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/res_send.c @@ -0,0 +1,61 @@ +/* $OpenBSD: res_send.c,v 1.8 2014/03/26 18:13:15 eric Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <resolv.h> +#include <string.h> +#include <stdlib.h> + +int +res_send(const u_char *buf, int buflen, u_char *ans, int anslen) +{ + struct asr_query *as; + struct asr_result ar; + size_t len; + + res_init(); + + if (ans == NULL || anslen <= 0) { + errno = EINVAL; + return (-1); + } + + as = res_send_async(buf, buflen, NULL); + if (as == NULL) + return (-1); /* errno set */ + + asr_run_sync(as, &ar); + + if (ar.ar_errno) { + errno = ar.ar_errno; + return (-1); + } + + len = anslen; + if (ar.ar_datalen < len) + len = ar.ar_datalen; + memmove(ans, ar.ar_data, len); + free(ar.ar_data); + + return (ar.ar_datalen); +} diff --git a/foobar/portable/openbsd-compat/libasr/res_send_async.c b/foobar/portable/openbsd-compat/libasr/res_send_async.c new file mode 100644 index 00000000..7eeeef48 --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/res_send_async.c @@ -0,0 +1,806 @@ +/* $OpenBSD: res_send_async.c,v 1.39 2019/09/28 11:21:07 eric Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <netinet/in.h> +#include <arpa/nameser.h> +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +#include <arpa/nameser_compat.h> +#endif +#include <netdb.h> + +#include <asr.h> +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <resolv.h> /* for res_random */ +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "asr_private.h" + +#define OP_QUERY (0) + +static int res_send_async_run(struct asr_query *, struct asr_result *); +static int sockaddr_connect(const struct sockaddr *, int); +static int udp_send(struct asr_query *); +static int udp_recv(struct asr_query *); +static int tcp_write(struct asr_query *); +static int tcp_read(struct asr_query *); +static int validate_packet(struct asr_query *); +static int setup_query(struct asr_query *, const char *, const char *, int, int); +static int ensure_ibuf(struct asr_query *, size_t); +static int iter_ns(struct asr_query *); + +#define AS_NS_SA(p) ((p)->as_ctx->ac_ns[(p)->as.dns.nsidx - 1]) + + +struct asr_query * +res_send_async(const unsigned char *buf, int buflen, void *asr) +{ + struct asr_ctx *ac; + struct asr_query *as; + struct asr_unpack p; + struct asr_dns_header h; + struct asr_dns_query q; + + DPRINT_PACKET("asr: res_send_async()", buf, buflen); + + ac = _asr_use_resolver(asr); + if ((as = _asr_async_new(ac, ASR_SEND)) == NULL) { + _asr_ctx_unref(ac); + return (NULL); /* errno set */ + } + as->as_run = res_send_async_run; + + as->as_flags |= ASYNC_EXTOBUF; + as->as.dns.obuf = (unsigned char *)buf; + as->as.dns.obuflen = buflen; + as->as.dns.obufsize = buflen; + + _asr_unpack_init(&p, buf, buflen); + _asr_unpack_header(&p, &h); + _asr_unpack_query(&p, &q); + if (p.err) { + errno = EINVAL; + goto err; + } + as->as.dns.reqid = h.id; + as->as.dns.type = q.q_type; + as->as.dns.class = q.q_class; + as->as.dns.dname = strdup(q.q_dname); + if (as->as.dns.dname == NULL) + goto err; /* errno set */ + + _asr_ctx_unref(ac); + return (as); + err: + if (as) + _asr_async_free(as); + _asr_ctx_unref(ac); + return (NULL); +} +DEF_WEAK(res_send_async); + +/* + * Unlike res_query(), this version will actually return the packet + * if it has received a valid one (errno == 0) even if h_errno is + * not NETDB_SUCCESS. So the packet *must* be freed if necessary. + */ +struct asr_query * +res_query_async(const char *name, int class, int type, void *asr) +{ + struct asr_ctx *ac; + struct asr_query *as; + + DPRINT("asr: res_query_async(\"%s\", %i, %i)\n", name, class, type); + + ac = _asr_use_resolver(asr); + as = _res_query_async_ctx(name, class, type, ac); + _asr_ctx_unref(ac); + + return (as); +} +DEF_WEAK(res_query_async); + +struct asr_query * +_res_query_async_ctx(const char *name, int class, int type, struct asr_ctx *a_ctx) +{ + struct asr_query *as; + + DPRINT("asr: res_query_async_ctx(\"%s\", %i, %i)\n", name, class, type); + + if ((as = _asr_async_new(a_ctx, ASR_SEND)) == NULL) + return (NULL); /* errno set */ + as->as_run = res_send_async_run; + + /* This adds a "." to name if it doesn't already has one. + * That's how res_query() behaves (through res_mkquery"). + */ + if (setup_query(as, name, NULL, class, type) == -1) + goto err; /* errno set */ + + return (as); + + err: + if (as) + _asr_async_free(as); + + return (NULL); +} + +static int +res_send_async_run(struct asr_query *as, struct asr_result *ar) +{ + next: + switch (as->as_state) { + + case ASR_STATE_INIT: + + if (as->as_ctx->ac_nscount == 0) { + ar->ar_errno = ECONNREFUSED; + async_set_state(as, ASR_STATE_HALT); + break; + } + + async_set_state(as, ASR_STATE_NEXT_NS); + break; + + case ASR_STATE_NEXT_NS: + + if (iter_ns(as) == -1) { + ar->ar_errno = ETIMEDOUT; + async_set_state(as, ASR_STATE_HALT); + break; + } + + if (as->as_ctx->ac_options & RES_USEVC || + as->as.dns.obuflen > PACKETSZ) + async_set_state(as, ASR_STATE_TCP_WRITE); + else + async_set_state(as, ASR_STATE_UDP_SEND); + break; + + case ASR_STATE_UDP_SEND: + + if (udp_send(as) == -1) { + async_set_state(as, ASR_STATE_NEXT_NS); + break; + } + async_set_state(as, ASR_STATE_UDP_RECV); + ar->ar_cond = ASR_WANT_READ; + ar->ar_fd = as->as_fd; + ar->ar_timeout = as->as_timeout; + return (ASYNC_COND); + break; + + case ASR_STATE_UDP_RECV: + + if (udp_recv(as) == -1) { + if (errno == ENOMEM) { + ar->ar_errno = errno; + async_set_state(as, ASR_STATE_HALT); + break; + } + if (errno != EOVERFLOW) { + /* Fail or timeout */ + async_set_state(as, ASR_STATE_NEXT_NS); + break; + } + if (as->as_ctx->ac_options & RES_IGNTC) + async_set_state(as, ASR_STATE_PACKET); + else + async_set_state(as, ASR_STATE_TCP_WRITE); + } else + async_set_state(as, ASR_STATE_PACKET); + break; + + case ASR_STATE_TCP_WRITE: + + switch (tcp_write(as)) { + case -1: /* fail or timeout */ + async_set_state(as, ASR_STATE_NEXT_NS); + break; + case 0: + async_set_state(as, ASR_STATE_TCP_READ); + ar->ar_cond = ASR_WANT_READ; + ar->ar_fd = as->as_fd; + ar->ar_timeout = as->as_timeout; + return (ASYNC_COND); + case 1: + ar->ar_cond = ASR_WANT_WRITE; + ar->ar_fd = as->as_fd; + ar->ar_timeout = as->as_timeout; + return (ASYNC_COND); + } + break; + + case ASR_STATE_TCP_READ: + + switch (tcp_read(as)) { + case -1: /* Fail or timeout */ + if (errno == ENOMEM) { + ar->ar_errno = errno; + async_set_state(as, ASR_STATE_HALT); + } else + async_set_state(as, ASR_STATE_NEXT_NS); + break; + case 0: + async_set_state(as, ASR_STATE_PACKET); + break; + case 1: + ar->ar_cond = ASR_WANT_READ; + ar->ar_fd = as->as_fd; + ar->ar_timeout = as->as_timeout; + return (ASYNC_COND); + } + break; + + case ASR_STATE_PACKET: + + memmove(&ar->ar_ns, AS_NS_SA(as), SA_LEN(AS_NS_SA(as))); + ar->ar_datalen = as->as.dns.ibuflen; + ar->ar_data = as->as.dns.ibuf; + as->as.dns.ibuf = NULL; + ar->ar_errno = 0; + ar->ar_rcode = as->as.dns.rcode; + async_set_state(as, ASR_STATE_HALT); + break; + + case ASR_STATE_HALT: + + if (ar->ar_errno) { + ar->ar_h_errno = TRY_AGAIN; + ar->ar_count = 0; + ar->ar_datalen = -1; + ar->ar_data = NULL; + } else if (as->as.dns.ancount) { + ar->ar_h_errno = NETDB_SUCCESS; + ar->ar_count = as->as.dns.ancount; + } else { + ar->ar_count = 0; + switch (as->as.dns.rcode) { + case NXDOMAIN: + ar->ar_h_errno = HOST_NOT_FOUND; + break; + case SERVFAIL: + ar->ar_h_errno = TRY_AGAIN; + break; + case NOERROR: + ar->ar_h_errno = NO_DATA; + break; + default: + ar->ar_h_errno = NO_RECOVERY; + } + } + return (ASYNC_DONE); + + default: + + ar->ar_errno = EOPNOTSUPP; + ar->ar_h_errno = NETDB_INTERNAL; + async_set_state(as, ASR_STATE_HALT); + break; + } + goto next; +} + +static int +sockaddr_connect(const struct sockaddr *sa, int socktype) +{ + int errno_save, sock, flags; + + if ((sock = socket(sa->sa_family, socktype, 0)) == -1) + goto fail; + + if ((flags = fcntl(sock, F_GETFL, 0)) == -1) + goto fail; + + flags |= O_NONBLOCK; + + if ((flags = fcntl(sock, F_SETFL, flags)) == -1) + goto fail; + + if (connect(sock, sa, SA_LEN(sa)) == -1) { + /* + * In the TCP case, the caller will be asked to poll for + * POLLOUT so that we start writing the packet in tcp_write() + * when the connection is established, or fail there on error. + */ + if (errno == EINPROGRESS) + return (sock); + goto fail; + } + + return (sock); + + fail: + + if (sock != -1) { + errno_save = errno; + close(sock); + errno = errno_save; + } + + return (-1); +} + +/* + * Prepare the DNS packet for the query type "type", class "class" and domain + * name created by the concatenation on "name" and "dom". + * Return 0 on success, set errno and return -1 on error. + */ +static int +setup_query(struct asr_query *as, const char *name, const char *dom, + int class, int type) +{ + struct asr_pack p; + struct asr_dns_header h; + char fqdn[MAXDNAME]; + char dname[MAXDNAME]; + + if (as->as_flags & ASYNC_EXTOBUF) { + errno = EINVAL; + DPRINT("attempting to write in user packet"); + return (-1); + } + + if (_asr_make_fqdn(name, dom, fqdn, sizeof(fqdn)) > sizeof(fqdn)) { + errno = EINVAL; + DPRINT("asr_make_fqdn: name too long\n"); + return (-1); + } + + if (_asr_dname_from_fqdn(fqdn, dname, sizeof(dname)) == -1) { + errno = EINVAL; + DPRINT("asr_dname_from_fqdn: invalid\n"); + return (-1); + } + + if (as->as.dns.obuf == NULL) { + as->as.dns.obufsize = PACKETSZ; + as->as.dns.obuf = malloc(as->as.dns.obufsize); + if (as->as.dns.obuf == NULL) + return (-1); /* errno set */ + } + as->as.dns.obuflen = 0; + + memset(&h, 0, sizeof h); + h.id = res_randomid(); + if (as->as_ctx->ac_options & RES_RECURSE) + h.flags |= RD_MASK; +#ifdef RES_USE_CD + if (as->as_ctx->ac_options & RES_USE_CD) + h.flags |= CD_MASK; +#endif + h.qdcount = 1; + if (as->as_ctx->ac_options & (RES_USE_EDNS0 | RES_USE_DNSSEC)) + h.arcount = 1; + + _asr_pack_init(&p, as->as.dns.obuf, as->as.dns.obufsize); + _asr_pack_header(&p, &h); + _asr_pack_query(&p, type, class, dname); + if (as->as_ctx->ac_options & (RES_USE_EDNS0 | RES_USE_DNSSEC)) + _asr_pack_edns0(&p, MAXPACKETSZ, + as->as_ctx->ac_options & RES_USE_DNSSEC); + if (p.err) { + DPRINT("error packing query"); + errno = EINVAL; + return (-1); + } + + /* Remember the parameters. */ + as->as.dns.reqid = h.id; + as->as.dns.type = type; + as->as.dns.class = class; + if (as->as.dns.dname) + free(as->as.dns.dname); + as->as.dns.dname = strdup(dname); + if (as->as.dns.dname == NULL) { + DPRINT("strdup"); + return (-1); /* errno set */ + } + as->as.dns.obuflen = p.offset; + + DPRINT_PACKET("asr_setup_query", as->as.dns.obuf, as->as.dns.obuflen); + + return (0); +} + +/* + * Create a connect UDP socket and send the output packet. + * + * Return 0 on success, or -1 on error (errno set). + */ +static int +udp_send(struct asr_query *as) +{ + ssize_t n; + int save_errno; +#ifdef DEBUG + char buf[256]; +#endif + + DPRINT("asr: [%p] connecting to %s UDP\n", as, + _asr_print_sockaddr(AS_NS_SA(as), buf, sizeof buf)); + + as->as_fd = sockaddr_connect(AS_NS_SA(as), SOCK_DGRAM); + if (as->as_fd == -1) + return (-1); /* errno set */ + + n = send(as->as_fd, as->as.dns.obuf, as->as.dns.obuflen, 0); + if (n == -1) { + save_errno = errno; + close(as->as_fd); + errno = save_errno; + as->as_fd = -1; + return (-1); + } + + return (0); +} + +/* + * Try to receive a valid packet from the current UDP socket. + * + * Return 0 if a full packet could be read, or -1 on error (errno set). + */ +static int +udp_recv(struct asr_query *as) +{ + ssize_t n; + int save_errno; + + if (ensure_ibuf(as, MAXPACKETSZ) == -1) { + save_errno = errno; + close(as->as_fd); + errno = save_errno; + as->as_fd = -1; + return (-1); + } + + n = recv(as->as_fd, as->as.dns.ibuf, as->as.dns.ibufsize, 0); + save_errno = errno; + close(as->as_fd); + errno = save_errno; + as->as_fd = -1; + if (n == -1) + return (-1); + + as->as.dns.ibuflen = n; + + DPRINT_PACKET("asr_udp_recv()", as->as.dns.ibuf, as->as.dns.ibuflen); + + if (validate_packet(as) == -1) + return (-1); /* errno set */ + + return (0); +} + +/* + * Write the output packet to the TCP socket. + * + * Return 0 when all bytes have been sent, 1 there is no buffer space on the + * socket or it is not connected yet, or -1 on error (errno set). + */ +static int +tcp_write(struct asr_query *as) +{ + struct msghdr msg; + struct iovec iov[2]; + uint16_t len; + ssize_t n; + size_t offset; + int i; +#ifdef DEBUG + char buf[256]; +#endif + + /* First try to connect if not already */ + if (as->as_fd == -1) { + DPRINT("asr: [%p] connecting to %s TCP\n", as, + _asr_print_sockaddr(AS_NS_SA(as), buf, sizeof buf)); + as->as_fd = sockaddr_connect(AS_NS_SA(as), SOCK_STREAM); + if (as->as_fd == -1) + return (-1); /* errno set */ +/* + * Some systems (MacOS X) have SO_NOSIGPIPE instead of MSG_NOSIGNAL. + * If neither is available the system is probably broken. We might + * want to detect this at configure time. + */ +#ifdef SO_NOSIGPIPE + i = 1; + if (setsockopt(as->as_fd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&i, + sizeof(i)) == -1) + return (-1); /* errno set */ +#endif + as->as.dns.datalen = 0; /* bytes sent */ + return (1); + } + + i = 0; + + /* Prepend de packet length if not sent already. */ + if (as->as.dns.datalen < sizeof(len)) { + offset = 0; + len = htons(as->as.dns.obuflen); + iov[i].iov_base = (char *)(&len) + as->as.dns.datalen; + iov[i].iov_len = sizeof(len) - as->as.dns.datalen; + i++; + } else + offset = as->as.dns.datalen - sizeof(len); + + iov[i].iov_base = as->as.dns.obuf + offset; + iov[i].iov_len = as->as.dns.obuflen - offset; + i++; + + memset(&msg, 0, sizeof msg); + msg.msg_iov = iov; + msg.msg_iovlen = i; + + send_again: +/* See above. */ +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif + n = sendmsg(as->as_fd, &msg, MSG_NOSIGNAL); + if (n == -1) { + if (errno == EINTR) + goto send_again; + goto close; /* errno set */ + } + + as->as.dns.datalen += n; + + if (as->as.dns.datalen == as->as.dns.obuflen + sizeof(len)) { + /* All sent. Prepare for TCP read */ + as->as.dns.datalen = 0; + return (0); + } + + /* More data to write */ + return (1); + +close: + close(as->as_fd); + as->as_fd = -1; + return (-1); +} + +/* + * Try to read a valid packet from the current TCP socket. + * + * Return 0 if a full packet could be read, 1 if more data is needed and the + * socket must be read again, or -1 on error (errno set). + */ +static int +tcp_read(struct asr_query *as) +{ + ssize_t n; + size_t offset, len; + char *pos; + int save_errno, nfds; + struct pollfd pfd; + + /* We must read the packet len first */ + if (as->as.dns.datalen < sizeof(as->as.dns.pktlen)) { + + pos = (char *)(&as->as.dns.pktlen) + as->as.dns.datalen; + len = sizeof(as->as.dns.pktlen) - as->as.dns.datalen; + + read_again0: + n = read(as->as_fd, pos, len); + if (n == -1) { + if (errno == EINTR) + goto read_again0; + goto close; /* errno set */ + } + if (n == 0) { + errno = ECONNRESET; + goto close; + } + as->as.dns.datalen += n; + if (as->as.dns.datalen < sizeof(as->as.dns.pktlen)) + return (1); /* need more data */ + + as->as.dns.ibuflen = ntohs(as->as.dns.pktlen); + if (ensure_ibuf(as, as->as.dns.ibuflen) == -1) + goto close; /* errno set */ + + pfd.fd = as->as_fd; + pfd.events = POLLIN; + poll_again: + nfds = poll(&pfd, 1, 0); + if (nfds == -1) { + if (errno == EINTR) + goto poll_again; + goto close; /* errno set */ + } + if (nfds == 0) + return (1); /* no more data available */ + } + + offset = as->as.dns.datalen - sizeof(as->as.dns.pktlen); + pos = as->as.dns.ibuf + offset; + len = as->as.dns.ibuflen - offset; + + read_again: + n = read(as->as_fd, pos, len); + if (n == -1) { + if (errno == EINTR) + goto read_again; + goto close; /* errno set */ + } + if (n == 0) { + errno = ECONNRESET; + goto close; + } + as->as.dns.datalen += n; + + /* See if we got all the advertised bytes. */ + if (as->as.dns.datalen != as->as.dns.ibuflen + sizeof(as->as.dns.pktlen)) + return (1); + + DPRINT_PACKET("asr_tcp_read()", as->as.dns.ibuf, as->as.dns.ibuflen); + + if (validate_packet(as) == -1) + goto close; /* errno set */ + + errno = 0; +close: + save_errno = errno; + close(as->as_fd); + errno = save_errno; + as->as_fd = -1; + return (errno ? -1 : 0); +} + +/* + * Make sure the input buffer is at least "n" bytes long, and allocate or + * extend it if necessary. Return 0 on success, or set errno and return -1. + */ +static int +ensure_ibuf(struct asr_query *as, size_t n) +{ + char *t; + + if (as->as.dns.ibufsize >= n) + return (0); + + t = recallocarray(as->as.dns.ibuf, as->as.dns.ibufsize, n, 1); + if (t == NULL) + return (-1); /* errno set */ + as->as.dns.ibuf = t; + as->as.dns.ibufsize = n; + + return (0); +} + +/* + * Check if the received packet is valid. + * Return 0 on success, or set errno and return -1. + */ +static int +validate_packet(struct asr_query *as) +{ + struct asr_unpack p; + struct asr_dns_header h; + struct asr_dns_query q; + struct asr_dns_rr rr; + int r; + + _asr_unpack_init(&p, as->as.dns.ibuf, as->as.dns.ibuflen); + + _asr_unpack_header(&p, &h); + if (p.err) + goto inval; + + if (h.id != as->as.dns.reqid) { + DPRINT("incorrect reqid\n"); + goto inval; + } + if (h.qdcount != 1) + goto inval; + /* Should be zero, we could allow this */ + if ((h.flags & Z_MASK) != 0) + goto inval; + /* Actually, it depends on the request but we only use OP_QUERY */ + if (OPCODE(h.flags) != OP_QUERY) + goto inval; + /* Must be a response */ + if ((h.flags & QR_MASK) == 0) + goto inval; + + as->as.dns.rcode = RCODE(h.flags); + as->as.dns.ancount = h.ancount; + + _asr_unpack_query(&p, &q); + if (p.err) + goto inval; + + if (q.q_type != as->as.dns.type || + q.q_class != as->as.dns.class || + strcasecmp(q.q_dname, as->as.dns.dname)) { + DPRINT("incorrect type/class/dname '%s' != '%s'\n", + q.q_dname, as->as.dns.dname); + goto inval; + } + + /* Check for truncation */ + if (h.flags & TC_MASK && !(as->as_ctx->ac_options & RES_IGNTC)) { + DPRINT("truncated\n"); + errno = EOVERFLOW; + return (-1); + } + + /* Validate the rest of the packet */ + for (r = h.ancount + h.nscount + h.arcount; r; r--) + _asr_unpack_rr(&p, &rr); + + /* Report any error found when unpacking the RRs. */ + if (p.err) { + DPRINT("unpack: %s\n", strerror(p.err)); + errno = p.err; + return (-1); + } + + if (p.offset != as->as.dns.ibuflen) { + DPRINT("trailing garbage\n"); + errno = EMSGSIZE; + return (-1); + } + + return (0); + + inval: + errno = EINVAL; + return (-1); +} + +/* + * Set the async context nameserver index to the next nameserver, cycling + * over the list until the maximum retry counter is reached. Return 0 on + * success, or -1 if all nameservers were used. + */ +static int +iter_ns(struct asr_query *as) +{ + for (;;) { + if (as->as.dns.nsloop >= as->as_ctx->ac_nsretries) + return (-1); + + as->as.dns.nsidx += 1; + if (as->as.dns.nsidx <= as->as_ctx->ac_nscount) + break; + as->as.dns.nsidx = 0; + as->as.dns.nsloop++; + DPRINT("asr: iter_ns(): cycle %i\n", as->as.dns.nsloop); + } + + as->as_timeout = 1000 * (as->as_ctx->ac_nstimeout << as->as.dns.nsloop); + if (as->as.dns.nsloop > 0) + as->as_timeout /= as->as_ctx->ac_nscount; + if (as->as_timeout < 1000) + as->as_timeout = 1000; + + return (0); +} diff --git a/foobar/portable/openbsd-compat/libasr/sethostent.c b/foobar/portable/openbsd-compat/libasr/sethostent.c new file mode 100644 index 00000000..61fa3e2f --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/sethostent.c @@ -0,0 +1,36 @@ +/* $OpenBSD: sethostent.c,v 1.2 2018/04/28 15:09:35 schwarze Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <netdb.h> +#include <stddef.h> + +void +sethostent(int stayopen) +{ +} + +void +endhostent(void) +{ +} + +struct hostent * +gethostent(void) +{ + h_errno = NETDB_INTERNAL; + return NULL; +} diff --git a/foobar/portable/openbsd-compat/libasr/thread_private.h b/foobar/portable/openbsd-compat/libasr/thread_private.h new file mode 100644 index 00000000..23951975 --- /dev/null +++ b/foobar/portable/openbsd-compat/libasr/thread_private.h @@ -0,0 +1,8 @@ +/* + * + */ +#define __is_threaded (0) +#define _THREAD_PRIVATE_MUTEX(x) +#define _THREAD_PRIVATE_MUTEX_LOCK(x) +#define _THREAD_PRIVATE_MUTEX_UNLOCK(x) +#define _THREAD_PRIVATE(a, b, c) (c) diff --git a/foobar/portable/openbsd-compat/libressl.c b/foobar/portable/openbsd-compat/libressl.c new file mode 100644 index 00000000..f4f2b52e --- /dev/null +++ b/foobar/portable/openbsd-compat/libressl.c @@ -0,0 +1,131 @@ +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * 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 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + +#include <sys/types.h> + +#include <unistd.h> +#include <stdio.h> + +#include <openssl/err.h> +#include <openssl/bio.h> +#include <openssl/objects.h> +#include <openssl/evp.h> +#include <openssl/x509.h> +#include <openssl/pem.h> +#include <openssl/ssl.h> + +int +SSL_CTX_use_certificate_chain(SSL_CTX *ctx, char *buf, off_t len) +{ + int ret; + BIO*in; + X509*x; + X509*ca; + unsigned long err; + + ret = 0; + x = ca = NULL; + + if ((in = BIO_new_mem_buf(buf, len)) == NULL) { + SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_CHAIN_FILE, ERR_R_BUF_LIB); + goto end; + } + + if ((x = PEM_read_bio_X509(in, NULL, + ctx->default_passwd_callback, + ctx->default_passwd_callback_userdata)) == NULL) { + SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_CHAIN_FILE, ERR_R_PEM_LIB); + goto end; + } + + if (!SSL_CTX_use_certificate(ctx, x) || ERR_peek_error() != 0) + goto end; + + /* If we could set up our certificate, now proceed to + * the CA certificates. + */ + + if (ctx->extra_certs != NULL) { + sk_X509_pop_free(ctx->extra_certs, X509_free); + ctx->extra_certs = NULL; + } + + while ((ca = PEM_read_bio_X509(in, NULL, + ctx->default_passwd_callback, + ctx->default_passwd_callback_userdata)) != NULL) { + + if (!SSL_CTX_add_extra_chain_cert(ctx, ca)) + goto end; + } + + err = ERR_peek_last_error(); + if (ERR_GET_LIB(err) == ERR_LIB_PEM && + ERR_GET_REASON(err) == PEM_R_NO_START_LINE) + ERR_clear_error(); + else + goto end; + + ret = 1; +end: + if (ca != NULL) + X509_free(ca); + if (x != NULL) + X509_free(x); + if (in != NULL) + BIO_free(in); + return (ret); +} diff --git a/foobar/portable/openbsd-compat/nanosleep.c b/foobar/portable/openbsd-compat/nanosleep.c new file mode 100644 index 00000000..1256c0b5 --- /dev/null +++ b/foobar/portable/openbsd-compat/nanosleep.c @@ -0,0 +1,63 @@ + +/* + * Copyright (c) 1999-2004 Damien Miller <djm@mindrot.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#ifdef HAVE_SYS_SELECT_H +# include <sys/select.h> +#endif +#ifdef HAVE_SYS_TIME_H +# include <sys/time.h> +#endif + +#include <err.h> +#include <string.h> +#include <signal.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +int +nanosleep(const struct timespec *req, struct timespec *rem) +{ + int rc, saverrno; + extern int errno; + struct timeval tstart, tstop, tremain, time2wait; + + TIMESPEC_TO_TIMEVAL(&time2wait, req); + (void) gettimeofday(&tstart, NULL); + rc = select(0, NULL, NULL, NULL, &time2wait); + if (rc == -1) { + saverrno = errno; + (void) gettimeofday (&tstop, NULL); + errno = saverrno; + tremain.tv_sec = time2wait.tv_sec - + (tstop.tv_sec - tstart.tv_sec); + tremain.tv_usec = time2wait.tv_usec - + (tstop.tv_usec - tstart.tv_usec); + tremain.tv_sec += tremain.tv_usec / 1000000L; + tremain.tv_usec %= 1000000L; + } else { + tremain.tv_sec = 0; + tremain.tv_usec = 0; + } + if (rem != NULL) + TIMEVAL_TO_TIMESPEC(&tremain, rem); + + return(rc); +} diff --git a/foobar/portable/openbsd-compat/openbsd-compat.h b/foobar/portable/openbsd-compat/openbsd-compat.h new file mode 100644 index 00000000..57748621 --- /dev/null +++ b/foobar/portable/openbsd-compat/openbsd-compat.h @@ -0,0 +1,340 @@ +/* $Id: openbsd-compat.h,v 1.51 2010/10/07 10:25:29 djm Exp $ */ + +/* + * Copyright (c) 1999-2003 Damien Miller. All rights reserved. + * Copyright (c) 2003 Ben Lindstrom. All rights reserved. + * Copyright (c) 2002 Tim Rice. 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 AUTHOR ``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 AUTHOR 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 _OPENBSD_COMPAT_H +#define _OPENBSD_COMPAT_H + +#include "includes.h" + +#include <sys/types.h> + +#include <sys/socket.h> +#include <netinet/in.h> + +/* OpenBSD function replacements */ +#include "base64.h" + +#include <sys/queue.h> +#include <sys/tree.h> +#include "bsd-vis.h" + +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif + +#ifndef HAVE_BASENAME +char *basename(const char *path); +#endif + +#ifndef HAVE_CLOSEFROM +void closefrom(int); +#endif + +#if !defined(HAVE_REALPATH) || defined(BROKEN_REALPATH) +char *realpath(const char *path, char *resolved); +#endif + +#if !HAVE_DECL_STRLCPY +size_t strlcpy(char *dst, const char *src, size_t size); +#endif + +#if !HAVE_DECL_STRLCAT +size_t strlcat(char *dst, const char *src, size_t size); +#endif + +#ifndef HAVE_STRMODE +void strmode(int mode, char *p); +#endif + +#ifndef HAVE_DAEMON +int daemon(int nochdir, int noclose); +#endif + +#ifndef HAVE_DIRNAME +char *dirname(const char *path); +#endif + +#ifndef HAVE_FMT_SCALED +#define FMT_SCALED_STRSIZE 7 +int fmt_scaled(long long number, char *result); +#endif + +#ifndef HAVE_SCAN_SCALED +int scan_scaled(char *, long long *); +#endif + +#ifndef HAVE_INET_NTOP +const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); +#endif + +#ifndef HAVE_STRSEP +char *strsep(char **stringp, const char *delim); +#endif + +#ifndef HAVE_SETPROCTITLE +void setproctitle(const char *fmt, ...); +void compat_init_setproctitle(int argc, char *argv[]); +#endif + +#if !defined(HAVE_GETOPT) || !defined(HAVE_GETOPT_OPTRESET) +int BSDgetopt(int argc, char * const *argv, const char *opts); +#endif + +/* Home grown routines */ +#include "bsd-misc.h" +/* #include "bsd-setres_id.h" */ +/* #include "bsd-statvfs.h" */ +#include "bsd-waitpid.h" +/* #include "bsd-poll.h" */ + +#ifndef HAVE_GETPEEREID +int getpeereid(int , uid_t *, gid_t *); +#endif + +#if !defined(HAVE_ARC4RANDOM) || defined(LIBRESSL_VERSION_NUMBER) +unsigned int arc4random(void); +#endif + +#if 0 +#if defined(LIBRESSL_VERSION_NUMBER) +# define arc4random_stir() +#elif defined(HAVE_ARC4RANDOM_STIR) +void arc4random_stir(void); +#elif defined(HAVE_ARC4RANDOM) +/* Recent system/libressl implementation; no need for explicit stir */ +# define arc4random_stir() +#else +/* openbsd-compat/arc4random.c provides arc4random_stir() */ +void arc4random_stir(void); +#endif +#endif + +#if !defined(HAVE_ARC4RANDOM_BUF) || defined(LIBRESSL_VERSION_NUMBER) +void arc4random_buf(void *, size_t); +#endif + +#if !defined(HAVE_ARC4RANDOM_UNIFORM) || defined(LIBRESSL_VERSION_NUMBER) +uint32_t arc4random_uniform(uint32_t); +#endif + +#if !defined(SSL_OP_NO_CLIENT_RENEGOTIATION) && !defined(LIBRESSL_VERSION_NUMBER) +#define SSL_OP_NO_CLIENT_RENEGOTIATION 0 +#endif + +#ifndef HAVE_ASPRINTF +int asprintf(char **, const char *, ...); +#endif + +/* #include <sys/types.h> XXX needed? For size_t */ + +#ifndef HAVE_SNPRINTF +int snprintf(char *, size_t, const char *, ...); +#endif + +#ifndef HAVE_STRTOLL +long long strtoll(const char *, char **, int); +#endif + +#ifndef HAVE_STRTOUL +unsigned long strtoul(const char *, char **, int); +#endif + +#ifndef HAVE_STRTOULL +unsigned long long strtoull(const char *, char **, int); +#endif + +#ifndef HAVE_STRTONUM +long long strtonum(const char *nptr, long long minval, long long maxval, const char **errstr); +#endif + +#if !defined(HAVE_VASPRINTF) || !defined(HAVE_VSNPRINTF) +# include <stdarg.h> +#endif + +#ifndef HAVE_VASPRINTF +int vasprintf(char **, const char *, va_list); +#endif + +#ifndef HAVE_VSNPRINTF +int vsnprintf(char *, size_t, const char *, va_list); +#endif + +#if !defined(HAVE_EXPLICIT_BZERO) || defined(LIBRESSL_VERSION_NUMBER) +void explicit_bzero(void *p, size_t n); +#endif + +/* OpenSMTPD-portable specific entries */ + +#ifndef HAVE_FGETLN +#include <stdio.h> +#include <string.h> +char * fgetln(FILE *stream, size_t *len); +#endif + +#ifndef HAVE_FPARSELN +#include <stdio.h> +#include <string.h> +char * fparseln(FILE *fp, size_t *size, size_t *lineno, const char str[3], int flags); +#endif + +#ifndef HAVE_FREEZERO +void freezero(void *, size_t); +#endif + +#ifndef HAVE_PIDFILE +int pidfile(const char *basename); +#endif + +#ifndef HAVE_PW_DUP +struct passwd *pw_dup(const struct passwd *); +#endif + +#if !defined(HAVE_REALLOCARRAY) || defined(LIBRESSL_VERSION_NUMBER) +void *reallocarray(void *, size_t, size_t); +#endif + +#if !defined(HAVE_RECALLOCARRAY) || defined(LIBRESSL_VERSION_NUMBER) +void *recallocarray(void *, size_t, size_t, size_t); +#endif + +#ifndef HAVE_ERRC +__attribute__ ((noreturn)) +void errc(int, int, const char *, ...); +#endif + +#ifndef HAVE_INET_NET_PTON +int inet_net_pton(int, const char *, void *, size_t); +#endif + +#ifndef HAVE_PLEDGE +#define pledge(promises, paths) 0 +#endif + +#ifndef HAVE_MALLOC_CONCEAL +#define malloc_conceal malloc +#endif + +#ifndef HAVE_CALLOC_CONCEAL +#define calloc_conceal calloc +#endif + +#ifndef HAVE_RES_HNOK +int res_hnok(const char *); +#endif + +#if !HAVE_DECL_AF_LOCAL +#define AF_LOCAL AF_UNIX +#endif + +#if !HAVE_DECL_PF_LOCAL +#define PF_LOCAL PF_UNIX +#endif + +#if !HAVE_DECL_WAIT_MYPGRP +#define WAIT_MYPGRP 0 +#endif + +#if !HAVE_DECL_IPPORT_HILASTAUTO +#define IPPORT_HILASTAUTO 65535 +#endif + +#ifndef HAVE_FLOCK +int flock(int, int); +#endif + +#ifndef HAVE_SETRESGID +int setresgid(uid_t, uid_t, uid_t); +#endif + +#ifndef HAVE_SETRESUID +int setresuid(uid_t, uid_t, uid_t); +#endif + +#ifndef HAVE_GETLINE +ssize_t getline(char **, size_t *, FILE *); +#endif + +#ifndef HAVE_CRYPT_CHECKPASS +int crypt_checkpass(const char *, const char *); +#endif + +#ifndef HAVE_STRNDUP +char * strndup(const char *, size_t); +#endif + +#ifndef HAVE_STRNLEN +char * strnlen(const char *, size_t); +#endif + +#ifndef HAVE_STRUCT_TIMEVAL +struct timeval { + long tv_sec; + long tv_usec; +} +#endif + +#ifdef NEED_NANOSLEEP +#ifndef HAVE_STRUCT_TIMESPEC +struct timespec { + time_t tv_sec; + long tv_nsec; +}; +#endif +int nanosleep(const struct timespec *, struct timespec *); +#endif + +#ifdef NEED_SETEGID +int setegid(uid_t); +#endif + +#ifdef NEED_SETEUID +int seteuid(uid_t); +#endif + +#ifdef NEED_SETSID +#define setsid() setpgrp(0, getpid()) +#endif + +#ifdef NEED_SIGNAL +typedef void (*mysig_t)(int); +mysig_t mysignal(int sig, mysig_t act); +#define signal(a,b) mysignal(a,b) +#endif + +#ifdef NEED_STRERROR +const char *strerror(int); +#endif + +#ifdef NEED_USLEEP +int usleep(unsigned int useconds); +#endif + +char *get_progname(char *); + + +#endif /* _OPENBSD_COMPAT_H */ diff --git a/foobar/portable/openbsd-compat/paths_h/paths.h b/foobar/portable/openbsd-compat/paths_h/paths.h new file mode 100644 index 00000000..6b66a9c1 --- /dev/null +++ b/foobar/portable/openbsd-compat/paths_h/paths.h @@ -0,0 +1,8 @@ +#ifndef PATHS_H +#define PATHS_H + +#ifndef _PATH_DEFPATH +#define _PATH_DEFPATH "/bin:/usr/bin" +#endif + +#endif diff --git a/foobar/portable/openbsd-compat/pidfile.c b/foobar/portable/openbsd-compat/pidfile.c new file mode 100644 index 00000000..d6f83880 --- /dev/null +++ b/foobar/portable/openbsd-compat/pidfile.c @@ -0,0 +1,112 @@ +/* $OpenBSD: pidfile.c,v 1.8 2008/06/26 05:42:05 ray 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. + */ + +/* OPENBSD ORIGINAL: lib/libutil/pidfile.c */ + +#include "includes.h" +#ifndef HAVE_PIDFILE + +#include <sys/param.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +static char *pidfile_path; +static pid_t pidfile_pid; + +static void pidfile_cleanup(void); + +extern char *__progname; + +int +pidfile(const char *basename) +{ + int save_errno; + pid_t pid; + FILE *f; + + if (basename == NULL) + basename = __progname; + + if (pidfile_path != NULL) { + free(pidfile_path); + pidfile_path = NULL; + } + + (void) asprintf(&pidfile_path, "%s/%s.pid", SMTPD_PIDDIR, basename); + if (pidfile_path == NULL) + return (-1); + + if ((f = fopen(pidfile_path, "w")) == NULL) { + save_errno = errno; + free(pidfile_path); + pidfile_path = NULL; + errno = save_errno; + return (-1); + } + + pid = getpid(); + if (fprintf(f, "%ld\n", (long)pid) <= 0) { + fclose(f); + save_errno = errno; + (void) unlink(pidfile_path); + free(pidfile_path); + pidfile_path = NULL; + errno = save_errno; + return (-1); + } + + fclose(f); + pidfile_pid = pid; + if (atexit(pidfile_cleanup) < 0) { + save_errno = errno; + (void) 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()) + (void) unlink(pidfile_path); +} + +#endif diff --git a/foobar/portable/openbsd-compat/progname.c b/foobar/portable/openbsd-compat/progname.c new file mode 100644 index 00000000..b8b9ae30 --- /dev/null +++ b/foobar/portable/openbsd-compat/progname.c @@ -0,0 +1,62 @@ + +/* + * Copyright (c) 1999-2004 Damien Miller <djm@mindrot.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#ifdef HAVE_SYS_SELECT_H +# include <sys/select.h> +#endif +#ifdef HAVE_SYS_TIME_H +# include <sys/time.h> +#endif + +#include <err.h> +#include <string.h> +#include <signal.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +/* + * NB. duplicate __progname in case it is an alias for argv[0] + * Otherwise it may get clobbered by setproctitle() + */ +char *get_progname(char *argv0) +{ + char *retp; +#ifdef HAVE___PROGNAME + extern char *__progname; + + if ((retp = strdup(__progname)) == NULL) + err(1, NULL); +#else + char *p; + + if (argv0 == NULL) + return ("unknown"); /* XXX */ + p = strrchr(argv0, '/'); + if (p == NULL) + p = argv0; + else + p++; + + if ((retp = strdup(p)) == NULL) + err(1, NULL); +#endif + return retp; +} diff --git a/foobar/portable/openbsd-compat/reallocarray.c b/foobar/portable/openbsd-compat/reallocarray.c new file mode 100644 index 00000000..9beec719 --- /dev/null +++ b/foobar/portable/openbsd-compat/reallocarray.c @@ -0,0 +1,42 @@ +/* $OpenBSD: reallocarray.c,v 1.1 2014/05/08 21:43:49 deraadt Exp $ */ +/* + * Copyright (c) 2008 Otto Moerbeek <otto@drijf.net> + * + * 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. + */ + +/* OPENBSD ORIGINAL: lib/libc/stdlib/reallocarray.c */ + +#include "includes.h" + +#include <sys/types.h> +#include <errno.h> +#include <stdint.h> +#include <stdlib.h> + +/* + * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX + * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW + */ +#define MUL_NO_OVERFLOW (1UL << (sizeof(size_t) * 4)) + +void * +reallocarray(void *optr, size_t nmemb, size_t size) +{ + if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && + nmemb > 0 && SIZE_MAX / nmemb < size) { + errno = ENOMEM; + return NULL; + } + return realloc(optr, size * nmemb); +} diff --git a/foobar/portable/openbsd-compat/recallocarray.c b/foobar/portable/openbsd-compat/recallocarray.c new file mode 100644 index 00000000..fc0b5a8a --- /dev/null +++ b/foobar/portable/openbsd-compat/recallocarray.c @@ -0,0 +1,84 @@ +/* $OpenBSD: recallocarray.c,v 1.1 2017/03/06 18:44:21 otto Exp $ */ +/* + * Copyright (c) 2008, 2017 Otto Moerbeek <otto@drijf.net> + * + * 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. + */ + +/* OPENBSD ORIGINAL: lib/libc/stdlib/recallocarray.c */ + +#include "includes.h" + +#include <errno.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <unistd.h> + +/* + * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX + * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW + */ +#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4)) + +void * +recallocarray(void *ptr, size_t oldnmemb, size_t newnmemb, size_t size) +{ + size_t oldsize, newsize; + void *newptr; + + if (ptr == NULL) + return calloc(newnmemb, size); + + if ((newnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && + newnmemb > 0 && SIZE_MAX / newnmemb < size) { + errno = ENOMEM; + return NULL; + } + newsize = newnmemb * size; + + if ((oldnmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && + oldnmemb > 0 && SIZE_MAX / oldnmemb < size) { + errno = EINVAL; + return NULL; + } + oldsize = oldnmemb * size; + + /* + * Don't bother too much if we're shrinking just a bit, + * we do not shrink for series of small steps, oh well. + */ + if (newsize <= oldsize) { + size_t d = oldsize - newsize; + + if (d < oldsize / 2 && d < (size_t)getpagesize()) { + memset((char *)ptr + newsize, 0, d); + return ptr; + } + } + + newptr = malloc(newsize); + if (newptr == NULL) + return NULL; + + if (newsize > oldsize) { + memcpy(newptr, ptr, oldsize); + memset((char *)newptr + oldsize, 0, newsize - oldsize); + } else + memcpy(newptr, ptr, newsize); + + explicit_bzero(ptr, oldsize); + free(ptr); + + return newptr; +} diff --git a/foobar/portable/openbsd-compat/res_hnok.c b/foobar/portable/openbsd-compat/res_hnok.c new file mode 100644 index 00000000..a4b54baf --- /dev/null +++ b/foobar/portable/openbsd-compat/res_hnok.c @@ -0,0 +1,169 @@ +/* $OpenBSD: res_comp.c,v 1.14 2008/04/16 22:35:23 deraadt Exp $ */ + +/* + * ++Copyright++ 1985, 1993 + * - + * Copyright (c) 1985, 1993 + * The Regents of the University of California. 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. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * - + * Portions Copyright (c) 1993 by Digital Equipment Corporation. + * + * 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, and that + * the name of Digital Equipment Corporation not be used in advertising or + * publicity pertaining to distribution of the document or software without + * specific, written prior permission. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DIGITAL EQUIPMENT + * CORPORATION 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. + * - + * --Copyright-- + */ + +/* OPENBSD ORIGINAL: lib/libc/net/res_comp.c */ + +#include "includes.h" + +/* + * Verify that a domain name uses an acceptable character set. + */ + +/* + * Note the conspicuous absence of ctype macros in these definitions. On + * non-ASCII hosts, we can't depend on string literals or ctype macros to + * tell us anything about network-format data. The rest of the BIND system + * is not careful about this, but for some reason, we're doing it right here. + */ +#define PERIOD 0x2e +#define hyphenchar(c) ((c) == 0x2d) +#define bslashchar(c) ((c) == 0x5c) +#define underscorechar(c) ((c) == 0x5f) +#define periodchar(c) ((c) == PERIOD) +#define asterchar(c) ((c) == 0x2a) +#define alphachar(c) (((c) >= 0x41 && (c) <= 0x5a) \ + || ((c) >= 0x61 && (c) <= 0x7a)) +#define digitchar(c) ((c) >= 0x30 && (c) <= 0x39) + +#define borderchar(c) (alphachar(c) || digitchar(c)) +#define middlechar(c) (borderchar(c) || hyphenchar(c) || underscorechar(c)) +#define domainchar(c) ((c) > 0x20 && (c) < 0x7f) + +int +res_hnok(const char *dn) +{ + int pch = PERIOD, ch = *dn++; + + while (ch != '\0') { + int nch = *dn++; + + if (periodchar(ch)) { + ; + } else if (periodchar(pch)) { + if (!borderchar(ch)) + return (0); + } else if (periodchar(nch) || nch == '\0') { + if (!borderchar(ch)) + return (0); + } else { + if (!middlechar(ch)) + return (0); + } + pch = ch, ch = nch; + } + return (1); +} + +#if 0 + +/* + * hostname-like (A, MX, WKS) owners can have "*" as their first label + * but must otherwise be as a host name. + */ +int +res_ownok(const char *dn) +{ + if (asterchar(dn[0])) { + if (periodchar(dn[1])) + return (res_hnok(dn+2)); + if (dn[1] == '\0') + return (1); + } + return (res_hnok(dn)); +} + +/* + * SOA RNAMEs and RP RNAMEs can have any printable character in their first + * label, but the rest of the name has to look like a host name. + */ +int +res_mailok(const char *dn) +{ + int ch, escaped = 0; + + /* "." is a valid missing representation */ + if (*dn == '\0') + return(1); + + /* otherwise <label>.<hostname> */ + while ((ch = *dn++) != '\0') { + if (!domainchar(ch)) + return (0); + if (!escaped && periodchar(ch)) + break; + if (escaped) + escaped = 0; + else if (bslashchar(ch)) + escaped = 1; + } + if (periodchar(ch)) + return (res_hnok(dn)); + return(0); +} + +/* + * This function is quite liberal, since RFC 1034's character sets are only + * recommendations. + */ +int +res_dnok(const char *dn) +{ + int ch; + + while ((ch = *dn++) != '\0') + if (!domainchar(ch)) + return (0); + return (1); +} + +#endif /* if 0 */ diff --git a/foobar/portable/openbsd-compat/res_randomid.c b/foobar/portable/openbsd-compat/res_randomid.c new file mode 100644 index 00000000..c848c41d --- /dev/null +++ b/foobar/portable/openbsd-compat/res_randomid.c @@ -0,0 +1,13 @@ +#include "includes.h" + +#include <time.h> + +unsigned int +res_randomid(void) +{ + struct timespec ts; + + /* This is from musl C library */ + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_nsec + ts.tv_nsec / 65536UL & 0xffff; +} diff --git a/foobar/portable/openbsd-compat/setegid.c b/foobar/portable/openbsd-compat/setegid.c new file mode 100644 index 00000000..9e056351 --- /dev/null +++ b/foobar/portable/openbsd-compat/setegid.c @@ -0,0 +1,39 @@ + +/* + * Copyright (c) 1999-2004 Damien Miller <djm@mindrot.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#ifdef HAVE_SYS_SELECT_H +# include <sys/select.h> +#endif +#ifdef HAVE_SYS_TIME_H +# include <sys/time.h> +#endif + +#include <err.h> +#include <string.h> +#include <signal.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +int +setegid(uid_t egid) +{ + return(setresgid(-1, egid, -1)); +} diff --git a/foobar/portable/openbsd-compat/seteuid.c b/foobar/portable/openbsd-compat/seteuid.c new file mode 100644 index 00000000..6f581125 --- /dev/null +++ b/foobar/portable/openbsd-compat/seteuid.c @@ -0,0 +1,39 @@ + +/* + * Copyright (c) 1999-2004 Damien Miller <djm@mindrot.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#ifdef HAVE_SYS_SELECT_H +# include <sys/select.h> +#endif +#ifdef HAVE_SYS_TIME_H +# include <sys/time.h> +#endif + +#include <err.h> +#include <string.h> +#include <signal.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +int +seteuid(uid_t euid) +{ + return (setreuid(-1, euid)); +} diff --git a/foobar/portable/openbsd-compat/setproctitle.c b/foobar/portable/openbsd-compat/setproctitle.c new file mode 100644 index 00000000..eef70c14 --- /dev/null +++ b/foobar/portable/openbsd-compat/setproctitle.c @@ -0,0 +1,167 @@ +/* Based on conf.c from UCB sendmail 8.8.8 */ + +/* + * Copyright 2003 Damien Miller + * Copyright (c) 1983, 1995-1997 Eric P. Allman + * Copyright (c) 1988, 1993 + * The Regents of the University of California. 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. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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 "includes.h" + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#ifdef HAVE_SYS_PSTAT_H +#include <sys/pstat.h> +#endif +#include <string.h> + +#if defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS) +#include <vis.h> +#else +#include "bsd-vis.h" +#endif + +#define SPT_NONE 0 /* don't use it at all */ +#define SPT_PSTAT 1 /* use pstat(PSTAT_SETCMD, ...) */ +#define SPT_REUSEARGV 2 /* cover argv with title information */ + +#ifndef SPT_TYPE +# define SPT_TYPE SPT_NONE +#endif + +#ifndef SPT_PADCHAR +# define SPT_PADCHAR '\0' +#endif + +#if SPT_TYPE == SPT_REUSEARGV +static char *argv_start = NULL; +static size_t argv_env_len = 0; +#endif + +void +compat_init_setproctitle(int argc, char *argv[]) +{ +#if !defined(HAVE_SETPROCTITLE) && \ + defined(SPT_TYPE) && SPT_TYPE == SPT_REUSEARGV + extern char **environ; + char *lastargv = NULL; + char **envp = environ; + int i; + + /* + * NB: This assumes that argv has already been copied out of the + * way. This is true for sshd, but may not be true for other + * programs. Beware. + */ + + if (argc == 0 || argv[0] == NULL) + return; + + /* Fail if we can't allocate room for the new environment */ + for (i = 0; envp[i] != NULL; i++) + ; + if ((environ = calloc(i + 1, sizeof(*environ))) == NULL) { + environ = envp; /* put it back */ + return; + } + + /* + * Find the last argv string or environment variable within + * our process memory area. + */ + for (i = 0; i < argc; i++) { + if (lastargv == NULL || lastargv + 1 == argv[i]) + lastargv = argv[i] + strlen(argv[i]); + } + for (i = 0; envp[i] != NULL; i++) { + if (lastargv + 1 == envp[i]) + lastargv = envp[i] + strlen(envp[i]); + } + + argv[1] = NULL; + argv_start = argv[0]; + argv_env_len = lastargv - argv[0] - 1; + + /* + * Copy environment + * XXX - will truncate env on strdup fail + */ + for (i = 0; envp[i] != NULL; i++) + environ[i] = strdup(envp[i]); + environ[i] = NULL; +#endif /* SPT_REUSEARGV */ +} + +void +setproctitle(const char *fmt, ...) +{ +#if SPT_TYPE != SPT_NONE + va_list ap; + char buf[1024], ptitle[1024]; + size_t len; + int r; + extern char *__progname; +#if SPT_TYPE == SPT_PSTAT + union pstun pst; +#endif + +#if SPT_TYPE == SPT_REUSEARGV + if (argv_env_len <= 0) + return; +#endif + + strlcpy(buf, __progname, sizeof(buf)); + + r = -1; + va_start(ap, fmt); + if (fmt != NULL) { + len = strlcat(buf, ": ", sizeof(buf)); + if (len < sizeof(buf)) + r = vsnprintf(buf + len, sizeof(buf) - len , fmt, ap); + } + va_end(ap); + if (r == -1 || (size_t)r >= sizeof(buf) - len) + return; + strnvis(ptitle, buf, sizeof(ptitle), + VIS_CSTYLE|VIS_NL|VIS_TAB|VIS_OCTAL); + +#if SPT_TYPE == SPT_PSTAT + pst.pst_command = ptitle; + pstat(PSTAT_SETCMD, pst, strlen(ptitle), 0, 0); +#elif SPT_TYPE == SPT_REUSEARGV +/* debug("setproctitle: copy \"%s\" into len %d", + buf, argv_env_len); */ + len = strlcpy(argv_start, ptitle, argv_env_len); + for(; len < argv_env_len; len++) + argv_start[len] = SPT_PADCHAR; +#endif + +#endif /* SPT_NONE */ +} diff --git a/foobar/portable/openbsd-compat/setresgid.c b/foobar/portable/openbsd-compat/setresgid.c new file mode 100644 index 00000000..e798cdad --- /dev/null +++ b/foobar/portable/openbsd-compat/setresgid.c @@ -0,0 +1,41 @@ +/* Subset of uidswap.c from portable OpenSSH */ + +/* $OpenBSD: uidswap.c,v 1.35 2006/08/03 03:34:42 deraadt Exp $ */ +/* + * Author: Tatu Ylonen <ylo@cs.hut.fi> + * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + * All rights reserved + * Code for uid-swapping. + * + * As far as I am concerned, the code I have written for this software + * can be used freely for any purpose. Any derived versions of this + * software must be clearly marked as such, and if the derived work is + * incompatible with the protocol description in the RFC file, it must be + * called by a name other than "ssh" or "Secure Shell". + */ + +#include "includes.h" + +#include <stdarg.h> +#include <string.h> +#include <unistd.h> + +#include "log.h" + +int setresgid(uid_t rgid, uid_t egid, uid_t sgid) +{ + +#if defined(HAVE_SETRESGID) && !defined(BROKEN_SETRESGID) + if (setresgid(rgid, egid, sgid) < 0) + fatal("setresgid %u: %.100s", (u_int)rgid, strerror(errno)); +#elif defined(HAVE_SETREGID) && !defined(BROKEN_SETREGID) + if (setregid(rgid, egid) < 0) + fatal("setregid %u: %.100s", (u_int)rgid, strerror(errno)); +#else + if (setegid(egid) < 0) + fatal("setegid %u: %.100s", (u_int)egid, strerror(errno)); + if (setgid(rgid) < 0) + fatal("setgid %u: %.100s", (u_int)rgid, strerror(errno)); +#endif + return (0); +} diff --git a/foobar/portable/openbsd-compat/setresuid.c b/foobar/portable/openbsd-compat/setresuid.c new file mode 100644 index 00000000..36df39e9 --- /dev/null +++ b/foobar/portable/openbsd-compat/setresuid.c @@ -0,0 +1,43 @@ +/* Subset of uidswap.c from portable OpenSSH */ + +/* $OpenBSD: uidswap.c,v 1.35 2006/08/03 03:34:42 deraadt Exp $ */ +/* + * Author: Tatu Ylonen <ylo@cs.hut.fi> + * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland + * All rights reserved + * Code for uid-swapping. + * + * As far as I am concerned, the code I have written for this software + * can be used freely for any purpose. Any derived versions of this + * software must be clearly marked as such, and if the derived work is + * incompatible with the protocol description in the RFC file, it must be + * called by a name other than "ssh" or "Secure Shell". + */ + +#include "includes.h" + +#include <stdarg.h> +#include <string.h> +#include <unistd.h> + +#include "log.h" + +int setresuid(uid_t ruid, uid_t euid, uid_t suid) +{ + +#if defined(HAVE_SETRESUID) && !defined(BROKEN_SETRESUID) + if (setresuid(ruid, euid, suid) < 0) + fatal("setresuid %u: %.100s", (u_int)ruid, strerror(errno)); +#elif defined(HAVE_SETREUID) && !defined(BROKEN_SETREUID) + if (setreuid(ruid, euid) < 0) + fatal("setreuid %u: %.100s", (u_int)ruid, strerror(errno)); +#else +# ifndef SETEUID_BREAKS_SETUID + if (seteuid(euid) < 0) + fatal("seteuid %u: %.100s", (u_int)euid, strerror(errno)); +# endif + if (setuid(ruid) < 0) + fatal("setuid %u: %.100s", (u_int)ruid, strerror(errno)); +#endif + return (0); +} diff --git a/foobar/portable/openbsd-compat/signal.c b/foobar/portable/openbsd-compat/signal.c new file mode 100644 index 00000000..cdae0cd2 --- /dev/null +++ b/foobar/portable/openbsd-compat/signal.c @@ -0,0 +1,60 @@ + +/* + * Copyright (c) 1999-2004 Damien Miller <djm@mindrot.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#ifdef HAVE_SYS_SELECT_H +# include <sys/select.h> +#endif +#ifdef HAVE_SYS_TIME_H +# include <sys/time.h> +#endif + +#include <err.h> +#include <string.h> +#include <signal.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +mysig_t +mysignal(int sig, mysig_t act) +{ +#ifdef HAVE_SIGACTION + struct sigaction sa, osa; + + if (sigaction(sig, NULL, &osa) == -1) + return (mysig_t) -1; + if (osa.sa_handler != act) { + memset(&sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; +#ifdef SA_INTERRUPT + if (sig == SIGALRM) + sa.sa_flags |= SA_INTERRUPT; +#endif + sa.sa_handler = act; + if (sigaction(sig, &sa, NULL) == -1) + return (mysig_t) -1; + } + return (osa.sa_handler); +#else + #undef signal + return (signal(sig, act)); +#endif +} diff --git a/foobar/portable/openbsd-compat/strerror.c b/foobar/portable/openbsd-compat/strerror.c new file mode 100644 index 00000000..0ddad2d4 --- /dev/null +++ b/foobar/portable/openbsd-compat/strerror.c @@ -0,0 +1,44 @@ + +/* + * Copyright (c) 1999-2004 Damien Miller <djm@mindrot.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#ifdef HAVE_SYS_SELECT_H +# include <sys/select.h> +#endif +#ifdef HAVE_SYS_TIME_H +# include <sys/time.h> +#endif + +#include <err.h> +#include <string.h> +#include <signal.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +const char *strerror(int e) +{ + extern int sys_nerr; + extern char *sys_errlist[]; + + if ((e >= 0) && (e < sys_nerr)) + return (sys_errlist[e]); + + return ("unlisted error"); +} diff --git a/foobar/portable/openbsd-compat/strlcat.c b/foobar/portable/openbsd-compat/strlcat.c new file mode 100644 index 00000000..979eb876 --- /dev/null +++ b/foobar/portable/openbsd-compat/strlcat.c @@ -0,0 +1,59 @@ +/* $OpenBSD: strlcat.c,v 1.13 2005/08/08 08:05:37 espie Exp $ */ + +/* + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * + * 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. + */ + +/* OPENBSD ORIGINAL: lib/libc/string/strlcat.c */ + +#include "includes.h" + +#include <sys/types.h> +#include <string.h> + +/* + * Appends src to string dst of size siz (unlike strncat, siz is the + * full size of dst, not space left). At most siz-1 characters + * will be copied. Always NUL terminates (unless siz <= strlen(dst)). + * Returns strlen(src) + MIN(siz, strlen(initial dst)). + * If retval >= siz, truncation occurred. + */ +size_t +strlcat(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + size_t dlen; + + /* Find the end of dst and adjust bytes left but don't go past end */ + while (n-- != 0 && *d != '\0') + d++; + dlen = d - dst; + n = siz - dlen; + + if (n == 0) + return(dlen + strlen(s)); + while (*s != '\0') { + if (n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + + return(dlen + (s - src)); /* count does not include NUL */ +} diff --git a/foobar/portable/openbsd-compat/strlcpy.c b/foobar/portable/openbsd-compat/strlcpy.c new file mode 100644 index 00000000..2562ebbf --- /dev/null +++ b/foobar/portable/openbsd-compat/strlcpy.c @@ -0,0 +1,55 @@ +/* $OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp $ */ + +/* + * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> + * + * 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. + */ + +/* OPENBSD ORIGINAL: lib/libc/string/strlcpy.c */ + +#include "includes.h" + +#include <sys/types.h> +#include <string.h> + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t +strlcpy(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + + /* Copy as many bytes as will fit */ + if (n != 0) { + while (--n != 0) { + if ((*d++ = *s++) == '\0') + break; + } + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + + return(s - src - 1); /* count does not include NUL */ +} diff --git a/foobar/portable/openbsd-compat/strmode.c b/foobar/portable/openbsd-compat/strmode.c new file mode 100644 index 00000000..2fc2bb0e --- /dev/null +++ b/foobar/portable/openbsd-compat/strmode.c @@ -0,0 +1,146 @@ +/* $OpenBSD: strmode.c,v 1.7 2005/08/08 08:05:37 espie Exp $ */ +/*- + * Copyright (c) 1990 The Regents of the University of California. + * 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. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + */ + +/* OPENBSD ORIGINAL: lib/libc/string/strmode.c */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <string.h> + +/* XXX mode should be mode_t */ + +void +strmode(int mode, char *p) +{ + /* print type */ + switch (mode & S_IFMT) { + case S_IFDIR: /* directory */ + *p++ = 'd'; + break; + case S_IFCHR: /* character special */ + *p++ = 'c'; + break; + case S_IFBLK: /* block special */ + *p++ = 'b'; + break; + case S_IFREG: /* regular */ + *p++ = '-'; + break; + case S_IFLNK: /* symbolic link */ + *p++ = 'l'; + break; +#ifdef S_IFSOCK + case S_IFSOCK: /* socket */ + *p++ = 's'; + break; +#endif +#ifdef S_IFIFO + case S_IFIFO: /* fifo */ + *p++ = 'p'; + break; +#endif + default: /* unknown */ + *p++ = '?'; + break; + } + /* usr */ + if (mode & S_IRUSR) + *p++ = 'r'; + else + *p++ = '-'; + if (mode & S_IWUSR) + *p++ = 'w'; + else + *p++ = '-'; + switch (mode & (S_IXUSR | S_ISUID)) { + case 0: + *p++ = '-'; + break; + case S_IXUSR: + *p++ = 'x'; + break; + case S_ISUID: + *p++ = 'S'; + break; + case S_IXUSR | S_ISUID: + *p++ = 's'; + break; + } + /* group */ + if (mode & S_IRGRP) + *p++ = 'r'; + else + *p++ = '-'; + if (mode & S_IWGRP) + *p++ = 'w'; + else + *p++ = '-'; + switch (mode & (S_IXGRP | S_ISGID)) { + case 0: + *p++ = '-'; + break; + case S_IXGRP: + *p++ = 'x'; + break; + case S_ISGID: + *p++ = 'S'; + break; + case S_IXGRP | S_ISGID: + *p++ = 's'; + break; + } + /* other */ + if (mode & S_IROTH) + *p++ = 'r'; + else + *p++ = '-'; + if (mode & S_IWOTH) + *p++ = 'w'; + else + *p++ = '-'; + switch (mode & (S_IXOTH | S_ISVTX)) { + case 0: + *p++ = '-'; + break; + case S_IXOTH: + *p++ = 'x'; + break; + case S_ISVTX: + *p++ = 'T'; + break; + case S_IXOTH | S_ISVTX: + *p++ = 't'; + break; + } + *p++ = ' '; /* will be a '+' if ACL's implemented */ + *p = '\0'; +} diff --git a/foobar/portable/openbsd-compat/strndup.c b/foobar/portable/openbsd-compat/strndup.c new file mode 100644 index 00000000..f43ba659 --- /dev/null +++ b/foobar/portable/openbsd-compat/strndup.c @@ -0,0 +1,39 @@ +/* $OpenBSD: strndup.c,v 1.2 2015/08/31 02:53:57 guenther Exp $ */ + +/* + * Copyright (c) 2010 Todd C. Miller <Todd.Miller@courtesan.com> + * + * 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 <sys/types.h> + +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +char * +strndup(const char *str, size_t maxlen) +{ + char *copy; + size_t len; + + len = strnlen(str, maxlen); + copy = malloc(len + 1); + if (copy != NULL) { + (void)memcpy(copy, str, len); + copy[len] = '\0'; + } + + return copy; +} diff --git a/foobar/portable/openbsd-compat/strnlen.c b/foobar/portable/openbsd-compat/strnlen.c new file mode 100644 index 00000000..a2017e19 --- /dev/null +++ b/foobar/portable/openbsd-compat/strnlen.c @@ -0,0 +1,32 @@ +/* $OpenBSD: strnlen.c,v 1.8 2016/10/16 17:37:39 dtucker Exp $ */ + +/* + * Copyright (c) 2010 Todd C. Miller <Todd.Miller@courtesan.com> + * + * 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 <sys/types.h> + +#include <string.h> + +size_t +strnlen(const char *str, size_t maxlen) +{ + const char *cp; + + for (cp = str; maxlen != 0 && *cp != '\0'; cp++, maxlen--) + ; + + return (size_t)(cp - str); +} diff --git a/foobar/portable/openbsd-compat/strsep.c b/foobar/portable/openbsd-compat/strsep.c new file mode 100644 index 00000000..b36eb8fd --- /dev/null +++ b/foobar/portable/openbsd-compat/strsep.c @@ -0,0 +1,79 @@ +/* $OpenBSD: strsep.c,v 1.6 2005/08/08 08:05:37 espie Exp $ */ + +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. 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. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + */ + +/* OPENBSD ORIGINAL: lib/libc/string/strsep.c */ + +#include "includes.h" + +#if !defined(HAVE_STRSEP) + +#include <string.h> +#include <stdio.h> + +/* + * Get next token from string *stringp, where tokens are possibly-empty + * strings separated by characters from delim. + * + * Writes NULs into the string at *stringp to end tokens. + * delim need not remain constant from call to call. + * On return, *stringp points past the last NUL written (if there might + * be further tokens), or is NULL (if there are definitely no more tokens). + * + * If *stringp is NULL, strsep returns NULL. + */ +char * +strsep(char **stringp, const char *delim) +{ + char *s; + const char *spanp; + int c, sc; + char *tok; + + if ((s = *stringp) == NULL) + return (NULL); + for (tok = s;;) { + c = *s++; + spanp = delim; + do { + if ((sc = *spanp++) == c) { + if (c == 0) + s = NULL; + else + s[-1] = 0; + *stringp = s; + return (tok); + } + } while (sc != 0); + } + /* NOTREACHED */ +} + +#endif /* !defined(HAVE_STRSEP) */ diff --git a/foobar/portable/openbsd-compat/strtonum.c b/foobar/portable/openbsd-compat/strtonum.c new file mode 100644 index 00000000..87f2f24b --- /dev/null +++ b/foobar/portable/openbsd-compat/strtonum.c @@ -0,0 +1,72 @@ +/* $OpenBSD: strtonum.c,v 1.6 2004/08/03 19:38:01 millert Exp $ */ + +/* + * Copyright (c) 2004 Ted Unangst and Todd Miller + * All rights reserved. + * + * 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. + */ + +/* OPENBSD ORIGINAL: lib/libc/stdlib/strtonum.c */ + +#include "includes.h" + +#ifndef HAVE_STRTONUM +#include <stdlib.h> +#include <limits.h> +#include <errno.h> + +#define INVALID 1 +#define TOOSMALL 2 +#define TOOLARGE 3 + +long long +strtonum(const char *numstr, long long minval, long long maxval, + const char **errstrp) +{ + long long ll = 0; + char *ep; + int error = 0; + struct errval { + const char *errstr; + int err; + } ev[4] = { + { NULL, 0 }, + { "invalid", EINVAL }, + { "too small", ERANGE }, + { "too large", ERANGE }, + }; + + ev[0].err = errno; + errno = 0; + if (minval > maxval) + error = INVALID; + else { + ll = strtoll(numstr, &ep, 10); + if (numstr == ep || *ep != '\0') + error = INVALID; + else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval) + error = TOOSMALL; + else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval) + error = TOOLARGE; + } + if (errstrp != NULL) + *errstrp = ev[error].errstr; + errno = ev[error].err; + if (error) + ll = 0; + + return (ll); +} + +#endif /* HAVE_STRTONUM */ diff --git a/foobar/portable/openbsd-compat/sys/queue.h b/foobar/portable/openbsd-compat/sys/queue.h new file mode 100644 index 00000000..28aaaa37 --- /dev/null +++ b/foobar/portable/openbsd-compat/sys/queue.h @@ -0,0 +1,653 @@ +/* $OpenBSD: queue.h,v 1.36 2012/04/11 13:29:14 naddy Exp $ */ +/* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */ + +/* + * Copyright (c) 1991, 1993 + * The Regents of the University of California. 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. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + * @(#)queue.h 8.5 (Berkeley) 8/20/94 + */ + +/* OPENBSD ORIGINAL: sys/sys/queue.h */ + +#ifndef _FAKE_QUEUE_H_ +#define _FAKE_QUEUE_H_ + +/* + * Require for OS/X and other platforms that have old/broken/incomplete + * <sys/queue.h>. + */ +#undef SLIST_HEAD +#undef SLIST_HEAD_INITIALIZER +#undef SLIST_ENTRY +#undef SLIST_FOREACH_PREVPTR +#undef SLIST_FIRST +#undef SLIST_END +#undef SLIST_EMPTY +#undef SLIST_NEXT +#undef SLIST_FOREACH +#undef SLIST_INIT +#undef SLIST_INSERT_AFTER +#undef SLIST_INSERT_HEAD +#undef SLIST_REMOVE_HEAD +#undef SLIST_REMOVE +#undef SLIST_REMOVE_NEXT +#undef LIST_HEAD +#undef LIST_HEAD_INITIALIZER +#undef LIST_ENTRY +#undef LIST_FIRST +#undef LIST_END +#undef LIST_EMPTY +#undef LIST_NEXT +#undef LIST_FOREACH +#undef LIST_INIT +#undef LIST_INSERT_AFTER +#undef LIST_INSERT_BEFORE +#undef LIST_INSERT_HEAD +#undef LIST_REMOVE +#undef LIST_REPLACE +#undef SIMPLEQ_HEAD +#undef SIMPLEQ_HEAD_INITIALIZER +#undef SIMPLEQ_ENTRY +#undef SIMPLEQ_FIRST +#undef SIMPLEQ_END +#undef SIMPLEQ_EMPTY +#undef SIMPLEQ_NEXT +#undef SIMPLEQ_FOREACH +#undef SIMPLEQ_INIT +#undef SIMPLEQ_INSERT_HEAD +#undef SIMPLEQ_INSERT_TAIL +#undef SIMPLEQ_INSERT_AFTER +#undef SIMPLEQ_REMOVE_HEAD +#undef TAILQ_HEAD +#undef TAILQ_HEAD_INITIALIZER +#undef TAILQ_ENTRY +#undef TAILQ_FIRST +#undef TAILQ_END +#undef TAILQ_NEXT +#undef TAILQ_LAST +#undef TAILQ_PREV +#undef TAILQ_EMPTY +#undef TAILQ_FOREACH +#undef TAILQ_FOREACH_REVERSE +#undef TAILQ_INIT +#undef TAILQ_INSERT_HEAD +#undef TAILQ_INSERT_TAIL +#undef TAILQ_INSERT_AFTER +#undef TAILQ_INSERT_BEFORE +#undef TAILQ_REMOVE +#undef TAILQ_REPLACE +#undef CIRCLEQ_HEAD +#undef CIRCLEQ_HEAD_INITIALIZER +#undef CIRCLEQ_ENTRY +#undef CIRCLEQ_FIRST +#undef CIRCLEQ_LAST +#undef CIRCLEQ_END +#undef CIRCLEQ_NEXT +#undef CIRCLEQ_PREV +#undef CIRCLEQ_EMPTY +#undef CIRCLEQ_FOREACH +#undef CIRCLEQ_FOREACH_REVERSE +#undef CIRCLEQ_INIT +#undef CIRCLEQ_INSERT_AFTER +#undef CIRCLEQ_INSERT_BEFORE +#undef CIRCLEQ_INSERT_HEAD +#undef CIRCLEQ_INSERT_TAIL +#undef CIRCLEQ_REMOVE +#undef CIRCLEQ_REPLACE + +/* + * This file defines five types of data structures: singly-linked lists, + * lists, simple queues, tail queues, and circular queues. + * + * + * A singly-linked list is headed by a single forward pointer. The elements + * are singly linked for minimum space and pointer manipulation overhead at + * the expense of O(n) removal for arbitrary elements. New elements can be + * added to the list after an existing element or at the head of the list. + * Elements being removed from the head of the list should use the explicit + * macro for this purpose for optimum efficiency. A singly-linked list may + * only be traversed in the forward direction. Singly-linked lists are ideal + * for applications with large datasets and few or no removals or for + * implementing a LIFO queue. + * + * A list is headed by a single forward pointer (or an array of forward + * pointers for a hash table header). The elements are doubly linked + * so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before + * or after an existing element or at the head of the list. A list + * may only be traversed in the forward direction. + * + * A simple queue is headed by a pair of pointers, one the head of the + * list and the other to the tail of the list. The elements are singly + * linked to save space, so elements can only be removed from the + * head of the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the + * list. A simple queue may only be traversed in the forward direction. + * + * A tail queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or + * after an existing element, at the head of the list, or at the end of + * the list. A tail queue may be traversed in either direction. + * + * A circle queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the list. + * A circle queue may be traversed in either direction, but has a more + * complex end of list detection. + * + * For details on the use of these macros, see the queue(3) manual page. + */ + +#if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC)) +#define _Q_INVALIDATE(a) (a) = ((void *)-1) +#else +#define _Q_INVALIDATE(a) +#endif + +/* + * Singly-linked List definitions. + */ +#define SLIST_HEAD(name, type) \ +struct name { \ + struct type *slh_first; /* first element */ \ +} + +#define SLIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define SLIST_ENTRY(type) \ +struct { \ + struct type *sle_next; /* next element */ \ +} + +/* + * Singly-linked List access methods. + */ +#define SLIST_FIRST(head) ((head)->slh_first) +#define SLIST_END(head) NULL +#define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head)) +#define SLIST_NEXT(elm, field) ((elm)->field.sle_next) + +#define SLIST_FOREACH(var, head, field) \ + for((var) = SLIST_FIRST(head); \ + (var) != SLIST_END(head); \ + (var) = SLIST_NEXT(var, field)) + +#define SLIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = SLIST_FIRST(head); \ + (var) && ((tvar) = SLIST_NEXT(var, field), 1); \ + (var) = (tvar)) + +/* + * Singly-linked List functions. + */ +#define SLIST_INIT(head) { \ + SLIST_FIRST(head) = SLIST_END(head); \ +} + +#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ + (elm)->field.sle_next = (slistelm)->field.sle_next; \ + (slistelm)->field.sle_next = (elm); \ +} while (0) + +#define SLIST_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.sle_next = (head)->slh_first; \ + (head)->slh_first = (elm); \ +} while (0) + +#define SLIST_REMOVE_AFTER(elm, field) do { \ + (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \ +} while (0) + +#define SLIST_REMOVE_HEAD(head, field) do { \ + (head)->slh_first = (head)->slh_first->field.sle_next; \ +} while (0) + +#define SLIST_REMOVE(head, elm, type, field) do { \ + if ((head)->slh_first == (elm)) { \ + SLIST_REMOVE_HEAD((head), field); \ + } else { \ + struct type *curelm = (head)->slh_first; \ + \ + while (curelm->field.sle_next != (elm)) \ + curelm = curelm->field.sle_next; \ + curelm->field.sle_next = \ + curelm->field.sle_next->field.sle_next; \ + _Q_INVALIDATE((elm)->field.sle_next); \ + } \ +} while (0) + +/* + * List definitions. + */ +#define LIST_HEAD(name, type) \ +struct name { \ + struct type *lh_first; /* first element */ \ +} + +#define LIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define LIST_ENTRY(type) \ +struct { \ + struct type *le_next; /* next element */ \ + struct type **le_prev; /* address of previous next element */ \ +} + +/* + * List access methods + */ +#define LIST_FIRST(head) ((head)->lh_first) +#define LIST_END(head) NULL +#define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head)) +#define LIST_NEXT(elm, field) ((elm)->field.le_next) + +#define LIST_FOREACH(var, head, field) \ + for((var) = LIST_FIRST(head); \ + (var)!= LIST_END(head); \ + (var) = LIST_NEXT(var, field)) + +#define LIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = LIST_FIRST(head); \ + (var) && ((tvar) = LIST_NEXT(var, field), 1); \ + (var) = (tvar)) + +/* + * List functions. + */ +#define LIST_INIT(head) do { \ + LIST_FIRST(head) = LIST_END(head); \ +} while (0) + +#define LIST_INSERT_AFTER(listelm, elm, field) do { \ + if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ + (listelm)->field.le_next->field.le_prev = \ + &(elm)->field.le_next; \ + (listelm)->field.le_next = (elm); \ + (elm)->field.le_prev = &(listelm)->field.le_next; \ +} while (0) + +#define LIST_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.le_prev = (listelm)->field.le_prev; \ + (elm)->field.le_next = (listelm); \ + *(listelm)->field.le_prev = (elm); \ + (listelm)->field.le_prev = &(elm)->field.le_next; \ +} while (0) + +#define LIST_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.le_next = (head)->lh_first) != NULL) \ + (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ + (head)->lh_first = (elm); \ + (elm)->field.le_prev = &(head)->lh_first; \ +} while (0) + +#define LIST_REMOVE(elm, field) do { \ + if ((elm)->field.le_next != NULL) \ + (elm)->field.le_next->field.le_prev = \ + (elm)->field.le_prev; \ + *(elm)->field.le_prev = (elm)->field.le_next; \ + _Q_INVALIDATE((elm)->field.le_prev); \ + _Q_INVALIDATE((elm)->field.le_next); \ +} while (0) + +#define LIST_REPLACE(elm, elm2, field) do { \ + if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \ + (elm2)->field.le_next->field.le_prev = \ + &(elm2)->field.le_next; \ + (elm2)->field.le_prev = (elm)->field.le_prev; \ + *(elm2)->field.le_prev = (elm2); \ + _Q_INVALIDATE((elm)->field.le_prev); \ + _Q_INVALIDATE((elm)->field.le_next); \ +} while (0) + +/* + * Simple queue definitions. + */ +#define SIMPLEQ_HEAD(name, type) \ +struct name { \ + struct type *sqh_first; /* first element */ \ + struct type **sqh_last; /* addr of last next element */ \ +} + +#define SIMPLEQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).sqh_first } + +#define SIMPLEQ_ENTRY(type) \ +struct { \ + struct type *sqe_next; /* next element */ \ +} + +/* + * Simple queue access methods. + */ +#define SIMPLEQ_FIRST(head) ((head)->sqh_first) +#define SIMPLEQ_END(head) NULL +#define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head)) +#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) + +#define SIMPLEQ_FOREACH(var, head, field) \ + for((var) = SIMPLEQ_FIRST(head); \ + (var) != SIMPLEQ_END(head); \ + (var) = SIMPLEQ_NEXT(var, field)) + +#define SIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = SIMPLEQ_FIRST(head); \ + (var) && ((tvar) = SIMPLEQ_NEXT(var, field), 1); \ + (var) = (tvar)) + +/* + * Simple queue functions. + */ +#define SIMPLEQ_INIT(head) do { \ + (head)->sqh_first = NULL; \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (0) + +#define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (head)->sqh_first = (elm); \ +} while (0) + +#define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.sqe_next = NULL; \ + *(head)->sqh_last = (elm); \ + (head)->sqh_last = &(elm)->field.sqe_next; \ +} while (0) + +#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (listelm)->field.sqe_next = (elm); \ +} while (0) + +#define SIMPLEQ_REMOVE_HEAD(head, field) do { \ + if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (0) + +#define SIMPLEQ_REMOVE_AFTER(head, elm, field) do { \ + if (((elm)->field.sqe_next = (elm)->field.sqe_next->field.sqe_next) \ + == NULL) \ + (head)->sqh_last = &(elm)->field.sqe_next; \ +} while (0) + +/* + * Tail queue definitions. + */ +#define TAILQ_HEAD(name, type) \ +struct name { \ + struct type *tqh_first; /* first element */ \ + struct type **tqh_last; /* addr of last next element */ \ +} + +#define TAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).tqh_first } + +#define TAILQ_ENTRY(type) \ +struct { \ + struct type *tqe_next; /* next element */ \ + struct type **tqe_prev; /* address of previous next element */ \ +} + +/* + * tail queue access methods + */ +#define TAILQ_FIRST(head) ((head)->tqh_first) +#define TAILQ_END(head) NULL +#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) +#define TAILQ_LAST(head, headname) \ + (*(((struct headname *)((head)->tqh_last))->tqh_last)) +/* XXX */ +#define TAILQ_PREV(elm, headname, field) \ + (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) +#define TAILQ_EMPTY(head) \ + (TAILQ_FIRST(head) == TAILQ_END(head)) + +#define TAILQ_FOREACH(var, head, field) \ + for((var) = TAILQ_FIRST(head); \ + (var) != TAILQ_END(head); \ + (var) = TAILQ_NEXT(var, field)) + +#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = TAILQ_FIRST(head); \ + (var) != TAILQ_END(head) && \ + ((tvar) = TAILQ_NEXT(var, field), 1); \ + (var) = (tvar)) + + +#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ + for((var) = TAILQ_LAST(head, headname); \ + (var) != TAILQ_END(head); \ + (var) = TAILQ_PREV(var, headname, field)) + +#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ + for ((var) = TAILQ_LAST(head, headname); \ + (var) != TAILQ_END(head) && \ + ((tvar) = TAILQ_PREV(var, headname, field), 1); \ + (var) = (tvar)) + +/* + * Tail queue functions. + */ +#define TAILQ_INIT(head) do { \ + (head)->tqh_first = NULL; \ + (head)->tqh_last = &(head)->tqh_first; \ +} while (0) + +#define TAILQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ + (head)->tqh_first->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (head)->tqh_first = (elm); \ + (elm)->field.tqe_prev = &(head)->tqh_first; \ +} while (0) + +#define TAILQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.tqe_next = NULL; \ + (elm)->field.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &(elm)->field.tqe_next; \ +} while (0) + +#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ + (elm)->field.tqe_next->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (listelm)->field.tqe_next = (elm); \ + (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ +} while (0) + +#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ + (elm)->field.tqe_next = (listelm); \ + *(listelm)->field.tqe_prev = (elm); \ + (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ +} while (0) + +#define TAILQ_REMOVE(head, elm, field) do { \ + if (((elm)->field.tqe_next) != NULL) \ + (elm)->field.tqe_next->field.tqe_prev = \ + (elm)->field.tqe_prev; \ + else \ + (head)->tqh_last = (elm)->field.tqe_prev; \ + *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ + _Q_INVALIDATE((elm)->field.tqe_prev); \ + _Q_INVALIDATE((elm)->field.tqe_next); \ +} while (0) + +#define TAILQ_REPLACE(head, elm, elm2, field) do { \ + if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \ + (elm2)->field.tqe_next->field.tqe_prev = \ + &(elm2)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm2)->field.tqe_next; \ + (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ + *(elm2)->field.tqe_prev = (elm2); \ + _Q_INVALIDATE((elm)->field.tqe_prev); \ + _Q_INVALIDATE((elm)->field.tqe_next); \ +} while (0) + +/* + * Circular queue definitions. + */ +#define CIRCLEQ_HEAD(name, type) \ +struct name { \ + struct type *cqh_first; /* first element */ \ + struct type *cqh_last; /* last element */ \ +} + +#define CIRCLEQ_HEAD_INITIALIZER(head) \ + { CIRCLEQ_END(&head), CIRCLEQ_END(&head) } + +#define CIRCLEQ_ENTRY(type) \ +struct { \ + struct type *cqe_next; /* next element */ \ + struct type *cqe_prev; /* previous element */ \ +} + +/* + * Circular queue access methods + */ +#define CIRCLEQ_FIRST(head) ((head)->cqh_first) +#define CIRCLEQ_LAST(head) ((head)->cqh_last) +#define CIRCLEQ_END(head) ((void *)(head)) +#define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next) +#define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev) +#define CIRCLEQ_EMPTY(head) \ + (CIRCLEQ_FIRST(head) == CIRCLEQ_END(head)) + +#define CIRCLEQ_FOREACH(var, head, field) \ + for((var) = CIRCLEQ_FIRST(head); \ + (var) != CIRCLEQ_END(head); \ + (var) = CIRCLEQ_NEXT(var, field)) + +#define CIRCLEQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = CIRCLEQ_FIRST(head); \ + (var) != CIRCLEQ_END(head) && \ + ((tvar) = CIRCLEQ_NEXT(var, field), 1); \ + (var) = (tvar)) + +#define CIRCLEQ_FOREACH_REVERSE(var, head, field) \ + for((var) = CIRCLEQ_LAST(head); \ + (var) != CIRCLEQ_END(head); \ + (var) = CIRCLEQ_PREV(var, field)) + +#define CIRCLEQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ + for ((var) = CIRCLEQ_LAST(head, headname); \ + (var) != CIRCLEQ_END(head) && \ + ((tvar) = CIRCLEQ_PREV(var, headname, field), 1); \ + (var) = (tvar)) + +/* + * Circular queue functions. + */ +#define CIRCLEQ_INIT(head) do { \ + (head)->cqh_first = CIRCLEQ_END(head); \ + (head)->cqh_last = CIRCLEQ_END(head); \ +} while (0) + +#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + (elm)->field.cqe_next = (listelm)->field.cqe_next; \ + (elm)->field.cqe_prev = (listelm); \ + if ((listelm)->field.cqe_next == CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm); \ + else \ + (listelm)->field.cqe_next->field.cqe_prev = (elm); \ + (listelm)->field.cqe_next = (elm); \ +} while (0) + +#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ + (elm)->field.cqe_next = (listelm); \ + (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ + if ((listelm)->field.cqe_prev == CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm); \ + else \ + (listelm)->field.cqe_prev->field.cqe_next = (elm); \ + (listelm)->field.cqe_prev = (elm); \ +} while (0) + +#define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.cqe_next = (head)->cqh_first; \ + (elm)->field.cqe_prev = CIRCLEQ_END(head); \ + if ((head)->cqh_last == CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm); \ + else \ + (head)->cqh_first->field.cqe_prev = (elm); \ + (head)->cqh_first = (elm); \ +} while (0) + +#define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.cqe_next = CIRCLEQ_END(head); \ + (elm)->field.cqe_prev = (head)->cqh_last; \ + if ((head)->cqh_first == CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm); \ + else \ + (head)->cqh_last->field.cqe_next = (elm); \ + (head)->cqh_last = (elm); \ +} while (0) + +#define CIRCLEQ_REMOVE(head, elm, field) do { \ + if ((elm)->field.cqe_next == CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm)->field.cqe_prev; \ + else \ + (elm)->field.cqe_next->field.cqe_prev = \ + (elm)->field.cqe_prev; \ + if ((elm)->field.cqe_prev == CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm)->field.cqe_next; \ + else \ + (elm)->field.cqe_prev->field.cqe_next = \ + (elm)->field.cqe_next; \ + _Q_INVALIDATE((elm)->field.cqe_prev); \ + _Q_INVALIDATE((elm)->field.cqe_next); \ +} while (0) + +#define CIRCLEQ_REPLACE(head, elm, elm2, field) do { \ + if (((elm2)->field.cqe_next = (elm)->field.cqe_next) == \ + CIRCLEQ_END(head)) \ + (head).cqh_last = (elm2); \ + else \ + (elm2)->field.cqe_next->field.cqe_prev = (elm2); \ + if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) == \ + CIRCLEQ_END(head)) \ + (head).cqh_first = (elm2); \ + else \ + (elm2)->field.cqe_prev->field.cqe_next = (elm2); \ + _Q_INVALIDATE((elm)->field.cqe_prev); \ + _Q_INVALIDATE((elm)->field.cqe_next); \ +} while (0) + +#endif /* !_FAKE_QUEUE_H_ */ diff --git a/foobar/portable/openbsd-compat/sys/tree.h b/foobar/portable/openbsd-compat/sys/tree.h new file mode 100644 index 00000000..7f7546ec --- /dev/null +++ b/foobar/portable/openbsd-compat/sys/tree.h @@ -0,0 +1,755 @@ +/* $OpenBSD: tree.h,v 1.13 2011/07/09 00:19:45 pirofti Exp $ */ +/* + * Copyright 2002 Niels Provos <provos@citi.umich.edu> + * 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 AUTHOR ``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 AUTHOR 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. + */ + +/* OPENBSD ORIGINAL: sys/sys/tree.h */ + +#include "config.h" +#ifdef NO_ATTRIBUTE_ON_RETURN_TYPE +# define __attribute__(x) +#endif + +#ifndef _SYS_TREE_H_ +#define _SYS_TREE_H_ + +/* + * This file defines data structures for different types of trees: + * splay trees and red-black trees. + * + * A splay tree is a self-organizing data structure. Every operation + * on the tree causes a splay to happen. The splay moves the requested + * node to the root of the tree and partly rebalances it. + * + * This has the benefit that request locality causes faster lookups as + * the requested nodes move to the top of the tree. On the other hand, + * every lookup causes memory writes. + * + * The Balance Theorem bounds the total access time for m operations + * and n inserts on an initially empty tree as O((m + n)lg n). The + * amortized cost for a sequence of m accesses to a splay tree is O(lg n); + * + * A red-black tree is a binary search tree with the node color as an + * extra attribute. It fulfills a set of conditions: + * - every search path from the root to a leaf consists of the + * same number of black nodes, + * - each red node (except for the root) has a black parent, + * - each leaf node is black. + * + * Every operation on a red-black tree is bounded as O(lg n). + * The maximum height of a red-black tree is 2lg (n+1). + */ + +#define SPLAY_HEAD(name, type) \ +struct name { \ + struct type *sph_root; /* root of the tree */ \ +} + +#define SPLAY_INITIALIZER(root) \ + { NULL } + +#define SPLAY_INIT(root) do { \ + (root)->sph_root = NULL; \ +} while (0) + +#define SPLAY_ENTRY(type) \ +struct { \ + struct type *spe_left; /* left element */ \ + struct type *spe_right; /* right element */ \ +} + +#define SPLAY_LEFT(elm, field) (elm)->field.spe_left +#define SPLAY_RIGHT(elm, field) (elm)->field.spe_right +#define SPLAY_ROOT(head) (head)->sph_root +#define SPLAY_EMPTY(head) (SPLAY_ROOT(head) == NULL) + +/* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */ +#define SPLAY_ROTATE_RIGHT(head, tmp, field) do { \ + SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field); \ + SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ + (head)->sph_root = tmp; \ +} while (0) + +#define SPLAY_ROTATE_LEFT(head, tmp, field) do { \ + SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field); \ + SPLAY_LEFT(tmp, field) = (head)->sph_root; \ + (head)->sph_root = tmp; \ +} while (0) + +#define SPLAY_LINKLEFT(head, tmp, field) do { \ + SPLAY_LEFT(tmp, field) = (head)->sph_root; \ + tmp = (head)->sph_root; \ + (head)->sph_root = SPLAY_LEFT((head)->sph_root, field); \ +} while (0) + +#define SPLAY_LINKRIGHT(head, tmp, field) do { \ + SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ + tmp = (head)->sph_root; \ + (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field); \ +} while (0) + +#define SPLAY_ASSEMBLE(head, node, left, right, field) do { \ + SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field); \ + SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\ + SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field); \ + SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field); \ +} while (0) + +/* Generates prototypes and inline functions */ + +#define SPLAY_PROTOTYPE(name, type, field, cmp) \ +void name##_SPLAY(struct name *, struct type *); \ +void name##_SPLAY_MINMAX(struct name *, int); \ +struct type *name##_SPLAY_INSERT(struct name *, struct type *); \ +struct type *name##_SPLAY_REMOVE(struct name *, struct type *); \ + \ +/* Finds the node with the same key as elm */ \ +static __inline struct type * \ +name##_SPLAY_FIND(struct name *head, struct type *elm) \ +{ \ + if (SPLAY_EMPTY(head)) \ + return(NULL); \ + name##_SPLAY(head, elm); \ + if ((cmp)(elm, (head)->sph_root) == 0) \ + return (head->sph_root); \ + return (NULL); \ +} \ + \ +static __inline struct type * \ +name##_SPLAY_NEXT(struct name *head, struct type *elm) \ +{ \ + name##_SPLAY(head, elm); \ + if (SPLAY_RIGHT(elm, field) != NULL) { \ + elm = SPLAY_RIGHT(elm, field); \ + while (SPLAY_LEFT(elm, field) != NULL) { \ + elm = SPLAY_LEFT(elm, field); \ + } \ + } else \ + elm = NULL; \ + return (elm); \ +} \ + \ +static __inline struct type * \ +name##_SPLAY_MIN_MAX(struct name *head, int val) \ +{ \ + name##_SPLAY_MINMAX(head, val); \ + return (SPLAY_ROOT(head)); \ +} + +/* Main splay operation. + * Moves node close to the key of elm to top + */ +#define SPLAY_GENERATE(name, type, field, cmp) \ +struct type * \ +name##_SPLAY_INSERT(struct name *head, struct type *elm) \ +{ \ + if (SPLAY_EMPTY(head)) { \ + SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL; \ + } else { \ + int __comp; \ + name##_SPLAY(head, elm); \ + __comp = (cmp)(elm, (head)->sph_root); \ + if(__comp < 0) { \ + SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\ + SPLAY_RIGHT(elm, field) = (head)->sph_root; \ + SPLAY_LEFT((head)->sph_root, field) = NULL; \ + } else if (__comp > 0) { \ + SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\ + SPLAY_LEFT(elm, field) = (head)->sph_root; \ + SPLAY_RIGHT((head)->sph_root, field) = NULL; \ + } else \ + return ((head)->sph_root); \ + } \ + (head)->sph_root = (elm); \ + return (NULL); \ +} \ + \ +struct type * \ +name##_SPLAY_REMOVE(struct name *head, struct type *elm) \ +{ \ + struct type *__tmp; \ + if (SPLAY_EMPTY(head)) \ + return (NULL); \ + name##_SPLAY(head, elm); \ + if ((cmp)(elm, (head)->sph_root) == 0) { \ + if (SPLAY_LEFT((head)->sph_root, field) == NULL) { \ + (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\ + } else { \ + __tmp = SPLAY_RIGHT((head)->sph_root, field); \ + (head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\ + name##_SPLAY(head, elm); \ + SPLAY_RIGHT((head)->sph_root, field) = __tmp; \ + } \ + return (elm); \ + } \ + return (NULL); \ +} \ + \ +void \ +name##_SPLAY(struct name *head, struct type *elm) \ +{ \ + struct type __node, *__left, *__right, *__tmp; \ + int __comp; \ +\ + SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ + __left = __right = &__node; \ +\ + while ((__comp = (cmp)(elm, (head)->sph_root))) { \ + if (__comp < 0) { \ + __tmp = SPLAY_LEFT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if ((cmp)(elm, __tmp) < 0){ \ + SPLAY_ROTATE_RIGHT(head, __tmp, field); \ + if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKLEFT(head, __right, field); \ + } else if (__comp > 0) { \ + __tmp = SPLAY_RIGHT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if ((cmp)(elm, __tmp) > 0){ \ + SPLAY_ROTATE_LEFT(head, __tmp, field); \ + if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKRIGHT(head, __left, field); \ + } \ + } \ + SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ +} \ + \ +/* Splay with either the minimum or the maximum element \ + * Used to find minimum or maximum element in tree. \ + */ \ +void name##_SPLAY_MINMAX(struct name *head, int __comp) \ +{ \ + struct type __node, *__left, *__right, *__tmp; \ +\ + SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ + __left = __right = &__node; \ +\ + while (1) { \ + if (__comp < 0) { \ + __tmp = SPLAY_LEFT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if (__comp < 0){ \ + SPLAY_ROTATE_RIGHT(head, __tmp, field); \ + if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKLEFT(head, __right, field); \ + } else if (__comp > 0) { \ + __tmp = SPLAY_RIGHT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if (__comp > 0) { \ + SPLAY_ROTATE_LEFT(head, __tmp, field); \ + if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKRIGHT(head, __left, field); \ + } \ + } \ + SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ +} + +#define SPLAY_NEGINF -1 +#define SPLAY_INF 1 + +#define SPLAY_INSERT(name, x, y) name##_SPLAY_INSERT(x, y) +#define SPLAY_REMOVE(name, x, y) name##_SPLAY_REMOVE(x, y) +#define SPLAY_FIND(name, x, y) name##_SPLAY_FIND(x, y) +#define SPLAY_NEXT(name, x, y) name##_SPLAY_NEXT(x, y) +#define SPLAY_MIN(name, x) (SPLAY_EMPTY(x) ? NULL \ + : name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF)) +#define SPLAY_MAX(name, x) (SPLAY_EMPTY(x) ? NULL \ + : name##_SPLAY_MIN_MAX(x, SPLAY_INF)) + +#define SPLAY_FOREACH(x, name, head) \ + for ((x) = SPLAY_MIN(name, head); \ + (x) != NULL; \ + (x) = SPLAY_NEXT(name, head, x)) + +/* Macros that define a red-black tree */ +#define RB_HEAD(name, type) \ +struct name { \ + struct type *rbh_root; /* root of the tree */ \ +} + +#define RB_INITIALIZER(root) \ + { NULL } + +#define RB_INIT(root) do { \ + (root)->rbh_root = NULL; \ +} while (0) + +#define RB_BLACK 0 +#define RB_RED 1 +#define RB_ENTRY(type) \ +struct { \ + struct type *rbe_left; /* left element */ \ + struct type *rbe_right; /* right element */ \ + struct type *rbe_parent; /* parent element */ \ + int rbe_color; /* node color */ \ +} + +#define RB_LEFT(elm, field) (elm)->field.rbe_left +#define RB_RIGHT(elm, field) (elm)->field.rbe_right +#define RB_PARENT(elm, field) (elm)->field.rbe_parent +#define RB_COLOR(elm, field) (elm)->field.rbe_color +#define RB_ROOT(head) (head)->rbh_root +#define RB_EMPTY(head) (RB_ROOT(head) == NULL) + +#define RB_SET(elm, parent, field) do { \ + RB_PARENT(elm, field) = parent; \ + RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL; \ + RB_COLOR(elm, field) = RB_RED; \ +} while (0) + +#define RB_SET_BLACKRED(black, red, field) do { \ + RB_COLOR(black, field) = RB_BLACK; \ + RB_COLOR(red, field) = RB_RED; \ +} while (0) + +#ifndef RB_AUGMENT +#define RB_AUGMENT(x) do {} while (0) +#endif + +#define RB_ROTATE_LEFT(head, elm, tmp, field) do { \ + (tmp) = RB_RIGHT(elm, field); \ + if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field))) { \ + RB_PARENT(RB_LEFT(tmp, field), field) = (elm); \ + } \ + RB_AUGMENT(elm); \ + if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field))) { \ + if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ + RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ + else \ + RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ + } else \ + (head)->rbh_root = (tmp); \ + RB_LEFT(tmp, field) = (elm); \ + RB_PARENT(elm, field) = (tmp); \ + RB_AUGMENT(tmp); \ + if ((RB_PARENT(tmp, field))) \ + RB_AUGMENT(RB_PARENT(tmp, field)); \ +} while (0) + +#define RB_ROTATE_RIGHT(head, elm, tmp, field) do { \ + (tmp) = RB_LEFT(elm, field); \ + if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field))) { \ + RB_PARENT(RB_RIGHT(tmp, field), field) = (elm); \ + } \ + RB_AUGMENT(elm); \ + if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field))) { \ + if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ + RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ + else \ + RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ + } else \ + (head)->rbh_root = (tmp); \ + RB_RIGHT(tmp, field) = (elm); \ + RB_PARENT(elm, field) = (tmp); \ + RB_AUGMENT(tmp); \ + if ((RB_PARENT(tmp, field))) \ + RB_AUGMENT(RB_PARENT(tmp, field)); \ +} while (0) + +/* Generates prototypes and inline functions */ +#define RB_PROTOTYPE(name, type, field, cmp) \ + RB_PROTOTYPE_INTERNAL(name, type, field, cmp,) +#define RB_PROTOTYPE_STATIC(name, type, field, cmp) \ + RB_PROTOTYPE_INTERNAL(name, type, field, cmp, __attribute__((__unused__)) static) +#define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr) \ +attr void name##_RB_INSERT_COLOR(struct name *, struct type *); \ +attr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *);\ +attr struct type *name##_RB_REMOVE(struct name *, struct type *); \ +attr struct type *name##_RB_INSERT(struct name *, struct type *); \ +attr struct type *name##_RB_FIND(struct name *, struct type *); \ +attr struct type *name##_RB_NFIND(struct name *, struct type *); \ +attr struct type *name##_RB_NEXT(struct type *); \ +attr struct type *name##_RB_PREV(struct type *); \ +attr struct type *name##_RB_MINMAX(struct name *, int); \ + \ + +/* Main rb operation. + * Moves node close to the key of elm to top + */ +#define RB_GENERATE(name, type, field, cmp) \ + RB_GENERATE_INTERNAL(name, type, field, cmp,) +#define RB_GENERATE_STATIC(name, type, field, cmp) \ + RB_GENERATE_INTERNAL(name, type, field, cmp, __attribute__((__unused__)) static) +#define RB_GENERATE_INTERNAL(name, type, field, cmp, attr) \ +attr void \ +name##_RB_INSERT_COLOR(struct name *head, struct type *elm) \ +{ \ + struct type *parent, *gparent, *tmp; \ + while ((parent = RB_PARENT(elm, field)) && \ + RB_COLOR(parent, field) == RB_RED) { \ + gparent = RB_PARENT(parent, field); \ + if (parent == RB_LEFT(gparent, field)) { \ + tmp = RB_RIGHT(gparent, field); \ + if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ + RB_COLOR(tmp, field) = RB_BLACK; \ + RB_SET_BLACKRED(parent, gparent, field);\ + elm = gparent; \ + continue; \ + } \ + if (RB_RIGHT(parent, field) == elm) { \ + RB_ROTATE_LEFT(head, parent, tmp, field);\ + tmp = parent; \ + parent = elm; \ + elm = tmp; \ + } \ + RB_SET_BLACKRED(parent, gparent, field); \ + RB_ROTATE_RIGHT(head, gparent, tmp, field); \ + } else { \ + tmp = RB_LEFT(gparent, field); \ + if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ + RB_COLOR(tmp, field) = RB_BLACK; \ + RB_SET_BLACKRED(parent, gparent, field);\ + elm = gparent; \ + continue; \ + } \ + if (RB_LEFT(parent, field) == elm) { \ + RB_ROTATE_RIGHT(head, parent, tmp, field);\ + tmp = parent; \ + parent = elm; \ + elm = tmp; \ + } \ + RB_SET_BLACKRED(parent, gparent, field); \ + RB_ROTATE_LEFT(head, gparent, tmp, field); \ + } \ + } \ + RB_COLOR(head->rbh_root, field) = RB_BLACK; \ +} \ + \ +attr void \ +name##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \ +{ \ + struct type *tmp; \ + while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) && \ + elm != RB_ROOT(head)) { \ + if (RB_LEFT(parent, field) == elm) { \ + tmp = RB_RIGHT(parent, field); \ + if (RB_COLOR(tmp, field) == RB_RED) { \ + RB_SET_BLACKRED(tmp, parent, field); \ + RB_ROTATE_LEFT(head, parent, tmp, field);\ + tmp = RB_RIGHT(parent, field); \ + } \ + if ((RB_LEFT(tmp, field) == NULL || \ + RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ + (RB_RIGHT(tmp, field) == NULL || \ + RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ + RB_COLOR(tmp, field) = RB_RED; \ + elm = parent; \ + parent = RB_PARENT(elm, field); \ + } else { \ + if (RB_RIGHT(tmp, field) == NULL || \ + RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\ + struct type *oleft; \ + if ((oleft = RB_LEFT(tmp, field)))\ + RB_COLOR(oleft, field) = RB_BLACK;\ + RB_COLOR(tmp, field) = RB_RED; \ + RB_ROTATE_RIGHT(head, tmp, oleft, field);\ + tmp = RB_RIGHT(parent, field); \ + } \ + RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ + RB_COLOR(parent, field) = RB_BLACK; \ + if (RB_RIGHT(tmp, field)) \ + RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\ + RB_ROTATE_LEFT(head, parent, tmp, field);\ + elm = RB_ROOT(head); \ + break; \ + } \ + } else { \ + tmp = RB_LEFT(parent, field); \ + if (RB_COLOR(tmp, field) == RB_RED) { \ + RB_SET_BLACKRED(tmp, parent, field); \ + RB_ROTATE_RIGHT(head, parent, tmp, field);\ + tmp = RB_LEFT(parent, field); \ + } \ + if ((RB_LEFT(tmp, field) == NULL || \ + RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ + (RB_RIGHT(tmp, field) == NULL || \ + RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ + RB_COLOR(tmp, field) = RB_RED; \ + elm = parent; \ + parent = RB_PARENT(elm, field); \ + } else { \ + if (RB_LEFT(tmp, field) == NULL || \ + RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\ + struct type *oright; \ + if ((oright = RB_RIGHT(tmp, field)))\ + RB_COLOR(oright, field) = RB_BLACK;\ + RB_COLOR(tmp, field) = RB_RED; \ + RB_ROTATE_LEFT(head, tmp, oright, field);\ + tmp = RB_LEFT(parent, field); \ + } \ + RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ + RB_COLOR(parent, field) = RB_BLACK; \ + if (RB_LEFT(tmp, field)) \ + RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\ + RB_ROTATE_RIGHT(head, parent, tmp, field);\ + elm = RB_ROOT(head); \ + break; \ + } \ + } \ + } \ + if (elm) \ + RB_COLOR(elm, field) = RB_BLACK; \ +} \ + \ +attr struct type * \ +name##_RB_REMOVE(struct name *head, struct type *elm) \ +{ \ + struct type *child, *parent, *old = elm; \ + int color; \ + if (RB_LEFT(elm, field) == NULL) \ + child = RB_RIGHT(elm, field); \ + else if (RB_RIGHT(elm, field) == NULL) \ + child = RB_LEFT(elm, field); \ + else { \ + struct type *left; \ + elm = RB_RIGHT(elm, field); \ + while ((left = RB_LEFT(elm, field))) \ + elm = left; \ + child = RB_RIGHT(elm, field); \ + parent = RB_PARENT(elm, field); \ + color = RB_COLOR(elm, field); \ + if (child) \ + RB_PARENT(child, field) = parent; \ + if (parent) { \ + if (RB_LEFT(parent, field) == elm) \ + RB_LEFT(parent, field) = child; \ + else \ + RB_RIGHT(parent, field) = child; \ + RB_AUGMENT(parent); \ + } else \ + RB_ROOT(head) = child; \ + if (RB_PARENT(elm, field) == old) \ + parent = elm; \ + (elm)->field = (old)->field; \ + if (RB_PARENT(old, field)) { \ + if (RB_LEFT(RB_PARENT(old, field), field) == old)\ + RB_LEFT(RB_PARENT(old, field), field) = elm;\ + else \ + RB_RIGHT(RB_PARENT(old, field), field) = elm;\ + RB_AUGMENT(RB_PARENT(old, field)); \ + } else \ + RB_ROOT(head) = elm; \ + RB_PARENT(RB_LEFT(old, field), field) = elm; \ + if (RB_RIGHT(old, field)) \ + RB_PARENT(RB_RIGHT(old, field), field) = elm; \ + if (parent) { \ + left = parent; \ + do { \ + RB_AUGMENT(left); \ + } while ((left = RB_PARENT(left, field))); \ + } \ + goto color; \ + } \ + parent = RB_PARENT(elm, field); \ + color = RB_COLOR(elm, field); \ + if (child) \ + RB_PARENT(child, field) = parent; \ + if (parent) { \ + if (RB_LEFT(parent, field) == elm) \ + RB_LEFT(parent, field) = child; \ + else \ + RB_RIGHT(parent, field) = child; \ + RB_AUGMENT(parent); \ + } else \ + RB_ROOT(head) = child; \ +color: \ + if (color == RB_BLACK) \ + name##_RB_REMOVE_COLOR(head, parent, child); \ + return (old); \ +} \ + \ +/* Inserts a node into the RB tree */ \ +attr struct type * \ +name##_RB_INSERT(struct name *head, struct type *elm) \ +{ \ + struct type *tmp; \ + struct type *parent = NULL; \ + int comp = 0; \ + tmp = RB_ROOT(head); \ + while (tmp) { \ + parent = tmp; \ + comp = (cmp)(elm, parent); \ + if (comp < 0) \ + tmp = RB_LEFT(tmp, field); \ + else if (comp > 0) \ + tmp = RB_RIGHT(tmp, field); \ + else \ + return (tmp); \ + } \ + RB_SET(elm, parent, field); \ + if (parent != NULL) { \ + if (comp < 0) \ + RB_LEFT(parent, field) = elm; \ + else \ + RB_RIGHT(parent, field) = elm; \ + RB_AUGMENT(parent); \ + } else \ + RB_ROOT(head) = elm; \ + name##_RB_INSERT_COLOR(head, elm); \ + return (NULL); \ +} \ + \ +/* Finds the node with the same key as elm */ \ +attr struct type * \ +name##_RB_FIND(struct name *head, struct type *elm) \ +{ \ + struct type *tmp = RB_ROOT(head); \ + int comp; \ + while (tmp) { \ + comp = cmp(elm, tmp); \ + if (comp < 0) \ + tmp = RB_LEFT(tmp, field); \ + else if (comp > 0) \ + tmp = RB_RIGHT(tmp, field); \ + else \ + return (tmp); \ + } \ + return (NULL); \ +} \ + \ +/* Finds the first node greater than or equal to the search key */ \ +attr struct type * \ +name##_RB_NFIND(struct name *head, struct type *elm) \ +{ \ + struct type *tmp = RB_ROOT(head); \ + struct type *res = NULL; \ + int comp; \ + while (tmp) { \ + comp = cmp(elm, tmp); \ + if (comp < 0) { \ + res = tmp; \ + tmp = RB_LEFT(tmp, field); \ + } \ + else if (comp > 0) \ + tmp = RB_RIGHT(tmp, field); \ + else \ + return (tmp); \ + } \ + return (res); \ +} \ + \ +/* ARGSUSED */ \ +attr struct type * \ +name##_RB_NEXT(struct type *elm) \ +{ \ + if (RB_RIGHT(elm, field)) { \ + elm = RB_RIGHT(elm, field); \ + while (RB_LEFT(elm, field)) \ + elm = RB_LEFT(elm, field); \ + } else { \ + if (RB_PARENT(elm, field) && \ + (elm == RB_LEFT(RB_PARENT(elm, field), field))) \ + elm = RB_PARENT(elm, field); \ + else { \ + while (RB_PARENT(elm, field) && \ + (elm == RB_RIGHT(RB_PARENT(elm, field), field)))\ + elm = RB_PARENT(elm, field); \ + elm = RB_PARENT(elm, field); \ + } \ + } \ + return (elm); \ +} \ + \ +/* ARGSUSED */ \ +attr struct type * \ +name##_RB_PREV(struct type *elm) \ +{ \ + if (RB_LEFT(elm, field)) { \ + elm = RB_LEFT(elm, field); \ + while (RB_RIGHT(elm, field)) \ + elm = RB_RIGHT(elm, field); \ + } else { \ + if (RB_PARENT(elm, field) && \ + (elm == RB_RIGHT(RB_PARENT(elm, field), field))) \ + elm = RB_PARENT(elm, field); \ + else { \ + while (RB_PARENT(elm, field) && \ + (elm == RB_LEFT(RB_PARENT(elm, field), field)))\ + elm = RB_PARENT(elm, field); \ + elm = RB_PARENT(elm, field); \ + } \ + } \ + return (elm); \ +} \ + \ +attr struct type * \ +name##_RB_MINMAX(struct name *head, int val) \ +{ \ + struct type *tmp = RB_ROOT(head); \ + struct type *parent = NULL; \ + while (tmp) { \ + parent = tmp; \ + if (val < 0) \ + tmp = RB_LEFT(tmp, field); \ + else \ + tmp = RB_RIGHT(tmp, field); \ + } \ + return (parent); \ +} + +#define RB_NEGINF -1 +#define RB_INF 1 + +#define RB_INSERT(name, x, y) name##_RB_INSERT(x, y) +#define RB_REMOVE(name, x, y) name##_RB_REMOVE(x, y) +#define RB_FIND(name, x, y) name##_RB_FIND(x, y) +#define RB_NFIND(name, x, y) name##_RB_NFIND(x, y) +#define RB_NEXT(name, x, y) name##_RB_NEXT(y) +#define RB_PREV(name, x, y) name##_RB_PREV(y) +#define RB_MIN(name, x) name##_RB_MINMAX(x, RB_NEGINF) +#define RB_MAX(name, x) name##_RB_MINMAX(x, RB_INF) + +#define RB_FOREACH(x, name, head) \ + for ((x) = RB_MIN(name, head); \ + (x) != NULL; \ + (x) = name##_RB_NEXT(x)) + +#define RB_FOREACH_SAFE(x, name, head, y) \ + for ((x) = RB_MIN(name, head); \ + ((x) != NULL) && ((y) = name##_RB_NEXT(x), 1); \ + (x) = (y)) + +#define RB_FOREACH_REVERSE(x, name, head) \ + for ((x) = RB_MAX(name, head); \ + (x) != NULL; \ + (x) = name##_RB_PREV(x)) + +#define RB_FOREACH_REVERSE_SAFE(x, name, head, y) \ + for ((x) = RB_MAX(name, head); \ + ((x) != NULL) && ((y) = name##_RB_PREV(x), 1); \ + (x) = (y)) + +#endif /* _SYS_TREE_H_ */ diff --git a/foobar/portable/openbsd-compat/usleep.c b/foobar/portable/openbsd-compat/usleep.c new file mode 100644 index 00000000..946befc9 --- /dev/null +++ b/foobar/portable/openbsd-compat/usleep.c @@ -0,0 +1,43 @@ + +/* + * Copyright (c) 1999-2004 Damien Miller <djm@mindrot.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#ifdef HAVE_SYS_SELECT_H +# include <sys/select.h> +#endif +#ifdef HAVE_SYS_TIME_H +# include <sys/time.h> +#endif + +#include <err.h> +#include <string.h> +#include <signal.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +int +usleep(unsigned int useconds) +{ + struct timespec ts; + + ts.tv_sec = useconds / 1000000; + ts.tv_nsec = (useconds % 1000000) * 1000; + return nanosleep(&ts, NULL); +} diff --git a/foobar/portable/openbsd-compat/vis.c b/foobar/portable/openbsd-compat/vis.c new file mode 100644 index 00000000..a49b4dc3 --- /dev/null +++ b/foobar/portable/openbsd-compat/vis.c @@ -0,0 +1,222 @@ +/* $OpenBSD: vis.c,v 1.19 2005/09/01 17:15:49 millert Exp $ */ +/*- + * Copyright (c) 1989, 1993 + * The Regents of the University of California. 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. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + */ + +/* OPENBSD ORIGINAL: lib/libc/gen/vis.c */ + +#include "includes.h" + +#include <ctype.h> +#include <string.h> + +#include "bsd-vis.h" + +#define isoctal(c) (((u_char)(c)) >= '0' && ((u_char)(c)) <= '7') +#define isvisible(c) \ + (((u_int)(c) <= UCHAR_MAX && isascii((u_char)(c)) && \ + (((c) != '*' && (c) != '?' && (c) != '[' && (c) != '#') || \ + (flag & VIS_GLOB) == 0) && isgraph((u_char)(c))) || \ + ((flag & VIS_SP) == 0 && (c) == ' ') || \ + ((flag & VIS_TAB) == 0 && (c) == '\t') || \ + ((flag & VIS_NL) == 0 && (c) == '\n') || \ + ((flag & VIS_SAFE) && ((c) == '\b' || \ + (c) == '\007' || (c) == '\r' || \ + isgraph((u_char)(c))))) + +/* + * vis - visually encode characters + */ +char * +vis(char *dst, int c, int flag, int nextc) +{ + if (isvisible(c)) { + *dst++ = c; + if (c == '\\' && (flag & VIS_NOSLASH) == 0) + *dst++ = '\\'; + *dst = '\0'; + return (dst); + } + + if (flag & VIS_CSTYLE) { + switch(c) { + case '\n': + *dst++ = '\\'; + *dst++ = 'n'; + goto done; + case '\r': + *dst++ = '\\'; + *dst++ = 'r'; + goto done; + case '\b': + *dst++ = '\\'; + *dst++ = 'b'; + goto done; + case '\a': + *dst++ = '\\'; + *dst++ = 'a'; + goto done; + case '\v': + *dst++ = '\\'; + *dst++ = 'v'; + goto done; + case '\t': + *dst++ = '\\'; + *dst++ = 't'; + goto done; + case '\f': + *dst++ = '\\'; + *dst++ = 'f'; + goto done; + case ' ': + *dst++ = '\\'; + *dst++ = 's'; + goto done; + case '\0': + *dst++ = '\\'; + *dst++ = '0'; + if (isoctal(nextc)) { + *dst++ = '0'; + *dst++ = '0'; + } + goto done; + } + } + if (((c & 0177) == ' ') || (flag & VIS_OCTAL) || + ((flag & VIS_GLOB) && (c == '*' || c == '?' || c == '[' || c == '#'))) { + *dst++ = '\\'; + *dst++ = ((u_char)c >> 6 & 07) + '0'; + *dst++ = ((u_char)c >> 3 & 07) + '0'; + *dst++ = ((u_char)c & 07) + '0'; + goto done; + } + if ((flag & VIS_NOSLASH) == 0) + *dst++ = '\\'; + if (c & 0200) { + c &= 0177; + *dst++ = 'M'; + } + if (iscntrl((u_char)c)) { + *dst++ = '^'; + if (c == 0177) + *dst++ = '?'; + else + *dst++ = c + '@'; + } else { + *dst++ = '-'; + *dst++ = c; + } +done: + *dst = '\0'; + return (dst); +} + +/* + * strvis, strnvis, strvisx - visually encode characters from src into dst + * + * Dst must be 4 times the size of src to account for possible + * expansion. The length of dst, not including the trailing NULL, + * is returned. + * + * Strnvis will write no more than siz-1 bytes (and will NULL terminate). + * The number of bytes needed to fully encode the string is returned. + * + * Strvisx encodes exactly len bytes from src into dst. + * This is useful for encoding a block of data. + */ +int +strvis(char *dst, const char *src, int flag) +{ + char c; + char *start; + + for (start = dst; (c = *src);) + dst = vis(dst, c, flag, *++src); + *dst = '\0'; + return (dst - start); +} + +int +strnvis(char *dst, const char *src, size_t siz, int flag) +{ + char *start, *end; + char tbuf[5]; + int c, i; + + i = 0; + for (start = dst, end = start + siz - 1; (c = *src) && dst < end; ) { + if (isvisible(c)) { + i = 1; + *dst++ = c; + if (c == '\\' && (flag & VIS_NOSLASH) == 0) { + /* need space for the extra '\\' */ + if (dst < end) + *dst++ = '\\'; + else { + dst--; + i = 2; + break; + } + } + src++; + } else { + i = vis(tbuf, c, flag, *++src) - tbuf; + if (dst + i <= end) { + memcpy(dst, tbuf, i); + dst += i; + } else { + src--; + break; + } + } + } + if (siz > 0) + *dst = '\0'; + if (dst + i > end) { + /* adjust return value for truncation */ + while ((c = *src)) + dst += vis(tbuf, c, flag, *++src) - tbuf; + } + return (dst - start); +} + +int +strvisx(char *dst, const char *src, size_t len, int flag) +{ + char c; + char *start; + + for (start = dst; len > 1; len--) { + c = *src; + dst = vis(dst, c, flag, *++src); + } + if (len) + dst = vis(dst, *src, flag, '\0'); + *dst = '\0'; + return (dst - start); +} diff --git a/foobar/portable/smtpd/Makefile b/foobar/portable/smtpd/Makefile new file mode 100644 index 00000000..a3dbc9d1 --- /dev/null +++ b/foobar/portable/smtpd/Makefile @@ -0,0 +1,10 @@ +# $OpenBSD: Makefile,v 1.18 2018/05/24 11:38:24 gilles Exp $ + +.include <bsd.own.mk> + +SUBDIR = smtpd +SUBDIR+= smtpctl +SUBDIR+= smtp +SUBDIR+= mail + +.include <bsd.subdir.mk> diff --git a/foobar/portable/smtpd/aliases.5 b/foobar/portable/smtpd/aliases.5 new file mode 100644 index 00000000..7c250c81 --- /dev/null +++ b/foobar/portable/smtpd/aliases.5 @@ -0,0 +1,102 @@ +.\" $OpenBSD: aliases.5,v 1.16 2020/04/23 21:28:10 jmc Exp $ +.\" +.\" Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: April 23 2020 $ +.Dt ALIASES 5 +.Os +.Sh NAME +.Nm aliases +.Nd aliases file for smtpd +.Sh DESCRIPTION +This manual page describes the format of the +.Nm +file, as used by +.Xr smtpd 8 . +An alias in its simplest form is used to assign an arbitrary name +to an email address, or a group of email addresses. +This provides a convenient way to send mail. +For example an alias could refer to all users of a group: +email to that alias would be sent to all members of the group. +Much more complex aliases can be defined however: +an alias can refer to other aliases, +be used to send mail to a file instead of another person, +or to execute various commands. +.Pp +Within the file, +.Ql # +is a comment delimiter; anything placed after it is discarded. +The file consists of key/value mappings of the form: +.Bd -filled -offset indent +key: value1, value2, value3, ... +.Ed +.Pp +.Em key +is always folded to lowercase before alias lookups to ensure that +there can be no ambiguity. +The key is expanded to the corresponding values, +which consist of one or more of the following: +.Bl -tag -width Ds +.It Em user +A user on the host machine. +The user must have a valid entry in the +.Xr passwd 5 +database file. +.It Ar /path/to/file +Append messages to +.Ar file , +specified by its absolute pathname. +.It | Ns Ar command +Pipe the message to +.Ar command +on its standard input. +The command is run under the privileges of the daemon's unprivileged account. +.It : Ns Ar include : Ns Ar /path/to/file +Include any definitions in +.Ar file +as alias entries. +The format of the file is identical to this one. +.It Ar user-part@domain-part +An email address in RFC 5322 format. +If an address extension is appended to the user-part, +it is first compared for an exact match. +It is then stripped so that an address such as user+ext@example.com +will only use the part that precedes +.Sq + +as a +.Em key . +.It Ar error : Ns Ar code message +A status code and message to return. +The code must be 3 digits, +starting 4XX (TempFail) or 5XX (PermFail). +The message must be present and can be freely chosen. +.El +.Sh FILES +.Bl -tag -width "/etc/mail/aliasesXXX" -compact +.It Pa /etc/mail/aliases +Default +.Nm +file. +.El +.Sh SEE ALSO +.Xr smtpd.conf 5 , +.Xr makemap 8 , +.Xr newaliases 8 , +.Xr smtpd 8 +.Sh HISTORY +The +.Nm +file format appeared in +.Bx 4.0 . diff --git a/foobar/portable/smtpd/aliases.c b/foobar/portable/smtpd/aliases.c new file mode 100644 index 00000000..0f8a5c1e --- /dev/null +++ b/foobar/portable/smtpd/aliases.c @@ -0,0 +1,234 @@ +/* $OpenBSD: aliases.c,v 1.78 2020/04/28 21:46:43 eric Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> + +#include <ctype.h> +#include <errno.h> +#include <event.h> +#include <imsg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#ifdef HAVE_UTIL_H +#include <util.h> +#endif +#ifdef HAVE_LIBUTIL_H +#include <libutil.h> +#endif + +#include "smtpd.h" +#include "log.h" + +static int aliases_expand_include(struct expand *, const char *); + +int +aliases_get(struct expand *expand, const char *username) +{ + struct expandnode *xn; + char buf[SMTPD_MAXLOCALPARTSIZE]; + size_t nbaliases; + int ret; + union lookup lk; + struct dispatcher *dsp; + struct table *mapping = NULL; + char *pbuf; + + dsp = dict_xget(env->sc_dispatchers, expand->rule->dispatcher); + mapping = table_find(env, dsp->u.local.table_alias); + + xlowercase(buf, username, sizeof(buf)); + + /* first, check if entry has a user-part tag */ + pbuf = strchr(buf, *env->sc_subaddressing_delim); + if (pbuf) { + ret = table_lookup(mapping, K_ALIAS, buf, &lk); + if (ret < 0) + return (-1); + if (ret) + goto expand; + *pbuf = '\0'; + } + + /* no user-part tag, try looking up user */ + ret = table_lookup(mapping, K_ALIAS, buf, &lk); + if (ret <= 0) + return ret; + +expand: + /* foreach node in table_alias expandtree, we merge */ + nbaliases = 0; + RB_FOREACH(xn, expandtree, &lk.expand->tree) { + if (xn->type == EXPAND_INCLUDE) + nbaliases += aliases_expand_include(expand, + xn->u.buffer); + else { + expand_insert(expand, xn); + nbaliases++; + } + } + + expand_free(lk.expand); + + log_debug("debug: aliases_get: returned %zd aliases", nbaliases); + return nbaliases; +} + +int +aliases_virtual_get(struct expand *expand, const struct mailaddr *maddr) +{ + struct expandnode *xn; + union lookup lk; + char buf[LINE_MAX]; + char user[LINE_MAX]; + char tag[LINE_MAX]; + char domain[LINE_MAX]; + char *pbuf; + int nbaliases; + int ret; + struct dispatcher *dsp; + struct table *mapping = NULL; + + dsp = dict_xget(env->sc_dispatchers, expand->rule->dispatcher); + mapping = table_find(env, dsp->u.local.table_virtual); + + if (!bsnprintf(user, sizeof(user), "%s", maddr->user)) + return 0; + if (!bsnprintf(domain, sizeof(domain), "%s", maddr->domain)) + return 0; + xlowercase(user, user, sizeof(user)); + xlowercase(domain, domain, sizeof(domain)); + + memset(tag, '\0', sizeof tag); + pbuf = strchr(user, *env->sc_subaddressing_delim); + if (pbuf) { + if (!bsnprintf(tag, sizeof(tag), "%s", pbuf + 1)) + return 0; + xlowercase(tag, tag, sizeof(tag)); + *pbuf = '\0'; + } + + /* first, check if entry has a user-part tag */ + if (tag[0]) { + if (!bsnprintf(buf, sizeof(buf), "%s%c%s@%s", + user, *env->sc_subaddressing_delim, tag, domain)) + return 0; + ret = table_lookup(mapping, K_ALIAS, buf, &lk); + if (ret < 0) + return (-1); + if (ret) + goto expand; + } + + /* then, check if entry exists without user-part tag */ + if (!bsnprintf(buf, sizeof(buf), "%s@%s", user, domain)) + return 0; + ret = table_lookup(mapping, K_ALIAS, buf, &lk); + if (ret < 0) + return (-1); + if (ret) + goto expand; + + if (tag[0]) { + /* Failed ? We lookup for username + user-part tag */ + if (!bsnprintf(buf, sizeof(buf), "%s%c%s", + user, *env->sc_subaddressing_delim, tag)) + return 0; + ret = table_lookup(mapping, K_ALIAS, buf, &lk); + if (ret < 0) + return (-1); + if (ret) + goto expand; + } + + /* Failed ? We lookup for username only */ + if (!bsnprintf(buf, sizeof(buf), "%s", user)) + return 0; + ret = table_lookup(mapping, K_ALIAS, buf, &lk); + if (ret < 0) + return (-1); + if (ret) + goto expand; + + /* Do not try catch-all entries if there is no domain */ + if (domain[0] == '\0') + return 0; + + if (!bsnprintf(buf, sizeof(buf), "@%s", domain)) + return 0; + /* Failed ? We lookup for catch all for virtual domain */ + ret = table_lookup(mapping, K_ALIAS, buf, &lk); + if (ret < 0) + return (-1); + if (ret) + goto expand; + + /* Failed ? We lookup for a *global* catch all */ + ret = table_lookup(mapping, K_ALIAS, "@", &lk); + if (ret <= 0) + return (ret); + +expand: + /* foreach node in table_virtual expand, we merge */ + nbaliases = 0; + RB_FOREACH(xn, expandtree, &lk.expand->tree) { + if (xn->type == EXPAND_INCLUDE) + nbaliases += aliases_expand_include(expand, + xn->u.buffer); + else { + expand_insert(expand, xn); + nbaliases++; + } + } + + expand_free(lk.expand); + + log_debug("debug: aliases_virtual_get: '%s' resolved to %d nodes", + buf, nbaliases); + + return nbaliases; +} + +static int +aliases_expand_include(struct expand *expand, const char *filename) +{ + FILE *fp; + char *line; + size_t len, lineno = 0; + char delim[3] = { '\\', '#', '\0' }; + + fp = fopen(filename, "r"); + if (fp == NULL) { + log_warn("warn: failed to open include file \"%s\".", filename); + return 0; + } + + while ((line = fparseln(fp, &len, &lineno, delim, 0)) != NULL) { + expand_line(expand, line, 0); + free(line); + } + + fclose(fp); + return 1; +} diff --git a/foobar/portable/smtpd/bounce.c b/foobar/portable/smtpd/bounce.c new file mode 100644 index 00000000..4a4a0992 --- /dev/null +++ b/foobar/portable/smtpd/bounce.c @@ -0,0 +1,820 @@ +/* $OpenBSD: bounce.c,v 1.82 2020/04/24 11:34:07 eric Exp $ */ + +/* + * Copyright (c) 2009 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> + +#include <err.h> +#include <errno.h> +#include <event.h> +#include <imsg.h> +#include <inttypes.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <limits.h> + +#include "smtpd.h" +#include "log.h" + +#define BOUNCE_MAXRUN 2 +#define BOUNCE_HIWAT 65535 + +enum { + BOUNCE_EHLO, + BOUNCE_MAIL, + BOUNCE_RCPT, + BOUNCE_DATA, + BOUNCE_DATA_NOTICE, + BOUNCE_DATA_MESSAGE, + BOUNCE_DATA_END, + BOUNCE_QUIT, + BOUNCE_CLOSE, +}; + +struct bounce_envelope { + TAILQ_ENTRY(bounce_envelope) entry; + uint64_t id; + struct mailaddr dest; + char *report; + uint8_t esc_class; + uint8_t esc_code; +}; + +struct bounce_message { + SPLAY_ENTRY(bounce_message) sp_entry; + TAILQ_ENTRY(bounce_message) entry; + uint32_t msgid; + struct delivery_bounce bounce; + char *smtpname; + char *to; + time_t timeout; + TAILQ_HEAD(, bounce_envelope) envelopes; +}; + +struct bounce_session { + char *smtpname; + struct bounce_message *msg; + FILE *msgfp; + int state; + struct io *io; + uint64_t boundary; +}; + +SPLAY_HEAD(bounce_message_tree, bounce_message); +static int bounce_message_cmp(const struct bounce_message *, + const struct bounce_message *); +SPLAY_PROTOTYPE(bounce_message_tree, bounce_message, sp_entry, + bounce_message_cmp); + +static void bounce_drain(void); +static void bounce_send(struct bounce_session *, const char *, ...); +static int bounce_next_message(struct bounce_session *); +static int bounce_next(struct bounce_session *); +static void bounce_delivery(struct bounce_message *, int, const char *); +static void bounce_status(struct bounce_session *, const char *, ...); +static void bounce_io(struct io *, int, void *); +static void bounce_timeout(int, short, void *); +static void bounce_free(struct bounce_session *); +static const char *action_str(const struct delivery_bounce *); + +static struct tree wait_fd; +static struct bounce_message_tree messages; +static TAILQ_HEAD(, bounce_message) pending; + +static int nmessage = 0; +static int running = 0; +static struct event ev_timer; + +static void +bounce_init(void) +{ + static int init = 0; + + if (init == 0) { + TAILQ_INIT(&pending); + SPLAY_INIT(&messages); + tree_init(&wait_fd); + evtimer_set(&ev_timer, bounce_timeout, NULL); + init = 1; + } +} + +void +bounce_add(uint64_t evpid) +{ + char buf[LINE_MAX], *line; + struct envelope evp; + struct bounce_message key, *msg; + struct bounce_envelope *be; + + bounce_init(); + + if (queue_envelope_load(evpid, &evp) == 0) { + m_create(p_scheduler, IMSG_QUEUE_DELIVERY_PERMFAIL, 0, 0, -1); + m_add_evpid(p_scheduler, evpid); + m_close(p_scheduler); + return; + } + + if (evp.type != D_BOUNCE) + errx(1, "bounce: evp:%016" PRIx64 " is not of type D_BOUNCE!", + evp.id); + + key.msgid = evpid_to_msgid(evpid); + key.bounce = evp.agent.bounce; + key.smtpname = evp.smtpname; + + switch (evp.esc_class) { + case ESC_STATUS_OK: + key.bounce.type = B_DELIVERED; + break; + case ESC_STATUS_TEMPFAIL: + key.bounce.type = B_DELAYED; + break; + default: + key.bounce.type = B_FAILED; + } + + key.bounce.dsn_ret = evp.dsn_ret; + key.bounce.ttl = evp.ttl; + msg = SPLAY_FIND(bounce_message_tree, &messages, &key); + if (msg == NULL) { + msg = xcalloc(1, sizeof(*msg)); + msg->msgid = key.msgid; + msg->bounce = key.bounce; + + TAILQ_INIT(&msg->envelopes); + + msg->smtpname = xstrdup(evp.smtpname); + (void)snprintf(buf, sizeof(buf), "%s@%s", evp.sender.user, + evp.sender.domain); + msg->to = xstrdup(buf); + nmessage += 1; + SPLAY_INSERT(bounce_message_tree, &messages, msg); + log_debug("debug: bounce: new message %08" PRIx32, + msg->msgid); + stat_increment("bounce.message", 1); + } else + TAILQ_REMOVE(&pending, msg, entry); + + line = evp.errorline; + if (strlen(line) > 4 && (*line == '1' || *line == '6')) + line += 4; + (void)snprintf(buf, sizeof(buf), "%s@%s: %s", evp.dest.user, + evp.dest.domain, line); + + be = xmalloc(sizeof *be); + be->id = evpid; + be->report = xstrdup(buf); + (void)strlcpy(be->dest.user, evp.dest.user, sizeof(be->dest.user)); + (void)strlcpy(be->dest.domain, evp.dest.domain, + sizeof(be->dest.domain)); + be->esc_class = evp.esc_class; + be->esc_code = evp.esc_code; + TAILQ_INSERT_TAIL(&msg->envelopes, be, entry); + log_debug("debug: bounce: adding report %16"PRIx64": %s", be->id, be->report); + + msg->timeout = time(NULL) + 1; + TAILQ_INSERT_TAIL(&pending, msg, entry); + + stat_increment("bounce.envelope", 1); + bounce_drain(); +} + +void +bounce_fd(int fd) +{ + struct bounce_session *s; + struct bounce_message *msg; + + log_debug("debug: bounce: got enqueue socket %d", fd); + + if (fd == -1 || TAILQ_EMPTY(&pending)) { + log_debug("debug: bounce: cancelling"); + if (fd != -1) + close(fd); + running -= 1; + bounce_drain(); + return; + } + + msg = TAILQ_FIRST(&pending); + + s = xcalloc(1, sizeof(*s)); + s->smtpname = xstrdup(msg->smtpname); + s->state = BOUNCE_EHLO; + s->io = io_new(); + io_set_callback(s->io, bounce_io, s); + io_set_fd(s->io, fd); + io_set_timeout(s->io, 30000); + io_set_read(s->io); + s->boundary = generate_uid(); + + log_debug("debug: bounce: new session %p", s); + stat_increment("bounce.session", 1); +} + +static void +bounce_timeout(int fd, short ev, void *arg) +{ + log_debug("debug: bounce: timeout"); + + bounce_drain(); +} + +static void +bounce_drain() +{ + struct bounce_message *msg; + struct timeval tv; + time_t t; + + log_debug("debug: bounce: drain: nmessage=%d running=%d", + nmessage, running); + + while (1) { + if (running >= BOUNCE_MAXRUN) { + log_debug("debug: bounce: max session reached"); + return; + } + + if (nmessage == 0) { + log_debug("debug: bounce: no more messages"); + return; + } + + if (running >= nmessage) { + log_debug("debug: bounce: enough sessions running"); + return; + } + + if ((msg = TAILQ_FIRST(&pending)) == NULL) { + log_debug("debug: bounce: no more pending messages"); + return; + } + + t = time(NULL); + if (msg->timeout > t) { + log_debug("debug: bounce: next message not ready yet"); + if (!evtimer_pending(&ev_timer, NULL)) { + log_debug("debug: bounce: setting timer"); + tv.tv_sec = msg->timeout - t; + tv.tv_usec = 0; + evtimer_add(&ev_timer, &tv); + } + return; + } + + log_debug("debug: bounce: requesting new enqueue socket..."); + m_compose(p_pony, IMSG_QUEUE_SMTP_SESSION, 0, 0, -1, NULL, 0); + + running += 1; + } +} + +static void +bounce_send(struct bounce_session *s, const char *fmt, ...) +{ + va_list ap; + char *p; + int len; + + va_start(ap, fmt); + if ((len = vasprintf(&p, fmt, ap)) == -1) + fatal("bounce: vasprintf"); + va_end(ap); + + log_trace(TRACE_BOUNCE, "bounce: %p: >>> %s", s, p); + + io_xprintf(s->io, "%s\r\n", p); + + free(p); +} + +static const char * +bounce_duration(long long int d) +{ + static char buf[32]; + + if (d < 60) { + (void)snprintf(buf, sizeof buf, "%lld second%s", d, + (d == 1) ? "" : "s"); + } else if (d < 3600) { + d = d / 60; + (void)snprintf(buf, sizeof buf, "%lld minute%s", d, + (d == 1) ? "" : "s"); + } + else if (d < 3600 * 24) { + d = d / 3600; + (void)snprintf(buf, sizeof buf, "%lld hour%s", d, + (d == 1) ? "" : "s"); + } + else { + d = d / (3600 * 24); + (void)snprintf(buf, sizeof buf, "%lld day%s", d, + (d == 1) ? "" : "s"); + } + return (buf); +} + +#define NOTICE_INTRO \ + " Hi!\r\n\r\n" \ + " This is the MAILER-DAEMON, please DO NOT REPLY to this email.\r\n" + +const char *notice_error = + " An error has occurred while attempting to deliver a message for\r\n" + " the following list of recipients:\r\n\r\n"; + +const char *notice_warning = + " A message is delayed for more than %s for the following\r\n" + " list of recipients:\r\n\r\n"; + +const char *notice_warning2 = + " Please note that this is only a temporary failure report.\r\n" + " The message is kept in the queue for up to %s.\r\n" + " You DO NOT NEED to re-send the message to these recipients.\r\n\r\n"; + +const char *notice_success = + " Your message was successfully delivered to these recipients.\r\n\r\n"; + +const char *notice_relay = + " Your message was relayed to these recipients.\r\n\r\n"; + +static int +bounce_next_message(struct bounce_session *s) +{ + struct bounce_message *msg; + char buf[LINE_MAX]; + int fd; + time_t now; + + again: + + now = time(NULL); + + TAILQ_FOREACH(msg, &pending, entry) { + if (msg->timeout > now) + continue; + if (strcmp(msg->smtpname, s->smtpname)) + continue; + break; + } + if (msg == NULL) + return (0); + + TAILQ_REMOVE(&pending, msg, entry); + SPLAY_REMOVE(bounce_message_tree, &messages, msg); + + if ((fd = queue_message_fd_r(msg->msgid)) == -1) { + bounce_delivery(msg, IMSG_QUEUE_DELIVERY_TEMPFAIL, + "Could not open message fd"); + goto again; + } + + if ((s->msgfp = fdopen(fd, "r")) == NULL) { + (void)snprintf(buf, sizeof(buf), "fdopen: %s", strerror(errno)); + log_warn("warn: bounce: fdopen"); + close(fd); + bounce_delivery(msg, IMSG_QUEUE_DELIVERY_TEMPFAIL, buf); + goto again; + } + + s->msg = msg; + return (1); +} + +static int +bounce_next(struct bounce_session *s) +{ + struct bounce_envelope *evp; + char *line = NULL; + size_t n, sz = 0; + ssize_t len; + + switch (s->state) { + case BOUNCE_EHLO: + bounce_send(s, "EHLO %s", s->smtpname); + s->state = BOUNCE_MAIL; + break; + + case BOUNCE_MAIL: + case BOUNCE_DATA_END: + log_debug("debug: bounce: %p: getting next message...", s); + if (bounce_next_message(s) == 0) { + log_debug("debug: bounce: %p: no more messages", s); + bounce_send(s, "QUIT"); + s->state = BOUNCE_CLOSE; + break; + } + log_debug("debug: bounce: %p: found message %08"PRIx32, + s, s->msg->msgid); + bounce_send(s, "MAIL FROM: <>"); + s->state = BOUNCE_RCPT; + break; + + case BOUNCE_RCPT: + bounce_send(s, "RCPT TO: <%s>", s->msg->to); + s->state = BOUNCE_DATA; + break; + + case BOUNCE_DATA: + bounce_send(s, "DATA"); + s->state = BOUNCE_DATA_NOTICE; + break; + + case BOUNCE_DATA_NOTICE: + /* Construct an appropriate notice. */ + + io_xprintf(s->io, + "Subject: Delivery status notification: %s\r\n" + "From: Mailer Daemon <MAILER-DAEMON@%s>\r\n" + "To: %s\r\n" + "Date: %s\r\n" + "MIME-Version: 1.0\r\n" + "Content-Type: multipart/mixed;" + "boundary=\"%16" PRIu64 "/%s\"\r\n" + "\r\n" + "This is a MIME-encapsulated message.\r\n" + "\r\n", + action_str(&s->msg->bounce), + s->smtpname, + s->msg->to, + time_to_text(time(NULL)), + s->boundary, + s->smtpname); + + io_xprintf(s->io, + "--%16" PRIu64 "/%s\r\n" + "Content-Description: Notification\r\n" + "Content-Type: text/plain; charset=us-ascii\r\n" + "\r\n" + NOTICE_INTRO + "\r\n", + s->boundary, s->smtpname); + + switch (s->msg->bounce.type) { + case B_FAILED: + io_xprint(s->io, notice_error); + break; + case B_DELAYED: + io_xprintf(s->io, notice_warning, + bounce_duration(s->msg->bounce.delay)); + break; + case B_DELIVERED: + io_xprint(s->io, s->msg->bounce.mta_without_dsn ? + notice_relay : notice_success); + break; + default: + log_warn("warn: bounce: unknown bounce_type"); + } + + TAILQ_FOREACH(evp, &s->msg->envelopes, entry) { + io_xprint(s->io, evp->report); + io_xprint(s->io, "\r\n"); + } + io_xprint(s->io, "\r\n"); + + if (s->msg->bounce.type == B_DELAYED) + io_xprintf(s->io, notice_warning2, + bounce_duration(s->msg->bounce.ttl)); + + io_xprintf(s->io, + " Below is a copy of the original message:\r\n" + "\r\n"); + + io_xprintf(s->io, + "--%16" PRIu64 "/%s\r\n" + "Content-Description: Delivery Report\r\n" + "Content-Type: message/delivery-status\r\n" + "\r\n", + s->boundary, s->smtpname); + + io_xprintf(s->io, + "Reporting-MTA: dns; %s\r\n" + "\r\n", + s->smtpname); + + TAILQ_FOREACH(evp, &s->msg->envelopes, entry) { + io_xprintf(s->io, + "Final-Recipient: rfc822; %s@%s\r\n" + "Action: %s\r\n" + "Status: %s\r\n" + "\r\n", + evp->dest.user, + evp->dest.domain, + action_str(&s->msg->bounce), + esc_code(evp->esc_class, evp->esc_code)); + } + + log_trace(TRACE_BOUNCE, "bounce: %p: >>> [... %zu bytes ...]", + s, io_queued(s->io)); + + s->state = BOUNCE_DATA_MESSAGE; + break; + + case BOUNCE_DATA_MESSAGE: + io_xprintf(s->io, + "--%16" PRIu64 "/%s\r\n" + "Content-Description: Message headers\r\n" + "Content-Type: text/rfc822-headers\r\n" + "\r\n", + s->boundary, s->smtpname); + + n = io_queued(s->io); + while (io_queued(s->io) < BOUNCE_HIWAT) { + if ((len = getline(&line, &sz, s->msgfp)) == -1) + break; + if (len == 1 && line[0] == '\n' && /* end of headers */ + s->msg->bounce.type == B_DELIVERED && + s->msg->bounce.dsn_ret == DSN_RETHDRS) { + free(line); + fclose(s->msgfp); + s->msgfp = NULL; + io_xprintf(s->io, + "\r\n--%16" PRIu64 "/%s--\r\n", s->boundary, + s->smtpname); + bounce_send(s, "."); + s->state = BOUNCE_DATA_END; + return (0); + } + line[len - 1] = '\0'; + io_xprintf(s->io, "%s%s\r\n", + (len == 2 && line[0] == '.') ? "." : "", line); + } + free(line); + + if (ferror(s->msgfp)) { + fclose(s->msgfp); + s->msgfp = NULL; + bounce_delivery(s->msg, IMSG_QUEUE_DELIVERY_TEMPFAIL, + "Error reading message"); + s->msg = NULL; + return (-1); + } + + io_xprintf(s->io, + "\r\n--%16" PRIu64 "/%s--\r\n", s->boundary, s->smtpname); + + log_trace(TRACE_BOUNCE, "bounce: %p: >>> [... %zu bytes ...]", + s, io_queued(s->io) - n); + + if (feof(s->msgfp)) { + fclose(s->msgfp); + s->msgfp = NULL; + bounce_send(s, "."); + s->state = BOUNCE_DATA_END; + } + break; + + case BOUNCE_QUIT: + bounce_send(s, "QUIT"); + s->state = BOUNCE_CLOSE; + break; + + default: + fatalx("bounce: bad state"); + } + + return (0); +} + + +static void +bounce_delivery(struct bounce_message *msg, int delivery, const char *status) +{ + struct bounce_envelope *be; + struct envelope evp; + size_t n; + const char *f; + + n = 0; + while ((be = TAILQ_FIRST(&msg->envelopes))) { + if (delivery == IMSG_QUEUE_DELIVERY_TEMPFAIL) { + if (queue_envelope_load(be->id, &evp) == 0) { + fatalx("could not reload envelope!"); + } + evp.retry++; + evp.lasttry = msg->timeout; + envelope_set_errormsg(&evp, "%s", status); + queue_envelope_update(&evp); + m_create(p_scheduler, delivery, 0, 0, -1); + m_add_envelope(p_scheduler, &evp); + m_close(p_scheduler); + } else { + m_create(p_scheduler, delivery, 0, 0, -1); + m_add_evpid(p_scheduler, be->id); + m_close(p_scheduler); + queue_envelope_delete(be->id); + } + TAILQ_REMOVE(&msg->envelopes, be, entry); + free(be->report); + free(be); + n += 1; + } + + + if (delivery == IMSG_QUEUE_DELIVERY_TEMPFAIL) + f = "TempFail"; + else if (delivery == IMSG_QUEUE_DELIVERY_PERMFAIL) + f = "PermFail"; + else + f = NULL; + + if (f) + log_warnx("warn: %s injecting failure report on message %08" + PRIx32 " to <%s> for %zu envelope%s: %s", + f, msg->msgid, msg->to, n, n > 1 ? "s":"", status); + + nmessage -= 1; + stat_decrement("bounce.message", 1); + stat_decrement("bounce.envelope", n); + free(msg->smtpname); + free(msg->to); + free(msg); +} + +static void +bounce_status(struct bounce_session *s, const char *fmt, ...) +{ + va_list ap; + char *status; + int len, delivery; + + /* Ignore if there is no message */ + if (s->msg == NULL) + return; + + va_start(ap, fmt); + if ((len = vasprintf(&status, fmt, ap)) == -1) + fatal("bounce: vasprintf"); + va_end(ap); + + if (*status == '2') + delivery = IMSG_QUEUE_DELIVERY_OK; + else if (*status == '5' || *status == '6') + delivery = IMSG_QUEUE_DELIVERY_PERMFAIL; + else + delivery = IMSG_QUEUE_DELIVERY_TEMPFAIL; + + bounce_delivery(s->msg, delivery, status); + s->msg = NULL; + if (s->msgfp) + fclose(s->msgfp); + + free(status); +} + +static void +bounce_free(struct bounce_session *s) +{ + log_debug("debug: bounce: %p: deleting session", s); + + io_free(s->io); + + free(s->smtpname); + free(s); + + running -= 1; + stat_decrement("bounce.session", 1); + bounce_drain(); +} + +static void +bounce_io(struct io *io, int evt, void *arg) +{ + struct bounce_session *s = arg; + const char *error; + char *line, *msg; + int cont; + size_t len; + + log_trace(TRACE_IO, "bounce: %p: %s %s", s, io_strevent(evt), + io_strio(io)); + + switch (evt) { + case IO_DATAIN: + nextline: + line = io_getline(s->io, &len); + if (line == NULL && io_datalen(s->io) >= LINE_MAX) { + bounce_status(s, "Input too long"); + bounce_free(s); + return; + } + + if (line == NULL) + break; + + /* Strip trailing '\r' */ + if (len && line[len - 1] == '\r') + line[--len] = '\0'; + + log_trace(TRACE_BOUNCE, "bounce: %p: <<< %s", s, line); + + if ((error = parse_smtp_response(line, len, &msg, &cont))) { + bounce_status(s, "Bad response: %s", error); + bounce_free(s); + return; + } + if (cont) + goto nextline; + + if (s->state == BOUNCE_CLOSE) { + bounce_free(s); + return; + } + + if (line[0] != '2' && line[0] != '3') { /* fail */ + bounce_status(s, "%s", line); + s->state = BOUNCE_QUIT; + } else if (s->state == BOUNCE_DATA_END) { /* accepted */ + bounce_status(s, "%s", line); + } + + if (bounce_next(s) == -1) { + bounce_free(s); + return; + } + + io_set_write(io); + break; + + case IO_LOWAT: + if (s->state == BOUNCE_DATA_MESSAGE) + if (bounce_next(s) == -1) { + bounce_free(s); + return; + } + if (io_queued(s->io) == 0) + io_set_read(io); + break; + + default: + bounce_status(s, "442 i/o error %d", evt); + bounce_free(s); + break; + } +} + +static int +bounce_message_cmp(const struct bounce_message *a, + const struct bounce_message *b) +{ + int r; + + if (a->msgid < b->msgid) + return (-1); + if (a->msgid > b->msgid) + return (1); + if ((r = strcmp(a->smtpname, b->smtpname))) + return (r); + + return memcmp(&a->bounce, &b->bounce, sizeof (a->bounce)); +} + +static const char * +action_str(const struct delivery_bounce *b) +{ + switch (b->type) { + case B_FAILED: + return ("failed"); + case B_DELAYED: + return ("delayed"); + case B_DELIVERED: + if (b->mta_without_dsn) + return ("relayed"); + + return ("delivered"); + default: + log_warn("warn: bounce: unknown bounce_type"); + return (""); + } +} + +SPLAY_GENERATE(bounce_message_tree, bounce_message, sp_entry, + bounce_message_cmp); diff --git a/foobar/portable/smtpd/ca.c b/foobar/portable/smtpd/ca.c new file mode 100644 index 00000000..a27db87a --- /dev/null +++ b/foobar/portable/smtpd/ca.c @@ -0,0 +1,777 @@ +/* $OpenBSD: ca.c,v 1.36 2019/09/21 07:46:53 semarie Exp $ */ + +/* + * Copyright (c) 2014 Reyk Floeter <reyk@openbsd.org> + * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/tree.h> + +#include <grp.h> /* needed for setgroups */ +#include <err.h> +#include <imsg.h> +#include <limits.h> +#include <pwd.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <openssl/ssl.h> +#include <openssl/pem.h> +#include <openssl/evp.h> +#include <openssl/ecdsa.h> +#include <openssl/rsa.h> +#include <openssl/engine.h> +#include <openssl/err.h> + +#include "smtpd.h" +#include "log.h" +#include "ssl.h" + +static int ca_verify_cb(int, X509_STORE_CTX *); + +static int rsae_send_imsg(int, const unsigned char *, unsigned char *, + RSA *, int, unsigned int); +static int rsae_pub_enc(int, const unsigned char *, unsigned char *, + RSA *, int); +static int rsae_pub_dec(int,const unsigned char *, unsigned char *, + RSA *, int); +static int rsae_priv_enc(int, const unsigned char *, unsigned char *, + RSA *, int); +static int rsae_priv_dec(int, const unsigned char *, unsigned char *, + RSA *, int); +static int rsae_mod_exp(BIGNUM *, const BIGNUM *, RSA *, BN_CTX *); +static int rsae_bn_mod_exp(BIGNUM *, const BIGNUM *, const BIGNUM *, + const BIGNUM *, BN_CTX *, BN_MONT_CTX *); +static int rsae_init(RSA *); +static int rsae_finish(RSA *); +static int rsae_keygen(RSA *, int, BIGNUM *, BN_GENCB *); + +#if defined(SUPPORT_ECDSA) +static ECDSA_SIG *ecdsae_do_sign(const unsigned char *, int, const BIGNUM *, + const BIGNUM *, EC_KEY *); +static int ecdsae_sign_setup(EC_KEY *, BN_CTX *, BIGNUM **, BIGNUM **); +static int ecdsae_do_verify(const unsigned char *, int, const ECDSA_SIG *, + EC_KEY *); +#endif + +static uint64_t reqid = 0; + +static void +ca_shutdown(void) +{ + log_debug("debug: ca agent exiting"); + _exit(0); +} + +int +ca(void) +{ + struct passwd *pw; + + purge_config(PURGE_LISTENERS|PURGE_TABLES|PURGE_RULES|PURGE_DISPATCHERS); + + if ((pw = getpwnam(SMTPD_USER)) == NULL) + fatalx("unknown user " SMTPD_USER); + + if (chroot(PATH_CHROOT) == -1) + fatal("ca: chroot"); + if (chdir("/") == -1) + fatal("ca: chdir(\"/\")"); + + config_process(PROC_CA); + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("ca: cannot drop privileges"); + + imsg_callback = ca_imsg; + event_init(); + + signal(SIGINT, SIG_IGN); + signal(SIGTERM, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + + config_peer(PROC_CONTROL); + config_peer(PROC_PARENT); + config_peer(PROC_PONY); + + /* Ignore them until we get our config */ + mproc_disable(p_pony); + +#if HAVE_PLEDGE + if (pledge("stdio", NULL) == -1) + err(1, "pledge"); +#endif + + event_dispatch(); + fatalx("exited event loop"); + + return (0); +} + +void +ca_init(void) +{ + BIO *in = NULL; + EVP_PKEY *pkey = NULL; + struct pki *pki; + const char *k; + void *iter_dict; + + log_debug("debug: init private ssl-tree"); + iter_dict = NULL; + while (dict_iter(env->sc_pki_dict, &iter_dict, &k, (void **)&pki)) { + if (pki->pki_key == NULL) + continue; + + if ((in = BIO_new_mem_buf(pki->pki_key, + pki->pki_key_len)) == NULL) + fatalx("ca_launch: key"); + + if ((pkey = PEM_read_bio_PrivateKey(in, + NULL, NULL, NULL)) == NULL) + fatalx("ca_launch: PEM"); + BIO_free(in); + + pki->pki_pkey = pkey; + + freezero(pki->pki_key, pki->pki_key_len); + pki->pki_key = NULL; + } +} + +static int +ca_verify_cb(int ok, X509_STORE_CTX *ctx) +{ + switch (X509_STORE_CTX_get_error(ctx)) { + case X509_V_OK: + break; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: + break; + case X509_V_ERR_CERT_NOT_YET_VALID: + case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: + break; + case X509_V_ERR_CERT_HAS_EXPIRED: + case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: + break; + case X509_V_ERR_NO_EXPLICIT_POLICY: + break; + } + return ok; +} + +int +ca_X509_verify(void *certificate, void *chain, const char *CAfile, + const char *CRLfile, const char **errstr) +{ + X509_STORE *store = NULL; + X509_STORE_CTX *xsc = NULL; + int ret = 0; + long error = 0; + + if ((store = X509_STORE_new()) == NULL) + goto end; + + if (!X509_STORE_load_locations(store, CAfile, NULL)) { + log_warn("warn: unable to load CA file %s", CAfile); + goto end; + } + X509_STORE_set_default_paths(store); + + if ((xsc = X509_STORE_CTX_new()) == NULL) + goto end; + + if (X509_STORE_CTX_init(xsc, store, certificate, chain) != 1) + goto end; + + X509_STORE_CTX_set_verify_cb(xsc, ca_verify_cb); + + ret = X509_verify_cert(xsc); + +end: + *errstr = NULL; + if (ret != 1) { + if (xsc) { + error = X509_STORE_CTX_get_error(xsc); + *errstr = X509_verify_cert_error_string(error); + } + else if (ERR_peek_last_error()) + *errstr = ERR_error_string(ERR_peek_last_error(), NULL); + } + + X509_STORE_CTX_free(xsc); + X509_STORE_free(store); + + return ret > 0 ? 1 : 0; +} + +void +ca_imsg(struct mproc *p, struct imsg *imsg) +{ + RSA *rsa = NULL; +#if defined(SUPPORT_ECDSA) + EC_KEY *ecdsa = NULL; +#endif + const void *from = NULL; + unsigned char *to = NULL; + struct msg m; + const char *pkiname; + size_t flen, tlen, padding; +#if defined(SUPPORT_ECDSA) + int buf_len; +#endif + struct pki *pki; + int ret = 0; + uint64_t id; + int v; + + if (imsg == NULL) + ca_shutdown(); + + switch (imsg->hdr.type) { + case IMSG_CONF_START: + return; + case IMSG_CONF_END: + ca_init(); + + /* Start fulfilling requests */ + mproc_enable(p_pony); + return; + + case IMSG_CTL_VERBOSE: + m_msg(&m, imsg); + m_get_int(&m, &v); + m_end(&m); + log_trace_verbose(v); + return; + + case IMSG_CTL_PROFILE: + m_msg(&m, imsg); + m_get_int(&m, &v); + m_end(&m); + profiling = v; + return; + + case IMSG_CA_RSA_PRIVENC: + case IMSG_CA_RSA_PRIVDEC: + m_msg(&m, imsg); + m_get_id(&m, &id); + m_get_string(&m, &pkiname); + m_get_data(&m, &from, &flen); + m_get_size(&m, &tlen); + m_get_size(&m, &padding); + m_end(&m); + + pki = dict_get(env->sc_pki_dict, pkiname); + if (pki == NULL || pki->pki_pkey == NULL || + (rsa = EVP_PKEY_get1_RSA(pki->pki_pkey)) == NULL) + fatalx("ca_imsg: invalid pki"); + + if ((to = calloc(1, tlen)) == NULL) + fatalx("ca_imsg: calloc"); + + switch (imsg->hdr.type) { + case IMSG_CA_RSA_PRIVENC: + ret = RSA_private_encrypt(flen, from, to, rsa, + padding); + break; + case IMSG_CA_RSA_PRIVDEC: + ret = RSA_private_decrypt(flen, from, to, rsa, + padding); + break; + } + + m_create(p, imsg->hdr.type, 0, 0, -1); + m_add_id(p, id); + m_add_int(p, ret); + if (ret > 0) + m_add_data(p, to, (size_t)ret); + m_close(p); + + free(to); + RSA_free(rsa); + return; + +#if defined(SUPPORT_ECDSA) + case IMSG_CA_ECDSA_SIGN: + m_msg(&m, imsg); + m_get_id(&m, &id); + m_get_string(&m, &pkiname); + m_get_data(&m, &from, &flen); + m_end(&m); + + pki = dict_get(env->sc_pki_dict, pkiname); + if (pki == NULL || pki->pki_pkey == NULL || + (ecdsa = EVP_PKEY_get1_EC_KEY(pki->pki_pkey)) == NULL) + fatalx("ca_imsg: invalid pki"); + + buf_len = ECDSA_size(ecdsa); + if ((to = calloc(1, buf_len)) == NULL) + fatalx("ca_imsg: calloc"); + ret = ECDSA_sign(0, from, flen, to, &buf_len, ecdsa); + m_create(p, imsg->hdr.type, 0, 0, -1); + m_add_id(p, id); + m_add_int(p, ret); + if (ret > 0) + m_add_data(p, to, (size_t)buf_len); + m_close(p); + free(to); + EC_KEY_free(ecdsa); + return; +#endif + } + errx(1, "ca_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); +} + +/* + * RSA privsep engine (called from unprivileged processes) + */ + +const RSA_METHOD *rsa_default = NULL; + +static RSA_METHOD *rsae_method = NULL; + +static int +rsae_send_imsg(int flen, const unsigned char *from, unsigned char *to, + RSA *rsa, int padding, unsigned int cmd) +{ + int ret = 0; + struct imsgbuf *ibuf; + struct imsg imsg; + int n, done = 0; + const void *toptr; + char *pkiname; + size_t tlen; + struct msg m; + uint64_t id; + + if ((pkiname = RSA_get_ex_data(rsa, 0)) == NULL) + return (0); + + /* + * Send a synchronous imsg because we cannot defer the RSA + * operation in OpenSSL's engine layer. + */ + m_create(p_ca, cmd, 0, 0, -1); + reqid++; + m_add_id(p_ca, reqid); + m_add_string(p_ca, pkiname); + m_add_data(p_ca, (const void *)from, (size_t)flen); + m_add_size(p_ca, (size_t)RSA_size(rsa)); + m_add_size(p_ca, (size_t)padding); + m_flush(p_ca); + + ibuf = &p_ca->imsgbuf; + + while (!done) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatalx("imsg_read"); + if (n == 0) + fatalx("pipe closed"); + + while (!done) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatalx("imsg_get error"); + if (n == 0) + break; + + log_imsg(PROC_PONY, PROC_CA, &imsg); + + switch (imsg.hdr.type) { + case IMSG_CA_RSA_PRIVENC: + case IMSG_CA_RSA_PRIVDEC: + break; + default: + /* Another imsg is queued up in the buffer */ + pony_imsg(p_ca, &imsg); + imsg_free(&imsg); + continue; + } + + m_msg(&m, &imsg); + m_get_id(&m, &id); + if (id != reqid) + fatalx("invalid response id"); + m_get_int(&m, &ret); + if (ret > 0) + m_get_data(&m, &toptr, &tlen); + m_end(&m); + + if (ret > 0) + memcpy(to, toptr, tlen); + done = 1; + + imsg_free(&imsg); + } + } + mproc_event_add(p_ca); + + return (ret); +} + +static int +rsae_pub_enc(int flen,const unsigned char *from, unsigned char *to, RSA *rsa, + int padding) +{ + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + return (RSA_meth_get_pub_enc(rsa_default)(flen, from, to, rsa, padding)); +} + +static int +rsae_pub_dec(int flen,const unsigned char *from, unsigned char *to, RSA *rsa, + int padding) +{ + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + return (RSA_meth_get_pub_dec(rsa_default)(flen, from, to, rsa, padding)); +} + +static int +rsae_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, + int padding) +{ + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + if (RSA_get_ex_data(rsa, 0) != NULL) + return (rsae_send_imsg(flen, from, to, rsa, padding, + IMSG_CA_RSA_PRIVENC)); + return (RSA_meth_get_priv_enc(rsa_default)(flen, from, to, rsa, padding)); +} + +static int +rsae_priv_dec(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, + int padding) +{ + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + if (RSA_get_ex_data(rsa, 0) != NULL) + return (rsae_send_imsg(flen, from, to, rsa, padding, + IMSG_CA_RSA_PRIVDEC)); + + return (RSA_meth_get_priv_dec(rsa_default)(flen, from, to, rsa, padding)); +} + +static int +rsae_mod_exp(BIGNUM *r0, const BIGNUM *I, RSA *rsa, BN_CTX *ctx) +{ + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + return (RSA_meth_get_mod_exp(rsa_default)(r0, I, rsa, ctx)); +} + +static int +rsae_bn_mod_exp(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, + const BIGNUM *m, BN_CTX *ctx, BN_MONT_CTX *m_ctx) +{ + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + return (RSA_meth_get_bn_mod_exp(rsa_default)(r, a, p, m, ctx, m_ctx)); +} + +static int +rsae_init(RSA *rsa) +{ + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + if (RSA_meth_get_init(rsa_default) == NULL) + return (1); + return (RSA_meth_get_init(rsa_default)(rsa)); +} + +static int +rsae_finish(RSA *rsa) +{ + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + if (RSA_meth_get_finish(rsa_default) == NULL) + return (1); + return (RSA_meth_get_finish(rsa_default)(rsa)); +} + +static int +rsae_keygen(RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb) +{ + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + return (RSA_meth_get_keygen(rsa_default)(rsa, bits, e, cb)); +} + + +#if defined(SUPPORT_ECDSA) +/* + * ECDSA privsep engine (called from unprivileged processes) + */ + +const ECDSA_METHOD *ecdsa_default = NULL; + +static ECDSA_METHOD *ecdsae_method = NULL; + +ECDSA_METHOD * +ECDSA_METHOD_new_temporary(const char *name, int); + +ECDSA_METHOD * +ECDSA_METHOD_new_temporary(const char *name, int flags) +{ + ECDSA_METHOD *ecdsa; + + if ((ecdsa = calloc(1, sizeof (*ecdsa))) == NULL) + return NULL; + + if ((ecdsa->name = strdup(name)) == NULL) { + free(ecdsa); + return NULL; + } + + ecdsa->flags = flags; + return ecdsa; +} + +static ECDSA_SIG * +ecdsae_send_enc_imsg(const unsigned char *dgst, int dgst_len, + const BIGNUM *inv, const BIGNUM *rp, EC_KEY *eckey) +{ + int ret = 0; + struct imsgbuf *ibuf; + struct imsg imsg; + int n, done = 0; + const void *toptr; + char *pkiname; + size_t tlen; + struct msg m; + uint64_t id; + ECDSA_SIG *sig = NULL; + + if ((pkiname = ECDSA_get_ex_data(eckey, 0)) == NULL) + return (0); + + /* + * Send a synchronous imsg because we cannot defer the ECDSA + * operation in OpenSSL's engine layer. + */ + m_create(p_ca, IMSG_CA_ECDSA_SIGN, 0, 0, -1); + reqid++; + m_add_id(p_ca, reqid); + m_add_string(p_ca, pkiname); + m_add_data(p_ca, (const void *)dgst, (size_t)dgst_len); + m_flush(p_ca); + + ibuf = &p_ca->imsgbuf; + + while (!done) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatalx("imsg_read"); + if (n == 0) + fatalx("pipe closed"); + while (!done) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatalx("imsg_get error"); + if (n == 0) + break; + + log_imsg(PROC_PONY, PROC_CA, &imsg); + + switch (imsg.hdr.type) { + case IMSG_CA_ECDSA_SIGN: + break; + default: + /* Another imsg is queued up in the buffer */ + pony_imsg(p_ca, &imsg); + imsg_free(&imsg); + continue; + } + + m_msg(&m, &imsg); + m_get_id(&m, &id); + if (id != reqid) + fatalx("invalid response id"); + m_get_int(&m, &ret); + if (ret > 0) + m_get_data(&m, &toptr, &tlen); + m_end(&m); + done = 1; + + if (ret > 0) + d2i_ECDSA_SIG(&sig, (const unsigned char **)&toptr, tlen); + imsg_free(&imsg); + } + } + mproc_event_add(p_ca); + + return (sig); +} + +ECDSA_SIG * +ecdsae_do_sign(const unsigned char *dgst, int dgst_len, + const BIGNUM *inv, const BIGNUM *rp, EC_KEY *eckey) +{ + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + if (ECDSA_get_ex_data(eckey, 0) != NULL) + return (ecdsae_send_enc_imsg(dgst, dgst_len, inv, rp, eckey)); + return (ecdsa_default->ecdsa_do_sign(dgst, dgst_len, inv, rp, eckey)); +} + +int +ecdsae_sign_setup(EC_KEY *eckey, BN_CTX *ctx, BIGNUM **kinv, + BIGNUM **r) +{ + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + return (ecdsa_default->ecdsa_sign_setup(eckey, ctx, kinv, r)); +} + +int +ecdsae_do_verify(const unsigned char *dgst, int dgst_len, + const ECDSA_SIG *sig, EC_KEY *eckey) +{ + log_debug("debug: %s: %s", proc_name(smtpd_process), __func__); + return (ecdsa_default->ecdsa_do_verify(dgst, dgst_len, sig, eckey)); +} +#endif + +static void +rsa_engine_init(void) +{ + ENGINE *e; + const char *errstr, *name; + + if ((rsae_method = RSA_meth_new("RSA privsep engine", 0)) == NULL) { + errstr = "RSA_meth_new"; + goto fail; + } + + RSA_meth_set_pub_enc(rsae_method, rsae_pub_enc); + RSA_meth_set_pub_dec(rsae_method, rsae_pub_dec); + RSA_meth_set_priv_enc(rsae_method, rsae_priv_enc); + RSA_meth_set_priv_dec(rsae_method, rsae_priv_dec); + RSA_meth_set_mod_exp(rsae_method, rsae_mod_exp); + RSA_meth_set_bn_mod_exp(rsae_method, rsae_bn_mod_exp); + RSA_meth_set_init(rsae_method, rsae_init); + RSA_meth_set_finish(rsae_method, rsae_finish); + RSA_meth_set_keygen(rsae_method, rsae_keygen); + + if ((e = ENGINE_get_default_RSA()) == NULL) { + if ((e = ENGINE_new()) == NULL) { + errstr = "ENGINE_new"; + goto fail; + } + if (!ENGINE_set_name(e, RSA_meth_get0_name(rsae_method))) { + errstr = "ENGINE_set_name"; + goto fail; + } + if ((rsa_default = RSA_get_default_method()) == NULL) { + errstr = "RSA_get_default_method"; + goto fail; + } + } else if ((rsa_default = ENGINE_get_RSA(e)) == NULL) { + errstr = "ENGINE_get_RSA"; + goto fail; + } + + if ((name = ENGINE_get_name(e)) == NULL) + name = "unknown RSA engine"; + + log_debug("debug: %s: using %s", __func__, name); + + if (RSA_meth_get_mod_exp(rsa_default) == NULL) + RSA_meth_set_mod_exp(rsae_method, NULL); + if (RSA_meth_get_bn_mod_exp(rsa_default) == NULL) + RSA_meth_set_bn_mod_exp(rsae_method, NULL); + if (RSA_meth_get_keygen(rsa_default) == NULL) + RSA_meth_set_keygen(rsae_method, NULL); + RSA_meth_set_flags(rsae_method, + RSA_meth_get_flags(rsa_default) | RSA_METHOD_FLAG_NO_CHECK); + RSA_meth_set0_app_data(rsae_method, + RSA_meth_get0_app_data(rsa_default)); + + if (!ENGINE_set_RSA(e, rsae_method)) { + errstr = "ENGINE_set_RSA"; + goto fail; + } + if (!ENGINE_set_default_RSA(e)) { + errstr = "ENGINE_set_default_RSA"; + goto fail; + } + + return; + + fail: + ssl_error(errstr); + fatalx("%s", errstr); +} + +#if defined(SUPPORT_ECDSA) +static void +ecdsa_engine_init(void) +{ + ENGINE *e; + const char *errstr, *name; + + if ((ecdsae_method = ECDSA_METHOD_new_temporary("ECDSA privsep engine", 0)) == NULL) { + errstr = "ECDSA_METHOD_new_temporary"; + goto fail; + } + + ecdsae_method->ecdsa_do_sign = ecdsae_do_sign; + ecdsae_method->ecdsa_sign_setup = ecdsae_sign_setup; + ecdsae_method->ecdsa_do_verify = ecdsae_do_verify; + + if ((e = ENGINE_get_default_ECDSA()) == NULL) { + if ((e = ENGINE_new()) == NULL) { + errstr = "ENGINE_new"; + goto fail; + } + if (!ENGINE_set_name(e, ecdsae_method->name)) { + errstr = "ENGINE_set_name"; + goto fail; + } + if ((ecdsa_default = ECDSA_get_default_method()) == NULL) { + errstr = "ECDSA_get_default_method"; + goto fail; + } + } else if ((ecdsa_default = ENGINE_get_ECDSA(e)) == NULL) { + errstr = "ENGINE_get_ECDSA"; + goto fail; + } + + if ((name = ENGINE_get_name(e)) == NULL) + name = "unknown ECDSA engine"; + + log_debug("debug: %s: using %s", __func__, name); + + if (!ENGINE_set_ECDSA(e, ecdsae_method)) { + errstr = "ENGINE_set_ECDSA"; + goto fail; + } + if (!ENGINE_set_default_ECDSA(e)) { + errstr = "ENGINE_set_default_ECDSA"; + goto fail; + } + + return; + + fail: + ssl_error(errstr); + fatalx("%s", errstr); +} +#endif + +void +ca_engine_init(void) +{ + rsa_engine_init(); +#if defined(SUPPORT_ECDSA) + ecdsa_engine_init(); +#endif +} diff --git a/foobar/portable/smtpd/cert.c b/foobar/portable/smtpd/cert.c new file mode 100644 index 00000000..79b1df91 --- /dev/null +++ b/foobar/portable/smtpd/cert.c @@ -0,0 +1,416 @@ +/* $OpenBSD: cert.c,v 1.2 2018/12/11 07:25:57 eric Exp $ */ + +/* + * Copyright (c) 2018 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/tree.h> +#include <sys/queue.h> +#include <netinet/in.h> + +#include <imsg.h> +#include <limits.h> +#include <openssl/err.h> +#include <openssl/ssl.h> +#include <stdio.h> +#include <string.h> + +#include "log.h" +#include "smtpd.h" +#include "ssl.h" + +#define p_cert p_lka + +struct request { + SPLAY_ENTRY(request) entry; + uint32_t id; + void (*cb_get_certificate)(void *, int, const char *, + const void *, size_t); + void (*cb_verify)(void *, int); + void *arg; +}; + +#define MAX_CERTS 16 +#define MAX_CERT_LEN (MAX_IMSGSIZE - (IMSG_HEADER_SIZE + sizeof(size_t))) + +struct session { + SPLAY_ENTRY(session) entry; + uint32_t id; + struct mproc *proc; + char *cert[MAX_CERTS]; + size_t cert_len[MAX_CERTS]; + int cert_count; +}; + +SPLAY_HEAD(cert_reqtree, request); +SPLAY_HEAD(cert_sestree, session); + +static int request_cmp(struct request *, struct request *); +static int session_cmp(struct session *, struct session *); +SPLAY_PROTOTYPE(cert_reqtree, request, entry, request_cmp); +SPLAY_PROTOTYPE(cert_sestree, session, entry, session_cmp); + +static void cert_do_verify(struct session *, const char *, int); +static int cert_X509_verify(struct session *, const char *, const char *); + +static struct cert_reqtree reqs = SPLAY_INITIALIZER(&reqs); +static struct cert_sestree sess = SPLAY_INITIALIZER(&sess); + +int +cert_init(const char *name, int fallback, void (*cb)(void *, int, + const char *, const void *, size_t), void *arg) +{ + struct request *req; + + req = calloc(1, sizeof(*req)); + if (req == NULL) { + cb(arg, CA_FAIL, NULL, NULL, 0); + return 0; + } + while (req->id == 0 || SPLAY_FIND(cert_reqtree, &reqs, req)) + req->id = arc4random(); + req->cb_get_certificate = cb; + req->arg = arg; + SPLAY_INSERT(cert_reqtree, &reqs, req); + + m_create(p_cert, IMSG_CERT_INIT, req->id, 0, -1); + m_add_string(p_cert, name); + m_add_int(p_cert, fallback); + m_close(p_cert); + + return 1; +} + +int +cert_verify(const void *ssl, const char *name, int fallback, + void (*cb)(void *, int), void *arg) +{ + struct request *req; + X509 *x; + STACK_OF(X509) *xchain; + unsigned char *cert_der[MAX_CERTS]; + int cert_len[MAX_CERTS]; + int i, cert_count, ret; + + x = SSL_get_peer_certificate(ssl); + if (x == NULL) { + cb(arg, CERT_NOCERT); + return 0; + } + + ret = 0; + memset(cert_der, 0, sizeof(cert_der)); + + req = calloc(1, sizeof(*req)); + if (req == NULL) + goto end; + while (req->id == 0 || SPLAY_FIND(cert_reqtree, &reqs, req)) + req->id = arc4random(); + req->cb_verify = cb; + req->arg = arg; + SPLAY_INSERT(cert_reqtree, &reqs, req); + + cert_count = 1; + if ((xchain = SSL_get_peer_cert_chain(ssl))) { + cert_count += sk_X509_num(xchain); + if (cert_count > MAX_CERTS) { + log_warnx("warn: certificate chain too long"); + goto end; + } + } + + for (i = 0; i < cert_count; ++i) { + if (i != 0) { + if ((x = sk_X509_value(xchain, i - 1)) == NULL) { + log_warnx("warn: failed to retrieve certificate"); + goto end; + } + } + + cert_len[i] = i2d_X509(x, &cert_der[i]); + if (i == 0) + X509_free(x); + + if (cert_len[i] < 0) { + log_warnx("warn: failed to encode certificate"); + goto end; + } + + log_debug("debug: certificate %i: len=%d", i, cert_len[i]); + if (cert_len[i] > (int)MAX_CERT_LEN) { + log_warnx("warn: certificate too long"); + goto end; + } + } + + /* Send the cert chain, one cert at a time */ + for (i = 0; i < cert_count; ++i) { + m_create(p_cert, IMSG_CERT_CERTIFICATE, req->id, 0, -1); + m_add_data(p_cert, cert_der[i], cert_len[i]); + m_close(p_cert); + } + + /* Tell lookup process that it can start verifying, we're done */ + m_create(p_cert, IMSG_CERT_VERIFY, req->id, 0, -1); + m_add_string(p_cert, name); + m_add_int(p_cert, fallback); + m_close(p_cert); + + ret = 1; + + end: + for (i = 0; i < MAX_CERTS; ++i) + free(cert_der[i]); + + if (ret == 0) { + if (req) + SPLAY_REMOVE(cert_reqtree, &reqs, req); + free(req); + cb(arg, CERT_ERROR); + } + + return ret; +} + + +void +cert_dispatch_request(struct mproc *proc, struct imsg *imsg) +{ + struct pki *pki; + struct session key, *s; + const char *name; + const void *data; + size_t datalen; + struct msg m; + uint32_t reqid; + char buf[LINE_MAX]; + int fallback; + + reqid = imsg->hdr.peerid; + m_msg(&m, imsg); + + switch (imsg->hdr.type) { + + case IMSG_CERT_INIT: + m_get_string(&m, &name); + m_get_int(&m, &fallback); + m_end(&m); + + xlowercase(buf, name, sizeof(buf)); + log_debug("debug: looking up pki \"%s\"", buf); + pki = dict_get(env->sc_pki_dict, buf); + if (pki == NULL && fallback) + pki = dict_get(env->sc_pki_dict, "*"); + + m_create(proc, IMSG_CERT_INIT, reqid, 0, -1); + if (pki) { + m_add_int(proc, CA_OK); + m_add_string(proc, pki->pki_name); + m_add_data(proc, pki->pki_cert, pki->pki_cert_len); + } else { + m_add_int(proc, CA_FAIL); + m_add_string(proc, NULL); + m_add_data(proc, NULL, 0); + } + m_close(proc); + return; + + case IMSG_CERT_CERTIFICATE: + m_get_data(&m, &data, &datalen); + m_end(&m); + + key.id = reqid; + key.proc = proc; + s = SPLAY_FIND(cert_sestree, &sess, &key); + if (s == NULL) { + s = calloc(1, sizeof(*s)); + s->proc = proc; + s->id = reqid; + SPLAY_INSERT(cert_sestree, &sess, s); + } + + if (s->cert_count == MAX_CERTS) + fatalx("%s: certificate chain too long", __func__); + + s->cert[s->cert_count] = xmemdup(data, datalen); + s->cert_len[s->cert_count] = datalen; + s->cert_count++; + return; + + case IMSG_CERT_VERIFY: + m_get_string(&m, &name); + m_get_int(&m, &fallback); + m_end(&m); + + key.id = reqid; + key.proc = proc; + s = SPLAY_FIND(cert_sestree, &sess, &key); + if (s == NULL) + fatalx("%s: no certificate", __func__); + + SPLAY_REMOVE(cert_sestree, &sess, s); + cert_do_verify(s, name, fallback); + return; + + default: + fatalx("%s: %s", __func__, imsg_to_str(imsg->hdr.type)); + } +} + +void +cert_dispatch_result(struct mproc *proc, struct imsg *imsg) +{ + struct request key, *req; + struct msg m; + const void *cert; + const char *name; + size_t cert_len; + int res; + + key.id = imsg->hdr.peerid; + req = SPLAY_FIND(cert_reqtree, &reqs, &key); + if (req == NULL) + fatalx("%s: unknown request %08x", __func__, imsg->hdr.peerid); + + m_msg(&m, imsg); + + switch (imsg->hdr.type) { + + case IMSG_CERT_INIT: + m_get_int(&m, &res); + m_get_string(&m, &name); + m_get_data(&m, &cert, &cert_len); + m_end(&m); + SPLAY_REMOVE(cert_reqtree, &reqs, req); + req->cb_get_certificate(req->arg, res, name, cert, cert_len); + free(req); + break; + + case IMSG_CERT_VERIFY: + m_get_int(&m, &res); + m_end(&m); + SPLAY_REMOVE(cert_reqtree, &reqs, req); + req->cb_verify(req->arg, res); + free(req); + break; + } +} + +static void +cert_do_verify(struct session *s, const char *name, int fallback) +{ + struct ca *ca; + const char *cafile; + int i, res; + + ca = dict_get(env->sc_ca_dict, name); + if (ca == NULL) + if (fallback) + ca = dict_get(env->sc_ca_dict, "*"); + cafile = ca ? ca->ca_cert_file : CA_FILE; + + if (ca == NULL && !fallback) + res = CERT_NOCA; + else if (!cert_X509_verify(s, cafile, NULL)) + res = CERT_INVALID; + else + res = CERT_OK; + + for (i = 0; i < s->cert_count; ++i) + free(s->cert[i]); + + m_create(s->proc, IMSG_CERT_VERIFY, s->id, 0, -1); + m_add_int(s->proc, res); + m_close(s->proc); + + free(s); +} + +static int +cert_X509_verify(struct session *s, const char *CAfile, + const char *CRLfile) +{ + X509 *x509; + X509 *x509_tmp; + STACK_OF(X509) *x509_chain; + const unsigned char *d2i; + int i, ret = 0; + const char *errstr; + + x509 = NULL; + x509_tmp = NULL; + x509_chain = NULL; + + d2i = s->cert[0]; + if (d2i_X509(&x509, &d2i, s->cert_len[0]) == NULL) { + x509 = NULL; + goto end; + } + + if (s->cert_count > 1) { + x509_chain = sk_X509_new_null(); + for (i = 1; i < s->cert_count; ++i) { + d2i = s->cert[i]; + if (d2i_X509(&x509_tmp, &d2i, s->cert_len[i]) == NULL) + goto end; + sk_X509_insert(x509_chain, x509_tmp, i); + x509_tmp = NULL; + } + } + if (!ca_X509_verify(x509, x509_chain, CAfile, NULL, &errstr)) + log_debug("debug: X509 verify: %s", errstr); + else + ret = 1; + +end: + X509_free(x509); + X509_free(x509_tmp); + if (x509_chain) + sk_X509_pop_free(x509_chain, X509_free); + + return ret; +} + +static int +request_cmp(struct request *a, struct request *b) +{ + if (a->id < b->id) + return -1; + if (a->id > b->id) + return 1; + return 0; +} + +SPLAY_GENERATE(cert_reqtree, request, entry, request_cmp); + +static int +session_cmp(struct session *a, struct session *b) +{ + if (a->id < b->id) + return -1; + if (a->id > b->id) + return 1; + if (a->proc < b->proc) + return -1; + if (a->proc > b->proc) + return 1; + return 0; +} + +SPLAY_GENERATE(cert_sestree, session, entry, session_cmp); diff --git a/foobar/portable/smtpd/compress_backend.c b/foobar/portable/smtpd/compress_backend.c new file mode 100644 index 00000000..1b974662 --- /dev/null +++ b/foobar/portable/smtpd/compress_backend.c @@ -0,0 +1,72 @@ +/* $OpenBSD: compress_backend.c,v 1.9 2015/01/20 17:37:54 deraadt Exp $ */ + +/* + * Copyright (c) 2012 Charles Longeau <chl@openbsd.org> + * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <imsg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> + +#include "smtpd.h" + +#define BUFFER_SIZE 16364 + +extern struct compress_backend compress_gzip; + +struct compress_backend * +compress_backend_lookup(const char *name) +{ + if (!strcmp(name, "gzip")) + return &compress_gzip; + + return NULL; +} + +size_t +compress_chunk(void *ib, size_t ibsz, void *ob, size_t obsz) +{ + return (env->sc_comp->compress_chunk(ib, ibsz, ob, obsz)); +} + +size_t +uncompress_chunk(void *ib, size_t ibsz, void *ob, size_t obsz) +{ + return (env->sc_comp->uncompress_chunk(ib, ibsz, ob, obsz)); +} + +int +compress_file(FILE *ifile, FILE *ofile) +{ + return (env->sc_comp->compress_file(ifile, ofile)); +} + +int +uncompress_file(FILE *ifile, FILE *ofile) +{ + return (env->sc_comp->uncompress_file(ifile, ofile)); +} diff --git a/foobar/portable/smtpd/compress_gzip.c b/foobar/portable/smtpd/compress_gzip.c new file mode 100644 index 00000000..dd60aeec --- /dev/null +++ b/foobar/portable/smtpd/compress_gzip.c @@ -0,0 +1,186 @@ +/* $OpenBSD: compress_gzip.c,v 1.10 2015/12/28 22:08:30 jung Exp $ */ + +/* + * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2012 Charles Longeau <chl@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <err.h> +#include <fcntl.h> +#include <imsg.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> + +#include <zlib.h> + +#include "smtpd.h" +#include "log.h" + + +#define GZIP_BUFFER_SIZE 16384 + + +static size_t compress_gzip_chunk(void *, size_t, void *, size_t); +static size_t uncompress_gzip_chunk(void *, size_t, void *, size_t); +static int compress_gzip_file(FILE *, FILE *); +static int uncompress_gzip_file(FILE *, FILE *); + + +struct compress_backend compress_gzip = { + compress_gzip_chunk, + uncompress_gzip_chunk, + + compress_gzip_file, + uncompress_gzip_file, +}; + +static size_t +compress_gzip_chunk(void *ib, size_t ibsz, void *ob, size_t obsz) +{ + z_stream *strm; + size_t ret = 0; + + if ((strm = calloc(1, sizeof *strm)) == NULL) + return 0; + + strm->zalloc = Z_NULL; + strm->zfree = Z_NULL; + strm->opaque = Z_NULL; + if (deflateInit2(strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, + (15+16), 8, Z_DEFAULT_STRATEGY) != Z_OK) + goto end; + + strm->avail_in = ibsz; + strm->next_in = (unsigned char *)ib; + strm->avail_out = obsz; + strm->next_out = (unsigned char *)ob; + if (deflate(strm, Z_FINISH) != Z_STREAM_END) + goto end; + + ret = strm->total_out; + +end: + deflateEnd(strm); + free(strm); + return ret; +} + + +static size_t +uncompress_gzip_chunk(void *ib, size_t ibsz, void *ob, size_t obsz) +{ + z_stream *strm; + size_t ret = 0; + + if ((strm = calloc(1, sizeof *strm)) == NULL) + return 0; + + strm->zalloc = Z_NULL; + strm->zfree = Z_NULL; + strm->opaque = Z_NULL; + strm->avail_in = 0; + strm->next_in = Z_NULL; + + if (inflateInit2(strm, (15+16)) != Z_OK) + goto end; + + strm->avail_in = ibsz; + strm->next_in = (unsigned char *)ib; + strm->avail_out = obsz; + strm->next_out = (unsigned char *)ob; + + if (inflate(strm, Z_FINISH) != Z_STREAM_END) + goto end; + + ret = strm->total_out; + +end: + deflateEnd(strm); + free(strm); + return ret; +} + + +static int +compress_gzip_file(FILE *in, FILE *out) +{ + gzFile gzf; + char ibuf[GZIP_BUFFER_SIZE]; + int r, w; + int ret = 0; + + if (in == NULL || out == NULL) + return (0); + + gzf = gzdopen(fileno(out), "wb"); + if (gzf == NULL) + return (0); + + while ((r = fread(ibuf, 1, GZIP_BUFFER_SIZE, in)) != 0) { + if ((w = gzwrite(gzf, ibuf, r)) != r) + goto end; + } + if (!feof(in)) + goto end; + + ret = 1; + +end: + gzclose(gzf); + return (ret); +} + + +static int +uncompress_gzip_file(FILE *in, FILE *out) +{ + gzFile gzf; + char obuf[GZIP_BUFFER_SIZE]; + int r, w; + int ret = 0; + + if (in == NULL || out == NULL) + return (0); + + gzf = gzdopen(fileno(in), "r"); + if (gzf == NULL) + return (0); + + while ((r = gzread(gzf, obuf, sizeof(obuf))) > 0) { + if ((w = fwrite(obuf, r, 1, out)) != 1) + goto end; + } + if (!gzeof(gzf)) + goto end; + + ret = 1; + +end: + gzclose(gzf); + return (ret); +} diff --git a/foobar/portable/smtpd/config.c b/foobar/portable/smtpd/config.c new file mode 100644 index 00000000..8fe983d6 --- /dev/null +++ b/foobar/portable/smtpd/config.c @@ -0,0 +1,350 @@ +/* $OpenBSD: config.c,v 1.51 2019/12/18 10:00:39 gilles Exp $ */ + +/* + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/resource.h> + +#include <event.h> +#include <ifaddrs.h> +#include <imsg.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <string.h> +#include <unistd.h> + +#include <openssl/ssl.h> + +#include "smtpd.h" +#include "log.h" +#include "ssl.h" + +void set_local(struct smtpd *, const char *); +void set_localaddrs(struct smtpd *, struct table *); + +struct smtpd * +config_default(void) +{ + struct smtpd *conf = NULL; + struct mta_limits *limits = NULL; + struct table *t = NULL; + char hostname[HOST_NAME_MAX+1]; + + if (getmailname(hostname, sizeof hostname) == -1) + return NULL; + + if ((conf = calloc(1, sizeof(*conf))) == NULL) + return conf; + + (void)strlcpy(conf->sc_hostname, hostname, sizeof(conf->sc_hostname)); + + conf->sc_maxsize = DEFAULT_MAX_BODY_SIZE; + conf->sc_subaddressing_delim = SUBADDRESSING_DELIMITER; + conf->sc_ttl = SMTPD_QUEUE_EXPIRY; + conf->sc_srs_ttl = SMTPD_QUEUE_EXPIRY / 86400; + + conf->sc_mta_max_deferred = 100; + conf->sc_scheduler_max_inflight = 5000; + conf->sc_scheduler_max_schedule = 10; + conf->sc_scheduler_max_evp_batch_size = 256; + conf->sc_scheduler_max_msg_batch_size = 1024; + + conf->sc_session_max_rcpt = 1000; + conf->sc_session_max_mails = 100; + + conf->sc_mda_max_session = 50; + conf->sc_mda_max_user_session = 7; + conf->sc_mda_task_hiwat = 50; + conf->sc_mda_task_lowat = 30; + conf->sc_mda_task_release = 10; + + /* Report mails delayed for more than 4 hours */ + conf->sc_bounce_warn[0] = 3600 * 4; + + conf->sc_tables_dict = calloc(1, sizeof(*conf->sc_tables_dict)); + conf->sc_rules = calloc(1, sizeof(*conf->sc_rules)); + conf->sc_dispatchers = calloc(1, sizeof(*conf->sc_dispatchers)); + conf->sc_listeners = calloc(1, sizeof(*conf->sc_listeners)); + conf->sc_ca_dict = calloc(1, sizeof(*conf->sc_ca_dict)); + conf->sc_pki_dict = calloc(1, sizeof(*conf->sc_pki_dict)); + conf->sc_ssl_dict = calloc(1, sizeof(*conf->sc_ssl_dict)); + conf->sc_limits_dict = calloc(1, sizeof(*conf->sc_limits_dict)); + conf->sc_mda_wrappers = calloc(1, sizeof(*conf->sc_mda_wrappers)); + conf->sc_filter_processes_dict = calloc(1, sizeof(*conf->sc_filter_processes_dict)); + conf->sc_dispatcher_bounce = calloc(1, sizeof(*conf->sc_dispatcher_bounce)); + conf->sc_filters_dict = calloc(1, sizeof(*conf->sc_filters_dict)); + limits = calloc(1, sizeof(*limits)); + + if (conf->sc_tables_dict == NULL || + conf->sc_rules == NULL || + conf->sc_dispatchers == NULL || + conf->sc_listeners == NULL || + conf->sc_ca_dict == NULL || + conf->sc_pki_dict == NULL || + conf->sc_ssl_dict == NULL || + conf->sc_limits_dict == NULL || + conf->sc_mda_wrappers == NULL || + conf->sc_filter_processes_dict == NULL || + conf->sc_dispatcher_bounce == NULL || + conf->sc_filters_dict == NULL || + limits == NULL) + goto error; + + dict_init(conf->sc_dispatchers); + dict_init(conf->sc_mda_wrappers); + dict_init(conf->sc_ca_dict); + dict_init(conf->sc_pki_dict); + dict_init(conf->sc_ssl_dict); + dict_init(conf->sc_tables_dict); + dict_init(conf->sc_limits_dict); + dict_init(conf->sc_filter_processes_dict); + + limit_mta_set_defaults(limits); + + dict_xset(conf->sc_limits_dict, "default", limits); + + TAILQ_INIT(conf->sc_listeners); + TAILQ_INIT(conf->sc_rules); + + + /* bounce dispatcher */ + conf->sc_dispatcher_bounce->type = DISPATCHER_BOUNCE; + + /* + * declare special "localhost", "anyhost" and "localnames" tables + */ + set_local(conf, conf->sc_hostname); + + t = table_create(conf, "static", "<anydestination>", NULL); + table_add(t, "*", NULL); + + hostname[strcspn(hostname, ".")] = '\0'; + if (strcmp(conf->sc_hostname, hostname) != 0) + table_add(t, hostname, NULL); + + table_create(conf, "getpwnam", "<getpwnam>", NULL); + + return conf; + +error: + free(conf->sc_tables_dict); + free(conf->sc_rules); + free(conf->sc_dispatchers); + free(conf->sc_listeners); + free(conf->sc_ca_dict); + free(conf->sc_pki_dict); + free(conf->sc_ssl_dict); + free(conf->sc_limits_dict); + free(conf->sc_mda_wrappers); + free(conf->sc_filter_processes_dict); + free(conf->sc_dispatcher_bounce); + free(conf->sc_filters_dict); + free(limits); + free(conf); + return NULL; +} + +void +set_local(struct smtpd *conf, const char *hostname) +{ + struct table *t; + + t = table_create(conf, "static", "<localnames>", NULL); + table_add(t, "localhost", NULL); + table_add(t, hostname, NULL); + + set_localaddrs(conf, t); +} + +void +set_localaddrs(struct smtpd *conf, struct table *localnames) +{ + struct ifaddrs *ifap, *p; + struct sockaddr_storage ss; + struct sockaddr_in *sain; + struct sockaddr_in6 *sin6; + struct table *t; + char buf[NI_MAXHOST + 5]; + + t = table_create(conf, "static", "<anyhost>", NULL); + table_add(t, "local", NULL); + table_add(t, "0.0.0.0/0", NULL); + table_add(t, "::/0", NULL); + + if (getifaddrs(&ifap) == -1) + fatal("getifaddrs"); + + t = table_create(conf, "static", "<localhost>", NULL); + table_add(t, "local", NULL); + + for (p = ifap; p != NULL; p = p->ifa_next) { + if (p->ifa_addr == NULL) + continue; + switch (p->ifa_addr->sa_family) { + case AF_INET: + sain = (struct sockaddr_in *)&ss; + *sain = *(struct sockaddr_in *)p->ifa_addr; +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + sain->sin_len = sizeof(struct sockaddr_in); +#endif + table_add(t, ss_to_text(&ss), NULL); + table_add(localnames, ss_to_text(&ss), NULL); + (void)snprintf(buf, sizeof buf, "[%s]", ss_to_text(&ss)); + table_add(localnames, buf, NULL); + break; + + case AF_INET6: + sin6 = (struct sockaddr_in6 *)&ss; + *sin6 = *(struct sockaddr_in6 *)p->ifa_addr; +#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN + sin6->sin6_len = sizeof(struct sockaddr_in6); +#endif + table_add(t, ss_to_text(&ss), NULL); + table_add(localnames, ss_to_text(&ss), NULL); + (void)snprintf(buf, sizeof buf, "[%s]", ss_to_text(&ss)); + table_add(localnames, buf, NULL); + (void)snprintf(buf, sizeof buf, "[ipv6:%s]", ss_to_text(&ss)); + table_add(localnames, buf, NULL); + break; + } + } + + freeifaddrs(ifap); +} + +void +purge_config(uint8_t what) +{ + struct dispatcher *d; + struct listener *l; + struct table *t; + struct rule *r; + struct pki *p; + const char *k; + void *iter_dict; + + if (what & PURGE_LISTENERS) { + while ((l = TAILQ_FIRST(env->sc_listeners)) != NULL) { + TAILQ_REMOVE(env->sc_listeners, l, entry); + free(l); + } + free(env->sc_listeners); + env->sc_listeners = NULL; + } + if (what & PURGE_TABLES) { + while (dict_root(env->sc_tables_dict, NULL, (void **)&t)) + table_destroy(env, t); + free(env->sc_tables_dict); + env->sc_tables_dict = NULL; + } + if (what & PURGE_RULES) { + while ((r = TAILQ_FIRST(env->sc_rules)) != NULL) { + TAILQ_REMOVE(env->sc_rules, r, r_entry); + free(r); + } + free(env->sc_rules); + env->sc_rules = NULL; + } + if (what & PURGE_DISPATCHERS) { + while (dict_poproot(env->sc_dispatchers, (void **)&d)) { + free(d); + } + free(env->sc_dispatchers); + env->sc_dispatchers = NULL; + } + if (what & PURGE_PKI) { + while (dict_poproot(env->sc_pki_dict, (void **)&p)) { + freezero(p->pki_cert, p->pki_cert_len); + freezero(p->pki_key, p->pki_key_len); + EVP_PKEY_free(p->pki_pkey); + free(p); + } + free(env->sc_pki_dict); + env->sc_pki_dict = NULL; + } else if (what & PURGE_PKI_KEYS) { + iter_dict = NULL; + while (dict_iter(env->sc_pki_dict, &iter_dict, &k, + (void **)&p)) { + freezero(p->pki_cert, p->pki_cert_len); + p->pki_cert = NULL; + freezero(p->pki_key, p->pki_key_len); + p->pki_key = NULL; + EVP_PKEY_free(p->pki_pkey); + p->pki_pkey = NULL; + } + } +} + +#ifndef CONFIG_MINIMUM + +void +config_process(enum smtp_proc_type proc) +{ + struct rlimit rl; + + smtpd_process = proc; + setproctitle("%s", proc_title(proc)); + + if (getrlimit(RLIMIT_NOFILE, &rl) == -1) + fatal("fdlimit: getrlimit"); + rl.rlim_cur = rl.rlim_max; + if (setrlimit(RLIMIT_NOFILE, &rl) == -1) + if (errno != EINVAL) + fatal("fdlimit: setrlimit"); +} + +void +config_peer(enum smtp_proc_type proc) +{ + struct mproc *p; + + if (proc == smtpd_process) + fatal("config_peers: cannot peer with oneself"); + + if (proc == PROC_CONTROL) + p = p_control; + else if (proc == PROC_LKA) + p = p_lka; + else if (proc == PROC_PARENT) + p = p_parent; + else if (proc == PROC_QUEUE) + p = p_queue; + else if (proc == PROC_SCHEDULER) + p = p_scheduler; + else if (proc == PROC_PONY) + p = p_pony; + else if (proc == PROC_CA) + p = p_ca; + else + fatalx("bad peer"); + + mproc_enable(p); +} + +#else + +void config_process(enum smtp_proc_type proc) {} +void config_peer(enum smtp_proc_type proc) {} + +#endif diff --git a/foobar/portable/smtpd/control.c b/foobar/portable/smtpd/control.c new file mode 100644 index 00000000..0e35bbd1 --- /dev/null +++ b/foobar/portable/smtpd/control.c @@ -0,0 +1,817 @@ +/* $OpenBSD: control.c,v 1.123 2018/05/31 21:06:12 gilles Exp $ */ + +/* + * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> + * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/un.h> + +#include <err.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <grp.h> /* needed for setgroups */ +#include <imsg.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <limits.h> + +#include "smtpd.h" +#include "log.h" + +#define CONTROL_BACKLOG 5 + +struct ctl_conn { + uint32_t id; + uint8_t flags; +#define CTL_CONN_NOTIFY 0x01 + struct mproc mproc; + uid_t euid; + gid_t egid; +}; + +struct { + struct event ev; + int fd; +} control_state; + +static void control_imsg(struct mproc *, struct imsg *); +static void control_shutdown(void); +static void control_listen(void); +static void control_accept(int, short, void *); +static void control_close(struct ctl_conn *); +static void control_dispatch_ext(struct mproc *, struct imsg *); +static void control_digest_update(const char *, size_t, int); +static void control_broadcast_verbose(int, int); + +static struct stat_backend *stat_backend = NULL; +extern const char *backend_stat; + +static uint64_t connid = 0; +static struct tree ctl_conns; +static struct tree ctl_count; +static struct stat_digest digest; + +#define CONTROL_FD_RESERVE 5 +#define CONTROL_MAXCONN_PER_CLIENT 32 + +static void +control_imsg(struct mproc *p, struct imsg *imsg) +{ + struct ctl_conn *c; + struct stat_value val; + struct msg m; + const char *key; + const void *data; + size_t sz; + + if (imsg == NULL) { + if (p->proc != PROC_CLIENT) + control_shutdown(); + return; + } + + switch (imsg->hdr.type) { + case IMSG_CTL_OK: + case IMSG_CTL_FAIL: + case IMSG_CTL_LIST_MESSAGES: + case IMSG_CTL_LIST_ENVELOPES: + case IMSG_CTL_DISCOVER_EVPID: + case IMSG_CTL_DISCOVER_MSGID: + case IMSG_CTL_MTA_SHOW_HOSTS: + case IMSG_CTL_MTA_SHOW_RELAYS: + case IMSG_CTL_MTA_SHOW_ROUTES: + case IMSG_CTL_MTA_SHOW_HOSTSTATS: + case IMSG_CTL_MTA_SHOW_BLOCK: + c = tree_get(&ctl_conns, imsg->hdr.peerid); + if (c == NULL) + return; + imsg->hdr.peerid = 0; + m_forward(&c->mproc, imsg); + return; + + case IMSG_CTL_SMTP_SESSION: + c = tree_get(&ctl_conns, imsg->hdr.peerid); + if (c == NULL) + return; + m_compose(&c->mproc, IMSG_CTL_OK, 0, 0, imsg->fd, NULL, 0); + return; + + case IMSG_STAT_INCREMENT: + m_msg(&m, imsg); + m_get_string(&m, &key); + m_get_data(&m, &data, &sz); + m_end(&m); + if (sz != sizeof(val)) + fatalx("control: IMSG_STAT_INCREMENT size mismatch"); + memmove(&val, data, sz); + if (stat_backend) + stat_backend->increment(key, val.u.counter); + control_digest_update(key, val.u.counter, 1); + return; + + case IMSG_STAT_DECREMENT: + m_msg(&m, imsg); + m_get_string(&m, &key); + m_get_data(&m, &data, &sz); + m_end(&m); + if (sz != sizeof(val)) + fatalx("control: IMSG_STAT_DECREMENT size mismatch"); + memmove(&val, data, sz); + if (stat_backend) + stat_backend->decrement(key, val.u.counter); + control_digest_update(key, val.u.counter, 0); + return; + + case IMSG_STAT_SET: + m_msg(&m, imsg); + m_get_string(&m, &key); + m_get_data(&m, &data, &sz); + m_end(&m); + if (sz != sizeof(val)) + fatalx("control: IMSG_STAT_SET size mismatch"); + memmove(&val, data, sz); + if (stat_backend) + stat_backend->set(key, &val); + return; + } + + errx(1, "control_imsg: unexpected %s imsg", + imsg_to_str(imsg->hdr.type)); +} + +int +control_create_socket(void) +{ + struct sockaddr_un s_un; + int fd; + mode_t old_umask; + + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + fatal("control: socket"); + + memset(&s_un, 0, sizeof(s_un)); + s_un.sun_family = AF_UNIX; + if (strlcpy(s_un.sun_path, SMTPD_SOCKET, + sizeof(s_un.sun_path)) >= sizeof(s_un.sun_path)) + fatal("control: socket name too long"); + + if (connect(fd, (struct sockaddr *)&s_un, sizeof(s_un)) == 0) + fatalx("control socket already listening"); + + if (unlink(SMTPD_SOCKET) == -1) + if (errno != ENOENT) + fatal("control: cannot unlink socket"); + + old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH); + if (bind(fd, (struct sockaddr *)&s_un, sizeof(s_un)) == -1) { + (void)umask(old_umask); + fatal("control: bind"); + } + (void)umask(old_umask); + + if (chmod(SMTPD_SOCKET, + S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) == -1) { + (void)unlink(SMTPD_SOCKET); + fatal("control: chmod"); + } + + io_set_nonblocking(fd); + control_state.fd = fd; + + return fd; +} + +int +control(void) +{ + struct passwd *pw; + + purge_config(PURGE_EVERYTHING); + + if ((pw = getpwnam(SMTPD_USER)) == NULL) + fatalx("unknown user " SMTPD_USER); + + stat_backend = env->sc_stat; + stat_backend->init(); + + if (chroot(PATH_CHROOT) == -1) + fatal("control: chroot"); + if (chdir("/") == -1) + fatal("control: chdir(\"/\")"); + + config_process(PROC_CONTROL); + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("control: cannot drop privileges"); + + imsg_callback = control_imsg; + event_init(); + + signal(SIGINT, SIG_IGN); + signal(SIGTERM, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + + tree_init(&ctl_conns); + tree_init(&ctl_count); + + memset(&digest, 0, sizeof digest); + digest.startup = time(NULL); + + config_peer(PROC_SCHEDULER); + config_peer(PROC_QUEUE); + config_peer(PROC_PARENT); + config_peer(PROC_LKA); + config_peer(PROC_PONY); + config_peer(PROC_CA); + + control_listen(); + +#if HAVE_PLEDGE + if (pledge("stdio unix recvfd sendfd", NULL) == -1) + err(1, "pledge"); +#endif + + event_dispatch(); + fatalx("exited event loop"); + + return (0); +} + +static void +control_shutdown(void) +{ + log_debug("debug: control agent exiting"); + _exit(0); +} + +static void +control_listen(void) +{ + if (listen(control_state.fd, CONTROL_BACKLOG) == -1) + fatal("control_listen"); + + event_set(&control_state.ev, control_state.fd, EV_READ|EV_PERSIST, + control_accept, NULL); + event_add(&control_state.ev, NULL); +} + +/* ARGSUSED */ +static void +control_accept(int listenfd, short event, void *arg) +{ + int connfd; + socklen_t len; + struct sockaddr_un s_un; + struct ctl_conn *c; + size_t *count; + uid_t euid; + gid_t egid; + +#if defined(HAVE_GETDTABLESIZE) && defined(HAVE_GETDTABLECOUNT) + if (getdtablesize() - getdtablecount() < CONTROL_FD_RESERVE) + goto pause; +#else + if (available_fds(CONTROL_FD_RESERVE)) + goto pause; +#endif + + len = sizeof(s_un); + if ((connfd = accept(listenfd, (struct sockaddr *)&s_un, &len)) == -1) { + if (errno == ENFILE || errno == EMFILE) + goto pause; + if (errno == EINTR || errno == EWOULDBLOCK || + errno == ECONNABORTED) + return; + fatal("control_accept: accept"); + } + + io_set_nonblocking(connfd); + + if (getpeereid(connfd, &euid, &egid) == -1) + fatal("getpeereid"); + + count = tree_get(&ctl_count, euid); + if (count == NULL) { + count = xcalloc(1, sizeof *count); + tree_xset(&ctl_count, euid, count); + } + + if (*count == CONTROL_MAXCONN_PER_CLIENT) { + close(connfd); + log_warnx("warn: too many connections to control socket " + "from user with uid %lu", (unsigned long int)euid); + return; + } + (*count)++; + + do { + ++connid; + } while (tree_get(&ctl_conns, connid)); + + c = xcalloc(1, sizeof(*c)); + c->euid = euid; + c->egid = egid; + c->id = connid; + c->mproc.proc = PROC_CLIENT; + c->mproc.handler = control_dispatch_ext; + c->mproc.data = c; + if ((c->mproc.name = strdup(proc_title(c->mproc.proc))) == NULL) + fatal("strdup"); + mproc_init(&c->mproc, connfd); + mproc_enable(&c->mproc); + tree_xset(&ctl_conns, c->id, c); + + stat_backend->increment("control.session", 1); + return; + +pause: + log_warnx("warn: ctl client limit hit, disabling new connections"); + event_del(&control_state.ev); +} + +static void +control_close(struct ctl_conn *c) +{ + size_t *count; + + count = tree_xget(&ctl_count, c->euid); + (*count)--; + if (*count == 0) { + tree_xpop(&ctl_count, c->euid); + free(count); + } + tree_xpop(&ctl_conns, c->id); + mproc_clear(&c->mproc); + free(c); + + stat_backend->decrement("control.session", 1); + +#if defined(HAVE_GETDTABLESIZE) && defined(HAVE_GETDTABLECOUNT) + if (getdtablesize() - getdtablecount() < CONTROL_FD_RESERVE) + return; +#else + if (available_fds(CONTROL_FD_RESERVE)) + return; +#endif + + if (!event_pending(&control_state.ev, EV_READ, NULL)) { + log_warnx("warn: re-enabling ctl connections"); + event_add(&control_state.ev, NULL); + } +} + +static void +control_digest_update(const char *key, size_t value, int incr) +{ + size_t *p; + + p = NULL; + + if (!strcmp(key, "smtp.session")) { + if (incr) + p = &digest.clt_connect; + else + digest.clt_disconnect += value; + } + else if (!strcmp(key, "scheduler.envelope")) { + if (incr) + p = &digest.evp_enqueued; + else + digest.evp_dequeued += value; + } + else if (!strcmp(key, "scheduler.envelope.expired")) + p = &digest.evp_expired; + else if (!strcmp(key, "scheduler.envelope.removed")) + p = &digest.evp_removed; + else if (!strcmp(key, "scheduler.delivery.ok")) + p = &digest.dlv_ok; + else if (!strcmp(key, "scheduler.delivery.permfail")) + p = &digest.dlv_permfail; + else if (!strcmp(key, "scheduler.delivery.tempfail")) + p = &digest.dlv_tempfail; + else if (!strcmp(key, "scheduler.delivery.loop")) + p = &digest.dlv_loop; + + else if (!strcmp(key, "queue.bounce")) + p = &digest.evp_bounce; + + if (p) { + if (incr) + *p = *p + value; + else + *p = *p - value; + } +} + +/* ARGSUSED */ +static void +control_dispatch_ext(struct mproc *p, struct imsg *imsg) +{ + struct sockaddr_storage ss; + struct ctl_conn *c; + int v; + struct stat_kv *kvp; + char *key; + struct stat_value val; + size_t len; + uint64_t evpid; + uint32_t msgid; + + c = p->data; + + if (imsg == NULL) { + control_close(c); + return; + } + + if (imsg->hdr.peerid != IMSG_VERSION) { + m_compose(p, IMSG_CTL_FAIL, IMSG_VERSION, 0, -1, NULL, 0); + return; + } + + switch (imsg->hdr.type) { + case IMSG_CTL_SMTP_SESSION: + if (env->sc_flags & SMTPD_SMTP_PAUSED) { + m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); + return; + } + m_compose(p_pony, IMSG_CTL_SMTP_SESSION, c->id, 0, -1, + &c->euid, sizeof(c->euid)); + return; + + case IMSG_CTL_GET_DIGEST: + if (c->euid) + goto badcred; + digest.timestamp = time(NULL); + m_compose(p, IMSG_CTL_GET_DIGEST, 0, 0, -1, &digest, sizeof digest); + return; + + case IMSG_CTL_GET_STATS: + if (c->euid) + goto badcred; + kvp = imsg->data; + if (!stat_backend->iter(&kvp->iter, &key, &val)) + kvp->iter = NULL; + else { + (void)strlcpy(kvp->key, key, sizeof kvp->key); + kvp->val = val; + } + m_compose(p, IMSG_CTL_GET_STATS, 0, 0, -1, kvp, sizeof *kvp); + return; + + case IMSG_CTL_VERBOSE: + if (c->euid) + goto badcred; + + if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(v)) + goto badcred; + + memcpy(&v, imsg->data, sizeof(v)); + log_trace_verbose(v); + + control_broadcast_verbose(IMSG_CTL_VERBOSE, v); + + m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); + return; + + case IMSG_CTL_TRACE_ENABLE: + if (c->euid) + goto badcred; + + if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(v)) + goto badcred; + + memcpy(&v, imsg->data, sizeof(v)); + tracing |= v; + log_trace_verbose(tracing); + + control_broadcast_verbose(IMSG_CTL_VERBOSE, tracing); + + m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); + return; + + case IMSG_CTL_TRACE_DISABLE: + if (c->euid) + goto badcred; + + if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(v)) + goto badcred; + + memcpy(&v, imsg->data, sizeof(v)); + tracing &= ~v; + log_trace_verbose(tracing); + + control_broadcast_verbose(IMSG_CTL_VERBOSE, tracing); + + m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); + return; + + case IMSG_CTL_PROFILE_ENABLE: + if (c->euid) + goto badcred; + + if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(v)) + goto badcred; + + memcpy(&v, imsg->data, sizeof(v)); + profiling |= v; + + control_broadcast_verbose(IMSG_CTL_PROFILE, profiling); + + m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); + return; + + case IMSG_CTL_PROFILE_DISABLE: + if (c->euid) + goto badcred; + + if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof(v)) + goto badcred; + + memcpy(&v, imsg->data, sizeof(v)); + profiling &= ~v; + + control_broadcast_verbose(IMSG_CTL_PROFILE, profiling); + + m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); + return; + + case IMSG_CTL_PAUSE_EVP: + if (c->euid) + goto badcred; + + imsg->hdr.peerid = c->id; + m_forward(p_scheduler, imsg); + return; + + case IMSG_CTL_PAUSE_MDA: + if (c->euid) + goto badcred; + + if (env->sc_flags & SMTPD_MDA_PAUSED) { + m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); + return; + } + log_info("info: mda paused"); + env->sc_flags |= SMTPD_MDA_PAUSED; + m_compose(p_queue, IMSG_CTL_PAUSE_MDA, 0, 0, -1, NULL, 0); + m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); + return; + + case IMSG_CTL_PAUSE_MTA: + if (c->euid) + goto badcred; + + if (env->sc_flags & SMTPD_MTA_PAUSED) { + m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); + return; + } + log_info("info: mta paused"); + env->sc_flags |= SMTPD_MTA_PAUSED; + m_compose(p_queue, IMSG_CTL_PAUSE_MTA, 0, 0, -1, NULL, 0); + m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); + return; + + case IMSG_CTL_PAUSE_SMTP: + if (c->euid) + goto badcred; + + if (env->sc_flags & SMTPD_SMTP_PAUSED) { + m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); + return; + } + log_info("info: smtp paused"); + env->sc_flags |= SMTPD_SMTP_PAUSED; + m_compose(p_pony, IMSG_CTL_PAUSE_SMTP, 0, 0, -1, NULL, 0); + m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); + return; + + case IMSG_CTL_RESUME_EVP: + if (c->euid) + goto badcred; + + imsg->hdr.peerid = c->id; + m_forward(p_scheduler, imsg); + return; + + case IMSG_CTL_RESUME_MDA: + if (c->euid) + goto badcred; + + if (!(env->sc_flags & SMTPD_MDA_PAUSED)) { + m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); + return; + } + log_info("info: mda resumed"); + env->sc_flags &= ~SMTPD_MDA_PAUSED; + m_compose(p_queue, IMSG_CTL_RESUME_MDA, 0, 0, -1, NULL, 0); + m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); + return; + + case IMSG_CTL_RESUME_MTA: + if (c->euid) + goto badcred; + + if (!(env->sc_flags & SMTPD_MTA_PAUSED)) { + m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); + return; + } + log_info("info: mta resumed"); + env->sc_flags &= ~SMTPD_MTA_PAUSED; + m_compose(p_queue, IMSG_CTL_RESUME_MTA, 0, 0, -1, NULL, 0); + m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); + return; + + case IMSG_CTL_RESUME_SMTP: + if (c->euid) + goto badcred; + + if (!(env->sc_flags & SMTPD_SMTP_PAUSED)) { + m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); + return; + } + log_info("info: smtp resumed"); + env->sc_flags &= ~SMTPD_SMTP_PAUSED; + m_forward(p_pony, imsg); + m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); + return; + + case IMSG_CTL_RESUME_ROUTE: + if (c->euid) + goto badcred; + + m_forward(p_pony, imsg); + m_compose(p, IMSG_CTL_OK, 0, 0, -1, NULL, 0); + return; + + case IMSG_CTL_LIST_MESSAGES: + if (c->euid) + goto badcred; + m_compose(p_scheduler, IMSG_CTL_LIST_MESSAGES, c->id, 0, -1, + imsg->data, imsg->hdr.len - sizeof(imsg->hdr)); + return; + + case IMSG_CTL_LIST_ENVELOPES: + if (c->euid) + goto badcred; + m_compose(p_scheduler, IMSG_CTL_LIST_ENVELOPES, c->id, 0, -1, + imsg->data, imsg->hdr.len - sizeof(imsg->hdr)); + return; + + case IMSG_CTL_MTA_SHOW_HOSTS: + case IMSG_CTL_MTA_SHOW_RELAYS: + case IMSG_CTL_MTA_SHOW_ROUTES: + case IMSG_CTL_MTA_SHOW_HOSTSTATS: + case IMSG_CTL_MTA_SHOW_BLOCK: + if (c->euid) + goto badcred; + + imsg->hdr.peerid = c->id; + m_forward(p_pony, imsg); + return; + + case IMSG_CTL_SHOW_STATUS: + if (c->euid) + goto badcred; + + m_compose(p, IMSG_CTL_SHOW_STATUS, 0, 0, -1, &env->sc_flags, + sizeof(env->sc_flags)); + return; + + case IMSG_CTL_MTA_BLOCK: + case IMSG_CTL_MTA_UNBLOCK: + if (c->euid) + goto badcred; + + if (imsg->hdr.len - IMSG_HEADER_SIZE <= sizeof(ss)) + goto invalid; + memmove(&ss, imsg->data, sizeof(ss)); + m_create(p_pony, imsg->hdr.type, c->id, 0, -1); + m_add_sockaddr(p_pony, (struct sockaddr *)&ss); + m_add_string(p_pony, (char *)imsg->data + sizeof(ss)); + m_close(p_pony); + return; + + case IMSG_CTL_SCHEDULE: + if (c->euid) + goto badcred; + + imsg->hdr.peerid = c->id; + m_forward(p_scheduler, imsg); + return; + + case IMSG_CTL_REMOVE: + if (c->euid) + goto badcred; + + imsg->hdr.peerid = c->id; + m_forward(p_scheduler, imsg); + return; + + case IMSG_CTL_UPDATE_TABLE: + if (c->euid) + goto badcred; + + /* table name too long */ + len = strlen(imsg->data); + if (len >= LINE_MAX) + goto invalid; + + imsg->hdr.peerid = c->id; + m_forward(p_lka, imsg); + return; + + case IMSG_CTL_DISCOVER_EVPID: + if (c->euid) + goto badcred; + + if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof evpid) + goto invalid; + + memmove(&evpid, imsg->data, sizeof evpid); + m_create(p_queue, imsg->hdr.type, c->id, 0, -1); + m_add_evpid(p_queue, evpid); + m_close(p_queue); + return; + + case IMSG_CTL_DISCOVER_MSGID: + if (c->euid) + goto badcred; + + if (imsg->hdr.len - IMSG_HEADER_SIZE != sizeof msgid) + goto invalid; + + memmove(&msgid, imsg->data, sizeof msgid); + m_create(p_queue, imsg->hdr.type, c->id, 0, -1); + m_add_msgid(p_queue, msgid); + m_close(p_queue); + return; + + default: + log_debug("debug: control_dispatch_ext: " + "error handling %s imsg", + imsg_to_str(imsg->hdr.type)); + return; + } +badcred: +invalid: + m_compose(p, IMSG_CTL_FAIL, 0, 0, -1, NULL, 0); +} + +static void +control_broadcast_verbose(int msg, int v) +{ + m_create(p_lka, msg, 0, 0, -1); + m_add_int(p_lka, v); + m_close(p_lka); + + m_create(p_pony, msg, 0, 0, -1); + m_add_int(p_pony, v); + m_close(p_pony); + + m_create(p_queue, msg, 0, 0, -1); + m_add_int(p_queue, v); + m_close(p_queue); + + m_create(p_ca, msg, 0, 0, -1); + m_add_int(p_ca, v); + m_close(p_ca); + + m_create(p_scheduler, msg, 0, 0, -1); + m_add_int(p_scheduler, v); + m_close(p_scheduler); + + m_create(p_parent, msg, 0, 0, -1); + m_add_int(p_parent, v); + m_close(p_parent); +} diff --git a/foobar/portable/smtpd/crypto.c b/foobar/portable/smtpd/crypto.c new file mode 100644 index 00000000..20a422cd --- /dev/null +++ b/foobar/portable/smtpd/crypto.c @@ -0,0 +1,400 @@ +/* $OpenBSD: crypto.c,v 1.8 2019/06/28 13:32:50 deraadt Exp $ */ + +/* + * Copyright (c) 2013 Gilles Chehade <gilles@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/stat.h> + +#include <stdlib.h> +#include <string.h> + +#include <openssl/evp.h> + + +#define CRYPTO_BUFFER_SIZE 16384 + +#define GCM_TAG_SIZE 16 +#define IV_SIZE 12 +#define KEY_SIZE 32 + +/* bump if we ever switch from aes-256-gcm to anything else */ +#define API_VERSION 1 + + +int crypto_setup(const char *, size_t); +int crypto_encrypt_file(FILE *, FILE *); +int crypto_decrypt_file(FILE *, FILE *); +size_t crypto_encrypt_buffer(const char *, size_t, char *, size_t); +size_t crypto_decrypt_buffer(const char *, size_t, char *, size_t); + +static struct crypto_ctx { + unsigned char key[KEY_SIZE]; +} cp; + +int +crypto_setup(const char *key, size_t len) +{ + if (len != KEY_SIZE) + return 0; + + memset(&cp, 0, sizeof cp); + + /* openssl rand -hex 16 */ + memcpy(cp.key, key, sizeof cp.key); + + return 1; +} + +int +crypto_encrypt_file(FILE * in, FILE * out) +{ + EVP_CIPHER_CTX *ctx; + uint8_t ibuf[CRYPTO_BUFFER_SIZE]; + uint8_t obuf[CRYPTO_BUFFER_SIZE]; + uint8_t iv[IV_SIZE]; + uint8_t tag[GCM_TAG_SIZE]; + uint8_t version = API_VERSION; + size_t r, w; + int len; + int ret = 0; + struct stat sb; + + /* XXX - Do NOT encrypt files bigger than 64GB */ + if (fstat(fileno(in), &sb) == -1) + return 0; + if (sb.st_size >= 0x1000000000LL) + return 0; + + /* prepend version byte*/ + if ((w = fwrite(&version, 1, sizeof version, out)) != sizeof version) + return 0; + + /* generate and prepend IV */ + memset(iv, 0, sizeof iv); + arc4random_buf(iv, sizeof iv); + if ((w = fwrite(iv, 1, sizeof iv, out)) != sizeof iv) + return 0; + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) + return 0; + + EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, cp.key, iv); + + /* encrypt until end of file */ + while ((r = fread(ibuf, 1, CRYPTO_BUFFER_SIZE, in)) != 0) { + if (!EVP_EncryptUpdate(ctx, obuf, &len, ibuf, r)) + goto end; + if (len && (w = fwrite(obuf, len, 1, out)) != 1) + goto end; + } + if (!feof(in)) + goto end; + + /* finalize and write last chunk if any */ + if (!EVP_EncryptFinal_ex(ctx, obuf, &len)) + goto end; + if (len && (w = fwrite(obuf, len, 1, out)) != 1) + goto end; + + /* get and append tag */ + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, sizeof tag, tag); + if ((w = fwrite(tag, sizeof tag, 1, out)) != 1) + goto end; + + fflush(out); + ret = 1; + +end: + EVP_CIPHER_CTX_free(ctx); + return ret; +} + +int +crypto_decrypt_file(FILE * in, FILE * out) +{ + EVP_CIPHER_CTX *ctx; + uint8_t ibuf[CRYPTO_BUFFER_SIZE]; + uint8_t obuf[CRYPTO_BUFFER_SIZE]; + uint8_t iv[IV_SIZE]; + uint8_t tag[GCM_TAG_SIZE]; + uint8_t version; + size_t r, w; + off_t sz; + int len; + int ret = 0; + struct stat sb; + + /* input file too small to be an encrypted file */ + if (fstat(fileno(in), &sb) == -1) + return 0; + if (sb.st_size <= (off_t) (sizeof version + sizeof tag + sizeof iv)) + return 0; + sz = sb.st_size; + + /* extract tag */ + if (fseek(in, -sizeof(tag), SEEK_END) == -1) + return 0; + if ((r = fread(tag, 1, sizeof tag, in)) != sizeof tag) + return 0; + + if (fseek(in, 0, SEEK_SET) == -1) + return 0; + + /* extract version */ + if ((r = fread(&version, 1, sizeof version, in)) != sizeof version) + return 0; + if (version != API_VERSION) + return 0; + + /* extract IV */ + memset(iv, 0, sizeof iv); + if ((r = fread(iv, 1, sizeof iv, in)) != sizeof iv) + return 0; + + /* real ciphertext length */ + sz -= sizeof version; + sz -= sizeof iv; + sz -= sizeof tag; + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) + return 0; + + EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, cp.key, iv); + + /* set expected tag */ + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, sizeof tag, tag); + + /* decrypt until end of ciphertext */ + while (sz) { + if (sz > CRYPTO_BUFFER_SIZE) + r = fread(ibuf, 1, CRYPTO_BUFFER_SIZE, in); + else + r = fread(ibuf, 1, sz, in); + if (!r) + break; + if (!EVP_DecryptUpdate(ctx, obuf, &len, ibuf, r)) + goto end; + if (len && (w = fwrite(obuf, len, 1, out)) != 1) + goto end; + sz -= r; + } + if (ferror(in)) + goto end; + + /* finalize, write last chunk if any and perform authentication check */ + if (!EVP_DecryptFinal_ex(ctx, obuf, &len)) + goto end; + if (len && (w = fwrite(obuf, len, 1, out)) != 1) + goto end; + + fflush(out); + ret = 1; + +end: + EVP_CIPHER_CTX_free(ctx); + return ret; +} + +size_t +crypto_encrypt_buffer(const char *in, size_t inlen, char *out, size_t outlen) +{ + EVP_CIPHER_CTX *ctx; + uint8_t iv[IV_SIZE]; + uint8_t tag[GCM_TAG_SIZE]; + uint8_t version = API_VERSION; + off_t sz; + int olen; + int len = 0; + int ret = 0; + + /* output buffer does not have enough room */ + if (outlen < inlen + sizeof version + sizeof tag + sizeof iv) + return 0; + + /* input should not exceed 64GB */ + sz = inlen; + if (sz >= 0x1000000000LL) + return 0; + + /* prepend version */ + *out = version; + len++; + + /* generate IV */ + memset(iv, 0, sizeof iv); + arc4random_buf(iv, sizeof iv); + memcpy(out + len, iv, sizeof iv); + len += sizeof iv; + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) + return 0; + + EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, cp.key, iv); + + /* encrypt buffer */ + if (!EVP_EncryptUpdate(ctx, out + len, &olen, in, inlen)) + goto end; + len += olen; + + /* finalize and write last chunk if any */ + if (!EVP_EncryptFinal_ex(ctx, out + len, &olen)) + goto end; + len += olen; + + /* get and append tag */ + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, sizeof tag, tag); + memcpy(out + len, tag, sizeof tag); + ret = len + sizeof tag; + +end: + EVP_CIPHER_CTX_free(ctx); + return ret; +} + +size_t +crypto_decrypt_buffer(const char *in, size_t inlen, char *out, size_t outlen) +{ + EVP_CIPHER_CTX *ctx; + uint8_t iv[IV_SIZE]; + uint8_t tag[GCM_TAG_SIZE]; + int olen; + int len = 0; + int ret = 0; + + /* out does not have enough room */ + if (outlen < inlen - sizeof tag + sizeof iv) + return 0; + + /* extract tag */ + memcpy(tag, in + inlen - sizeof tag, sizeof tag); + inlen -= sizeof tag; + + /* check version */ + if (*in != API_VERSION) + return 0; + in++; + inlen--; + + /* extract IV */ + memset(iv, 0, sizeof iv); + memcpy(iv, in, sizeof iv); + inlen -= sizeof iv; + in += sizeof iv; + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) + return 0; + + EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, cp.key, iv); + + /* set expected tag */ + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, sizeof tag, tag); + + /* decrypt buffer */ + if (!EVP_DecryptUpdate(ctx, out, &olen, in, inlen)) + goto end; + len += olen; + + /* finalize, write last chunk if any and perform authentication check */ + if (!EVP_DecryptFinal_ex(ctx, out + len, &olen)) + goto end; + ret = len + olen; + +end: + EVP_CIPHER_CTX_free(ctx); + return ret; +} + +#if 0 +int +main(int argc, char *argv[]) +{ + if (argc != 3) { + printf("usage: crypto <key> <buffer>\n"); + return 1; + } + + if (!crypto_setup(argv[1], strlen(argv[1]))) { + printf("crypto_setup failed\n"); + return 1; + } + + { + char encbuffer[4096]; + size_t enclen; + char decbuffer[4096]; + size_t declen; + + printf("encrypt/decrypt buffer: "); + enclen = crypto_encrypt_buffer(argv[2], strlen(argv[2]), + encbuffer, sizeof encbuffer); + + /* uncomment below to provoke integrity check failure */ + /* + * encbuffer[13] = 0x42; + * encbuffer[14] = 0x42; + * encbuffer[15] = 0x42; + * encbuffer[16] = 0x42; + */ + + declen = crypto_decrypt_buffer(encbuffer, enclen, + decbuffer, sizeof decbuffer); + if (declen != 0 && !strncmp(argv[2], decbuffer, declen)) + printf("ok\n"); + else + printf("nope\n"); + } + + { + FILE *fpin; + FILE *fpout; + printf("encrypt/decrypt file: "); + + fpin = fopen("/etc/passwd", "r"); + fpout = fopen("/tmp/passwd.enc", "w"); + if (!crypto_encrypt_file(fpin, fpout)) { + printf("encryption failed\n"); + return 1; + } + fclose(fpin); + fclose(fpout); + + /* uncomment below to provoke integrity check failure */ + /* + * fpin = fopen("/tmp/passwd.enc", "a"); + * fprintf(fpin, "borken"); + * fclose(fpin); + */ + fpin = fopen("/tmp/passwd.enc", "r"); + fpout = fopen("/tmp/passwd.dec", "w"); + if (!crypto_decrypt_file(fpin, fpout)) + printf("nope\n"); + else + printf("ok\n"); + fclose(fpin); + fclose(fpout); + } + + + return 0; +} +#endif diff --git a/foobar/portable/smtpd/dict.c b/foobar/portable/smtpd/dict.c new file mode 100644 index 00000000..e660f0a5 --- /dev/null +++ b/foobar/portable/smtpd/dict.c @@ -0,0 +1,269 @@ +/* $OpenBSD: dict.c,v 1.6 2018/12/23 16:06:24 gilles Exp $ */ + +/* + * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/tree.h> + +#include <err.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> + +#include "dict.h" + +struct dictentry { + SPLAY_ENTRY(dictentry) entry; + const char *key; + void *data; +}; + +static int dictentry_cmp(struct dictentry *, struct dictentry *); + +SPLAY_PROTOTYPE(_dict, dictentry, entry, dictentry_cmp); + +int +dict_check(struct dict *d, const char *k) +{ + struct dictentry key; + + key.key = k; + return (SPLAY_FIND(_dict, &d->dict, &key) != NULL); +} + +static inline struct dictentry * +dict_alloc(const char *k, void *data) +{ + struct dictentry *e; + size_t s = strlen(k) + 1; + void *t; + + if ((e = malloc(sizeof(*e) + s)) == NULL) + return NULL; + + e->key = t = (char*)(e) + sizeof(*e); + e->data = data; + memmove(t, k, s); + + return (e); +} + +void * +dict_set(struct dict *d, const char *k, void *data) +{ + struct dictentry *entry, key; + char *old; + + key.key = k; + if ((entry = SPLAY_FIND(_dict, &d->dict, &key)) == NULL) { + if ((entry = dict_alloc(k, data)) == NULL) + err(1, "dict_set: malloc"); + SPLAY_INSERT(_dict, &d->dict, entry); + old = NULL; + d->count += 1; + } else { + old = entry->data; + entry->data = data; + } + + return (old); +} + +void +dict_xset(struct dict *d, const char * k, void *data) +{ + struct dictentry *entry; + + if ((entry = dict_alloc(k, data)) == NULL) + err(1, "dict_xset: malloc"); + if (SPLAY_INSERT(_dict, &d->dict, entry)) + errx(1, "dict_xset(%p, %s)", d, k); + d->count += 1; +} + +void * +dict_get(struct dict *d, const char *k) +{ + struct dictentry key, *entry; + + key.key = k; + if ((entry = SPLAY_FIND(_dict, &d->dict, &key)) == NULL) + return (NULL); + + return (entry->data); +} + +void * +dict_xget(struct dict *d, const char *k) +{ + struct dictentry key, *entry; + + key.key = k; + if ((entry = SPLAY_FIND(_dict, &d->dict, &key)) == NULL) + errx(1, "dict_xget(%p, %s)", d, k); + + return (entry->data); +} + +void * +dict_pop(struct dict *d, const char *k) +{ + struct dictentry key, *entry; + void *data; + + key.key = k; + if ((entry = SPLAY_FIND(_dict, &d->dict, &key)) == NULL) + return (NULL); + + data = entry->data; + SPLAY_REMOVE(_dict, &d->dict, entry); + free(entry); + d->count -= 1; + + return (data); +} + +void * +dict_xpop(struct dict *d, const char *k) +{ + struct dictentry key, *entry; + void *data; + + key.key = k; + if ((entry = SPLAY_FIND(_dict, &d->dict, &key)) == NULL) + errx(1, "dict_xpop(%p, %s)", d, k); + + data = entry->data; + SPLAY_REMOVE(_dict, &d->dict, entry); + free(entry); + d->count -= 1; + + return (data); +} + +int +dict_poproot(struct dict *d, void **data) +{ + struct dictentry *entry; + + entry = SPLAY_ROOT(&d->dict); + if (entry == NULL) + return (0); + if (data) + *data = entry->data; + SPLAY_REMOVE(_dict, &d->dict, entry); + free(entry); + d->count -= 1; + + return (1); +} + +int +dict_root(struct dict *d, const char **k, void **data) +{ + struct dictentry *entry; + + entry = SPLAY_ROOT(&d->dict); + if (entry == NULL) + return (0); + if (k) + *k = entry->key; + if (data) + *data = entry->data; + return (1); +} + +int +dict_iter(struct dict *d, void **hdl, const char **k, void **data) +{ + struct dictentry *curr = *hdl; + + if (curr == NULL) + curr = SPLAY_MIN(_dict, &d->dict); + else + curr = SPLAY_NEXT(_dict, &d->dict, curr); + + if (curr) { + *hdl = curr; + if (k) + *k = curr->key; + if (data) + *data = curr->data; + return (1); + } + + return (0); +} + +int +dict_iterfrom(struct dict *d, void **hdl, const char *kfrom, const char **k, + void **data) +{ + struct dictentry *curr = *hdl, key; + + if (curr == NULL) { + if (kfrom == NULL) + curr = SPLAY_MIN(_dict, &d->dict); + else { + key.key = kfrom; + curr = SPLAY_FIND(_dict, &d->dict, &key); + if (curr == NULL) { + SPLAY_INSERT(_dict, &d->dict, &key); + curr = SPLAY_NEXT(_dict, &d->dict, &key); + SPLAY_REMOVE(_dict, &d->dict, &key); + } + } + } else + curr = SPLAY_NEXT(_dict, &d->dict, curr); + + if (curr) { + *hdl = curr; + if (k) + *k = curr->key; + if (data) + *data = curr->data; + return (1); + } + + return (0); +} + +void +dict_merge(struct dict *dst, struct dict *src) +{ + struct dictentry *entry; + + while (!SPLAY_EMPTY(&src->dict)) { + entry = SPLAY_ROOT(&src->dict); + SPLAY_REMOVE(_dict, &src->dict, entry); + if (SPLAY_INSERT(_dict, &dst->dict, entry)) + errx(1, "dict_merge: duplicate"); + } + dst->count += src->count; + src->count = 0; +} + +static int +dictentry_cmp(struct dictentry *a, struct dictentry *b) +{ + return strcmp(a->key, b->key); +} + +SPLAY_GENERATE(_dict, dictentry, entry, dictentry_cmp); diff --git a/foobar/portable/smtpd/dict.h b/foobar/portable/smtpd/dict.h new file mode 100644 index 00000000..c5d47e1a --- /dev/null +++ b/foobar/portable/smtpd/dict.h @@ -0,0 +1,48 @@ +/* $OpenBSD: dict.h,v 1.1 2018/12/23 16:06:24 gilles Exp $ */ + +/* + * Copyright (c) 2013 Eric Faurot <eric@openbsd.org> + * Copyright (c) 2011 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _DICT_H_ +#define _DICT_H_ + +SPLAY_HEAD(_dict, dictentry); + +struct dict { + struct _dict dict; + size_t count; +}; + + +/* dict.c */ +#define dict_init(d) do { SPLAY_INIT(&((d)->dict)); (d)->count = 0; } while(0) +#define dict_empty(d) SPLAY_EMPTY(&((d)->dict)) +#define dict_count(d) ((d)->count) +int dict_check(struct dict *, const char *); +void *dict_set(struct dict *, const char *, void *); +void dict_xset(struct dict *, const char *, void *); +void *dict_get(struct dict *, const char *); +void *dict_xget(struct dict *, const char *); +void *dict_pop(struct dict *, const char *); +void *dict_xpop(struct dict *, const char *); +int dict_poproot(struct dict *, void **); +int dict_root(struct dict *, const char **, void **); +int dict_iter(struct dict *, void **, const char **, void **); +int dict_iterfrom(struct dict *, void **, const char *, const char **, void **); +void dict_merge(struct dict *, struct dict *); + +#endif diff --git a/foobar/portable/smtpd/dns.c b/foobar/portable/smtpd/dns.c new file mode 100644 index 00000000..a3107e89 --- /dev/null +++ b/foobar/portable/smtpd/dns.c @@ -0,0 +1,379 @@ +/* $OpenBSD: dns.c,v 1.89 2019/09/18 11:26:30 eric Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2011-2014 Eric Faurot <eric@faurot.net> + * + * 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 "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/tree.h> +#include <sys/queue.h> +#include <sys/uio.h> + +#include <netinet/in.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +#include <arpa/nameser_compat.h> +#endif +#include <netdb.h> + +#include <asr.h> +#include <event.h> +#include <netdb.h> +#include <resolv.h> +#include <imsg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> + +#include "smtpd.h" +#include "log.h" +#include "unpack_dns.h" + +/* On OpenBSD, this function is not needed because we don't free addrinfo */ +#if defined(NOOP_ASR_FREEADDRINFO) +#define asr_freeaddrinfo(x) do { } while(0); +#endif + +struct dns_lookup { + struct dns_session *session; + char *host; + int preference; +}; + +struct dns_session { + struct mproc *p; + uint64_t reqid; + int type; + char name[HOST_NAME_MAX+1]; + size_t mxfound; + int error; + int refcount; +}; + +static void dns_lookup_host(struct dns_session *, const char *, int); +static void dns_dispatch_host(struct asr_result *, void *); +static void dns_dispatch_mx(struct asr_result *, void *); +static void dns_dispatch_mx_preference(struct asr_result *, void *); + +static int +domainname_is_addr(const char *s, struct sockaddr *sa, socklen_t *sl) +{ + struct addrinfo hints, *res; + socklen_t sl2; + size_t l; + char buf[SMTPD_MAXDOMAINPARTSIZE]; + int i6, error; + + if (*s != '[') + return (0); + + i6 = (strncasecmp("[IPv6:", s, 6) == 0); + s += i6 ? 6 : 1; + + l = strlcpy(buf, s, sizeof(buf)); + if (l >= sizeof(buf) || l == 0 || buf[l - 1] != ']') + return (0); + + buf[l - 1] = '\0'; + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_NUMERICHOST; + hints.ai_socktype = SOCK_STREAM; + if (i6) + hints.ai_family = AF_INET6; + + res = NULL; + if ((error = getaddrinfo(buf, NULL, &hints, &res))) { + log_warnx("getaddrinfo: %s", gai_strerror(error)); + } + + if (!res) + return (0); + + if (sa && sl) { + sl2 = *sl; + if (sl2 > res->ai_addrlen) + sl2 = res->ai_addrlen; + memmove(sa, res->ai_addr, sl2); + *sl = res->ai_addrlen; + } + + freeaddrinfo(res); + return (1); +} + +void +dns_imsg(struct mproc *p, struct imsg *imsg) +{ + struct sockaddr_storage ss; + struct dns_session *s; + struct sockaddr *sa; + struct asr_query *as; + struct msg m; + const char *domain, *mx, *host; + socklen_t sl; + + s = xcalloc(1, sizeof *s); + s->type = imsg->hdr.type; + s->p = p; + + m_msg(&m, imsg); + m_get_id(&m, &s->reqid); + + switch (s->type) { + + case IMSG_MTA_DNS_HOST: + m_get_string(&m, &host); + m_end(&m); + dns_lookup_host(s, host, -1); + return; + + case IMSG_MTA_DNS_MX: + m_get_string(&m, &domain); + m_end(&m); + (void)strlcpy(s->name, domain, sizeof(s->name)); + + sa = (struct sockaddr *)&ss; + sl = sizeof(ss); + + if (domainname_is_addr(domain, sa, &sl)) { + m_create(s->p, IMSG_MTA_DNS_HOST, 0, 0, -1); + m_add_id(s->p, s->reqid); + m_add_string(s->p, sockaddr_to_text(sa)); + m_add_sockaddr(s->p, sa); + m_add_int(s->p, -1); + m_close(s->p); + + m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1); + m_add_id(s->p, s->reqid); + m_add_int(s->p, DNS_OK); + m_close(s->p); + free(s); + return; + } + + as = res_query_async(s->name, C_IN, T_MX, NULL); + if (as == NULL) { + log_warn("warn: res_query_async: %s", s->name); + m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1); + m_add_id(s->p, s->reqid); + m_add_int(s->p, DNS_EINVAL); + m_close(s->p); + free(s); + return; + } + + event_asr_run(as, dns_dispatch_mx, s); + return; + + case IMSG_MTA_DNS_MX_PREFERENCE: + m_get_string(&m, &domain); + m_get_string(&m, &mx); + m_end(&m); + (void)strlcpy(s->name, mx, sizeof(s->name)); + + as = res_query_async(domain, C_IN, T_MX, NULL); + if (as == NULL) { + m_create(s->p, IMSG_MTA_DNS_MX_PREFERENCE, 0, 0, -1); + m_add_id(s->p, s->reqid); + m_add_int(s->p, DNS_ENOTFOUND); + m_close(s->p); + free(s); + return; + } + + event_asr_run(as, dns_dispatch_mx_preference, s); + return; + + default: + log_warnx("warn: bad dns request %d", s->type); + fatal(NULL); + } +} + +static void +dns_dispatch_host(struct asr_result *ar, void *arg) +{ + struct dns_session *s; + struct dns_lookup *lookup = arg; + struct addrinfo *ai; + + s = lookup->session; + + for (ai = ar->ar_addrinfo; ai; ai = ai->ai_next) { + s->mxfound++; + m_create(s->p, IMSG_MTA_DNS_HOST, 0, 0, -1); + m_add_id(s->p, s->reqid); + m_add_string(s->p, lookup->host); + m_add_sockaddr(s->p, ai->ai_addr); + m_add_int(s->p, lookup->preference); + m_close(s->p); + } + free(lookup->host); + free(lookup); + if (ar->ar_addrinfo) + asr_freeaddrinfo(ar->ar_addrinfo); + + if (ar->ar_gai_errno) + s->error = ar->ar_gai_errno; + + if (--s->refcount) + return; + + m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1); + m_add_id(s->p, s->reqid); + m_add_int(s->p, s->mxfound ? DNS_OK : DNS_ENOTFOUND); + m_close(s->p); + free(s); +} + +static void +dns_dispatch_mx(struct asr_result *ar, void *arg) +{ + struct dns_session *s = arg; + struct unpack pack; + struct dns_header h; + struct dns_query q; + struct dns_rr rr; + char buf[512]; + size_t found; + + if (ar->ar_h_errno && ar->ar_h_errno != NO_DATA && + ar->ar_h_errno != NOTIMP) { + + m_create(s->p, IMSG_MTA_DNS_HOST_END, 0, 0, -1); + m_add_id(s->p, s->reqid); + if (ar->ar_rcode == NXDOMAIN) + m_add_int(s->p, DNS_ENONAME); + else if (ar->ar_h_errno == NO_RECOVERY) + m_add_int(s->p, DNS_EINVAL); + else + m_add_int(s->p, DNS_RETRY); + m_close(s->p); + free(s); + free(ar->ar_data); + return; + } + + unpack_init(&pack, ar->ar_data, ar->ar_datalen); + unpack_header(&pack, &h); + unpack_query(&pack, &q); + + found = 0; + for (; h.ancount; h.ancount--) { + unpack_rr(&pack, &rr); + if (rr.rr_type != T_MX) + continue; + print_dname(rr.rr.mx.exchange, buf, sizeof(buf)); + buf[strlen(buf) - 1] = '\0'; + dns_lookup_host(s, buf, rr.rr.mx.preference); + found++; + } + free(ar->ar_data); + + /* fallback to host if no MX is found. */ + if (found == 0) + dns_lookup_host(s, s->name, 0); +} + +static void +dns_dispatch_mx_preference(struct asr_result *ar, void *arg) +{ + struct dns_session *s = arg; + struct unpack pack; + struct dns_header h; + struct dns_query q; + struct dns_rr rr; + char buf[512]; + int error; + + if (ar->ar_h_errno) { + if (ar->ar_rcode == NXDOMAIN) + error = DNS_ENONAME; + else if (ar->ar_h_errno == NO_RECOVERY + || ar->ar_h_errno == NO_DATA) + error = DNS_EINVAL; + else + error = DNS_RETRY; + } + else { + error = DNS_ENOTFOUND; + unpack_init(&pack, ar->ar_data, ar->ar_datalen); + unpack_header(&pack, &h); + unpack_query(&pack, &q); + for (; h.ancount; h.ancount--) { + unpack_rr(&pack, &rr); + if (rr.rr_type != T_MX) + continue; + print_dname(rr.rr.mx.exchange, buf, sizeof(buf)); + buf[strlen(buf) - 1] = '\0'; + if (!strcasecmp(s->name, buf)) { + error = DNS_OK; + break; + } + } + } + + free(ar->ar_data); + + m_create(s->p, IMSG_MTA_DNS_MX_PREFERENCE, 0, 0, -1); + m_add_id(s->p, s->reqid); + m_add_int(s->p, error); + if (error == DNS_OK) + m_add_int(s->p, rr.rr.mx.preference); + m_close(s->p); + free(s); +} + +static void +dns_lookup_host(struct dns_session *s, const char *host, int preference) +{ + struct dns_lookup *lookup; + struct addrinfo hints; + char hostcopy[HOST_NAME_MAX+1]; + char *p; + void *as; + + lookup = xcalloc(1, sizeof *lookup); + lookup->preference = preference; + lookup->host = xstrdup(host); + lookup->session = s; + s->refcount++; + + if (*host == '[') { + if (strncasecmp("[IPv6:", host, 6) == 0) + host += 6; + else + host += 1; + (void)strlcpy(hostcopy, host, sizeof hostcopy); + p = strchr(hostcopy, ']'); + if (p) + *p = 0; + host = hostcopy; + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_ADDRCONFIG; + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + as = getaddrinfo_async(host, NULL, &hints, NULL); + event_asr_run(as, dns_dispatch_host, lookup); +} diff --git a/foobar/portable/smtpd/enqueue.c b/foobar/portable/smtpd/enqueue.c new file mode 100644 index 00000000..0ef694b5 --- /dev/null +++ b/foobar/portable/smtpd/enqueue.c @@ -0,0 +1,932 @@ +/* $OpenBSD: enqueue.c,v 1.118 2020/03/18 20:17:14 eric Exp $ */ + +/* + * Copyright (c) 2005 Henning Brauer <henning@bulabula.org> + * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, 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 "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/tree.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <grp.h> +#include <imsg.h> +#include <inttypes.h> +#include <pwd.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <limits.h> + +#include "smtpd.h" + +extern struct imsgbuf *ibuf; + +void usage(void); +static void build_from(char *, struct passwd *); +static int parse_message(FILE *, int, int, FILE *); +static void parse_addr(char *, size_t, int); +static void parse_addr_terminal(int); +static char *qualify_addr(char *); +static void rcpt_add(char *); +static int open_connection(void); +static int get_responses(FILE *, int); +static int send_line(FILE *, int, char *, ...); +static int enqueue_offline(int, char *[], FILE *, FILE *); +static int savedeadletter(struct passwd *, FILE *); + +extern int srv_connected(void); + +enum headerfields { + HDR_NONE, + HDR_FROM, + HDR_TO, + HDR_CC, + HDR_BCC, + HDR_SUBJECT, + HDR_DATE, + HDR_MSGID, + HDR_MIME_VERSION, + HDR_CONTENT_TYPE, + HDR_CONTENT_DISPOSITION, + HDR_CONTENT_TRANSFER_ENCODING, + HDR_USER_AGENT +}; + +struct { + char *word; + enum headerfields type; +} keywords[] = { + { "From:", HDR_FROM }, + { "To:", HDR_TO }, + { "Cc:", HDR_CC }, + { "Bcc:", HDR_BCC }, + { "Subject:", HDR_SUBJECT }, + { "Date:", HDR_DATE }, + { "Message-Id:", HDR_MSGID }, + { "MIME-Version:", HDR_MIME_VERSION }, + { "Content-Type:", HDR_CONTENT_TYPE }, + { "Content-Disposition:", HDR_CONTENT_DISPOSITION }, + { "Content-Transfer-Encoding:", HDR_CONTENT_TRANSFER_ENCODING }, + { "User-Agent:", HDR_USER_AGENT }, +}; + +#define LINESPLIT 990 +#define SMTP_LINELEN 1000 +#define TIMEOUTMSG "Timeout\n" + +#define WSP(c) (c == ' ' || c == '\t') + +int verbose = 0; +static char host[HOST_NAME_MAX+1]; +char *user = NULL; +time_t timestamp; + +struct { + int fd; + char *from; + char *fromname; + char **rcpts; + char *dsn_notify; + char *dsn_ret; + char *dsn_envid; + int rcpt_cnt; + int need_linesplit; + int saw_date; + int saw_msgid; + int saw_from; + int saw_mime_version; + int saw_content_type; + int saw_content_disposition; + int saw_content_transfer_encoding; + int saw_user_agent; + int noheader; +} msg; + +struct { + uint quote; + uint comment; + uint esc; + uint brackets; + size_t wpos; + char buf[SMTP_LINELEN]; +} pstate; + +#define QP_TEST_WRAP(fp, buf, linelen, size) do { \ + if (((linelen) += (size)) + 1 > 76) { \ + fprintf((fp), "=\r\n"); \ + if (buf[0] == '.') \ + fprintf((fp), "."); \ + (linelen) = (size); \ + } \ +} while (0) + +/* RFC 2045 section 6.7 */ +static void +qp_encoded_write(FILE *fp, char *buf) +{ + size_t linelen = 0; + + for (;buf[0] != '\0' && buf[0] != '\n'; buf++) { + /* + * Point 3: Any TAB (HT) or SPACE characters on an encoded line + * MUST thus be followed on that line by a printable character. + * + * Ergo, only encode if the next character is EOL. + */ + if (buf[0] == ' ' || buf[0] == '\t') { + if (buf[1] == '\n') { + QP_TEST_WRAP(fp, buf, linelen, 3); + fprintf(fp, "=%2X", *buf & 0xff); + } else { + QP_TEST_WRAP(fp, buf, linelen, 1); + fprintf(fp, "%c", *buf & 0xff); + } + /* + * Point 1, with exclusion of point 2, skip EBCDIC NOTE. + * Do this after whitespace check, else they would match here. + */ + } else if (!((buf[0] >= 33 && buf[0] <= 60) || + (buf[0] >= 62 && buf[0] <= 126))) { + QP_TEST_WRAP(fp, buf, linelen, 3); + fprintf(fp, "=%2X", *buf & 0xff); + /* Point 2: 33 through 60 inclusive, and 62 through 126 */ + } else { + QP_TEST_WRAP(fp, buf, linelen, 1); + fprintf(fp, "%c", *buf); + } + } + fprintf(fp, "\r\n"); +} + +int +enqueue(int argc, char *argv[], FILE *ofp) +{ + int i, ch, tflag = 0; + char *fake_from = NULL, *buf = NULL; + struct passwd *pw; + FILE *fp = NULL, *fout; + size_t sz = 0, envid_sz = 0; + ssize_t len; + char *line; + int inheaders = 1; + int save_argc; + char **save_argv; + int no_getlogin = 0; + + memset(&msg, 0, sizeof(msg)); + time(×tamp); + + save_argc = argc; + save_argv = argv; + + while ((ch = getopt(argc, argv, + "A:B:b:E::e:F:f:iJ::L:mN:o:p:qr:R:StvV:x")) != -1) { + switch (ch) { + case 'f': + fake_from = optarg; + break; + case 'F': + msg.fromname = optarg; + break; + case 'N': + msg.dsn_notify = optarg; + break; + case 'r': + fake_from = optarg; + break; + case 'R': + msg.dsn_ret = optarg; + break; + case 'S': + no_getlogin = 1; + break; + case 't': + tflag = 1; + break; + case 'v': + verbose = 1; + break; + case 'V': + msg.dsn_envid = optarg; + break; + /* all remaining: ignored, sendmail compat */ + case 'A': + case 'B': + case 'b': + case 'E': + case 'e': + case 'i': + case 'L': + case 'm': + case 'o': + case 'p': + case 'x': + break; + case 'q': + /* XXX: implement "process all now" */ + return (EX_SOFTWARE); + default: + usage(); + } + } + + argc -= optind; + argv += optind; + + if (getmailname(host, sizeof(host)) == -1) + errx(EX_NOHOST, "getmailname"); + if (no_getlogin) { + if ((pw = getpwuid(getuid())) == NULL) + user = "anonymous"; + if (pw != NULL) + user = xstrdup(pw->pw_name); + } + else { + uid_t ruid = getuid(); + + if ((user = getlogin()) != NULL && *user != '\0') { + if ((pw = getpwnam(user)) == NULL || + (ruid != 0 && ruid != pw->pw_uid)) + pw = getpwuid(ruid); + } else if ((pw = getpwuid(ruid)) == NULL) { + user = "anonymous"; + } + user = xstrdup(pw ? pw->pw_name : user); + } + + build_from(fake_from, pw); + + while (argc > 0) { + rcpt_add(argv[0]); + argv++; + argc--; + } + + if ((fp = tmpfile()) == NULL) + err(EX_UNAVAILABLE, "tmpfile"); + + msg.noheader = parse_message(stdin, fake_from == NULL, tflag, fp); + + if (msg.rcpt_cnt == 0) + errx(EX_SOFTWARE, "no recipients"); + + /* init session */ + rewind(fp); + + /* check if working in offline mode */ + /* If the server is not running, enqueue the message offline */ + + if (!srv_connected()) { +#if HAVE_PLEDGE + if (pledge("stdio", NULL) == -1) + err(1, "pledge"); +#endif + return (enqueue_offline(save_argc, save_argv, fp, ofp)); + } + + if ((msg.fd = open_connection()) == -1) + errx(EX_UNAVAILABLE, "server too busy"); + +#if HAVE_PLEDGE + if (pledge("stdio wpath cpath", NULL) == -1) + err(1, "pledge"); +#endif + + fout = fdopen(msg.fd, "a+"); + if (fout == NULL) + err(EX_UNAVAILABLE, "fdopen"); + + /* + * We need to call get_responses after every command because we don't + * support PIPELINING on the server-side yet. + */ + + /* banner */ + if (!get_responses(fout, 1)) + goto fail; + + if (!send_line(fout, verbose, "EHLO localhost\r\n")) + goto fail; + if (!get_responses(fout, 1)) + goto fail; + + if (msg.dsn_envid != NULL) + envid_sz = strlen(msg.dsn_envid); + + if (!send_line(fout, verbose, "MAIL FROM:<%s> %s%s %s%s\r\n", + msg.from, + msg.dsn_ret ? "RET=" : "", + msg.dsn_ret ? msg.dsn_ret : "", + envid_sz ? "ENVID=" : "", + envid_sz ? msg.dsn_envid : "")) + goto fail; + if (!get_responses(fout, 1)) + goto fail; + + for (i = 0; i < msg.rcpt_cnt; i++) { + if (!send_line(fout, verbose, "RCPT TO:<%s> %s%s\r\n", + msg.rcpts[i], + msg.dsn_notify ? "NOTIFY=" : "", + msg.dsn_notify ? msg.dsn_notify : "")) + goto fail; + if (!get_responses(fout, 1)) + goto fail; + } + + if (!send_line(fout, verbose, "DATA\r\n")) + goto fail; + if (!get_responses(fout, 1)) + goto fail; + + /* add From */ + if (!msg.saw_from && !send_line(fout, 0, "From: %s%s<%s>\r\n", + msg.fromname ? msg.fromname : "", msg.fromname ? " " : "", + msg.from)) + goto fail; + + /* add Date */ + if (!msg.saw_date && !send_line(fout, 0, "Date: %s\r\n", + time_to_text(timestamp))) + goto fail; + + if (msg.need_linesplit) { + /* we will always need to mime encode for long lines */ + if (!msg.saw_mime_version && !send_line(fout, 0, + "MIME-Version: 1.0\r\n")) + goto fail; + if (!msg.saw_content_type && !send_line(fout, 0, + "Content-Type: text/plain; charset=unknown-8bit\r\n")) + goto fail; + if (!msg.saw_content_disposition && !send_line(fout, 0, + "Content-Disposition: inline\r\n")) + goto fail; + if (!msg.saw_content_transfer_encoding && !send_line(fout, 0, + "Content-Transfer-Encoding: quoted-printable\r\n")) + goto fail; + } + + /* add separating newline */ + if (msg.noheader) { + if (!send_line(fout, 0, "\r\n")) + goto fail; + inheaders = 0; + } + + for (;;) { + if ((len = getline(&buf, &sz, fp)) == -1) { + if (feof(fp)) + break; + else + err(EX_UNAVAILABLE, "getline"); + } + + /* newlines have been normalized on first parsing */ + if (buf[len-1] != '\n') + errx(EX_SOFTWARE, "expect EOL"); + len--; + + if (buf[0] == '.') { + if (fputc('.', fout) == EOF) + goto fail; + } + + line = buf; + + if (inheaders) { + if (strncasecmp("from ", line, 5) == 0) + continue; + if (strncasecmp("return-path: ", line, 13) == 0) + continue; + } + + if (msg.saw_content_transfer_encoding || msg.noheader || + inheaders || !msg.need_linesplit) { + if (!send_line(fout, 0, "%.*s\r\n", (int)len, line)) + goto fail; + if (inheaders && buf[0] == '\n') + inheaders = 0; + continue; + } + + /* we don't have a content transfer encoding, use our default */ + qp_encoded_write(fout, line); + } + free(buf); + if (!send_line(fout, verbose, ".\r\n")) + goto fail; + if (!get_responses(fout, 1)) + goto fail; + + if (!send_line(fout, verbose, "QUIT\r\n")) + goto fail; + if (!get_responses(fout, 1)) + goto fail; + + fclose(fp); + fclose(fout); + + exit(EX_OK); + +fail: + if (pw) + savedeadletter(pw, fp); + exit(EX_SOFTWARE); +} + +static int +get_responses(FILE *fin, int n) +{ + char *buf = NULL; + size_t sz = 0; + ssize_t len; + int e, ret = 0; + + fflush(fin); + if ((e = ferror(fin))) { + warnx("ferror: %d", e); + goto err; + } + + while (n) { + if ((len = getline(&buf, &sz, fin)) == -1) { + if (ferror(fin)) { + warn("getline"); + goto err; + } else if (feof(fin)) + break; + else + err(EX_UNAVAILABLE, "getline"); + } + + /* account for \r\n linebreaks */ + if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n') + buf[--len - 1] = '\n'; + + if (len < 4) { + warnx("bad response"); + goto err; + } + + if (verbose) + printf("<<< %.*s", (int)len, buf); + + if (buf[3] == '-') + continue; + if (buf[0] != '2' && buf[0] != '3') { + warnx("command failed: %.*s", (int)len, buf); + goto err; + } + n--; + } + + ret = 1; +err: + free(buf); + return ret; +} + +static int +send_line(FILE *fp, int v, char *fmt, ...) +{ + int ret = 0; + va_list ap; + + va_start(ap, fmt); + if (vfprintf(fp, fmt, ap) >= 0) + ret = 1; + va_end(ap); + + if (ret && v) { + printf(">>> "); + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + } + + return (ret); +} + +static void +build_from(char *fake_from, struct passwd *pw) +{ + char *p; + + if (fake_from == NULL) + msg.from = qualify_addr(user); + else { + if (fake_from[0] == '<') { + if (fake_from[strlen(fake_from) - 1] != '>') + errx(1, "leading < but no trailing >"); + fake_from[strlen(fake_from) - 1] = 0; + p = xstrdup(fake_from + 1); + + msg.from = qualify_addr(p); + free(p); + } else + msg.from = qualify_addr(fake_from); + } + + if (msg.fromname == NULL && fake_from == NULL && pw != NULL) { + int len, apos; + + len = strcspn(pw->pw_gecos, ","); + if ((p = memchr(pw->pw_gecos, '&', len))) { + apos = p - pw->pw_gecos; + if (asprintf(&msg.fromname, "%.*s%s%.*s", + apos, pw->pw_gecos, + pw->pw_name, + len - apos - 1, p + 1) == -1) + err(1, "asprintf"); + msg.fromname[apos] = toupper((unsigned char)msg.fromname[apos]); + } else { + if (asprintf(&msg.fromname, "%.*s", len, + pw->pw_gecos) == -1) + err(1, "asprintf"); + } + } +} + +static int +parse_message(FILE *fin, int get_from, int tflag, FILE *fout) +{ + char *buf = NULL; + size_t sz = 0; + ssize_t len; + uint i, cur = HDR_NONE; + uint header_seen = 0, header_done = 0; + + memset(&pstate, 0, sizeof(pstate)); + for (;;) { + if ((len = getline(&buf, &sz, fin)) == -1) { + if (feof(fin)) + break; + else + err(EX_UNAVAILABLE, "getline"); + } + + /* account for \r\n linebreaks */ + if (len >= 2 && buf[len - 2] == '\r' && buf[len - 1] == '\n') + buf[--len - 1] = '\n'; + + if (len == 1 && buf[0] == '\n') /* end of header */ + header_done = 1; + + if (!WSP(buf[0])) { /* whitespace -> continuation */ + if (cur == HDR_FROM) + parse_addr_terminal(1); + if (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC) + parse_addr_terminal(0); + cur = HDR_NONE; + } + + /* not really exact, if we are still in headers */ + if (len + (buf[len - 1] == '\n' ? 0 : 1) >= LINESPLIT) + msg.need_linesplit = 1; + + for (i = 0; !header_done && cur == HDR_NONE && + i < nitems(keywords); i++) + if ((size_t)len > strlen(keywords[i].word) && + !strncasecmp(buf, keywords[i].word, + strlen(keywords[i].word))) + cur = keywords[i].type; + + if (cur != HDR_NONE) + header_seen = 1; + + if (cur != HDR_BCC) { + if (!send_line(fout, 0, "%.*s", (int)len, buf)) + err(1, "write error"); + if (buf[len - 1] != '\n') { + if (fputc('\n', fout) == EOF) + err(1, "write error"); + } + } + + /* + * using From: as envelope sender is not sendmail compatible, + * but I really want it that way - maybe needs a knob + */ + if (cur == HDR_FROM) { + msg.saw_from++; + if (get_from) + parse_addr(buf, len, 1); + } + + if (tflag && (cur == HDR_TO || cur == HDR_CC || cur == HDR_BCC)) + parse_addr(buf, len, 0); + + if (cur == HDR_DATE) + msg.saw_date++; + if (cur == HDR_MSGID) + msg.saw_msgid++; + if (cur == HDR_MIME_VERSION) + msg.saw_mime_version = 1; + if (cur == HDR_CONTENT_TYPE) + msg.saw_content_type = 1; + if (cur == HDR_CONTENT_DISPOSITION) + msg.saw_content_disposition = 1; + if (cur == HDR_CONTENT_TRANSFER_ENCODING) + msg.saw_content_transfer_encoding = 1; + if (cur == HDR_USER_AGENT) + msg.saw_user_agent = 1; + } + + free(buf); + return (!header_seen); +} + +static void +parse_addr(char *s, size_t len, int is_from) +{ + size_t pos = 0; + int terminal = 0; + + /* unless this is a continuation... */ + if (!WSP(s[pos]) && s[pos] != ',' && s[pos] != ';') { + /* ... skip over everything before the ':' */ + for (; pos < len && s[pos] != ':'; pos++) + ; /* nothing */ + /* ... and check & reset parser state */ + parse_addr_terminal(is_from); + } + + /* skip over ':' ',' ';' and whitespace */ + for (; pos < len && !pstate.quote && (WSP(s[pos]) || s[pos] == ':' || + s[pos] == ',' || s[pos] == ';'); pos++) + ; /* nothing */ + + for (; pos < len; pos++) { + if (!pstate.esc && !pstate.quote && s[pos] == '(') + pstate.comment++; + if (!pstate.comment && !pstate.esc && s[pos] == '"') + pstate.quote = !pstate.quote; + + if (!pstate.comment && !pstate.quote && !pstate.esc) { + if (s[pos] == ':') { /* group */ + for (pos++; pos < len && WSP(s[pos]); pos++) + ; /* nothing */ + pstate.wpos = 0; + } + if (s[pos] == '\n' || s[pos] == '\r') + break; + if (s[pos] == ',' || s[pos] == ';') { + terminal = 1; + break; + } + if (s[pos] == '<') { + pstate.brackets = 1; + pstate.wpos = 0; + } + if (pstate.brackets && s[pos] == '>') + terminal = 1; + } + + if (!pstate.comment && !terminal && (!(!(pstate.quote || + pstate.esc) && (s[pos] == '<' || WSP(s[pos]))))) { + if (pstate.wpos >= sizeof(pstate.buf)) + errx(1, "address exceeds buffer size"); + pstate.buf[pstate.wpos++] = s[pos]; + } + + if (!pstate.quote && pstate.comment && s[pos] == ')') + pstate.comment--; + + if (!pstate.esc && !pstate.comment && s[pos] == '\\') + pstate.esc = 1; + else + pstate.esc = 0; + } + + if (terminal) + parse_addr_terminal(is_from); + + for (; pos < len && (s[pos] == '\r' || s[pos] == '\n'); pos++) + ; /* nothing */ + + if (pos < len) + parse_addr(s + pos, len - pos, is_from); +} + +static void +parse_addr_terminal(int is_from) +{ + if (pstate.comment || pstate.quote || pstate.esc) + errx(1, "syntax error in address"); + if (pstate.wpos) { + if (pstate.wpos >= sizeof(pstate.buf)) + errx(1, "address exceeds buffer size"); + pstate.buf[pstate.wpos] = '\0'; + if (is_from) + msg.from = qualify_addr(pstate.buf); + else + rcpt_add(pstate.buf); + pstate.wpos = 0; + } +} + +static char * +qualify_addr(char *in) +{ + char *out; + + if (strlen(in) > 0 && strchr(in, '@') == NULL) { + if (asprintf(&out, "%s@%s", in, host) == -1) + err(1, "qualify asprintf"); + } else + out = xstrdup(in); + + return (out); +} + +static void +rcpt_add(char *addr) +{ + void *nrcpts; + char *p; + int n; + + n = 1; + p = addr; + while ((p = strchr(p, ',')) != NULL) { + n++; + p++; + } + + if ((nrcpts = reallocarray(msg.rcpts, + msg.rcpt_cnt + n, sizeof(char *))) == NULL) + err(1, "rcpt_add realloc"); + msg.rcpts = nrcpts; + + while (n--) { + if ((p = strchr(addr, ',')) != NULL) + *p++ = '\0'; + msg.rcpts[msg.rcpt_cnt++] = qualify_addr(addr); + if (p == NULL) + break; + addr = p; + } +} + +static int +open_connection(void) +{ + struct imsg imsg; + int fd; + int n; + + imsg_compose(ibuf, IMSG_CTL_SMTP_SESSION, IMSG_VERSION, 0, -1, NULL, 0); + + while (ibuf->w.queued) + if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN) + err(1, "write error"); + + while (1) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + errx(1, "imsg_read error"); + if (n == 0) + errx(1, "pipe closed"); + + if ((n = imsg_get(ibuf, &imsg)) == -1) + errx(1, "imsg_get error"); + if (n == 0) + continue; + + switch (imsg.hdr.type) { + case IMSG_CTL_OK: + break; + case IMSG_CTL_FAIL: + errx(1, "server disallowed submission request"); + default: + errx(1, "unexpected imsg reply type"); + } + + fd = imsg.fd; + imsg_free(&imsg); + + break; + } + + return fd; +} + +static int +enqueue_offline(int argc, char *argv[], FILE *ifile, FILE *ofile) +{ + int i, ch; + + for (i = 1; i < argc; i++) { + if (strchr(argv[i], '|') != NULL) { + warnx("%s contains illegal character", argv[i]); + ftruncate(fileno(ofile), 0); + exit(EX_SOFTWARE); + } + if (fprintf(ofile, "%s%s", i == 1 ? "" : "|", argv[i]) < 0) + goto write_error; + } + + if (fputc('\n', ofile) == EOF) + goto write_error; + + while ((ch = fgetc(ifile)) != EOF) { + if (fputc(ch, ofile) == EOF) + goto write_error; + } + + if (ferror(ifile)) { + warn("read error"); + ftruncate(fileno(ofile), 0); + exit(EX_UNAVAILABLE); + } + + if (fclose(ofile) == EOF) + goto write_error; + + return (EX_TEMPFAIL); +write_error: + warn("write error"); + ftruncate(fileno(ofile), 0); + exit(EX_UNAVAILABLE); +} + +static int +savedeadletter(struct passwd *pw, FILE *in) +{ + char buffer[PATH_MAX]; + FILE *fp; + char *buf = NULL; + size_t sz = 0; + ssize_t len; + + (void)snprintf(buffer, sizeof buffer, "%s/dead.letter", pw->pw_dir); + + if (fseek(in, 0, SEEK_SET) != 0) + return 0; + + if ((fp = fopen(buffer, "w")) == NULL) + return 0; + + /* add From */ + if (!msg.saw_from) + fprintf(fp, "From: %s%s<%s>\n", + msg.fromname ? msg.fromname : "", + msg.fromname ? " " : "", + msg.from); + + /* add Date */ + if (!msg.saw_date) + fprintf(fp, "Date: %s\n", time_to_text(timestamp)); + + if (msg.need_linesplit) { + /* we will always need to mime encode for long lines */ + if (!msg.saw_mime_version) + fprintf(fp, "MIME-Version: 1.0\n"); + if (!msg.saw_content_type) + fprintf(fp, "Content-Type: text/plain; " + "charset=unknown-8bit\n"); + if (!msg.saw_content_disposition) + fprintf(fp, "Content-Disposition: inline\n"); + if (!msg.saw_content_transfer_encoding) + fprintf(fp, "Content-Transfer-Encoding: " + "quoted-printable\n"); + } + + /* add separating newline */ + if (msg.noheader) + fprintf(fp, "\n"); + + while ((len = getline(&buf, &sz, in)) != -1) { + if (buf[len - 1] == '\n') + buf[len - 1] = '\0'; + fprintf(fp, "%s\n", buf); + } + + free(buf); + fprintf(fp, "\n"); + fclose(fp); + return 1; +} diff --git a/foobar/portable/smtpd/envelope.c b/foobar/portable/smtpd/envelope.c new file mode 100644 index 00000000..35d98b79 --- /dev/null +++ b/foobar/portable/smtpd/envelope.c @@ -0,0 +1,786 @@ +/* $OpenBSD: envelope.c,v 1.47 2019/11/25 14:18:32 gilles Exp $ */ + +/* + * Copyright (c) 2013 Eric Faurot <eric@openbsd.org> + * Copyright (c) 2011-2013 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <imsg.h> +#include <inttypes.h> +#include <pwd.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "smtpd.h" +#include "log.h" + +static int envelope_ascii_load(struct envelope *, struct dict *); +static void envelope_ascii_dump(const struct envelope *, char **, size_t *, + const char *); + +void +envelope_set_errormsg(struct envelope *e, char *fmt, ...) +{ + int ret; + va_list ap; + + va_start(ap, fmt); + ret = vsnprintf(e->errorline, sizeof(e->errorline), fmt, ap); + va_end(ap); + + /* this should not happen */ + if (ret < 0) + err(1, "vsnprintf"); + + if ((size_t)ret >= sizeof(e->errorline)) + (void)strlcpy(e->errorline + (sizeof(e->errorline) - 4), + "...", 4); +} + +void +envelope_set_esc_class(struct envelope *e, enum enhanced_status_class class) +{ + e->esc_class = class; +} + +void +envelope_set_esc_code(struct envelope *e, enum enhanced_status_code code) +{ + e->esc_code = code; +} + +static int +envelope_buffer_to_dict(struct dict *d, const char *ibuf, size_t buflen) +{ + static char lbuf[sizeof(struct envelope)]; + size_t len; + char *buf, *field, *nextline; + + memset(lbuf, 0, sizeof lbuf); + if (strlcpy(lbuf, ibuf, sizeof lbuf) >= sizeof lbuf) + goto err; + buf = lbuf; + + while (buflen > 0) { + len = strcspn(buf, "\n"); + buf[len] = '\0'; + nextline = buf + len + 1; + buflen -= (nextline - buf); + + field = buf; + while (*buf && (isalnum((unsigned char)*buf) || *buf == '-')) + buf++; + if (!*buf) + goto err; + + /* skip whitespaces before separator */ + while (*buf && isspace((unsigned char)*buf)) + *buf++ = 0; + + /* we *want* ':' */ + if (*buf != ':') + goto err; + *buf++ = 0; + + /* skip whitespaces after separator */ + while (*buf && isspace((unsigned char)*buf)) + *buf++ = 0; + dict_set(d, field, buf); + buf = nextline; + } + + return (1); + +err: + return (0); +} + +int +envelope_load_buffer(struct envelope *ep, const char *ibuf, size_t buflen) +{ + struct dict d; + const char *val, *errstr; + long long version; + int ret = 0; + + dict_init(&d); + if (!envelope_buffer_to_dict(&d, ibuf, buflen)) { + log_debug("debug: cannot parse envelope to dict"); + goto end; + } + + val = dict_get(&d, "version"); + if (val == NULL) { + log_debug("debug: envelope version not found"); + goto end; + } + version = strtonum(val, 1, 64, &errstr); + if (errstr) { + log_debug("debug: cannot parse envelope version: %s", val); + goto end; + } + + if (version != SMTPD_ENVELOPE_VERSION) { + log_debug("debug: bad envelope version %lld", version); + goto end; + } + + memset(ep, 0, sizeof *ep); + ret = envelope_ascii_load(ep, &d); + if (ret) + ep->version = SMTPD_ENVELOPE_VERSION; +end: + while (dict_poproot(&d, NULL)) + ; + return (ret); +} + +int +envelope_dump_buffer(const struct envelope *ep, char *dest, size_t len) +{ + char *p = dest; + + envelope_ascii_dump(ep, &dest, &len, "version"); + envelope_ascii_dump(ep, &dest, &len, "dispatcher"); + envelope_ascii_dump(ep, &dest, &len, "tag"); + envelope_ascii_dump(ep, &dest, &len, "type"); + envelope_ascii_dump(ep, &dest, &len, "smtpname"); + envelope_ascii_dump(ep, &dest, &len, "helo"); + envelope_ascii_dump(ep, &dest, &len, "hostname"); + envelope_ascii_dump(ep, &dest, &len, "username"); + envelope_ascii_dump(ep, &dest, &len, "errorline"); + envelope_ascii_dump(ep, &dest, &len, "sockaddr"); + envelope_ascii_dump(ep, &dest, &len, "sender"); + envelope_ascii_dump(ep, &dest, &len, "rcpt"); + envelope_ascii_dump(ep, &dest, &len, "dest"); + envelope_ascii_dump(ep, &dest, &len, "ctime"); + envelope_ascii_dump(ep, &dest, &len, "last-try"); + envelope_ascii_dump(ep, &dest, &len, "last-bounce"); + envelope_ascii_dump(ep, &dest, &len, "ttl"); + envelope_ascii_dump(ep, &dest, &len, "retry"); + envelope_ascii_dump(ep, &dest, &len, "flags"); + envelope_ascii_dump(ep, &dest, &len, "dsn-notify"); + envelope_ascii_dump(ep, &dest, &len, "dsn-ret"); + envelope_ascii_dump(ep, &dest, &len, "dsn-envid"); + envelope_ascii_dump(ep, &dest, &len, "dsn-orcpt"); + envelope_ascii_dump(ep, &dest, &len, "esc-class"); + envelope_ascii_dump(ep, &dest, &len, "esc-code"); + + switch (ep->type) { + case D_MDA: + envelope_ascii_dump(ep, &dest, &len, "mda-exec"); + envelope_ascii_dump(ep, &dest, &len, "mda-subaddress"); + envelope_ascii_dump(ep, &dest, &len, "mda-user"); + break; + case D_MTA: + break; + case D_BOUNCE: + envelope_ascii_dump(ep, &dest, &len, "bounce-ttl"); + envelope_ascii_dump(ep, &dest, &len, "bounce-delay"); + envelope_ascii_dump(ep, &dest, &len, "bounce-type"); + break; + default: + return (0); + } + + if (dest == NULL) + return (0); + + return (dest - p); +} + +static int +ascii_load_uint8(uint8_t *dest, char *buf) +{ + const char *errstr; + + *dest = strtonum(buf, 0, 0xff, &errstr); + if (errstr) + return 0; + return 1; +} + +static int +ascii_load_uint16(uint16_t *dest, char *buf) +{ + const char *errstr; + + *dest = strtonum(buf, 0, 0xffff, &errstr); + if (errstr) + return 0; + return 1; +} + +static int +ascii_load_uint32(uint32_t *dest, char *buf) +{ + const char *errstr; + + *dest = strtonum(buf, 0, 0xffffffff, &errstr); + if (errstr) + return 0; + return 1; +} + +static int +ascii_load_time(time_t *dest, char *buf) +{ + const char *errstr; + + *dest = strtonum(buf, 0, LLONG_MAX, &errstr); + if (errstr) + return 0; + return 1; +} + +static int +ascii_load_type(enum delivery_type *dest, char *buf) +{ + if (strcasecmp(buf, "mda") == 0) + *dest = D_MDA; + else if (strcasecmp(buf, "mta") == 0) + *dest = D_MTA; + else if (strcasecmp(buf, "bounce") == 0) + *dest = D_BOUNCE; + else + return 0; + return 1; +} + +static int +ascii_load_string(char *dest, char *buf, size_t len) +{ + if (strlcpy(dest, buf, len) >= len) + return 0; + return 1; +} + +static int +ascii_load_sockaddr(struct sockaddr_storage *ss, char *buf) +{ + struct sockaddr_in6 ssin6; + struct sockaddr_in ssin; + + memset(&ssin, 0, sizeof ssin); + memset(&ssin6, 0, sizeof ssin6); + + if (!strcmp("local", buf)) { + ss->ss_family = AF_LOCAL; + } + else if (strncasecmp("IPv6:", buf, 5) == 0) { + /* XXX - remove this after 6.6 release */ + if (inet_pton(AF_INET6, buf + 5, &ssin6.sin6_addr) != 1) + return 0; + ssin6.sin6_family = AF_INET6; + memcpy(ss, &ssin6, sizeof(ssin6)); +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN + ss->ss_len = sizeof(struct sockaddr_in6); +#endif + } + else if (buf[0] == '[' && buf[strlen(buf)-1] == ']') { + buf[strlen(buf)-1] = '\0'; + if (inet_pton(AF_INET6, buf+1, &ssin6.sin6_addr) != 1) + return 0; + ssin6.sin6_family = AF_INET6; + memcpy(ss, &ssin6, sizeof(ssin6)); +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN + ss->ss_len = sizeof(struct sockaddr_in6); +#endif + } + else { + if (inet_pton(AF_INET, buf, &ssin.sin_addr) != 1) + return 0; + ssin.sin_family = AF_INET; + memcpy(ss, &ssin, sizeof(ssin)); +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN + ss->ss_len = sizeof(struct sockaddr_in); +#endif + } + return 1; +} + +static int +ascii_load_mailaddr(struct mailaddr *dest, char *buf) +{ + if (!text_to_mailaddr(dest, buf)) + return 0; + return 1; +} + +static int +ascii_load_flags(enum envelope_flags *dest, char *buf) +{ + char *flag; + + while ((flag = strsep(&buf, " ,|")) != NULL) { + if (strcasecmp(flag, "authenticated") == 0) + *dest |= EF_AUTHENTICATED; + else if (strcasecmp(flag, "enqueued") == 0) + ; + else if (strcasecmp(flag, "bounce") == 0) + *dest |= EF_BOUNCE; + else if (strcasecmp(flag, "internal") == 0) + *dest |= EF_INTERNAL; + else + return 0; + } + return 1; +} + +static int +ascii_load_bounce_type(enum bounce_type *dest, char *buf) +{ + if (strcasecmp(buf, "error") == 0 || strcasecmp(buf, "failed") == 0) + *dest = B_FAILED; + else if (strcasecmp(buf, "warn") == 0 || + strcasecmp(buf, "delayed") == 0) + *dest = B_DELAYED; + else if (strcasecmp(buf, "dsn") == 0 || + strcasecmp(buf, "delivered") == 0) + *dest = B_DELIVERED; + else + return 0; + return 1; +} + +static int +ascii_load_dsn_ret(enum dsn_ret *ret, char *buf) +{ + if (strcasecmp(buf, "HDRS") == 0) + *ret = DSN_RETHDRS; + else if (strcasecmp(buf, "FULL") == 0) + *ret = DSN_RETFULL; + else + return 0; + return 1; +} + +static int +ascii_load_field(const char *field, struct envelope *ep, char *buf) +{ + if (strcasecmp("dispatcher", field) == 0) + return ascii_load_string(ep->dispatcher, buf, + sizeof ep->dispatcher); + + if (strcasecmp("bounce-delay", field) == 0) + return ascii_load_time(&ep->agent.bounce.delay, buf); + + if (strcasecmp("bounce-ttl", field) == 0) + return ascii_load_time(&ep->agent.bounce.ttl, buf); + + if (strcasecmp("bounce-type", field) == 0) + return ascii_load_bounce_type(&ep->agent.bounce.type, buf); + + if (strcasecmp("ctime", field) == 0) + return ascii_load_time(&ep->creation, buf); + + if (strcasecmp("dest", field) == 0) + return ascii_load_mailaddr(&ep->dest, buf); + + if (strcasecmp("username", field) == 0) + return ascii_load_string(ep->username, buf, sizeof(ep->username)); + + if (strcasecmp("errorline", field) == 0) + return ascii_load_string(ep->errorline, buf, + sizeof ep->errorline); + + if (strcasecmp("ttl", field) == 0) + return ascii_load_time(&ep->ttl, buf); + + if (strcasecmp("flags", field) == 0) + return ascii_load_flags(&ep->flags, buf); + + if (strcasecmp("helo", field) == 0) + return ascii_load_string(ep->helo, buf, sizeof ep->helo); + + if (strcasecmp("hostname", field) == 0) + return ascii_load_string(ep->hostname, buf, + sizeof ep->hostname); + + if (strcasecmp("last-bounce", field) == 0) + return ascii_load_time(&ep->lastbounce, buf); + + if (strcasecmp("last-try", field) == 0) + return ascii_load_time(&ep->lasttry, buf); + + if (strcasecmp("retry", field) == 0) + return ascii_load_uint16(&ep->retry, buf); + + if (strcasecmp("rcpt", field) == 0) + return ascii_load_mailaddr(&ep->rcpt, buf); + + if (strcasecmp("mda-exec", field) == 0) + return ascii_load_string(ep->mda_exec, buf, sizeof(ep->mda_exec)); + + if (strcasecmp("mda-subaddress", field) == 0) + return ascii_load_string(ep->mda_subaddress, buf, sizeof(ep->mda_subaddress)); + + if (strcasecmp("mda-user", field) == 0) + return ascii_load_string(ep->mda_user, buf, sizeof(ep->mda_user)); + + if (strcasecmp("sender", field) == 0) + return ascii_load_mailaddr(&ep->sender, buf); + + if (strcasecmp("smtpname", field) == 0) + return ascii_load_string(ep->smtpname, buf, + sizeof(ep->smtpname)); + + if (strcasecmp("sockaddr", field) == 0) + return ascii_load_sockaddr(&ep->ss, buf); + + if (strcasecmp("tag", field) == 0) + return ascii_load_string(ep->tag, buf, sizeof ep->tag); + + if (strcasecmp("type", field) == 0) + return ascii_load_type(&ep->type, buf); + + if (strcasecmp("version", field) == 0) + return ascii_load_uint32(&ep->version, buf); + + if (strcasecmp("dsn-notify", field) == 0) + return ascii_load_uint8(&ep->dsn_notify, buf); + + if (strcasecmp("dsn-orcpt", field) == 0) + return ascii_load_mailaddr(&ep->dsn_orcpt, buf); + + if (strcasecmp("dsn-ret", field) == 0) + return ascii_load_dsn_ret(&ep->dsn_ret, buf); + + if (strcasecmp("dsn-envid", field) == 0) + return ascii_load_string(ep->dsn_envid, buf, + sizeof(ep->dsn_envid)); + + if (strcasecmp("esc-class", field) == 0) + return ascii_load_uint8(&ep->esc_class, buf); + + if (strcasecmp("esc-code", field) == 0) + return ascii_load_uint8(&ep->esc_code, buf); + + return (0); +} + +static int +envelope_ascii_load(struct envelope *ep, struct dict *d) +{ + const char *field; + char *value; + void *hdl; + + hdl = NULL; + while (dict_iter(d, &hdl, &field, (void **)&value)) + if (!ascii_load_field(field, ep, value)) + goto err; + + return (1); + +err: + log_warnx("envelope: invalid field \"%s\"", field); + return (0); +} + + +static int +ascii_dump_uint8(uint8_t src, char *dest, size_t len) +{ + return bsnprintf(dest, len, "%d", src); +} + +static int +ascii_dump_uint16(uint16_t src, char *dest, size_t len) +{ + return bsnprintf(dest, len, "%d", src); +} + +static int +ascii_dump_uint32(uint32_t src, char *dest, size_t len) +{ + return bsnprintf(dest, len, "%d", src); +} + +static int +ascii_dump_time(time_t src, char *dest, size_t len) +{ + return bsnprintf(dest, len, "%lld", (long long) src); +} + +static int +ascii_dump_string(const char *src, char *dest, size_t len) +{ + return bsnprintf(dest, len, "%s", src); +} + +static int +ascii_dump_type(enum delivery_type type, char *dest, size_t len) +{ + char *p = NULL; + + switch (type) { + case D_MDA: + p = "mda"; + break; + case D_MTA: + p = "mta"; + break; + case D_BOUNCE: + p = "bounce"; + break; + default: + return 0; + } + + return bsnprintf(dest, len, "%s", p); +} + +static int +ascii_dump_mailaddr(const struct mailaddr *addr, char *dest, size_t len) +{ + return bsnprintf(dest, len, "%s@%s", + addr->user, addr->domain); +} + +static int +ascii_dump_flags(enum envelope_flags flags, char *buf, size_t len) +{ + size_t cpylen = 0; + + buf[0] = '\0'; + if (flags) { + if (flags & EF_AUTHENTICATED) + cpylen = strlcat(buf, "authenticated", len); + if (flags & EF_BOUNCE) { + if (buf[0] != '\0') + (void)strlcat(buf, " ", len); + cpylen = strlcat(buf, "bounce", len); + } + if (flags & EF_INTERNAL) { + if (buf[0] != '\0') + (void)strlcat(buf, " ", len); + cpylen = strlcat(buf, "internal", len); + } + } + + return cpylen < len ? 1 : 0; +} + +static int +ascii_dump_bounce_type(enum bounce_type type, char *dest, size_t len) +{ + char *p = NULL; + + switch (type) { + case B_FAILED: + p = "failed"; + break; + case B_DELAYED: + p = "delayed"; + break; + case B_DELIVERED: + p = "delivered"; + break; + default: + return 0; + } + return bsnprintf(dest, len, "%s", p); +} + + +static int +ascii_dump_dsn_ret(enum dsn_ret flag, char *dest, size_t len) +{ + size_t cpylen = 0; + + dest[0] = '\0'; + if (flag == DSN_RETFULL) + cpylen = strlcat(dest, "FULL", len); + else if (flag == DSN_RETHDRS) + cpylen = strlcat(dest, "HDRS", len); + + return cpylen < len ? 1 : 0; +} + +static int +ascii_dump_field(const char *field, const struct envelope *ep, + char *buf, size_t len) +{ + if (strcasecmp(field, "dispatcher") == 0) + return ascii_dump_string(ep->dispatcher, buf, len); + + if (strcasecmp(field, "bounce-delay") == 0) { + if (ep->agent.bounce.type != B_DELAYED) + return (1); + return ascii_dump_time(ep->agent.bounce.delay, buf, len); + } + + if (strcasecmp(field, "bounce-ttl") == 0) { + if (ep->agent.bounce.type != B_DELAYED) + return (1); + return ascii_dump_time(ep->agent.bounce.ttl, buf, len); + } + + if (strcasecmp(field, "bounce-type") == 0) + return ascii_dump_bounce_type(ep->agent.bounce.type, buf, len); + + if (strcasecmp(field, "ctime") == 0) + return ascii_dump_time(ep->creation, buf, len); + + if (strcasecmp(field, "dest") == 0) + return ascii_dump_mailaddr(&ep->dest, buf, len); + + if (strcasecmp(field, "username") == 0) { + if (ep->username[0]) + return ascii_dump_string(ep->username, buf, len); + return 1; + } + + if (strcasecmp(field, "errorline") == 0) + return ascii_dump_string(ep->errorline, buf, len); + + if (strcasecmp(field, "ttl") == 0) + return ascii_dump_time(ep->ttl, buf, len); + + if (strcasecmp(field, "flags") == 0) + return ascii_dump_flags(ep->flags, buf, len); + + if (strcasecmp(field, "helo") == 0) + return ascii_dump_string(ep->helo, buf, len); + + if (strcasecmp(field, "hostname") == 0) + return ascii_dump_string(ep->hostname, buf, len); + + if (strcasecmp(field, "last-bounce") == 0) + return ascii_dump_time(ep->lastbounce, buf, len); + + if (strcasecmp(field, "last-try") == 0) + return ascii_dump_time(ep->lasttry, buf, len); + + if (strcasecmp(field, "retry") == 0) + return ascii_dump_uint16(ep->retry, buf, len); + + if (strcasecmp(field, "rcpt") == 0) + return ascii_dump_mailaddr(&ep->rcpt, buf, len); + + if (strcasecmp(field, "mda-exec") == 0) { + if (ep->mda_exec[0]) + return ascii_dump_string(ep->mda_exec, buf, len); + return 1; + } + + if (strcasecmp(field, "mda-subaddress") == 0) { + if (ep->mda_subaddress[0]) + return ascii_dump_string(ep->mda_subaddress, buf, len); + return 1; + } + + if (strcasecmp(field, "mda-user") == 0) { + if (ep->mda_user[0]) + return ascii_dump_string(ep->mda_user, buf, len); + return 1; + } + + if (strcasecmp(field, "sender") == 0) + return ascii_dump_mailaddr(&ep->sender, buf, len); + + if (strcasecmp(field, "smtpname") == 0) + return ascii_dump_string(ep->smtpname, buf, len); + + if (strcasecmp(field, "sockaddr") == 0) + return ascii_dump_string(ss_to_text(&ep->ss), buf, len); + + if (strcasecmp(field, "tag") == 0) + return ascii_dump_string(ep->tag, buf, len); + + if (strcasecmp(field, "type") == 0) + return ascii_dump_type(ep->type, buf, len); + + if (strcasecmp(field, "version") == 0) + return ascii_dump_uint32(SMTPD_ENVELOPE_VERSION, buf, len); + + if (strcasecmp(field, "dsn-notify") == 0) + return ascii_dump_uint8(ep->dsn_notify, buf, len); + + if (strcasecmp(field, "dsn-ret") == 0) + return ascii_dump_dsn_ret(ep->dsn_ret, buf, len); + + if (strcasecmp(field, "dsn-orcpt") == 0) { + if (ep->dsn_orcpt.user[0] && ep->dsn_orcpt.domain[0]) + return ascii_dump_mailaddr(&ep->dsn_orcpt, buf, len); + return 1; + } + + if (strcasecmp(field, "dsn-envid") == 0) + return ascii_dump_string(ep->dsn_envid, buf, len); + + if (strcasecmp(field, "esc-class") == 0) { + if (ep->esc_class) + return ascii_dump_uint8(ep->esc_class, buf, len); + return 1; + } + + if (strcasecmp(field, "esc-code") == 0) { + /* this is not a pasto, we dump esc_code if esc_class is !0 */ + if (ep->esc_class) + return ascii_dump_uint8(ep->esc_code, buf, len); + return 1; + } + + return (0); +} + +static void +envelope_ascii_dump(const struct envelope *ep, char **dest, size_t *len, + const char *field) +{ + char buf[8192]; + int l; + + if (*dest == NULL) + return; + + memset(buf, 0, sizeof buf); + if (!ascii_dump_field(field, ep, buf, sizeof buf)) + goto err; + if (buf[0] == '\0') + return; + + l = snprintf(*dest, *len, "%s: %s\n", field, buf); + if (l < 0 || (size_t) l >= *len) + goto err; + *dest += l; + *len -= l; + + return; +err: + *dest = NULL; +} diff --git a/foobar/portable/smtpd/esc.c b/foobar/portable/smtpd/esc.c new file mode 100644 index 00000000..64a44c79 --- /dev/null +++ b/foobar/portable/smtpd/esc.c @@ -0,0 +1,116 @@ +/* $OpenBSD: esc.c,v 1.5 2016/09/03 22:16:39 gilles Exp $ */ + +/* + * Copyright (c) 2014 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <stdio.h> +#include <limits.h> + +#include "smtpd-defines.h" +#include "smtpd-api.h" + +static struct escode { + enum enhanced_status_code code; + const char *description; +} esc[] = { + /* 0.0 */ + { ESC_OTHER_STATUS, "Other/Undefined" }, + + /* 1.x */ + { ESC_OTHER_ADDRESS_STATUS, "Other/Undefined address status" }, + { ESC_BAD_DESTINATION_MAILBOX_ADDRESS, "Bad destination mailbox address" }, + { ESC_BAD_DESTINATION_SYSTEM_ADDRESS, "Bad destination system address" }, + { ESC_BAD_DESTINATION_MAILBOX_ADDRESS_SYNTAX, "Bad destination mailbox address syntax" }, + { ESC_DESTINATION_MAILBOX_ADDRESS_AMBIGUOUS, "Destination mailbox address ambiguous" }, + { ESC_DESTINATION_ADDRESS_VALID, "Destination address valid" }, + { ESC_DESTINATION_MAILBOX_HAS_MOVED, "Destination mailbox has moved, No forwarding address" }, + { ESC_BAD_SENDER_MAILBOX_ADDRESS_SYNTAX, "Bad sender's mailbox address syntax" }, + { ESC_BAD_SENDER_SYSTEM_ADDRESS, "Bad sender's system address syntax" }, + + /* 2.x */ + { ESC_OTHER_MAILBOX_STATUS, "Other/Undefined mailbox status" }, + { ESC_MAILBOX_DISABLED, "Mailbox disabled, not accepting messages" }, + { ESC_MAILBOX_FULL, "Mailbox full" }, + { ESC_MESSAGE_LENGTH_TOO_LARGE, "Message length exceeds administrative limit" }, + { ESC_MAILING_LIST_EXPANSION_PROBLEM, "Mailing list expansion problem" }, + + /* 3.x */ + { ESC_OTHER_MAIL_SYSTEM_STATUS, "Other/Undefined mail system status" }, + { ESC_MAIL_SYSTEM_FULL, "Mail system full" }, + { ESC_SYSTEM_NOT_ACCEPTING_MESSAGES, "System not accepting network messages" }, + { ESC_SYSTEM_NOT_CAPABLE_OF_SELECTED_FEATURES, "System not capable of selected features" }, + { ESC_MESSAGE_TOO_BIG_FOR_SYSTEM, "Message too big for system" }, + { ESC_SYSTEM_INCORRECTLY_CONFIGURED, "System incorrectly configured" }, + + /* 4.x */ + { ESC_OTHER_NETWORK_ROUTING_STATUS, "Other/Undefined network or routing status" }, + { ESC_NO_ANSWER_FROM_HOST, "No answer from host" }, + { ESC_BAD_CONNECTION, "Bad connection" }, + { ESC_DIRECTORY_SERVER_FAILURE, "Directory server failure" }, + { ESC_UNABLE_TO_ROUTE, "Unable to route" }, + { ESC_MAIL_SYSTEM_CONGESTION, "Mail system congestion" }, + { ESC_ROUTING_LOOP_DETECTED, "Routing loop detected" }, + { ESC_DELIVERY_TIME_EXPIRED, "Delivery time expired" }, + + /* 5.x */ + { ESC_INVALID_RECIPIENT, "Invalid recipient" }, + { ESC_INVALID_COMMAND, "Invalid command" }, + { ESC_SYNTAX_ERROR, "Syntax error" }, + { ESC_TOO_MANY_RECIPIENTS, "Too many recipients" }, + { ESC_INVALID_COMMAND_ARGUMENTS, "Invalid command arguments" }, + { ESC_WRONG_PROTOCOL_VERSION, "Wrong protocol version" }, + + /* 6.x */ + { ESC_OTHER_MEDIA_ERROR, "Other/Undefined media error" }, + { ESC_MEDIA_NOT_SUPPORTED, "Media not supported" }, + { ESC_CONVERSION_REQUIRED_AND_PROHIBITED, "Conversion required and prohibited" }, + { ESC_CONVERSION_REQUIRED_BUT_NOT_SUPPORTED, "Conversion required but not supported" }, + { ESC_CONVERSION_WITH_LOSS_PERFORMED, "Conversion with loss performed" }, + { ESC_CONVERSION_FAILED, "Conversion failed" }, + + /* 7.x */ + { ESC_OTHER_SECURITY_STATUS, "Other/Undefined security status" }, + { ESC_DELIVERY_NOT_AUTHORIZED_MESSAGE_REFUSED, "Delivery not authorized, message refused" }, + { ESC_MAILING_LIST_EXPANSION_PROHIBITED, "Mailing list expansion prohibited" }, + { ESC_SECURITY_CONVERSION_REQUIRED_NOT_POSSIBLE,"Security conversion required but not possible" }, + { ESC_SECURITY_FEATURES_NOT_SUPPORTED, "Security features not supported" }, + { ESC_CRYPTOGRAPHIC_FAILURE, "Cryptographic failure" }, + { ESC_CRYPTOGRAPHIC_ALGORITHM_NOT_SUPPORTED, "Cryptographic algorithm not supported" }, + { ESC_MESSAGE_TOO_BIG_FOR_SYSTEM, "Message integrity failure" }, +}; + +const char * +esc_code(enum enhanced_status_class class, enum enhanced_status_code code) +{ + static char buffer[6]; + + (void)snprintf(buffer, sizeof buffer, "%d.%d.%d", class, code / 10, code % 10); + return buffer; + +} + +const char * +esc_description(enum enhanced_status_code code) +{ + uint32_t i; + + for (i = 0; i < nitems(esc); ++i) + if (code == esc[i].code) + return esc[i].description; + return "Other/Undefined"; +} diff --git a/foobar/portable/smtpd/expand.c b/foobar/portable/smtpd/expand.c new file mode 100644 index 00000000..a4306fc0 --- /dev/null +++ b/foobar/portable/smtpd/expand.c @@ -0,0 +1,332 @@ +/* $OpenBSD: expand.c,v 1.31 2018/05/31 21:06:12 gilles Exp $ */ + +/* + * Copyright (c) 2009 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> + +#include <ctype.h> +#include <event.h> +#include <imsg.h> +#include <stdio.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_UTIL_H +#include <util.h> +#endif +#ifdef HAVE_LIBUTIL_H +#include <libutil.h> +#endif + +#include "smtpd.h" +#include "log.h" + +static const char *expandnode_info(struct expandnode *); + +struct expandnode * +expand_lookup(struct expand *expand, struct expandnode *key) +{ + return RB_FIND(expandtree, &expand->tree, key); +} + +int +expand_to_text(struct expand *expand, char *buf, size_t sz) +{ + struct expandnode *xn; + + buf[0] = '\0'; + + RB_FOREACH(xn, expandtree, &expand->tree) { + if (buf[0]) + (void)strlcat(buf, ", ", sz); + if (strlcat(buf, expandnode_to_text(xn), sz) >= sz) + return 0; + } + + return 1; +} + +void +expand_insert(struct expand *expand, struct expandnode *node) +{ + struct expandnode *xn; + + node->rule = expand->rule; + node->parent = expand->parent; + + log_trace(TRACE_EXPAND, "expand: %p: expand_insert() called for %s", + expand, expandnode_info(node)); + if (node->type == EXPAND_USERNAME && + expand->parent && + expand->parent->type == EXPAND_USERNAME && + !strcmp(expand->parent->u.user, node->u.user)) { + log_trace(TRACE_EXPAND, "expand: %p: setting sameuser = 1", + expand); + node->sameuser = 1; + } + + if (expand_lookup(expand, node)) { + log_trace(TRACE_EXPAND, "expand: %p: node found, discarding", + expand); + return; + } + + xn = xmemdup(node, sizeof *xn); + xn->rule = expand->rule; + xn->parent = expand->parent; + if (xn->parent) + xn->depth = xn->parent->depth + 1; + else + xn->depth = 0; + RB_INSERT(expandtree, &expand->tree, xn); + if (expand->queue) + TAILQ_INSERT_TAIL(expand->queue, xn, tq_entry); + expand->nb_nodes++; + log_trace(TRACE_EXPAND, "expand: %p: inserted node %p", expand, xn); +} + +void +expand_clear(struct expand *expand) +{ + struct expandnode *xn; + + log_trace(TRACE_EXPAND, "expand: %p: clearing expand tree", expand); + if (expand->queue) + while ((xn = TAILQ_FIRST(expand->queue))) + TAILQ_REMOVE(expand->queue, xn, tq_entry); + + while ((xn = RB_ROOT(&expand->tree)) != NULL) { + RB_REMOVE(expandtree, &expand->tree, xn); + free(xn); + } +} + +void +expand_free(struct expand *expand) +{ + expand_clear(expand); + + log_trace(TRACE_EXPAND, "expand: %p: freeing expand tree", expand); + free(expand); +} + +int +expand_cmp(struct expandnode *e1, struct expandnode *e2) +{ + struct expandnode *p1, *p2; + int r; + + if (e1->type < e2->type) + return -1; + if (e1->type > e2->type) + return 1; + if (e1->sameuser < e2->sameuser) + return -1; + if (e1->sameuser > e2->sameuser) + return 1; + if (e1->realuser < e2->realuser) + return -1; + if (e1->realuser > e2->realuser) + return 1; + + r = memcmp(&e1->u, &e2->u, sizeof(e1->u)); + if (r) + return (r); + + if (e1->parent == e2->parent) + return (0); + + if (e1->parent == NULL) + return (-1); + if (e2->parent == NULL) + return (1); + + /* + * The same node can be expanded in for different dest context. + * Wen need to distinguish between those. + */ + for(p1 = e1->parent; p1->type != EXPAND_ADDRESS; p1 = p1->parent) + ; + for(p2 = e2->parent; p2->type != EXPAND_ADDRESS; p2 = p2->parent) + ; + if (p1 < p2) + return (-1); + if (p1 > p2) + return (1); + + if (e1->type != EXPAND_FILENAME && e1->type != EXPAND_FILTER) + return (0); + + /* + * For external delivery, we need to distinguish between users. + * If we can't find a username, we assume it is _smtpd. + */ + for(p1 = e1->parent; p1 && p1->type != EXPAND_USERNAME; p1 = p1->parent) + ; + for(p2 = e2->parent; p2 && p2->type != EXPAND_USERNAME; p2 = p2->parent) + ; + if (p1 < p2) + return (-1); + if (p1 > p2) + return (1); + + return (0); +} + +static int +expand_line_split(char **line, char **ret) +{ + static char buffer[LINE_MAX]; + int esc, dq, sq; + size_t i; + char *s; + + memset(buffer, 0, sizeof buffer); + esc = dq = sq = 0; + i = 0; + for (s = *line; (*s) && (i < sizeof(buffer)); ++s) { + if (esc) { + buffer[i++] = *s; + esc = 0; + continue; + } + if (*s == '\\') { + esc = 1; + continue; + } + if (*s == ',' && !dq && !sq) { + *ret = buffer; + *line = s+1; + return (1); + } + + buffer[i++] = *s; + esc = 0; + + if (*s == '"' && !sq) + dq ^= 1; + if (*s == '\'' && !dq) + sq ^= 1; + } + + if (esc || dq || sq || i == sizeof(buffer)) + return (-1); + + *ret = buffer; + *line = s; + return (i ? 1 : 0); +} + +int +expand_line(struct expand *expand, const char *s, int do_includes) +{ + struct expandnode xn; + char buffer[LINE_MAX]; + char *p, *subrcpt; + int ret; + + memset(buffer, 0, sizeof buffer); + if (strlcpy(buffer, s, sizeof buffer) >= sizeof buffer) + return 0; + + p = buffer; + while ((ret = expand_line_split(&p, &subrcpt)) > 0) { + subrcpt = strip(subrcpt); + if (subrcpt[0] == '\0') + continue; + if (!text_to_expandnode(&xn, subrcpt)) + return 0; + if (!do_includes) + if (xn.type == EXPAND_INCLUDE) + continue; + expand_insert(expand, &xn); + } + + if (ret >= 0) + return 1; + + /* expand_line_split() returned < 0 */ + return 0; +} + +static const char * +expandnode_info(struct expandnode *e) +{ + static char buffer[1024]; + const char *type = NULL; + const char *value = NULL; + char tmp[64]; + + switch (e->type) { + case EXPAND_FILTER: + type = "filter"; + break; + case EXPAND_FILENAME: + type = "filename"; + break; + case EXPAND_INCLUDE: + type = "include"; + break; + case EXPAND_USERNAME: + type = "username"; + break; + case EXPAND_ADDRESS: + type = "address"; + break; + case EXPAND_ERROR: + type = "error"; + break; + case EXPAND_INVALID: + default: + return NULL; + } + + if ((value = expandnode_to_text(e)) == NULL) + return NULL; + + (void)strlcpy(buffer, type, sizeof buffer); + (void)strlcat(buffer, ":", sizeof buffer); + if (strlcat(buffer, value, sizeof buffer) >= sizeof buffer) + return NULL; + + (void)snprintf(tmp, sizeof(tmp), "[parent=%p", e->parent); + if (strlcat(buffer, tmp, sizeof buffer) >= sizeof buffer) + return NULL; + + (void)snprintf(tmp, sizeof(tmp), ", rule=%p", e->rule); + if (strlcat(buffer, tmp, sizeof buffer) >= sizeof buffer) + return NULL; + + if (e->rule) { + (void)snprintf(tmp, sizeof(tmp), ", dispatcher=%p", e->rule->dispatcher); + if (strlcat(buffer, tmp, sizeof buffer) >= sizeof buffer) + return NULL; + } + + if (strlcat(buffer, "]", sizeof buffer) >= sizeof buffer) + return NULL; + + return buffer; +} + +RB_GENERATE(expandtree, expandnode, entry, expand_cmp); diff --git a/foobar/portable/smtpd/filter.c b/foobar/portable/smtpd/filter.c new file mode 100644 index 00000000..614486b7 --- /dev/null +++ b/foobar/portable/smtpd/filter.c @@ -0,0 +1,868 @@ +/* $OpenBSD: filter.c,v 1.25 2017/01/09 09:53:23 reyk Exp $ */ + +/* + * Copyright (c) 2011 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/wait.h> + +#include <netinet/in.h> + +#include <ctype.h> +#include <errno.h> +#include <event.h> +#include <imsg.h> +#include <inttypes.h> +#include <limits.h> +#include <resolv.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "smtpd.h" +#include "log.h" + +enum { + QUERY_READY, + QUERY_RUNNING, + QUERY_DONE +}; + + +struct filter_proc { + TAILQ_ENTRY(filter_proc) entry; + struct mproc mproc; + int hooks; + int flags; + int ready; +}; + +struct filter { + TAILQ_ENTRY(filter) entry; + struct filter_proc *proc; +}; +TAILQ_HEAD(filter_lst, filter); + +TAILQ_HEAD(filter_query_lst, filter_query); +struct filter_session { + uint64_t id; + int terminate; + struct filter_lst *filters; + struct filter *fcurr; + + int error; + struct io *iev; + size_t idatalen; + FILE *ofile; + + struct filter_query *eom; +}; + +struct filter_query { + uint64_t qid; + int type; + struct filter_session *session; + + int state; + struct filter *current; + + /* current data */ + union { + struct { + struct sockaddr_storage local; + struct sockaddr_storage remote; + char hostname[HOST_NAME_MAX+1]; + } connect; + char line[LINE_MAX]; + struct mailaddr maddr; + size_t datalen; + } u; + + /* current response */ + struct { + int status; + int code; + char *response; + } smtp; +}; + +static void filter_imsg(struct mproc *, struct imsg *); +static void filter_post_event(uint64_t, int, struct filter *, struct filter *); +static struct filter_query *filter_query(struct filter_session *, int); +static void filter_drain_query(struct filter_query *); +static void filter_run_query(struct filter *, struct filter_query *); +static void filter_end_query(struct filter_query *); +static void filter_set_sink(struct filter_session *, int); +static int filter_tx(struct filter_session *, int); +static void filter_tx_io(struct io *, int, void *); + +static TAILQ_HEAD(, filter_proc) procs; +struct dict chains; + +static const char * filter_session_to_text(struct filter_session *); +static const char * filter_query_to_text(struct filter_query *); +static const char * filter_to_text(struct filter *); +static const char * filter_proc_to_text(struct filter_proc *); +static const char * query_to_str(int); +static const char * event_to_str(int); +static const char * status_to_str(int); +static const char * filterimsg_to_str(int); + +struct tree sessions; +struct tree queries; + +static void +filter_add_arg(struct filter_conf *filter, char *arg) +{ + if (filter->argc == MAX_FILTER_ARGS) { + log_warnx("warn: filter \"%s\" is full", filter->name); + fatalx("exiting"); + } + filter->argv[filter->argc++] = arg; +} + +static void +filter_extend_chain(struct filter_lst *chain, const char *name) +{ + struct filter *n; + struct filter_lst *fchain; + struct filter_conf *fconf; + int i; + + fconf = dict_xget(&env->sc_filters, name); + if (fconf->chain) { + log_debug("filter: extending with \"%s\"", name); + for (i = 0; i < fconf->argc; i++) + filter_extend_chain(chain, fconf->argv[i]); + } + else { + log_debug("filter: adding filter \"%s\"", name); + n = xcalloc(1, sizeof(*n), "filter_extend_chain"); + fchain = dict_get(&chains, name); + n->proc = TAILQ_FIRST(fchain)->proc; + TAILQ_INSERT_TAIL(chain, n, entry); + } +} + +void +filter_postfork(void) +{ + static int prepare = 0; + struct filter_conf *filter; + void *iter; + struct filter_proc *proc; + struct filter_lst *fchain; + struct filter *f; + struct mproc *p; + int done, i; + + if (prepare) + return; + prepare = 1; + + TAILQ_INIT(&procs); + dict_init(&chains); + + log_debug("filter: building simple chains..."); + + /* create all filter proc and associated chains */ + iter = NULL; + while (dict_iter(&env->sc_filters, &iter, NULL, (void **)&filter)) { + if (filter->chain) + continue; + + log_debug("filter: building simple chain \"%s\"", filter->name); + proc = xcalloc(1, sizeof(*proc), "filter_postfork"); + p = &proc->mproc; + p->handler = filter_imsg; + p->proc = PROC_FILTER; + p->name = xstrdup(filter->name, "filter_postfork"); + p->data = proc; + if (tracing & TRACE_DEBUG) + filter_add_arg(filter, "-v"); + if (foreground_log) + filter_add_arg(filter, "-d"); + if (mproc_fork(p, filter->path, filter->argv) < 0) + fatalx("filter_postfork"); + + log_debug("filter: registering proc \"%s\"", filter->name); + f = xcalloc(1, sizeof(*f), "filter_postfork"); + f->proc = proc; + + TAILQ_INSERT_TAIL(&procs, proc, entry); + fchain = xcalloc(1, sizeof(*fchain), "filter_postfork"); + TAILQ_INIT(fchain); + TAILQ_INSERT_TAIL(fchain, f, entry); + dict_xset(&chains, filter->name, fchain); + filter->done = 1; + } + + log_debug("filter: building complex chains..."); + + /* resolve all chains */ + done = 0; + while (!done) { + done = 1; + iter = NULL; + while (dict_iter(&env->sc_filters, &iter, NULL, + (void **)&filter)) { + if (filter->done) + continue; + done = 0; + filter->done = 1; + for (i = 0; i < filter->argc; i++) { + if (!dict_get(&chains, filter->argv[i])) { + filter->done = 0; + break; + } + } + if (filter->done == 0) + continue; + fchain = xcalloc(1, sizeof(*fchain), "filter_postfork"); + TAILQ_INIT(fchain); + log_debug("filter: building chain \"%s\"...", + filter->name); + for (i = 0; i < filter->argc; i++) + filter_extend_chain(fchain, filter->argv[i]); + log_debug("filter: done building chain \"%s\"", + filter->name); + dict_xset(&chains, filter->name, fchain); + } + } + log_debug("filter: done building complex chains"); + + fchain = xcalloc(1, sizeof(*fchain), "filter_postfork"); + TAILQ_INIT(fchain); + dict_xset(&chains, "<no-filter>", fchain); +} + +void +filter_configure(void) +{ + static int init = 0; + struct filter_proc *p; + + if (init) + return; + init = 1; + + tree_init(&sessions); + tree_init(&queries); + + TAILQ_FOREACH(p, &procs, entry) { + m_create(&p->mproc, IMSG_FILTER_REGISTER, 0, 0, -1); + m_add_u32(&p->mproc, FILTER_API_VERSION); + m_add_string(&p->mproc, p->mproc.name); + m_close(&p->mproc); + mproc_enable(&p->mproc); + } + + if (TAILQ_FIRST(&procs) == NULL) + smtp_configure(); +} + +void +filter_event(uint64_t id, int event) +{ + struct filter_session *s; + + if (event == EVENT_DISCONNECT) + /* On disconnect, the session is virtualy dead */ + s = tree_xpop(&sessions, id); + else + s = tree_xget(&sessions, id); + + filter_post_event(id, event, TAILQ_FIRST(s->filters), NULL); + + if (event == EVENT_DISCONNECT) { + if (s->iev) + io_free(s->iev); + if (s->ofile) + fclose(s->ofile); + free(s); + } +} + +void +filter_connect(uint64_t id, const struct sockaddr *local, + const struct sockaddr *remote, const char *host, const char *filter) +{ + struct filter_session *s; + struct filter_query *q; + + s = xcalloc(1, sizeof(*s), "filter_event"); + s->id = id; + if (filter == NULL) + filter = "<no-filter>"; + s->filters = dict_xget(&chains, filter); + tree_xset(&sessions, s->id, s); + + filter_event(id, EVENT_CONNECT); + q = filter_query(s, QUERY_CONNECT); + + memmove(&q->u.connect.local, local, SA_LEN(local)); + memmove(&q->u.connect.remote, remote, SA_LEN(remote)); + strlcpy(q->u.connect.hostname, host, sizeof(q->u.connect.hostname)); + + q->smtp.status = FILTER_OK; + q->smtp.code = 0; + q->smtp.response = NULL; + + filter_drain_query(q); +} + +void +filter_mailaddr(uint64_t id, int type, const struct mailaddr *maddr) +{ + struct filter_session *s; + struct filter_query *q; + + s = tree_xget(&sessions, id); + q = filter_query(s, type); + + strlcpy(q->u.maddr.user, maddr->user, sizeof(q->u.maddr.user)); + strlcpy(q->u.maddr.domain, maddr->domain, sizeof(q->u.maddr.domain)); + + filter_drain_query(q); +} + +void +filter_line(uint64_t id, int type, const char *line) +{ + struct filter_session *s; + struct filter_query *q; + + s = tree_xget(&sessions, id); + q = filter_query(s, type); + + if (line) + strlcpy(q->u.line, line, sizeof(q->u.line)); + + filter_drain_query(q); +} + +void +filter_eom(uint64_t id, int type, size_t datalen) +{ + struct filter_session *s; + struct filter_query *q; + + s = tree_xget(&sessions, id); + q = filter_query(s, type); + q->u.datalen = datalen; + + filter_drain_query(q); +} + +static void +filter_set_sink(struct filter_session *s, int sink) +{ + struct mproc *p; + + while (s->fcurr) { + if (s->fcurr->proc->hooks & HOOK_DATALINE) { + log_trace(TRACE_FILTERS, "filter: sending fd %d to %s", + sink, filter_to_text(s->fcurr)); + p = &s->fcurr->proc->mproc; + m_create(p, IMSG_FILTER_PIPE, 0, 0, sink); + m_add_id(p, s->id); + m_close(p); + return; + } + s->fcurr = TAILQ_PREV(s->fcurr, filter_lst, entry); + } + + log_trace(TRACE_FILTERS, "filter: chain input is %d", sink); + smtp_filter_fd(s->id, sink); +} + +void +filter_build_fd_chain(uint64_t id, int sink) +{ + struct filter_session *s; + int fd; + + s = tree_xget(&sessions, id); + s->fcurr = TAILQ_LAST(s->filters, filter_lst); + + fd = filter_tx(s, sink); + filter_set_sink(s, fd); +} + +void +filter_post_event(uint64_t id, int event, struct filter *f, struct filter *end) +{ + for(; f && f != end; f = TAILQ_NEXT(f, entry)) { + log_trace(TRACE_FILTERS, "filter: post-event event=%s filter=%s", + event_to_str(event), f->proc->mproc.name); + + m_create(&f->proc->mproc, IMSG_FILTER_EVENT, 0, 0, -1); + m_add_id(&f->proc->mproc, id); + m_add_int(&f->proc->mproc, event); + m_close(&f->proc->mproc); + } +} + +static struct filter_query * +filter_query(struct filter_session *s, int type) +{ + struct filter_query *q; + + q = xcalloc(1, sizeof(*q), "filter_query"); + q->qid = generate_uid(); + q->session = s; + q->type = type; + + q->state = QUERY_READY; + q->current = TAILQ_FIRST(s->filters); + + log_trace(TRACE_FILTERS, "filter: new query %s", query_to_str(type)); + + return (q); +} + +static void +filter_drain_query(struct filter_query *q) +{ + log_trace(TRACE_FILTERS, "filter: filter_drain_query %s", + filter_query_to_text(q)); + + /* + * The query must be passed through all filters that registered + * a hook, until one rejects it. + */ + while (q->state != QUERY_DONE) { + /* Walk over all filters */ + while (q->current) { + filter_run_query(q->current, q); + if (q->state == QUERY_RUNNING) { + log_trace(TRACE_FILTERS, + "filter: waiting for running query %s", + filter_query_to_text(q)); + return; + } + } + q->state = QUERY_DONE; + } + + /* Defer the response if the file is not closed yet. */ + if (q->type == QUERY_EOM && q->session->ofile && q->smtp.status == FILTER_OK) { + log_debug("filter: deferring eom query..."); + q->session->eom = q; + return; + } + + filter_end_query(q); +} + +static void +filter_run_query(struct filter *f, struct filter_query *q) +{ + log_trace(TRACE_FILTERS, + "filter: running filter %s for query %s", + filter_to_text(f), filter_query_to_text(q)); + + m_create(&f->proc->mproc, IMSG_FILTER_QUERY, 0, 0, -1); + m_add_id(&f->proc->mproc, q->session->id); + m_add_id(&f->proc->mproc, q->qid); + m_add_int(&f->proc->mproc, q->type); + + switch (q->type) { + case QUERY_CONNECT: + m_add_sockaddr(&f->proc->mproc, + (struct sockaddr *)&q->u.connect.local); + m_add_sockaddr(&f->proc->mproc, + (struct sockaddr *)&q->u.connect.remote); + m_add_string(&f->proc->mproc, q->u.connect.hostname); + break; + case QUERY_HELO: + m_add_string(&f->proc->mproc, q->u.line); + break; + case QUERY_MAIL: + case QUERY_RCPT: + m_add_mailaddr(&f->proc->mproc, &q->u.maddr); + break; + case QUERY_EOM: + m_add_u32(&f->proc->mproc, q->u.datalen); + break; + default: + break; + } + m_close(&f->proc->mproc); + + tree_xset(&queries, q->qid, q); + q->state = QUERY_RUNNING; +} + +static void +filter_end_query(struct filter_query *q) +{ + struct filter_session *s = q->session; + const char *response = q->smtp.response; + + log_trace(TRACE_FILTERS, "filter: filter_end_query %s", + filter_query_to_text(q)); + + if (q->type == QUERY_EOM && q->smtp.status == FILTER_OK) { + if (s->error || q->u.datalen != s->idatalen) { + response = "Internal error"; + q->smtp.code = 451; + q->smtp.status = FILTER_FAIL; + if (!s->error) + log_warnx("filter: datalen mismatch on session %" PRIx64 + ": %zu/%zu", s->id, s->idatalen, q->u.datalen); + } + } + + log_trace(TRACE_FILTERS, + "filter: query %016"PRIx64" done: " + "status=%s code=%d response=\"%s\"", + q->qid, + status_to_str(q->smtp.status), + q->smtp.code, + response); + + smtp_filter_response(s->id, q->type, q->smtp.status, q->smtp.code, + response); + free(q->smtp.response); + free(q); +} + +static void +filter_imsg(struct mproc *p, struct imsg *imsg) +{ + struct filter_proc *proc = p->data; + struct filter_session *s; + struct filter_query *q; + struct msg m; + const char *line; + uint64_t qid; + uint32_t datalen; + int type, status, code; + + if (imsg == NULL) { + log_warnx("warn: filter \"%s\" closed unexpectedly", p->name); + fatalx("exiting"); + } + + log_trace(TRACE_FILTERS, "filter: imsg %s from procfilter %s", + filterimsg_to_str(imsg->hdr.type), + filter_proc_to_text(proc)); + + switch (imsg->hdr.type) { + + case IMSG_FILTER_REGISTER: + if (proc->ready) { + log_warnx("warn: filter \"%s\" already registered", + proc->mproc.name); + exit(1); + } + + m_msg(&m, imsg); + m_get_int(&m, &proc->hooks); + m_get_int(&m, &proc->flags); + m_end(&m); + proc->ready = 1; + + log_debug("debug: filter \"%s\": hooks 0x%08x flags 0x%04x", + proc->mproc.name, proc->hooks, proc->flags); + + TAILQ_FOREACH(proc, &procs, entry) + if (!proc->ready) + return; + + smtp_configure(); + break; + + case IMSG_FILTER_RESPONSE: + m_msg(&m, imsg); + m_get_id(&m, &qid); + m_get_int(&m, &type); + if (type == QUERY_EOM) + m_get_u32(&m, &datalen); + m_get_int(&m, &status); + m_get_int(&m, &code); + if (m_is_eom(&m)) + line = NULL; + else + m_get_string(&m, &line); + m_end(&m); + + q = tree_xpop(&queries, qid); + if (q->type != type) { + log_warnx("warn: filter: type mismatch %d != %d", + q->type, type); + fatalx("exiting"); + } + q->smtp.status = status; + if (code) + q->smtp.code = code; + if (line) { + free(q->smtp.response); + q->smtp.response = xstrdup(line, "filter_imsg"); + } + q->state = (status == FILTER_OK) ? QUERY_READY : QUERY_DONE; + if (type == QUERY_EOM) + q->u.datalen = datalen; + + q->current = TAILQ_NEXT(q->current, entry); + filter_drain_query(q); + break; + + case IMSG_FILTER_PIPE: + m_msg(&m, imsg); + m_get_id(&m, &qid); + m_end(&m); + + s = tree_xget(&sessions, qid); + s->fcurr = TAILQ_PREV(s->fcurr, filter_lst, entry); + filter_set_sink(s, imsg->fd); + break; + + default: + log_warnx("warn: bad imsg from filter %s", p->name); + exit(1); + } +} + +static int +filter_tx(struct filter_session *s, int sink) +{ + int sp[2]; + + s->idatalen = 0; + s->eom = NULL; + s->error = 0; + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1) { + log_warn("warn: filter: socketpair"); + return (-1); + } + + if ((s->ofile = fdopen(sink, "w")) == NULL) { + log_warn("warn: filter: fdopen"); + close(sp[0]); + close(sp[1]); + return (-1); + } + + io_set_nonblocking(sp[0]); + io_set_nonblocking(sp[1]); + + s->iev = io_new(); + io_set_callback(s->iev, filter_tx_io, s); + io_set_fd(s->iev, sp[0]); + io_set_read(s->iev); + + return (sp[1]); +} + +static void +filter_tx_io(struct io *io, int evt, void *arg) +{ + struct filter_session *s = arg; + size_t len, n; + char *data; + + log_trace(TRACE_FILTERS, "filter: filter_tx_io(%p, %s)", + s, io_strevent(evt)); + + switch (evt) { + case IO_DATAIN: + data = io_data(s->iev); + len = io_datalen(s->iev); + + log_trace(TRACE_FILTERS, + "filter: filter_tx_io: datain (%zu) for req %016"PRIx64"", + len, s->id); + + n = fwrite(data, 1, len, s->ofile); + if (n != len) { + log_warnx("warn: filter_tx_io: fwrite %zu/%zu", n, len); + s->error = 1; + break; + } + s->idatalen += n; + io_drop(s->iev, n); + return; + + case IO_DISCONNECTED: + log_trace(TRACE_FILTERS, + "debug: filter: tx done (%zu) for req %016"PRIx64, + s->idatalen, s->id); + break; + + default: + log_warn("warn: filter_tx_io: bad evt (%d) for req %016"PRIx64, + evt, s->id); + s->error = 1; + break; + } + + io_free(s->iev); + s->iev = NULL; + fclose(s->ofile); + s->ofile = NULL; + + /* deferred eom request */ + if (s->eom) { + log_debug("filter: running eom query..."); + filter_end_query(s->eom); + } else { + log_debug("filter: eom not received yet"); + } +} + +static const char * +filter_query_to_text(struct filter_query *q) +{ + static char buf[1024]; + char tmp[1024]; + + tmp[0] = '\0'; + + switch (q->type) { + case QUERY_CONNECT: + strlcat(tmp, "=", sizeof tmp); + strlcat(tmp, ss_to_text(&q->u.connect.local), + sizeof tmp); + strlcat(tmp, " <-> ", sizeof tmp); + strlcat(tmp, ss_to_text(&q->u.connect.remote), + sizeof tmp); + strlcat(tmp, "(", sizeof tmp); + strlcat(tmp, q->u.connect.hostname, sizeof tmp); + strlcat(tmp, ")", sizeof tmp); + break; + case QUERY_MAIL: + case QUERY_RCPT: + snprintf(tmp, sizeof tmp, "=%s@%s", + q->u.maddr.user, q->u.maddr.domain); + break; + case QUERY_HELO: + snprintf(tmp, sizeof tmp, "=%s", q->u.line); + break; + default: + break; + } + snprintf(buf, sizeof buf, "%016"PRIx64"[%s%s,%s]", + q->qid, query_to_str(q->type), tmp, + filter_session_to_text(q->session)); + + return (buf); +} + +static const char * +filter_session_to_text(struct filter_session *s) +{ + static char buf[1024]; + + if (s == NULL) + return "filter_session@NULL"; + + snprintf(buf, sizeof(buf), + "filter_session@%p[datalen=%zu,eom=%p,ofile=%p]", + s, s->idatalen, s->eom, s->ofile); + + return buf; +} + +static const char * +filter_to_text(struct filter *f) +{ + static char buf[1024]; + + snprintf(buf, sizeof buf, "filter:%s", filter_proc_to_text(f->proc)); + + return (buf); +} + +static const char * +filter_proc_to_text(struct filter_proc *proc) +{ + static char buf[1024]; + + snprintf(buf, sizeof buf, "%s[hooks=0x%08x,flags=0x%04x]", + proc->mproc.name, proc->hooks, proc->flags); + + return (buf); +} + +#define CASE(x) case x : return #x + +static const char * +filterimsg_to_str(int imsg) +{ + switch (imsg) { + CASE(IMSG_FILTER_REGISTER); + CASE(IMSG_FILTER_EVENT); + CASE(IMSG_FILTER_QUERY); + CASE(IMSG_FILTER_PIPE); + CASE(IMSG_FILTER_RESPONSE); + default: + return "IMSG_FILTER_???"; + } +} + +static const char * +query_to_str(int query) +{ + switch (query) { + CASE(QUERY_CONNECT); + CASE(QUERY_HELO); + CASE(QUERY_MAIL); + CASE(QUERY_RCPT); + CASE(QUERY_DATA); + CASE(QUERY_EOM); + CASE(QUERY_DATALINE); + default: + return "QUERY_???"; + } +} + +static const char * +event_to_str(int event) +{ + switch (event) { + CASE(EVENT_CONNECT); + CASE(EVENT_RESET); + CASE(EVENT_DISCONNECT); + CASE(EVENT_TX_BEGIN); + CASE(EVENT_TX_COMMIT); + CASE(EVENT_TX_ROLLBACK); + default: + return "EVENT_???"; + } +} + +static const char * +status_to_str(int status) +{ + switch (status) { + CASE(FILTER_OK); + CASE(FILTER_FAIL); + CASE(FILTER_CLOSE); + default: + return "FILTER_???"; + } +} diff --git a/foobar/portable/smtpd/forward.5 b/foobar/portable/smtpd/forward.5 new file mode 100644 index 00000000..5a68f229 --- /dev/null +++ b/foobar/portable/smtpd/forward.5 @@ -0,0 +1,83 @@ +.\" $OpenBSD: forward.5,v 1.9 2015/03/13 22:41:54 eric Exp $ +.\" +.\" Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: March 13 2015 $ +.Dt FORWARD 5 +.Os +.Sh NAME +.Nm forward +.Nd email forwarding information file +.Sh DESCRIPTION +Users may put a +.Nm .forward +file in their home directory. +If this file exists, +.Xr smtpd 8 +forwards email to the destinations specified therein. +.Pp +A +.Nm .forward +file contains a list of expansion values, as described in +.Xr aliases 5 . +Each expansion value should be on a line by itself. +However, the +.Nm .forward +mechanism differs from the aliases mechanism in that it disallows +file inclusion +.Pq :include: +and it performs expansion under the user ID of the +.Nm .forward +file owner. +.Pp +Permissions on the +.Nm .forward +file are very strict and expansion is rejected if the file is +group or world-writable; +if the home directory is group writeable; +or if the file is not owned by the user. +.Pp +Users should avoid editing directly the +.Nm .forward +file to prevent delivery failures from occurring if a message +arrives while the file is not fully written. +The best option is to use a temporary file and use the +.Xr mv 1 +command to atomically overwrite the former +.Nm .forward . +Alternatively, setting the +.Xr sticky 8 +bit on the home directory will cause the +.Nm .forward +lookup to return a temporary failure, causing mails to be deferred. +.Sh FILES +.Bl -tag -width "~/.forwardXXX" -compact +.It Pa ~/.forward +Email forwarding information. +.El +.Sh EXAMPLES +The following file forwards mail to +.Dq user@example.com , +and pipes the same mail to +.Dq examplemda . +.Bd -literal -offset indent +# empty lines are ignored + +user@example.com # anything after # is ignored +"|/path/to/examplemda" +.Ed +.Sh SEE ALSO +.Xr aliases 5 , +.Xr smtpd 8 diff --git a/foobar/portable/smtpd/forward.c b/foobar/portable/smtpd/forward.c new file mode 100644 index 00000000..7494c6ce --- /dev/null +++ b/foobar/portable/smtpd/forward.c @@ -0,0 +1,104 @@ +/* $OpenBSD: forward.c,v 1.39 2015/12/28 22:08:30 jung Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <event.h> +#include <imsg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_UTIL_H +#include <util.h> +#endif +#ifdef HAVE_LIBUTIL_H +#include <libutil.h> +#endif +#include <unistd.h> +#include <limits.h> + +#include "smtpd.h" +#include "log.h" + +#define MAX_FORWARD_SIZE (4 * 1024) +#define MAX_EXPAND_NODES (100) + +int +forwards_get(int fd, struct expand *expand) +{ + FILE *fp = NULL; + char *line = NULL; + size_t len; + size_t lineno; + size_t save; + int ret; + struct stat sb; + + ret = -1; + if (fstat(fd, &sb) == -1) + goto end; + + /* if it's empty just pretend that no expansion took place */ + if (sb.st_size == 0) { + log_info("info: forward file is empty"); + ret = 0; + goto end; + } + + /* over MAX_FORWARD_SIZE, temporarily fail */ + if (sb.st_size >= MAX_FORWARD_SIZE) { + log_info("info: forward file exceeds max size"); + goto end; + } + + if ((fp = fdopen(fd, "r")) == NULL) { + log_warn("warn: fdopen failure in forwards_get()"); + goto end; + } + + lineno = 0; + save = expand->nb_nodes; + while ((line = fparseln(fp, &len, &lineno, NULL, 0)) != NULL) { + if (!expand_line(expand, line, 0)) { + log_info("info: parse error in forward file"); + goto end; + } + if (expand->nb_nodes > MAX_EXPAND_NODES) { + log_info("info: forward file expanded too many nodes"); + goto end; + } + free(line); + } + + ret = expand->nb_nodes > save ? 1 : 0; + +end: + free(line); + if (fp) + fclose(fp); + else + close(fd); + return ret; +} diff --git a/foobar/portable/smtpd/iobuf.c b/foobar/portable/smtpd/iobuf.c new file mode 100644 index 00000000..dec10660 --- /dev/null +++ b/foobar/portable/smtpd/iobuf.c @@ -0,0 +1,462 @@ +/* $OpenBSD: iobuf.c,v 1.13 2020/04/24 11:34:07 eric Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/uio.h> + +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#ifdef IO_TLS +#include <openssl/err.h> +#include <openssl/ssl.h> +#endif + +#include "iobuf.h" + +#define IOBUF_MAX 65536 +#define IOBUFQ_MIN 4096 + +struct ioqbuf *ioqbuf_alloc(struct iobuf *, size_t); +void iobuf_drain(struct iobuf *, size_t); + +int +iobuf_init(struct iobuf *io, size_t size, size_t max) +{ + memset(io, 0, sizeof *io); + + if (max == 0) + max = IOBUF_MAX; + + if (size == 0) + size = max; + + if (size > max) + return (-1); + + if ((io->buf = calloc(size, 1)) == NULL) + return (-1); + + io->size = size; + io->max = max; + + return (0); +} + +void +iobuf_clear(struct iobuf *io) +{ + struct ioqbuf *q; + + free(io->buf); + + while ((q = io->outq)) { + io->outq = q->next; + free(q); + } + + memset(io, 0, sizeof (*io)); +} + +void +iobuf_drain(struct iobuf *io, size_t n) +{ + struct ioqbuf *q; + size_t left = n; + + while ((q = io->outq) && left) { + if ((q->wpos - q->rpos) > left) { + q->rpos += left; + left = 0; + } else { + left -= q->wpos - q->rpos; + io->outq = q->next; + free(q); + } + } + + io->queued -= (n - left); + if (io->outq == NULL) + io->outqlast = NULL; +} + +int +iobuf_extend(struct iobuf *io, size_t n) +{ + char *t; + + if (n > io->max) + return (-1); + + if (io->max - io->size < n) + return (-1); + + t = recallocarray(io->buf, io->size, io->size + n, 1); + if (t == NULL) + return (-1); + + io->size += n; + io->buf = t; + + return (0); +} + +size_t +iobuf_left(struct iobuf *io) +{ + return io->size - io->wpos; +} + +size_t +iobuf_space(struct iobuf *io) +{ + return io->size - (io->wpos - io->rpos); +} + +size_t +iobuf_len(struct iobuf *io) +{ + return io->wpos - io->rpos; +} + +char * +iobuf_data(struct iobuf *io) +{ + return io->buf + io->rpos; +} + +void +iobuf_drop(struct iobuf *io, size_t n) +{ + if (n >= iobuf_len(io)) { + io->rpos = io->wpos = 0; + return; + } + + io->rpos += n; +} + +char * +iobuf_getline(struct iobuf *iobuf, size_t *rlen) +{ + char *buf; + size_t len, i; + + buf = iobuf_data(iobuf); + len = iobuf_len(iobuf); + + for (i = 0; i + 1 <= len; i++) + if (buf[i] == '\n') { + /* Note: the returned address points into the iobuf + * buffer. We NUL-end it for convenience, and discard + * the data from the iobuf, so that the caller doesn't + * have to do it. The data remains "valid" as long + * as the iobuf does not overwrite it, that is until + * the next call to iobuf_normalize() or iobuf_extend(). + */ + iobuf_drop(iobuf, i + 1); + buf[i] = '\0'; + if (rlen) + *rlen = i; + return (buf); + } + + return (NULL); +} + +void +iobuf_normalize(struct iobuf *io) +{ + if (io->rpos == 0) + return; + + if (io->rpos == io->wpos) { + io->rpos = io->wpos = 0; + return; + } + + memmove(io->buf, io->buf + io->rpos, io->wpos - io->rpos); + io->wpos -= io->rpos; + io->rpos = 0; +} + +ssize_t +iobuf_read(struct iobuf *io, int fd) +{ + ssize_t n; + + n = read(fd, io->buf + io->wpos, iobuf_left(io)); + if (n == -1) { + /* XXX is this really what we want? */ + if (errno == EAGAIN || errno == EINTR) + return (IOBUF_WANT_READ); + return (IOBUF_ERROR); + } + if (n == 0) + return (IOBUF_CLOSED); + + io->wpos += n; + + return (n); +} + +struct ioqbuf * +ioqbuf_alloc(struct iobuf *io, size_t len) +{ + struct ioqbuf *q; + + if (len < IOBUFQ_MIN) + len = IOBUFQ_MIN; + + if ((q = malloc(sizeof(*q) + len)) == NULL) + return (NULL); + + q->rpos = 0; + q->wpos = 0; + q->size = len; + q->next = NULL; + q->buf = (char *)(q) + sizeof(*q); + + if (io->outqlast == NULL) + io->outq = q; + else + io->outqlast->next = q; + io->outqlast = q; + + return (q); +} + +size_t +iobuf_queued(struct iobuf *io) +{ + return io->queued; +} + +void * +iobuf_reserve(struct iobuf *io, size_t len) +{ + struct ioqbuf *q; + void *r; + + if (len == 0) + return (NULL); + + if (((q = io->outqlast) == NULL) || q->size - q->wpos <= len) { + if ((q = ioqbuf_alloc(io, len)) == NULL) + return (NULL); + } + + r = q->buf + q->wpos; + q->wpos += len; + io->queued += len; + + return (r); +} + +int +iobuf_queue(struct iobuf *io, const void *data, size_t len) +{ + void *buf; + + if (len == 0) + return (0); + + if ((buf = iobuf_reserve(io, len)) == NULL) + return (-1); + + memmove(buf, data, len); + + return (len); +} + +int +iobuf_queuev(struct iobuf *io, const struct iovec *iov, int iovcnt) +{ + int i; + size_t len = 0; + char *buf; + + for (i = 0; i < iovcnt; i++) + len += iov[i].iov_len; + + if ((buf = iobuf_reserve(io, len)) == NULL) + return (-1); + + for (i = 0; i < iovcnt; i++) { + if (iov[i].iov_len == 0) + continue; + memmove(buf, iov[i].iov_base, iov[i].iov_len); + buf += iov[i].iov_len; + } + + return (0); + +} + +int +iobuf_fqueue(struct iobuf *io, const char *fmt, ...) +{ + va_list ap; + int len; + + va_start(ap, fmt); + len = iobuf_vfqueue(io, fmt, ap); + va_end(ap); + + return (len); +} + +int +iobuf_vfqueue(struct iobuf *io, const char *fmt, va_list ap) +{ + char *buf; + int len; + + len = vasprintf(&buf, fmt, ap); + + if (len == -1) + return (-1); + + len = iobuf_queue(io, buf, len); + free(buf); + + return (len); +} + +ssize_t +iobuf_write(struct iobuf *io, int fd) +{ + struct iovec iov[IOV_MAX]; + struct ioqbuf *q; + int i; + ssize_t n; + + i = 0; + for (q = io->outq; q ; q = q->next) { + if (i >= IOV_MAX) + break; + iov[i].iov_base = q->buf + q->rpos; + iov[i].iov_len = q->wpos - q->rpos; + i++; + } + + n = writev(fd, iov, i); + if (n == -1) { + if (errno == EAGAIN || errno == EINTR) + return (IOBUF_WANT_WRITE); + if (errno == EPIPE) + return (IOBUF_CLOSED); + return (IOBUF_ERROR); + } + + iobuf_drain(io, n); + + return (n); +} + +int +iobuf_flush(struct iobuf *io, int fd) +{ + ssize_t s; + + while (io->queued) + if ((s = iobuf_write(io, fd)) < 0) + return (s); + + return (0); +} + +#ifdef IO_TLS + +int +iobuf_flush_tls(struct iobuf *io, void *tls) +{ + ssize_t s; + + while (io->queued) + if ((s = iobuf_write_tls(io, tls)) < 0) + return (s); + + return (0); +} + +ssize_t +iobuf_write_tls(struct iobuf *io, void *tls) +{ + struct ioqbuf *q; + int r; + ssize_t n; + + q = io->outq; + n = SSL_write(tls, q->buf + q->rpos, q->wpos - q->rpos); + if (n <= 0) { + switch ((r = SSL_get_error(tls, n))) { + case SSL_ERROR_WANT_READ: + return (IOBUF_WANT_READ); + case SSL_ERROR_WANT_WRITE: + return (IOBUF_WANT_WRITE); + case SSL_ERROR_ZERO_RETURN: /* connection closed */ + return (IOBUF_CLOSED); + case SSL_ERROR_SYSCALL: + if (ERR_peek_last_error()) + return (IOBUF_TLSERROR); + return (IOBUF_ERROR); + default: + return (IOBUF_TLSERROR); + } + } + iobuf_drain(io, n); + + return (n); +} + +ssize_t +iobuf_read_tls(struct iobuf *io, void *tls) +{ + ssize_t n; + int r; + + n = SSL_read(tls, io->buf + io->wpos, iobuf_left(io)); + if (n < 0) { + switch ((r = SSL_get_error(tls, n))) { + case SSL_ERROR_WANT_READ: + return (IOBUF_WANT_READ); + case SSL_ERROR_WANT_WRITE: + return (IOBUF_WANT_WRITE); + case SSL_ERROR_SYSCALL: + if (ERR_peek_last_error()) + return (IOBUF_TLSERROR); + return (IOBUF_ERROR); + default: + return (IOBUF_TLSERROR); + } + } else if (n == 0) + return (IOBUF_CLOSED); + + io->wpos += n; + + return (n); +} + +#endif /* IO_TLS */ diff --git a/foobar/portable/smtpd/iobuf.h b/foobar/portable/smtpd/iobuf.h new file mode 100644 index 00000000..c454d0a1 --- /dev/null +++ b/foobar/portable/smtpd/iobuf.h @@ -0,0 +1,67 @@ +/* $OpenBSD: iobuf.h,v 1.5 2019/06/12 17:42:53 eric Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +struct ioqbuf { + struct ioqbuf *next; + char *buf; + size_t size; + size_t wpos; + size_t rpos; +}; + +struct iobuf { + char *buf; + size_t max; + size_t size; + size_t wpos; + size_t rpos; + + size_t queued; + struct ioqbuf *outq; + struct ioqbuf *outqlast; +}; + +#define IOBUF_WANT_READ -1 +#define IOBUF_WANT_WRITE -2 +#define IOBUF_CLOSED -3 +#define IOBUF_ERROR -4 +#define IOBUF_TLSERROR -5 + +int iobuf_init(struct iobuf *, size_t, size_t); +void iobuf_clear(struct iobuf *); + +int iobuf_extend(struct iobuf *, size_t); +void iobuf_normalize(struct iobuf *); +void iobuf_drop(struct iobuf *, size_t); +size_t iobuf_space(struct iobuf *); +size_t iobuf_len(struct iobuf *); +size_t iobuf_left(struct iobuf *); +char *iobuf_data(struct iobuf *); +char *iobuf_getline(struct iobuf *, size_t *); +ssize_t iobuf_read(struct iobuf *, int); +ssize_t iobuf_read_tls(struct iobuf *, void *); + +size_t iobuf_queued(struct iobuf *); +void* iobuf_reserve(struct iobuf *, size_t); +int iobuf_queue(struct iobuf *, const void*, size_t); +int iobuf_queuev(struct iobuf *, const struct iovec *, int); +int iobuf_fqueue(struct iobuf *, const char *, ...); +int iobuf_vfqueue(struct iobuf *, const char *, va_list); +int iobuf_flush(struct iobuf *, int); +int iobuf_flush_tls(struct iobuf *, void *); +ssize_t iobuf_write(struct iobuf *, int); +ssize_t iobuf_write_tls(struct iobuf *, void *); diff --git a/foobar/portable/smtpd/ioev.c b/foobar/portable/smtpd/ioev.c new file mode 100644 index 00000000..e0a8a096 --- /dev/null +++ b/foobar/portable/smtpd/ioev.c @@ -0,0 +1,1064 @@ +/* $OpenBSD: ioev.c,v 1.42 2019/06/12 17:42:53 eric Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/socket.h> + +#include <err.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <inttypes.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <unistd.h> + +#include "ioev.h" +#include "iobuf.h" + +#ifdef IO_TLS +#include <openssl/err.h> +#include <openssl/ssl.h> +#endif + +enum { + IO_STATE_NONE, + IO_STATE_CONNECT, + IO_STATE_CONNECT_TLS, + IO_STATE_ACCEPT_TLS, + IO_STATE_UP, + + IO_STATE_MAX, +}; + +#define IO_PAUSE_IN IO_IN +#define IO_PAUSE_OUT IO_OUT +#define IO_READ 0x04 +#define IO_WRITE 0x08 +#define IO_RW (IO_READ | IO_WRITE) +#define IO_RESET 0x10 /* internal */ +#define IO_HELD 0x20 /* internal */ + +struct io { + int sock; + void *arg; + void (*cb)(struct io*, int, void *); + struct iobuf iobuf; + size_t lowat; + int timeout; + int flags; + int state; + struct event ev; + void *tls; + const char *error; /* only valid immediately on callback */ +}; + +const char* io_strflags(int); +const char* io_evstr(short); + +void _io_init(void); +void io_hold(struct io *); +void io_release(struct io *); +void io_callback(struct io*, int); +void io_dispatch(int, short, void *); +void io_dispatch_connect(int, short, void *); +size_t io_pending(struct io *); +size_t io_queued(struct io*); +void io_reset(struct io *, short, void (*)(int, short, void*)); +void io_frame_enter(const char *, struct io *, int); +void io_frame_leave(struct io *); + +#ifdef IO_TLS +void ssl_error(const char *); /* XXX external */ + +static const char* io_tls_error(void); +void io_dispatch_accept_tls(int, short, void *); +void io_dispatch_connect_tls(int, short, void *); +void io_dispatch_read_tls(int, short, void *); +void io_dispatch_write_tls(int, short, void *); +void io_reload_tls(struct io *io); +#endif + +static struct io *current = NULL; +static uint64_t frame = 0; +static int _io_debug = 0; + +#define io_debug(args...) do { if (_io_debug) printf(args); } while(0) + + +const char* +io_strio(struct io *io) +{ + static char buf[128]; + char ssl[128]; + + ssl[0] = '\0'; +#ifdef IO_TLS + if (io->tls) { + (void)snprintf(ssl, sizeof ssl, " tls=%s:%s:%d", + SSL_get_version(io->tls), + SSL_get_cipher_name(io->tls), + SSL_get_cipher_bits(io->tls, NULL)); + } +#endif + + (void)snprintf(buf, sizeof buf, + "<io:%p fd=%d to=%d fl=%s%s ib=%zu ob=%zu>", + io, io->sock, io->timeout, io_strflags(io->flags), ssl, + io_pending(io), io_queued(io)); + + return (buf); +} + +#define CASE(x) case x : return #x + +const char* +io_strevent(int evt) +{ + static char buf[32]; + + switch (evt) { + CASE(IO_CONNECTED); + CASE(IO_TLSREADY); + CASE(IO_DATAIN); + CASE(IO_LOWAT); + CASE(IO_DISCONNECTED); + CASE(IO_TIMEOUT); + CASE(IO_ERROR); + default: + (void)snprintf(buf, sizeof(buf), "IO_? %d", evt); + return buf; + } +} + +void +io_set_nonblocking(int fd) +{ + int flags; + + if ((flags = fcntl(fd, F_GETFL)) == -1) + err(1, "io_set_blocking:fcntl(F_GETFL)"); + + flags |= O_NONBLOCK; + + if (fcntl(fd, F_SETFL, flags) == -1) + err(1, "io_set_blocking:fcntl(F_SETFL)"); +} + +void +io_set_nolinger(int fd) +{ + struct linger l; + + memset(&l, 0, sizeof(l)); + if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) == -1) + err(1, "io_set_linger:setsockopt()"); +} + +/* + * Event framing must not rely on an io pointer to refer to the "same" io + * throughout the frame, because this is not always the case: + * + * 1) enter(addr0) -> free(addr0) -> leave(addr0) = SEGV + * 2) enter(addr0) -> free(addr0) -> malloc == addr0 -> leave(addr0) = BAD! + * + * In both case, the problem is that the io is freed in the callback, so + * the pointer becomes invalid. If that happens, the user is required to + * call io_clear, so we can adapt the frame state there. + */ +void +io_frame_enter(const char *where, struct io *io, int ev) +{ + io_debug("\n=== %" PRIu64 " ===\n" + "io_frame_enter(%s, %s, %s)\n", + frame, where, io_evstr(ev), io_strio(io)); + + if (current) + errx(1, "io_frame_enter: interleaved frames"); + + current = io; + + io_hold(io); +} + +void +io_frame_leave(struct io *io) +{ + io_debug("io_frame_leave(%" PRIu64 ")\n", frame); + + if (current && current != io) + errx(1, "io_frame_leave: io mismatch"); + + /* io has been cleared */ + if (current == NULL) + goto done; + + /* TODO: There is a possible optimization there: + * In a typical half-duplex request/response scenario, + * the io is waiting to read a request, and when done, it queues + * the response in the output buffer and goes to write mode. + * There, the write event is set and will be triggered in the next + * event frame. In most case, the write call could be done + * immediately as part of the last read frame, thus avoiding to go + * through the event loop machinery. So, as an optimisation, we + * could detect that case here and force an event dispatching. + */ + + /* Reload the io if it has not been reset already. */ + io_release(io); + current = NULL; + done: + io_debug("=== /%" PRIu64 "\n", frame); + + frame += 1; +} + +void +_io_init() +{ + static int init = 0; + + if (init) + return; + + init = 1; + _io_debug = getenv("IO_DEBUG") != NULL; +} + +struct io * +io_new(void) +{ + struct io *io; + + _io_init(); + + if ((io = calloc(1, sizeof(*io))) == NULL) + return NULL; + + io->sock = -1; + io->timeout = -1; + + if (iobuf_init(&io->iobuf, 0, 0) == -1) { + free(io); + return NULL; + } + + return io; +} + +void +io_free(struct io *io) +{ + io_debug("io_clear(%p)\n", io); + + /* the current io is virtually dead */ + if (io == current) + current = NULL; + +#ifdef IO_TLS + SSL_free(io->tls); + io->tls = NULL; +#endif + + if (event_initialized(&io->ev)) + event_del(&io->ev); + if (io->sock != -1) { + close(io->sock); + io->sock = -1; + } + + iobuf_clear(&io->iobuf); + free(io); +} + +void +io_hold(struct io *io) +{ + io_debug("io_enter(%p)\n", io); + + if (io->flags & IO_HELD) + errx(1, "io_hold: io is already held"); + + io->flags &= ~IO_RESET; + io->flags |= IO_HELD; +} + +void +io_release(struct io *io) +{ + if (!(io->flags & IO_HELD)) + errx(1, "io_release: io is not held"); + + io->flags &= ~IO_HELD; + if (!(io->flags & IO_RESET)) + io_reload(io); +} + +void +io_set_fd(struct io *io, int fd) +{ + io->sock = fd; + if (fd != -1) + io_reload(io); +} + +void +io_set_callback(struct io *io, void(*cb)(struct io *, int, void *), void *arg) +{ + io->cb = cb; + io->arg = arg; +} + +void +io_set_timeout(struct io *io, int msec) +{ + io_debug("io_set_timeout(%p, %d)\n", io, msec); + + io->timeout = msec; +} + +void +io_set_lowat(struct io *io, size_t lowat) +{ + io_debug("io_set_lowat(%p, %zu)\n", io, lowat); + + io->lowat = lowat; +} + +void +io_pause(struct io *io, int dir) +{ + io_debug("io_pause(%p, %x)\n", io, dir); + + io->flags |= dir & (IO_PAUSE_IN | IO_PAUSE_OUT); + io_reload(io); +} + +void +io_resume(struct io *io, int dir) +{ + io_debug("io_resume(%p, %x)\n", io, dir); + + io->flags &= ~(dir & (IO_PAUSE_IN | IO_PAUSE_OUT)); + io_reload(io); +} + +void +io_set_read(struct io *io) +{ + int mode; + + io_debug("io_set_read(%p)\n", io); + + mode = io->flags & IO_RW; + if (!(mode == 0 || mode == IO_WRITE)) + errx(1, "io_set_read(): full-duplex or reading"); + + io->flags &= ~IO_RW; + io->flags |= IO_READ; + io_reload(io); +} + +void +io_set_write(struct io *io) +{ + int mode; + + io_debug("io_set_write(%p)\n", io); + + mode = io->flags & IO_RW; + if (!(mode == 0 || mode == IO_READ)) + errx(1, "io_set_write(): full-duplex or writing"); + + io->flags &= ~IO_RW; + io->flags |= IO_WRITE; + io_reload(io); +} + +const char * +io_error(struct io *io) +{ + return io->error; +} + +void * +io_tls(struct io *io) +{ + return io->tls; +} + +int +io_fileno(struct io *io) +{ + return io->sock; +} + +int +io_paused(struct io *io, int what) +{ + return (io->flags & (IO_PAUSE_IN | IO_PAUSE_OUT)) == what; +} + +/* + * Buffered output functions + */ + +int +io_write(struct io *io, const void *buf, size_t len) +{ + int r; + + r = iobuf_queue(&io->iobuf, buf, len); + + io_reload(io); + + return r; +} + +int +io_writev(struct io *io, const struct iovec *iov, int iovcount) +{ + int r; + + r = iobuf_queuev(&io->iobuf, iov, iovcount); + + io_reload(io); + + return r; +} + +int +io_print(struct io *io, const char *s) +{ + return io_write(io, s, strlen(s)); +} + +int +io_printf(struct io *io, const char *fmt, ...) +{ + va_list ap; + int r; + + va_start(ap, fmt); + r = io_vprintf(io, fmt, ap); + va_end(ap); + + return r; +} + +int +io_vprintf(struct io *io, const char *fmt, va_list ap) +{ + + char *buf; + int len; + + len = vasprintf(&buf, fmt, ap); + if (len == -1) + return -1; + len = io_write(io, buf, len); + free(buf); + + return len; +} + +size_t +io_queued(struct io *io) +{ + return iobuf_queued(&io->iobuf); +} + +/* + * Buffered input functions + */ + +void * +io_data(struct io *io) +{ + return iobuf_data(&io->iobuf); +} + +size_t +io_datalen(struct io *io) +{ + return iobuf_len(&io->iobuf); +} + +char * +io_getline(struct io *io, size_t *sz) +{ + return iobuf_getline(&io->iobuf, sz); +} + +void +io_drop(struct io *io, size_t sz) +{ + return iobuf_drop(&io->iobuf, sz); +} + + +#define IO_READING(io) (((io)->flags & IO_RW) != IO_WRITE) +#define IO_WRITING(io) (((io)->flags & IO_RW) != IO_READ) + +/* + * Setup the necessary events as required by the current io state, + * honouring duplex mode and i/o pauses. + */ +void +io_reload(struct io *io) +{ + short events; + + /* io will be reloaded at release time */ + if (io->flags & IO_HELD) + return; + + iobuf_normalize(&io->iobuf); + +#ifdef IO_TLS + if (io->tls) { + io_reload_tls(io); + return; + } +#endif + + io_debug("io_reload(%p)\n", io); + + events = 0; + if (IO_READING(io) && !(io->flags & IO_PAUSE_IN)) + events = EV_READ; + if (IO_WRITING(io) && !(io->flags & IO_PAUSE_OUT) && io_queued(io)) + events |= EV_WRITE; + + io_reset(io, events, io_dispatch); +} + +/* Set the requested event. */ +void +io_reset(struct io *io, short events, void (*dispatch)(int, short, void*)) +{ + struct timeval tv, *ptv; + + io_debug("io_reset(%p, %s, %p) -> %s\n", + io, io_evstr(events), dispatch, io_strio(io)); + + /* + * Indicate that the event has already been reset so that reload + * is not called on frame_leave. + */ + io->flags |= IO_RESET; + + if (event_initialized(&io->ev)) + event_del(&io->ev); + + /* + * The io is paused by the user, so we don't want the timeout to be + * effective. + */ + if (events == 0) + return; + + event_set(&io->ev, io->sock, events, dispatch, io); + if (io->timeout >= 0) { + tv.tv_sec = io->timeout / 1000; + tv.tv_usec = (io->timeout % 1000) * 1000; + ptv = &tv; + } else + ptv = NULL; + + event_add(&io->ev, ptv); +} + +size_t +io_pending(struct io *io) +{ + return iobuf_len(&io->iobuf); +} + +const char* +io_strflags(int flags) +{ + static char buf[64]; + + buf[0] = '\0'; + + switch (flags & IO_RW) { + case 0: + (void)strlcat(buf, "rw", sizeof buf); + break; + case IO_READ: + (void)strlcat(buf, "R", sizeof buf); + break; + case IO_WRITE: + (void)strlcat(buf, "W", sizeof buf); + break; + case IO_RW: + (void)strlcat(buf, "RW", sizeof buf); + break; + } + + if (flags & IO_PAUSE_IN) + (void)strlcat(buf, ",F_PI", sizeof buf); + if (flags & IO_PAUSE_OUT) + (void)strlcat(buf, ",F_PO", sizeof buf); + + return buf; +} + +const char* +io_evstr(short ev) +{ + static char buf[64]; + char buf2[16]; + int n; + + n = 0; + buf[0] = '\0'; + + if (ev == 0) { + (void)strlcat(buf, "<NONE>", sizeof(buf)); + return buf; + } + + if (ev & EV_TIMEOUT) { + (void)strlcat(buf, "EV_TIMEOUT", sizeof(buf)); + ev &= ~EV_TIMEOUT; + n++; + } + + if (ev & EV_READ) { + if (n) + (void)strlcat(buf, "|", sizeof(buf)); + (void)strlcat(buf, "EV_READ", sizeof(buf)); + ev &= ~EV_READ; + n++; + } + + if (ev & EV_WRITE) { + if (n) + (void)strlcat(buf, "|", sizeof(buf)); + (void)strlcat(buf, "EV_WRITE", sizeof(buf)); + ev &= ~EV_WRITE; + n++; + } + + if (ev & EV_SIGNAL) { + if (n) + (void)strlcat(buf, "|", sizeof(buf)); + (void)strlcat(buf, "EV_SIGNAL", sizeof(buf)); + ev &= ~EV_SIGNAL; + n++; + } + + if (ev) { + if (n) + (void)strlcat(buf, "|", sizeof(buf)); + (void)strlcat(buf, "EV_?=0x", sizeof(buf)); + (void)snprintf(buf2, sizeof(buf2), "%hx", ev); + (void)strlcat(buf, buf2, sizeof(buf)); + } + + return buf; +} + +void +io_dispatch(int fd, short ev, void *humppa) +{ + struct io *io = humppa; + size_t w; + ssize_t n; + int saved_errno; + + io_frame_enter("io_dispatch", io, ev); + + if (ev == EV_TIMEOUT) { + io_callback(io, IO_TIMEOUT); + goto leave; + } + + if (ev & EV_WRITE && (w = io_queued(io))) { + if ((n = iobuf_write(&io->iobuf, io->sock)) < 0) { + if (n == IOBUF_WANT_WRITE) /* kqueue bug? */ + goto read; + if (n == IOBUF_CLOSED) + io_callback(io, IO_DISCONNECTED); + else { + saved_errno = errno; + io->error = strerror(errno); + errno = saved_errno; + io_callback(io, IO_ERROR); + } + goto leave; + } + if (w > io->lowat && w - n <= io->lowat) + io_callback(io, IO_LOWAT); + } + read: + + if (ev & EV_READ) { + iobuf_normalize(&io->iobuf); + if ((n = iobuf_read(&io->iobuf, io->sock)) < 0) { + if (n == IOBUF_CLOSED) + io_callback(io, IO_DISCONNECTED); + else { + saved_errno = errno; + io->error = strerror(errno); + errno = saved_errno; + io_callback(io, IO_ERROR); + } + goto leave; + } + if (n) + io_callback(io, IO_DATAIN); + } + +leave: + io_frame_leave(io); +} + +void +io_callback(struct io *io, int evt) +{ + io->cb(io, evt, io->arg); +} + +int +io_connect(struct io *io, const struct sockaddr *sa, const struct sockaddr *bsa) +{ + int sock, errno_save; + + if ((sock = socket(sa->sa_family, SOCK_STREAM, 0)) == -1) + goto fail; + + io_set_nonblocking(sock); + io_set_nolinger(sock); + + if (bsa && bind(sock, bsa, SA_LEN(bsa)) == -1) + goto fail; + + if (connect(sock, sa, SA_LEN(sa)) == -1) + if (errno != EINPROGRESS) + goto fail; + + io->sock = sock; + io_reset(io, EV_WRITE, io_dispatch_connect); + + return (sock); + + fail: + if (sock != -1) { + errno_save = errno; + close(sock); + errno = errno_save; + io->error = strerror(errno); + } + return (-1); +} + +void +io_dispatch_connect(int fd, short ev, void *humppa) +{ + struct io *io = humppa; + int r, e; + socklen_t sl; + + io_frame_enter("io_dispatch_connect", io, ev); + + if (ev == EV_TIMEOUT) { + close(fd); + io->sock = -1; + io_callback(io, IO_TIMEOUT); + } else { + sl = sizeof(e); + r = getsockopt(fd, SOL_SOCKET, SO_ERROR, &e, &sl); + if (r == -1) { + warn("io_dispatch_connect: getsockopt"); + e = errno; + } + if (e) { + close(fd); + io->sock = -1; + io->error = strerror(e); + io_callback(io, e == ETIMEDOUT ? IO_TIMEOUT : IO_ERROR); + } + else { + io->state = IO_STATE_UP; + io_callback(io, IO_CONNECTED); + } + } + + io_frame_leave(io); +} + +#ifdef IO_TLS + +static const char* +io_tls_error(void) +{ + static char buf[128]; + unsigned long e; + + e = ERR_peek_last_error(); + if (e) { + ERR_error_string(e, buf); + return (buf); + } + + return ("No TLS error"); +} + +int +io_start_tls(struct io *io, void *tls) +{ + int mode; + + mode = io->flags & IO_RW; + if (mode == 0 || mode == IO_RW) + errx(1, "io_start_tls(): full-duplex or unset"); + + if (io->tls) + errx(1, "io_start_tls(): TLS already started"); + io->tls = tls; + + if (SSL_set_fd(io->tls, io->sock) == 0) { + ssl_error("io_start_tls:SSL_set_fd"); + return (-1); + } + + if (mode == IO_WRITE) { + io->state = IO_STATE_CONNECT_TLS; + SSL_set_connect_state(io->tls); + io_reset(io, EV_WRITE, io_dispatch_connect_tls); + } else { + io->state = IO_STATE_ACCEPT_TLS; + SSL_set_accept_state(io->tls); + io_reset(io, EV_READ, io_dispatch_accept_tls); + } + + return (0); +} + +void +io_dispatch_accept_tls(int fd, short event, void *humppa) +{ + struct io *io = humppa; + int e, ret; + + io_frame_enter("io_dispatch_accept_tls", io, event); + + if (event == EV_TIMEOUT) { + io_callback(io, IO_TIMEOUT); + goto leave; + } + + if ((ret = SSL_accept(io->tls)) > 0) { + io->state = IO_STATE_UP; + io_callback(io, IO_TLSREADY); + goto leave; + } + + switch ((e = SSL_get_error(io->tls, ret))) { + case SSL_ERROR_WANT_READ: + io_reset(io, EV_READ, io_dispatch_accept_tls); + break; + case SSL_ERROR_WANT_WRITE: + io_reset(io, EV_WRITE, io_dispatch_accept_tls); + break; + default: + io->error = io_tls_error(); + ssl_error("io_dispatch_accept_tls:SSL_accept"); + io_callback(io, IO_ERROR); + break; + } + + leave: + io_frame_leave(io); +} + +void +io_dispatch_connect_tls(int fd, short event, void *humppa) +{ + struct io *io = humppa; + int e, ret; + + io_frame_enter("io_dispatch_connect_tls", io, event); + + if (event == EV_TIMEOUT) { + io_callback(io, IO_TIMEOUT); + goto leave; + } + + if ((ret = SSL_connect(io->tls)) > 0) { + io->state = IO_STATE_UP; + io_callback(io, IO_TLSREADY); + goto leave; + } + + switch ((e = SSL_get_error(io->tls, ret))) { + case SSL_ERROR_WANT_READ: + io_reset(io, EV_READ, io_dispatch_connect_tls); + break; + case SSL_ERROR_WANT_WRITE: + io_reset(io, EV_WRITE, io_dispatch_connect_tls); + break; + default: + io->error = io_tls_error(); + ssl_error("io_dispatch_connect_ssl:SSL_connect"); + io_callback(io, IO_TLSERROR); + break; + } + + leave: + io_frame_leave(io); +} + +void +io_dispatch_read_tls(int fd, short event, void *humppa) +{ + struct io *io = humppa; + int n, saved_errno; + + io_frame_enter("io_dispatch_read_tls", io, event); + + if (event == EV_TIMEOUT) { + io_callback(io, IO_TIMEOUT); + goto leave; + } + +again: + iobuf_normalize(&io->iobuf); + switch ((n = iobuf_read_tls(&io->iobuf, (SSL*)io->tls))) { + case IOBUF_WANT_READ: + io_reset(io, EV_READ, io_dispatch_read_tls); + break; + case IOBUF_WANT_WRITE: + io_reset(io, EV_WRITE, io_dispatch_read_tls); + break; + case IOBUF_CLOSED: + io_callback(io, IO_DISCONNECTED); + break; + case IOBUF_ERROR: + saved_errno = errno; + io->error = strerror(errno); + errno = saved_errno; + io_callback(io, IO_ERROR); + break; + case IOBUF_TLSERROR: + io->error = io_tls_error(); + ssl_error("io_dispatch_read_tls:SSL_read"); + io_callback(io, IO_ERROR); + break; + default: + io_debug("io_dispatch_read_tls(...) -> r=%d\n", n); + io_callback(io, IO_DATAIN); + if (current == io && IO_READING(io) && SSL_pending(io->tls)) + goto again; + } + + leave: + io_frame_leave(io); +} + +void +io_dispatch_write_tls(int fd, short event, void *humppa) +{ + struct io *io = humppa; + int n, saved_errno; + size_t w2, w; + + io_frame_enter("io_dispatch_write_tls", io, event); + + if (event == EV_TIMEOUT) { + io_callback(io, IO_TIMEOUT); + goto leave; + } + + w = io_queued(io); + switch ((n = iobuf_write_tls(&io->iobuf, (SSL*)io->tls))) { + case IOBUF_WANT_READ: + io_reset(io, EV_READ, io_dispatch_write_tls); + break; + case IOBUF_WANT_WRITE: + io_reset(io, EV_WRITE, io_dispatch_write_tls); + break; + case IOBUF_CLOSED: + io_callback(io, IO_DISCONNECTED); + break; + case IOBUF_ERROR: + saved_errno = errno; + io->error = strerror(errno); + errno = saved_errno; + io_callback(io, IO_ERROR); + break; + case IOBUF_TLSERROR: + io->error = io_tls_error(); + ssl_error("io_dispatch_write_tls:SSL_write"); + io_callback(io, IO_ERROR); + break; + default: + io_debug("io_dispatch_write_tls(...) -> w=%d\n", n); + w2 = io_queued(io); + if (w > io->lowat && w2 <= io->lowat) + io_callback(io, IO_LOWAT); + break; + } + + leave: + io_frame_leave(io); +} + +void +io_reload_tls(struct io *io) +{ + short ev = 0; + void (*dispatch)(int, short, void*) = NULL; + + switch (io->state) { + case IO_STATE_CONNECT_TLS: + ev = EV_WRITE; + dispatch = io_dispatch_connect_tls; + break; + case IO_STATE_ACCEPT_TLS: + ev = EV_READ; + dispatch = io_dispatch_accept_tls; + break; + case IO_STATE_UP: + ev = 0; + if (IO_READING(io) && !(io->flags & IO_PAUSE_IN)) { + ev = EV_READ; + dispatch = io_dispatch_read_tls; + } + else if (IO_WRITING(io) && !(io->flags & IO_PAUSE_OUT) && + io_queued(io)) { + ev = EV_WRITE; + dispatch = io_dispatch_write_tls; + } + if (!ev) + return; /* paused */ + break; + default: + errx(1, "io_reload_tls(): bad state"); + } + + io_reset(io, ev, dispatch); +} + +#endif /* IO_TLS */ diff --git a/foobar/portable/smtpd/ioev.h b/foobar/portable/smtpd/ioev.h new file mode 100644 index 00000000..f155a7fc --- /dev/null +++ b/foobar/portable/smtpd/ioev.h @@ -0,0 +1,70 @@ +/* $OpenBSD: ioev.h,v 1.18 2019/09/11 04:19:19 martijn Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +enum { + IO_CONNECTED = 0, /* connection successful */ + IO_TLSREADY, /* TLS started successfully */ + IO_TLSERROR, /* XXX - needs more work */ + IO_DATAIN, /* new data in input buffer */ + IO_LOWAT, /* output queue running low */ + IO_DISCONNECTED, /* error? */ + IO_TIMEOUT, /* error? */ + IO_ERROR, /* details? */ +}; + +#define IO_IN 0x01 +#define IO_OUT 0x02 + +struct io; + +void io_set_nonblocking(int); +void io_set_nolinger(int); + +struct io *io_new(void); +void io_free(struct io *); +void io_set_read(struct io *); +void io_set_write(struct io *); +void io_set_fd(struct io *, int); +void io_set_callback(struct io *io, void(*)(struct io *, int, void *), void *); +void io_set_timeout(struct io *, int); +void io_set_lowat(struct io *, size_t); +void io_pause(struct io *, int); +void io_resume(struct io *, int); +void io_reload(struct io *); +int io_connect(struct io *, const struct sockaddr *, const struct sockaddr *); +int io_start_tls(struct io *, void *); +const char* io_strio(struct io *); +const char* io_strevent(int); +const char* io_error(struct io *); +void* io_tls(struct io *); +int io_fileno(struct io *); +int io_paused(struct io *, int); + +/* Buffered output functions */ +int io_write(struct io *, const void *, size_t); +int io_writev(struct io *, const struct iovec *, int); +int io_print(struct io *, const char *); +int io_printf(struct io *, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); +int io_vprintf(struct io *, const char *, va_list); +size_t io_queued(struct io *); + +/* Buffered input functions */ +void* io_data(struct io *); +size_t io_datalen(struct io *); +char* io_getline(struct io *, size_t *); +void io_drop(struct io *, size_t); diff --git a/foobar/portable/smtpd/libressl.c b/foobar/portable/smtpd/libressl.c new file mode 100644 index 00000000..57d74389 --- /dev/null +++ b/foobar/portable/smtpd/libressl.c @@ -0,0 +1,213 @@ +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * 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 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + +/* + * SSL operations needed when running in a privilege separated environment. + * Adapted from openssl's ssl_rsa.c by Pierre-Yves Ritschard . + */ + +#include "includes.h" + +#include <sys/types.h> + +#include <limits.h> +#include <unistd.h> +#include <stdio.h> + +#include <openssl/err.h> +#include <openssl/bio.h> +#include <openssl/objects.h> +#include <openssl/evp.h> +#include <openssl/x509.h> +#include <openssl/pem.h> +#include <openssl/ssl.h> + +#include "log.h" +#include "ssl.h" + +#define SSL_ECDH_CURVE "prime256v1" + +/* + * Read a bio that contains our certificate in "PEM" format, + * possibly followed by a sequence of CA certificates that should be + * sent to the peer in the Certificate message. + */ +static int +ssl_ctx_use_certificate_chain_bio(SSL_CTX *ctx, BIO *in) +{ + int ret = 0; + X509 *x = NULL; + + ERR_clear_error(); /* clear error stack for SSL_CTX_use_certificate() */ + + x = PEM_read_bio_X509_AUX(in, NULL, ctx->default_passwd_callback, + ctx->default_passwd_callback_userdata); + if (x == NULL) { + SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_CHAIN_FILE, ERR_R_PEM_LIB); + goto end; + } + + ret = SSL_CTX_use_certificate(ctx, x); + + if (ERR_peek_error() != 0) + ret = 0; + /* Key/certificate mismatch doesn't imply ret==0 ... */ + if (ret) { + /* + * If we could set up our certificate, now proceed to + * the CA certificates. + */ + X509 *ca; + int r; + unsigned long err; + + if (ctx->extra_certs != NULL) { + sk_X509_pop_free(ctx->extra_certs, X509_free); + ctx->extra_certs = NULL; + } + + while ((ca = PEM_read_bio_X509(in, NULL, + ctx->default_passwd_callback, + ctx->default_passwd_callback_userdata)) != NULL) { + r = SSL_CTX_add_extra_chain_cert(ctx, ca); + if (!r) { + X509_free(ca); + ret = 0; + goto end; + } + /* + * Note that we must not free r if it was successfully + * added to the chain (while we must free the main + * certificate, since its reference count is increased + * by SSL_CTX_use_certificate). + */ + } + + /* When the while loop ends, it's usually just EOF. */ + err = ERR_peek_last_error(); + if (ERR_GET_LIB(err) == ERR_LIB_PEM && + ERR_GET_REASON(err) == PEM_R_NO_START_LINE) + ERR_clear_error(); + else + ret = 0; /* some real error */ + } + +end: + if (x != NULL) + X509_free(x); + return (ret); +} + +int +SSL_CTX_use_certificate_chain_mem(SSL_CTX *ctx, void *buf, int len) +{ + BIO *in; + int ret = 0; + + in = BIO_new_mem_buf(buf, len); + if (in == NULL) { + SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_CHAIN_FILE, ERR_R_BUF_LIB); + goto end; + } + + ret = ssl_ctx_use_certificate_chain_bio(ctx, in); + +end: + BIO_free(in); + return (ret); +} + +#ifndef HAVE_SSL_CTX_SET_ECDH_AUTO +void +SSL_CTX_set_ecdh_auto(SSL_CTX *ctx, int enable) +{ + int nid; + EC_KEY *ecdh; + + if (!enable) + return; + + if ((nid = OBJ_sn2nid(SSL_ECDH_CURVE)) == 0) { + ssl_error("ssl_set_ecdh_auto"); + fatal("ssl_set_ecdh_auto: unknown curve name " + SSL_ECDH_CURVE); + } + + if ((ecdh = EC_KEY_new_by_curve_name(nid)) == NULL) { + ssl_error("ssl_set_ecdh_auto"); + fatal("ssl_set_ecdh_auto: unable to create curve " + SSL_ECDH_CURVE); + } + + SSL_CTX_set_tmp_ecdh(ctx, ecdh); + SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE); + EC_KEY_free(ecdh); +} +#endif + +#ifndef HAVE_SSL_CTX_SET_DH_AUTO +void +SSL_CTX_set_dh_auto(SSL_CTX *ctx, int enable) +{ + if (!enable) + return; + + /* stub until OpenSSL catches up with this ... */ + log_warnx("OpenSSL does not support SSL_CTX_set_dh_auto (yet ?)"); + return; +} +#endif diff --git a/foobar/portable/smtpd/limit.c b/foobar/portable/smtpd/limit.c new file mode 100644 index 00000000..25e7a026 --- /dev/null +++ b/foobar/portable/smtpd/limit.c @@ -0,0 +1,124 @@ +/* $OpenBSD: limit.c,v 1.5 2016/06/15 19:59:03 gilles Exp $ */ + +/* + * Copyright (c) 2013 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> + +#include <ctype.h> +#include <err.h> +#include <event.h> +#include <fcntl.h> +#include <imsg.h> +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <string.h> + +#include "smtpd.h" +#include "log.h" + +void +limit_mta_set_defaults(struct mta_limits *limits) +{ + limits->maxconn_per_host = 10; + limits->maxconn_per_route = 5; + limits->maxconn_per_source = 100; + limits->maxconn_per_connector = 20; + limits->maxconn_per_relay = 100; + limits->maxconn_per_domain = 100; + + limits->conndelay_host = 0; + limits->conndelay_route = 5; + limits->conndelay_source = 0; + limits->conndelay_connector = 0; + limits->conndelay_relay = 2; + limits->conndelay_domain = 0; + + limits->discdelay_route = 3; + + limits->max_mail_per_session = 100; + limits->sessdelay_transaction = 0; + limits->sessdelay_keepalive = 10; + + limits->max_failures_per_session = 25; + + limits->family = AF_UNSPEC; + + limits->task_hiwat = 50; + limits->task_lowat = 30; + limits->task_release = 10; +} + +int +limit_mta_set(struct mta_limits *limits, const char *key, int64_t value) +{ + if (!strcmp(key, "max-conn-per-host")) + limits->maxconn_per_host = value; + else if (!strcmp(key, "max-conn-per-route")) + limits->maxconn_per_route = value; + else if (!strcmp(key, "max-conn-per-source")) + limits->maxconn_per_source = value; + else if (!strcmp(key, "max-conn-per-connector")) + limits->maxconn_per_connector = value; + else if (!strcmp(key, "max-conn-per-relay")) + limits->maxconn_per_relay = value; + else if (!strcmp(key, "max-conn-per-domain")) + limits->maxconn_per_domain = value; + + else if (!strcmp(key, "conn-delay-host")) + limits->conndelay_host = value; + else if (!strcmp(key, "conn-delay-route")) + limits->conndelay_route = value; + else if (!strcmp(key, "conn-delay-source")) + limits->conndelay_source = value; + else if (!strcmp(key, "conn-delay-connector")) + limits->conndelay_connector = value; + else if (!strcmp(key, "conn-delay-relay")) + limits->conndelay_relay = value; + else if (!strcmp(key, "conn-delay-domain")) + limits->conndelay_domain = value; + + else if (!strcmp(key, "reconn-delay-route")) + limits->discdelay_route = value; + + else if (!strcmp(key, "session-mail-max")) + limits->max_mail_per_session = value; + else if (!strcmp(key, "session-transaction-delay")) + limits->sessdelay_transaction = value; + else if (!strcmp(key, "session-keepalive")) + limits->sessdelay_keepalive = value; + + else if (!strcmp(key, "max-failures-per-session")) + limits->max_failures_per_session = value; + + else if (!strcmp(key, "task-hiwat")) + limits->task_hiwat = value; + else if (!strcmp(key, "task-lowat")) + limits->task_lowat = value; + else if (!strcmp(key, "task-release")) + limits->task_release = value; + + else + return (0); + + return (1); +} diff --git a/foobar/portable/smtpd/lka.c b/foobar/portable/smtpd/lka.c new file mode 100644 index 00000000..6ac21245 --- /dev/null +++ b/foobar/portable/smtpd/lka.c @@ -0,0 +1,914 @@ +/* $OpenBSD: lka.c,v 1.243 2019/12/21 10:23:37 gilles Exp $ */ + +/* + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> + * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2012 Eric Faurot <eric@faurot.net> + * + * 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 "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <sys/uio.h> + +#include <netinet/in.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <netdb.h> +#include <grp.h> /* needed for setgroups */ +#include <imsg.h> +#include <openssl/err.h> +#include <openssl/ssl.h> +#include <pwd.h> +#include <resolv.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "smtpd.h" +#include "log.h" +#include "ssl.h" + +static void lka_imsg(struct mproc *, struct imsg *); +static void lka_shutdown(void); +static void lka_sig_handler(int, short, void *); +static int lka_authenticate(const char *, const char *, const char *); +static int lka_credentials(const char *, const char *, char *, size_t); +static int lka_userinfo(const char *, const char *, struct userinfo *); +static int lka_addrname(const char *, const struct sockaddr *, + struct addrname *); +static int lka_mailaddrmap(const char *, const char *, const struct mailaddr *); + +static void proc_timeout(int fd, short event, void *p); + +struct event ev_proc_ready; + +static void +lka_imsg(struct mproc *p, struct imsg *imsg) +{ + struct table *table; + int ret; + struct sockaddr_storage ss; + struct userinfo userinfo; + struct addrname addrname; + struct envelope evp; + struct mailaddr maddr; + struct msg m; + union lookup lk; + char buf[LINE_MAX]; + const char *tablename, *username, *password, *label, *procname; + uint64_t reqid; + int v; + struct timeval tv; + const char *direction; + const char *rdns; + const char *command; + const char *response; + const char *ciphers; + const char *address; + const char *domain; + const char *helomethod; + const char *heloname; + const char *filter_name; + const char *result; + struct sockaddr_storage ss_src, ss_dest; + int filter_response; + int filter_phase; + const char *filter_param; + uint32_t msgid; + uint32_t subsystems; + uint64_t evpid; + size_t msgsz; + int ok; + int fcrdns; + + if (imsg == NULL) + lka_shutdown(); + + switch (imsg->hdr.type) { + + case IMSG_GETADDRINFO: + case IMSG_GETNAMEINFO: + case IMSG_RES_QUERY: + resolver_dispatch_request(p, imsg); + return; + + case IMSG_CERT_INIT: + case IMSG_CERT_CERTIFICATE: + case IMSG_CERT_VERIFY: + cert_dispatch_request(p, imsg); + return; + + case IMSG_MTA_DNS_HOST: + case IMSG_MTA_DNS_MX: + case IMSG_MTA_DNS_MX_PREFERENCE: + dns_imsg(p, imsg); + return; + + case IMSG_SMTP_CHECK_SENDER: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_string(&m, &tablename); + m_get_string(&m, &username); + m_get_mailaddr(&m, &maddr); + m_end(&m); + + ret = lka_mailaddrmap(tablename, username, &maddr); + + m_create(p, IMSG_SMTP_CHECK_SENDER, 0, 0, -1); + m_add_id(p, reqid); + m_add_int(p, ret); + m_close(p); + return; + + case IMSG_SMTP_EXPAND_RCPT: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_envelope(&m, &evp); + m_end(&m); + lka_session(reqid, &evp); + return; + + case IMSG_SMTP_LOOKUP_HELO: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_string(&m, &tablename); + m_get_sockaddr(&m, (struct sockaddr *)&ss); + m_end(&m); + + ret = lka_addrname(tablename, (struct sockaddr*)&ss, + &addrname); + + m_create(p, IMSG_SMTP_LOOKUP_HELO, 0, 0, -1); + m_add_id(p, reqid); + m_add_int(p, ret); + if (ret == LKA_OK) + m_add_string(p, addrname.name); + m_close(p); + return; + + case IMSG_SMTP_AUTHENTICATE: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_string(&m, &tablename); + m_get_string(&m, &username); + m_get_string(&m, &password); + m_end(&m); + + if (!tablename[0]) { + m_create(p_parent, IMSG_LKA_AUTHENTICATE, + 0, 0, -1); + m_add_id(p_parent, reqid); + m_add_string(p_parent, username); + m_add_string(p_parent, password); + m_close(p_parent); + return; + } + + ret = lka_authenticate(tablename, username, password); + + m_create(p, IMSG_SMTP_AUTHENTICATE, 0, 0, -1); + m_add_id(p, reqid); + m_add_int(p, ret); + m_close(p); + return; + + case IMSG_MDA_LOOKUP_USERINFO: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_string(&m, &tablename); + m_get_string(&m, &username); + m_end(&m); + + ret = lka_userinfo(tablename, username, &userinfo); + + m_create(p, IMSG_MDA_LOOKUP_USERINFO, 0, 0, -1); + m_add_id(p, reqid); + m_add_int(p, ret); + if (ret == LKA_OK) + m_add_data(p, &userinfo, sizeof(userinfo)); + m_close(p); + return; + + case IMSG_MTA_LOOKUP_CREDENTIALS: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_string(&m, &tablename); + m_get_string(&m, &label); + m_end(&m); + + lka_credentials(tablename, label, buf, sizeof(buf)); + + m_create(p, IMSG_MTA_LOOKUP_CREDENTIALS, 0, 0, -1); + m_add_id(p, reqid); + m_add_string(p, buf); + m_close(p); + return; + + case IMSG_MTA_LOOKUP_SOURCE: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_string(&m, &tablename); + m_end(&m); + + table = table_find(env, tablename); + + m_create(p, IMSG_MTA_LOOKUP_SOURCE, 0, 0, -1); + m_add_id(p, reqid); + + if (table == NULL) { + log_warn("warn: source address table %s missing", + tablename); + m_add_int(p, LKA_TEMPFAIL); + } + else { + ret = table_fetch(table, K_SOURCE, &lk); + if (ret == -1) + m_add_int(p, LKA_TEMPFAIL); + else if (ret == 0) + m_add_int(p, LKA_PERMFAIL); + else { + m_add_int(p, LKA_OK); + m_add_sockaddr(p, + (struct sockaddr *)&lk.source.addr); + } + } + m_close(p); + return; + + case IMSG_MTA_LOOKUP_HELO: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_string(&m, &tablename); + m_get_sockaddr(&m, (struct sockaddr *)&ss); + m_end(&m); + + ret = lka_addrname(tablename, (struct sockaddr*)&ss, + &addrname); + + m_create(p, IMSG_MTA_LOOKUP_HELO, 0, 0, -1); + m_add_id(p, reqid); + m_add_int(p, ret); + if (ret == LKA_OK) + m_add_string(p, addrname.name); + m_close(p); + return; + + case IMSG_MTA_LOOKUP_SMARTHOST: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_string(&m, &domain); + m_get_string(&m, &tablename); + m_end(&m); + + table = table_find(env, tablename); + + m_create(p, IMSG_MTA_LOOKUP_SMARTHOST, 0, 0, -1); + m_add_id(p, reqid); + + if (table == NULL) { + log_warn("warn: smarthost table %s missing", tablename); + m_add_int(p, LKA_TEMPFAIL); + } + else { + if (domain == NULL) + ret = table_fetch(table, K_RELAYHOST, &lk); + else + ret = table_lookup(table, K_RELAYHOST, domain, &lk); + + if (ret == -1) + m_add_int(p, LKA_TEMPFAIL); + else if (ret == 0) + m_add_int(p, LKA_PERMFAIL); + else { + m_add_int(p, LKA_OK); + m_add_string(p, lk.relayhost); + } + } + m_close(p); + return; + + case IMSG_CONF_START: + return; + + case IMSG_CONF_END: + if (tracing & TRACE_TABLES) + table_dump_all(env); + + /* fork & exec tables that need it */ + table_open_all(env); + +#if HAVE_PLEDGE + /* revoke proc & exec */ + if (pledge("stdio rpath inet dns getpw recvfd sendfd", + NULL) == -1) + err(1, "pledge"); +#endif + + /* setup proc registering task */ + evtimer_set(&ev_proc_ready, proc_timeout, &ev_proc_ready); + tv.tv_sec = 0; + tv.tv_usec = 10; + evtimer_add(&ev_proc_ready, &tv); + return; + + case IMSG_LKA_OPEN_FORWARD: + lka_session_forward_reply(imsg->data, imsg->fd); + return; + + case IMSG_LKA_AUTHENTICATE: + imsg->hdr.type = IMSG_SMTP_AUTHENTICATE; + m_forward(p_pony, imsg); + return; + + case IMSG_CTL_VERBOSE: + m_msg(&m, imsg); + m_get_int(&m, &v); + m_end(&m); + log_trace_verbose(v); + return; + + case IMSG_CTL_PROFILE: + m_msg(&m, imsg); + m_get_int(&m, &v); + m_end(&m); + profiling = v; + return; + + case IMSG_CTL_UPDATE_TABLE: + ret = 0; + table = table_find(env, imsg->data); + if (table == NULL) { + log_warnx("warn: Lookup table not found: " + "\"%s\"", (char *)imsg->data); + } else + ret = table_update(table); + + m_compose(p_control, + (ret == 1) ? IMSG_CTL_OK : IMSG_CTL_FAIL, + imsg->hdr.peerid, 0, -1, NULL, 0); + return; + + case IMSG_LKA_PROCESSOR_FORK: + m_msg(&m, imsg); + m_get_string(&m, &procname); + m_get_u32(&m, &subsystems); + m_end(&m); + + m_create(p, IMSG_LKA_PROCESSOR_ERRFD, 0, 0, -1); + m_add_string(p, procname); + m_close(p); + + lka_proc_forked(procname, subsystems, imsg->fd); + return; + + case IMSG_LKA_PROCESSOR_ERRFD: + m_msg(&m, imsg); + m_get_string(&m, &procname); + m_end(&m); + + lka_proc_errfd(procname, imsg->fd); + shutdown(imsg->fd, SHUT_WR); + return; + + case IMSG_REPORT_SMTP_LINK_CONNECT: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_string(&m, &rdns); + m_get_int(&m, &fcrdns); + m_get_sockaddr(&m, (struct sockaddr *)&ss_src); + m_get_sockaddr(&m, (struct sockaddr *)&ss_dest); + m_end(&m); + + lka_report_smtp_link_connect(direction, &tv, reqid, rdns, fcrdns, &ss_src, &ss_dest); + return; + + case IMSG_REPORT_SMTP_LINK_GREETING: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_string(&m, &domain); + m_end(&m); + + lka_report_smtp_link_greeting(direction, reqid, &tv, domain); + return; + + case IMSG_REPORT_SMTP_LINK_DISCONNECT: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_end(&m); + + lka_report_smtp_link_disconnect(direction, &tv, reqid); + return; + + case IMSG_REPORT_SMTP_LINK_IDENTIFY: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_string(&m, &helomethod); + m_get_string(&m, &heloname); + m_end(&m); + + lka_report_smtp_link_identify(direction, &tv, reqid, helomethod, heloname); + return; + + case IMSG_REPORT_SMTP_LINK_TLS: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_string(&m, &ciphers); + m_end(&m); + + lka_report_smtp_link_tls(direction, &tv, reqid, ciphers); + return; + + case IMSG_REPORT_SMTP_LINK_AUTH: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_string(&m, &username); + m_get_string(&m, &result); + m_end(&m); + + lka_report_smtp_link_auth(direction, &tv, reqid, username, result); + return; + + case IMSG_REPORT_SMTP_TX_RESET: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_u32(&m, &msgid); + m_end(&m); + + lka_report_smtp_tx_reset(direction, &tv, reqid, msgid); + return; + + case IMSG_REPORT_SMTP_TX_BEGIN: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_u32(&m, &msgid); + m_end(&m); + + lka_report_smtp_tx_begin(direction, &tv, reqid, msgid); + return; + + case IMSG_REPORT_SMTP_TX_MAIL: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_u32(&m, &msgid); + m_get_string(&m, &address); + m_get_int(&m, &ok); + m_end(&m); + + lka_report_smtp_tx_mail(direction, &tv, reqid, msgid, address, ok); + return; + + case IMSG_REPORT_SMTP_TX_RCPT: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_u32(&m, &msgid); + m_get_string(&m, &address); + m_get_int(&m, &ok); + m_end(&m); + + lka_report_smtp_tx_rcpt(direction, &tv, reqid, msgid, address, ok); + return; + + case IMSG_REPORT_SMTP_TX_ENVELOPE: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_u32(&m, &msgid); + m_get_id(&m, &evpid); + m_end(&m); + + lka_report_smtp_tx_envelope(direction, &tv, reqid, msgid, evpid); + return; + + case IMSG_REPORT_SMTP_TX_DATA: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_u32(&m, &msgid); + m_get_int(&m, &ok); + m_end(&m); + + lka_report_smtp_tx_data(direction, &tv, reqid, msgid, ok); + return; + + case IMSG_REPORT_SMTP_TX_COMMIT: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_u32(&m, &msgid); + m_get_size(&m, &msgsz); + m_end(&m); + + lka_report_smtp_tx_commit(direction, &tv, reqid, msgid, msgsz); + return; + + case IMSG_REPORT_SMTP_TX_ROLLBACK: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_u32(&m, &msgid); + m_end(&m); + + lka_report_smtp_tx_rollback(direction, &tv, reqid, msgid); + return; + + case IMSG_REPORT_SMTP_PROTOCOL_CLIENT: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_string(&m, &command); + m_end(&m); + + lka_report_smtp_protocol_client(direction, &tv, reqid, command); + return; + + case IMSG_REPORT_SMTP_PROTOCOL_SERVER: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_string(&m, &response); + m_end(&m); + + lka_report_smtp_protocol_server(direction, &tv, reqid, response); + return; + + case IMSG_REPORT_SMTP_FILTER_RESPONSE: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_get_int(&m, &filter_phase); + m_get_int(&m, &filter_response); + m_get_string(&m, &filter_param); + m_end(&m); + + lka_report_smtp_filter_response(direction, &tv, reqid, + filter_phase, filter_response, filter_param); + return; + + case IMSG_REPORT_SMTP_TIMEOUT: + m_msg(&m, imsg); + m_get_string(&m, &direction); + m_get_timeval(&m, &tv); + m_get_id(&m, &reqid); + m_end(&m); + + lka_report_smtp_timeout(direction, &tv, reqid); + return; + + case IMSG_FILTER_SMTP_PROTOCOL: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &filter_phase); + m_get_string(&m, &filter_param); + m_end(&m); + + lka_filter_protocol(reqid, filter_phase, filter_param); + return; + + case IMSG_FILTER_SMTP_BEGIN: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_string(&m, &filter_name); + m_end(&m); + + lka_filter_begin(reqid, filter_name); + return; + + case IMSG_FILTER_SMTP_END: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_end(&m); + + lka_filter_end(reqid); + return; + + case IMSG_FILTER_SMTP_DATA_BEGIN: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_end(&m); + + lka_filter_data_begin(reqid); + return; + + case IMSG_FILTER_SMTP_DATA_END: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_end(&m); + + lka_filter_data_end(reqid); + return; + + } + + errx(1, "lka_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); +} + +static void +lka_sig_handler(int sig, short event, void *p) +{ + int status; + pid_t pid; + + switch (sig) { + case SIGCHLD: + do { + pid = waitpid(-1, &status, WNOHANG); + } while (pid > 0 || (pid == -1 && errno == EINTR)); + break; + default: + fatalx("lka_sig_handler: unexpected signal"); + } +} + +void +lka_shutdown(void) +{ + log_debug("debug: lookup agent exiting"); + _exit(0); +} + +int +lka(void) +{ + struct passwd *pw; + struct event ev_sigchld; + + purge_config(PURGE_LISTENERS); + + if ((pw = getpwnam(SMTPD_USER)) == NULL) + fatalx("unknown user " SMTPD_USER); + + config_process(PROC_LKA); + + if (initgroups(pw->pw_name, pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("lka: cannot drop privileges"); + + imsg_callback = lka_imsg; + event_init(); + + signal_set(&ev_sigchld, SIGCHLD, lka_sig_handler, NULL); + signal_add(&ev_sigchld, NULL); + signal(SIGINT, SIG_IGN); + signal(SIGTERM, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + + config_peer(PROC_PARENT); + config_peer(PROC_QUEUE); + config_peer(PROC_CONTROL); + config_peer(PROC_PONY); + + /* Ignore them until we get our config */ + mproc_disable(p_pony); + + lka_report_init(); + lka_filter_init(); + +#if HAVE_PLEDGE + /* proc & exec will be revoked before serving requests */ + if (pledge("stdio rpath inet dns getpw recvfd sendfd proc exec", NULL) == -1) + err(1, "pledge"); +#endif + + event_dispatch(); + fatalx("exited event loop"); + + return (0); +} + +static void +proc_timeout(int fd, short event, void *p) +{ + struct event *ev = p; + struct timeval tv; + + if (!lka_proc_ready()) + goto reset; + + lka_filter_ready(); + mproc_enable(p_pony); + return; + +reset: + tv.tv_sec = 0; + tv.tv_usec = 10; + evtimer_add(ev, &tv); +} + + +static int +lka_authenticate(const char *tablename, const char *user, const char *password) +{ + struct table *table; + union lookup lk; + + log_debug("debug: lka: authenticating for %s:%s", tablename, user); + table = table_find(env, tablename); + if (table == NULL) { + log_warnx("warn: could not find table %s needed for authentication", + tablename); + return (LKA_TEMPFAIL); + } + + switch (table_lookup(table, K_CREDENTIALS, user, &lk)) { + case -1: + log_warnx("warn: user credentials lookup fail for %s:%s", + tablename, user); + return (LKA_TEMPFAIL); + case 0: + return (LKA_PERMFAIL); + default: + if (crypt_checkpass(password, lk.creds.password) == 0) + return (LKA_OK); + return (LKA_PERMFAIL); + } +} + +static int +lka_credentials(const char *tablename, const char *label, char *dst, size_t sz) +{ + struct table *table; + union lookup lk; + char *buf; + int buflen, r; + + table = table_find(env, tablename); + if (table == NULL) { + log_warnx("warn: credentials table %s missing", tablename); + return (LKA_TEMPFAIL); + } + + dst[0] = '\0'; + + switch (table_lookup(table, K_CREDENTIALS, label, &lk)) { + case -1: + log_warnx("warn: credentials lookup fail for %s:%s", + tablename, label); + return (LKA_TEMPFAIL); + case 0: + log_warnx("warn: credentials not found for %s:%s", + tablename, label); + return (LKA_PERMFAIL); + default: + if ((buflen = asprintf(&buf, "%c%s%c%s", '\0', + lk.creds.username, '\0', lk.creds.password)) == -1) { + log_warn("warn"); + return (LKA_TEMPFAIL); + } + + r = base64_encode((unsigned char *)buf, buflen, dst, sz); + free(buf); + + if (r == -1) { + log_warnx("warn: credentials parse error for %s:%s", + tablename, label); + return (LKA_TEMPFAIL); + } + return (LKA_OK); + } +} + +static int +lka_userinfo(const char *tablename, const char *username, struct userinfo *res) +{ + struct table *table; + union lookup lk; + + log_debug("debug: lka: userinfo %s:%s", tablename, username); + table = table_find(env, tablename); + if (table == NULL) { + log_warnx("warn: cannot find user table %s", tablename); + return (LKA_TEMPFAIL); + } + + switch (table_lookup(table, K_USERINFO, username, &lk)) { + case -1: + log_warnx("warn: failure during userinfo lookup %s:%s", + tablename, username); + return (LKA_TEMPFAIL); + case 0: + return (LKA_PERMFAIL); + default: + *res = lk.userinfo; + return (LKA_OK); + } +} + +static int +lka_addrname(const char *tablename, const struct sockaddr *sa, + struct addrname *res) +{ + struct table *table; + union lookup lk; + const char *source; + + source = sa_to_text(sa); + + log_debug("debug: lka: helo %s:%s", tablename, source); + table = table_find(env, tablename); + if (table == NULL) { + log_warnx("warn: cannot find helo table %s", tablename); + return (LKA_TEMPFAIL); + } + + switch (table_lookup(table, K_ADDRNAME, source, &lk)) { + case -1: + log_warnx("warn: failure during helo lookup %s:%s", + tablename, source); + return (LKA_TEMPFAIL); + case 0: + return (LKA_PERMFAIL); + default: + *res = lk.addrname; + return (LKA_OK); + } +} + +static int +lka_mailaddrmap(const char *tablename, const char *username, const struct mailaddr *maddr) +{ + struct table *table; + struct maddrnode *mn; + union lookup lk; + int found; + + log_debug("debug: lka: mailaddrmap %s:%s", tablename, username); + table = table_find(env, tablename); + if (table == NULL) { + log_warnx("warn: cannot find mailaddrmap table %s", tablename); + return (LKA_TEMPFAIL); + } + + switch (table_lookup(table, K_MAILADDRMAP, username, &lk)) { + case -1: + log_warnx("warn: failure during mailaddrmap lookup %s:%s", + tablename, username); + return (LKA_TEMPFAIL); + case 0: + return (LKA_PERMFAIL); + default: + found = 0; + TAILQ_FOREACH(mn, &lk.maddrmap->queue, entries) { + if (!mailaddr_match(maddr, &mn->mailaddr)) + continue; + found = 1; + break; + } + maddrmap_free(lk.maddrmap); + if (found) + return (LKA_OK); + return (LKA_PERMFAIL); + } + return (LKA_OK); +} diff --git a/foobar/portable/smtpd/lka_filter.c b/foobar/portable/smtpd/lka_filter.c new file mode 100644 index 00000000..2dc66057 --- /dev/null +++ b/foobar/portable/smtpd/lka_filter.c @@ -0,0 +1,1746 @@ +/* $OpenBSD: lka_filter.c,v 1.62 2020/04/24 11:34:07 eric Exp $ */ + +/* + * Copyright (c) 2018 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> + +#include <netinet/in.h> + +#include <errno.h> +#include <event.h> +#include <imsg.h> +#include <inttypes.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "smtpd.h" +#include "log.h" + +#define PROTOCOL_VERSION "0.6" + +struct filter; +struct filter_session; +static void filter_protocol_internal(struct filter_session *, uint64_t *, uint64_t, enum filter_phase, const char *); +static void filter_protocol(uint64_t, enum filter_phase, const char *); +static void filter_protocol_next(uint64_t, uint64_t, enum filter_phase); +static void filter_protocol_query(struct filter *, uint64_t, uint64_t, const char *, const char *); + +static void filter_data_internal(struct filter_session *, uint64_t, uint64_t, const char *); +static void filter_data(uint64_t, const char *); +static void filter_data_next(uint64_t, uint64_t, const char *); +static void filter_data_query(struct filter *, uint64_t, uint64_t, const char *); + +static int filter_builtins_notimpl(struct filter_session *, struct filter *, uint64_t, const char *); +static int filter_builtins_connect(struct filter_session *, struct filter *, uint64_t, const char *); +static int filter_builtins_helo(struct filter_session *, struct filter *, uint64_t, const char *); +static int filter_builtins_mail_from(struct filter_session *, struct filter *, uint64_t, const char *); +static int filter_builtins_rcpt_to(struct filter_session *, struct filter *, uint64_t, const char *); +static int filter_builtins_data(struct filter_session *, struct filter *, uint64_t, const char *); +static int filter_builtins_commit(struct filter_session *, struct filter *, uint64_t, const char *); + +static void filter_result_proceed(uint64_t); +static void filter_result_junk(uint64_t); +static void filter_result_rewrite(uint64_t, const char *); +static void filter_result_reject(uint64_t, const char *); +static void filter_result_disconnect(uint64_t, const char *); + +static void filter_session_io(struct io *, int, void *); +void lka_filter_process_response(const char *, const char *); + + +struct filter_session { + uint64_t id; + struct io *io; + + char *lastparam; + + char *filter_name; + struct sockaddr_storage ss_src; + struct sockaddr_storage ss_dest; + char *rdns; + int fcrdns; + + char *helo; + char *username; + char *mail_from; + + enum filter_phase phase; +}; + +static struct filter_exec { + enum filter_phase phase; + const char *phase_name; + int (*func)(struct filter_session *, struct filter *, uint64_t, const char *); +} filter_execs[FILTER_PHASES_COUNT] = { + { FILTER_CONNECT, "connect", filter_builtins_connect }, + { FILTER_HELO, "helo", filter_builtins_helo }, + { FILTER_EHLO, "ehlo", filter_builtins_helo }, + { FILTER_STARTTLS, "starttls", filter_builtins_notimpl }, + { FILTER_AUTH, "auth", filter_builtins_notimpl }, + { FILTER_MAIL_FROM, "mail-from", filter_builtins_mail_from }, + { FILTER_RCPT_TO, "rcpt-to", filter_builtins_rcpt_to }, + { FILTER_DATA, "data", filter_builtins_data }, + { FILTER_DATA_LINE, "data-line", filter_builtins_notimpl }, + { FILTER_RSET, "rset", filter_builtins_notimpl }, + { FILTER_QUIT, "quit", filter_builtins_notimpl }, + { FILTER_NOOP, "noop", filter_builtins_notimpl }, + { FILTER_HELP, "help", filter_builtins_notimpl }, + { FILTER_WIZ, "wiz", filter_builtins_notimpl }, + { FILTER_COMMIT, "commit", filter_builtins_commit }, +}; + +struct filter { + uint64_t id; + uint32_t phases; + const char *name; + const char *proc; + struct filter **chain; + size_t chain_size; + struct filter_config *config; +}; +static struct dict filters; + +struct filter_entry { + TAILQ_ENTRY(filter_entry) entries; + uint64_t id; + const char *name; +}; + +struct filter_chain { + TAILQ_HEAD(, filter_entry) chain[nitems(filter_execs)]; +}; + +static struct dict filter_smtp_in; + +static struct tree sessions; +static int filters_inited; + +static struct dict filter_chains; + +struct reporter_proc { + TAILQ_ENTRY(reporter_proc) entries; + const char *name; +}; +TAILQ_HEAD(reporters, reporter_proc); + +static struct dict report_smtp_in; +static struct dict report_smtp_out; + +static struct smtp_events { + const char *event; +} smtp_events[] = { + { "link-connect" }, + { "link-disconnect" }, + { "link-greeting" }, + { "link-identify" }, + { "link-tls" }, + { "link-auth" }, + + { "tx-reset" }, + { "tx-begin" }, + { "tx-mail" }, + { "tx-rcpt" }, + { "tx-envelope" }, + { "tx-data" }, + { "tx-commit" }, + { "tx-rollback" }, + + { "protocol-client" }, + { "protocol-server" }, + + { "filter-report" }, + { "filter-response" }, + + { "timeout" }, +}; + +static int processors_inited = 0; +static struct dict processors; + +struct processor_instance { + char *name; + struct io *io; + struct io *errfd; + int ready; + uint32_t subsystems; +}; + +static void processor_io(struct io *, int, void *); +static void processor_errfd(struct io *, int, void *); +void lka_filter_process_response(const char *, const char *); + +int +lka_proc_ready(void) +{ + void *iter; + struct processor_instance *pi; + + iter = NULL; + while (dict_iter(&processors, &iter, NULL, (void **)&pi)) + if (!pi->ready) + return 0; + return 1; +} + +static void +lka_proc_config(struct processor_instance *pi) +{ + io_printf(pi->io, "config|smtpd-version|%s\n", SMTPD_VERSION); + io_printf(pi->io, "config|smtp-session-timeout|%d\n", SMTPD_SESSION_TIMEOUT); + if (pi->subsystems & FILTER_SUBSYSTEM_SMTP_IN) + io_printf(pi->io, "config|subsystem|smtp-in\n"); + if (pi->subsystems & FILTER_SUBSYSTEM_SMTP_OUT) + io_printf(pi->io, "config|subsystem|smtp-out\n"); + io_printf(pi->io, "config|ready\n"); +} + +void +lka_proc_forked(const char *name, uint32_t subsystems, int fd) +{ + struct processor_instance *processor; + + if (!processors_inited) { + dict_init(&processors); + processors_inited = 1; + } + + processor = xcalloc(1, sizeof *processor); + processor->name = xstrdup(name); + processor->io = io_new(); + processor->subsystems = subsystems; + + io_set_nonblocking(fd); + + io_set_fd(processor->io, fd); + io_set_callback(processor->io, processor_io, processor->name); + dict_xset(&processors, name, processor); +} + +void +lka_proc_errfd(const char *name, int fd) +{ + struct processor_instance *processor; + + processor = dict_xget(&processors, name); + + io_set_nonblocking(fd); + + processor->errfd = io_new(); + io_set_fd(processor->errfd, fd); + io_set_callback(processor->errfd, processor_errfd, processor->name); + + lka_proc_config(processor); +} + +struct io * +lka_proc_get_io(const char *name) +{ + struct processor_instance *processor; + + processor = dict_xget(&processors, name); + + return processor->io; +} + +static void +processor_register(const char *name, const char *line) +{ + struct processor_instance *processor; + + processor = dict_xget(&processors, name); + + if (strcmp(line, "register|ready") == 0) { + processor->ready = 1; + return; + } + + if (strncmp(line, "register|report|", 16) == 0) { + lka_report_register_hook(name, line+16); + return; + } + + if (strncmp(line, "register|filter|", 16) == 0) { + lka_filter_register_hook(name, line+16); + return; + } + + fatalx("Invalid register line received: %s", line); +} + +static void +processor_io(struct io *io, int evt, void *arg) +{ + struct processor_instance *processor; + const char *name = arg; + char *line = NULL; + ssize_t len; + + switch (evt) { + case IO_DATAIN: + while ((line = io_getline(io, &len)) != NULL) { + if (strncmp("register|", line, 9) == 0) { + processor_register(name, line); + continue; + } + + processor = dict_xget(&processors, name); + if (!processor->ready) + fatalx("Non-register message before register|" + "ready: %s", line); + else if (strncmp(line, "filter-result|", 14) == 0 || + strncmp(line, "filter-dataline|", 16) == 0) + lka_filter_process_response(name, line); + else if (strncmp(line, "report|", 7) == 0) + lka_report_proc(name, line); + else + fatalx("Invalid filter message type: %s", line); + } + } +} + +static void +processor_errfd(struct io *io, int evt, void *arg) +{ + const char *name = arg; + char *line = NULL; + ssize_t len; + + switch (evt) { + case IO_DATAIN: + while ((line = io_getline(io, &len)) != NULL) + log_warnx("%s: %s", name, line); + } +} + +void +lka_filter_init(void) +{ + void *iter; + const char *name; + struct filter *filter; + struct filter_config *filter_config; + size_t i; + char buffer[LINE_MAX]; /* for traces */ + + dict_init(&filters); + dict_init(&filter_chains); + + /* first pass, allocate and init individual filters */ + iter = NULL; + while (dict_iter(env->sc_filters_dict, &iter, &name, (void **)&filter_config)) { + switch (filter_config->filter_type) { + case FILTER_TYPE_BUILTIN: + filter = xcalloc(1, sizeof(*filter)); + filter->name = name; + filter->phases |= (1<<filter_config->phase); + filter->config = filter_config; + dict_set(&filters, name, filter); + log_trace(TRACE_FILTERS, "filters init type=builtin, name=%s, hooks=%08x", + name, filter->phases); + break; + + case FILTER_TYPE_PROC: + filter = xcalloc(1, sizeof(*filter)); + filter->name = name; + filter->proc = filter_config->proc; + filter->config = filter_config; + dict_set(&filters, name, filter); + log_trace(TRACE_FILTERS, "filters init type=proc, name=%s, proc=%s", + name, filter_config->proc); + break; + + case FILTER_TYPE_CHAIN: + break; + } + } + + /* second pass, allocate and init filter chains but don't build yet */ + iter = NULL; + while (dict_iter(env->sc_filters_dict, &iter, &name, (void **)&filter_config)) { + switch (filter_config->filter_type) { + case FILTER_TYPE_CHAIN: + filter = xcalloc(1, sizeof(*filter)); + filter->name = name; + filter->chain = xcalloc(filter_config->chain_size, sizeof(void **)); + filter->chain_size = filter_config->chain_size; + filter->config = filter_config; + + buffer[0] = '\0'; + for (i = 0; i < filter->chain_size; ++i) { + filter->chain[i] = dict_xget(&filters, filter_config->chain[i]); + if (i) + (void)strlcat(buffer, ", ", sizeof buffer); + (void)strlcat(buffer, filter->chain[i]->name, sizeof buffer); + } + log_trace(TRACE_FILTERS, "filters init type=chain, name=%s { %s }", name, buffer); + + dict_set(&filters, name, filter); + break; + + case FILTER_TYPE_BUILTIN: + case FILTER_TYPE_PROC: + break; + } + } +} + +void +lka_filter_register_hook(const char *name, const char *hook) +{ + struct dict *subsystem; + struct filter *filter; + const char *filter_name; + void *iter; + size_t i; + + if (strncasecmp(hook, "smtp-in|", 8) == 0) { + subsystem = &filter_smtp_in; + hook += 8; + } + else + fatalx("Invalid message direction: %s", hook); + + for (i = 0; i < nitems(filter_execs); i++) + if (strcmp(hook, filter_execs[i].phase_name) == 0) + break; + if (i == nitems(filter_execs)) + fatalx("Unrecognized report name: %s", hook); + + iter = NULL; + while (dict_iter(&filters, &iter, &filter_name, (void **)&filter)) + if (filter->proc && strcmp(name, filter->proc) == 0) + filter->phases |= (1<<filter_execs[i].phase); +} + +void +lka_filter_ready(void) +{ + struct filter *filter; + struct filter *subfilter; + const char *filter_name; + struct filter_entry *filter_entry; + struct filter_chain *filter_chain; + void *iter; + size_t i; + size_t j; + + /* all filters are ready, actually build the filter chains */ + iter = NULL; + while (dict_iter(&filters, &iter, &filter_name, (void **)&filter)) { + filter_chain = xcalloc(1, sizeof *filter_chain); + for (i = 0; i < nitems(filter_execs); i++) + TAILQ_INIT(&filter_chain->chain[i]); + dict_set(&filter_chains, filter_name, filter_chain); + + if (filter->chain) { + for (i = 0; i < filter->chain_size; i++) { + subfilter = filter->chain[i]; + for (j = 0; j < nitems(filter_execs); ++j) { + if (subfilter->phases & (1<<j)) { + filter_entry = xcalloc(1, sizeof *filter_entry); + filter_entry->id = generate_uid(); + filter_entry->name = subfilter->name; + TAILQ_INSERT_TAIL(&filter_chain->chain[j], + filter_entry, entries); + } + } + } + continue; + } + + for (i = 0; i < nitems(filter_execs); ++i) { + if (filter->phases & (1<<i)) { + filter_entry = xcalloc(1, sizeof *filter_entry); + filter_entry->id = generate_uid(); + filter_entry->name = filter_name; + TAILQ_INSERT_TAIL(&filter_chain->chain[i], + filter_entry, entries); + } + } + } +} + +int +lka_filter_proc_in_session(uint64_t reqid, const char *proc) +{ + struct filter_session *fs; + struct filter *filter; + size_t i; + + if ((fs = tree_get(&sessions, reqid)) == NULL) + return 0; + + filter = dict_get(&filters, fs->filter_name); + if (filter == NULL || (filter->proc == NULL && filter->chain == NULL)) + return 0; + + if (filter->proc) + return strcmp(filter->proc, proc) == 0 ? 1 : 0; + + for (i = 0; i < filter->chain_size; i++) + if (filter->chain[i]->proc && + strcmp(filter->chain[i]->proc, proc) == 0) + return 1; + + return 0; +} + +void +lka_filter_begin(uint64_t reqid, const char *filter_name) +{ + struct filter_session *fs; + + if (!filters_inited) { + tree_init(&sessions); + filters_inited = 1; + } + + fs = xcalloc(1, sizeof (struct filter_session)); + fs->id = reqid; + fs->filter_name = xstrdup(filter_name); + tree_xset(&sessions, fs->id, fs); + + log_trace(TRACE_FILTERS, "%016"PRIx64" filters session-begin", reqid); +} + +void +lka_filter_end(uint64_t reqid) +{ + struct filter_session *fs; + + fs = tree_xpop(&sessions, reqid); + free(fs->rdns); + free(fs->helo); + free(fs->mail_from); + free(fs->username); + free(fs->lastparam); + free(fs); + log_trace(TRACE_FILTERS, "%016"PRIx64" filters session-end", reqid); +} + +void +lka_filter_data_begin(uint64_t reqid) +{ + struct filter_session *fs; + int sp[2]; + int fd = -1; + + fs = tree_xget(&sessions, reqid); + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1) + goto end; + io_set_nonblocking(sp[0]); + io_set_nonblocking(sp[1]); + fd = sp[0]; + fs->io = io_new(); + io_set_fd(fs->io, sp[1]); + io_set_callback(fs->io, filter_session_io, fs); + +end: + m_create(p_pony, IMSG_FILTER_SMTP_DATA_BEGIN, 0, 0, fd); + m_add_id(p_pony, reqid); + m_add_int(p_pony, fd != -1 ? 1 : 0); + m_close(p_pony); + log_trace(TRACE_FILTERS, "%016"PRIx64" filters data-begin fd=%d", reqid, fd); +} + +void +lka_filter_data_end(uint64_t reqid) +{ + struct filter_session *fs; + + fs = tree_xget(&sessions, reqid); + if (fs->io) { + io_free(fs->io); + fs->io = NULL; + } + log_trace(TRACE_FILTERS, "%016"PRIx64" filters data-end", reqid); +} + +static void +filter_session_io(struct io *io, int evt, void *arg) +{ + struct filter_session *fs = arg; + char *line = NULL; + ssize_t len; + + log_trace(TRACE_IO, "filter session: %p: %s %s", fs, io_strevent(evt), + io_strio(io)); + + switch (evt) { + case IO_DATAIN: + nextline: + line = io_getline(fs->io, &len); + /* No complete line received */ + if (line == NULL) + return; + + filter_data(fs->id, line); + + goto nextline; + + case IO_DISCONNECTED: + io_free(fs->io); + fs->io = NULL; + break; + } +} + +void +lka_filter_process_response(const char *name, const char *line) +{ + uint64_t reqid; + uint64_t token; + char buffer[LINE_MAX]; + char *ep = NULL; + char *kind = NULL; + char *qid = NULL; + /*char *phase = NULL;*/ + char *response = NULL; + char *parameter = NULL; + struct filter_session *fs; + + (void)strlcpy(buffer, line, sizeof buffer); + if ((ep = strchr(buffer, '|')) == NULL) + fatalx("Missing token: %s", line); + ep[0] = '\0'; + + kind = buffer; + + qid = ep+1; + if ((ep = strchr(qid, '|')) == NULL) + fatalx("Missing reqid: %s", line); + ep[0] = '\0'; + + reqid = strtoull(qid, &ep, 16); + if (qid[0] == '\0' || *ep != '\0') + fatalx("Invalid reqid: %s", line); + if (errno == ERANGE && reqid == ULLONG_MAX) + fatal("Invalid reqid: %s", line); + + qid = ep+1; + if ((ep = strchr(qid, '|')) == NULL) + fatal("Missing directive: %s", line); + ep[0] = '\0'; + + token = strtoull(qid, &ep, 16); + if (qid[0] == '\0' || *ep != '\0') + fatalx("Invalid token: %s", line); + if (errno == ERANGE && token == ULLONG_MAX) + fatal("Invalid token: %s", line); + + response = ep+1; + + /* session can legitimately disappear on a resume */ + if ((fs = tree_get(&sessions, reqid)) == NULL) + return; + + if (strcmp(kind, "filter-dataline") == 0) { + if (fs->phase != FILTER_DATA_LINE) + fatalx("filter-dataline out of dataline phase"); + filter_data_next(token, reqid, response); + return; + } + if (fs->phase == FILTER_DATA_LINE) + fatalx("filter-result in dataline phase"); + + if ((ep = strchr(response, '|'))) { + parameter = ep + 1; + ep[0] = '\0'; + } + + if (strcmp(response, "proceed") == 0) { + if (parameter != NULL) + fatalx("Unexpected parameter after proceed: %s", line); + filter_protocol_next(token, reqid, 0); + return; + } else if (strcmp(response, "junk") == 0) { + if (parameter != NULL) + fatalx("Unexpected parameter after junk: %s", line); + if (fs->phase == FILTER_COMMIT) + fatalx("filter-reponse junk after DATA"); + filter_result_junk(reqid); + return; + } else { + if (parameter == NULL) + fatalx("Missing parameter: %s", line); + + if (strcmp(response, "rewrite") == 0) + filter_result_rewrite(reqid, parameter); + else if (strcmp(response, "reject") == 0) + filter_result_reject(reqid, parameter); + else if (strcmp(response, "disconnect") == 0) + filter_result_disconnect(reqid, parameter); + else + fatalx("Invalid directive: %s", line); + } +} + +void +lka_filter_protocol(uint64_t reqid, enum filter_phase phase, const char *param) +{ + filter_protocol(reqid, phase, param); +} + +static void +filter_protocol_internal(struct filter_session *fs, uint64_t *token, uint64_t reqid, enum filter_phase phase, const char *param) +{ + struct filter_chain *filter_chain; + struct filter_entry *filter_entry; + struct filter *filter; + struct timeval tv; + const char *phase_name = filter_execs[phase].phase_name; + int resume = 1; + + if (!*token) { + fs->phase = phase; + resume = 0; + } + + /* XXX - this sanity check requires a protocol change, stub for now */ + phase = fs->phase; + if (fs->phase != phase) + fatalx("misbehaving filter"); + + /* based on token, identify the filter_entry we should apply */ + filter_chain = dict_get(&filter_chains, fs->filter_name); + filter_entry = TAILQ_FIRST(&filter_chain->chain[fs->phase]); + if (*token) { + TAILQ_FOREACH(filter_entry, &filter_chain->chain[fs->phase], entries) + if (filter_entry->id == *token) + break; + if (filter_entry == NULL) + fatalx("misbehaving filter"); + filter_entry = TAILQ_NEXT(filter_entry, entries); + } + + /* no filter_entry, we either had none or reached end of chain */ + if (filter_entry == NULL) { + log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, resume=%s, " + "action=proceed", + fs->id, phase_name, resume ? "y" : "n"); + filter_result_proceed(reqid); + return; + } + + /* process param with current filter_entry */ + *token = filter_entry->id; + filter = dict_get(&filters, filter_entry->name); + if (filter->proc) { + log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, " + "resume=%s, action=deferred, filter=%s", + fs->id, phase_name, resume ? "y" : "n", + filter->name); + filter_protocol_query(filter, filter_entry->id, reqid, + filter_execs[fs->phase].phase_name, param); + return; /* deferred response */ + } + + if (filter_execs[fs->phase].func(fs, filter, reqid, param)) { + if (filter->config->rewrite) { + log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, " + "resume=%s, action=rewrite, filter=%s, query=%s, response=%s", + fs->id, phase_name, resume ? "y" : "n", + filter->name, + param, + filter->config->rewrite); + filter_result_rewrite(reqid, filter->config->rewrite); + return; + } + else if (filter->config->disconnect) { + log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, " + "resume=%s, action=disconnect, filter=%s, query=%s, response=%s", + fs->id, phase_name, resume ? "y" : "n", + filter->name, + param, + filter->config->disconnect); + filter_result_disconnect(reqid, filter->config->disconnect); + return; + } + else if (filter->config->junk) { + log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, " + "resume=%s, action=junk, filter=%s, query=%s", + fs->id, phase_name, resume ? "y" : "n", + filter->name, + param); + filter_result_junk(reqid); + return; + } else if (filter->config->report) { + log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, " + "resume=%s, action=report, filter=%s, query=%s response=%s", + fs->id, phase_name, resume ? "y" : "n", + filter->name, + param, filter->config->report); + + gettimeofday(&tv, NULL); + lka_report_filter_report(fs->id, filter->name, 1, + "smtp-in", &tv, filter->config->report); + } else if (filter->config->bypass) { + log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, " + "resume=%s, action=bypass, filter=%s, query=%s", + fs->id, phase_name, resume ? "y" : "n", + filter->name, + param); + filter_result_proceed(reqid); + return; + } else { + log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, " + "resume=%s, action=reject, filter=%s, query=%s, response=%s", + fs->id, phase_name, resume ? "y" : "n", + filter->name, + param, + filter->config->reject); + filter_result_reject(reqid, filter->config->reject); + return; + } + } + + log_trace(TRACE_FILTERS, "%016"PRIx64" filters protocol phase=%s, " + "resume=%s, action=proceed, filter=%s, query=%s", + fs->id, phase_name, resume ? "y" : "n", + filter->name, + param); + + /* filter_entry resulted in proceed, try next filter */ + filter_protocol_internal(fs, token, reqid, phase, param); + return; +} + +static void +filter_data_internal(struct filter_session *fs, uint64_t token, uint64_t reqid, const char *line) +{ + struct filter_chain *filter_chain; + struct filter_entry *filter_entry; + struct filter *filter; + + if (!token) + fs->phase = FILTER_DATA_LINE; + if (fs->phase != FILTER_DATA_LINE) + fatalx("misbehaving filter"); + + /* based on token, identify the filter_entry we should apply */ + filter_chain = dict_get(&filter_chains, fs->filter_name); + filter_entry = TAILQ_FIRST(&filter_chain->chain[fs->phase]); + if (token) { + TAILQ_FOREACH(filter_entry, &filter_chain->chain[fs->phase], entries) + if (filter_entry->id == token) + break; + if (filter_entry == NULL) + fatalx("misbehaving filter"); + filter_entry = TAILQ_NEXT(filter_entry, entries); + } + + /* no filter_entry, we either had none or reached end of chain */ + if (filter_entry == NULL) { + io_printf(fs->io, "%s\n", line); + return; + } + + /* pass data to the filter */ + filter = dict_get(&filters, filter_entry->name); + filter_data_query(filter, filter_entry->id, reqid, line); +} + +static void +filter_protocol(uint64_t reqid, enum filter_phase phase, const char *param) +{ + struct filter_session *fs; + uint64_t token = 0; + char *nparam = NULL; + + fs = tree_xget(&sessions, reqid); + + switch (phase) { + case FILTER_HELO: + case FILTER_EHLO: + free(fs->helo); + fs->helo = xstrdup(param); + break; + case FILTER_MAIL_FROM: + free(fs->mail_from); + fs->mail_from = xstrdup(param + 1); + *strchr(fs->mail_from, '>') = '\0'; + param = fs->mail_from; + + break; + case FILTER_RCPT_TO: + nparam = xstrdup(param + 1); + *strchr(nparam, '>') = '\0'; + param = nparam; + break; + case FILTER_STARTTLS: + /* TBD */ + break; + default: + break; + } + + free(fs->lastparam); + fs->lastparam = xstrdup(param); + + filter_protocol_internal(fs, &token, reqid, phase, param); + if (nparam) + free(nparam); +} + +static void +filter_protocol_next(uint64_t token, uint64_t reqid, enum filter_phase phase) +{ + struct filter_session *fs; + + /* session can legitimately disappear on a resume */ + if ((fs = tree_get(&sessions, reqid)) == NULL) + return; + + filter_protocol_internal(fs, &token, reqid, phase, fs->lastparam); +} + +static void +filter_data(uint64_t reqid, const char *line) +{ + struct filter_session *fs; + + fs = tree_xget(&sessions, reqid); + + filter_data_internal(fs, 0, reqid, line); +} + +static void +filter_data_next(uint64_t token, uint64_t reqid, const char *line) +{ + struct filter_session *fs; + + /* session can legitimately disappear on a resume */ + if ((fs = tree_get(&sessions, reqid)) == NULL) + return; + + filter_data_internal(fs, token, reqid, line); +} + +static void +filter_protocol_query(struct filter *filter, uint64_t token, uint64_t reqid, const char *phase, const char *param) +{ + int n; + struct filter_session *fs; + struct timeval tv; + + gettimeofday(&tv, NULL); + + fs = tree_xget(&sessions, reqid); + if (strcmp(phase, "connect") == 0) + n = io_printf(lka_proc_get_io(filter->proc), + "filter|%s|%lld.%06ld|smtp-in|%s|%016"PRIx64"|%016"PRIx64"|%s|%s\n", + PROTOCOL_VERSION, + (long long int)tv.tv_sec, tv.tv_usec, + phase, reqid, token, fs->rdns, param); + else + n = io_printf(lka_proc_get_io(filter->proc), + "filter|%s|%lld.%06ld|smtp-in|%s|%016"PRIx64"|%016"PRIx64"|%s\n", + PROTOCOL_VERSION, + (long long int)tv.tv_sec, tv.tv_usec, + phase, reqid, token, param); + if (n == -1) + fatalx("failed to write to processor"); +} + +static void +filter_data_query(struct filter *filter, uint64_t token, uint64_t reqid, const char *line) +{ + int n; + struct timeval tv; + + gettimeofday(&tv, NULL); + + n = io_printf(lka_proc_get_io(filter->proc), + "filter|%s|%lld.%06ld|smtp-in|data-line|" + "%016"PRIx64"|%016"PRIx64"|%s\n", + PROTOCOL_VERSION, + (long long int)tv.tv_sec, tv.tv_usec, + reqid, token, line); + if (n == -1) + fatalx("failed to write to processor"); +} + +static void +filter_result_proceed(uint64_t reqid) +{ + m_create(p_pony, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1); + m_add_id(p_pony, reqid); + m_add_int(p_pony, FILTER_PROCEED); + m_close(p_pony); +} + +static void +filter_result_junk(uint64_t reqid) +{ + m_create(p_pony, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1); + m_add_id(p_pony, reqid); + m_add_int(p_pony, FILTER_JUNK); + m_close(p_pony); +} + +static void +filter_result_rewrite(uint64_t reqid, const char *param) +{ + m_create(p_pony, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1); + m_add_id(p_pony, reqid); + m_add_int(p_pony, FILTER_REWRITE); + m_add_string(p_pony, param); + m_close(p_pony); +} + +static void +filter_result_reject(uint64_t reqid, const char *message) +{ + m_create(p_pony, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1); + m_add_id(p_pony, reqid); + m_add_int(p_pony, FILTER_REJECT); + m_add_string(p_pony, message); + m_close(p_pony); +} + +static void +filter_result_disconnect(uint64_t reqid, const char *message) +{ + m_create(p_pony, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1); + m_add_id(p_pony, reqid); + m_add_int(p_pony, FILTER_DISCONNECT); + m_add_string(p_pony, message); + m_close(p_pony); +} + + +/* below is code for builtin filters */ + +static int +filter_check_rdns_table(struct filter *filter, enum table_service kind, const char *key) +{ + int ret = 0; + + if (filter->config->rdns_table == NULL) + return 0; + + if (table_match(filter->config->rdns_table, kind, key) > 0) + ret = 1; + + return filter->config->not_rdns_table < 0 ? !ret : ret; +} + +static int +filter_check_rdns_regex(struct filter *filter, const char *key) +{ + int ret = 0; + + if (filter->config->rdns_regex == NULL) + return 0; + + if (table_match(filter->config->rdns_regex, K_REGEX, key) > 0) + ret = 1; + return filter->config->not_rdns_regex < 0 ? !ret : ret; +} + +static int +filter_check_src_table(struct filter *filter, enum table_service kind, const char *key) +{ + int ret = 0; + + if (filter->config->src_table == NULL) + return 0; + + if (table_match(filter->config->src_table, kind, key) > 0) + ret = 1; + return filter->config->not_src_table < 0 ? !ret : ret; +} + +static int +filter_check_src_regex(struct filter *filter, const char *key) +{ + int ret = 0; + + if (filter->config->src_regex == NULL) + return 0; + + if (table_match(filter->config->src_regex, K_REGEX, key) > 0) + ret = 1; + return filter->config->not_src_regex < 0 ? !ret : ret; +} + +static int +filter_check_helo_table(struct filter *filter, enum table_service kind, const char *key) +{ + int ret = 0; + + if (filter->config->helo_table == NULL) + return 0; + + if (table_match(filter->config->helo_table, kind, key) > 0) + ret = 1; + return filter->config->not_helo_table < 0 ? !ret : ret; +} + +static int +filter_check_helo_regex(struct filter *filter, const char *key) +{ + int ret = 0; + + if (filter->config->helo_regex == NULL) + return 0; + + if (table_match(filter->config->helo_regex, K_REGEX, key) > 0) + ret = 1; + return filter->config->not_helo_regex < 0 ? !ret : ret; +} + +static int +filter_check_auth(struct filter *filter, const char *username) +{ + int ret = 0; + + if (!filter->config->auth) + return 0; + + ret = username ? 1 : 0; + + return filter->config->not_auth < 0 ? !ret : ret; +} + +static int +filter_check_auth_table(struct filter *filter, enum table_service kind, const char *key) +{ + int ret = 0; + + if (filter->config->auth_table == NULL) + return 0; + + if (key && table_match(filter->config->auth_table, kind, key) > 0) + ret = 1; + + return filter->config->not_auth_table < 0 ? !ret : ret; +} + +static int +filter_check_auth_regex(struct filter *filter, const char *key) +{ + int ret = 0; + + if (filter->config->auth_regex == NULL) + return 0; + + if (key && table_match(filter->config->auth_regex, K_REGEX, key) > 0) + ret = 1; + return filter->config->not_auth_regex < 0 ? !ret : ret; +} + + +static int +filter_check_mail_from_table(struct filter *filter, enum table_service kind, const char *key) +{ + int ret = 0; + + if (filter->config->mail_from_table == NULL) + return 0; + + if (table_match(filter->config->mail_from_table, kind, key) > 0) + ret = 1; + return filter->config->not_mail_from_table < 0 ? !ret : ret; +} + +static int +filter_check_mail_from_regex(struct filter *filter, const char *key) +{ + int ret = 0; + + if (filter->config->mail_from_regex == NULL) + return 0; + + if (table_match(filter->config->mail_from_regex, K_REGEX, key) > 0) + ret = 1; + return filter->config->not_mail_from_regex < 0 ? !ret : ret; +} + +static int +filter_check_rcpt_to_table(struct filter *filter, enum table_service kind, const char *key) +{ + int ret = 0; + + if (filter->config->rcpt_to_table == NULL) + return 0; + + if (table_match(filter->config->rcpt_to_table, kind, key) > 0) + ret = 1; + return filter->config->not_rcpt_to_table < 0 ? !ret : ret; +} + +static int +filter_check_rcpt_to_regex(struct filter *filter, const char *key) +{ + int ret = 0; + + if (filter->config->rcpt_to_regex == NULL) + return 0; + + if (table_match(filter->config->rcpt_to_regex, K_REGEX, key) > 0) + ret = 1; + return filter->config->not_rcpt_to_regex < 0 ? !ret : ret; +} + +static int +filter_check_fcrdns(struct filter *filter, int fcrdns) +{ + int ret = 0; + + if (!filter->config->fcrdns) + return 0; + + ret = fcrdns == 1; + return filter->config->not_fcrdns < 0 ? !ret : ret; +} + +static int +filter_check_rdns(struct filter *filter, const char *hostname) +{ + int ret = 0; + struct netaddr netaddr; + + if (!filter->config->rdns) + return 0; + + /* this is a hack until smtp session properly deals with lack of rdns */ + ret = strcmp("<unknown>", hostname); + if (ret == 0) + return filter->config->not_rdns < 0 ? !ret : ret; + + /* if text_to_netaddress succeeds, + * we don't have an rDNS so the filter should match + */ + ret = !text_to_netaddr(&netaddr, hostname); + return filter->config->not_rdns < 0 ? !ret : ret; +} + +static int +filter_builtins_notimpl(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param) +{ + return 0; +} + +static int +filter_builtins_global(struct filter_session *fs, struct filter *filter, uint64_t reqid) +{ + return filter_check_fcrdns(filter, fs->fcrdns) || + filter_check_rdns(filter, fs->rdns) || + filter_check_rdns_table(filter, K_DOMAIN, fs->rdns) || + filter_check_rdns_regex(filter, fs->rdns) || + filter_check_src_table(filter, K_NETADDR, ss_to_text(&fs->ss_src)) || + filter_check_src_regex(filter, ss_to_text(&fs->ss_src)) || + filter_check_helo_table(filter, K_DOMAIN, fs->helo) || + filter_check_helo_regex(filter, fs->helo) || + filter_check_auth(filter, fs->username) || + filter_check_auth_table(filter, K_STRING, fs->username) || + filter_check_auth_table(filter, K_CREDENTIALS, fs->username) || + filter_check_auth_regex(filter, fs->username) || + filter_check_mail_from_table(filter, K_MAILADDR, fs->mail_from) || + filter_check_mail_from_regex(filter, fs->mail_from); +} + +static int +filter_builtins_connect(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param) +{ + return filter_builtins_global(fs, filter, reqid); +} + +static int +filter_builtins_helo(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param) +{ + return filter_builtins_global(fs, filter, reqid); +} + +static int +filter_builtins_mail_from(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param) +{ + return filter_builtins_global(fs, filter, reqid); +} + +static int +filter_builtins_rcpt_to(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param) +{ + return filter_builtins_global(fs, filter, reqid) || + filter_check_rcpt_to_table(filter, K_MAILADDR, param) || + filter_check_rcpt_to_regex(filter, param); +} + +static int +filter_builtins_data(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param) +{ + return filter_builtins_global(fs, filter, reqid); +} + +static int +filter_builtins_commit(struct filter_session *fs, struct filter *filter, uint64_t reqid, const char *param) +{ + return filter_builtins_global(fs, filter, reqid); +} + +static void +report_smtp_broadcast(uint64_t, const char *, struct timeval *, const char *, + const char *, ...) __attribute__((__format__ (printf, 5, 6))); + +void +lka_report_init(void) +{ + struct reporters *tailq; + size_t i; + + dict_init(&report_smtp_in); + dict_init(&report_smtp_out); + + for (i = 0; i < nitems(smtp_events); ++i) { + tailq = xcalloc(1, sizeof (struct reporters)); + TAILQ_INIT(tailq); + dict_xset(&report_smtp_in, smtp_events[i].event, tailq); + + tailq = xcalloc(1, sizeof (struct reporters)); + TAILQ_INIT(tailq); + dict_xset(&report_smtp_out, smtp_events[i].event, tailq); + } +} + +void +lka_report_register_hook(const char *name, const char *hook) +{ + struct dict *subsystem; + struct reporter_proc *rp; + struct reporters *tailq; + void *iter; + size_t i; + + if (strncmp(hook, "smtp-in|", 8) == 0) { + subsystem = &report_smtp_in; + hook += 8; + } + else if (strncmp(hook, "smtp-out|", 9) == 0) { + subsystem = &report_smtp_out; + hook += 9; + } + else + fatalx("Invalid message direction: %s", hook); + + if (strcmp(hook, "*") == 0) { + iter = NULL; + while (dict_iter(subsystem, &iter, NULL, (void **)&tailq)) { + rp = xcalloc(1, sizeof *rp); + rp->name = xstrdup(name); + TAILQ_INSERT_TAIL(tailq, rp, entries); + } + return; + } + + for (i = 0; i < nitems(smtp_events); i++) + if (strcmp(hook, smtp_events[i].event) == 0) + break; + if (i == nitems(smtp_events)) + fatalx("Unrecognized report name: %s", hook); + + tailq = dict_get(subsystem, hook); + rp = xcalloc(1, sizeof *rp); + rp->name = xstrdup(name); + TAILQ_INSERT_TAIL(tailq, rp, entries); +} + +static void +report_smtp_broadcast(uint64_t reqid, const char *direction, struct timeval *tv, const char *event, + const char *format, ...) +{ + va_list ap; + struct dict *d; + struct reporters *tailq; + struct reporter_proc *rp; + + if (strcmp("smtp-in", direction) == 0) + d = &report_smtp_in; + + else if (strcmp("smtp-out", direction) == 0) + d = &report_smtp_out; + + else + fatalx("unexpected direction: %s", direction); + + tailq = dict_xget(d, event); + TAILQ_FOREACH(rp, tailq, entries) { + if (!lka_filter_proc_in_session(reqid, rp->name)) + continue; + + va_start(ap, format); + if (io_printf(lka_proc_get_io(rp->name), + "report|%s|%lld.%06ld|%s|%s|%016"PRIx64"%s", + PROTOCOL_VERSION, (long long int)tv->tv_sec, tv->tv_usec, direction, + event, reqid, format[0] != '\n' ? "|" : "") == -1 || + io_vprintf(lka_proc_get_io(rp->name), format, ap) == -1) + fatalx("failed to write to processor"); + va_end(ap); + } +} + +void +lka_report_smtp_link_connect(const char *direction, struct timeval *tv, uint64_t reqid, const char *rdns, + int fcrdns, + const struct sockaddr_storage *ss_src, + const struct sockaddr_storage *ss_dest) +{ + struct filter_session *fs; + char src[NI_MAXHOST + 5]; + char dest[NI_MAXHOST + 5]; + uint16_t src_port = 0; + uint16_t dest_port = 0; + const char *fcrdns_str; + + if (ss_src->ss_family == AF_INET) + src_port = ntohs(((const struct sockaddr_in *)ss_src)->sin_port); + else if (ss_src->ss_family == AF_INET6) + src_port = ntohs(((const struct sockaddr_in6 *)ss_src)->sin6_port); + + if (ss_dest->ss_family == AF_INET) + dest_port = ntohs(((const struct sockaddr_in *)ss_dest)->sin_port); + else if (ss_dest->ss_family == AF_INET6) + dest_port = ntohs(((const struct sockaddr_in6 *)ss_dest)->sin6_port); + + if (strcmp(ss_to_text(ss_src), "local") == 0) { + (void)snprintf(src, sizeof src, "unix:%s", SMTPD_SOCKET); + (void)snprintf(dest, sizeof dest, "unix:%s", SMTPD_SOCKET); + } else { + (void)snprintf(src, sizeof src, "%s:%d", ss_to_text(ss_src), src_port); + (void)snprintf(dest, sizeof dest, "%s:%d", ss_to_text(ss_dest), dest_port); + } + + switch (fcrdns) { + case 1: + fcrdns_str = "pass"; + break; + case 0: + fcrdns_str = "fail"; + break; + default: + fcrdns_str = "error"; + break; + } + + fs = tree_xget(&sessions, reqid); + fs->rdns = xstrdup(rdns); + fs->fcrdns = fcrdns; + fs->ss_src = *ss_src; + fs->ss_dest = *ss_dest; + + report_smtp_broadcast(reqid, direction, tv, "link-connect", + "%s|%s|%s|%s\n", rdns, fcrdns_str, src, dest); +} + +void +lka_report_smtp_link_disconnect(const char *direction, struct timeval *tv, uint64_t reqid) +{ + report_smtp_broadcast(reqid, direction, tv, "link-disconnect", "\n"); +} + +void +lka_report_smtp_link_greeting(const char *direction, uint64_t reqid, + struct timeval *tv, const char *domain) +{ + report_smtp_broadcast(reqid, direction, tv, "link-greeting", "%s\n", + domain); +} + +void +lka_report_smtp_link_auth(const char *direction, struct timeval *tv, uint64_t reqid, + const char *username, const char *result) +{ + struct filter_session *fs; + + if (strcmp(result, "pass") == 0) { + fs = tree_xget(&sessions, reqid); + fs->username = xstrdup(username); + } + report_smtp_broadcast(reqid, direction, tv, "link-auth", "%s|%s\n", + username, result); +} + +void +lka_report_smtp_link_identify(const char *direction, struct timeval *tv, + uint64_t reqid, const char *method, const char *heloname) +{ + report_smtp_broadcast(reqid, direction, tv, "link-identify", "%s|%s\n", + method, heloname); +} + +void +lka_report_smtp_link_tls(const char *direction, struct timeval *tv, uint64_t reqid, const char *ciphers) +{ + report_smtp_broadcast(reqid, direction, tv, "link-tls", "%s\n", + ciphers); +} + +void +lka_report_smtp_tx_reset(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid) +{ + report_smtp_broadcast(reqid, direction, tv, "tx-reset", "%08x\n", + msgid); +} + +void +lka_report_smtp_tx_begin(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid) +{ + report_smtp_broadcast(reqid, direction, tv, "tx-begin", "%08x\n", + msgid); +} + +void +lka_report_smtp_tx_mail(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid, const char *address, int ok) +{ + const char *result; + + switch (ok) { + case 1: + result = "ok"; + break; + case 0: + result = "permfail"; + break; + default: + result = "tempfail"; + break; + } + report_smtp_broadcast(reqid, direction, tv, "tx-mail", "%08x|%s|%s\n", + msgid, result, address); +} + +void +lka_report_smtp_tx_rcpt(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid, const char *address, int ok) +{ + const char *result; + + switch (ok) { + case 1: + result = "ok"; + break; + case 0: + result = "permfail"; + break; + default: + result = "tempfail"; + break; + } + report_smtp_broadcast(reqid, direction, tv, "tx-rcpt", "%08x|%s|%s\n", + msgid, result, address); +} + +void +lka_report_smtp_tx_envelope(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid, uint64_t evpid) +{ + report_smtp_broadcast(reqid, direction, tv, "tx-envelope", + "%08x|%016"PRIx64"\n", msgid, evpid); +} + +void +lka_report_smtp_tx_data(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid, int ok) +{ + const char *result; + + switch (ok) { + case 1: + result = "ok"; + break; + case 0: + result = "permfail"; + break; + default: + result = "tempfail"; + break; + } + report_smtp_broadcast(reqid, direction, tv, "tx-data", "%08x|%s\n", + msgid, result); +} + +void +lka_report_smtp_tx_commit(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid, size_t msgsz) +{ + report_smtp_broadcast(reqid, direction, tv, "tx-commit", "%08x|%zd\n", + msgid, msgsz); +} + +void +lka_report_smtp_tx_rollback(const char *direction, struct timeval *tv, uint64_t reqid, uint32_t msgid) +{ + report_smtp_broadcast(reqid, direction, tv, "tx-rollback", "%08x\n", + msgid); +} + +void +lka_report_smtp_protocol_client(const char *direction, struct timeval *tv, uint64_t reqid, const char *command) +{ + report_smtp_broadcast(reqid, direction, tv, "protocol-client", "%s\n", + command); +} + +void +lka_report_smtp_protocol_server(const char *direction, struct timeval *tv, uint64_t reqid, const char *response) +{ + report_smtp_broadcast(reqid, direction, tv, "protocol-server", "%s\n", + response); +} + +void +lka_report_smtp_filter_response(const char *direction, struct timeval *tv, uint64_t reqid, + int phase, int response, const char *param) +{ + const char *phase_name; + const char *response_name; + + switch (phase) { + case FILTER_CONNECT: + phase_name = "connected"; + break; + case FILTER_HELO: + phase_name = "helo"; + break; + case FILTER_EHLO: + phase_name = "ehlo"; + break; + case FILTER_STARTTLS: + phase_name = "tls"; + break; + case FILTER_AUTH: + phase_name = "auth"; + break; + case FILTER_MAIL_FROM: + phase_name = "mail-from"; + break; + case FILTER_RCPT_TO: + phase_name = "rcpt-to"; + break; + case FILTER_DATA: + phase_name = "data"; + break; + case FILTER_DATA_LINE: + phase_name = "data-line"; + break; + case FILTER_RSET: + phase_name = "rset"; + break; + case FILTER_QUIT: + phase_name = "quit"; + break; + case FILTER_NOOP: + phase_name = "noop"; + break; + case FILTER_HELP: + phase_name = "help"; + break; + case FILTER_WIZ: + phase_name = "wiz"; + break; + case FILTER_COMMIT: + phase_name = "commit"; + break; + default: + phase_name = ""; + } + + switch (response) { + case FILTER_PROCEED: + response_name = "proceed"; + break; + case FILTER_JUNK: + response_name = "junk"; + break; + case FILTER_REWRITE: + response_name = "rewrite"; + break; + case FILTER_REJECT: + response_name = "reject"; + break; + case FILTER_DISCONNECT: + response_name = "disconnect"; + break; + default: + response_name = ""; + } + + report_smtp_broadcast(reqid, direction, tv, "filter-response", + "%s|%s%s%s\n", phase_name, response_name, param ? "|" : "", + param ? param : ""); +} + +void +lka_report_smtp_timeout(const char *direction, struct timeval *tv, uint64_t reqid) +{ + report_smtp_broadcast(reqid, direction, tv, "timeout", "\n"); +} + +void +lka_report_filter_report(uint64_t reqid, const char *name, int builtin, + const char *direction, struct timeval *tv, const char *message) +{ + report_smtp_broadcast(reqid, direction, tv, "filter-report", + "%s|%s|%s\n", builtin ? "builtin" : "proc", + name, message); +} + +void +lka_report_proc(const char *name, const char *line) +{ + char buffer[LINE_MAX]; + struct timeval tv; + char *ep, *sp, *direction; + uint64_t reqid; + + if (strlcpy(buffer, line + 7, sizeof(buffer)) >= sizeof(buffer)) + fatalx("Invalid report: line too long: %s", line); + + errno = 0; + tv.tv_sec = strtoll(buffer, &ep, 10); + if (ep[0] != '.' || errno != 0) + fatalx("Invalid report: invalid time: %s", line); + sp = ep + 1; + tv.tv_usec = strtol(sp, &ep, 10); + if (ep[0] != '|' || errno != 0) + fatalx("Invalid report: invalid time: %s", line); + if (ep - sp != 6) + fatalx("Invalid report: invalid time: %s", line); + + direction = ep + 1; + if (strncmp(direction, "smtp-in|", 8) == 0) { + direction[7] = '\0'; + direction += 7; +#if 0 + } else if (strncmp(direction, "smtp-out|", 9) == 0) { + direction[8] = '\0'; + direction += 8; +#endif + } else + fatalx("Invalid report: invalid direction: %s", line); + + reqid = strtoull(sp, &ep, 16); + if (ep[0] != '|' || errno != 0) + fatalx("Invalid report: invalid reqid: %s", line); + sp = ep + 1; + + lka_report_filter_report(reqid, name, 0, direction, &tv, sp); +} diff --git a/foobar/portable/smtpd/lka_session.c b/foobar/portable/smtpd/lka_session.c new file mode 100644 index 00000000..999e01d6 --- /dev/null +++ b/foobar/portable/smtpd/lka_session.c @@ -0,0 +1,556 @@ +/* $OpenBSD: lka_session.c,v 1.93 2019/09/20 17:46:05 gilles Exp $ */ + +/* + * Copyright (c) 2011 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/wait.h> + +#include <netinet/in.h> + +#include <ctype.h> +#include <errno.h> +#include <event.h> +#include <imsg.h> +#include <resolv.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> + +#include "smtpd.h" +#include "log.h" + +#define EXPAND_DEPTH 10 + +#define F_WAITING 0x01 + +struct lka_session { + uint64_t id; /* given by smtp */ + + TAILQ_HEAD(, envelope) deliverylist; + struct expand expand; + + int flags; + int error; + const char *errormsg; + struct envelope envelope; + struct xnodes nodes; + /* waiting for fwdrq */ + struct rule *rule; + struct expandnode *node; +}; + +static void lka_expand(struct lka_session *, struct rule *, + struct expandnode *); +static void lka_submit(struct lka_session *, struct rule *, + struct expandnode *); +static void lka_resume(struct lka_session *); + +static int init; +static struct tree sessions; + +void +lka_session(uint64_t id, struct envelope *envelope) +{ + struct lka_session *lks; + struct expandnode xn; + + if (init == 0) { + init = 1; + tree_init(&sessions); + } + + lks = xcalloc(1, sizeof(*lks)); + lks->id = id; + RB_INIT(&lks->expand.tree); + TAILQ_INIT(&lks->deliverylist); + tree_xset(&sessions, lks->id, lks); + + lks->envelope = *envelope; + + TAILQ_INIT(&lks->nodes); + memset(&xn, 0, sizeof xn); + xn.type = EXPAND_ADDRESS; + xn.u.mailaddr = lks->envelope.rcpt; + lks->expand.parent = NULL; + lks->expand.rule = NULL; + lks->expand.queue = &lks->nodes; + expand_insert(&lks->expand, &xn); + lka_resume(lks); +} + +void +lka_session_forward_reply(struct forward_req *fwreq, int fd) +{ + struct lka_session *lks; + struct dispatcher *dsp; + struct rule *rule; + struct expandnode *xn; + int ret; + + lks = tree_xget(&sessions, fwreq->id); + xn = lks->node; + rule = lks->rule; + + lks->flags &= ~F_WAITING; + + switch (fwreq->status) { + case 0: + /* permanent failure while lookup ~/.forward */ + log_trace(TRACE_EXPAND, "expand: ~/.forward failed for user %s", + fwreq->user); + lks->error = LKA_PERMFAIL; + break; + case 1: + if (fd == -1) { + dsp = dict_get(env->sc_dispatchers, lks->rule->dispatcher); + if (dsp->u.local.forward_only) { + log_trace(TRACE_EXPAND, "expand: no .forward " + "for user %s on forward-only rule", fwreq->user); + lks->error = LKA_TEMPFAIL; + } + else if (dsp->u.local.expand_only) { + log_trace(TRACE_EXPAND, "expand: no .forward " + "for user %s and no default action on rule", fwreq->user); + lks->error = LKA_PERMFAIL; + } + else { + log_trace(TRACE_EXPAND, "expand: no .forward for " + "user %s, just deliver", fwreq->user); + lka_submit(lks, rule, xn); + } + } + else { + dsp = dict_get(env->sc_dispatchers, rule->dispatcher); + + /* expand for the current user and rule */ + lks->expand.rule = rule; + lks->expand.parent = xn; + + /* forwards_get() will close the descriptor no matter what */ + ret = forwards_get(fd, &lks->expand); + if (ret == -1) { + log_trace(TRACE_EXPAND, "expand: temporary " + "forward error for user %s", fwreq->user); + lks->error = LKA_TEMPFAIL; + } + else if (ret == 0) { + if (dsp->u.local.forward_only) { + log_trace(TRACE_EXPAND, "expand: empty .forward " + "for user %s on forward-only rule", fwreq->user); + lks->error = LKA_TEMPFAIL; + } + else if (dsp->u.local.expand_only) { + log_trace(TRACE_EXPAND, "expand: empty .forward " + "for user %s and no default action on rule", fwreq->user); + lks->error = LKA_PERMFAIL; + } + else { + log_trace(TRACE_EXPAND, "expand: empty .forward " + "for user %s, just deliver", fwreq->user); + lka_submit(lks, rule, xn); + } + } + } + break; + default: + /* temporary failure while looking up ~/.forward */ + lks->error = LKA_TEMPFAIL; + } + + if (lks->error == LKA_TEMPFAIL && lks->errormsg == NULL) + lks->errormsg = "424 4.2.4 Mailing list expansion problem"; + if (lks->error == LKA_PERMFAIL && lks->errormsg == NULL) + lks->errormsg = "524 5.2.4 Mailing list expansion problem"; + + lka_resume(lks); +} + +static void +lka_resume(struct lka_session *lks) +{ + struct envelope *ep; + struct expandnode *xn; + + if (lks->error) + goto error; + + /* pop next node and expand it */ + while ((xn = TAILQ_FIRST(&lks->nodes))) { + TAILQ_REMOVE(&lks->nodes, xn, tq_entry); + lka_expand(lks, xn->rule, xn); + if (lks->flags & F_WAITING) + return; + if (lks->error) + goto error; + } + + /* delivery list is empty, reject */ + if (TAILQ_FIRST(&lks->deliverylist) == NULL) { + log_trace(TRACE_EXPAND, "expand: lka_done: expanded to empty " + "delivery list"); + lks->error = LKA_PERMFAIL; + lks->errormsg = "524 5.2.4 Mailing list expansion problem"; + } + error: + if (lks->error) { + m_create(p_pony, IMSG_SMTP_EXPAND_RCPT, 0, 0, -1); + m_add_id(p_pony, lks->id); + m_add_int(p_pony, lks->error); + + if (lks->errormsg) + m_add_string(p_pony, lks->errormsg); + else { + if (lks->error == LKA_PERMFAIL) + m_add_string(p_pony, "550 Invalid recipient"); + else if (lks->error == LKA_TEMPFAIL) + m_add_string(p_pony, "451 Temporary failure"); + } + + m_close(p_pony); + while ((ep = TAILQ_FIRST(&lks->deliverylist)) != NULL) { + TAILQ_REMOVE(&lks->deliverylist, ep, entry); + free(ep); + } + } + else { + /* Process the delivery list and submit envelopes to queue */ + while ((ep = TAILQ_FIRST(&lks->deliverylist)) != NULL) { + TAILQ_REMOVE(&lks->deliverylist, ep, entry); + m_create(p_queue, IMSG_LKA_ENVELOPE_SUBMIT, 0, 0, -1); + m_add_id(p_queue, lks->id); + m_add_envelope(p_queue, ep); + m_close(p_queue); + free(ep); + } + + m_create(p_queue, IMSG_LKA_ENVELOPE_COMMIT, 0, 0, -1); + m_add_id(p_queue, lks->id); + m_close(p_queue); + } + + expand_clear(&lks->expand); + tree_xpop(&sessions, lks->id); + free(lks); +} + +static void +lka_expand(struct lka_session *lks, struct rule *rule, struct expandnode *xn) +{ + struct forward_req fwreq; + struct envelope ep; + struct expandnode node; + struct mailaddr maddr; + struct dispatcher *dsp; + struct table *userbase; + int r; + union lookup lk; + char *tag; + const char *srs_decoded; + + if (xn->depth >= EXPAND_DEPTH) { + log_trace(TRACE_EXPAND, "expand: lka_expand: node too deep."); + lks->error = LKA_PERMFAIL; + lks->errormsg = "524 5.2.4 Mailing list expansion problem"; + return; + } + + switch (xn->type) { + case EXPAND_INVALID: + case EXPAND_INCLUDE: + fatalx("lka_expand: unexpected type"); + break; + + case EXPAND_ADDRESS: + + log_trace(TRACE_EXPAND, "expand: lka_expand: address: %s@%s " + "[depth=%d]", + xn->u.mailaddr.user, xn->u.mailaddr.domain, xn->depth); + + + ep = lks->envelope; + ep.dest = xn->u.mailaddr; + if (xn->parent) /* nodes with parent are forward addresses */ + ep.flags |= EF_INTERNAL; + + /* handle SRS */ + if (env->sc_srs_key != NULL && + ep.sender.user[0] == '\0' && + (strncasecmp(ep.rcpt.user, "SRS0=", 5) == 0 || + strncasecmp(ep.rcpt.user, "SRS1=", 5) == 0)) { + srs_decoded = srs_decode(mailaddr_to_text(&ep.rcpt)); + if (srs_decoded && + text_to_mailaddr(&ep.rcpt, srs_decoded)) { + /* flag envelope internal and override rcpt */ + ep.flags |= EF_INTERNAL; + xn->u.mailaddr = ep.rcpt; + lks->envelope = ep; + } + else { + log_warn("SRS failed to decode: %s", + mailaddr_to_text(&ep.rcpt)); + } + } + + /* Pass the node through the ruleset */ + rule = ruleset_match(&ep); + if (rule == NULL || rule->reject) { + lks->error = (errno == EAGAIN) ? + LKA_TEMPFAIL : LKA_PERMFAIL; + break; + } + + dsp = dict_xget(env->sc_dispatchers, rule->dispatcher); + if (dsp->type == DISPATCHER_REMOTE) { + lka_submit(lks, rule, xn); + } + else if (dsp->u.local.table_virtual) { + /* expand */ + lks->expand.rule = rule; + lks->expand.parent = xn; + + /* temporary replace the mailaddr with a copy where + * we eventually strip the '+'-part before lookup. + */ + maddr = xn->u.mailaddr; + xlowercase(maddr.user, xn->u.mailaddr.user, + sizeof maddr.user); + r = aliases_virtual_get(&lks->expand, &maddr); + if (r == -1) { + lks->error = LKA_TEMPFAIL; + log_trace(TRACE_EXPAND, "expand: lka_expand: " + "error in virtual alias lookup"); + } + else if (r == 0) { + lks->error = LKA_PERMFAIL; + log_trace(TRACE_EXPAND, "expand: lka_expand: " + "no aliases for virtual"); + } + if (lks->error == LKA_TEMPFAIL && lks->errormsg == NULL) + lks->errormsg = "424 4.2.4 Mailing list expansion problem"; + if (lks->error == LKA_PERMFAIL && lks->errormsg == NULL) + lks->errormsg = "524 5.2.4 Mailing list expansion problem"; + } + else { + lks->expand.rule = rule; + lks->expand.parent = xn; + xn->rule = rule; + + memset(&node, 0, sizeof node); + node.type = EXPAND_USERNAME; + xlowercase(node.u.user, xn->u.mailaddr.user, + sizeof node.u.user); + expand_insert(&lks->expand, &node); + } + break; + + case EXPAND_USERNAME: + log_trace(TRACE_EXPAND, "expand: lka_expand: username: %s " + "[depth=%d, sameuser=%d]", + xn->u.user, xn->depth, xn->sameuser); + + /* expand aliases with the given rule */ + dsp = dict_xget(env->sc_dispatchers, rule->dispatcher); + + lks->expand.rule = rule; + lks->expand.parent = xn; + + if (!xn->sameuser && + (dsp->u.local.table_alias || dsp->u.local.table_virtual)) { + if (dsp->u.local.table_alias) + r = aliases_get(&lks->expand, xn->u.user); + if (dsp->u.local.table_virtual) + r = aliases_virtual_get(&lks->expand, &xn->u.mailaddr); + if (r == -1) { + log_trace(TRACE_EXPAND, "expand: lka_expand: " + "error in alias lookup"); + lks->error = LKA_TEMPFAIL; + if (lks->errormsg == NULL) + lks->errormsg = "424 4.2.4 Mailing list expansion problem"; + } + if (r) + break; + } + + /* gilles+hackers@ -> gilles@ */ + if ((tag = strchr(xn->u.user, *env->sc_subaddressing_delim)) != NULL) { + *tag++ = '\0'; + (void)strlcpy(xn->subaddress, tag, sizeof xn->subaddress); + } + + userbase = table_find(env, dsp->u.local.table_userbase); + r = table_lookup(userbase, K_USERINFO, xn->u.user, &lk); + if (r == -1) { + log_trace(TRACE_EXPAND, "expand: lka_expand: " + "backend error while searching user"); + lks->error = LKA_TEMPFAIL; + break; + } + if (r == 0) { + log_trace(TRACE_EXPAND, "expand: lka_expand: " + "user-part does not match system user"); + lks->error = LKA_PERMFAIL; + break; + } + xn->realuser = 1; + + if (xn->sameuser && xn->parent->forwarded) { + log_trace(TRACE_EXPAND, "expand: lka_expand: same " + "user, submitting"); + lka_submit(lks, rule, xn); + break; + } + + /* no aliases found, query forward file */ + lks->rule = rule; + lks->node = xn; + xn->forwarded = 1; + + memset(&fwreq, 0, sizeof(fwreq)); + fwreq.id = lks->id; + (void)strlcpy(fwreq.user, lk.userinfo.username, sizeof(fwreq.user)); + (void)strlcpy(fwreq.directory, lk.userinfo.directory, sizeof(fwreq.directory)); + fwreq.uid = lk.userinfo.uid; + fwreq.gid = lk.userinfo.gid; + + m_compose(p_parent, IMSG_LKA_OPEN_FORWARD, 0, 0, -1, + &fwreq, sizeof(fwreq)); + lks->flags |= F_WAITING; + break; + + case EXPAND_FILENAME: + dsp = dict_xget(env->sc_dispatchers, rule->dispatcher); + if (dsp->u.local.forward_only) { + log_trace(TRACE_EXPAND, "expand: filename matched on forward-only rule"); + lks->error = LKA_TEMPFAIL; + break; + } + log_trace(TRACE_EXPAND, "expand: lka_expand: filename: %s " + "[depth=%d]", xn->u.buffer, xn->depth); + lka_submit(lks, rule, xn); + break; + + case EXPAND_ERROR: + dsp = dict_xget(env->sc_dispatchers, rule->dispatcher); + if (dsp->u.local.forward_only) { + log_trace(TRACE_EXPAND, "expand: error matched on forward-only rule"); + lks->error = LKA_TEMPFAIL; + break; + } + log_trace(TRACE_EXPAND, "expand: lka_expand: error: %s " + "[depth=%d]", xn->u.buffer, xn->depth); + if (xn->u.buffer[0] == '4') + lks->error = LKA_TEMPFAIL; + else if (xn->u.buffer[0] == '5') + lks->error = LKA_PERMFAIL; + lks->errormsg = xn->u.buffer; + break; + + case EXPAND_FILTER: + dsp = dict_xget(env->sc_dispatchers, rule->dispatcher); + if (dsp->u.local.forward_only) { + log_trace(TRACE_EXPAND, "expand: filter matched on forward-only rule"); + lks->error = LKA_TEMPFAIL; + break; + } + log_trace(TRACE_EXPAND, "expand: lka_expand: filter: %s " + "[depth=%d]", xn->u.buffer, xn->depth); + lka_submit(lks, rule, xn); + break; + } +} + +static struct expandnode * +lka_find_ancestor(struct expandnode *xn, enum expand_type type) +{ + while (xn && (xn->type != type)) + xn = xn->parent; + if (xn == NULL) { + log_warnx("warn: lka_find_ancestor: no ancestors of type %d", + type); + fatalx(NULL); + } + return (xn); +} + +static void +lka_submit(struct lka_session *lks, struct rule *rule, struct expandnode *xn) +{ + struct envelope *ep; + struct dispatcher *dsp; + const char *user; + const char *format; + + ep = xmemdup(&lks->envelope, sizeof *ep); + (void)strlcpy(ep->dispatcher, rule->dispatcher, sizeof ep->dispatcher); + + dsp = dict_xget(env->sc_dispatchers, ep->dispatcher); + + switch (dsp->type) { + case DISPATCHER_REMOTE: + if (xn->type != EXPAND_ADDRESS) + fatalx("lka_deliver: expect address"); + ep->type = D_MTA; + ep->dest = xn->u.mailaddr; + break; + + case DISPATCHER_BOUNCE: + case DISPATCHER_LOCAL: + if (xn->type != EXPAND_USERNAME && + xn->type != EXPAND_FILENAME && + xn->type != EXPAND_FILTER) + fatalx("lka_deliver: wrong type: %d", xn->type); + + ep->type = D_MDA; + ep->dest = lka_find_ancestor(xn, EXPAND_ADDRESS)->u.mailaddr; + if (xn->type == EXPAND_USERNAME) { + (void)strlcpy(ep->mda_user, xn->u.user, sizeof(ep->mda_user)); + (void)strlcpy(ep->mda_subaddress, xn->subaddress, sizeof(ep->mda_subaddress)); + } + else { + user = !xn->parent->realuser ? + SMTPD_USER : + xn->parent->u.user; + (void)strlcpy(ep->mda_user, user, sizeof (ep->mda_user)); + + /* this battle needs to be fought ... */ + if (xn->type == EXPAND_FILTER && + strcmp(ep->mda_user, SMTPD_USER) == 0) + log_warnx("commands executed from aliases " + "run with %s privileges", SMTPD_USER); + + if (xn->type == EXPAND_FILENAME) + format = PATH_LIBEXEC"/mail.mboxfile -f %%{mbox.from} %s"; + else if (xn->type == EXPAND_FILTER) + format = "%s"; + (void)snprintf(ep->mda_exec, sizeof(ep->mda_exec), + format, xn->u.buffer); + } + break; + } + + TAILQ_INSERT_TAIL(&lks->deliverylist, ep, entry); +} diff --git a/foobar/portable/smtpd/log.c b/foobar/portable/smtpd/log.c new file mode 100644 index 00000000..14f681e3 --- /dev/null +++ b/foobar/portable/smtpd/log.c @@ -0,0 +1,220 @@ +/* $OpenBSD: log.c,v 1.20 2017/03/21 12:06:56 bluhm Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <syslog.h> +#include <errno.h> +#include <time.h> + +static int debug; +static int verbose; +const char *log_procname; + +void log_init(int, int); +void log_procinit(const char *); +void log_setverbose(int); +int log_getverbose(void); +void log_warn(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_warnx(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_info(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_debug(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void logit(int, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); +void vlog(int, const char *, va_list) + __attribute__((__format__ (printf, 2, 0))); +__dead void fatal(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +__dead void fatalx(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); + +void +log_init(int n_debug, int facility) +{ + extern char *__progname; + + debug = n_debug; + verbose = n_debug; + log_procinit(__progname); + + if (!debug) + openlog(__progname, LOG_PID | LOG_NDELAY, facility); + + tzset(); +} + +void +log_procinit(const char *procname) +{ + if (procname != NULL) + log_procname = procname; +} + +void +log_setverbose(int v) +{ + verbose = v; +} + +int +log_getverbose(void) +{ + return (verbose); +} + +void +logit(int pri, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vlog(pri, fmt, ap); + va_end(ap); +} + +void +vlog(int pri, const char *fmt, va_list ap) +{ + char *nfmt; + int saved_errno = errno; + + if (debug) { + /* best effort in out of mem situations */ + if (asprintf(&nfmt, "%s\n", fmt) == -1) { + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + } else { + vfprintf(stderr, nfmt, ap); + free(nfmt); + } + fflush(stderr); + } else + vsyslog(pri, fmt, ap); + + errno = saved_errno; +} + +void +log_warn(const char *emsg, ...) +{ + char *nfmt; + va_list ap; + int saved_errno = errno; + + /* best effort to even work in out of memory situations */ + if (emsg == NULL) + logit(LOG_ERR, "%s", strerror(saved_errno)); + else { + va_start(ap, emsg); + + if (asprintf(&nfmt, "%s: %s", emsg, + strerror(saved_errno)) == -1) { + /* we tried it... */ + vlog(LOG_ERR, emsg, ap); + logit(LOG_ERR, "%s", strerror(saved_errno)); + } else { + vlog(LOG_ERR, nfmt, ap); + free(nfmt); + } + va_end(ap); + } + + errno = saved_errno; +} + +void +log_warnx(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_ERR, emsg, ap); + va_end(ap); +} + +void +log_info(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_INFO, emsg, ap); + va_end(ap); +} + +void +log_debug(const char *emsg, ...) +{ + va_list ap; + + if (verbose > 1) { + va_start(ap, emsg); + vlog(LOG_DEBUG, emsg, ap); + va_end(ap); + } +} + +static void +vfatalc(int code, const char *emsg, va_list ap) +{ + static char s[BUFSIZ]; + const char *sep; + + if (emsg != NULL) { + (void)vsnprintf(s, sizeof(s), emsg, ap); + sep = ": "; + } else { + s[0] = '\0'; + sep = ""; + } + if (code) + logit(LOG_CRIT, "%s: %s%s%s", + log_procname, s, sep, strerror(code)); + else + logit(LOG_CRIT, "%s%s%s", log_procname, sep, s); +} + +void +fatal(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vfatalc(errno, emsg, ap); + va_end(ap); + exit(1); +} + +void +fatalx(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vfatalc(0, emsg, ap); + va_end(ap); + exit(1); +} diff --git a/foobar/portable/smtpd/log.h b/foobar/portable/smtpd/log.h new file mode 100644 index 00000000..81d0973c --- /dev/null +++ b/foobar/portable/smtpd/log.h @@ -0,0 +1,52 @@ +/* $OpenBSD: log.h,v 1.8 2018/04/26 20:57:59 eric Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef LOG_H +#define LOG_H + +#include "openbsd-compat.h" + +#include <syslog.h> + +#include <stdarg.h> +#ifdef HAVE_SYS_CDEFS_H +#include <sys/cdefs.h> +#endif + +void log_init(int, int); +void log_procinit(const char *); +void log_setverbose(int); +int log_getverbose(void); +void log_warn(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_warnx(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_info(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void log_debug(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +void logit(int, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); +void vlog(int, const char *, va_list) + __attribute__((__format__ (printf, 2, 0))); +__dead void fatal(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); +__dead void fatalx(const char *, ...) + __attribute__((__format__ (printf, 1, 2))); + +#endif /* LOG_H */ diff --git a/foobar/portable/smtpd/mail.lmtp.8 b/foobar/portable/smtpd/mail.lmtp.8 new file mode 100644 index 00000000..98dee00d --- /dev/null +++ b/foobar/portable/smtpd/mail.lmtp.8 @@ -0,0 +1,55 @@ +.\" $OpenBSD: mail.lmtp.8,v 1.1 2017/02/14 15:16:34 gilles Exp $ +.\" +.\" Copyright (c) 2017 Gilles Chehade <gilles@poolp.org> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: February 14 2017 $ +.Dt MAIL.LMTP 8 +.Os +.Sh NAME +.Nm mail.lmtp +.Nd deliver mail through LMTP +.Sh SYNOPSIS +.Nm mail.lmtp +.Op Fl d Ar destination +.Op Fl f Ar from +.Op Fl l Ar lhlo +.Ar user ... +.Sh DESCRIPTION +.Nm +reads the standard input up to an end-of-file and delivers it to +an LMTP server for each +.Ar user Ns 's +address. +The +.Ar user +must be a valid user name or email address. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl d Ar destination +Specify the destination LMTP address. +.It Fl f Ar from +Specify the sender's name or email address. +.It Fl l Ar lhlo +Specify the LHLO argument used in the LMTP session. +By default, +.Nm mail.lmtp +will default to "localhost". +.El +.Sh EXIT STATUS +.Ex -std mail.lmtp +.Sh SEE ALSO +.Xr mail 1 , +.Xr smtpd 8 diff --git a/foobar/portable/smtpd/mail.lmtp.c b/foobar/portable/smtpd/mail.lmtp.c new file mode 100644 index 00000000..90b89990 --- /dev/null +++ b/foobar/portable/smtpd/mail.lmtp.c @@ -0,0 +1,332 @@ +/* + * Copyright (c) 2017 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> + +enum phase { + PHASE_BANNER, + PHASE_HELO, + PHASE_MAILFROM, + PHASE_RCPTTO, + PHASE_DATA, + PHASE_EOM, + PHASE_QUIT +}; + +struct session { + const char *lhlo; + const char *mailfrom; + char *rcptto; + + char **rcpts; + int n_rcpts; +}; + +static int lmtp_connect(const char *); +static void lmtp_engine(int, struct session *); +static void stream_file(FILE *); + +int +main(int argc, char *argv[]) +{ + int ch; + int conn; + const char *destination = "localhost"; + struct session session; + + if (! geteuid()) + errx(EX_TEMPFAIL, "mail.lmtp: may not be executed as root"); + + session.lhlo = "localhost"; + session.mailfrom = getenv("SENDER"); + session.rcptto = NULL; + + while ((ch = getopt(argc, argv, "d:l:f:ru")) != -1) { + switch (ch) { + case 'd': + destination = optarg; + break; + case 'l': + session.lhlo = optarg; + break; + case 'f': + session.mailfrom = optarg; + break; + + case 'r': + session.rcptto = getenv("RECIPIENT"); + break; + + case 'u': + session.rcptto = getenv("USER"); + break; + + default: + break; + } + } + argc -= optind; + argv += optind; + + if (session.mailfrom == NULL) + errx(EX_TEMPFAIL, "sender must be specified with -f"); + + if (argc == 0 && session.rcptto == NULL) + errx(EX_TEMPFAIL, "no recipient was specified"); + + if (session.rcptto) { + session.rcpts = &session.rcptto; + session.n_rcpts = 1; + } + else { + session.rcpts = argv; + session.n_rcpts = argc; + } + + conn = lmtp_connect(destination); + lmtp_engine(conn, &session); + + return (0); +} + +static int +lmtp_connect_inet(const char *destination) +{ + struct addrinfo hints, *res, *res0; + char *destcopy = NULL; + const char *hostname = NULL; + const char *servname = NULL; + const char *cause = NULL; + char *p; + int n, s = -1, save_errno; + + if ((destcopy = strdup(destination)) == NULL) + err(EX_TEMPFAIL, NULL); + + servname = "25"; + hostname = destcopy; + p = destcopy; + if (*p == '[') { + if ((p = strchr(destcopy, ']')) == NULL) + errx(EX_TEMPFAIL, "inet: invalid address syntax"); + + /* remove [ and ] */ + *p = '\0'; + hostname++; + if (strncasecmp(hostname, "IPv6:", 5) == 0) + hostname += 5; + + /* extract port if any */ + switch (*(p+1)) { + case ':': + servname = p+2; + break; + case '\0': + break; + default: + errx(EX_TEMPFAIL, "inet: invalid address syntax"); + } + } + else if ((p = strchr(destcopy, ':')) != NULL) { + *p++ = '\0'; + servname = p; + } + + memset(&hints, 0, sizeof hints); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_NUMERICSERV; + n = getaddrinfo(hostname, servname, &hints, &res0); + if (n) + errx(EX_TEMPFAIL, "inet: %s", gai_strerror(n)); + + for (res = res0; res; res = res->ai_next) { + s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (s == -1) { + cause = "socket"; + continue; + } + + if (connect(s, res->ai_addr, res->ai_addrlen) == -1) { + cause = "connect"; + save_errno = errno; + close(s); + errno = save_errno; + s = -1; + continue; + } + break; + } + + freeaddrinfo(res0); + if (s == -1) + errx(EX_TEMPFAIL, "%s", cause); + + free(destcopy); + return s; +} + +static int +lmtp_connect_unix(const char *destination) +{ + struct sockaddr_un addr; + int s; + + if (*destination != '/') + errx(EX_TEMPFAIL, "unix: path must be absolute"); + + if ((s = socket(PF_LOCAL, SOCK_STREAM, 0)) == -1) + err(EX_TEMPFAIL, NULL); + + memset(&addr, 0, sizeof addr); + addr.sun_family = AF_UNIX; + if (strlcpy(addr.sun_path, destination, sizeof addr.sun_path) + >= sizeof addr.sun_path) + errx(EX_TEMPFAIL, "unix: socket path is too long"); + + if (connect(s, (struct sockaddr *)&addr, sizeof addr) == -1) + err(EX_TEMPFAIL, "connect"); + + return s; +} + +static int +lmtp_connect(const char *destination) +{ + if (destination[0] == '/') + return lmtp_connect_unix(destination); + return lmtp_connect_inet(destination); +} + +static void +lmtp_engine(int fd_read, struct session *session) +{ + int fd_write = 0; + FILE *file_read = 0; + FILE *file_write = 0; + char *line = NULL; + size_t linesize = 0; + ssize_t linelen; + enum phase phase = PHASE_BANNER; + + if ((fd_write = dup(fd_read)) == -1) + err(EX_TEMPFAIL, "dup"); + + if ((file_read = fdopen(fd_read, "r")) == NULL) + err(EX_TEMPFAIL, "fdopen"); + + if ((file_write = fdopen(fd_write, "w")) == NULL) + err(EX_TEMPFAIL, "fdopen"); + + do { + fflush(file_write); + + if ((linelen = getline(&line, &linesize, file_read)) == -1) { + if (ferror(file_read)) + err(EX_TEMPFAIL, "getline"); + else + errx(EX_TEMPFAIL, "unexpected EOF from LMTP server"); + } + line[strcspn(line, "\n")] = '\0'; + line[strcspn(line, "\r")] = '\0'; + + if (linelen < 4 || + !isdigit((unsigned char)line[0]) || + !isdigit((unsigned char)line[1]) || + !isdigit((unsigned char)line[2]) || + (line[3] != ' ' && line[3] != '-')) + errx(EX_TEMPFAIL, "LMTP server sent an invalid line"); + + if (line[0] != (phase == PHASE_DATA ? '3' : '2')) + errx(EX_TEMPFAIL, "LMTP server error: %s", line); + + if (line[3] == '-') + continue; + + switch (phase) { + + case PHASE_BANNER: + fprintf(file_write, "LHLO %s\r\n", session->lhlo); + phase++; + break; + + case PHASE_HELO: + fprintf(file_write, "MAIL FROM:<%s>\r\n", session->mailfrom); + phase++; + break; + + case PHASE_MAILFROM: + fprintf(file_write, "RCPT TO:<%s>\r\n", session->rcpts[session->n_rcpts - 1]); + if (session->n_rcpts - 1 == 0) { + phase++; + break; + } + session->n_rcpts--; + break; + + case PHASE_RCPTTO: + fprintf(file_write, "DATA\r\n"); + phase++; + break; + + case PHASE_DATA: + stream_file(file_write); + fprintf(file_write, ".\r\n"); + phase++; + break; + + case PHASE_EOM: + fprintf(file_write, "QUIT\r\n"); + phase++; + break; + + case PHASE_QUIT: + exit(0); + } + } while (1); +} + +static void +stream_file(FILE *conn) +{ + char *line = NULL; + size_t linesize = 0; + ssize_t linelen; + + while ((linelen = getline(&line, &linesize, stdin)) != -1) { + line[strcspn(line, "\n")] = '\0'; + if (line[0] == '.') + fprintf(conn, "."); + fprintf(conn, "%s\r\n", line); + } + free(line); + if (ferror(stdin)) + err(EX_TEMPFAIL, "getline"); +} diff --git a/foobar/portable/smtpd/mail.maildir.8 b/foobar/portable/smtpd/mail.maildir.8 new file mode 100644 index 00000000..ce822698 --- /dev/null +++ b/foobar/portable/smtpd/mail.maildir.8 @@ -0,0 +1,45 @@ +.\" $OpenBSD: mail.maildir.8,v 1.5 2018/05/30 12:37:57 jmc Exp $ +.\" +.\" Copyright (c) 2017 Gilles Chehade <gilles@poolp.org> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: May 30 2018 $ +.Dt MAIL.MAILDIR 8 +.Os +.Sh NAME +.Nm mail.maildir +.Nd store mail in a maildir +.Sh SYNOPSIS +.Nm mail.maildir +.Op Fl j +.Op Ar pathname +.Sh DESCRIPTION +.Nm +reads the standard input up to an end-of-file and adds it to the +mail directory located in +.Ar pathname +or to the mail directory +.Pa Maildir +located in the user's home directory. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl j +Scan message for X-Spam and move to Junk folder if result is positive. +.El +.Sh EXIT STATUS +.Ex -std mail.maildir +.Sh SEE ALSO +.Xr mail 1 , +.Xr smtpd 8 diff --git a/foobar/portable/smtpd/mail.maildir.c b/foobar/portable/smtpd/mail.maildir.c new file mode 100644 index 00000000..fe6adba6 --- /dev/null +++ b/foobar/portable/smtpd/mail.maildir.c @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2017 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + +#include <sys/types.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <sysexits.h> +#include <unistd.h> + +#define MAILADDR_ESCAPE "!#$%&'*/?^`{|}~" + +static int maildir_subdir(const char *, char *, size_t); +static void maildir_mkdirs(const char *); +static void maildir_engine(const char *, int); +static int mkdirs_component(const char *, mode_t); +static int mkdirs(const char *, mode_t); + +int +main(int argc, char *argv[]) +{ + int ch; + int junk = 0; + + if (! geteuid()) + errx(1, "mail.maildir: may not be executed as root"); + + while ((ch = getopt(argc, argv, "j")) != -1) { + switch (ch) { + case 'j': + junk = 1; + break; + default: + break; + } + } + argc -= optind; + argv += optind; + + if (argc > 1) + errx(1, "mail.maildir: only one maildir is allowed"); + + maildir_engine(argv[0], junk); + + return (0); +} + +static int +maildir_subdir(const char *extension, char *dest, size_t len) +{ + char *sanitized; + + if (strlcpy(dest, extension, len) >= len) + return 0; + + for (sanitized = dest; *sanitized; sanitized++) + if (strchr(MAILADDR_ESCAPE, *sanitized)) + *sanitized = ':'; + + return 1; +} + +static void +maildir_mkdirs(const char *dirname) +{ + uint i; + int ret; + char pathname[PATH_MAX]; + char *subdirs[] = { "cur", "tmp", "new" }; + + if (mkdirs(dirname, 0700) == -1 && errno != EEXIST) { + if (errno == EINVAL || errno == ENAMETOOLONG) + err(1, NULL); + err(EX_TEMPFAIL, NULL); + } + + for (i = 0; i < nitems(subdirs); ++i) { + ret = snprintf(pathname, sizeof pathname, "%s/%s", dirname, + subdirs[i]); + if (ret < 0 || (size_t)ret >= sizeof pathname) + errc(1, ENAMETOOLONG, "%s/%s", dirname, subdirs[i]); + if (mkdir(pathname, 0700) == -1 && errno != EEXIST) + err(EX_TEMPFAIL, NULL); + } +} + +static void +maildir_engine(const char *dirname, int junk) +{ + char rootpath[PATH_MAX]; + char junkpath[PATH_MAX]; + char extpath[PATH_MAX]; + char subdir[PATH_MAX]; + char filename[PATH_MAX]; + char hostname[HOST_NAME_MAX+1]; + + char tmp[PATH_MAX]; + char new[PATH_MAX]; + + int ret; + + int fd; + FILE *fp; + char *line = NULL; + size_t linesize = 0; + ssize_t linelen; + struct stat sb; + char *home; + char *extension; + + int is_junk = 0; + int in_hdr = 1; + + if (dirname == NULL) { + if ((home = getenv("HOME")) == NULL) + err(1, NULL); + ret = snprintf(rootpath, sizeof rootpath, "%s/Maildir", home); + if (ret < 0 || (size_t)ret >= sizeof rootpath) + errc(1, ENAMETOOLONG, "%s/Maildir", home); + dirname = rootpath; + } + maildir_mkdirs(dirname); + + if (junk) { + /* create Junk subdirectory */ + ret = snprintf(junkpath, sizeof junkpath, "%s/.Junk", dirname); + if (ret < 0 || (size_t)ret >= sizeof junkpath) + errc(1, ENAMETOOLONG, "%s/.Junk", dirname); + maildir_mkdirs(junkpath); + } + + if ((extension = getenv("EXTENSION")) != NULL) { + if (maildir_subdir(extension, subdir, sizeof(subdir)) && + subdir[0]) { + ret = snprintf(extpath, sizeof extpath, "%s/.%s", + dirname, subdir); + if (ret < 0 || (size_t)ret >= sizeof extpath) + errc(1, ENAMETOOLONG, "%s/.%s", + dirname, subdir); + if (stat(extpath, &sb) != -1) { + dirname = extpath; + maildir_mkdirs(dirname); + } + } + } + + if (gethostname(hostname, sizeof hostname) != 0) + (void)strlcpy(hostname, "localhost", sizeof hostname); + + (void)snprintf(filename, sizeof filename, "%lld.%08x.%s", + (long long int) time(NULL), + arc4random(), + hostname); + + (void)snprintf(tmp, sizeof tmp, "%s/tmp/%s", dirname, filename); + + fd = open(tmp, O_CREAT | O_EXCL | O_WRONLY, 0600); + if (fd == -1) + err(EX_TEMPFAIL, NULL); + if ((fp = fdopen(fd, "w")) == NULL) + err(EX_TEMPFAIL, NULL); + + while ((linelen = getline(&line, &linesize, stdin)) != -1) { + line[strcspn(line, "\n")] = '\0'; + if (line[0] == '\0') + in_hdr = 0; + if (junk && in_hdr && + (strcasecmp(line, "x-spam: yes") == 0 || + strcasecmp(line, "x-spam-flag: yes") == 0)) + is_junk = 1; + fprintf(fp, "%s\n", line); + } + free(line); + if (ferror(stdin)) + err(EX_TEMPFAIL, NULL); + + if (fflush(fp) == EOF || + ferror(fp) || + fsync(fd) == -1 || + fclose(fp) == EOF) + err(EX_TEMPFAIL, NULL); + + (void)snprintf(new, sizeof new, "%s/new/%s", + is_junk ? junkpath : dirname, filename); + + if (rename(tmp, new) == -1) + err(EX_TEMPFAIL, NULL); + + exit(0); +} + + +static int +mkdirs_component(const char *path, mode_t mode) +{ + struct stat sb; + + if (stat(path, &sb) == -1) { + if (errno != ENOENT) + return 0; + if (mkdir(path, mode | S_IWUSR | S_IXUSR) == -1) + return 0; + } + else if (!S_ISDIR(sb.st_mode)) { + errno = ENOTDIR; + return 0; + } + + return 1; +} + +static int +mkdirs(const char *path, mode_t mode) +{ + char buf[PATH_MAX]; + int i = 0; + int done = 0; + const char *p; + + /* absolute path required */ + if (*path != '/') { + errno = EINVAL; + return 0; + } + + /* make sure we don't exceed PATH_MAX */ + if (strlen(path) >= sizeof buf) { + errno = ENAMETOOLONG; + return 0; + } + + memset(buf, 0, sizeof buf); + for (p = path; *p; p++) { + if (*p == '/') { + if (buf[0] != '\0') + if (!mkdirs_component(buf, mode)) + return 0; + while (*p == '/') + p++; + buf[i++] = '/'; + buf[i++] = *p; + if (*p == '\0' && ++done) + break; + continue; + } + buf[i++] = *p; + } + if (!done) + if (!mkdirs_component(buf, mode)) + return 0; + + if (chmod(path, mode) == -1) + return 0; + + return 1; +} diff --git a/foobar/portable/smtpd/mail.mboxfile.8 b/foobar/portable/smtpd/mail.mboxfile.8 new file mode 100644 index 00000000..015adcb5 --- /dev/null +++ b/foobar/portable/smtpd/mail.mboxfile.8 @@ -0,0 +1,34 @@ +.\" $OpenBSD: mail.mboxfile.8,v 1.1 2018/07/25 10:19:28 gilles Exp $ +.\" +.\" Copyright (c) 2017 Gilles Chehade <gilles@poolp.org> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: July 25 2018 $ +.Dt MAIL.MDA 8 +.Os +.Sh NAME +.Nm mail.mboxfile +.Nd deliver mail to a file in mbox format +.Sh SYNOPSIS +.Nm mail.mboxfile +.Ar file +.Sh DESCRIPTION +.Nm +appends mail to a file in mbox format and acknowledges delivery success or failure +with its exit status. +.Sh EXIT STATUS +.Ex -std mail.mboxfile +.Sh SEE ALSO +.Xr mail 1 , +.Xr smtpd 8 diff --git a/foobar/portable/smtpd/mail.mboxfile.c b/foobar/portable/smtpd/mail.mboxfile.c new file mode 100644 index 00000000..097a8d96 --- /dev/null +++ b/foobar/portable/smtpd/mail.mboxfile.c @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2018 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <time.h> +#include <unistd.h> + +static void mboxfile_engine(const char *sender, const char *filename); + +int +main(int argc, char *argv[]) +{ + int ch; + char *sender = "<unknown>"; + + if (! geteuid()) + errx(1, "mail.mboxfile: may not be executed as root"); + + while ((ch = getopt(argc, argv, "f:")) != -1) { + switch (ch) { + case 'f': + sender = optarg; + break; + default: + break; + } + } + argc -= optind; + argv += optind; + + if (argc > 1) + errx(1, "mail.mboxfile: only one mboxfile is allowed"); + + mboxfile_engine(sender, argv[0]); + + return (0); +} + +static void +mboxfile_engine(const char *sender, const char *filename) +{ + int fd; + FILE *fp; + char *line = NULL; + size_t linesize = 0; + ssize_t linelen; + time_t now; + + time(&now); + +#ifndef O_EXLOCK +#define O_EXLOCK 0 +#endif + fd = open(filename, O_CREAT | O_APPEND | O_WRONLY | O_EXLOCK, 0600); +#ifndef HAVE_O_EXLOCK + /* XXX : do something! */ +#endif + if (fd == -1) + err(EX_TEMPFAIL, NULL); + + if ((fp = fdopen(fd, "w")) == NULL) + err(EX_TEMPFAIL, NULL); + + fprintf(fp, "From %s %s", sender, ctime(&now)); + while ((linelen = getline(&line, &linesize, stdin)) != -1) { + line[strcspn(line, "\n")] = '\0'; + if (strncmp(line, "From ", 5) == 0) + fprintf(fp, ">%s\n", line); + else + fprintf(fp, "%s\n", line); + } + fprintf(fp, "\n"); + free(line); + if (ferror(stdin)) + err(EX_TEMPFAIL, NULL); + + if (fflush(fp) == EOF || + ferror(fp) || + (fsync(fd) == -1 && errno != EINVAL) || + fclose(fp) == EOF) + err(EX_TEMPFAIL, NULL); +} diff --git a/foobar/portable/smtpd/mail.mda.8 b/foobar/portable/smtpd/mail.mda.8 new file mode 100644 index 00000000..61fed733 --- /dev/null +++ b/foobar/portable/smtpd/mail.mda.8 @@ -0,0 +1,35 @@ +.\" $OpenBSD: mail.mda.8,v 1.1 2017/08/09 07:56:10 gilles Exp $ +.\" +.\" Copyright (c) 2017 Gilles Chehade <gilles@poolp.org> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: August 9 2017 $ +.Dt MAIL.MDA 8 +.Os +.Sh NAME +.Nm mail.mda +.Nd deliver mail to a program +.Sh SYNOPSIS +.Nm mail.mda +.Ar program +.Sh DESCRIPTION +.Nm +executes the program and its parameters. +The program must read from the standard input up to an end-of-file +and acknowledge delivery success or failure with its exit status. +.Sh EXIT STATUS +.Ex -std mail.mda +.Sh SEE ALSO +.Xr mail 1 , +.Xr smtpd 8 diff --git a/foobar/portable/smtpd/mail.mda.c b/foobar/portable/smtpd/mail.mda.c new file mode 100644 index 00000000..23958071 --- /dev/null +++ b/foobar/portable/smtpd/mail.mda.c @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2017 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> + +int +main(int argc, char *argv[]) +{ + int ch; + int ret; + + if (! geteuid()) + errx(1, "mail.mda: may not be executed as root"); + + while ((ch = getopt(argc, argv, "")) != -1) { + switch (ch) { + default: + break; + } + } + argc -= optind; + argv += optind; + + if (argc == 0) + errx(1, "mail.mda: command required"); + + if (argc > 1) + errx(1, "mail.mda: only one command is supported"); + + /* could not obtain a shell or could not obtain wait status, + * tempfail */ + if ((ret = system(argv[0])) == -1) + errx(EX_TEMPFAIL, "%s", strerror(errno)); + + /* not exited properly but we have no details, + * tempfail */ + if (! WIFEXITED(ret)) + exit(EX_TEMPFAIL); + + exit(WEXITSTATUS(ret)); +} diff --git a/foobar/portable/smtpd/mail/Makefile b/foobar/portable/smtpd/mail/Makefile new file mode 100644 index 00000000..b2bc2a26 --- /dev/null +++ b/foobar/portable/smtpd/mail/Makefile @@ -0,0 +1,20 @@ +# $OpenBSD: Makefile,v 1.8 2018/07/25 10:19:28 gilles Exp $ +.PATH: ${.CURDIR}/.. + +PROGS= mail.lmtp mail.maildir mail.mboxfile mail.mda + +MAN= mail.lmtp.8 mail.maildir.8 mail.mboxfile.8 mail.mda.8 + +BINOWN= root +BINGRP= wheel + +BINDIR= /usr/libexec + +CFLAGS+= -fstack-protector-all +CFLAGS+= -Wall -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wmissing-declarations +CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual +CFLAGS+= -Wsign-compare +CFLAGS+= -Werror-implicit-function-declaration + +.include <bsd.prog.mk> diff --git a/foobar/portable/smtpd/mailaddr.c b/foobar/portable/smtpd/mailaddr.c new file mode 100644 index 00000000..4346e3dc --- /dev/null +++ b/foobar/portable/smtpd/mailaddr.c @@ -0,0 +1,135 @@ +/* $OpenBSD: mailaddr.c,v 1.3 2018/05/31 21:06:12 gilles Exp $ */ + +/* + * Copyright (c) 2015 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> + +#include <ctype.h> +#include <event.h> +#include <imsg.h> +#include <stdio.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> + +#include "smtpd.h" +#include "log.h" + +static int +mailaddr_line_split(char **line, char **ret) +{ + static char buffer[LINE_MAX]; + int esc, dq, sq; + size_t i; + char *s; + + memset(buffer, 0, sizeof buffer); + esc = dq = sq = 0; + i = 0; + for (s = *line; (*s) && (i < sizeof(buffer)); ++s) { + if (esc) { + buffer[i++] = *s; + esc = 0; + continue; + } + if (*s == '\\') { + esc = 1; + continue; + } + if (*s == ',' && !dq && !sq) { + *ret = buffer; + *line = s+1; + return (1); + } + + buffer[i++] = *s; + esc = 0; + + if (*s == '"' && !sq) + dq ^= 1; + if (*s == '\'' && !dq) + sq ^= 1; + } + + if (esc || dq || sq || i == sizeof(buffer)) + return (-1); + + *ret = buffer; + *line = s; + return (i ? 1 : 0); +} + +int +mailaddr_line(struct maddrmap *maddrmap, const char *s) +{ + struct maddrnode mn; + char buffer[LINE_MAX]; + char *p, *subrcpt; + int ret; + + memset(buffer, 0, sizeof buffer); + if (strlcpy(buffer, s, sizeof buffer) >= sizeof buffer) + return 0; + + p = buffer; + while ((ret = mailaddr_line_split(&p, &subrcpt)) > 0) { + subrcpt = strip(subrcpt); + if (subrcpt[0] == '\0') + continue; + if (!text_to_mailaddr(&mn.mailaddr, subrcpt)) + return 0; + log_debug("subrcpt: [%s]", subrcpt); + maddrmap_insert(maddrmap, &mn); + } + + if (ret >= 0) + return 1; + /* expand_line_split() returned < 0 */ + return 0; +} + +void +maddrmap_init(struct maddrmap *maddrmap) +{ + TAILQ_INIT(&maddrmap->queue); +} + +void +maddrmap_insert(struct maddrmap *maddrmap, struct maddrnode *maddrnode) +{ + struct maddrnode *mn; + + mn = xmemdup(maddrnode, sizeof *maddrnode); + TAILQ_INSERT_TAIL(&maddrmap->queue, mn, entries); +} + +void +maddrmap_free(struct maddrmap *maddrmap) +{ + struct maddrnode *mn; + + while ((mn = TAILQ_FIRST(&maddrmap->queue))) { + TAILQ_REMOVE(&maddrmap->queue, mn, entries); + free(mn); + } + free(maddrmap); +} diff --git a/foobar/portable/smtpd/makemap.8 b/foobar/portable/smtpd/makemap.8 new file mode 100644 index 00000000..674bef6f --- /dev/null +++ b/foobar/portable/smtpd/makemap.8 @@ -0,0 +1,174 @@ +.\" $OpenBSD: makemap.8,v 1.30 2018/11/25 14:41:16 gilles Exp $ +.\" +.\" Copyright (c) 2009 Jacek Masiulaniec <jacekm@openbsd.org> +.\" Copyright (c) 2008-2009 Gilles Chehade <gilles@poolp.org> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: November 25 2018 $ +.Dt MAKEMAP 8 +.Os +.Sh NAME +.Nm makemap +.Nd create database maps for smtpd +.Sh SYNOPSIS +.Nm makemap +.Op Fl U +.Op Fl d Ar dbtype +.Op Fl o Ar dbfile +.Op Fl t Ar type +.Ar file +.Sh DESCRIPTION +Maps provide a generic interface for associating textual key to a value. +Such associations may be accessed through a plaintext file, database, or DNS. +The format of these file types is described below. +.Nm +itself creates the database maps used by keyed map lookups specified in +.Xr smtpd.conf 5 . +.Pp +.Nm +reads input from +.Ar file +and writes data to a file whose name is made by adding a +.Dq .db +suffix to +.Ar file . +The current line can be extended over multiple lines using a backslash +.Pq Sq \e . +Comments can be put anywhere in the file using a hash mark +.Pq Sq # , +and extend to the end of the current line. +Care should be taken when commenting out multi-line text: +the comment is effective until the end of the entire block. +In all cases, +.Nm +reads lines consisting of words separated by whitespace. +The first word of a line is the database key; +the remainder represents the mapped value. +The database key and value may optionally be separated +by the colon character. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl d Ar dbtype +Specify the format of the database. +Available formats are +.Ar hash +and +.Ar btree . +The default value is +.Ar hash . +.It Fl o Ar dbfile +Write the generated database to +.Ar dbfile . +.It Fl t Ar type +Specify the format of the resulting map file. +The default map format is suitable for storing simple, unstructured, +key-to-value string associations. +However, if the mapped value has special meaning, +as in the case of the virtual domains file, +a suitable +.Ar type +must be provided. +The available output types are: +.Bl -tag -width "aliases" +.It Cm aliases +The mapped value is a comma-separated list of mail destinations. +This format can be used for building user aliases and +user mappings for virtual domain files. +.It Cm set +There is no mapped value \(en a map of this type will only allow for +the lookup of keys. +This format can be used for building primary domain maps. +.El +.It Fl U +Instead of generating a database map from text input, +dump the contents of a database map as text +with the key and value separated with a tab. +.El +.Sh PRIMARY DOMAINS +Primary domains can be kept in tables. +To create a primary domain table, add each primary domain on a +single line by itself. +.Pp +In addition to adding an entry to the primary domain map, +one must add a filter rule that accepts mail for the domain +map, for example: +.Bd -literal -offset indent +table domains db:/etc/mail/domains.db + +action "local" mbox + +match for domain <domains> action "local" +.Ed +.Sh VIRTUAL DOMAINS +Virtual domains may also be kept in tables. +To create a virtual domain table, add each virtual domain on a +single line by itself. +.Pp +Virtual domains expect a mapping of virtual users to real users +in order to determine if a recipient is accepted or not. +The mapping format is an extension to +.Xr aliases 5 , +which allows the use of +.Dq user@domain.tld +to accept user only on the specified domain, +.Dq user +to accept the user for any of the virtual domains, +.Dq @domain.tld +to provide a catch-all for the specified domain and +.Dq @ +to provide a global catch-all for all domains. +.Xr smtpd 8 +will perform the lookups in that specific order. +.Pp +To create single virtual address, add +.Dq user@example.com user +to the users map. +To handle all mail destined to any user at example.com, add +.Dq @example.com user +to the virtual map. +.Pp +In addition to adding an entry to the virtual map, +one must add a filter rule that accepts mail for virtual domains, +for example: +.Bd -literal -offset indent +table vdomains db:/etc/mail/vdomains.db +table vusers db:/etc/mail/users.db + +action "local" mbox virtual <vusers> + +match for domain <vdomains> action "local" +match for domain "example.org" action "local" +.Ed +.Sh FILES +.Bl -tag -width "/etc/mail/aliasesXXX" -compact +.It Pa /etc/mail/aliases +List of user mail aliases. +.It Pa /etc/mail/secrets +List of remote host credentials. +.El +.Sh EXIT STATUS +.Ex -std makemap +.Sh SEE ALSO +.Xr aliases 5 , +.Xr smtpd.conf 5 , +.Xr table 5 , +.Xr newaliases 8 , +.Xr smtpd 8 +.Sh HISTORY +The +.Nm +command first appeared in +.Ox 4.6 +as a replacement for the equivalent command shipped with sendmail. diff --git a/foobar/portable/smtpd/makemap.c b/foobar/portable/smtpd/makemap.c new file mode 100644 index 00000000..be6122f1 --- /dev/null +++ b/foobar/portable/smtpd/makemap.c @@ -0,0 +1,522 @@ +/* $OpenBSD: makemap.c,v 1.73 2020/02/24 16:16:07 millert Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2008-2009 Jacek Masiulaniec <jacekm@dobremiasto.net> + * + * 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 "includes.h" + +#ifdef HAVE_SYS_FILE_H +#include <sys/file.h> /* Needed for flock */ +#endif +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/tree.h> +#include <sys/queue.h> +#include <sys/socket.h> + +#include <ctype.h> +#ifdef HAVE_DB_H +#include <db.h> +#elif defined(HAVE_DB1_DB_H) +#include <db1/db.h> +#elif defined(HAVE_DB_185_H) +#include <db_185.h> +#endif +#include <err.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <imsg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <limits.h> +#ifdef HAVE_UTIL_H +#include <util.h> +#endif +#ifdef HAVE_LIBUTIL_H +#include <libutil.h> +#endif + +#include "smtpd.h" +#include "log.h" + +#define PATH_ALIASES SMTPD_CONFDIR "/aliases" + +static void usage(void); +static int parse_map(DB *, int *, char *); +static int parse_entry(DB *, int *, char *, size_t, size_t); +static int parse_mapentry(DB *, int *, char *, size_t, size_t); +static int parse_setentry(DB *, int *, char *, size_t, size_t); +static int make_plain(DBT *, char *); +static int make_aliases(DBT *, char *); +static char *conf_aliases(char *); +static int dump_db(const char *, DBTYPE); + +struct smtpd *env; +char *source; +static int mode; + +enum output_type { + T_PLAIN, + T_ALIASES, + T_SET +} type; + +/* + * Stub functions so that makemap compiles using minimum object files. + */ +int +fork_proc_backend(const char *backend, const char *conf, const char *procname) +{ + return (-1); +} + +int +makemap(int prog_mode, int argc, char *argv[]) +{ + struct stat sb; + char dbname[PATH_MAX]; + DB *db; + const char *opts; + char *conf, *oflag = NULL; + int ch, dbputs = 0, Uflag = 0; + DBTYPE dbtype = DB_HASH; + char *p; + gid_t gid; + int fd = -1; + + gid = getgid(); + if (setresgid(gid, gid, gid) == -1) + err(1, "setresgid"); + + if ((env = config_default()) == NULL) + err(1, NULL); + + log_init(1, LOG_MAIL); + + mode = prog_mode; + conf = CONF_FILE; + type = T_PLAIN; + opts = "b:C:d:ho:O:t:U"; + if (mode == P_NEWALIASES) + opts = "f:h"; + + while ((ch = getopt(argc, argv, opts)) != -1) { + switch (ch) { + case 'b': + if (optarg && strcmp(optarg, "i") == 0) + mode = P_NEWALIASES; + break; + case 'C': + break; /* for compatibility */ + case 'd': + if (strcmp(optarg, "hash") == 0) + dbtype = DB_HASH; + else if (strcmp(optarg, "btree") == 0) + dbtype = DB_BTREE; + else + errx(1, "unsupported DB type '%s'", optarg); + break; + case 'f': + conf = optarg; + break; + case 'o': + oflag = optarg; + break; + case 'O': + if (strncmp(optarg, "AliasFile=", 10) != 0) + break; + type = T_ALIASES; + p = strchr(optarg, '='); + source = ++p; + break; + case 't': + if (strcmp(optarg, "aliases") == 0) + type = T_ALIASES; + else if (strcmp(optarg, "set") == 0) + type = T_SET; + else + errx(1, "unsupported type '%s'", optarg); + break; + case 'U': + Uflag = 1; + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + /* sendmail-compat makemap ... re-execute using proper interface */ + if (argc == 2) { + if (oflag) + usage(); + + p = strstr(argv[1], ".db"); + if (p == NULL || strcmp(p, ".db") != 0) { + if (!bsnprintf(dbname, sizeof dbname, "%s.db", + argv[1])) + errx(1, "database name too long"); + } + else { + if (strlcpy(dbname, argv[1], sizeof dbname) + >= sizeof dbname) + errx(1, "database name too long"); + } + + execl(PATH_MAKEMAP, "makemap", "-d", argv[0], "-o", dbname, + "-", (char *)NULL); + err(1, "execl"); + } + + if (mode == P_NEWALIASES) { + if (geteuid()) + errx(1, "need root privileges"); + if (argc != 0) + usage(); + type = T_ALIASES; + if (source == NULL) + source = conf_aliases(conf); + } else { + if (argc != 1) + usage(); + source = argv[0]; + } + + if (Uflag) + return dump_db(source, dbtype); + + if (oflag == NULL && asprintf(&oflag, "%s.db", source) == -1) + err(1, "asprintf"); + + if (strcmp(source, "-") != 0) + if (stat(source, &sb) == -1) + err(1, "stat: %s", source); + + if (!bsnprintf(dbname, sizeof(dbname), "%s.XXXXXXXXXXX", oflag)) + errx(1, "path too long"); + if ((fd = mkstemp(dbname)) == -1) + err(1, "mkstemp"); + + db = dbopen(dbname, O_TRUNC|O_RDWR, 0644, dbtype, NULL); + if (db == NULL) { + warn("dbopen: %s", dbname); + goto bad; + } + + if (strcmp(source, "-") != 0) + if (fchmod(db->fd(db), sb.st_mode) == -1 || + fchown(db->fd(db), sb.st_uid, sb.st_gid) == -1) { + warn("couldn't carry ownership and perms to %s", + dbname); + goto bad; + } + + if (!parse_map(db, &dbputs, source)) + goto bad; + + if (db->close(db) == -1) { + warn("dbclose: %s", dbname); + goto bad; + } + + /* force to disk before renaming over an existing file */ + if (fsync(fd) == -1) { + warn("fsync: %s", dbname); + goto bad; + } + if (close(fd) == -1) { + fd = -1; + warn("close: %s", dbname); + goto bad; + } + fd = -1; + + if (rename(dbname, oflag) == -1) { + warn("rename"); + goto bad; + } + + if (mode == P_NEWALIASES) + printf("%s: %d aliases\n", source, dbputs); + else if (dbputs == 0) + warnx("warning: empty map created: %s", oflag); + + return 0; +bad: + if (fd != -1) + close(fd); + unlink(dbname); + return 1; +} + +static int +parse_map(DB *db, int *dbputs, char *filename) +{ + FILE *fp; + char *line; + size_t len; + size_t lineno = 0; + + if (strcmp(filename, "-") == 0) + fp = fdopen(0, "r"); + else + fp = fopen(filename, "r"); + if (fp == NULL) { + warn("%s", filename); + return 0; + } + + if (!isatty(fileno(fp)) && flock(fileno(fp), LOCK_SH|LOCK_NB) == -1) { + if (errno == EWOULDBLOCK) + warnx("%s is locked", filename); + else + warn("%s: flock", filename); + fclose(fp); + return 0; + } + + while ((line = fparseln(fp, &len, &lineno, + NULL, FPARSELN_UNESCCOMM)) != NULL) { + if (!parse_entry(db, dbputs, line, len, lineno)) { + free(line); + fclose(fp); + return 0; + } + free(line); + } + + fclose(fp); + return 1; +} + +static int +parse_entry(DB *db, int *dbputs, char *line, size_t len, size_t lineno) +{ + switch (type) { + case T_PLAIN: + case T_ALIASES: + return parse_mapentry(db, dbputs, line, len, lineno); + case T_SET: + return parse_setentry(db, dbputs, line, len, lineno); + } + return 0; +} + +static int +parse_mapentry(DB *db, int *dbputs, char *line, size_t len, size_t lineno) +{ + DBT key; + DBT val; + char *keyp; + char *valp; + + keyp = line; + while (isspace((unsigned char)*keyp)) + keyp++; + if (*keyp == '\0') + return 1; + + valp = keyp; + strsep(&valp, " \t:"); + if (valp == NULL || valp == keyp) + goto bad; + while (*valp == ':' || isspace((unsigned char)*valp)) + valp++; + if (*valp == '\0') + goto bad; + + /* Check for dups. */ + key.data = keyp; + key.size = strlen(keyp) + 1; + + xlowercase(key.data, key.data, strlen(key.data) + 1); + if (db->get(db, &key, &val, 0) == 0) { + warnx("%s:%zd: duplicate entry for %s", source, lineno, keyp); + return 0; + } + + if (type == T_PLAIN) { + if (!make_plain(&val, valp)) + goto bad; + } + else if (type == T_ALIASES) { + if (!make_aliases(&val, valp)) + goto bad; + } + + if (db->put(db, &key, &val, 0) == -1) { + warn("dbput"); + return 0; + } + + (*dbputs)++; + + free(val.data); + + return 1; + +bad: + warnx("%s:%zd: invalid entry", source, lineno); + return 0; +} + +static int +parse_setentry(DB *db, int *dbputs, char *line, size_t len, size_t lineno) +{ + DBT key; + DBT val; + char *keyp; + + keyp = line; + while (isspace((unsigned char)*keyp)) + keyp++; + if (*keyp == '\0') + return 1; + + val.data = "<set>"; + val.size = strlen(val.data) + 1; + + /* Check for dups. */ + key.data = keyp; + key.size = strlen(keyp) + 1; + xlowercase(key.data, key.data, strlen(key.data) + 1); + if (db->get(db, &key, &val, 0) == 0) { + warnx("%s:%zd: duplicate entry for %s", source, lineno, keyp); + return 0; + } + + if (db->put(db, &key, &val, 0) == -1) { + warn("dbput"); + return 0; + } + + (*dbputs)++; + + return 1; +} + +static int +make_plain(DBT *val, char *text) +{ + val->data = xstrdup(text); + val->size = strlen(text) + 1; + + return (val->size); +} + +static int +make_aliases(DBT *val, char *text) +{ + struct expandnode xn; + char *subrcpt; + char *origtext; + + val->data = NULL; + val->size = 0; + + origtext = xstrdup(text); + + while ((subrcpt = strsep(&text, ",")) != NULL) { + /* subrcpt: strip initial and trailing whitespace. */ + subrcpt = strip(subrcpt); + if (*subrcpt == '\0') + goto error; + + if (!text_to_expandnode(&xn, subrcpt)) + goto error; + } + + val->data = origtext; + val->size = strlen(origtext) + 1; + return (val->size); + +error: + free(origtext); + + return 0; +} + +static char * +conf_aliases(char *cfgpath) +{ + struct table *table; + char *path; + char *p; + + if (parse_config(env, cfgpath, 0)) + exit(1); + + table = table_find(env, "aliases"); + if (table == NULL) + return (PATH_ALIASES); + + path = xstrdup(table->t_config); + p = strstr(path, ".db"); + if (p == NULL || strcmp(p, ".db") != 0) { + return (path); + } + *p = '\0'; + return (path); +} + +static int +dump_db(const char *dbname, DBTYPE dbtype) +{ + DB *db; + DBT key, val; + char *keystr, *valstr; + int r; + + db = dbopen(dbname, O_RDONLY, 0644, dbtype, NULL); + if (db == NULL) + err(1, "dbopen: %s", dbname); + + for (r = db->seq(db, &key, &val, R_FIRST); r == 0; + r = db->seq(db, &key, &val, R_NEXT)) { + keystr = key.data; + valstr = val.data; + if (keystr[key.size - 1] == '\0') + key.size--; + if (valstr[val.size - 1] == '\0') + val.size--; + printf("%.*s\t%.*s\n", (int)key.size, keystr, + (int)val.size, valstr); + } + if (r == -1) + err(1, "db->seq: %s", dbname); + + if (db->close(db) == -1) + err(1, "dbclose: %s", dbname); + + return 0; +} + +static void +usage(void) +{ + if (mode == P_NEWALIASES) + fprintf(stderr, "usage: newaliases [-f file]\n"); + else + fprintf(stderr, "usage: makemap [-U] [-d dbtype] [-o dbfile] " + "[-t type] file\n"); + exit(1); +} diff --git a/foobar/portable/smtpd/mda.c b/foobar/portable/smtpd/mda.c new file mode 100644 index 00000000..5e8fec19 --- /dev/null +++ b/foobar/portable/smtpd/mda.c @@ -0,0 +1,919 @@ +/* $OpenBSD: mda.c,v 1.141 2019/10/03 08:50:08 gilles Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> + * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <grp.h> /* needed for setgroups */ +#include <imsg.h> +#include <inttypes.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <time.h> +#include <unistd.h> +#include <limits.h> +#if defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS) +#include <vis.h> +#else +#include "bsd-vis.h" +#endif + +#include "smtpd.h" +#include "log.h" + +#define MDA_HIWAT 65536 + +struct mda_envelope { + TAILQ_ENTRY(mda_envelope) entry; + uint64_t session_id; + uint64_t id; + time_t creation; + char *sender; + char *rcpt; + char *dest; + char *user; + char *dispatcher; + char *mda_subaddress; + char *mda_exec; +}; + +#define USER_WAITINFO 0x01 +#define USER_RUNNABLE 0x02 +#define USER_ONHOLD 0x04 +#define USER_HOLDQ 0x08 + +struct mda_user { + uint64_t id; + TAILQ_ENTRY(mda_user) entry; + TAILQ_ENTRY(mda_user) entry_runnable; + char name[LOGIN_NAME_MAX]; + char usertable[PATH_MAX]; + size_t evpcount; + TAILQ_HEAD(, mda_envelope) envelopes; + int flags; + size_t running; + struct userinfo userinfo; +}; + +struct mda_session { + uint64_t id; + struct mda_user *user; + struct mda_envelope *evp; + struct io *io; + FILE *datafp; +}; + +static void mda_io(struct io *, int, void *); +static int mda_check_loop(FILE *, struct mda_envelope *); +static int mda_getlastline(int, char *, size_t); +static void mda_done(struct mda_session *); +static void mda_fail(struct mda_user *, int, const char *, + enum enhanced_status_code); +static void mda_drain(void); +static void mda_log(const struct mda_envelope *, const char *, const char *); +static void mda_queue_ok(uint64_t); +static void mda_queue_tempfail(uint64_t, const char *, + enum enhanced_status_code); +static void mda_queue_permfail(uint64_t, const char *, enum enhanced_status_code); +static void mda_queue_loop(uint64_t); +static struct mda_user *mda_user(const struct envelope *); +static void mda_user_free(struct mda_user *); +static const char *mda_user_to_text(const struct mda_user *); +static struct mda_envelope *mda_envelope(uint64_t, const struct envelope *); +static void mda_envelope_free(struct mda_envelope *); +static struct mda_session * mda_session(struct mda_user *); +static const char *mda_sysexit_to_str(int); + +static struct tree sessions; +static struct tree users; + +static TAILQ_HEAD(, mda_user) runnable; + +void +mda_imsg(struct mproc *p, struct imsg *imsg) +{ + struct mda_session *s; + struct mda_user *u; + struct mda_envelope *e; + struct envelope evp; + struct deliver deliver; + struct msg m; + const void *data; + const char *error, *parent_error, *syserror; + uint64_t reqid; + size_t sz; + char out[256], buf[LINE_MAX]; + int n; + enum lka_resp_status status; + enum mda_resp_status mda_status; + int mda_sysexit; + + switch (imsg->hdr.type) { + case IMSG_MDA_LOOKUP_USERINFO: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, (int *)&status); + if (status == LKA_OK) + m_get_data(&m, &data, &sz); + m_end(&m); + + u = tree_xget(&users, reqid); + + if (status == LKA_TEMPFAIL) + mda_fail(u, 0, + "Temporary failure in user lookup", + ESC_OTHER_ADDRESS_STATUS); + else if (status == LKA_PERMFAIL) + mda_fail(u, 1, + "Permanent failure in user lookup", + ESC_DESTINATION_MAILBOX_HAS_MOVED); + else { + if (sz != sizeof(u->userinfo)) + fatalx("mda: userinfo size mismatch"); + memmove(&u->userinfo, data, sz); + u->flags &= ~USER_WAITINFO; + u->flags |= USER_RUNNABLE; + TAILQ_INSERT_TAIL(&runnable, u, entry_runnable); + mda_drain(); + } + return; + + case IMSG_QUEUE_DELIVER: + m_msg(&m, imsg); + m_get_envelope(&m, &evp); + m_end(&m); + + u = mda_user(&evp); + + if (u->evpcount >= env->sc_mda_task_hiwat) { + if (!(u->flags & USER_ONHOLD)) { + log_debug("debug: mda: hiwat reached for " + "user \"%s\": holding envelopes", + mda_user_to_text(u)); + u->flags |= USER_ONHOLD; + } + } + + if (u->flags & USER_ONHOLD) { + u->flags |= USER_HOLDQ; + m_create(p_queue, IMSG_MDA_DELIVERY_HOLD, + 0, 0, -1); + m_add_evpid(p_queue, evp.id); + m_add_id(p_queue, u->id); + m_close(p_queue); + return; + } + + e = mda_envelope(u->id, &evp); + TAILQ_INSERT_TAIL(&u->envelopes, e, entry); + u->evpcount += 1; + stat_increment("mda.pending", 1); + + if (!(u->flags & USER_RUNNABLE) && + !(u->flags & USER_WAITINFO)) { + u->flags |= USER_RUNNABLE; + TAILQ_INSERT_TAIL(&runnable, u, entry_runnable); + } + + mda_drain(); + return; + + case IMSG_MDA_OPEN_MESSAGE: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_end(&m); + + s = tree_xget(&sessions, reqid); + e = s->evp; + + if (imsg->fd == -1) { + log_debug("debug: mda: cannot get message fd"); + mda_queue_tempfail(e->id, + "Cannot get message fd", + ESC_OTHER_MAIL_SYSTEM_STATUS); + mda_log(e, "TempFail", "Cannot get message fd"); + mda_done(s); + return; + } + + log_debug("debug: mda: got message fd %d " + "for session %016"PRIx64 " evpid %016"PRIx64, + imsg->fd, s->id, e->id); + + if ((s->datafp = fdopen(imsg->fd, "r")) == NULL) { + log_warn("warn: mda: fdopen"); + close(imsg->fd); + mda_queue_tempfail(e->id, "fdopen failed", + ESC_OTHER_MAIL_SYSTEM_STATUS); + mda_log(e, "TempFail", "fdopen failed"); + mda_done(s); + return; + } + + /* check delivery loop */ + if (mda_check_loop(s->datafp, e)) { + log_debug("debug: mda: loop detected"); + mda_queue_loop(e->id); + mda_log(e, "PermFail", "Loop detected"); + mda_done(s); + return; + } + + /* start queueing delivery headers */ + if (e->sender[0]) + /* + * XXX: remove existing Return-Path, + * if any + */ + n = io_printf(s->io, + "Return-Path: <%s>\n" + "Delivered-To: %s\n", + e->sender, + e->rcpt ? e->rcpt : e->dest); + else + n = io_printf(s->io, + "Delivered-To: %s\n", + e->rcpt ? e->rcpt : e->dest); + if (n == -1) { + log_warn("warn: mda: " + "fail to write delivery info"); + mda_queue_tempfail(e->id, "Out of memory", + ESC_OTHER_MAIL_SYSTEM_STATUS); + mda_log(e, "TempFail", "Out of memory"); + mda_done(s); + return; + } + + /* request parent to fork a helper process */ + memset(&deliver, 0, sizeof deliver); + (void)text_to_mailaddr(&deliver.sender, s->evp->sender); + (void)text_to_mailaddr(&deliver.rcpt, s->evp->rcpt); + (void)text_to_mailaddr(&deliver.dest, s->evp->dest); + if (s->evp->mda_exec) + (void)strlcpy(deliver.mda_exec, s->evp->mda_exec, sizeof deliver.mda_exec); + if (s->evp->mda_subaddress) + (void)strlcpy(deliver.mda_subaddress, s->evp->mda_subaddress, sizeof deliver.mda_subaddress); + (void)strlcpy(deliver.dispatcher, s->evp->dispatcher, sizeof deliver.dispatcher); + deliver.userinfo = s->user->userinfo; + + log_debug("debug: mda: querying mda fd " + "for session %016"PRIx64 " evpid %016"PRIx64, + s->id, s->evp->id); + + m_create(p_parent, IMSG_MDA_FORK, 0, 0, -1); + m_add_id(p_parent, reqid); + m_add_data(p_parent, &deliver, sizeof(deliver)); + m_close(p_parent); + return; + + case IMSG_MDA_FORK: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_end(&m); + + s = tree_xget(&sessions, reqid); + e = s->evp; + if (imsg->fd == -1) { + log_warn("warn: mda: fail to retrieve mda fd"); + mda_queue_tempfail(e->id, "Cannot get mda fd", + ESC_OTHER_MAIL_SYSTEM_STATUS); + mda_log(e, "TempFail", "Cannot get mda fd"); + mda_done(s); + return; + } + + log_debug("debug: mda: got mda fd %d " + "for session %016"PRIx64 " evpid %016"PRIx64, + imsg->fd, s->id, s->evp->id); + + io_set_nonblocking(imsg->fd); + io_set_fd(s->io, imsg->fd); + io_set_write(s->io); + return; + + case IMSG_MDA_DONE: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, (int *)&mda_status); + m_get_int(&m, (int *)&mda_sysexit); + m_get_string(&m, &parent_error); + m_end(&m); + + s = tree_xget(&sessions, reqid); + e = s->evp; + /* + * Grab last line of mda stdout/stderr if available. + */ + out[0] = '\0'; + if (imsg->fd != -1) + mda_getlastline(imsg->fd, out, sizeof(out)); + + /* + * Choose between parent's description of error and + * child's output, the latter having preference over + * the former. + */ + error = NULL; + if (mda_status == MDA_OK) { + if (s->datafp || (s->io && io_queued(s->io))) { + error = "mda exited prematurely"; + mda_status = MDA_TEMPFAIL; + } + } else + error = out[0] ? out : parent_error; + + syserror = NULL; + if (mda_sysexit) + syserror = mda_sysexit_to_str(mda_sysexit); + + /* update queue entry */ + switch (mda_status) { + case MDA_TEMPFAIL: + mda_queue_tempfail(e->id, error, + ESC_OTHER_MAIL_SYSTEM_STATUS); + (void)snprintf(buf, sizeof buf, + "Error (%s%s%s)", + syserror ? syserror : "", + syserror ? ": " : "", + error); + mda_log(e, "TempFail", buf); + break; + case MDA_PERMFAIL: + mda_queue_permfail(e->id, error, + ESC_OTHER_MAIL_SYSTEM_STATUS); + (void)snprintf(buf, sizeof buf, + "Error (%s%s%s)", + syserror ? syserror : "", + syserror ? ": " : "", + error); + mda_log(e, "PermFail", buf); + break; + case MDA_OK: + mda_queue_ok(e->id); + mda_log(e, "Ok", "Delivered"); + break; + } + mda_done(s); + return; + } + + errx(1, "mda_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); +} + +void +mda_postfork() +{ +} + +void +mda_postprivdrop() +{ + tree_init(&sessions); + tree_init(&users); + TAILQ_INIT(&runnable); +} + +static void +mda_io(struct io *io, int evt, void *arg) +{ + struct mda_session *s = arg; + char *ln = NULL; + size_t sz = 0; + ssize_t len; + + log_trace(TRACE_IO, "mda: %p: %s %s", s, io_strevent(evt), + io_strio(io)); + + switch (evt) { + case IO_LOWAT: + + /* done */ + done: + if (s->datafp == NULL) { + log_debug("debug: mda: all data sent for session" + " %016"PRIx64 " evpid %016"PRIx64, + s->id, s->evp->id); + io_free(io); + s->io = NULL; + return; + } + + while (io_queued(s->io) < MDA_HIWAT) { + if ((len = getline(&ln, &sz, s->datafp)) == -1) + break; + if (io_write(s->io, ln, len) == -1) { + m_create(p_parent, IMSG_MDA_KILL, + 0, 0, -1); + m_add_id(p_parent, s->id); + m_add_string(p_parent, "Out of memory"); + m_close(p_parent); + io_pause(io, IO_OUT); + free(ln); + return; + } + } + + free(ln); + ln = NULL; + if (ferror(s->datafp)) { + log_debug("debug: mda: ferror on session %016"PRIx64, + s->id); + m_create(p_parent, IMSG_MDA_KILL, 0, 0, -1); + m_add_id(p_parent, s->id); + m_add_string(p_parent, "Error reading body"); + m_close(p_parent); + io_pause(io, IO_OUT); + return; + } + + if (feof(s->datafp)) { + log_debug("debug: mda: end-of-file for session" + " %016"PRIx64 " evpid %016"PRIx64, + s->id, s->evp->id); + fclose(s->datafp); + s->datafp = NULL; + if (io_queued(s->io) == 0) + goto done; + } + return; + + case IO_TIMEOUT: + log_debug("debug: mda: timeout on session %016"PRIx64, s->id); + io_pause(io, IO_OUT); + return; + + case IO_ERROR: + log_debug("debug: mda: io error on session %016"PRIx64": %s", + s->id, io_error(io)); + io_pause(io, IO_OUT); + return; + + case IO_DISCONNECTED: + log_debug("debug: mda: io disconnected on session %016"PRIx64, + s->id); + io_pause(io, IO_OUT); + return; + + default: + log_debug("debug: mda: unexpected event on session %016"PRIx64, + s->id); + io_pause(io, IO_OUT); + return; + } +} + +static int +mda_check_loop(FILE *fp, struct mda_envelope *e) +{ + char *buf = NULL; + size_t sz = 0; + ssize_t len; + int ret = 0; + + while ((len = getline(&buf, &sz, fp)) != -1) { + if (buf[len - 1] == '\n') + buf[len - 1] = '\0'; + + if (strchr(buf, ':') == NULL && !isspace((unsigned char)*buf)) + break; + + if (strncasecmp("Delivered-To: ", buf, 14) == 0) { + if (strcasecmp(buf + 14, e->dest) == 0) { + ret = 1; + break; + } + } + } + + free(buf); + fseek(fp, SEEK_SET, 0); + return (ret); +} + +static int +mda_getlastline(int fd, char *dst, size_t dstsz) +{ + FILE *fp; + char *ln = NULL; + size_t sz = 0; + ssize_t len; + int out = 0; + + if (lseek(fd, 0, SEEK_SET) < 0) { + log_warn("warn: mda: lseek"); + close(fd); + return (-1); + } + fp = fdopen(fd, "r"); + if (fp == NULL) { + log_warn("warn: mda: fdopen"); + close(fd); + return (-1); + } + while ((len = getline(&ln, &sz, fp)) != -1) { + if (ln[len - 1] == '\n') + ln[len - 1] = '\0'; + out = 1; + } + fclose(fp); + + if (out) { + (void)strlcpy(dst, "\"", dstsz); + (void)strnvis(dst + 1, ln, dstsz - 2, VIS_SAFE | VIS_CSTYLE | VIS_NL); + (void)strlcat(dst, "\"", dstsz); + } + + free(ln); + return (0); +} + +static void +mda_fail(struct mda_user *user, int permfail, const char *error, + enum enhanced_status_code code) +{ + struct mda_envelope *e; + + while ((e = TAILQ_FIRST(&user->envelopes))) { + TAILQ_REMOVE(&user->envelopes, e, entry); + if (permfail) { + mda_log(e, "PermFail", error); + mda_queue_permfail(e->id, error, code); + } + else { + mda_log(e, "TempFail", error); + mda_queue_tempfail(e->id, error, code); + } + mda_envelope_free(e); + } + + mda_user_free(user); +} + +static void +mda_drain(void) +{ + struct mda_user *u; + + while ((u = (TAILQ_FIRST(&runnable)))) { + + TAILQ_REMOVE(&runnable, u, entry_runnable); + + if (u->evpcount == 0 && u->running == 0) { + log_debug("debug: mda: all done for user \"%s\"", + mda_user_to_text(u)); + mda_user_free(u); + continue; + } + + if (u->evpcount == 0) { + log_debug("debug: mda: no more envelope for \"%s\"", + mda_user_to_text(u)); + u->flags &= ~USER_RUNNABLE; + continue; + } + + if (u->running >= env->sc_mda_max_user_session) { + log_debug("debug: mda: " + "maximum number of session reached for user \"%s\"", + mda_user_to_text(u)); + u->flags &= ~USER_RUNNABLE; + continue; + } + + if (tree_count(&sessions) >= env->sc_mda_max_session) { + log_debug("debug: mda: " + "maximum number of session reached"); + TAILQ_INSERT_HEAD(&runnable, u, entry_runnable); + return; + } + + mda_session(u); + + if (u->evpcount == env->sc_mda_task_lowat) { + if (u->flags & USER_ONHOLD) { + log_debug("debug: mda: down to lowat for user " + "\"%s\": releasing", + mda_user_to_text(u)); + u->flags &= ~USER_ONHOLD; + } + if (u->flags & USER_HOLDQ) { + m_create(p_queue, IMSG_MDA_HOLDQ_RELEASE, + 0, 0, -1); + m_add_id(p_queue, u->id); + m_add_int(p_queue, env->sc_mda_task_release); + m_close(p_queue); + } + } + + /* re-add the user at the tail of the queue */ + TAILQ_INSERT_TAIL(&runnable, u, entry_runnable); + } +} + +static void +mda_done(struct mda_session *s) +{ + log_debug("debug: mda: session %016" PRIx64 " done", s->id); + + tree_xpop(&sessions, s->id); + + mda_envelope_free(s->evp); + + s->user->running--; + if (!(s->user->flags & USER_RUNNABLE)) { + log_debug("debug: mda: user \"%s\" becomes runnable", + s->user->name); + TAILQ_INSERT_TAIL(&runnable, s->user, entry_runnable); + s->user->flags |= USER_RUNNABLE; + } + + if (s->datafp) + fclose(s->datafp); + if (s->io) + io_free(s->io); + + free(s); + + stat_decrement("mda.running", 1); + + mda_drain(); +} + +static void +mda_log(const struct mda_envelope *evp, const char *prefix, const char *status) +{ + char rcpt[LINE_MAX]; + + rcpt[0] = '\0'; + if (evp->rcpt) + (void)snprintf(rcpt, sizeof rcpt, "rcpt=<%s> ", evp->rcpt); + + log_info("%016"PRIx64" mda delivery evpid=%016" PRIx64 " from=<%s> to=<%s> " + "%suser=%s delay=%s result=%s stat=%s", + evp->session_id, + evp->id, + evp->sender ? evp->sender : "", + evp->dest, + rcpt, + evp->user, + duration_to_text(time(NULL) - evp->creation), + prefix, + status); +} + +static void +mda_queue_ok(uint64_t evpid) +{ + m_create(p_queue, IMSG_MDA_DELIVERY_OK, 0, 0, -1); + m_add_evpid(p_queue, evpid); + m_close(p_queue); +} + +static void +mda_queue_tempfail(uint64_t evpid, const char *reason, + enum enhanced_status_code code) +{ + m_create(p_queue, IMSG_MDA_DELIVERY_TEMPFAIL, 0, 0, -1); + m_add_evpid(p_queue, evpid); + m_add_string(p_queue, reason); + m_add_int(p_queue, (int)code); + m_close(p_queue); +} + +static void +mda_queue_permfail(uint64_t evpid, const char *reason, + enum enhanced_status_code code) +{ + m_create(p_queue, IMSG_MDA_DELIVERY_PERMFAIL, 0, 0, -1); + m_add_evpid(p_queue, evpid); + m_add_string(p_queue, reason); + m_add_int(p_queue, (int)code); + m_close(p_queue); +} + +static void +mda_queue_loop(uint64_t evpid) +{ + m_create(p_queue, IMSG_MDA_DELIVERY_LOOP, 0, 0, -1); + m_add_evpid(p_queue, evpid); + m_close(p_queue); +} + +static struct mda_user * +mda_user(const struct envelope *evp) +{ + struct dispatcher *dsp; + struct mda_user *u; + void *i; + + i = NULL; + dsp = dict_xget(env->sc_dispatchers, evp->dispatcher); + while (tree_iter(&users, &i, NULL, (void**)(&u))) { + if (!strcmp(evp->mda_user, u->name) && + !strcmp(dsp->u.local.table_userbase, u->usertable)) + return (u); + } + + u = xcalloc(1, sizeof *u); + u->id = generate_uid(); + TAILQ_INIT(&u->envelopes); + (void)strlcpy(u->name, evp->mda_user, sizeof(u->name)); + (void)strlcpy(u->usertable, dsp->u.local.table_userbase, + sizeof(u->usertable)); + + tree_xset(&users, u->id, u); + + m_create(p_lka, IMSG_MDA_LOOKUP_USERINFO, 0, 0, -1); + m_add_id(p_lka, u->id); + m_add_string(p_lka, dsp->u.local.table_userbase); + m_add_string(p_lka, evp->mda_user); + m_close(p_lka); + u->flags |= USER_WAITINFO; + + stat_increment("mda.user", 1); + + if (dsp->u.local.user) + log_debug("mda: new user %016" PRIx64 + " for \"%s\" delivering as \"%s\"", + u->id, mda_user_to_text(u), dsp->u.local.user); + else + log_debug("mda: new user %016" PRIx64 + " for \"%s\"", u->id, mda_user_to_text(u)); + + return (u); +} + +static void +mda_user_free(struct mda_user *u) +{ + tree_xpop(&users, u->id); + + if (u->flags & USER_HOLDQ) { + m_create(p_queue, IMSG_MDA_HOLDQ_RELEASE, 0, 0, -1); + m_add_id(p_queue, u->id); + m_add_int(p_queue, 0); + m_close(p_queue); + } + + free(u); + stat_decrement("mda.user", 1); +} + +static const char * +mda_user_to_text(const struct mda_user *u) +{ + static char buf[1024]; + + (void)snprintf(buf, sizeof(buf), "%s:%s", u->usertable, u->name); + + return (buf); +} + +static struct mda_envelope * +mda_envelope(uint64_t session_id, const struct envelope *evp) +{ + struct mda_envelope *e; + char buf[LINE_MAX]; + + e = xcalloc(1, sizeof *e); + e->session_id = session_id; + e->id = evp->id; + e->creation = evp->creation; + buf[0] = '\0'; + if (evp->sender.user[0] && evp->sender.domain[0]) + (void)snprintf(buf, sizeof buf, "%s@%s", + evp->sender.user, evp->sender.domain); + e->sender = xstrdup(buf); + (void)snprintf(buf, sizeof buf, "%s@%s", evp->dest.user, + evp->dest.domain); + e->dest = xstrdup(buf); + (void)snprintf(buf, sizeof buf, "%s@%s", evp->rcpt.user, + evp->rcpt.domain); + e->rcpt = xstrdup(buf); + e->user = evp->mda_user[0] ? + xstrdup(evp->mda_user) : xstrdup(evp->dest.user); + e->dispatcher = xstrdup(evp->dispatcher); + if (evp->mda_exec[0]) + e->mda_exec = xstrdup(evp->mda_exec); + if (evp->mda_subaddress[0]) + e->mda_subaddress = xstrdup(evp->mda_subaddress); + stat_increment("mda.envelope", 1); + return (e); +} + +static void +mda_envelope_free(struct mda_envelope *e) +{ + free(e->sender); + free(e->dest); + free(e->rcpt); + free(e->user); + free(e->mda_exec); + free(e); + + stat_decrement("mda.envelope", 1); +} + +static struct mda_session * +mda_session(struct mda_user * u) +{ + struct mda_session *s; + + s = xcalloc(1, sizeof *s); + s->id = generate_uid(); + s->user = u; + s->io = io_new(); + io_set_callback(s->io, mda_io, s); + + tree_xset(&sessions, s->id, s); + + s->evp = TAILQ_FIRST(&u->envelopes); + TAILQ_REMOVE(&u->envelopes, s->evp, entry); + u->evpcount--; + u->running++; + + stat_decrement("mda.pending", 1); + stat_increment("mda.running", 1); + + log_debug("debug: mda: new session %016" PRIx64 + " for user \"%s\" evpid %016" PRIx64, s->id, + mda_user_to_text(u), s->evp->id); + + m_create(p_queue, IMSG_MDA_OPEN_MESSAGE, 0, 0, -1); + m_add_id(p_queue, s->id); + m_add_msgid(p_queue, evpid_to_msgid(s->evp->id)); + m_close(p_queue); + + return (s); +} + +static const char * +mda_sysexit_to_str(int sysexit) +{ + switch (sysexit) { + case EX_USAGE: + return "command line usage error"; + case EX_DATAERR: + return "data format error"; + case EX_NOINPUT: + return "cannot open input"; + case EX_NOUSER: + return "user unknown"; + case EX_NOHOST: + return "host name unknown"; + case EX_UNAVAILABLE: + return "service unavailable"; + case EX_SOFTWARE: + return "internal software error"; + case EX_OSERR: + return "system resource problem"; + case EX_OSFILE: + return "critical OS file missing"; + case EX_CANTCREAT: + return "can't create user output file"; + case EX_IOERR: + return "input/output error"; + case EX_TEMPFAIL: + return "temporary failure"; + case EX_PROTOCOL: + return "remote error in protocol"; + case EX_NOPERM: + return "permission denied"; + case EX_CONFIG: + return "local configuration error"; + default: + break; + } + return NULL; +} + diff --git a/foobar/portable/smtpd/mda_mbox.c b/foobar/portable/smtpd/mda_mbox.c new file mode 100644 index 00000000..8918e3ee --- /dev/null +++ b/foobar/portable/smtpd/mda_mbox.c @@ -0,0 +1,94 @@ +/* $OpenBSD: mda_mbox.c,v 1.2 2020/02/03 15:41:22 gilles Exp $ */ + +/* + * Copyright (c) 2018 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> + +#include <err.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <imsg.h> +#include <paths.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> +#include <limits.h> + +#include "smtpd.h" + + +void +mda_mbox(struct deliver *deliver) +{ + int ret; + char sender[LINE_MAX]; + char *envp[] = { + "HOME=/", + "PATH=" _PATH_DEFPATH, + "LOGNAME=root", + "USER=root", + NULL, + }; + + if (deliver->sender.user[0] == '\0' && + deliver->sender.domain[0] == '\0') + ret = snprintf(sender, sizeof sender, "MAILER-DAEMON"); + else + ret = snprintf(sender, sizeof sender, "%s@%s", + deliver->sender.user, deliver->sender.domain); + if (ret < 0 || (size_t)ret >= sizeof sender) + errx(EX_TEMPFAIL, "sender address too long"); + + execle(PATH_MAILLOCAL, PATH_MAILLOCAL, "-f", + sender, deliver->userinfo.username, (char *)NULL, envp); + perror("execl"); + _exit(EX_TEMPFAIL); +} + +void +mda_mbox_init(struct deliver *deliver) +{ + int fd; + int ret; + char buffer[LINE_MAX]; + + ret = snprintf(buffer, sizeof buffer, "%s/%s", + _PATH_MAILDIR, deliver->userinfo.username); + if (ret < 0 || (size_t)ret >= sizeof buffer) + errx(EX_TEMPFAIL, "mailbox pathname too long"); + + if ((fd = open(buffer, O_CREAT|O_EXCL, 0)) == -1) { + if (errno == EEXIST) + return; + err(EX_TEMPFAIL, "open"); + } + + if (fchown(fd, deliver->userinfo.uid, deliver->userinfo.gid) == -1) + err(EX_TEMPFAIL, "fchown"); + + if (fchmod(fd, S_IRUSR|S_IWUSR) == -1) + err(EX_TEMPFAIL, "fchown"); +} diff --git a/foobar/portable/smtpd/mda_unpriv.c b/foobar/portable/smtpd/mda_unpriv.c new file mode 100644 index 00000000..2143b9a0 --- /dev/null +++ b/foobar/portable/smtpd/mda_unpriv.c @@ -0,0 +1,110 @@ +/* $OpenBSD: mda_unpriv.c,v 1.6 2020/02/02 22:13:48 gilles Exp $ */ + +/* + * Copyright (c) 2018 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> + +#include <err.h> +#include <errno.h> +#include <event.h> +#include <imsg.h> +#include <paths.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> + +#include "smtpd.h" + + +void +mda_unpriv(struct dispatcher *dsp, struct deliver *deliver, + const char *pw_name, const char *pw_dir) +{ + int idx; + char *mda_environ[11]; + char mda_exec[LINE_MAX]; + char mda_wrapper[LINE_MAX]; + const char *mda_command; + const char *mda_command_wrap; + + if (deliver->mda_exec[0]) + mda_command = deliver->mda_exec; + else + mda_command = dsp->u.local.command; + + if (strlcpy(mda_exec, mda_command, sizeof (mda_exec)) + >= sizeof (mda_exec)) + errx(1, "mda command line too long"); + + if (mda_expand_format(mda_exec, sizeof mda_exec, deliver, + &deliver->userinfo, NULL) == -1) + errx(1, "mda command line could not be expanded"); + + mda_command = mda_exec; + + /* setup environment similar to other MTA */ + idx = 0; + xasprintf(&mda_environ[idx++], "PATH=%s", _PATH_DEFPATH); + xasprintf(&mda_environ[idx++], "DOMAIN=%s", deliver->rcpt.domain); + xasprintf(&mda_environ[idx++], "HOME=%s", pw_dir); + xasprintf(&mda_environ[idx++], "RECIPIENT=%s@%s", deliver->dest.user, deliver->dest.domain); + xasprintf(&mda_environ[idx++], "SHELL=/bin/sh"); + xasprintf(&mda_environ[idx++], "LOCAL=%s", deliver->rcpt.user); + xasprintf(&mda_environ[idx++], "LOGNAME=%s", pw_name); + xasprintf(&mda_environ[idx++], "USER=%s", pw_name); + + if (deliver->sender.user[0]) + xasprintf(&mda_environ[idx++], "SENDER=%s@%s", + deliver->sender.user, deliver->sender.domain); + else + xasprintf(&mda_environ[idx++], "SENDER="); + + if (deliver->mda_subaddress[0]) + xasprintf(&mda_environ[idx++], "EXTENSION=%s", deliver->mda_subaddress); + + mda_environ[idx++] = (char *)NULL; + + if (dsp->u.local.mda_wrapper) { + mda_command_wrap = dict_get(env->sc_mda_wrappers, + dsp->u.local.mda_wrapper); + if (mda_command_wrap == NULL) + errx(1, "could not find wrapper %s", + dsp->u.local.mda_wrapper); + + if (strlcpy(mda_wrapper, mda_command_wrap, sizeof (mda_wrapper)) + >= sizeof (mda_wrapper)) + errx(1, "mda command line too long"); + + if (mda_expand_format(mda_wrapper, sizeof mda_wrapper, deliver, + &deliver->userinfo, mda_command) == -1) + errx(1, "mda command line could not be expanded"); + mda_command = mda_wrapper; + } + execle("/bin/sh", "/bin/sh", "-c", mda_command, (char *)NULL, + mda_environ); + + perror("execle"); + _exit(1); +} + diff --git a/foobar/portable/smtpd/mda_variables.c b/foobar/portable/smtpd/mda_variables.c new file mode 100644 index 00000000..b672e492 --- /dev/null +++ b/foobar/portable/smtpd/mda_variables.c @@ -0,0 +1,374 @@ +/* $OpenBSD: mda_variables.c,v 1.6 2019/09/19 07:35:36 gilles Exp $ */ + +/* + * Copyright (c) 2011-2017 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> + +#include <netinet/in.h> + +#include <imsg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> + +#include "smtpd.h" +#include "log.h" + +#define EXPAND_DEPTH 10 + +ssize_t mda_expand_format(char *, size_t, const struct deliver *, + const struct userinfo *, const char *); +static ssize_t mda_expand_token(char *, size_t, const char *, + const struct deliver *, const struct userinfo *, const char *); +static int mod_lowercase(char *, size_t); +static int mod_uppercase(char *, size_t); +static int mod_strip(char *, size_t); + +static struct modifiers { + char *name; + int (*f)(char *buf, size_t len); +} token_modifiers[] = { + { "lowercase", mod_lowercase }, + { "uppercase", mod_uppercase }, + { "strip", mod_strip }, + { "raw", NULL }, /* special case, must stay last */ +}; + +#define MAXTOKENLEN 128 + +static ssize_t +mda_expand_token(char *dest, size_t len, const char *token, + const struct deliver *dlv, const struct userinfo *ui, const char *mda_command) +{ + char rtoken[MAXTOKENLEN]; + char tmp[EXPAND_BUFFER]; + const char *string; + char *lbracket, *rbracket, *content, *sep, *mods; + ssize_t i; + ssize_t begoff, endoff; + const char *errstr = NULL; + int replace = 1; + int raw = 0; + + begoff = 0; + endoff = EXPAND_BUFFER; + mods = NULL; + + if (strlcpy(rtoken, token, sizeof rtoken) >= sizeof rtoken) + return -1; + + /* token[x[:y]] -> extracts optional x and y, converts into offsets */ + if ((lbracket = strchr(rtoken, '[')) && + (rbracket = strchr(rtoken, ']'))) { + /* ] before [ ... or empty */ + if (rbracket < lbracket || rbracket - lbracket <= 1) + return -1; + + *lbracket = *rbracket = '\0'; + content = lbracket + 1; + + if ((sep = strchr(content, ':')) == NULL) + endoff = begoff = strtonum(content, -EXPAND_BUFFER, + EXPAND_BUFFER, &errstr); + else { + *sep = '\0'; + if (content != sep) + begoff = strtonum(content, -EXPAND_BUFFER, + EXPAND_BUFFER, &errstr); + if (*(++sep)) { + if (errstr == NULL) + endoff = strtonum(sep, -EXPAND_BUFFER, + EXPAND_BUFFER, &errstr); + } + } + if (errstr) + return -1; + + /* token:mod_1,mod_2,mod_n -> extract modifiers */ + mods = strchr(rbracket + 1, ':'); + } else { + if ((mods = strchr(rtoken, ':')) != NULL) + *mods++ = '\0'; + } + + /* token -> expanded token */ + if (!strcasecmp("sender", rtoken)) { + if (snprintf(tmp, sizeof tmp, "%s@%s", + dlv->sender.user, dlv->sender.domain) >= (int)sizeof tmp) + return -1; + if (strcmp(tmp, "@") == 0) + (void)strlcpy(tmp, "", sizeof tmp); + string = tmp; + } + else if (!strcasecmp("rcpt", rtoken)) { + if (snprintf(tmp, sizeof tmp, "%s@%s", + dlv->rcpt.user, dlv->rcpt.domain) >= (int)sizeof tmp) + return -1; + if (strcmp(tmp, "@") == 0) + (void)strlcpy(tmp, "", sizeof tmp); + string = tmp; + } + else if (!strcasecmp("dest", rtoken)) { + if (snprintf(tmp, sizeof tmp, "%s@%s", + dlv->dest.user, dlv->dest.domain) >= (int)sizeof tmp) + return -1; + if (strcmp(tmp, "@") == 0) + (void)strlcpy(tmp, "", sizeof tmp); + string = tmp; + } + else if (!strcasecmp("sender.user", rtoken)) + string = dlv->sender.user; + else if (!strcasecmp("sender.domain", rtoken)) + string = dlv->sender.domain; + else if (!strcasecmp("user.username", rtoken)) + string = ui->username; + else if (!strcasecmp("user.directory", rtoken)) { + string = ui->directory; + replace = 0; + } + else if (!strcasecmp("rcpt.user", rtoken)) + string = dlv->rcpt.user; + else if (!strcasecmp("rcpt.domain", rtoken)) + string = dlv->rcpt.domain; + else if (!strcasecmp("dest.user", rtoken)) + string = dlv->dest.user; + else if (!strcasecmp("dest.domain", rtoken)) + string = dlv->dest.domain; + else if (!strcasecmp("mda", rtoken)) { + string = mda_command; + replace = 0; + } + else if (!strcasecmp("mbox.from", rtoken)) { + if (snprintf(tmp, sizeof tmp, "%s@%s", + dlv->sender.user, dlv->sender.domain) >= (int)sizeof tmp) + return -1; + if (strcmp(tmp, "@") == 0) + (void)strlcpy(tmp, "MAILER-DAEMON", sizeof tmp); + string = tmp; + } + else + return -1; + + if (string != tmp) { + if (strlcpy(tmp, string, sizeof tmp) >= sizeof tmp) + return -1; + string = tmp; + } + + /* apply modifiers */ + if (mods != NULL) { + do { + if ((sep = strchr(mods, '|')) != NULL) + *sep++ = '\0'; + for (i = 0; (size_t)i < nitems(token_modifiers); ++i) { + if (!strcasecmp(token_modifiers[i].name, mods)) { + if (token_modifiers[i].f == NULL) { + raw = 1; + break; + } + if (!token_modifiers[i].f(tmp, sizeof tmp)) + return -1; /* modifier error */ + break; + } + } + if ((size_t)i == nitems(token_modifiers)) + return -1; /* modifier not found */ + } while ((mods = sep) != NULL); + } + + if (!raw && replace) + for (i = 0; (size_t)i < strlen(tmp); ++i) + if (strchr(MAILADDR_ESCAPE, tmp[i])) + tmp[i] = ':'; + + /* expanded string is empty */ + i = strlen(string); + if (i == 0) + return 0; + + /* begin offset beyond end of string */ + if (begoff >= i) + return -1; + + /* end offset beyond end of string, make it end of string */ + if (endoff >= i) + endoff = i - 1; + + /* negative begin offset, make it relative to end of string */ + if (begoff < 0) + begoff += i; + /* negative end offset, make it relative to end of string, + * note that end offset is inclusive. + */ + if (endoff < 0) + endoff += i - 1; + + /* check that final offsets are valid */ + if (begoff < 0 || endoff < 0 || endoff < begoff) + return -1; + endoff += 1; /* end offset is inclusive */ + + /* check that substring does not exceed destination buffer length */ + i = endoff - begoff; + if ((size_t)i + 1 >= len) + return -1; + + string += begoff; + for (; i; i--) { + *dest = *string; + dest++; + string++; + } + + return endoff - begoff; +} + + +ssize_t +mda_expand_format(char *buf, size_t len, const struct deliver *dlv, + const struct userinfo *ui, const char *mda_command) +{ + char tmpbuf[EXPAND_BUFFER], *ptmp, *pbuf, *ebuf; + char exptok[EXPAND_BUFFER]; + ssize_t exptoklen; + char token[MAXTOKENLEN]; + size_t ret, tmpret; + + if (len < sizeof tmpbuf) { + log_warnx("mda_expand_format: tmp buffer < rule buffer"); + return -1; + } + + memset(tmpbuf, 0, sizeof tmpbuf); + pbuf = buf; + ptmp = tmpbuf; + ret = tmpret = 0; + + /* special case: ~/ only allowed expanded at the beginning */ + if (strncmp(pbuf, "~/", 2) == 0) { + tmpret = snprintf(ptmp, sizeof tmpbuf, "%s/", ui->directory); + if (tmpret >= sizeof tmpbuf) { + log_warnx("warn: user directory for %s too large", + ui->directory); + return 0; + } + ret += tmpret; + ptmp += tmpret; + pbuf += 2; + } + + + /* expansion loop */ + for (; *pbuf && ret < sizeof tmpbuf; ret += tmpret) { + if (*pbuf == '%' && *(pbuf + 1) == '%') { + *ptmp++ = *pbuf++; + pbuf += 1; + tmpret = 1; + continue; + } + + if (*pbuf != '%' || *(pbuf + 1) != '{') { + *ptmp++ = *pbuf++; + tmpret = 1; + continue; + } + + /* %{...} otherwise fail */ + if (*(pbuf+1) != '{' || (ebuf = strchr(pbuf+1, '}')) == NULL) + return 0; + + /* extract token from %{token} */ + if ((size_t)(ebuf - pbuf) - 1 >= sizeof token) + return 0; + + memcpy(token, pbuf+2, ebuf-pbuf-1); + if (strchr(token, '}') == NULL) + return 0; + *strchr(token, '}') = '\0'; + + exptoklen = mda_expand_token(exptok, sizeof exptok, token, dlv, + ui, mda_command); + if (exptoklen == -1) + return -1; + + /* writing expanded token at ptmp will overflow tmpbuf */ + if (sizeof (tmpbuf) - (ptmp - tmpbuf) <= (size_t)exptoklen) + return -1; + + memcpy(ptmp, exptok, exptoklen); + pbuf = ebuf + 1; + ptmp += exptoklen; + tmpret = exptoklen; + } + if (ret >= sizeof tmpbuf) + return -1; + + if ((ret = strlcpy(buf, tmpbuf, len)) >= len) + return -1; + + return ret; +} + +static int +mod_lowercase(char *buf, size_t len) +{ + char tmp[EXPAND_BUFFER]; + + if (!lowercase(tmp, buf, sizeof tmp)) + return 0; + if (strlcpy(buf, tmp, len) >= len) + return 0; + return 1; +} + +static int +mod_uppercase(char *buf, size_t len) +{ + char tmp[EXPAND_BUFFER]; + + if (!uppercase(tmp, buf, sizeof tmp)) + return 0; + if (strlcpy(buf, tmp, len) >= len) + return 0; + return 1; +} + +static int +mod_strip(char *buf, size_t len) +{ + char *tag, *at; + unsigned int i; + + /* gilles+hackers -> gilles */ + if ((tag = strchr(buf, *env->sc_subaddressing_delim)) != NULL) { + /* gilles+hackers@poolp.org -> gilles@poolp.org */ + if ((at = strchr(tag, '@')) != NULL) { + for (i = 0; i <= strlen(at); ++i) + tag[i] = at[i]; + } else + *tag = '\0'; + } + return 1; +} diff --git a/foobar/portable/smtpd/mproc.c b/foobar/portable/smtpd/mproc.c new file mode 100644 index 00000000..bde229e1 --- /dev/null +++ b/foobar/portable/smtpd/mproc.c @@ -0,0 +1,676 @@ +/* $OpenBSD: mproc.c,v 1.36 2020/03/17 09:01:53 tobhe Exp $ */ + +/* + * Copyright (c) 2012 Eric Faurot <eric@faurot.net> + * + * 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 "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/tree.h> +#include <sys/queue.h> +#include <sys/uio.h> + +#include <netinet/in.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> + +#include <err.h> +#include <errno.h> +#include <event.h> +#include <imsg.h> +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <string.h> +#include <unistd.h> + +#include "smtpd.h" +#include "log.h" + +static void mproc_dispatch(int, short, void *); + +static ssize_t imsg_read_nofd(struct imsgbuf *); + +int +mproc_fork(struct mproc *p, const char *path, char *argv[]) +{ + int sp[2]; + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1) + return (-1); + + io_set_nonblocking(sp[0]); + io_set_nonblocking(sp[1]); + + if ((p->pid = fork()) == -1) + goto err; + + if (p->pid == 0) { + /* child process */ + dup2(sp[0], STDIN_FILENO); + closefrom(STDERR_FILENO + 1); + execv(path, argv); + err(1, "execv: %s", path); + } + + /* parent process */ + close(sp[0]); + mproc_init(p, sp[1]); + return (0); + +err: + log_warn("warn: Failed to start process %s, instance of %s", argv[0], path); + close(sp[0]); + close(sp[1]); + return (-1); +} + +void +mproc_init(struct mproc *p, int fd) +{ + imsg_init(&p->imsgbuf, fd); +} + +void +mproc_clear(struct mproc *p) +{ + log_debug("debug: clearing p=%s, fd=%d, pid=%d", p->name, p->imsgbuf.fd, p->pid); + + event_del(&p->ev); + close(p->imsgbuf.fd); + imsg_clear(&p->imsgbuf); +} + +void +mproc_enable(struct mproc *p) +{ + if (p->enable == 0) { + log_trace(TRACE_MPROC, "mproc: %s -> %s: enabled", + proc_name(smtpd_process), + proc_name(p->proc)); + p->enable = 1; + } + mproc_event_add(p); +} + +void +mproc_disable(struct mproc *p) +{ + if (p->enable == 1) { + log_trace(TRACE_MPROC, "mproc: %s -> %s: disabled", + proc_name(smtpd_process), + proc_name(p->proc)); + p->enable = 0; + } + mproc_event_add(p); +} + +void +mproc_event_add(struct mproc *p) +{ + short events; + + if (p->enable) + events = EV_READ; + else + events = 0; + + if (p->imsgbuf.w.queued) + events |= EV_WRITE; + + if (p->events) + event_del(&p->ev); + + p->events = events; + if (events) { + event_set(&p->ev, p->imsgbuf.fd, events, mproc_dispatch, p); + event_add(&p->ev, NULL); + } +} + +static void +mproc_dispatch(int fd, short event, void *arg) +{ + struct mproc *p = arg; + struct imsg imsg; + ssize_t n; + + p->events = 0; + + if (event & EV_READ) { + + if (p->proc == PROC_CLIENT) + n = imsg_read_nofd(&p->imsgbuf); + else + n = imsg_read(&p->imsgbuf); + + switch (n) { + case -1: + if (errno == EAGAIN) + break; + log_warn("warn: %s -> %s: imsg_read", + proc_name(smtpd_process), p->name); + fatal("exiting"); + /* NOTREACHED */ + case 0: + /* this pipe is dead, so remove the event handler */ + log_debug("debug: %s -> %s: pipe closed", + proc_name(smtpd_process), p->name); + p->handler(p, NULL); + return; + default: + break; + } + } + + if (event & EV_WRITE) { + n = msgbuf_write(&p->imsgbuf.w); + if (n == 0 || (n == -1 && errno != EAGAIN)) { + /* this pipe is dead, so remove the event handler */ + log_debug("debug: %s -> %s: pipe closed", + proc_name(smtpd_process), p->name); + p->handler(p, NULL); + return; + } + } + + for (;;) { + if ((n = imsg_get(&p->imsgbuf, &imsg)) == -1) { + + if (smtpd_process == PROC_CONTROL && + p->proc == PROC_CLIENT) { + log_warnx("warn: client sent invalid imsg " + "over control socket"); + p->handler(p, NULL); + return; + } + log_warn("fatal: %s: error in imsg_get for %s", + proc_name(smtpd_process), p->name); + fatalx(NULL); + } + if (n == 0) + break; + + p->handler(p, &imsg); + + imsg_free(&imsg); + } + + mproc_event_add(p); +} + +/* This should go into libutil */ +static ssize_t +imsg_read_nofd(struct imsgbuf *ibuf) +{ + ssize_t n; + char *buf; + size_t len; + + buf = ibuf->r.buf + ibuf->r.wpos; + len = sizeof(ibuf->r.buf) - ibuf->r.wpos; + + while ((n = recv(ibuf->fd, buf, len, 0)) == -1) { + if (errno != EINTR) + return (n); + } + + ibuf->r.wpos += n; + return (n); +} + +void +m_forward(struct mproc *p, struct imsg *imsg) +{ + imsg_compose(&p->imsgbuf, imsg->hdr.type, imsg->hdr.peerid, + imsg->hdr.pid, imsg->fd, imsg->data, + imsg->hdr.len - sizeof(imsg->hdr)); + + if (imsg->hdr.type != IMSG_STAT_DECREMENT && + imsg->hdr.type != IMSG_STAT_INCREMENT) + log_trace(TRACE_MPROC, "mproc: %s -> %s : %zu %s (forward)", + proc_name(smtpd_process), + proc_name(p->proc), + imsg->hdr.len - sizeof(imsg->hdr), + imsg_to_str(imsg->hdr.type)); + + mproc_event_add(p); +} + +void +m_compose(struct mproc *p, uint32_t type, uint32_t peerid, pid_t pid, int fd, + void *data, size_t len) +{ + imsg_compose(&p->imsgbuf, type, peerid, pid, fd, data, len); + + if (type != IMSG_STAT_DECREMENT && + type != IMSG_STAT_INCREMENT) + log_trace(TRACE_MPROC, "mproc: %s -> %s : %zu %s", + proc_name(smtpd_process), + proc_name(p->proc), + len, + imsg_to_str(type)); + + mproc_event_add(p); +} + +void +m_composev(struct mproc *p, uint32_t type, uint32_t peerid, pid_t pid, + int fd, const struct iovec *iov, int n) +{ + size_t len; + int i; + + imsg_composev(&p->imsgbuf, type, peerid, pid, fd, iov, n); + + len = 0; + for (i = 0; i < n; i++) + len += iov[i].iov_len; + + if (type != IMSG_STAT_DECREMENT && + type != IMSG_STAT_INCREMENT) + log_trace(TRACE_MPROC, "mproc: %s -> %s : %zu %s", + proc_name(smtpd_process), + proc_name(p->proc), + len, + imsg_to_str(type)); + + mproc_event_add(p); +} + +void +m_create(struct mproc *p, uint32_t type, uint32_t peerid, pid_t pid, int fd) +{ + p->m_pos = 0; + p->m_type = type; + p->m_peerid = peerid; + p->m_pid = pid; + p->m_fd = fd; +} + +void +m_add(struct mproc *p, const void *data, size_t len) +{ + size_t alloc; + void *tmp; + + if (p->m_pos + len + IMSG_HEADER_SIZE > MAX_IMSGSIZE) { + log_warnx("warn: message too large"); + fatal(NULL); + } + + alloc = p->m_alloc ? p->m_alloc : 128; + while (p->m_pos + len > alloc) + alloc *= 2; + if (alloc != p->m_alloc) { + log_trace(TRACE_MPROC, "mproc: %s -> %s: realloc %zu -> %zu", + proc_name(smtpd_process), + proc_name(p->proc), + p->m_alloc, + alloc); + + tmp = recallocarray(p->m_buf, p->m_alloc, alloc, 1); + if (tmp == NULL) + fatal("realloc"); + p->m_alloc = alloc; + p->m_buf = tmp; + } + + memmove(p->m_buf + p->m_pos, data, len); + p->m_pos += len; +} + +void +m_close(struct mproc *p) +{ + if (imsg_compose(&p->imsgbuf, p->m_type, p->m_peerid, p->m_pid, p->m_fd, + p->m_buf, p->m_pos) == -1) + fatal("imsg_compose"); + + log_trace(TRACE_MPROC, "mproc: %s -> %s : %zu %s", + proc_name(smtpd_process), + proc_name(p->proc), + p->m_pos, + imsg_to_str(p->m_type)); + + mproc_event_add(p); +} + +void +m_flush(struct mproc *p) +{ + if (imsg_compose(&p->imsgbuf, p->m_type, p->m_peerid, p->m_pid, p->m_fd, + p->m_buf, p->m_pos) == -1) + fatal("imsg_compose"); + + log_trace(TRACE_MPROC, "mproc: %s -> %s : %zu %s (flush)", + proc_name(smtpd_process), + proc_name(p->proc), + p->m_pos, + imsg_to_str(p->m_type)); + + p->m_pos = 0; + + if (imsg_flush(&p->imsgbuf) == -1) + fatal("imsg_flush"); +} + +static struct imsg * current; + +static void +m_error(const char *error) +{ + char buf[512]; + + (void)snprintf(buf, sizeof buf, "%s: %s: %s", + proc_name(smtpd_process), + imsg_to_str(current->hdr.type), + error); + fatalx("%s", buf); +} + +void +m_msg(struct msg *m, struct imsg *imsg) +{ + current = imsg; + m->pos = imsg->data; + m->end = m->pos + (imsg->hdr.len - sizeof(imsg->hdr)); +} + +void +m_end(struct msg *m) +{ + if (m->pos != m->end) + m_error("not at msg end"); +} + +int +m_is_eom(struct msg *m) +{ + return (m->pos == m->end); +} + +static inline void +m_get(struct msg *m, void *dst, size_t sz) +{ + if (sz > MAX_IMSGSIZE || + m->end - m->pos < (ssize_t)sz) + fatalx("msg too short"); + + memmove(dst, m->pos, sz); + m->pos += sz; +} + +void +m_add_int(struct mproc *m, int v) +{ + m_add(m, &v, sizeof(v)); +}; + +void +m_add_u32(struct mproc *m, uint32_t u32) +{ + m_add(m, &u32, sizeof(u32)); +}; + +void +m_add_size(struct mproc *m, size_t sz) +{ + m_add(m, &sz, sizeof(sz)); +}; + +void +m_add_time(struct mproc *m, time_t v) +{ + m_add(m, &v, sizeof(v)); +}; + +void +m_add_timeval(struct mproc *m, struct timeval *tv) +{ + m_add(m, tv, sizeof(*tv)); +} + + +void +m_add_string(struct mproc *m, const char *v) +{ + if (v) { + m_add(m, "s", 1); + m_add(m, v, strlen(v) + 1); + } + else + m_add(m, "\0", 1); +}; + +void +m_add_data(struct mproc *m, const void *v, size_t len) +{ + m_add_size(m, len); + m_add(m, v, len); +}; + +void +m_add_id(struct mproc *m, uint64_t v) +{ + m_add(m, &v, sizeof(v)); +} + +void +m_add_evpid(struct mproc *m, uint64_t v) +{ + m_add(m, &v, sizeof(v)); +} + +void +m_add_msgid(struct mproc *m, uint32_t v) +{ + m_add(m, &v, sizeof(v)); +} + +void +m_add_sockaddr(struct mproc *m, const struct sockaddr *sa) +{ + m_add_size(m, SA_LEN(sa)); + m_add(m, sa, SA_LEN(sa)); +} + +void +m_add_mailaddr(struct mproc *m, const struct mailaddr *maddr) +{ + m_add(m, maddr, sizeof(*maddr)); +} + +void +m_add_envelope(struct mproc *m, const struct envelope *evp) +{ + char buf[sizeof(*evp)]; + + envelope_dump_buffer(evp, buf, sizeof(buf)); + m_add_evpid(m, evp->id); + m_add_string(m, buf); +} + +void +m_add_params(struct mproc *m, struct dict *d) +{ + const char *key; + char *value; + void *iter; + + if (d == NULL) { + m_add_size(m, 0); + return; + } + m_add_size(m, dict_count(d)); + iter = NULL; + while (dict_iter(d, &iter, &key, (void **)&value)) { + m_add_string(m, key); + m_add_string(m, value); + } +} + +void +m_get_int(struct msg *m, int *i) +{ + m_get(m, i, sizeof(*i)); +} + +void +m_get_u32(struct msg *m, uint32_t *u32) +{ + m_get(m, u32, sizeof(*u32)); +} + +void +m_get_size(struct msg *m, size_t *sz) +{ + m_get(m, sz, sizeof(*sz)); +} + +void +m_get_time(struct msg *m, time_t *t) +{ + m_get(m, t, sizeof(*t)); +} + +void +m_get_timeval(struct msg *m, struct timeval *tv) +{ + m_get(m, tv, sizeof(*tv)); +} + +void +m_get_string(struct msg *m, const char **s) +{ + uint8_t *end; + char c; + + if (m->pos >= m->end) + m_error("msg too short"); + + c = *m->pos++; + if (c == '\0') { + *s = NULL; + return; + } + + if (m->pos >= m->end) + m_error("msg too short"); + end = memchr(m->pos, 0, m->end - m->pos); + if (end == NULL) + m_error("unterminated string"); + + *s = m->pos; + m->pos = end + 1; +} + +void +m_get_data(struct msg *m, const void **data, size_t *sz) +{ + m_get_size(m, sz); + + if (*sz == 0) { + *data = NULL; + return; + } + + if (m->pos + *sz > m->end) + m_error("msg too short"); + + *data = m->pos; + m->pos += *sz; +} + +void +m_get_evpid(struct msg *m, uint64_t *evpid) +{ + m_get(m, evpid, sizeof(*evpid)); +} + +void +m_get_msgid(struct msg *m, uint32_t *msgid) +{ + m_get(m, msgid, sizeof(*msgid)); +} + +void +m_get_id(struct msg *m, uint64_t *id) +{ + m_get(m, id, sizeof(*id)); +} + +void +m_get_sockaddr(struct msg *m, struct sockaddr *sa) +{ + size_t len; + + m_get_size(m, &len); + m_get(m, sa, len); +} + +void +m_get_mailaddr(struct msg *m, struct mailaddr *maddr) +{ + m_get(m, maddr, sizeof(*maddr)); +} + +void +m_get_envelope(struct msg *m, struct envelope *evp) +{ + uint64_t evpid; + const char *buf; + + m_get_evpid(m, &evpid); + m_get_string(m, &buf); + if (buf == NULL) + fatalx("empty envelope buffer"); + + if (!envelope_load_buffer(evp, buf, strlen(buf))) + fatalx("failed to retrieve envelope"); + evp->id = evpid; +} + +void +m_get_params(struct msg *m, struct dict *d) +{ + size_t c; + const char *key; + const char *value; + char *tmp; + + dict_init(d); + + m_get_size(m, &c); + + for (; c; c--) { + m_get_string(m, &key); + m_get_string(m, &value); + if ((tmp = strdup(value)) == NULL) + fatal("m_get_params"); + dict_set(d, key, tmp); + } +} + +void +m_clear_params(struct dict *d) +{ + char *value; + + while (dict_poproot(d, (void **)&value)) + free(value); +} diff --git a/foobar/portable/smtpd/mta.c b/foobar/portable/smtpd/mta.c new file mode 100644 index 00000000..922170ae --- /dev/null +++ b/foobar/portable/smtpd/mta.c @@ -0,0 +1,2647 @@ +/* $OpenBSD: mta.c,v 1.234 2019/12/21 10:34:07 gilles Exp $ */ + +/* + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> + * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <imsg.h> +#include <inttypes.h> +#include <netdb.h> +#include <grp.h> /* needed for setgroups */ +#include <limits.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "smtpd.h" +#include "log.h" + +#define MAXERROR_PER_ROUTE 4 + +#define DELAY_CHECK_SOURCE 1 +#define DELAY_CHECK_SOURCE_SLOW 10 +#define DELAY_CHECK_SOURCE_FAST 0 +#define DELAY_CHECK_LIMIT 5 + +#define DELAY_QUADRATIC 1 +#define DELAY_ROUTE_BASE 15 +#define DELAY_ROUTE_MAX 3600 + +#define RELAY_ONHOLD 0x01 +#define RELAY_HOLDQ 0x02 + +static void mta_handle_envelope(struct envelope *, const char *); +static void mta_query_smarthost(struct envelope *); +static void mta_on_smarthost(struct envelope *, const char *); +static void mta_query_mx(struct mta_relay *); +static void mta_query_secret(struct mta_relay *); +static void mta_query_preference(struct mta_relay *); +static void mta_query_source(struct mta_relay *); +static void mta_on_mx(void *, void *, void *); +static void mta_on_secret(struct mta_relay *, const char *); +static void mta_on_preference(struct mta_relay *, int); +static void mta_on_source(struct mta_relay *, struct mta_source *); +static void mta_on_timeout(struct runq *, void *); +static void mta_connect(struct mta_connector *); +static void mta_route_enable(struct mta_route *); +static void mta_route_disable(struct mta_route *, int, int); +static void mta_drain(struct mta_relay *); +static void mta_delivery_flush_event(int, short, void *); +static void mta_flush(struct mta_relay *, int, const char *); +static struct mta_route *mta_find_route(struct mta_connector *, time_t, int*, + time_t*, struct mta_mx **); +static void mta_log(const struct mta_envelope *, const char *, const char *, + const char *, const char *); + +SPLAY_HEAD(mta_relay_tree, mta_relay); +static struct mta_relay *mta_relay(struct envelope *, struct relayhost *); +static void mta_relay_ref(struct mta_relay *); +static void mta_relay_unref(struct mta_relay *); +static void mta_relay_show(struct mta_relay *, struct mproc *, uint32_t, time_t); +static int mta_relay_cmp(const struct mta_relay *, const struct mta_relay *); +SPLAY_PROTOTYPE(mta_relay_tree, mta_relay, entry, mta_relay_cmp); + +SPLAY_HEAD(mta_host_tree, mta_host); +static struct mta_host *mta_host(const struct sockaddr *); +static void mta_host_ref(struct mta_host *); +static void mta_host_unref(struct mta_host *); +static int mta_host_cmp(const struct mta_host *, const struct mta_host *); +SPLAY_PROTOTYPE(mta_host_tree, mta_host, entry, mta_host_cmp); + +SPLAY_HEAD(mta_domain_tree, mta_domain); +static struct mta_domain *mta_domain(char *, int); +#if 0 +static void mta_domain_ref(struct mta_domain *); +#endif +static void mta_domain_unref(struct mta_domain *); +static int mta_domain_cmp(const struct mta_domain *, const struct mta_domain *); +SPLAY_PROTOTYPE(mta_domain_tree, mta_domain, entry, mta_domain_cmp); + +SPLAY_HEAD(mta_source_tree, mta_source); +static struct mta_source *mta_source(const struct sockaddr *); +static void mta_source_ref(struct mta_source *); +static void mta_source_unref(struct mta_source *); +static const char *mta_source_to_text(struct mta_source *); +static int mta_source_cmp(const struct mta_source *, const struct mta_source *); +SPLAY_PROTOTYPE(mta_source_tree, mta_source, entry, mta_source_cmp); + +static struct mta_connector *mta_connector(struct mta_relay *, + struct mta_source *); +static void mta_connector_free(struct mta_connector *); +static const char *mta_connector_to_text(struct mta_connector *); + +SPLAY_HEAD(mta_route_tree, mta_route); +static struct mta_route *mta_route(struct mta_source *, struct mta_host *); +static void mta_route_ref(struct mta_route *); +static void mta_route_unref(struct mta_route *); +static const char *mta_route_to_text(struct mta_route *); +static int mta_route_cmp(const struct mta_route *, const struct mta_route *); +SPLAY_PROTOTYPE(mta_route_tree, mta_route, entry, mta_route_cmp); + +struct mta_block { + SPLAY_ENTRY(mta_block) entry; + struct mta_source *source; + char *domain; +}; + +SPLAY_HEAD(mta_block_tree, mta_block); +void mta_block(struct mta_source *, char *); +void mta_unblock(struct mta_source *, char *); +int mta_is_blocked(struct mta_source *, char *); +static int mta_block_cmp(const struct mta_block *, const struct mta_block *); +SPLAY_PROTOTYPE(mta_block_tree, mta_block, entry, mta_block_cmp); + +static struct mta_relay_tree relays; +static struct mta_domain_tree domains; +static struct mta_host_tree hosts; +static struct mta_source_tree sources; +static struct mta_route_tree routes; +static struct mta_block_tree blocks; + +static struct tree wait_mx; +static struct tree wait_preference; +static struct tree wait_secret; +static struct tree wait_smarthost; +static struct tree wait_source; +static struct tree flush_evp; +static struct event ev_flush_evp; + +static struct runq *runq_relay; +static struct runq *runq_connector; +static struct runq *runq_route; +static struct runq *runq_hoststat; + +static time_t max_seen_conndelay_route; +static time_t max_seen_discdelay_route; + +#define HOSTSTAT_EXPIRE_DELAY (4 * 3600) +struct hoststat { + char name[HOST_NAME_MAX+1]; + time_t tm; + char error[LINE_MAX]; + struct tree deferred; +}; +static struct dict hoststat; + +void mta_hoststat_update(const char *, const char *); +void mta_hoststat_cache(const char *, uint64_t); +void mta_hoststat_uncache(const char *, uint64_t); +void mta_hoststat_reschedule(const char *); +static void mta_hoststat_remove_entry(struct hoststat *); + +void +mta_imsg(struct mproc *p, struct imsg *imsg) +{ + struct mta_relay *relay; + struct mta_domain *domain; + struct mta_host *host; + struct mta_route *route; + struct mta_block *block; + struct mta_mx *mx, *imx; + struct mta_source *source; + struct hoststat *hs; + struct sockaddr_storage ss; + struct envelope evp, *e; + struct msg m; + const char *secret; + const char *hostname; + const char *dom; + const char *smarthost; + uint64_t reqid; + time_t t; + char buf[LINE_MAX]; + int dnserror, preference, v, status; + void *iter; + uint64_t u64; + + switch (imsg->hdr.type) { + case IMSG_QUEUE_TRANSFER: + m_msg(&m, imsg); + m_get_envelope(&m, &evp); + m_end(&m); + mta_handle_envelope(&evp, NULL); + return; + + case IMSG_MTA_OPEN_MESSAGE: + mta_session_imsg(p, imsg); + return; + + case IMSG_MTA_LOOKUP_CREDENTIALS: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_string(&m, &secret); + m_end(&m); + relay = tree_xpop(&wait_secret, reqid); + mta_on_secret(relay, secret[0] ? secret : NULL); + return; + + case IMSG_MTA_LOOKUP_SOURCE: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &status); + if (status == LKA_OK) + m_get_sockaddr(&m, (struct sockaddr*)&ss); + m_end(&m); + + relay = tree_xpop(&wait_source, reqid); + mta_on_source(relay, (status == LKA_OK) ? + mta_source((struct sockaddr *)&ss) : NULL); + return; + + case IMSG_MTA_LOOKUP_SMARTHOST: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &status); + smarthost = NULL; + if (status == LKA_OK) + m_get_string(&m, &smarthost); + m_end(&m); + + e = tree_xpop(&wait_smarthost, reqid); + mta_on_smarthost(e, smarthost); + return; + + case IMSG_MTA_LOOKUP_HELO: + mta_session_imsg(p, imsg); + return; + + case IMSG_MTA_DNS_HOST: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_string(&m, &hostname); + m_get_sockaddr(&m, (struct sockaddr*)&ss); + m_get_int(&m, &preference); + m_end(&m); + domain = tree_xget(&wait_mx, reqid); + mx = xcalloc(1, sizeof *mx); + mx->mxname = xstrdup(hostname); + mx->host = mta_host((struct sockaddr*)&ss); + mx->preference = preference; + TAILQ_FOREACH(imx, &domain->mxs, entry) { + if (imx->preference > mx->preference) { + TAILQ_INSERT_BEFORE(imx, mx, entry); + return; + } + } + TAILQ_INSERT_TAIL(&domain->mxs, mx, entry); + return; + + case IMSG_MTA_DNS_HOST_END: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &dnserror); + m_end(&m); + domain = tree_xpop(&wait_mx, reqid); + domain->mxstatus = dnserror; + if (domain->mxstatus == DNS_OK) { + log_debug("debug: MXs for domain %s:", + domain->name); + TAILQ_FOREACH(mx, &domain->mxs, entry) + log_debug(" %s preference %d", + sa_to_text(mx->host->sa), + mx->preference); + } + else { + log_debug("debug: Failed MX query for %s:", + domain->name); + } + domain->lastmxquery = time(NULL); + waitq_run(&domain->mxs, domain); + return; + + case IMSG_MTA_DNS_MX_PREFERENCE: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &dnserror); + if (dnserror == 0) + m_get_int(&m, &preference); + m_end(&m); + + relay = tree_xpop(&wait_preference, reqid); + if (dnserror) { + log_warnx("warn: Couldn't find backup " + "preference for %s: error %d", + mta_relay_to_text(relay), dnserror); + preference = INT_MAX; + } + mta_on_preference(relay, preference); + return; + + case IMSG_CTL_RESUME_ROUTE: + u64 = *((uint64_t *)imsg->data); + if (u64) + log_debug("resuming route: %llu", + (unsigned long long)u64); + else + log_debug("resuming all routes"); + SPLAY_FOREACH(route, mta_route_tree, &routes) { + if (u64 && route->id != u64) + continue; + + if (route->flags & ROUTE_DISABLED) { + log_info("smtp-out: Enabling route %s per admin request", + mta_route_to_text(route)); + if (!runq_cancel(runq_route, route)) { + log_warnx("warn: route not on runq"); + fatalx("exiting"); + } + route->flags &= ~ROUTE_DISABLED; + route->flags |= ROUTE_NEW; + route->nerror = 0; + route->penalty = 0; + mta_route_unref(route); /* from mta_route_disable */ + } + + if (u64) + break; + } + return; + + case IMSG_CTL_MTA_SHOW_HOSTS: + t = time(NULL); + SPLAY_FOREACH(host, mta_host_tree, &hosts) { + (void)snprintf(buf, sizeof(buf), + "%s %s refcount=%d nconn=%zu lastconn=%s", + sockaddr_to_text(host->sa), + host->ptrname, + host->refcount, + host->nconn, + host->lastconn ? duration_to_text(t - host->lastconn) : "-"); + m_compose(p, IMSG_CTL_MTA_SHOW_HOSTS, + imsg->hdr.peerid, 0, -1, + buf, strlen(buf) + 1); + } + m_compose(p, IMSG_CTL_MTA_SHOW_HOSTS, imsg->hdr.peerid, + 0, -1, NULL, 0); + return; + + case IMSG_CTL_MTA_SHOW_RELAYS: + t = time(NULL); + SPLAY_FOREACH(relay, mta_relay_tree, &relays) + mta_relay_show(relay, p, imsg->hdr.peerid, t); + m_compose(p, IMSG_CTL_MTA_SHOW_RELAYS, imsg->hdr.peerid, + 0, -1, NULL, 0); + return; + + case IMSG_CTL_MTA_SHOW_ROUTES: + SPLAY_FOREACH(route, mta_route_tree, &routes) { + v = runq_pending(runq_route, route, &t); + (void)snprintf(buf, sizeof(buf), + "%llu. %s %c%c%c%c nconn=%zu nerror=%d penalty=%d timeout=%s", + (unsigned long long)route->id, + mta_route_to_text(route), + route->flags & ROUTE_NEW ? 'N' : '-', + route->flags & ROUTE_DISABLED ? 'D' : '-', + route->flags & ROUTE_RUNQ ? 'Q' : '-', + route->flags & ROUTE_KEEPALIVE ? 'K' : '-', + route->nconn, + route->nerror, + route->penalty, + v ? duration_to_text(t - time(NULL)) : "-"); + m_compose(p, IMSG_CTL_MTA_SHOW_ROUTES, + imsg->hdr.peerid, 0, -1, + buf, strlen(buf) + 1); + } + m_compose(p, IMSG_CTL_MTA_SHOW_ROUTES, imsg->hdr.peerid, + 0, -1, NULL, 0); + return; + + case IMSG_CTL_MTA_SHOW_HOSTSTATS: + iter = NULL; + while (dict_iter(&hoststat, &iter, &hostname, + (void **)&hs)) { + (void)snprintf(buf, sizeof(buf), + "%s|%llu|%s", + hostname, (unsigned long long) hs->tm, + hs->error); + m_compose(p, IMSG_CTL_MTA_SHOW_HOSTSTATS, + imsg->hdr.peerid, 0, -1, + buf, strlen(buf) + 1); + } + m_compose(p, IMSG_CTL_MTA_SHOW_HOSTSTATS, + imsg->hdr.peerid, + 0, -1, NULL, 0); + return; + + case IMSG_CTL_MTA_BLOCK: + m_msg(&m, imsg); + m_get_sockaddr(&m, (struct sockaddr*)&ss); + m_get_string(&m, &dom); + m_end(&m); + source = mta_source((struct sockaddr*)&ss); + if (*dom != '\0') { + if (!(strlcpy(buf, dom, sizeof(buf)) + >= sizeof(buf))) + mta_block(source, buf); + } + else + mta_block(source, NULL); + mta_source_unref(source); + m_compose(p, IMSG_CTL_OK, imsg->hdr.peerid, 0, -1, NULL, 0); + return; + + case IMSG_CTL_MTA_UNBLOCK: + m_msg(&m, imsg); + m_get_sockaddr(&m, (struct sockaddr*)&ss); + m_get_string(&m, &dom); + m_end(&m); + source = mta_source((struct sockaddr*)&ss); + if (*dom != '\0') { + if (!(strlcpy(buf, dom, sizeof(buf)) + >= sizeof(buf))) + mta_unblock(source, buf); + } + else + mta_unblock(source, NULL); + mta_source_unref(source); + m_compose(p, IMSG_CTL_OK, imsg->hdr.peerid, 0, -1, NULL, 0); + return; + + case IMSG_CTL_MTA_SHOW_BLOCK: + SPLAY_FOREACH(block, mta_block_tree, &blocks) { + (void)snprintf(buf, sizeof(buf), "%s -> %s", + mta_source_to_text(block->source), + block->domain ? block->domain : "*"); + m_compose(p, IMSG_CTL_MTA_SHOW_BLOCK, + imsg->hdr.peerid, 0, -1, buf, strlen(buf) + 1); + } + m_compose(p, IMSG_CTL_MTA_SHOW_BLOCK, imsg->hdr.peerid, + 0, -1, NULL, 0); + return; + } + + errx(1, "mta_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); +} + +void +mta_postfork(void) +{ +} + +void +mta_postprivdrop(void) +{ + SPLAY_INIT(&relays); + SPLAY_INIT(&domains); + SPLAY_INIT(&hosts); + SPLAY_INIT(&sources); + SPLAY_INIT(&routes); + SPLAY_INIT(&blocks); + + tree_init(&wait_secret); + tree_init(&wait_smarthost); + tree_init(&wait_mx); + tree_init(&wait_preference); + tree_init(&wait_source); + tree_init(&flush_evp); + dict_init(&hoststat); + + evtimer_set(&ev_flush_evp, mta_delivery_flush_event, NULL); + + runq_init(&runq_relay, mta_on_timeout); + runq_init(&runq_connector, mta_on_timeout); + runq_init(&runq_route, mta_on_timeout); + runq_init(&runq_hoststat, mta_on_timeout); +} + + +/* + * Local error on the given source. + */ +void +mta_source_error(struct mta_relay *relay, struct mta_route *route, const char *e) +{ + struct mta_connector *c; + + /* + * Remember the source as broken for this connector. + */ + c = mta_connector(relay, route->src); + if (!(c->flags & CONNECTOR_ERROR_SOURCE)) + log_info("smtp-out: Error on %s: %s", + mta_route_to_text(route), e); + c->flags |= CONNECTOR_ERROR_SOURCE; +} + +void +mta_route_error(struct mta_relay *relay, struct mta_route *route) +{ +#if 0 + route->nerror += 1; + + if (route->nerror > MAXERROR_PER_ROUTE) { + log_info("smtp-out: Too many errors on %s: " + "disabling for a while", mta_route_to_text(route)); + mta_route_disable(route, 2, ROUTE_DISABLED_SMTP); + } +#endif +} + +void +mta_route_ok(struct mta_relay *relay, struct mta_route *route) +{ + struct mta_connector *c; + + if (!(route->flags & ROUTE_NEW)) + return; + + log_debug("debug: mta-routing: route %s is now valid.", + mta_route_to_text(route)); + + route->nerror = 0; + route->flags &= ~ROUTE_NEW; + + c = mta_connector(relay, route->src); + mta_connect(c); +} + +void +mta_route_down(struct mta_relay *relay, struct mta_route *route) +{ +#if 0 + mta_route_disable(route, 2, ROUTE_DISABLED_SMTP); +#endif +} + +void +mta_route_collect(struct mta_relay *relay, struct mta_route *route) +{ + struct mta_connector *c; + + log_debug("debug: mta_route_collect(%s)", + mta_route_to_text(route)); + + relay->nconn -= 1; + relay->domain->nconn -= 1; + route->nconn -= 1; + route->src->nconn -= 1; + route->dst->nconn -= 1; + route->lastdisc = time(NULL); + + /* First connection failed */ + if (route->flags & ROUTE_NEW) + mta_route_disable(route, 1, ROUTE_DISABLED_NET); + + c = mta_connector(relay, route->src); + c->nconn -= 1; + mta_connect(c); + mta_route_unref(route); /* from mta_find_route() */ + mta_relay_unref(relay); /* from mta_connect() */ +} + +struct mta_task * +mta_route_next_task(struct mta_relay *relay, struct mta_route *route) +{ + struct mta_task *task; + + if ((task = TAILQ_FIRST(&relay->tasks))) { + TAILQ_REMOVE(&relay->tasks, task, entry); + relay->ntask -= 1; + task->relay = NULL; + + /* When the number of tasks is down to lowat, query some evp */ + if (relay->ntask == (size_t)relay->limits->task_lowat) { + if (relay->state & RELAY_ONHOLD) { + log_info("smtp-out: back to lowat on %s: releasing", + mta_relay_to_text(relay)); + relay->state &= ~RELAY_ONHOLD; + } + if (relay->state & RELAY_HOLDQ) { + m_create(p_queue, IMSG_MTA_HOLDQ_RELEASE, 0, 0, -1); + m_add_id(p_queue, relay->id); + m_add_int(p_queue, relay->limits->task_release); + m_close(p_queue); + } + } + else if (relay->ntask == 0 && relay->state & RELAY_HOLDQ) { + m_create(p_queue, IMSG_MTA_HOLDQ_RELEASE, 0, 0, -1); + m_add_id(p_queue, relay->id); + m_add_int(p_queue, 0); + m_close(p_queue); + } + } + + return (task); +} + +static void +mta_handle_envelope(struct envelope *evp, const char *smarthost) +{ + struct mta_relay *relay; + struct mta_task *task; + struct mta_envelope *e; + struct dispatcher *dispatcher; + struct mailaddr maddr; + struct relayhost relayh; + char buf[LINE_MAX]; + + dispatcher = dict_xget(env->sc_dispatchers, evp->dispatcher); + if (dispatcher->u.remote.smarthost && smarthost == NULL) { + mta_query_smarthost(evp); + return; + } + + memset(&relayh, 0, sizeof(relayh)); + relayh.tls = RELAY_TLS_OPPORTUNISTIC; + if (smarthost && !text_to_relayhost(&relayh, smarthost)) { + log_warnx("warn: Failed to parse smarthost %s", smarthost); + m_create(p_queue, IMSG_MTA_DELIVERY_TEMPFAIL, 0, 0, -1); + m_add_evpid(p_queue, evp->id); + m_add_string(p_queue, "Cannot parse smarthost"); + m_add_int(p_queue, ESC_OTHER_STATUS); + m_close(p_queue); + return; + } + + if (relayh.flags & RELAY_AUTH && dispatcher->u.remote.auth == NULL) { + log_warnx("warn: No auth table on action \"%s\" for relay %s", + evp->dispatcher, smarthost); + m_create(p_queue, IMSG_MTA_DELIVERY_TEMPFAIL, 0, 0, -1); + m_add_evpid(p_queue, evp->id); + m_add_string(p_queue, "No auth table for relaying"); + m_add_int(p_queue, ESC_OTHER_STATUS); + m_close(p_queue); + return; + } + + if (dispatcher->u.remote.tls_required) { + /* Reject relay if smtp+notls:// is requested */ + if (relayh.tls == RELAY_TLS_NO) { + log_warnx("warn: TLS required for action \"%s\"", + evp->dispatcher); + m_create(p_queue, IMSG_MTA_DELIVERY_TEMPFAIL, 0, 0, -1); + m_add_evpid(p_queue, evp->id); + m_add_string(p_queue, "TLS required for relaying"); + m_add_int(p_queue, ESC_OTHER_STATUS); + m_close(p_queue); + return; + } + /* Update smtp:// to smtp+tls:// */ + if (relayh.tls == RELAY_TLS_OPPORTUNISTIC) + relayh.tls = RELAY_TLS_STARTTLS; + } + + relay = mta_relay(evp, &relayh); + /* ignore if we don't know the limits yet */ + if (relay->limits && + relay->ntask >= (size_t)relay->limits->task_hiwat) { + if (!(relay->state & RELAY_ONHOLD)) { + log_info("smtp-out: hiwat reached on %s: holding envelopes", + mta_relay_to_text(relay)); + relay->state |= RELAY_ONHOLD; + } + } + + /* + * If the relay has too many pending tasks, tell the + * scheduler to hold it until further notice + */ + if (relay->state & RELAY_ONHOLD) { + relay->state |= RELAY_HOLDQ; + m_create(p_queue, IMSG_MTA_DELIVERY_HOLD, 0, 0, -1); + m_add_evpid(p_queue, evp->id); + m_add_id(p_queue, relay->id); + m_close(p_queue); + mta_relay_unref(relay); /* from here */ + return; + } + + task = NULL; + TAILQ_FOREACH(task, &relay->tasks, entry) + if (task->msgid == evpid_to_msgid(evp->id)) + break; + + if (task == NULL) { + task = xmalloc(sizeof *task); + TAILQ_INIT(&task->envelopes); + task->relay = relay; + relay->ntask += 1; + TAILQ_INSERT_TAIL(&relay->tasks, task, entry); + task->msgid = evpid_to_msgid(evp->id); + if (evp->sender.user[0] || evp->sender.domain[0]) + (void)snprintf(buf, sizeof buf, "%s@%s", + evp->sender.user, evp->sender.domain); + else + buf[0] = '\0'; + + if (dispatcher->u.remote.mail_from && evp->sender.user[0]) { + memset(&maddr, 0, sizeof (maddr)); + if (text_to_mailaddr(&maddr, + dispatcher->u.remote.mail_from)) { + (void)snprintf(buf, sizeof buf, "%s@%s", + maddr.user[0] ? maddr.user : evp->sender.user, + maddr.domain[0] ? maddr.domain : evp->sender.domain); + } + } + + task->sender = xstrdup(buf); + stat_increment("mta.task", 1); + } + + e = xcalloc(1, sizeof *e); + e->id = evp->id; + e->creation = evp->creation; + e->smtpname = xstrdup(evp->smtpname); + (void)snprintf(buf, sizeof buf, "%s@%s", + evp->dest.user, evp->dest.domain); + e->dest = xstrdup(buf); + (void)snprintf(buf, sizeof buf, "%s@%s", + evp->rcpt.user, evp->rcpt.domain); + if (strcmp(buf, e->dest)) + e->rcpt = xstrdup(buf); + e->task = task; + if (evp->dsn_orcpt.user[0] && evp->dsn_orcpt.domain[0]) { + (void)snprintf(buf, sizeof buf, "%s@%s", + evp->dsn_orcpt.user, evp->dsn_orcpt.domain); + e->dsn_orcpt = xstrdup(buf); + } + (void)strlcpy(e->dsn_envid, evp->dsn_envid, + sizeof e->dsn_envid); + e->dsn_notify = evp->dsn_notify; + e->dsn_ret = evp->dsn_ret; + + TAILQ_INSERT_TAIL(&task->envelopes, e, entry); + log_debug("debug: mta: received evp:%016" PRIx64 + " for <%s>", e->id, e->dest); + + stat_increment("mta.envelope", 1); + + mta_drain(relay); + mta_relay_unref(relay); /* from here */ +} + +static void +mta_delivery_flush_event(int fd, short event, void *arg) +{ + struct mta_envelope *e; + struct timeval tv; + + if (tree_poproot(&flush_evp, NULL, (void**)(&e))) { + + if (e->delivery == IMSG_MTA_DELIVERY_OK) { + m_create(p_queue, IMSG_MTA_DELIVERY_OK, 0, 0, -1); + m_add_evpid(p_queue, e->id); + m_add_int(p_queue, e->ext); + m_close(p_queue); + } else if (e->delivery == IMSG_MTA_DELIVERY_TEMPFAIL) { + m_create(p_queue, IMSG_MTA_DELIVERY_TEMPFAIL, 0, 0, -1); + m_add_evpid(p_queue, e->id); + m_add_string(p_queue, e->status); + m_add_int(p_queue, ESC_OTHER_STATUS); + m_close(p_queue); + } + else if (e->delivery == IMSG_MTA_DELIVERY_PERMFAIL) { + m_create(p_queue, IMSG_MTA_DELIVERY_PERMFAIL, 0, 0, -1); + m_add_evpid(p_queue, e->id); + m_add_string(p_queue, e->status); + m_add_int(p_queue, ESC_OTHER_STATUS); + m_close(p_queue); + } + else if (e->delivery == IMSG_MTA_DELIVERY_LOOP) { + m_create(p_queue, IMSG_MTA_DELIVERY_LOOP, 0, 0, -1); + m_add_evpid(p_queue, e->id); + m_close(p_queue); + } + else { + log_warnx("warn: bad delivery type %d for %016" PRIx64, + e->delivery, e->id); + fatalx("aborting"); + } + + log_debug("debug: mta: flush for %016"PRIx64" (-> %s)", e->id, e->dest); + + free(e->smtpname); + free(e->dest); + free(e->rcpt); + free(e->dsn_orcpt); + free(e); + + tv.tv_sec = 0; + tv.tv_usec = 0; + evtimer_add(&ev_flush_evp, &tv); + } +} + +void +mta_delivery_log(struct mta_envelope *e, const char *source, const char *relay, + int delivery, const char *status) +{ + if (delivery == IMSG_MTA_DELIVERY_OK) + mta_log(e, "Ok", source, relay, status); + else if (delivery == IMSG_MTA_DELIVERY_TEMPFAIL) + mta_log(e, "TempFail", source, relay, status); + else if (delivery == IMSG_MTA_DELIVERY_PERMFAIL) + mta_log(e, "PermFail", source, relay, status); + else if (delivery == IMSG_MTA_DELIVERY_LOOP) + mta_log(e, "PermFail", source, relay, "Loop detected"); + else { + log_warnx("warn: bad delivery type %d for %016" PRIx64, + delivery, e->id); + fatalx("aborting"); + } + + e->delivery = delivery; + if (status) + (void)strlcpy(e->status, status, sizeof(e->status)); +} + +void +mta_delivery_notify(struct mta_envelope *e) +{ + struct timeval tv; + + tree_xset(&flush_evp, e->id, e); + if (tree_count(&flush_evp) == 1) { + tv.tv_sec = 0; + tv.tv_usec = 0; + evtimer_add(&ev_flush_evp, &tv); + } +} + +static void +mta_query_mx(struct mta_relay *relay) +{ + uint64_t id; + + if (relay->status & RELAY_WAIT_MX) + return; + + log_debug("debug: mta: querying MX for %s...", + mta_relay_to_text(relay)); + + if (waitq_wait(&relay->domain->mxs, mta_on_mx, relay)) { + id = generate_uid(); + tree_xset(&wait_mx, id, relay->domain); + if (relay->domain->as_host) + m_create(p_lka, IMSG_MTA_DNS_HOST, 0, 0, -1); + else + m_create(p_lka, IMSG_MTA_DNS_MX, 0, 0, -1); + m_add_id(p_lka, id); + m_add_string(p_lka, relay->domain->name); + m_close(p_lka); + } + relay->status |= RELAY_WAIT_MX; + mta_relay_ref(relay); +} + +static void +mta_query_limits(struct mta_relay *relay) +{ + if (relay->status & RELAY_WAIT_LIMITS) + return; + + relay->limits = dict_get(env->sc_limits_dict, relay->domain->name); + if (relay->limits == NULL) + relay->limits = dict_get(env->sc_limits_dict, "default"); + + if (max_seen_conndelay_route < relay->limits->conndelay_route) + max_seen_conndelay_route = relay->limits->conndelay_route; + if (max_seen_discdelay_route < relay->limits->discdelay_route) + max_seen_discdelay_route = relay->limits->discdelay_route; +} + +static void +mta_query_secret(struct mta_relay *relay) +{ + if (relay->status & RELAY_WAIT_SECRET) + return; + + log_debug("debug: mta: querying secret for %s...", + mta_relay_to_text(relay)); + + tree_xset(&wait_secret, relay->id, relay); + relay->status |= RELAY_WAIT_SECRET; + + m_create(p_lka, IMSG_MTA_LOOKUP_CREDENTIALS, 0, 0, -1); + m_add_id(p_lka, relay->id); + m_add_string(p_lka, relay->authtable); + m_add_string(p_lka, relay->authlabel); + m_close(p_lka); + + mta_relay_ref(relay); +} + +static void +mta_query_smarthost(struct envelope *evp0) +{ + struct dispatcher *dispatcher; + struct envelope *evp; + + evp = malloc(sizeof(*evp)); + memmove(evp, evp0, sizeof(*evp)); + + dispatcher = dict_xget(env->sc_dispatchers, evp->dispatcher); + + log_debug("debug: mta: querying smarthost for %s:%s...", + evp->dispatcher, dispatcher->u.remote.smarthost); + + tree_xset(&wait_smarthost, evp->id, evp); + + m_create(p_lka, IMSG_MTA_LOOKUP_SMARTHOST, 0, 0, -1); + m_add_id(p_lka, evp->id); + if (dispatcher->u.remote.smarthost_domain) + m_add_string(p_lka, evp->dest.domain); + else + m_add_string(p_lka, NULL); + m_add_string(p_lka, dispatcher->u.remote.smarthost); + m_close(p_lka); + + log_debug("debug: mta: querying smarthost"); +} + +static void +mta_query_preference(struct mta_relay *relay) +{ + if (relay->status & RELAY_WAIT_PREFERENCE) + return; + + log_debug("debug: mta: querying preference for %s...", + mta_relay_to_text(relay)); + + tree_xset(&wait_preference, relay->id, relay); + relay->status |= RELAY_WAIT_PREFERENCE; + + m_create(p_lka, IMSG_MTA_DNS_MX_PREFERENCE, 0, 0, -1); + m_add_id(p_lka, relay->id); + m_add_string(p_lka, relay->domain->name); + m_add_string(p_lka, relay->backupname); + m_close(p_lka); + + mta_relay_ref(relay); +} + +static void +mta_query_source(struct mta_relay *relay) +{ + log_debug("debug: mta: querying source for %s...", + mta_relay_to_text(relay)); + + relay->sourceloop += 1; + + if (relay->sourcetable == NULL) { + /* + * This is a recursive call, but it only happens once, since + * another source will not be queried immediately. + */ + mta_relay_ref(relay); + mta_on_source(relay, mta_source(NULL)); + return; + } + + m_create(p_lka, IMSG_MTA_LOOKUP_SOURCE, 0, 0, -1); + m_add_id(p_lka, relay->id); + m_add_string(p_lka, relay->sourcetable); + m_close(p_lka); + + tree_xset(&wait_source, relay->id, relay); + relay->status |= RELAY_WAIT_SOURCE; + mta_relay_ref(relay); +} + +static void +mta_on_mx(void *tag, void *arg, void *data) +{ + struct mta_domain *domain = data; + struct mta_relay *relay = arg; + + log_debug("debug: mta: ... got mx (%p, %s, %s)", + tag, domain->name, mta_relay_to_text(relay)); + + switch (domain->mxstatus) { + case DNS_OK: + break; + case DNS_RETRY: + relay->fail = IMSG_MTA_DELIVERY_TEMPFAIL; + relay->failstr = "Temporary failure in MX lookup"; + break; + case DNS_EINVAL: + relay->fail = IMSG_MTA_DELIVERY_PERMFAIL; + relay->failstr = "Invalid domain name"; + break; + case DNS_ENONAME: + relay->fail = IMSG_MTA_DELIVERY_PERMFAIL; + relay->failstr = "Domain does not exist"; + break; + case DNS_ENOTFOUND: + relay->fail = IMSG_MTA_DELIVERY_TEMPFAIL; + if (relay->domain->as_host) + relay->failstr = "Host not found"; + else + relay->failstr = "No MX found for domain"; + break; + default: + fatalx("bad DNS lookup error code"); + break; + } + + if (domain->mxstatus) + log_info("smtp-out: Failed to resolve MX for %s: %s", + mta_relay_to_text(relay), relay->failstr); + + relay->status &= ~RELAY_WAIT_MX; + mta_drain(relay); + mta_relay_unref(relay); /* from mta_drain() */ +} + +static void +mta_on_secret(struct mta_relay *relay, const char *secret) +{ + log_debug("debug: mta: ... got secret for %s: %s", + mta_relay_to_text(relay), secret); + + if (secret) + relay->secret = strdup(secret); + + if (relay->secret == NULL) { + log_warnx("warn: Failed to retrieve secret " + "for %s", mta_relay_to_text(relay)); + relay->fail = IMSG_MTA_DELIVERY_TEMPFAIL; + relay->failstr = "Could not retrieve credentials"; + } + + relay->status &= ~RELAY_WAIT_SECRET; + mta_drain(relay); + mta_relay_unref(relay); /* from mta_query_secret() */ +} + +static void +mta_on_smarthost(struct envelope *evp, const char *smarthost) +{ + if (smarthost == NULL) { + log_warnx("warn: Failed to retrieve smarthost " + "for envelope %"PRIx64, evp->id); + m_create(p_queue, IMSG_MTA_DELIVERY_TEMPFAIL, 0, 0, -1); + m_add_evpid(p_queue, evp->id); + m_add_string(p_queue, "Cannot retrieve smarthost"); + m_add_int(p_queue, ESC_OTHER_STATUS); + m_close(p_queue); + return; + } + + log_debug("debug: mta: ... got smarthost for %016"PRIx64": %s", + evp->id, smarthost); + mta_handle_envelope(evp, smarthost); + free(evp); +} + +static void +mta_on_preference(struct mta_relay *relay, int preference) +{ + log_debug("debug: mta: ... got preference for %s: %d", + mta_relay_to_text(relay), preference); + + relay->backuppref = preference; + + relay->status &= ~RELAY_WAIT_PREFERENCE; + mta_drain(relay); + mta_relay_unref(relay); /* from mta_query_preference() */ +} + +static void +mta_on_source(struct mta_relay *relay, struct mta_source *source) +{ + struct mta_connector *c; + void *iter; + int delay, errmask; + + log_debug("debug: mta: ... got source for %s: %s", + mta_relay_to_text(relay), source ? mta_source_to_text(source) : "NULL"); + + relay->lastsource = time(NULL); + delay = DELAY_CHECK_SOURCE_SLOW; + + if (source) { + c = mta_connector(relay, source); + if (c->flags & CONNECTOR_NEW) { + c->flags &= ~CONNECTOR_NEW; + delay = DELAY_CHECK_SOURCE; + } + mta_connect(c); + if ((c->flags & CONNECTOR_ERROR) == 0) + relay->sourceloop = 0; + else + delay = DELAY_CHECK_SOURCE_FAST; + mta_source_unref(source); /* from constructor */ + } + else { + log_warnx("warn: Failed to get source address for %s", + mta_relay_to_text(relay)); + } + + if (tree_count(&relay->connectors) == 0) { + relay->fail = IMSG_MTA_DELIVERY_TEMPFAIL; + relay->failstr = "Could not retrieve source address"; + } + if (tree_count(&relay->connectors) < relay->sourceloop) { + relay->fail = IMSG_MTA_DELIVERY_TEMPFAIL; + relay->failstr = "No valid route to remote MX"; + + errmask = 0; + iter = NULL; + while (tree_iter(&relay->connectors, &iter, NULL, (void **)&c)) + errmask |= c->flags; + + if (errmask & CONNECTOR_ERROR_ROUTE_SMTP) + relay->failstr = "Destination seem to reject all mails"; + else if (errmask & CONNECTOR_ERROR_ROUTE_NET) + relay->failstr = "Network error on destination MXs"; + else if (errmask & CONNECTOR_ERROR_MX) + relay->failstr = "No MX found for destination"; + else if (errmask & CONNECTOR_ERROR_FAMILY) + relay->failstr = "Address family mismatch on destination MXs"; + else if (errmask & CONNECTOR_ERROR_BLOCKED) + relay->failstr = "All routes to destination blocked"; + else + relay->failstr = "No valid route to destination"; + } + + relay->nextsource = relay->lastsource + delay; + relay->status &= ~RELAY_WAIT_SOURCE; + mta_drain(relay); + mta_relay_unref(relay); /* from mta_query_source() */ +} + +static void +mta_connect(struct mta_connector *c) +{ + struct mta_route *route; + struct mta_mx *mx; + struct mta_limits *l = c->relay->limits; + int limits; + time_t nextconn, now; + + /* toggle the block flag */ + if (mta_is_blocked(c->source, c->relay->domain->name)) + c->flags |= CONNECTOR_ERROR_BLOCKED; + else + c->flags &= ~CONNECTOR_ERROR_BLOCKED; + + again: + + log_debug("debug: mta: connecting with %s", mta_connector_to_text(c)); + + /* Do not connect if this connector has an error. */ + if (c->flags & CONNECTOR_ERROR) { + log_debug("debug: mta: connector error"); + return; + } + + if (c->flags & CONNECTOR_WAIT) { + log_debug("debug: mta: cancelling connector timeout"); + runq_cancel(runq_connector, c); + c->flags &= ~CONNECTOR_WAIT; + } + + /* No job. */ + if (c->relay->ntask == 0) { + log_debug("debug: mta: no task for connector"); + return; + } + + /* Do not create more connections than necessary */ + if ((c->relay->nconn_ready >= c->relay->ntask) || + (c->relay->nconn > 2 && c->relay->nconn >= c->relay->ntask / 2)) { + log_debug("debug: mta: enough connections already"); + return; + } + + limits = 0; + nextconn = now = time(NULL); + + if (c->relay->domain->lastconn + l->conndelay_domain > nextconn) { + log_debug("debug: mta: cannot use domain %s before %llus", + c->relay->domain->name, + (unsigned long long) c->relay->domain->lastconn + l->conndelay_domain - now); + nextconn = c->relay->domain->lastconn + l->conndelay_domain; + } + if (c->relay->domain->nconn >= l->maxconn_per_domain) { + log_debug("debug: mta: hit domain limit"); + limits |= CONNECTOR_LIMIT_DOMAIN; + } + + if (c->source->lastconn + l->conndelay_source > nextconn) { + log_debug("debug: mta: cannot use source %s before %llus", + mta_source_to_text(c->source), + (unsigned long long) c->source->lastconn + l->conndelay_source - now); + nextconn = c->source->lastconn + l->conndelay_source; + } + if (c->source->nconn >= l->maxconn_per_source) { + log_debug("debug: mta: hit source limit"); + limits |= CONNECTOR_LIMIT_SOURCE; + } + + if (c->lastconn + l->conndelay_connector > nextconn) { + log_debug("debug: mta: cannot use %s before %llus", + mta_connector_to_text(c), + (unsigned long long) c->lastconn + l->conndelay_connector - now); + nextconn = c->lastconn + l->conndelay_connector; + } + if (c->nconn >= l->maxconn_per_connector) { + log_debug("debug: mta: hit connector limit"); + limits |= CONNECTOR_LIMIT_CONN; + } + + if (c->relay->lastconn + l->conndelay_relay > nextconn) { + log_debug("debug: mta: cannot use %s before %llus", + mta_relay_to_text(c->relay), + (unsigned long long) c->relay->lastconn + l->conndelay_relay - now); + nextconn = c->relay->lastconn + l->conndelay_relay; + } + if (c->relay->nconn >= l->maxconn_per_relay) { + log_debug("debug: mta: hit relay limit"); + limits |= CONNECTOR_LIMIT_RELAY; + } + + /* We can connect now, find a route */ + if (!limits && nextconn <= now) + route = mta_find_route(c, now, &limits, &nextconn, &mx); + else + route = NULL; + + /* No route */ + if (route == NULL) { + + if (c->flags & CONNECTOR_ERROR) { + /* XXX we might want to clear this flag later */ + log_debug("debug: mta-routing: no route available for %s: errors on connector", + mta_connector_to_text(c)); + return; + } + else if (limits) { + log_debug("debug: mta-routing: no route available for %s: limits reached", + mta_connector_to_text(c)); + nextconn = now + DELAY_CHECK_LIMIT; + } + else { + log_debug("debug: mta-routing: no route available for %s: must wait a bit", + mta_connector_to_text(c)); + } + log_debug("debug: mta: retrying to connect on %s in %llus...", + mta_connector_to_text(c), + (unsigned long long) nextconn - time(NULL)); + c->flags |= CONNECTOR_WAIT; + runq_schedule_at(runq_connector, nextconn, c); + return; + } + + log_debug("debug: mta-routing: spawning new connection on %s", + mta_route_to_text(route)); + + c->nconn += 1; + c->lastconn = time(NULL); + + c->relay->nconn += 1; + c->relay->lastconn = c->lastconn; + c->relay->domain->nconn += 1; + c->relay->domain->lastconn = c->lastconn; + route->nconn += 1; + route->lastconn = c->lastconn; + route->src->nconn += 1; + route->src->lastconn = c->lastconn; + route->dst->nconn += 1; + route->dst->lastconn = c->lastconn; + + mta_session(c->relay, route, mx->mxname); /* this never fails synchronously */ + mta_relay_ref(c->relay); + + goto again; +} + +static void +mta_on_timeout(struct runq *runq, void *arg) +{ + struct mta_connector *connector = arg; + struct mta_relay *relay = arg; + struct mta_route *route = arg; + struct hoststat *hs = arg; + + if (runq == runq_relay) { + log_debug("debug: mta: ... timeout for %s", + mta_relay_to_text(relay)); + relay->status &= ~RELAY_WAIT_CONNECTOR; + mta_drain(relay); + mta_relay_unref(relay); /* from mta_drain() */ + } + else if (runq == runq_connector) { + log_debug("debug: mta: ... timeout for %s", + mta_connector_to_text(connector)); + connector->flags &= ~CONNECTOR_WAIT; + mta_connect(connector); + } + else if (runq == runq_route) { + route->flags &= ~ROUTE_RUNQ; + mta_route_enable(route); + mta_route_unref(route); + } + else if (runq == runq_hoststat) { + log_debug("debug: mta: ... timeout for hoststat %s", + hs->name); + mta_hoststat_remove_entry(hs); + free(hs); + } +} + +static void +mta_route_disable(struct mta_route *route, int penalty, int reason) +{ + unsigned long long delay; + + route->penalty += penalty; + route->lastpenalty = time(NULL); + delay = (unsigned long long)DELAY_ROUTE_BASE * route->penalty * route->penalty; + if (delay > DELAY_ROUTE_MAX) + delay = DELAY_ROUTE_MAX; +#if 0 + delay = 60; +#endif + + log_info("smtp-out: Disabling route %s for %llus", + mta_route_to_text(route), delay); + + if (route->flags & ROUTE_DISABLED) + runq_cancel(runq_route, route); + else + mta_route_ref(route); + + route->flags |= reason & ROUTE_DISABLED; + runq_schedule(runq_route, delay, route); +} + +static void +mta_route_enable(struct mta_route *route) +{ + if (route->flags & ROUTE_DISABLED) { + log_info("smtp-out: Enabling route %s", + mta_route_to_text(route)); + route->flags &= ~ROUTE_DISABLED; + route->flags |= ROUTE_NEW; + route->nerror = 0; + } + + if (route->penalty) { +#if DELAY_QUADRATIC + route->penalty -= 1; + route->lastpenalty = time(NULL); +#else + route->penalty = 0; +#endif + } +} + +static void +mta_drain(struct mta_relay *r) +{ + char buf[64]; + + log_debug("debug: mta: draining %s " + "refcount=%d, ntask=%zu, nconnector=%zu, nconn=%zu", + mta_relay_to_text(r), + r->refcount, r->ntask, tree_count(&r->connectors), r->nconn); + + /* + * All done. + */ + if (r->ntask == 0) { + log_debug("debug: mta: all done for %s", mta_relay_to_text(r)); + return; + } + + /* + * If we know that this relay is failing flush the tasks. + */ + if (r->fail) { + mta_flush(r, r->fail, r->failstr); + return; + } + + /* Query secret if needed. */ + if (r->flags & RELAY_AUTH && r->secret == NULL) + mta_query_secret(r); + + /* Query our preference if needed. */ + if (r->backupname && r->backuppref == -1) + mta_query_preference(r); + + /* Query the domain MXs if needed. */ + if (r->domain->lastmxquery == 0) + mta_query_mx(r); + + /* Query the limits if needed. */ + if (r->limits == NULL) + mta_query_limits(r); + + /* Wait until we are ready to proceed. */ + if (r->status & RELAY_WAITMASK) { + buf[0] = '\0'; + if (r->status & RELAY_WAIT_MX) + (void)strlcat(buf, " MX", sizeof buf); + if (r->status & RELAY_WAIT_PREFERENCE) + (void)strlcat(buf, " preference", sizeof buf); + if (r->status & RELAY_WAIT_SECRET) + (void)strlcat(buf, " secret", sizeof buf); + if (r->status & RELAY_WAIT_SOURCE) + (void)strlcat(buf, " source", sizeof buf); + if (r->status & RELAY_WAIT_CONNECTOR) + (void)strlcat(buf, " connector", sizeof buf); + log_debug("debug: mta: %s waiting for%s", + mta_relay_to_text(r), buf); + return; + } + + /* + * We have pending task, and it's maybe time too try a new source. + */ + if (r->nextsource <= time(NULL)) + mta_query_source(r); + else { + log_debug("debug: mta: scheduling relay %s in %llus...", + mta_relay_to_text(r), + (unsigned long long) r->nextsource - time(NULL)); + runq_schedule_at(runq_relay, r->nextsource, r); + r->status |= RELAY_WAIT_CONNECTOR; + mta_relay_ref(r); + } +} + +static void +mta_flush(struct mta_relay *relay, int fail, const char *error) +{ + struct mta_envelope *e; + struct mta_task *task; + const char *domain; + void *iter; + struct mta_connector *c; + size_t n, r; + + log_debug("debug: mta_flush(%s, %d, \"%s\")", + mta_relay_to_text(relay), fail, error); + + if (fail != IMSG_MTA_DELIVERY_TEMPFAIL && fail != IMSG_MTA_DELIVERY_PERMFAIL) + errx(1, "unexpected delivery status %d", fail); + + n = 0; + while ((task = TAILQ_FIRST(&relay->tasks))) { + TAILQ_REMOVE(&relay->tasks, task, entry); + while ((e = TAILQ_FIRST(&task->envelopes))) { + TAILQ_REMOVE(&task->envelopes, e, entry); + + /* + * host was suspended, cache envelope id in hoststat tree + * so that it can be retried when a delivery succeeds for + * that domain. + */ + domain = strchr(e->dest, '@'); + if (fail == IMSG_MTA_DELIVERY_TEMPFAIL && domain) { + r = 0; + iter = NULL; + while (tree_iter(&relay->connectors, &iter, + NULL, (void **)&c)) { + if (c->flags & CONNECTOR_ERROR_ROUTE) + r++; + } + if (tree_count(&relay->connectors) == r) + mta_hoststat_cache(domain+1, e->id); + } + + mta_delivery_log(e, NULL, relay->domain->name, fail, error); + mta_delivery_notify(e); + + n++; + } + free(task->sender); + free(task); + } + + stat_decrement("mta.task", relay->ntask); + stat_decrement("mta.envelope", n); + relay->ntask = 0; + + /* release all waiting envelopes for the relay */ + if (relay->state & RELAY_HOLDQ) { + m_create(p_queue, IMSG_MTA_HOLDQ_RELEASE, 0, 0, -1); + m_add_id(p_queue, relay->id); + m_add_int(p_queue, -1); + m_close(p_queue); + } +} + +/* + * Find a route to use for this connector + */ +static struct mta_route * +mta_find_route(struct mta_connector *c, time_t now, int *limits, + time_t *nextconn, struct mta_mx **pmx) +{ + struct mta_route *route, *best; + struct mta_limits *l = c->relay->limits; + struct mta_mx *mx; + int level, limit_host, limit_route; + int family_mismatch, seen, suspended_route; + time_t tm; + + log_debug("debug: mta-routing: searching new route for %s...", + mta_connector_to_text(c)); + + tm = 0; + limit_host = 0; + limit_route = 0; + suspended_route = 0; + family_mismatch = 0; + level = -1; + best = NULL; + seen = 0; + + TAILQ_FOREACH(mx, &c->relay->domain->mxs, entry) { + /* + * New preference level + */ + if (mx->preference > level) { +#ifndef IGNORE_MX_PREFERENCE + /* + * Use the current best MX if found. + */ + if (best) + break; + + /* + * No candidate found. There are valid MXs at this + * preference level but they reached their limit, or + * we can't connect yet. + */ + if (limit_host || limit_route || tm) + break; + + /* + * If we are a backup MX, do not relay to MXs with + * a greater preference value. + */ + if (c->relay->backuppref >= 0 && + mx->preference >= c->relay->backuppref) + break; + + /* + * Start looking at MXs on this preference level. + */ +#endif + level = mx->preference; + } + + if (mx->host->flags & HOST_IGNORE) + continue; + + /* Found a possibly valid mx */ + seen++; + + if ((c->source->sa && + c->source->sa->sa_family != mx->host->sa->sa_family) || + (l->family && l->family != mx->host->sa->sa_family)) { + log_debug("debug: mta-routing: skipping host %s: AF mismatch", + mta_host_to_text(mx->host)); + family_mismatch = 1; + continue; + } + + if (mx->host->nconn >= l->maxconn_per_host) { + log_debug("debug: mta-routing: skipping host %s: too many connections", + mta_host_to_text(mx->host)); + limit_host = 1; + continue; + } + + if (mx->host->lastconn + l->conndelay_host > now) { + log_debug("debug: mta-routing: skipping host %s: cannot use before %llus", + mta_host_to_text(mx->host), + (unsigned long long) mx->host->lastconn + l->conndelay_host - now); + if (tm == 0 || mx->host->lastconn + l->conndelay_host < tm) + tm = mx->host->lastconn + l->conndelay_host; + continue; + } + + route = mta_route(c->source, mx->host); + + if (route->flags & ROUTE_DISABLED) { + log_debug("debug: mta-routing: skipping route %s: suspend", + mta_route_to_text(route)); + suspended_route |= route->flags & ROUTE_DISABLED; + mta_route_unref(route); /* from here */ + continue; + } + + if (route->nconn && (route->flags & ROUTE_NEW)) { + log_debug("debug: mta-routing: skipping route %s: not validated yet", + mta_route_to_text(route)); + limit_route = 1; + mta_route_unref(route); /* from here */ + continue; + } + + if (route->nconn >= l->maxconn_per_route) { + log_debug("debug: mta-routing: skipping route %s: too many connections", + mta_route_to_text(route)); + limit_route = 1; + mta_route_unref(route); /* from here */ + continue; + } + + if (route->lastconn + l->conndelay_route > now) { + log_debug("debug: mta-routing: skipping route %s: cannot use before %llus (delay after connect)", + mta_route_to_text(route), + (unsigned long long) route->lastconn + l->conndelay_route - now); + if (tm == 0 || route->lastconn + l->conndelay_route < tm) + tm = route->lastconn + l->conndelay_route; + mta_route_unref(route); /* from here */ + continue; + } + + if (route->lastdisc + l->discdelay_route > now) { + log_debug("debug: mta-routing: skipping route %s: cannot use before %llus (delay after disconnect)", + mta_route_to_text(route), + (unsigned long long) route->lastdisc + l->discdelay_route - now); + if (tm == 0 || route->lastdisc + l->discdelay_route < tm) + tm = route->lastdisc + l->discdelay_route; + mta_route_unref(route); /* from here */ + continue; + } + + /* Use the route with the lowest number of connections. */ + if (best && route->nconn >= best->nconn) { + log_debug("debug: mta-routing: skipping route %s: current one is better", + mta_route_to_text(route)); + mta_route_unref(route); /* from here */ + continue; + } + + if (best) + mta_route_unref(best); /* from here */ + best = route; + *pmx = mx; + log_debug("debug: mta-routing: selecting candidate route %s", + mta_route_to_text(route)); + } + + if (best) + return (best); + + /* Order is important */ + if (seen == 0) { + log_info("smtp-out: No MX found for %s", + mta_connector_to_text(c)); + c->flags |= CONNECTOR_ERROR_MX; + } + else if (limit_route) { + log_debug("debug: mta: hit route limit"); + *limits |= CONNECTOR_LIMIT_ROUTE; + } + else if (limit_host) { + log_debug("debug: mta: hit host limit"); + *limits |= CONNECTOR_LIMIT_HOST; + } + else if (tm) { + if (tm > *nextconn) + *nextconn = tm; + } + else if (family_mismatch) { + log_info("smtp-out: Address family mismatch on %s", + mta_connector_to_text(c)); + c->flags |= CONNECTOR_ERROR_FAMILY; + } + else if (suspended_route) { + log_info("smtp-out: No valid route for %s", + mta_connector_to_text(c)); + if (suspended_route & ROUTE_DISABLED_NET) + c->flags |= CONNECTOR_ERROR_ROUTE_NET; + if (suspended_route & ROUTE_DISABLED_SMTP) + c->flags |= CONNECTOR_ERROR_ROUTE_SMTP; + } + + return (NULL); +} + +static void +mta_log(const struct mta_envelope *evp, const char *prefix, const char *source, + const char *relay, const char *status) +{ + log_info("%016"PRIx64" mta delivery evpid=%016"PRIx64" " + "from=<%s> to=<%s> rcpt=<%s> source=\"%s\" " + "relay=\"%s\" delay=%s result=\"%s\" stat=\"%s\"", + evp->session, + evp->id, + evp->task->sender, + evp->dest, + evp->rcpt ? evp->rcpt : "-", + source ? source : "-", + relay, + duration_to_text(time(NULL) - evp->creation), + prefix, + status); +} + +static struct mta_relay * +mta_relay(struct envelope *e, struct relayhost *relayh) +{ + struct dispatcher *dispatcher; + struct mta_relay key, *r; + + dispatcher = dict_xget(env->sc_dispatchers, e->dispatcher); + + memset(&key, 0, sizeof key); + + key.pki_name = dispatcher->u.remote.pki; + key.ca_name = dispatcher->u.remote.ca; + key.authtable = dispatcher->u.remote.auth; + key.sourcetable = dispatcher->u.remote.source; + key.helotable = dispatcher->u.remote.helo_source; + key.heloname = dispatcher->u.remote.helo; + key.srs = dispatcher->u.remote.srs; + + if (relayh->hostname[0]) { + key.domain = mta_domain(relayh->hostname, 1); + } + else { + key.domain = mta_domain(e->dest.domain, 0); + if (dispatcher->u.remote.backup) { + key.backupname = dispatcher->u.remote.backupmx; + if (key.backupname == NULL) + key.backupname = e->smtpname; + } + } + + key.tls = relayh->tls; + key.flags |= relayh->flags; + key.port = relayh->port; + key.authlabel = relayh->authlabel; + if (!key.authlabel[0]) + key.authlabel = NULL; + + if ((key.tls == RELAY_TLS_STARTTLS || key.tls == RELAY_TLS_SMTPS) && + dispatcher->u.remote.tls_noverify == 0) + key.flags |= RELAY_TLS_VERIFY; + + if ((r = SPLAY_FIND(mta_relay_tree, &relays, &key)) == NULL) { + r = xcalloc(1, sizeof *r); + TAILQ_INIT(&r->tasks); + r->id = generate_uid(); + r->dispatcher = dispatcher; + r->tls = key.tls; + r->flags = key.flags; + r->domain = key.domain; + r->backupname = key.backupname ? + xstrdup(key.backupname) : NULL; + r->backuppref = -1; + r->port = key.port; + r->pki_name = key.pki_name ? xstrdup(key.pki_name) : NULL; + r->ca_name = key.ca_name ? xstrdup(key.ca_name) : NULL; + if (key.authtable) + r->authtable = xstrdup(key.authtable); + if (key.authlabel) + r->authlabel = xstrdup(key.authlabel); + if (key.sourcetable) + r->sourcetable = xstrdup(key.sourcetable); + if (key.helotable) + r->helotable = xstrdup(key.helotable); + if (key.heloname) + r->heloname = xstrdup(key.heloname); + r->srs = key.srs; + SPLAY_INSERT(mta_relay_tree, &relays, r); + stat_increment("mta.relay", 1); + } else { + mta_domain_unref(key.domain); /* from here */ + } + + r->refcount++; + return (r); +} + +static void +mta_relay_ref(struct mta_relay *r) +{ + r->refcount++; +} + +static void +mta_relay_unref(struct mta_relay *relay) +{ + struct mta_connector *c; + + if (--relay->refcount) + return; + + /* Make sure they are no envelopes held for this relay */ + if (relay->state & RELAY_HOLDQ) { + m_create(p_queue, IMSG_MTA_HOLDQ_RELEASE, 0, 0, -1); + m_add_id(p_queue, relay->id); + m_add_int(p_queue, 0); + m_close(p_queue); + } + + log_debug("debug: mta: freeing %s", mta_relay_to_text(relay)); + SPLAY_REMOVE(mta_relay_tree, &relays, relay); + + while ((tree_poproot(&relay->connectors, NULL, (void**)&c))) + mta_connector_free(c); + + free(relay->authlabel); + free(relay->authtable); + free(relay->backupname); + free(relay->pki_name); + free(relay->ca_name); + free(relay->helotable); + free(relay->heloname); + free(relay->secret); + free(relay->sourcetable); + + mta_domain_unref(relay->domain); /* from constructor */ + free(relay); + stat_decrement("mta.relay", 1); +} + +const char * +mta_relay_to_text(struct mta_relay *relay) +{ + static char buf[1024]; + char tmp[32]; + const char *sep = ","; + + (void)snprintf(buf, sizeof buf, "[relay:%s", relay->domain->name); + + if (relay->port) { + (void)strlcat(buf, sep, sizeof buf); + (void)snprintf(tmp, sizeof tmp, "port=%d", (int)relay->port); + (void)strlcat(buf, tmp, sizeof buf); + } + + (void)strlcat(buf, sep, sizeof buf); + switch(relay->tls) { + case RELAY_TLS_OPPORTUNISTIC: + (void)strlcat(buf, "smtp", sizeof buf); + break; + case RELAY_TLS_STARTTLS: + (void)strlcat(buf, "smtp+tls", sizeof buf); + break; + case RELAY_TLS_SMTPS: + (void)strlcat(buf, "smtps", sizeof buf); + break; + case RELAY_TLS_NO: + if (relay->flags & RELAY_LMTP) + (void)strlcat(buf, "lmtp", sizeof buf); + else + (void)strlcat(buf, "smtp+notls", sizeof buf); + break; + default: + (void)strlcat(buf, "???", sizeof buf); + } + + if (relay->flags & RELAY_AUTH) { + (void)strlcat(buf, sep, sizeof buf); + (void)strlcat(buf, "auth=", sizeof buf); + (void)strlcat(buf, relay->authtable, sizeof buf); + (void)strlcat(buf, ":", sizeof buf); + (void)strlcat(buf, relay->authlabel, sizeof buf); + } + + if (relay->pki_name) { + (void)strlcat(buf, sep, sizeof buf); + (void)strlcat(buf, "pki_name=", sizeof buf); + (void)strlcat(buf, relay->pki_name, sizeof buf); + } + + if (relay->domain->as_host) { + (void)strlcat(buf, sep, sizeof buf); + (void)strlcat(buf, "mx", sizeof buf); + } + + if (relay->backupname) { + (void)strlcat(buf, sep, sizeof buf); + (void)strlcat(buf, "backup=", sizeof buf); + (void)strlcat(buf, relay->backupname, sizeof buf); + } + + if (relay->sourcetable) { + (void)strlcat(buf, sep, sizeof buf); + (void)strlcat(buf, "sourcetable=", sizeof buf); + (void)strlcat(buf, relay->sourcetable, sizeof buf); + } + + if (relay->helotable) { + (void)strlcat(buf, sep, sizeof buf); + (void)strlcat(buf, "helotable=", sizeof buf); + (void)strlcat(buf, relay->helotable, sizeof buf); + } + + if (relay->heloname) { + (void)strlcat(buf, sep, sizeof buf); + (void)strlcat(buf, "heloname=", sizeof buf); + (void)strlcat(buf, relay->heloname, sizeof buf); + } + + (void)strlcat(buf, "]", sizeof buf); + + return (buf); +} + +static void +mta_relay_show(struct mta_relay *r, struct mproc *p, uint32_t id, time_t t) +{ + struct mta_connector *c; + void *iter; + char buf[1024], flags[1024], dur[64]; + time_t to; + + flags[0] = '\0'; + +#define SHOWSTATUS(f, n) do { \ + if (r->status & (f)) { \ + if (flags[0]) \ + (void)strlcat(flags, ",", sizeof(flags)); \ + (void)strlcat(flags, (n), sizeof(flags)); \ + } \ + } while(0) + + SHOWSTATUS(RELAY_WAIT_MX, "MX"); + SHOWSTATUS(RELAY_WAIT_PREFERENCE, "preference"); + SHOWSTATUS(RELAY_WAIT_SECRET, "secret"); + SHOWSTATUS(RELAY_WAIT_LIMITS, "limits"); + SHOWSTATUS(RELAY_WAIT_SOURCE, "source"); + SHOWSTATUS(RELAY_WAIT_CONNECTOR, "connector"); +#undef SHOWSTATUS + + if (runq_pending(runq_relay, r, &to)) + (void)snprintf(dur, sizeof(dur), "%s", duration_to_text(to - t)); + else + (void)strlcpy(dur, "-", sizeof(dur)); + + (void)snprintf(buf, sizeof(buf), "%s refcount=%d ntask=%zu nconn=%zu lastconn=%s timeout=%s wait=%s%s", + mta_relay_to_text(r), + r->refcount, + r->ntask, + r->nconn, + r->lastconn ? duration_to_text(t - r->lastconn) : "-", + dur, + flags, + (r->state & RELAY_ONHOLD) ? "ONHOLD" : ""); + m_compose(p, IMSG_CTL_MTA_SHOW_RELAYS, id, 0, -1, buf, strlen(buf) + 1); + + iter = NULL; + while (tree_iter(&r->connectors, &iter, NULL, (void **)&c)) { + + if (runq_pending(runq_connector, c, &to)) + (void)snprintf(dur, sizeof(dur), "%s", duration_to_text(to - t)); + else + (void)strlcpy(dur, "-", sizeof(dur)); + + flags[0] = '\0'; + +#define SHOWFLAG(f, n) do { \ + if (c->flags & (f)) { \ + if (flags[0]) \ + (void)strlcat(flags, ",", sizeof(flags)); \ + (void)strlcat(flags, (n), sizeof(flags)); \ + } \ + } while(0) + + SHOWFLAG(CONNECTOR_NEW, "NEW"); + SHOWFLAG(CONNECTOR_WAIT, "WAIT"); + + SHOWFLAG(CONNECTOR_ERROR_FAMILY, "ERROR_FAMILY"); + SHOWFLAG(CONNECTOR_ERROR_SOURCE, "ERROR_SOURCE"); + SHOWFLAG(CONNECTOR_ERROR_MX, "ERROR_MX"); + SHOWFLAG(CONNECTOR_ERROR_ROUTE_NET, "ERROR_ROUTE_NET"); + SHOWFLAG(CONNECTOR_ERROR_ROUTE_SMTP, "ERROR_ROUTE_SMTP"); + SHOWFLAG(CONNECTOR_ERROR_BLOCKED, "ERROR_BLOCKED"); + + SHOWFLAG(CONNECTOR_LIMIT_HOST, "LIMIT_HOST"); + SHOWFLAG(CONNECTOR_LIMIT_ROUTE, "LIMIT_ROUTE"); + SHOWFLAG(CONNECTOR_LIMIT_SOURCE, "LIMIT_SOURCE"); + SHOWFLAG(CONNECTOR_LIMIT_RELAY, "LIMIT_RELAY"); + SHOWFLAG(CONNECTOR_LIMIT_CONN, "LIMIT_CONN"); + SHOWFLAG(CONNECTOR_LIMIT_DOMAIN, "LIMIT_DOMAIN"); +#undef SHOWFLAG + + (void)snprintf(buf, sizeof(buf), + " connector %s refcount=%d nconn=%zu lastconn=%s timeout=%s flags=%s", + mta_source_to_text(c->source), + c->refcount, + c->nconn, + c->lastconn ? duration_to_text(t - c->lastconn) : "-", + dur, + flags); + m_compose(p, IMSG_CTL_MTA_SHOW_RELAYS, id, 0, -1, buf, + strlen(buf) + 1); + + + } +} + +static int +mta_relay_cmp(const struct mta_relay *a, const struct mta_relay *b) +{ + int r; + + if (a->domain < b->domain) + return (-1); + if (a->domain > b->domain) + return (1); + + if (a->tls < b->tls) + return (-1); + if (a->tls > b->tls) + return (1); + + if (a->flags < b->flags) + return (-1); + if (a->flags > b->flags) + return (1); + + if (a->port < b->port) + return (-1); + if (a->port > b->port) + return (1); + + if (a->authtable == NULL && b->authtable) + return (-1); + if (a->authtable && b->authtable == NULL) + return (1); + if (a->authtable && ((r = strcmp(a->authtable, b->authtable)))) + return (r); + if (a->authlabel == NULL && b->authlabel) + return (-1); + if (a->authlabel && b->authlabel == NULL) + return (1); + if (a->authlabel && ((r = strcmp(a->authlabel, b->authlabel)))) + return (r); + if (a->sourcetable == NULL && b->sourcetable) + return (-1); + if (a->sourcetable && b->sourcetable == NULL) + return (1); + if (a->sourcetable && ((r = strcmp(a->sourcetable, b->sourcetable)))) + return (r); + if (a->helotable == NULL && b->helotable) + return (-1); + if (a->helotable && b->helotable == NULL) + return (1); + if (a->helotable && ((r = strcmp(a->helotable, b->helotable)))) + return (r); + if (a->heloname == NULL && b->heloname) + return (-1); + if (a->heloname && b->heloname == NULL) + return (1); + if (a->heloname && ((r = strcmp(a->heloname, b->heloname)))) + return (r); + + if (a->pki_name == NULL && b->pki_name) + return (-1); + if (a->pki_name && b->pki_name == NULL) + return (1); + if (a->pki_name && ((r = strcmp(a->pki_name, b->pki_name)))) + return (r); + + if (a->ca_name == NULL && b->ca_name) + return (-1); + if (a->ca_name && b->ca_name == NULL) + return (1); + if (a->ca_name && ((r = strcmp(a->ca_name, b->ca_name)))) + return (r); + + if (a->backupname == NULL && b->backupname) + return (-1); + if (a->backupname && b->backupname == NULL) + return (1); + if (a->backupname && ((r = strcmp(a->backupname, b->backupname)))) + return (r); + + if (a->srs < b->srs) + return (-1); + if (a->srs > b->srs) + return (1); + + return (0); +} + +SPLAY_GENERATE(mta_relay_tree, mta_relay, entry, mta_relay_cmp); + +static struct mta_host * +mta_host(const struct sockaddr *sa) +{ + struct mta_host key, *h; + struct sockaddr_storage ss; + + memmove(&ss, sa, SA_LEN(sa)); + key.sa = (struct sockaddr*)&ss; + h = SPLAY_FIND(mta_host_tree, &hosts, &key); + + if (h == NULL) { + h = xcalloc(1, sizeof(*h)); + h->sa = xmemdup(sa, SA_LEN(sa)); + SPLAY_INSERT(mta_host_tree, &hosts, h); + stat_increment("mta.host", 1); + } + + h->refcount++; + return (h); +} + +static void +mta_host_ref(struct mta_host *h) +{ + h->refcount++; +} + +static void +mta_host_unref(struct mta_host *h) +{ + if (--h->refcount) + return; + + SPLAY_REMOVE(mta_host_tree, &hosts, h); + free(h->sa); + free(h->ptrname); + free(h); + stat_decrement("mta.host", 1); +} + +const char * +mta_host_to_text(struct mta_host *h) +{ + static char buf[1024]; + + if (h->ptrname) + (void)snprintf(buf, sizeof buf, "%s (%s)", + sa_to_text(h->sa), h->ptrname); + else + (void)snprintf(buf, sizeof buf, "%s", sa_to_text(h->sa)); + + return (buf); +} + +static int +mta_host_cmp(const struct mta_host *a, const struct mta_host *b) +{ + if (SA_LEN(a->sa) < SA_LEN(b->sa)) + return (-1); + if (SA_LEN(a->sa) > SA_LEN(b->sa)) + return (1); + return (memcmp(a->sa, b->sa, SA_LEN(a->sa))); +} + +SPLAY_GENERATE(mta_host_tree, mta_host, entry, mta_host_cmp); + +static struct mta_domain * +mta_domain(char *name, int as_host) +{ + struct mta_domain key, *d; + + key.name = name; + key.as_host = as_host; + d = SPLAY_FIND(mta_domain_tree, &domains, &key); + + if (d == NULL) { + d = xcalloc(1, sizeof(*d)); + d->name = xstrdup(name); + d->as_host = as_host; + TAILQ_INIT(&d->mxs); + SPLAY_INSERT(mta_domain_tree, &domains, d); + stat_increment("mta.domain", 1); + } + + d->refcount++; + return (d); +} + +#if 0 +static void +mta_domain_ref(struct mta_domain *d) +{ + d->refcount++; +} +#endif + +static void +mta_domain_unref(struct mta_domain *d) +{ + struct mta_mx *mx; + + if (--d->refcount) + return; + + while ((mx = TAILQ_FIRST(&d->mxs))) { + TAILQ_REMOVE(&d->mxs, mx, entry); + mta_host_unref(mx->host); /* from IMSG_DNS_HOST */ + free(mx->mxname); + free(mx); + } + + SPLAY_REMOVE(mta_domain_tree, &domains, d); + free(d->name); + free(d); + stat_decrement("mta.domain", 1); +} + +static int +mta_domain_cmp(const struct mta_domain *a, const struct mta_domain *b) +{ + if (a->as_host < b->as_host) + return (-1); + if (a->as_host > b->as_host) + return (1); + return (strcasecmp(a->name, b->name)); +} + +SPLAY_GENERATE(mta_domain_tree, mta_domain, entry, mta_domain_cmp); + +static struct mta_source * +mta_source(const struct sockaddr *sa) +{ + struct mta_source key, *s; + struct sockaddr_storage ss; + + if (sa) { + memmove(&ss, sa, SA_LEN(sa)); + key.sa = (struct sockaddr*)&ss; + } else + key.sa = NULL; + s = SPLAY_FIND(mta_source_tree, &sources, &key); + + if (s == NULL) { + s = xcalloc(1, sizeof(*s)); + if (sa) + s->sa = xmemdup(sa, SA_LEN(sa)); + SPLAY_INSERT(mta_source_tree, &sources, s); + stat_increment("mta.source", 1); + } + + s->refcount++; + return (s); +} + +static void +mta_source_ref(struct mta_source *s) +{ + s->refcount++; +} + +static void +mta_source_unref(struct mta_source *s) +{ + if (--s->refcount) + return; + + SPLAY_REMOVE(mta_source_tree, &sources, s); + free(s->sa); + free(s); + stat_decrement("mta.source", 1); +} + +static const char * +mta_source_to_text(struct mta_source *s) +{ + static char buf[1024]; + + if (s->sa == NULL) + return "[]"; + (void)snprintf(buf, sizeof buf, "%s", sa_to_text(s->sa)); + return (buf); +} + +static int +mta_source_cmp(const struct mta_source *a, const struct mta_source *b) +{ + if (a->sa == NULL) + return ((b->sa == NULL) ? 0 : -1); + if (b->sa == NULL) + return (1); + if (SA_LEN(a->sa) < SA_LEN(b->sa)) + return (-1); + if (SA_LEN(a->sa) > SA_LEN(b->sa)) + return (1); + return (memcmp(a->sa, b->sa, SA_LEN(a->sa))); +} + +SPLAY_GENERATE(mta_source_tree, mta_source, entry, mta_source_cmp); + +static struct mta_connector * +mta_connector(struct mta_relay *relay, struct mta_source *source) +{ + struct mta_connector *c; + + c = tree_get(&relay->connectors, (uintptr_t)(source)); + if (c == NULL) { + c = xcalloc(1, sizeof(*c)); + c->relay = relay; + c->source = source; + c->flags |= CONNECTOR_NEW; + mta_source_ref(source); + tree_xset(&relay->connectors, (uintptr_t)(source), c); + stat_increment("mta.connector", 1); + log_debug("debug: mta: new %s", mta_connector_to_text(c)); + } + + return (c); +} + +static void +mta_connector_free(struct mta_connector *c) +{ + log_debug("debug: mta: freeing %s", + mta_connector_to_text(c)); + + if (c->flags & CONNECTOR_WAIT) { + log_debug("debug: mta: cancelling timeout for %s", + mta_connector_to_text(c)); + runq_cancel(runq_connector, c); + } + mta_source_unref(c->source); /* from constructor */ + free(c); + + stat_decrement("mta.connector", 1); +} + +static const char * +mta_connector_to_text(struct mta_connector *c) +{ + static char buf[1024]; + + (void)snprintf(buf, sizeof buf, "[connector:%s->%s,0x%x]", + mta_source_to_text(c->source), + mta_relay_to_text(c->relay), + c->flags); + return (buf); +} + +static struct mta_route * +mta_route(struct mta_source *src, struct mta_host *dst) +{ + struct mta_route key, *r; + static uint64_t rid = 0; + + key.src = src; + key.dst = dst; + r = SPLAY_FIND(mta_route_tree, &routes, &key); + + if (r == NULL) { + r = xcalloc(1, sizeof(*r)); + r->src = src; + r->dst = dst; + r->flags |= ROUTE_NEW; + r->id = ++rid; + SPLAY_INSERT(mta_route_tree, &routes, r); + mta_source_ref(src); + mta_host_ref(dst); + stat_increment("mta.route", 1); + } + else if (r->flags & ROUTE_RUNQ) { + log_debug("debug: mta: mta_route_ref(): cancelling runq for route %s", + mta_route_to_text(r)); + r->flags &= ~(ROUTE_RUNQ | ROUTE_KEEPALIVE); + runq_cancel(runq_route, r); + r->refcount--; /* from mta_route_unref() */ + } + + r->refcount++; + return (r); +} + +static void +mta_route_ref(struct mta_route *r) +{ + r->refcount++; +} + +static void +mta_route_unref(struct mta_route *r) +{ + time_t sched, now; + int delay; + + if (--r->refcount) + return; + + /* + * Nothing references this route, but we might want to keep it alive + * for a while. + */ + now = time(NULL); + sched = 0; + + if (r->penalty) { +#if DELAY_QUADRATIC + delay = DELAY_ROUTE_BASE * r->penalty * r->penalty; +#else + delay = 15 * 60; +#endif + if (delay > DELAY_ROUTE_MAX) + delay = DELAY_ROUTE_MAX; + sched = r->lastpenalty + delay; + log_debug("debug: mta: mta_route_unref(): keeping route %s alive for %llus (penalty %d)", + mta_route_to_text(r), (unsigned long long) sched - now, r->penalty); + } else if (!(r->flags & ROUTE_KEEPALIVE)) { + if (r->lastconn + max_seen_conndelay_route > now) + sched = r->lastconn + max_seen_conndelay_route; + if (r->lastdisc + max_seen_discdelay_route > now && + r->lastdisc + max_seen_discdelay_route < sched) + sched = r->lastdisc + max_seen_discdelay_route; + + if (sched > now) + log_debug("debug: mta: mta_route_unref(): keeping route %s alive for %llus (imposed delay)", + mta_route_to_text(r), (unsigned long long) sched - now); + } + + if (sched > now) { + r->flags |= ROUTE_RUNQ; + runq_schedule_at(runq_route, sched, r); + r->refcount++; + return; + } + + log_debug("debug: mta: mta_route_unref(): really discarding route %s", + mta_route_to_text(r)); + + SPLAY_REMOVE(mta_route_tree, &routes, r); + mta_source_unref(r->src); /* from constructor */ + mta_host_unref(r->dst); /* from constructor */ + free(r); + stat_decrement("mta.route", 1); +} + +static const char * +mta_route_to_text(struct mta_route *r) +{ + static char buf[1024]; + + (void)snprintf(buf, sizeof buf, "%s <-> %s", + mta_source_to_text(r->src), + mta_host_to_text(r->dst)); + + return (buf); +} + +static int +mta_route_cmp(const struct mta_route *a, const struct mta_route *b) +{ + if (a->src < b->src) + return (-1); + if (a->src > b->src) + return (1); + + if (a->dst < b->dst) + return (-1); + if (a->dst > b->dst) + return (1); + + return (0); +} + +SPLAY_GENERATE(mta_route_tree, mta_route, entry, mta_route_cmp); + +void +mta_block(struct mta_source *src, char *dom) +{ + struct mta_block key, *b; + + key.source = src; + key.domain = dom; + + b = SPLAY_FIND(mta_block_tree, &blocks, &key); + if (b != NULL) + return; + + b = xcalloc(1, sizeof(*b)); + if (dom) + b->domain = xstrdup(dom); + b->source = src; + mta_source_ref(src); + SPLAY_INSERT(mta_block_tree, &blocks, b); +} + +void +mta_unblock(struct mta_source *src, char *dom) +{ + struct mta_block key, *b; + + key.source = src; + key.domain = dom; + + b = SPLAY_FIND(mta_block_tree, &blocks, &key); + if (b == NULL) + return; + + SPLAY_REMOVE(mta_block_tree, &blocks, b); + + mta_source_unref(b->source); + free(b->domain); + free(b); +} + +int +mta_is_blocked(struct mta_source *src, char *dom) +{ + struct mta_block key; + + key.source = src; + key.domain = dom; + + if (SPLAY_FIND(mta_block_tree, &blocks, &key)) + return (1); + + return (0); +} + +static +int +mta_block_cmp(const struct mta_block *a, const struct mta_block *b) +{ + if (a->source < b->source) + return (-1); + if (a->source > b->source) + return (1); + if (!a->domain && b->domain) + return (-1); + if (a->domain && !b->domain) + return (1); + if (a->domain == b->domain) + return (0); + return (strcasecmp(a->domain, b->domain)); +} + +SPLAY_GENERATE(mta_block_tree, mta_block, entry, mta_block_cmp); + + + +/* hoststat errors are not critical, we do best effort */ +void +mta_hoststat_update(const char *host, const char *error) +{ + struct hoststat *hs = NULL; + char buf[HOST_NAME_MAX+1]; + + if (!lowercase(buf, host, sizeof buf)) + return; + + hs = dict_get(&hoststat, buf); + if (hs == NULL) { + if ((hs = calloc(1, sizeof *hs)) == NULL) + return; + tree_init(&hs->deferred); + runq_schedule(runq_hoststat, HOSTSTAT_EXPIRE_DELAY, hs); + } + (void)strlcpy(hs->name, buf, sizeof hs->name); + (void)strlcpy(hs->error, error, sizeof hs->error); + hs->tm = time(NULL); + dict_set(&hoststat, buf, hs); + + runq_cancel(runq_hoststat, hs); + runq_schedule(runq_hoststat, HOSTSTAT_EXPIRE_DELAY, hs); +} + +void +mta_hoststat_cache(const char *host, uint64_t evpid) +{ + struct hoststat *hs = NULL; + char buf[HOST_NAME_MAX+1]; + + if (!lowercase(buf, host, sizeof buf)) + return; + + hs = dict_get(&hoststat, buf); + if (hs == NULL) + return; + + if (tree_count(&hs->deferred) >= env->sc_mta_max_deferred) + return; + + tree_set(&hs->deferred, evpid, NULL); +} + +void +mta_hoststat_uncache(const char *host, uint64_t evpid) +{ + struct hoststat *hs = NULL; + char buf[HOST_NAME_MAX+1]; + + if (!lowercase(buf, host, sizeof buf)) + return; + + hs = dict_get(&hoststat, buf); + if (hs == NULL) + return; + + tree_pop(&hs->deferred, evpid); +} + +void +mta_hoststat_reschedule(const char *host) +{ + struct hoststat *hs = NULL; + char buf[HOST_NAME_MAX+1]; + uint64_t evpid; + + if (!lowercase(buf, host, sizeof buf)) + return; + + hs = dict_get(&hoststat, buf); + if (hs == NULL) + return; + + while (tree_poproot(&hs->deferred, &evpid, NULL)) { + m_compose(p_queue, IMSG_MTA_SCHEDULE, 0, 0, -1, + &evpid, sizeof evpid); + } +} + +static void +mta_hoststat_remove_entry(struct hoststat *hs) +{ + while (tree_poproot(&hs->deferred, NULL, NULL)) + ; + dict_pop(&hoststat, hs->name); + runq_cancel(runq_hoststat, hs); +} diff --git a/foobar/portable/smtpd/mta_session.c b/foobar/portable/smtpd/mta_session.c new file mode 100644 index 00000000..632f5be7 --- /dev/null +++ b/foobar/portable/smtpd/mta_session.c @@ -0,0 +1,2004 @@ +/* $OpenBSD: mta_session.c,v 1.135 2020/04/24 11:34:07 eric Exp $ */ + +/* + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> + * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/uio.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <imsg.h> +#include <inttypes.h> +#include <netdb.h> +#include <openssl/ssl.h> +#include <pwd.h> +#include <resolv.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "smtpd.h" +#include "log.h" +#include "ssl.h" + +#define MAX_TRYBEFOREDISABLE 10 + +#define MTA_HIWAT 65535 + +enum mta_state { + MTA_INIT, + MTA_BANNER, + MTA_EHLO, + MTA_HELO, + MTA_LHLO, + MTA_STARTTLS, + MTA_AUTH, + MTA_AUTH_PLAIN, + MTA_AUTH_LOGIN, + MTA_AUTH_LOGIN_USER, + MTA_AUTH_LOGIN_PASS, + MTA_READY, + MTA_MAIL, + MTA_RCPT, + MTA_DATA, + MTA_BODY, + MTA_EOM, + MTA_LMTP_EOM, + MTA_RSET, + MTA_QUIT, +}; + +#define MTA_FORCE_ANYSSL 0x0001 +#define MTA_FORCE_SMTPS 0x0002 +#define MTA_FORCE_TLS 0x0004 +#define MTA_FORCE_PLAIN 0x0008 +#define MTA_WANT_SECURE 0x0010 +#define MTA_DOWNGRADE_PLAIN 0x0080 + +#define MTA_TLS 0x0100 +#define MTA_TLS_VERIFIED 0x0200 + +#define MTA_FREE 0x0400 +#define MTA_LMTP 0x0800 +#define MTA_WAIT 0x1000 +#define MTA_HANGON 0x2000 +#define MTA_RECONN 0x4000 + +#define MTA_EXT_STARTTLS 0x01 +#define MTA_EXT_PIPELINING 0x02 +#define MTA_EXT_AUTH 0x04 +#define MTA_EXT_AUTH_PLAIN 0x08 +#define MTA_EXT_AUTH_LOGIN 0x10 +#define MTA_EXT_SIZE 0x20 + +struct mta_session { + uint64_t id; + struct mta_relay *relay; + struct mta_route *route; + char *helo; + char *mxname; + + char *username; + + int flags; + + int attempt; + int use_smtps; + int use_starttls; + int use_smtp_tls; + int ready; + + struct event ev; + struct io *io; + int ext; + + size_t ext_size; + + size_t msgtried; + size_t msgcount; + size_t rcptcount; + int hangon; + + enum mta_state state; + struct mta_task *task; + struct mta_envelope *currevp; + FILE *datafp; + size_t datalen; + + size_t failures; + + char replybuf[2048]; +}; + +static void mta_session_init(void); +static void mta_start(int fd, short ev, void *arg); +static void mta_io(struct io *, int, void *); +static void mta_free(struct mta_session *); +static void mta_getnameinfo_cb(void *, int, const char *, const char *); +static void mta_on_ptr(void *, void *, void *); +static void mta_on_timeout(struct runq *, void *); +static void mta_connect(struct mta_session *); +static void mta_enter_state(struct mta_session *, int); +static void mta_flush_task(struct mta_session *, int, const char *, size_t, int); +static void mta_error(struct mta_session *, const char *, ...); +static void mta_send(struct mta_session *, char *, ...); +static ssize_t mta_queue_data(struct mta_session *); +static void mta_response(struct mta_session *, char *); +static const char * mta_strstate(int); +static void mta_cert_init(struct mta_session *); +static void mta_cert_init_cb(void *, int, const char *, const void *, size_t); +static void mta_cert_verify(struct mta_session *); +static void mta_cert_verify_cb(void *, int); +static void mta_tls_verified(struct mta_session *); +static struct mta_session *mta_tree_pop(struct tree *, uint64_t); +static const char * dsn_strret(enum dsn_ret); +static const char * dsn_strnotify(uint8_t); + +void mta_hoststat_update(const char *, const char *); +void mta_hoststat_reschedule(const char *); +void mta_hoststat_cache(const char *, uint64_t); +void mta_hoststat_uncache(const char *, uint64_t); + + +static void mta_filter_begin(struct mta_session *); +static void mta_filter_end(struct mta_session *); +static void mta_connected(struct mta_session *); +static void mta_disconnected(struct mta_session *); + +static void mta_report_link_connect(struct mta_session *, const char *, int, + const struct sockaddr_storage *, + const struct sockaddr_storage *); +static void mta_report_link_greeting(struct mta_session *, const char *); +static void mta_report_link_identify(struct mta_session *, const char *, const char *); +static void mta_report_link_tls(struct mta_session *, const char *); +static void mta_report_link_disconnect(struct mta_session *); +static void mta_report_link_auth(struct mta_session *, const char *, const char *); +static void mta_report_tx_reset(struct mta_session *, uint32_t); +static void mta_report_tx_begin(struct mta_session *, uint32_t); +static void mta_report_tx_mail(struct mta_session *, uint32_t, const char *, int); +static void mta_report_tx_rcpt(struct mta_session *, uint32_t, const char *, int); +static void mta_report_tx_envelope(struct mta_session *, uint32_t, uint64_t); +static void mta_report_tx_data(struct mta_session *, uint32_t, int); +static void mta_report_tx_commit(struct mta_session *, uint32_t, size_t); +static void mta_report_tx_rollback(struct mta_session *, uint32_t); +static void mta_report_protocol_client(struct mta_session *, const char *); +static void mta_report_protocol_server(struct mta_session *, const char *); +#if 0 +static void mta_report_filter_response(struct mta_session *, int, int, const char *); +#endif +static void mta_report_timeout(struct mta_session *); + + +static struct tree wait_helo; +static struct tree wait_ptr; +static struct tree wait_fd; +static struct tree wait_tls_init; +static struct tree wait_tls_verify; + +static struct runq *hangon; + +#define SESSION_FILTERED(s) \ + ((s)->relay->dispatcher->u.remote.filtername) + +static void +mta_session_init(void) +{ + static int init = 0; + + if (!init) { + tree_init(&wait_helo); + tree_init(&wait_ptr); + tree_init(&wait_fd); + tree_init(&wait_tls_init); + tree_init(&wait_tls_verify); + runq_init(&hangon, mta_on_timeout); + init = 1; + } +} + +void +mta_session(struct mta_relay *relay, struct mta_route *route, const char *mxname) +{ + struct mta_session *s; + struct timeval tv; + + mta_session_init(); + + s = xcalloc(1, sizeof *s); + s->id = generate_uid(); + s->relay = relay; + s->route = route; + s->mxname = xstrdup(mxname); + + mta_filter_begin(s); + + if (relay->flags & RELAY_LMTP) + s->flags |= MTA_LMTP; + switch (relay->tls) { + case RELAY_TLS_SMTPS: + s->flags |= MTA_FORCE_SMTPS; + s->flags |= MTA_WANT_SECURE; + break; + case RELAY_TLS_STARTTLS: + s->flags |= MTA_FORCE_TLS; + s->flags |= MTA_WANT_SECURE; + break; + case RELAY_TLS_OPPORTUNISTIC: + /* do not force anything, try tls then smtp */ + break; + case RELAY_TLS_NO: + s->flags |= MTA_FORCE_PLAIN; + break; + default: + fatalx("bad value for relay->tls: %d", relay->tls); + } + + log_debug("debug: mta: %p: spawned for relay %s", s, + mta_relay_to_text(relay)); + stat_increment("mta.session", 1); + + if (route->dst->ptrname || route->dst->lastptrquery) { + /* We want to delay the connection since to always notify + * the relay asynchronously. + */ + tv.tv_sec = 0; + tv.tv_usec = 0; + evtimer_set(&s->ev, mta_start, s); + evtimer_add(&s->ev, &tv); + } else if (waitq_wait(&route->dst->ptrname, mta_on_ptr, s)) { + resolver_getnameinfo(s->route->dst->sa, 0, mta_getnameinfo_cb, s); + } +} + +void +mta_session_imsg(struct mproc *p, struct imsg *imsg) +{ + struct mta_session *s; + struct msg m; + uint64_t reqid; + const char *name; + int status; + struct stat sb; + + switch (imsg->hdr.type) { + + case IMSG_MTA_OPEN_MESSAGE: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_end(&m); + + s = mta_tree_pop(&wait_fd, reqid); + if (s == NULL) { + if (imsg->fd != -1) + close(imsg->fd); + return; + } + + if (imsg->fd == -1) { + log_debug("debug: mta: failed to obtain msg fd"); + mta_flush_task(s, IMSG_MTA_DELIVERY_TEMPFAIL, + "Could not get message fd", 0, 0); + mta_enter_state(s, MTA_READY); + return; + } + + if ((s->ext & MTA_EXT_SIZE) && s->ext_size != 0) { + if (fstat(imsg->fd, &sb) == -1) { + log_debug("debug: mta: failed to stat msg fd"); + mta_flush_task(s, IMSG_MTA_DELIVERY_TEMPFAIL, + "Could not stat message fd", 0, 0); + mta_enter_state(s, MTA_READY); + close(imsg->fd); + return; + } + if (sb.st_size > (off_t)s->ext_size) { + log_debug("debug: mta: message too large for peer"); + mta_flush_task(s, IMSG_MTA_DELIVERY_PERMFAIL, + "message too large for peer", 0, 0); + mta_enter_state(s, MTA_READY); + close(imsg->fd); + return; + } + } + + s->datafp = fdopen(imsg->fd, "r"); + if (s->datafp == NULL) + fatal("mta: fdopen"); + + mta_enter_state(s, MTA_MAIL); + return; + + case IMSG_MTA_LOOKUP_HELO: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &status); + if (status == LKA_OK) + m_get_string(&m, &name); + m_end(&m); + + s = mta_tree_pop(&wait_helo, reqid); + if (s == NULL) + return; + + if (status == LKA_OK) { + s->helo = xstrdup(name); + mta_connect(s); + } else { + mta_source_error(s->relay, s->route, + "Failed to retrieve helo string"); + mta_free(s); + } + return; + + default: + errx(1, "mta_session_imsg: unexpected %s imsg", + imsg_to_str(imsg->hdr.type)); + } +} + +static struct mta_session * +mta_tree_pop(struct tree *wait, uint64_t reqid) +{ + struct mta_session *s; + + s = tree_xpop(wait, reqid); + if (s->flags & MTA_FREE) { + log_debug("debug: mta: %p: zombie session", s); + mta_free(s); + return (NULL); + } + s->flags &= ~MTA_WAIT; + + return (s); +} + +static void +mta_free(struct mta_session *s) +{ + struct mta_relay *relay; + struct mta_route *route; + + log_debug("debug: mta: %p: session done", s); + + mta_disconnected(s); + + if (s->ready) + s->relay->nconn_ready -= 1; + + if (s->flags & MTA_HANGON) { + log_debug("debug: mta: %p: cancelling hangon timer", s); + runq_cancel(hangon, s); + } + + if (s->io) + io_free(s->io); + + if (s->task) + fatalx("current task should have been deleted already"); + if (s->datafp) { + fclose(s->datafp); + s->datalen = 0; + } + free(s->helo); + + relay = s->relay; + route = s->route; + free(s->username); + free(s->mxname); + free(s); + stat_decrement("mta.session", 1); + mta_route_collect(relay, route); +} + +static void +mta_getnameinfo_cb(void *arg, int gaierrno, const char *host, const char *serv) +{ + struct mta_session *s = arg; + struct mta_host *h; + + h = s->route->dst; + h->lastptrquery = time(NULL); + if (host) + h->ptrname = xstrdup(host); + waitq_run(&h->ptrname, h->ptrname); +} + +static void +mta_on_timeout(struct runq *runq, void *arg) +{ + struct mta_session *s = arg; + + log_debug("mta: timeout for session hangon"); + + s->flags &= ~MTA_HANGON; + s->hangon++; + + mta_enter_state(s, MTA_READY); +} + +static void +mta_on_ptr(void *tag, void *arg, void *data) +{ + struct mta_session *s = arg; + + mta_connect(s); +} + +static void +mta_start(int fd, short ev, void *arg) +{ + struct mta_session *s = arg; + + mta_connect(s); +} + +static void +mta_connect(struct mta_session *s) +{ + struct sockaddr_storage ss; + struct sockaddr *sa; + int portno; + const char *schema; + + if (s->helo == NULL) { + if (s->relay->helotable && s->route->src->sa) { + m_create(p_lka, IMSG_MTA_LOOKUP_HELO, 0, 0, -1); + m_add_id(p_lka, s->id); + m_add_string(p_lka, s->relay->helotable); + m_add_sockaddr(p_lka, s->route->src->sa); + m_close(p_lka); + tree_xset(&wait_helo, s->id, s); + s->flags |= MTA_WAIT; + return; + } + else if (s->relay->heloname) + s->helo = xstrdup(s->relay->heloname); + else + s->helo = xstrdup(env->sc_hostname); + } + + if (s->io) { + io_free(s->io); + s->io = NULL; + } + + s->use_smtps = s->use_starttls = s->use_smtp_tls = 0; + + switch (s->attempt) { + case 0: + if (s->flags & MTA_FORCE_SMTPS) + s->use_smtps = 1; /* smtps */ + else if (s->flags & (MTA_FORCE_TLS|MTA_FORCE_ANYSSL)) + s->use_starttls = 1; /* tls, tls+smtps */ + else if (!(s->flags & MTA_FORCE_PLAIN)) + s->use_smtp_tls = 1; + break; + case 1: + if (s->flags & MTA_FORCE_ANYSSL) { + s->use_smtps = 1; /* tls+smtps */ + break; + } + else if (s->flags & MTA_DOWNGRADE_PLAIN) { + /* smtp, with tls failure */ + break; + } + default: + mta_free(s); + return; + } + portno = s->use_smtps ? 465 : 25; + + /* Override with relay-specified port */ + if (s->relay->port) + portno = s->relay->port; + + memmove(&ss, s->route->dst->sa, SA_LEN(s->route->dst->sa)); + sa = (struct sockaddr *)&ss; + + if (sa->sa_family == AF_INET) + ((struct sockaddr_in *)sa)->sin_port = htons(portno); + else if (sa->sa_family == AF_INET6) + ((struct sockaddr_in6 *)sa)->sin6_port = htons(portno); + + s->attempt += 1; + if (s->use_smtp_tls) + schema = "smtp://"; + else if (s->use_starttls) + schema = "smtp+tls://"; + else if (s->use_smtps) + schema = "smtps://"; + else if (s->flags & MTA_LMTP) + schema = "lmtp://"; + else + schema = "smtp+notls://"; + + log_info("%016"PRIx64" mta " + "connecting address=%s%s:%d host=%s", + s->id, schema, sa_to_text(s->route->dst->sa), + portno, s->route->dst->ptrname); + + mta_enter_state(s, MTA_INIT); + s->io = io_new(); + io_set_callback(s->io, mta_io, s); + io_set_timeout(s->io, 300000); + if (io_connect(s->io, sa, s->route->src->sa) == -1) { + /* + * This error is most likely a "no route", + * so there is no need to try again. + */ + log_debug("debug: mta: io_connect failed: %s", io_error(s->io)); + if (errno == EADDRNOTAVAIL) + mta_source_error(s->relay, s->route, io_error(s->io)); + else + mta_error(s, "Connection failed: %s", io_error(s->io)); + mta_free(s); + } +} + +static void +mta_enter_state(struct mta_session *s, int newstate) +{ + struct mta_envelope *e; + size_t envid_sz; + int oldstate; + ssize_t q; + char ibuf[LINE_MAX]; + char obuf[LINE_MAX]; + int offset; + const char *srs_sender; + +again: + oldstate = s->state; + + log_trace(TRACE_MTA, "mta: %p: %s -> %s", s, + mta_strstate(oldstate), + mta_strstate(newstate)); + + s->state = newstate; + + memset(s->replybuf, 0, sizeof s->replybuf); + + /* don't try this at home! */ +#define mta_enter_state(_s, _st) do { newstate = _st; goto again; } while (0) + + switch (s->state) { + case MTA_INIT: + case MTA_BANNER: + break; + + case MTA_EHLO: + s->ext = 0; + mta_send(s, "EHLO %s", s->helo); + mta_report_link_identify(s, "EHLO", s->helo); + break; + + case MTA_HELO: + s->ext = 0; + mta_send(s, "HELO %s", s->helo); + mta_report_link_identify(s, "HELO", s->helo); + break; + + case MTA_LHLO: + s->ext = 0; + mta_send(s, "LHLO %s", s->helo); + mta_report_link_identify(s, "LHLO", s->helo); + break; + + case MTA_STARTTLS: + if (s->flags & MTA_DOWNGRADE_PLAIN) + mta_enter_state(s, MTA_AUTH); + if (s->flags & MTA_TLS) /* already started */ + mta_enter_state(s, MTA_AUTH); + else if ((s->ext & MTA_EXT_STARTTLS) == 0) { + if (s->flags & MTA_FORCE_TLS || s->flags & MTA_WANT_SECURE) { + mta_error(s, "TLS required but not supported by remote host"); + s->flags |= MTA_RECONN; + } + else + /* server doesn't support starttls, do not use it */ + mta_enter_state(s, MTA_AUTH); + } + else + mta_send(s, "STARTTLS"); + break; + + case MTA_AUTH: + if (s->relay->secret && s->flags & MTA_TLS) { + if (s->ext & MTA_EXT_AUTH) { + if (s->ext & MTA_EXT_AUTH_PLAIN) { + mta_enter_state(s, MTA_AUTH_PLAIN); + break; + } + if (s->ext & MTA_EXT_AUTH_LOGIN) { + mta_enter_state(s, MTA_AUTH_LOGIN); + break; + } + log_debug("debug: mta: %p: no supported AUTH method on session", s); + mta_error(s, "no supported AUTH method"); + } + else { + log_debug("debug: mta: %p: AUTH not advertised on session", s); + mta_error(s, "AUTH not advertised"); + } + } + else if (s->relay->secret) { + log_debug("debug: mta: %p: not using AUTH on non-TLS " + "session", s); + mta_error(s, "Refuse to AUTH over unsecure channel"); + mta_connect(s); + } else { + mta_enter_state(s, MTA_READY); + } + break; + + case MTA_AUTH_PLAIN: + memset(ibuf, 0, sizeof ibuf); + if (base64_decode(s->relay->secret, (unsigned char *)ibuf, + sizeof(ibuf)-1) == -1) { + log_debug("debug: mta: %p: credentials too large on session", s); + mta_error(s, "Credentials too large"); + break; + } + s->username = xstrdup(ibuf+1); + mta_send(s, "AUTH PLAIN %s", s->relay->secret); + break; + + case MTA_AUTH_LOGIN: + mta_send(s, "AUTH LOGIN"); + break; + + case MTA_AUTH_LOGIN_USER: + memset(ibuf, 0, sizeof ibuf); + if (base64_decode(s->relay->secret, (unsigned char *)ibuf, + sizeof(ibuf)-1) == -1) { + log_debug("debug: mta: %p: credentials too large on session", s); + mta_error(s, "Credentials too large"); + break; + } + s->username = xstrdup(ibuf+1); + + memset(obuf, 0, sizeof obuf); + base64_encode((unsigned char *)ibuf + 1, strlen(ibuf + 1), obuf, sizeof obuf); + mta_send(s, "%s", obuf); + + memset(ibuf, 0, sizeof ibuf); + memset(obuf, 0, sizeof obuf); + break; + + case MTA_AUTH_LOGIN_PASS: + memset(ibuf, 0, sizeof ibuf); + if (base64_decode(s->relay->secret, (unsigned char *)ibuf, + sizeof(ibuf)-1) == -1) { + log_debug("debug: mta: %p: credentials too large on session", s); + mta_error(s, "Credentials too large"); + break; + } + + offset = strlen(ibuf+1)+2; + memset(obuf, 0, sizeof obuf); + base64_encode((unsigned char *)ibuf + offset, strlen(ibuf + offset), obuf, sizeof obuf); + mta_send(s, "%s", obuf); + + memset(ibuf, 0, sizeof ibuf); + memset(obuf, 0, sizeof obuf); + break; + + case MTA_READY: + /* Ready to send a new mail */ + if (s->ready == 0) { + s->ready = 1; + s->relay->nconn_ready += 1; + mta_route_ok(s->relay, s->route); + } + + if (s->msgtried >= MAX_TRYBEFOREDISABLE) { + log_info("%016"PRIx64" mta host-rejects-all-mails", + s->id); + mta_route_down(s->relay, s->route); + mta_enter_state(s, MTA_QUIT); + break; + } + + if (s->msgcount >= s->relay->limits->max_mail_per_session) { + log_debug("debug: mta: " + "%p: cannot send more message to relay %s", s, + mta_relay_to_text(s->relay)); + mta_enter_state(s, MTA_QUIT); + break; + } + + /* + * When downgrading from opportunistic TLS, clear flag and + * possibly reuse the same task (forbidden in other cases). + */ + if (s->flags & MTA_DOWNGRADE_PLAIN) + s->flags &= ~MTA_DOWNGRADE_PLAIN; + else if (s->task) + fatalx("task should be NULL at this point"); + + if (s->task == NULL) + s->task = mta_route_next_task(s->relay, s->route); + if (s->task == NULL) { + log_debug("debug: mta: %p: no task for relay %s", + s, mta_relay_to_text(s->relay)); + + if (s->relay->nconn > 1 || + s->hangon >= s->relay->limits->sessdelay_keepalive) { + mta_enter_state(s, MTA_QUIT); + break; + } + + log_debug("mta: debug: last connection: hanging on for %llds", + (long long)(s->relay->limits->sessdelay_keepalive - + s->hangon)); + s->flags |= MTA_HANGON; + runq_schedule(hangon, 1, s); + break; + } + + log_debug("debug: mta: %p: handling next task for relay %s", s, + mta_relay_to_text(s->relay)); + + stat_increment("mta.task.running", 1); + + m_create(p_queue, IMSG_MTA_OPEN_MESSAGE, 0, 0, -1); + m_add_id(p_queue, s->id); + m_add_msgid(p_queue, s->task->msgid); + m_close(p_queue); + + tree_xset(&wait_fd, s->id, s); + s->flags |= MTA_WAIT; + break; + + case MTA_MAIL: + s->currevp = TAILQ_FIRST(&s->task->envelopes); + + e = s->currevp; + s->hangon = 0; + s->msgtried++; + envid_sz = strlen(e->dsn_envid); + + /* SRS-encode if requested for the relay action, AND we're not + * bouncing, AND we have an RCPT which means we are forwarded, + * AND the RCPT has a '@' just for sanity check (will always). + */ + if (env->sc_srs_key != NULL && + s->relay->srs && + strchr(s->task->sender, '@') && + e->rcpt && + strchr(e->rcpt, '@')) { + /* encode and replace task sender with new SRS-sender */ + srs_sender = srs_encode(s->task->sender, + strchr(e->rcpt, '@') + 1); + if (srs_sender) { + free(s->task->sender); + s->task->sender = xstrdup(srs_sender); + } + } + + if (s->ext & MTA_EXT_DSN) { + mta_send(s, "MAIL FROM:<%s>%s%s%s%s", + s->task->sender, + e->dsn_ret ? " RET=" : "", + e->dsn_ret ? dsn_strret(e->dsn_ret) : "", + envid_sz ? " ENVID=" : "", + envid_sz ? e->dsn_envid : ""); + } else + mta_send(s, "MAIL FROM:<%s>", s->task->sender); + break; + + case MTA_RCPT: + if (s->currevp == NULL) + s->currevp = TAILQ_FIRST(&s->task->envelopes); + + e = s->currevp; + if (s->ext & MTA_EXT_DSN) { + mta_send(s, "RCPT TO:<%s>%s%s%s%s", + e->dest, + e->dsn_notify ? " NOTIFY=" : "", + e->dsn_notify ? dsn_strnotify(e->dsn_notify) : "", + e->dsn_orcpt ? " ORCPT=rfc822;" : "", + e->dsn_orcpt ? e->dsn_orcpt : ""); + } else + mta_send(s, "RCPT TO:<%s>", e->dest); + + mta_report_tx_envelope(s, s->task->msgid, e->id); + s->rcptcount++; + break; + + case MTA_DATA: + fseek(s->datafp, 0, SEEK_SET); + mta_send(s, "DATA"); + break; + + case MTA_BODY: + if (s->datafp == NULL) { + log_trace(TRACE_MTA, "mta: %p: end-of-file", s); + mta_enter_state(s, MTA_EOM); + break; + } + + if ((q = mta_queue_data(s)) == -1) { + s->flags |= MTA_FREE; + break; + } + if (q == 0) { + mta_enter_state(s, MTA_BODY); + break; + } + + log_trace(TRACE_MTA, "mta: %p: >>> [...%zd bytes...]", s, q); + break; + + case MTA_EOM: + mta_send(s, "."); + break; + + case MTA_LMTP_EOM: + /* LMTP reports status of each delivery, so enable read */ + io_set_read(s->io); + break; + + case MTA_RSET: + if (s->datafp) { + fclose(s->datafp); + s->datafp = NULL; + s->datalen = 0; + } + mta_send(s, "RSET"); + break; + + case MTA_QUIT: + mta_send(s, "QUIT"); + break; + + default: + fatalx("mta_enter_state: unknown state"); + } +#undef mta_enter_state +} + +/* + * Handle a response to an SMTP command + */ +static void +mta_response(struct mta_session *s, char *line) +{ + struct mta_envelope *e; + struct sockaddr_storage ss; + struct sockaddr *sa; + const char *domain; + char *pbuf; + socklen_t sa_len; + char buf[LINE_MAX]; + int delivery; + + switch (s->state) { + + case MTA_BANNER: + if (line[0] != '2') { + mta_error(s, "BANNER rejected: %s", line); + s->flags |= MTA_FREE; + return; + } + + pbuf = ""; + if (strlen(line) > 4) { + (void)strlcpy(buf, line + 4, sizeof buf); + if ((pbuf = strchr(buf, ' '))) + *pbuf = '\0'; + pbuf = valid_domainpart(buf) ? buf : ""; + } + mta_report_link_greeting(s, pbuf); + + if (s->flags & MTA_LMTP) + mta_enter_state(s, MTA_LHLO); + else + mta_enter_state(s, MTA_EHLO); + break; + + case MTA_EHLO: + if (line[0] != '2') { + /* rejected at ehlo state */ + if ((s->relay->flags & RELAY_AUTH) || + (s->flags & MTA_WANT_SECURE)) { + mta_error(s, "EHLO rejected: %s", line); + s->flags |= MTA_FREE; + return; + } + mta_enter_state(s, MTA_HELO); + return; + } + if (!(s->flags & MTA_FORCE_PLAIN)) + mta_enter_state(s, MTA_STARTTLS); + else + mta_enter_state(s, MTA_READY); + break; + + case MTA_HELO: + if (line[0] != '2') { + mta_error(s, "HELO rejected: %s", line); + s->flags |= MTA_FREE; + return; + } + mta_enter_state(s, MTA_READY); + break; + + case MTA_LHLO: + if (line[0] != '2') { + mta_error(s, "LHLO rejected: %s", line); + s->flags |= MTA_FREE; + return; + } + mta_enter_state(s, MTA_READY); + break; + + case MTA_STARTTLS: + if (line[0] != '2') { + if (!(s->flags & MTA_WANT_SECURE)) { + mta_enter_state(s, MTA_AUTH); + return; + } + /* XXX mark that the MX doesn't support STARTTLS */ + mta_error(s, "STARTTLS rejected: %s", line); + s->flags |= MTA_FREE; + return; + } + + mta_cert_init(s); + break; + + case MTA_AUTH_PLAIN: + if (line[0] != '2') { + mta_error(s, "AUTH rejected: %s", line); + mta_report_link_auth(s, s->username, "fail"); + s->flags |= MTA_FREE; + return; + } + mta_report_link_auth(s, s->username, "pass"); + mta_enter_state(s, MTA_READY); + break; + + case MTA_AUTH_LOGIN: + if (strncmp(line, "334 ", 4) != 0) { + mta_error(s, "AUTH rejected: %s", line); + mta_report_link_auth(s, s->username, "fail"); + s->flags |= MTA_FREE; + return; + } + mta_enter_state(s, MTA_AUTH_LOGIN_USER); + break; + + case MTA_AUTH_LOGIN_USER: + if (strncmp(line, "334 ", 4) != 0) { + mta_error(s, "AUTH rejected: %s", line); + mta_report_link_auth(s, s->username, "fail"); + s->flags |= MTA_FREE; + return; + } + mta_enter_state(s, MTA_AUTH_LOGIN_PASS); + break; + + case MTA_AUTH_LOGIN_PASS: + if (line[0] != '2') { + mta_error(s, "AUTH rejected: %s", line); + mta_report_link_auth(s, s->username, "fail"); + s->flags |= MTA_FREE; + return; + } + mta_report_link_auth(s, s->username, "pass"); + mta_enter_state(s, MTA_READY); + break; + + case MTA_MAIL: + if (line[0] != '2') { + if (line[0] == '5') + delivery = IMSG_MTA_DELIVERY_PERMFAIL; + else + delivery = IMSG_MTA_DELIVERY_TEMPFAIL; + + mta_flush_task(s, delivery, line, 0, 0); + mta_enter_state(s, MTA_RSET); + return; + } + mta_report_tx_begin(s, s->task->msgid); + mta_report_tx_mail(s, s->task->msgid, s->task->sender, 1); + mta_enter_state(s, MTA_RCPT); + break; + + case MTA_RCPT: + e = s->currevp; + + /* remove envelope from hosttat cache if there */ + if ((domain = strchr(e->dest, '@')) != NULL) { + domain++; + mta_hoststat_uncache(domain, e->id); + } + + s->currevp = TAILQ_NEXT(s->currevp, entry); + if (line[0] == '2') { + s->failures = 0; + /* + * this host is up, reschedule envelopes that + * were cached for reschedule. + */ + if (domain) + mta_hoststat_reschedule(domain); + } + else { + mta_report_tx_rollback(s, s->task->msgid); + mta_report_tx_reset(s, s->task->msgid); + if (line[0] == '5') + delivery = IMSG_MTA_DELIVERY_PERMFAIL; + else + delivery = IMSG_MTA_DELIVERY_TEMPFAIL; + s->failures++; + + /* remove failed envelope from task list */ + TAILQ_REMOVE(&s->task->envelopes, e, entry); + stat_decrement("mta.envelope", 1); + + /* log right away */ + (void)snprintf(buf, sizeof(buf), "%s", + mta_host_to_text(s->route->dst)); + + e->session = s->id; + /* XXX */ + /* + * getsockname() can only fail with ENOBUFS here + * best effort, don't log source ... + */ + sa_len = sizeof(ss); + sa = (struct sockaddr *)&ss; + if (getsockname(io_fileno(s->io), sa, &sa_len) == -1) + mta_delivery_log(e, NULL, buf, delivery, line); + else + mta_delivery_log(e, sa_to_text(sa), + buf, delivery, line); + + if (domain) + mta_hoststat_update(domain, e->status); + mta_delivery_notify(e); + + if (s->relay->limits->max_failures_per_session && + s->failures == s->relay->limits->max_failures_per_session) { + mta_flush_task(s, IMSG_MTA_DELIVERY_TEMPFAIL, + "Too many consecutive errors, closing connection", 0, 1); + mta_enter_state(s, MTA_QUIT); + break; + } + + /* + * if no more envelopes, flush failed queue + */ + if (TAILQ_EMPTY(&s->task->envelopes)) { + mta_flush_task(s, IMSG_MTA_DELIVERY_OK, + "No envelope", 0, 0); + mta_enter_state(s, MTA_RSET); + break; + } + } + + switch (line[0]) { + case '2': + mta_report_tx_rcpt(s, + s->task->msgid, e->dest, 1); + break; + case '4': + mta_report_tx_rcpt(s, + s->task->msgid, e->dest, -1); + break; + case '5': + mta_report_tx_rcpt(s, + s->task->msgid, e->dest, 0); + break; + } + + if (s->currevp == NULL) + mta_enter_state(s, MTA_DATA); + else + mta_enter_state(s, MTA_RCPT); + break; + + case MTA_DATA: + if (line[0] == '2' || line[0] == '3') { + mta_report_tx_data(s, s->task->msgid, 1); + mta_enter_state(s, MTA_BODY); + break; + } + + if (line[0] == '5') + delivery = IMSG_MTA_DELIVERY_PERMFAIL; + else + delivery = IMSG_MTA_DELIVERY_TEMPFAIL; + mta_report_tx_data(s, s->task->msgid, + delivery == IMSG_MTA_DELIVERY_TEMPFAIL ? -1 : 0); + mta_report_tx_rollback(s, s->task->msgid); + mta_report_tx_reset(s, s->task->msgid); + mta_flush_task(s, delivery, line, 0, 0); + mta_enter_state(s, MTA_RSET); + break; + + case MTA_LMTP_EOM: + case MTA_EOM: + if (line[0] == '2') { + delivery = IMSG_MTA_DELIVERY_OK; + s->msgtried = 0; + s->msgcount++; + } + else if (line[0] == '5') + delivery = IMSG_MTA_DELIVERY_PERMFAIL; + else + delivery = IMSG_MTA_DELIVERY_TEMPFAIL; + if (delivery != IMSG_MTA_DELIVERY_OK) { + mta_report_tx_rollback(s, s->task->msgid); + mta_report_tx_reset(s, s->task->msgid); + } + else { + mta_report_tx_commit(s, s->task->msgid, s->datalen); + mta_report_tx_reset(s, s->task->msgid); + } + mta_flush_task(s, delivery, line, (s->flags & MTA_LMTP) ? 1 : 0, 0); + if (s->task) { + s->rcptcount--; + mta_enter_state(s, MTA_LMTP_EOM); + } else { + s->rcptcount = 0; + if (s->relay->limits->sessdelay_transaction) { + log_debug("debug: mta: waiting for %llds before next transaction", + (long long int)s->relay->limits->sessdelay_transaction); + s->hangon = s->relay->limits->sessdelay_transaction -1; + s->flags |= MTA_HANGON; + runq_schedule(hangon, + s->relay->limits->sessdelay_transaction, s); + } + else + mta_enter_state(s, MTA_READY); + } + break; + + case MTA_RSET: + s->rcptcount = 0; + + if (s->task) { + mta_report_tx_rollback(s, s->task->msgid); + mta_report_tx_reset(s, s->task->msgid); + } + if (s->relay->limits->sessdelay_transaction) { + log_debug("debug: mta: waiting for %llds after reset", + (long long int)s->relay->limits->sessdelay_transaction); + s->hangon = s->relay->limits->sessdelay_transaction -1; + s->flags |= MTA_HANGON; + runq_schedule(hangon, + s->relay->limits->sessdelay_transaction, s); + } + else + mta_enter_state(s, MTA_READY); + break; + + default: + fatalx("mta_response() bad state"); + } +} + +static void +mta_io(struct io *io, int evt, void *arg) +{ + struct mta_session *s = arg; + char *line, *msg, *p; + size_t len; + const char *error; + int cont; + + log_trace(TRACE_IO, "mta: %p: %s %s", s, io_strevent(evt), + io_strio(io)); + + switch (evt) { + + case IO_CONNECTED: + mta_connected(s); + + if (s->use_smtps) { + io_set_write(io); + mta_cert_init(s); + } + else { + mta_enter_state(s, MTA_BANNER); + io_set_read(io); + } + break; + + case IO_TLSREADY: + log_info("%016"PRIx64" mta tls ciphers=%s", + s->id, ssl_to_text(io_tls(s->io))); + s->flags |= MTA_TLS; + + mta_report_link_tls(s, + ssl_to_text(io_tls(s->io))); + + mta_cert_verify(s); + break; + + case IO_DATAIN: + nextline: + line = io_getline(s->io, &len); + if (line == NULL) { + if (io_datalen(s->io) >= LINE_MAX) { + mta_error(s, "Input too long"); + mta_free(s); + } + return; + } + + /* Strip trailing '\r' */ + if (len && line[len - 1] == '\r') + line[--len] = '\0'; + + log_trace(TRACE_MTA, "mta: %p: <<< %s", s, line); + mta_report_protocol_server(s, line); + + if ((error = parse_smtp_response(line, len, &msg, &cont))) { + mta_error(s, "Bad response: %s", error); + mta_free(s); + return; + } + + /* read extensions */ + if (s->state == MTA_EHLO) { + if (strcmp(msg, "STARTTLS") == 0) + s->ext |= MTA_EXT_STARTTLS; + else if (strncmp(msg, "AUTH ", 5) == 0) { + s->ext |= MTA_EXT_AUTH; + if ((p = strstr(msg, " PLAIN")) && + (*(p+6) == '\0' || *(p+6) == ' ')) + s->ext |= MTA_EXT_AUTH_PLAIN; + if ((p = strstr(msg, " LOGIN")) && + (*(p+6) == '\0' || *(p+6) == ' ')) + s->ext |= MTA_EXT_AUTH_LOGIN; + } + else if (strcmp(msg, "PIPELINING") == 0) + s->ext |= MTA_EXT_PIPELINING; + else if (strcmp(msg, "DSN") == 0) + s->ext |= MTA_EXT_DSN; + else if (strncmp(msg, "SIZE ", 5) == 0) { + s->ext_size = strtonum(msg+5, 0, UINT32_MAX, &error); + if (error == NULL) + s->ext |= MTA_EXT_SIZE; + } + } + + /* continuation reply, we parse out the repeating statuses and ESC */ + if (cont) { + if (s->replybuf[0] == '\0') + (void)strlcat(s->replybuf, line, sizeof s->replybuf); + else if (len > 4) { + p = line + 4; + if (isdigit((unsigned char)p[0]) && p[1] == '.' && + isdigit((unsigned char)p[2]) && p[3] == '.' && + isdigit((unsigned char)p[4]) && isspace((unsigned char)p[5])) + p += 5; + (void)strlcat(s->replybuf, p, sizeof s->replybuf); + } + goto nextline; + } + + /* last line of a reply, check if we're on a continuation to parse out status and ESC. + * if we overflow reply buffer or are not on continuation, log entire last line. + */ + if (s->replybuf[0] == '\0') + (void)strlcat(s->replybuf, line, sizeof s->replybuf); + else if (len > 4) { + p = line + 4; + if (isdigit((unsigned char)p[0]) && p[1] == '.' && + isdigit((unsigned char)p[2]) && p[3] == '.' && + isdigit((unsigned char)p[4]) && isspace((unsigned char)p[5])) + p += 5; + if (strlcat(s->replybuf, p, sizeof s->replybuf) >= sizeof s->replybuf) + (void)strlcpy(s->replybuf, line, sizeof s->replybuf); + } + + if (s->state == MTA_QUIT) { + log_info("%016"PRIx64" mta disconnected reason=quit messages=%zu", + s->id, s->msgcount); + mta_free(s); + return; + } + io_set_write(io); + mta_response(s, s->replybuf); + if (s->flags & MTA_FREE) { + mta_free(s); + return; + } + if (s->flags & MTA_RECONN) { + s->flags &= ~MTA_RECONN; + mta_connect(s); + return; + } + + if (io_datalen(s->io)) { + log_debug("debug: mta: remaining data in input buffer"); + mta_error(s, "Remote host sent too much data"); + if (s->flags & MTA_WAIT) + s->flags |= MTA_FREE; + else + mta_free(s); + } + break; + + case IO_LOWAT: + if (s->state == MTA_BODY) { + mta_enter_state(s, MTA_BODY); + if (s->flags & MTA_FREE) { + mta_free(s); + return; + } + } + + if (io_queued(s->io) == 0) + io_set_read(io); + break; + + case IO_TIMEOUT: + log_debug("debug: mta: %p: connection timeout", s); + mta_error(s, "Connection timeout"); + mta_report_timeout(s); + if (!s->ready) + mta_connect(s); + else + mta_free(s); + break; + + case IO_ERROR: + case IO_TLSERROR: + log_debug("debug: mta: %p: IO error: %s", s, io_error(io)); + + if (s->state == MTA_STARTTLS && s->use_smtp_tls) { + /* error in non-strict SSL negotiation, downgrade to plain */ + log_info("smtp-out: Error on session %016"PRIx64 + ": opportunistic TLS failed, " + "downgrading to plain", s->id); + s->flags &= ~MTA_TLS; + s->flags |= MTA_DOWNGRADE_PLAIN; + mta_connect(s); + break; + } + + mta_error(s, "IO Error: %s", io_error(io)); + mta_free(s); + break; + + case IO_DISCONNECTED: + log_debug("debug: mta: %p: disconnected in state %s", + s, mta_strstate(s->state)); + mta_error(s, "Connection closed unexpectedly"); + if (!s->ready) + mta_connect(s); + else + mta_free(s); + break; + + default: + fatalx("mta_io() bad event"); + } +} + +static void +mta_send(struct mta_session *s, char *fmt, ...) +{ + va_list ap; + char *p; + int len; + + va_start(ap, fmt); + if ((len = vasprintf(&p, fmt, ap)) == -1) + fatal("mta: vasprintf"); + va_end(ap); + + log_trace(TRACE_MTA, "mta: %p: >>> %s", s, p); + + if (strncasecmp(p, "AUTH PLAIN ", 11) == 0) + mta_report_protocol_client(s, "AUTH PLAIN ********"); + else if (s->state == MTA_AUTH_LOGIN_USER || s->state == MTA_AUTH_LOGIN_PASS) + mta_report_protocol_client(s, "********"); + else + mta_report_protocol_client(s, p); + + io_xprintf(s->io, "%s\r\n", p); + + free(p); +} + +/* + * Queue some data into the input buffer + */ +static ssize_t +mta_queue_data(struct mta_session *s) +{ + char *ln = NULL; + size_t sz = 0, q; + ssize_t len; + + q = io_queued(s->io); + + while (io_queued(s->io) < MTA_HIWAT) { + if ((len = getline(&ln, &sz, s->datafp)) == -1) + break; + if (ln[len - 1] == '\n') + ln[len - 1] = '\0'; + s->datalen += io_xprintf(s->io, "%s%s\r\n", *ln == '.' ? "." : "", ln); + } + + free(ln); + if (ferror(s->datafp)) { + mta_flush_task(s, IMSG_MTA_DELIVERY_TEMPFAIL, + "Error reading content file", 0, 0); + return (-1); + } + + if (feof(s->datafp)) { + fclose(s->datafp); + s->datafp = NULL; + } + + return (io_queued(s->io) - q); +} + +static void +mta_flush_task(struct mta_session *s, int delivery, const char *error, size_t count, + int cache) +{ + struct mta_envelope *e; + char relay[LINE_MAX]; + size_t n; + struct sockaddr_storage ss; + struct sockaddr *sa; + socklen_t sa_len; + const char *domain; + + (void)snprintf(relay, sizeof relay, "%s", mta_host_to_text(s->route->dst)); + n = 0; + while ((e = TAILQ_FIRST(&s->task->envelopes))) { + + if (count && n == count) { + stat_decrement("mta.envelope", n); + return; + } + + TAILQ_REMOVE(&s->task->envelopes, e, entry); + + /* we're about to log, associate session to envelope */ + e->session = s->id; + e->ext = s->ext; + + /* XXX */ + /* + * getsockname() can only fail with ENOBUFS here + * best effort, don't log source ... + */ + sa = (struct sockaddr *)&ss; + sa_len = sizeof(ss); + if (getsockname(io_fileno(s->io), sa, &sa_len) == -1) + mta_delivery_log(e, NULL, relay, delivery, error); + else + mta_delivery_log(e, sa_to_text(sa), + relay, delivery, error); + + mta_delivery_notify(e); + + domain = strchr(e->dest, '@'); + if (domain) { + domain++; + mta_hoststat_update(domain, error); + if (cache) + mta_hoststat_cache(domain, e->id); + } + + n++; + } + + free(s->task->sender); + free(s->task); + s->task = NULL; + + if (s->datafp) { + fclose(s->datafp); + s->datafp = NULL; + } + + stat_decrement("mta.envelope", n); + stat_decrement("mta.task.running", 1); + stat_decrement("mta.task", 1); +} + +static void +mta_error(struct mta_session *s, const char *fmt, ...) +{ + va_list ap; + char *error; + int len; + + va_start(ap, fmt); + if ((len = vasprintf(&error, fmt, ap)) == -1) + fatal("mta: vasprintf"); + va_end(ap); + + if (s->msgcount) + log_info("smtp-out: Error on session %016"PRIx64 + " after %zu message%s sent: %s", s->id, s->msgcount, + (s->msgcount > 1) ? "s" : "", error); + else + log_info("%016"PRIx64" mta error reason=%s", + s->id, error); + + /* + * If not connected yet, and the error is not local, just ignore it + * and try to reconnect. + */ + if (s->state == MTA_INIT && + (errno == ETIMEDOUT || errno == ECONNREFUSED)) { + log_debug("debug: mta: not reporting route error yet"); + free(error); + return; + } + + mta_route_error(s->relay, s->route); + + if (s->task) + mta_flush_task(s, IMSG_MTA_DELIVERY_TEMPFAIL, error, 0, 0); + + free(error); +} + +static void +mta_cert_init(struct mta_session *s) +{ + const char *name; + int fallback; + + if (s->relay->pki_name) { + name = s->relay->pki_name; + fallback = 0; + } + else { + name = s->helo; + fallback = 1; + } + + if (cert_init(name, fallback, mta_cert_init_cb, s)) { + tree_xset(&wait_tls_init, s->id, s); + s->flags |= MTA_WAIT; + } +} + +static void +mta_cert_init_cb(void *arg, int status, const char *name, const void *cert, + size_t cert_len) +{ + struct mta_session *s = arg; + void *ssl; + char *xname = NULL, *xcert = NULL; + + if (s->flags & MTA_WAIT) + mta_tree_pop(&wait_tls_init, s->id); + + if (status == CA_FAIL && s->relay->pki_name) { + log_info("%016"PRIx64" mta closing reason=ca-failure", s->id); + mta_free(s); + return; + } + + if (name) + xname = xstrdup(name); + if (cert) + xcert = xmemdup(cert, cert_len); + ssl = ssl_mta_init(xname, xcert, cert_len, env->sc_tls_ciphers); + free(xname); + free(xcert); + if (ssl == NULL) + fatal("mta: ssl_mta_init"); + io_start_tls(s->io, ssl); +} + +static void +mta_cert_verify(struct mta_session *s) +{ + const char *name; + int fallback; + + if (s->relay->ca_name) { + name = s->relay->ca_name; + fallback = 0; + } + else { + name = s->helo; + fallback = 1; + } + + if (cert_verify(io_tls(s->io), name, fallback, mta_cert_verify_cb, s)) { + tree_xset(&wait_tls_verify, s->id, s); + io_pause(s->io, IO_IN); + s->flags |= MTA_WAIT; + } +} + +static void +mta_cert_verify_cb(void *arg, int status) +{ + struct mta_session *s = arg; + int match, resume = 0; + X509 *cert; + + if (s->flags & MTA_WAIT) { + mta_tree_pop(&wait_tls_verify, s->id); + resume = 1; + } + + if (status == CERT_OK) { + cert = SSL_get_peer_certificate(io_tls(s->io)); + if (!cert) + status = CERT_NOCERT; + else { + match = 0; + (void)ssl_check_name(cert, s->mxname, &match); + X509_free(cert); + if (!match) { + log_info("%016"PRIx64" mta " + "ssl_check_name: no match for '%s' in cert", + s->id, s->mxname); + status = CERT_INVALID; + } + } + } + + if (status == CERT_OK) + s->flags |= MTA_TLS_VERIFIED; + else if (s->relay->flags & RELAY_TLS_VERIFY) { + errno = 0; + mta_error(s, "SSL certificate check failed"); + mta_free(s); + return; + } + + mta_tls_verified(s); + if (resume) + io_resume(s->io, IO_IN); +} + +static void +mta_tls_verified(struct mta_session *s) +{ + X509 *x; + + x = SSL_get_peer_certificate(io_tls(s->io)); + if (x) { + log_info("%016"PRIx64" mta " + "server-cert-check result=\"%s\"", + s->id, + (s->flags & MTA_TLS_VERIFIED) ? "success" : "failure"); + X509_free(x); + } + + if (s->use_smtps) { + mta_enter_state(s, MTA_BANNER); + io_set_read(s->io); + } + else + mta_enter_state(s, MTA_EHLO); +} + +static const char * +dsn_strret(enum dsn_ret ret) +{ + if (ret == DSN_RETHDRS) + return "HDRS"; + else if (ret == DSN_RETFULL) + return "FULL"; + else { + log_debug("mta: invalid ret %d", ret); + return "???"; + } +} + +static const char * +dsn_strnotify(uint8_t arg) +{ + static char buf[32]; + size_t sz; + + buf[0] = '\0'; + if (arg & DSN_SUCCESS) + (void)strlcat(buf, "SUCCESS,", sizeof(buf)); + + if (arg & DSN_FAILURE) + (void)strlcat(buf, "FAILURE,", sizeof(buf)); + + if (arg & DSN_DELAY) + (void)strlcat(buf, "DELAY,", sizeof(buf)); + + if (arg & DSN_NEVER) + (void)strlcat(buf, "NEVER,", sizeof(buf)); + + /* trim trailing comma */ + sz = strlen(buf); + if (sz) + buf[sz - 1] = '\0'; + + return (buf); +} + +#define CASE(x) case x : return #x + +static const char * +mta_strstate(int state) +{ + switch (state) { + CASE(MTA_INIT); + CASE(MTA_BANNER); + CASE(MTA_EHLO); + CASE(MTA_HELO); + CASE(MTA_STARTTLS); + CASE(MTA_AUTH); + CASE(MTA_AUTH_PLAIN); + CASE(MTA_AUTH_LOGIN); + CASE(MTA_AUTH_LOGIN_USER); + CASE(MTA_AUTH_LOGIN_PASS); + CASE(MTA_READY); + CASE(MTA_MAIL); + CASE(MTA_RCPT); + CASE(MTA_DATA); + CASE(MTA_BODY); + CASE(MTA_EOM); + CASE(MTA_LMTP_EOM); + CASE(MTA_RSET); + CASE(MTA_QUIT); + default: + return "MTA_???"; + } +} + +static void +mta_filter_begin(struct mta_session *s) +{ + if (!SESSION_FILTERED(s)) + return; + + m_create(p_lka, IMSG_FILTER_SMTP_BEGIN, 0, 0, -1); + m_add_id(p_lka, s->id); + m_add_string(p_lka, s->relay->dispatcher->u.remote.filtername); + m_close(p_lka); +} + +static void +mta_filter_end(struct mta_session *s) +{ + if (!SESSION_FILTERED(s)) + return; + + m_create(p_lka, IMSG_FILTER_SMTP_END, 0, 0, -1); + m_add_id(p_lka, s->id); + m_close(p_lka); +} + +static void +mta_connected(struct mta_session *s) +{ + struct sockaddr sa_src; + struct sockaddr sa_dest; + int sa_len; + + log_info("%016"PRIx64" mta connected", s->id); + + if (getsockname(io_fileno(s->io), &sa_src, &sa_len) == -1) + bzero(&sa_src, sizeof sa_src); + if (getpeername(io_fileno(s->io), &sa_dest, &sa_len) == -1) + bzero(&sa_dest, sizeof sa_dest); + + mta_report_link_connect(s, + s->route->dst->ptrname, 1, + (struct sockaddr_storage *)&sa_src, + (struct sockaddr_storage *)&sa_dest); +} + +static void +mta_disconnected(struct mta_session *s) +{ + mta_report_link_disconnect(s); + mta_filter_end(s); +} + + +static void +mta_report_link_connect(struct mta_session *s, const char *rdns, int fcrdns, + const struct sockaddr_storage *ss_src, + const struct sockaddr_storage *ss_dest) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_connect("smtp-out", s->id, rdns, fcrdns, ss_src, ss_dest); +} + +static void +mta_report_link_greeting(struct mta_session *s, + const char *domain) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_greeting("smtp-out", s->id, domain); +} + +static void +mta_report_link_identify(struct mta_session *s, const char *method, const char *identity) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_identify("smtp-out", s->id, method, identity); +} + +static void +mta_report_link_tls(struct mta_session *s, const char *ssl) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_tls("smtp-out", s->id, ssl); +} + +static void +mta_report_link_disconnect(struct mta_session *s) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_disconnect("smtp-out", s->id); +} + +static void +mta_report_link_auth(struct mta_session *s, const char *user, const char *result) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_auth("smtp-out", s->id, user, result); +} + +static void +mta_report_tx_reset(struct mta_session *s, uint32_t msgid) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_reset("smtp-out", s->id, msgid); +} + +static void +mta_report_tx_begin(struct mta_session *s, uint32_t msgid) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_begin("smtp-out", s->id, msgid); +} + +static void +mta_report_tx_mail(struct mta_session *s, uint32_t msgid, const char *address, int ok) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_mail("smtp-out", s->id, msgid, address, ok); +} + +static void +mta_report_tx_rcpt(struct mta_session *s, uint32_t msgid, const char *address, int ok) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_rcpt("smtp-out", s->id, msgid, address, ok); +} + +static void +mta_report_tx_envelope(struct mta_session *s, uint32_t msgid, uint64_t evpid) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_envelope("smtp-out", s->id, msgid, evpid); +} + +static void +mta_report_tx_data(struct mta_session *s, uint32_t msgid, int ok) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_data("smtp-out", s->id, msgid, ok); +} + +static void +mta_report_tx_commit(struct mta_session *s, uint32_t msgid, size_t msgsz) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_commit("smtp-out", s->id, msgid, msgsz); +} + +static void +mta_report_tx_rollback(struct mta_session *s, uint32_t msgid) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_rollback("smtp-out", s->id, msgid); +} + +static void +mta_report_protocol_client(struct mta_session *s, const char *command) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_protocol_client("smtp-out", s->id, command); +} + +static void +mta_report_protocol_server(struct mta_session *s, const char *response) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_protocol_server("smtp-out", s->id, response); +} + +#if 0 +static void +mta_report_filter_response(struct mta_session *s, int phase, int response, const char *param) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_filter_response("smtp-out", s->id, phase, response, param); +} +#endif + +static void +mta_report_timeout(struct mta_session *s) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_timeout("smtp-out", s->id); +} diff --git a/foobar/portable/smtpd/newaliases.8 b/foobar/portable/smtpd/newaliases.8 new file mode 100644 index 00000000..b82e4515 --- /dev/null +++ b/foobar/portable/smtpd/newaliases.8 @@ -0,0 +1,86 @@ +.\" $OpenBSD: newaliases.8,v 1.12 2018/07/20 15:35:33 millert Exp $ +.\" +.\" Copyright (c) 2009 Jacek Masiulaniec <jacekm@openbsd.org> +.\" Copyright (c) 2008-2009 Gilles Chehade <gilles@poolp.org> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: July 20 2018 $ +.Dt NEWALIASES 8 +.Os +.Sh NAME +.Nm newaliases +.Nd rebuild mail aliases +.Sh SYNOPSIS +.Nm newaliases +.Op Fl f Ar file +.Sh DESCRIPTION +The +.Nm +utility makes changes to the mail aliases file visible to +.Xr smtpd 8 . +It should be run every time the +.Xr aliases 5 +file is changed. +The location of the alias file is defined in +.Xr smtpd.conf 5 , +and defaults to +.Pa /etc/mail/aliases . +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl f Ar file +Use +.Ar file +as the configuration file, +instead of the default +.Pa /etc/mail/smtpd.conf . +.El +.Pp +If using database (db) files, +.Nm +is equivalent to running +.Xr makemap 8 +as follows: +.Bd -literal -offset indent +# makemap -t aliases /etc/mail/aliases +.Ed +.Pp +If using plain text files, +.Nm +is equivalent to running +.Xr smtpctl 8 +as follows: +.Bd -literal -offset indent +# smtpctl update table aliases +.Ed +.Sh FILES +.Bl -tag -width "/etc/mail/aliasesXXX" -compact +.It Pa /etc/mail/aliases +List of local user mail aliases. +.It Pa /etc/mail/virtual +List of virtual host aliases. +.El +.Sh EXIT STATUS +.Ex -std newaliases +.Sh SEE ALSO +.Xr smtpd.conf 5 , +.Xr makemap 8 , +.Xr smtpctl 8 , +.Xr smtpd 8 +.Sh HISTORY +The +.Nm +command first appeared in +.Ox 4.6 +as a replacement for the equivalent command shipped with sendmail. diff --git a/foobar/portable/smtpd/parse.y b/foobar/portable/smtpd/parse.y new file mode 100644 index 00000000..db5be747 --- /dev/null +++ b/foobar/portable/smtpd/parse.y @@ -0,0 +1,3598 @@ +/* $OpenBSD: parse.y,v 1.277 2020/02/24 23:54:27 millert Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> + * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org> + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. + * Copyright (c) 2001 Theo de Raadt. All rights reserved. + * + * 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 "includes.h" + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/ioctl.h> + +#include <net/if.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <ifaddrs.h> +#include <imsg.h> +#include <inttypes.h> +#include <limits.h> +#include <netdb.h> +#include <pwd.h> +#include <stdarg.h> +#include <resolv.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#ifdef HAVE_UTIL_H +#include <util.h> +#endif + +#include <openssl/ssl.h> + +#include "smtpd.h" +#include "ssl.h" +#include "log.h" + +TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); +static struct file { + TAILQ_ENTRY(file) entry; + FILE *stream; + char *name; + size_t ungetpos; + size_t ungetsize; + u_char *ungetbuf; + int eof_reached; + int lineno; + int errors; +} *file, *topfile; +struct file *pushfile(const char *, int); +int popfile(void); +int check_file_secrecy(int, const char *); +int yyparse(void); +int yylex(void); +int kw_cmp(const void *, const void *); +int lookup(char *); +int igetc(void); +int lgetc(int); +void lungetc(int); +int findeol(void); +int yyerror(const char *, ...) + __attribute__((__format__ (printf, 1, 2))) + __attribute__((__nonnull__ (1))); + +TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); +struct sym { + TAILQ_ENTRY(sym) entry; + int used; + int persist; + char *nam; + char *val; +}; +int symset(const char *, const char *, int); +char *symget(const char *); + +struct smtpd *conf = NULL; +static int errors = 0; + +struct table *table = NULL; +struct mta_limits *limits; +static struct pki *pki; +static struct ca *sca; + +struct dispatcher *dispatcher; +struct rule *rule; +struct filter_proc *processor; +struct filter_config *filter_config; +static uint32_t last_dynchain_id = 1; + +enum listen_options { + LO_FAMILY = 0x000001, + LO_PORT = 0x000002, + LO_SSL = 0x000004, + LO_FILTER = 0x000008, + LO_PKI = 0x000010, + LO_AUTH = 0x000020, + LO_TAG = 0x000040, + LO_HOSTNAME = 0x000080, + LO_HOSTNAMES = 0x000100, + LO_MASKSOURCE = 0x000200, + LO_NODSN = 0x000400, + LO_SENDERS = 0x000800, + LO_RECEIVEDAUTH = 0x001000, + LO_MASQUERADE = 0x002000, + LO_CA = 0x004000, + LO_PROXY = 0x008000, +}; + +static struct listen_opts { + char *ifx; + int family; + in_port_t port; + uint16_t ssl; + char *filtername; + char *pki; + char *ca; + uint16_t auth; + struct table *authtable; + char *tag; + char *hostname; + struct table *hostnametable; + struct table *sendertable; + uint16_t flags; + + uint32_t options; +} listen_opts; + +static void create_sock_listener(struct listen_opts *); +static void create_if_listener(struct listen_opts *); +static void config_listener(struct listener *, struct listen_opts *); +static int host_v4(struct listen_opts *); +static int host_v6(struct listen_opts *); +static int host_dns(struct listen_opts *); +static int interface(struct listen_opts *); + +int delaytonum(char *); +int is_if_in_group(const char *, const char *); + +static int config_lo_mask_source(struct listen_opts *); + +typedef struct { + union { + int64_t number; + struct table *table; + char *string; + struct host *host; + struct mailaddr *maddr; + } v; + int lineno; +} YYSTYPE; + +%} + +%token ACTION ALIAS ANY ARROW AUTH AUTH_OPTIONAL +%token BACKUP BOUNCE BYPASS +%token CA CERT CHAIN CHROOT CIPHERS COMMIT COMPRESSION CONNECT +%token DATA DATA_LINE DHE DISCONNECT DOMAIN +%token EHLO ENABLE ENCRYPTION ERROR EXPAND_ONLY +%token FCRDNS FILTER FOR FORWARD_ONLY FROM +%token GROUP +%token HELO HELO_SRC HOST HOSTNAME HOSTNAMES +%token INCLUDE INET4 INET6 +%token JUNK +%token KEY +%token LIMIT LISTEN LMTP LOCAL +%token MAIL_FROM MAILDIR MASK_SRC MASQUERADE MATCH MAX_MESSAGE_SIZE MAX_DEFERRED MBOX MDA MTA MX +%token NO_DSN NO_VERIFY NOOP +%token ON +%token PHASE PKI PORT PROC PROC_EXEC PROXY_V2 +%token QUEUE QUIT +%token RCPT_TO RDNS RECIPIENT RECEIVEDAUTH REGEX RELAY REJECT REPORT REWRITE RSET +%token SCHEDULER SENDER SENDERS SMTP SMTP_IN SMTP_OUT SMTPS SOCKET SRC SRS SUB_ADDR_DELIM +%token TABLE TAG TAGGED TLS TLS_REQUIRE TTL +%token USER USERBASE +%token VERIFY VIRTUAL +%token WARN_INTERVAL WRAPPER + +%token <v.string> STRING +%token <v.number> NUMBER +%type <v.table> table +%type <v.number> size negation +%type <v.table> tables tablenew tableref +%% + +grammar : /* empty */ + | grammar '\n' + | grammar include '\n' + | grammar varset '\n' + | grammar bounce '\n' + | grammar ca '\n' + | grammar mda '\n' + | grammar mta '\n' + | grammar pki '\n' + | grammar proc '\n' + | grammar queue '\n' + | grammar scheduler '\n' + | grammar smtp '\n' + | grammar srs '\n' + | grammar listen '\n' + | grammar table '\n' + | grammar dispatcher '\n' + | grammar match '\n' + | grammar filter '\n' + | grammar error '\n' { file->errors++; } + ; + +include : INCLUDE STRING { + struct file *nfile; + + if ((nfile = pushfile($2, 0)) == NULL) { + yyerror("failed to include file %s", $2); + free($2); + YYERROR; + } + free($2); + + file = nfile; + lungetc('\n'); + } + ; + +varset : STRING '=' STRING { + char *s = $1; + while (*s++) { + if (isspace((unsigned char)*s)) { + yyerror("macro name cannot contain " + "whitespace"); + free($1); + free($3); + YYERROR; + } + } + if (symset($1, $3, 0) == -1) + fatal("cannot store variable"); + free($1); + free($3); + } + ; + +comma : ',' + | nl + | /* empty */ + ; + +optnl : '\n' optnl + | + ; + +nl : '\n' optnl + ; + +negation : '!' { $$ = 1; } + | /* empty */ { $$ = 0; } + ; + +assign : '=' | ARROW; + + +keyval : STRING assign STRING { + table_add(table, $1, $3); + free($1); + free($3); + } + ; + +keyval_list : keyval + | keyval comma keyval_list + ; + +stringel : STRING { + table_add(table, $1, NULL); + free($1); + } + ; + +string_list : stringel + | stringel comma string_list + ; + +tableval_list : string_list { } + | keyval_list { } + ; + +bounce: +BOUNCE WARN_INTERVAL { + memset(conf->sc_bounce_warn, 0, sizeof conf->sc_bounce_warn); +} bouncedelays +; + + +ca: +CA STRING { + char buf[HOST_NAME_MAX+1]; + + /* if not catchall, check that it is a valid domain */ + if (strcmp($2, "*") != 0) { + if (!res_hnok($2)) { + yyerror("not a valid domain name: %s", $2); + free($2); + YYERROR; + } + } + xlowercase(buf, $2, sizeof(buf)); + free($2); + sca = dict_get(conf->sc_ca_dict, buf); + if (sca == NULL) { + sca = xcalloc(1, sizeof *sca); + (void)strlcpy(sca->ca_name, buf, sizeof(sca->ca_name)); + dict_set(conf->sc_ca_dict, sca->ca_name, sca); + } +} ca_params +; + + +ca_params_opt: +CERT STRING { + sca->ca_cert_file = $2; +} +; + +ca_params: +ca_params_opt +; + + +mda: +MDA LIMIT limits_mda +| MDA WRAPPER STRING STRING { + if (dict_get(conf->sc_mda_wrappers, $3)) { + yyerror("mda wrapper already declared with that name: %s", $3); + YYERROR; + } + dict_set(conf->sc_mda_wrappers, $3, $4); +} +; + + +mta: +MTA MAX_DEFERRED NUMBER { + conf->sc_mta_max_deferred = $3; +} +| MTA LIMIT FOR DOMAIN STRING { + struct mta_limits *d; + + limits = dict_get(conf->sc_limits_dict, $5); + if (limits == NULL) { + limits = xcalloc(1, sizeof(*limits)); + dict_xset(conf->sc_limits_dict, $5, limits); + d = dict_xget(conf->sc_limits_dict, "default"); + memmove(limits, d, sizeof(*limits)); + } + free($5); +} limits_mta +| MTA LIMIT { + limits = dict_get(conf->sc_limits_dict, "default"); +} limits_mta +; + + +pki: +PKI STRING { + char buf[HOST_NAME_MAX+1]; + + /* if not catchall, check that it is a valid domain */ + if (strcmp($2, "*") != 0) { + if (!res_hnok($2)) { + yyerror("not a valid domain name: %s", $2); + free($2); + YYERROR; + } + } + xlowercase(buf, $2, sizeof(buf)); + free($2); + pki = dict_get(conf->sc_pki_dict, buf); + if (pki == NULL) { + pki = xcalloc(1, sizeof *pki); + (void)strlcpy(pki->pki_name, buf, sizeof(pki->pki_name)); + dict_set(conf->sc_pki_dict, pki->pki_name, pki); + } +} pki_params +; + +pki_params_opt: +CERT STRING { + pki->pki_cert_file = $2; +} +| KEY STRING { + pki->pki_key_file = $2; +} +| DHE STRING { + if (strcasecmp($2, "none") == 0) + pki->pki_dhe = 0; + else if (strcasecmp($2, "auto") == 0) + pki->pki_dhe = 1; + else if (strcasecmp($2, "legacy") == 0) + pki->pki_dhe = 2; + else { + yyerror("invalid DHE keyword: %s", $2); + free($2); + YYERROR; + } + free($2); +} +; + + +pki_params: +pki_params_opt pki_params +| /* empty */ +; + + +proc: +PROC STRING STRING { + if (dict_get(conf->sc_filter_processes_dict, $2)) { + yyerror("processor already exists with that name: %s", $2); + free($2); + free($3); + YYERROR; + } + processor = xcalloc(1, sizeof *processor); + processor->command = $3; +} proc_params { + dict_set(conf->sc_filter_processes_dict, $2, processor); + processor = NULL; +} +; + + +proc_params_opt: +USER STRING { + if (processor->user) { + yyerror("user already specified for this processor"); + free($2); + YYERROR; + } + processor->user = $2; +} +| GROUP STRING { + if (processor->group) { + yyerror("group already specified for this processor"); + free($2); + YYERROR; + } + processor->group = $2; +} +| CHROOT STRING { + if (processor->chroot) { + yyerror("chroot already specified for this processor"); + free($2); + YYERROR; + } + processor->chroot = $2; +} +; + +proc_params: +proc_params_opt proc_params +| /* empty */ +; + + +queue: +QUEUE COMPRESSION { + conf->sc_queue_flags |= QUEUE_COMPRESSION; +} +| QUEUE ENCRYPTION { + conf->sc_queue_flags |= QUEUE_ENCRYPTION; +} +| QUEUE ENCRYPTION STRING { + if (strcasecmp($3, "stdin") == 0 || strcasecmp($3, "-") == 0) { + conf->sc_queue_key = "stdin"; + free($3); + } + else + conf->sc_queue_key = $3; + conf->sc_queue_flags |= QUEUE_ENCRYPTION; +} +| QUEUE TTL STRING { + conf->sc_ttl = delaytonum($3); + if (conf->sc_ttl == -1) { + yyerror("invalid ttl delay: %s", $3); + free($3); + YYERROR; + } + free($3); +} +; + + +scheduler: +SCHEDULER LIMIT limits_scheduler +; + + +smtp: +SMTP LIMIT limits_smtp +| SMTP CIPHERS STRING { + conf->sc_tls_ciphers = $3; +} +| SMTP MAX_MESSAGE_SIZE size { + conf->sc_maxsize = $3; +} +| SMTP SUB_ADDR_DELIM STRING { + if (strlen($3) != 1) { + yyerror("subaddressing-delimiter must be one character"); + free($3); + YYERROR; + } + if (isspace((unsigned char)*$3) || !isprint((unsigned char)*$3) || *$3 == '@') { + yyerror("sub-addr-delim uses invalid character"); + free($3); + YYERROR; + } + conf->sc_subaddressing_delim = $3; +} +; + +srs: +SRS KEY STRING { + conf->sc_srs_key = $3; +} +| SRS KEY BACKUP STRING { + conf->sc_srs_key_backup = $4; +} +| SRS TTL STRING { + conf->sc_srs_ttl = delaytonum($3); + if (conf->sc_srs_ttl == -1) { + yyerror("ttl delay \"%s\" is invalid", $3); + free($3); + YYERROR; + } + + conf->sc_srs_ttl /= 86400; + if (conf->sc_srs_ttl == 0) { + yyerror("ttl delay \"%s\" is too short", $3); + free($3); + YYERROR; + } + free($3); +} +; + + +dispatcher_local_option: +USER STRING { + if (dispatcher->u.local.is_mbox) { + yyerror("user may not be specified for this dispatcher"); + YYERROR; + } + + if (dispatcher->u.local.forward_only) { + yyerror("user may not be specified for forward-only"); + YYERROR; + } + + if (dispatcher->u.local.expand_only) { + yyerror("user may not be specified for expand-only"); + YYERROR; + } + + if (dispatcher->u.local.user) { + yyerror("user already specified for this dispatcher"); + YYERROR; + } + + dispatcher->u.local.user = $2; +} +| ALIAS tables { + struct table *t = $2; + + if (dispatcher->u.local.table_alias) { + yyerror("alias mapping already specified for this dispatcher"); + YYERROR; + } + + if (dispatcher->u.local.table_virtual) { + yyerror("virtual mapping already specified for this dispatcher"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_HASH, K_ALIAS)) { + yyerror("table \"%s\" may not be used for alias lookups", + t->t_name); + YYERROR; + } + + dispatcher->u.local.table_alias = strdup(t->t_name); +} +| VIRTUAL tables { + struct table *t = $2; + + if (dispatcher->u.local.table_virtual) { + yyerror("virtual mapping already specified for this dispatcher"); + YYERROR; + } + + if (dispatcher->u.local.table_alias) { + yyerror("alias mapping already specified for this dispatcher"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_HASH, K_ALIAS)) { + yyerror("table \"%s\" may not be used for virtual lookups", + t->t_name); + YYERROR; + } + + dispatcher->u.local.table_virtual = strdup(t->t_name); +} +| USERBASE tables { + struct table *t = $2; + + if (dispatcher->u.local.table_userbase) { + yyerror("userbase mapping already specified for this dispatcher"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_HASH, K_USERINFO)) { + yyerror("table \"%s\" may not be used for userbase lookups", + t->t_name); + YYERROR; + } + + dispatcher->u.local.table_userbase = strdup(t->t_name); +} +| WRAPPER STRING { + if (! dict_get(conf->sc_mda_wrappers, $2)) { + yyerror("no mda wrapper with that name: %s", $2); + YYERROR; + } + dispatcher->u.local.mda_wrapper = $2; +} +; + +dispatcher_local_options: +dispatcher_local_option dispatcher_local_options +| /* empty */ +; + +dispatcher_local: +MBOX { + dispatcher->u.local.is_mbox = 1; + asprintf(&dispatcher->u.local.command, PATH_LIBEXEC"/mail.local -f %%{mbox.from} -- %%{user.username}"); +} dispatcher_local_options +| MAILDIR { + asprintf(&dispatcher->u.local.command, PATH_LIBEXEC"/mail.maildir"); +} dispatcher_local_options +| MAILDIR JUNK { + asprintf(&dispatcher->u.local.command, PATH_LIBEXEC"/mail.maildir -j"); +} dispatcher_local_options +| MAILDIR STRING { + if (strncmp($2, "~/", 2) == 0) + asprintf(&dispatcher->u.local.command, + PATH_LIBEXEC"/mail.maildir \"%%{user.directory}/%s\"", $2+2); + else + asprintf(&dispatcher->u.local.command, + PATH_LIBEXEC"/mail.maildir \"%s\"", $2); +} dispatcher_local_options +| MAILDIR STRING JUNK { + if (strncmp($2, "~/", 2) == 0) + asprintf(&dispatcher->u.local.command, + PATH_LIBEXEC"/mail.maildir -j \"%%{user.directory}/%s\"", $2+2); + else + asprintf(&dispatcher->u.local.command, + PATH_LIBEXEC"/mail.maildir -j \"%s\"", $2); +} dispatcher_local_options +| LMTP STRING { + asprintf(&dispatcher->u.local.command, + PATH_LIBEXEC"/mail.lmtp -d \"%s\" -u", $2); +} dispatcher_local_options +| LMTP STRING RCPT_TO { + asprintf(&dispatcher->u.local.command, + PATH_LIBEXEC"/mail.lmtp -d \"%s\" -r", $2); +} dispatcher_local_options +| MDA STRING { + asprintf(&dispatcher->u.local.command, + PATH_LIBEXEC"/mail.mda \"%s\"", $2); +} dispatcher_local_options +| FORWARD_ONLY { + dispatcher->u.local.forward_only = 1; +} dispatcher_local_options +| EXPAND_ONLY { + dispatcher->u.local.expand_only = 1; +} dispatcher_local_options + +; + +dispatcher_remote_option: +HELO STRING { + if (dispatcher->u.remote.helo) { + yyerror("helo already specified for this dispatcher"); + YYERROR; + } + + dispatcher->u.remote.helo = $2; +} +| HELO_SRC tables { + struct table *t = $2; + + if (dispatcher->u.remote.helo_source) { + yyerror("helo-source mapping already specified for this dispatcher"); + YYERROR; + } + if (!table_check_use(t, T_DYNAMIC|T_HASH, K_ADDRNAME)) { + yyerror("table \"%s\" may not be used for helo-source lookups", + t->t_name); + YYERROR; + } + + dispatcher->u.remote.helo_source = strdup(t->t_name); +} +| PKI STRING { + if (dispatcher->u.remote.pki) { + yyerror("pki already specified for this dispatcher"); + YYERROR; + } + + dispatcher->u.remote.pki = $2; +} +| CA STRING { + if (dispatcher->u.remote.ca) { + yyerror("ca already specified for this dispatcher"); + YYERROR; + } + + dispatcher->u.remote.ca = $2; +} +| SRC tables { + struct table *t = $2; + + if (dispatcher->u.remote.source) { + yyerror("source mapping already specified for this dispatcher"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_SOURCE)) { + yyerror("table \"%s\" may not be used for source lookups", + t->t_name); + YYERROR; + } + + dispatcher->u.remote.source = strdup(t->t_name); +} +| MAIL_FROM STRING { + if (dispatcher->u.remote.mail_from) { + yyerror("mail-from already specified for this dispatcher"); + YYERROR; + } + + dispatcher->u.remote.mail_from = $2; +} +| BACKUP MX STRING { + if (dispatcher->u.remote.backup) { + yyerror("backup already specified for this dispatcher"); + YYERROR; + } + if (dispatcher->u.remote.smarthost) { + yyerror("backup and host are mutually exclusive"); + YYERROR; + } + + dispatcher->u.remote.backup = 1; + dispatcher->u.remote.backupmx = $3; +} +| BACKUP { + if (dispatcher->u.remote.backup) { + yyerror("backup already specified for this dispatcher"); + YYERROR; + } + if (dispatcher->u.remote.smarthost) { + yyerror("backup and host are mutually exclusive"); + YYERROR; + } + + dispatcher->u.remote.backup = 1; +} +| HOST tables { + struct table *t = $2; + + if (dispatcher->u.remote.smarthost) { + yyerror("host mapping already specified for this dispatcher"); + YYERROR; + } + if (dispatcher->u.remote.backup) { + yyerror("backup and host are mutually exclusive"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_RELAYHOST)) { + yyerror("table \"%s\" may not be used for host lookups", + t->t_name); + YYERROR; + } + + dispatcher->u.remote.smarthost = strdup(t->t_name); +} +| DOMAIN tables { + struct table *t = $2; + + if (dispatcher->u.remote.smarthost) { + yyerror("host mapping already specified for this dispatcher"); + YYERROR; + } + if (dispatcher->u.remote.backup) { + yyerror("backup and domain are mutually exclusive"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_HASH, K_RELAYHOST)) { + yyerror("table \"%s\" may not be used for host lookups", + t->t_name); + YYERROR; + } + + dispatcher->u.remote.smarthost = strdup(t->t_name); + dispatcher->u.remote.smarthost_domain = 1; +} +| TLS { + if (dispatcher->u.remote.tls_required == 1) { + yyerror("tls already specified for this dispatcher"); + YYERROR; + } + + dispatcher->u.remote.tls_required = 1; +} +| TLS NO_VERIFY { + if (dispatcher->u.remote.tls_required == 1) { + yyerror("tls already specified for this dispatcher"); + YYERROR; + } + + dispatcher->u.remote.tls_required = 1; + dispatcher->u.remote.tls_noverify = 1; +} +| AUTH tables { + struct table *t = $2; + + if (dispatcher->u.remote.smarthost == NULL) { + yyerror("auth may not be specified without host on a dispatcher"); + YYERROR; + } + + if (dispatcher->u.remote.auth) { + yyerror("auth mapping already specified for this dispatcher"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_HASH, K_CREDENTIALS)) { + yyerror("table \"%s\" may not be used for auth lookups", + t->t_name); + YYERROR; + } + + dispatcher->u.remote.auth = strdup(t->t_name); +} +| FILTER STRING { + struct filter_config *fc; + + if (dispatcher->u.remote.filtername) { + yyerror("filter already specified for this dispatcher"); + YYERROR; + } + + if ((fc = dict_get(conf->sc_filters_dict, $2)) == NULL) { + yyerror("no filter exist with that name: %s", $2); + free($2); + YYERROR; + } + fc->filter_subsystem |= FILTER_SUBSYSTEM_SMTP_OUT; + dispatcher->u.remote.filtername = $2; +} +| FILTER { + char buffer[128]; + char *filtername; + + if (dispatcher->u.remote.filtername) { + yyerror("filter already specified for this dispatcher"); + YYERROR; + } + + do { + (void)snprintf(buffer, sizeof buffer, "<dynchain:%08x>", last_dynchain_id++); + } while (dict_check(conf->sc_filters_dict, buffer)); + + filtername = xstrdup(buffer); + filter_config = xcalloc(1, sizeof *filter_config); + filter_config->filter_type = FILTER_TYPE_CHAIN; + filter_config->filter_subsystem |= FILTER_SUBSYSTEM_SMTP_OUT; + dict_init(&filter_config->chain_procs); + dispatcher->u.remote.filtername = filtername; +} '{' filter_list '}' { + dict_set(conf->sc_filters_dict, dispatcher->u.remote.filtername, filter_config); + filter_config = NULL; +} +| SRS { + if (conf->sc_srs_key == NULL) { + yyerror("an srs key is required for srs to be specified in an action"); + YYERROR; + } + if (dispatcher->u.remote.srs == 1) { + yyerror("srs already specified for this dispatcher"); + YYERROR; + } + + dispatcher->u.remote.srs = 1; +} +; + +dispatcher_remote_options: +dispatcher_remote_option dispatcher_remote_options +| /* empty */ +; + +dispatcher_remote : +RELAY dispatcher_remote_options +; + +dispatcher_type: +dispatcher_local { + dispatcher->type = DISPATCHER_LOCAL; +} +| dispatcher_remote { + dispatcher->type = DISPATCHER_REMOTE; +} +; + +dispatcher_option: +TTL STRING { + if (dispatcher->ttl) { + yyerror("ttl already specified for this dispatcher"); + YYERROR; + } + + dispatcher->ttl = delaytonum($2); + if (dispatcher->ttl == -1) { + yyerror("ttl delay \"%s\" is invalid", $2); + free($2); + YYERROR; + } + free($2); +} +; + +dispatcher_options: +dispatcher_option dispatcher_options +| /* empty */ +; + +dispatcher: +ACTION STRING { + if (dict_get(conf->sc_dispatchers, $2)) { + yyerror("dispatcher already declared with that name: %s", $2); + YYERROR; + } + dispatcher = xcalloc(1, sizeof *dispatcher); +} dispatcher_type dispatcher_options { + if (dispatcher->type == DISPATCHER_LOCAL) + if (dispatcher->u.local.table_userbase == NULL) + dispatcher->u.local.table_userbase = "<getpwnam>"; + dict_set(conf->sc_dispatchers, $2, dispatcher); + dispatcher = NULL; +} +; + +match_option: +negation TAG tables { + struct table *t = $3; + + if (rule->flag_tag) { + yyerror("tag already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_STRING)) { + yyerror("table \"%s\" may not be used for tag lookups", + t->t_name); + YYERROR; + } + + rule->flag_tag = $1 ? -1 : 1; + rule->table_tag = strdup(t->t_name); +} +| +negation TAG REGEX tables { + struct table *t = $4; + + if (rule->flag_tag) { + yyerror("tag already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { + yyerror("table \"%s\" may not be used for tag lookups", + t->t_name); + YYERROR; + } + + rule->flag_tag = $1 ? -1 : 1; + rule->flag_tag_regex = 1; + rule->table_tag = strdup(t->t_name); +} + +| negation HELO tables { + struct table *t = $3; + + if (rule->flag_smtp_helo) { + yyerror("helo already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_DOMAIN)) { + yyerror("table \"%s\" may not be used for helo lookups", + t->t_name); + YYERROR; + } + + rule->flag_smtp_helo = $1 ? -1 : 1; + rule->table_smtp_helo = strdup(t->t_name); +} +| negation HELO REGEX tables { + struct table *t = $4; + + if (rule->flag_smtp_helo) { + yyerror("helo already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { + yyerror("table \"%s\" may not be used for helo lookups", + t->t_name); + YYERROR; + } + + rule->flag_smtp_helo = $1 ? -1 : 1; + rule->flag_smtp_helo_regex = 1; + rule->table_smtp_helo = strdup(t->t_name); +} +| negation TLS { + if (rule->flag_smtp_starttls) { + yyerror("tls already specified for this rule"); + YYERROR; + } + rule->flag_smtp_starttls = $1 ? -1 : 1; +} +| negation AUTH { + if (rule->flag_smtp_auth) { + yyerror("auth already specified for this rule"); + YYERROR; + } + rule->flag_smtp_auth = $1 ? -1 : 1; +} +| negation AUTH tables { + struct table *t = $3; + + if (rule->flag_smtp_auth) { + yyerror("auth already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_STRING|K_CREDENTIALS)) { + yyerror("table \"%s\" may not be used for auth lookups", + t->t_name); + YYERROR; + } + + rule->flag_smtp_auth = $1 ? -1 : 1; + rule->table_smtp_auth = strdup(t->t_name); +} +| negation AUTH REGEX tables { + struct table *t = $4; + + if (rule->flag_smtp_auth) { + yyerror("auth already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { + yyerror("table \"%s\" may not be used for auth lookups", + t->t_name); + YYERROR; + } + + rule->flag_smtp_auth = $1 ? -1 : 1; + rule->flag_smtp_auth_regex = 1; + rule->table_smtp_auth = strdup(t->t_name); +} +| negation MAIL_FROM tables { + struct table *t = $3; + + if (rule->flag_smtp_mail_from) { + yyerror("mail-from already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_MAILADDR)) { + yyerror("table \"%s\" may not be used for mail-from lookups", + t->t_name); + YYERROR; + } + + rule->flag_smtp_mail_from = $1 ? -1 : 1; + rule->table_smtp_mail_from = strdup(t->t_name); +} +| negation MAIL_FROM REGEX tables { + struct table *t = $4; + + if (rule->flag_smtp_mail_from) { + yyerror("mail-from already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { + yyerror("table \"%s\" may not be used for mail-from lookups", + t->t_name); + YYERROR; + } + + rule->flag_smtp_mail_from = $1 ? -1 : 1; + rule->flag_smtp_mail_from_regex = 1; + rule->table_smtp_mail_from = strdup(t->t_name); +} +| negation RCPT_TO tables { + struct table *t = $3; + + if (rule->flag_smtp_rcpt_to) { + yyerror("rcpt-to already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_MAILADDR)) { + yyerror("table \"%s\" may not be used for rcpt-to lookups", + t->t_name); + YYERROR; + } + + rule->flag_smtp_rcpt_to = $1 ? -1 : 1; + rule->table_smtp_rcpt_to = strdup(t->t_name); +} +| negation RCPT_TO REGEX tables { + struct table *t = $4; + + if (rule->flag_smtp_rcpt_to) { + yyerror("rcpt-to already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { + yyerror("table \"%s\" may not be used for rcpt-to lookups", + t->t_name); + YYERROR; + } + + rule->flag_smtp_rcpt_to = $1 ? -1 : 1; + rule->flag_smtp_rcpt_to_regex = 1; + rule->table_smtp_rcpt_to = strdup(t->t_name); +} + +| negation FROM SOCKET { + if (rule->flag_from) { + yyerror("from already specified for this rule"); + YYERROR; + } + rule->flag_from = $1 ? -1 : 1; + rule->flag_from_socket = 1; +} +| negation FROM LOCAL { + struct table *t = table_find(conf, "<localhost>"); + + if (rule->flag_from) { + yyerror("from already specified for this rule"); + YYERROR; + } + rule->flag_from = $1 ? -1 : 1; + rule->table_from = strdup(t->t_name); +} +| negation FROM ANY { + struct table *t = table_find(conf, "<anyhost>"); + + if (rule->flag_from) { + yyerror("from already specified for this rule"); + YYERROR; + } + rule->flag_from = $1 ? -1 : 1; + rule->table_from = strdup(t->t_name); +} +| negation FROM SRC tables { + struct table *t = $4; + + if (rule->flag_from) { + yyerror("from already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_NETADDR)) { + yyerror("table \"%s\" may not be used for from lookups", + t->t_name); + YYERROR; + } + + rule->flag_from = $1 ? -1 : 1; + rule->table_from = strdup(t->t_name); +} +| negation FROM SRC REGEX tables { + struct table *t = $5; + + if (rule->flag_from) { + yyerror("from already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { + yyerror("table \"%s\" may not be used for from lookups", + t->t_name); + YYERROR; + } + + rule->flag_from = $1 ? -1 : 1; + rule->flag_from_regex = 1; + rule->table_from = strdup(t->t_name); +} +| negation FROM RDNS { + if (rule->flag_from) { + yyerror("from already specified for this rule"); + YYERROR; + } + rule->flag_from = $1 ? -1 : 1; + rule->flag_from_rdns = 1; +} +| negation FROM RDNS tables { + struct table *t = $4; + + if (rule->flag_from) { + yyerror("from already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_DOMAIN)) { + yyerror("table \"%s\" may not be used for rdns lookups", + t->t_name); + YYERROR; + } + + rule->flag_from = $1 ? -1 : 1; + rule->flag_from_rdns = 1; + rule->table_from = strdup(t->t_name); +} +| negation FROM RDNS REGEX tables { + struct table *t = $5; + + if (rule->flag_from) { + yyerror("from already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_DOMAIN)) { + yyerror("table \"%s\" may not be used for rdns lookups", + t->t_name); + YYERROR; + } + + rule->flag_from = $1 ? -1 : 1; + rule->flag_from_regex = 1; + rule->flag_from_rdns = 1; + rule->table_from = strdup(t->t_name); +} + +| negation FROM AUTH { + struct table *anyhost = table_find(conf, "<anyhost>"); + + if (rule->flag_from) { + yyerror("from already specified for this rule"); + YYERROR; + } + + rule->flag_from = 1; + rule->table_from = strdup(anyhost->t_name); + rule->flag_smtp_auth = $1 ? -1 : 1; +} +| negation FROM AUTH tables { + struct table *anyhost = table_find(conf, "<anyhost>"); + struct table *t = $4; + + if (rule->flag_from) { + yyerror("from already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_STRING|K_CREDENTIALS)) { + yyerror("table \"%s\" may not be used for from lookups", + t->t_name); + YYERROR; + } + + rule->flag_from = 1; + rule->table_from = strdup(anyhost->t_name); + rule->flag_smtp_auth = $1 ? -1 : 1; + rule->table_smtp_auth = strdup(t->t_name); +} +| negation FROM AUTH REGEX tables { + struct table *anyhost = table_find(conf, "<anyhost>"); + struct table *t = $5; + + if (rule->flag_from) { + yyerror("from already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { + yyerror("table \"%s\" may not be used for from lookups", + t->t_name); + YYERROR; + } + + rule->flag_from = 1; + rule->table_from = strdup(anyhost->t_name); + rule->flag_smtp_auth = $1 ? -1 : 1; + rule->flag_smtp_auth_regex = 1; + rule->table_smtp_auth = strdup(t->t_name); +} + +| negation FROM MAIL_FROM tables { + struct table *anyhost = table_find(conf, "<anyhost>"); + struct table *t = $4; + + if (rule->flag_from) { + yyerror("from already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_MAILADDR)) { + yyerror("table \"%s\" may not be used for from lookups", + t->t_name); + YYERROR; + } + + rule->flag_from = 1; + rule->table_from = strdup(anyhost->t_name); + rule->flag_smtp_mail_from = $1 ? -1 : 1; + rule->table_smtp_mail_from = strdup(t->t_name); +} +| negation FROM MAIL_FROM REGEX tables { + struct table *anyhost = table_find(conf, "<anyhost>"); + struct table *t = $5; + + if (rule->flag_from) { + yyerror("from already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { + yyerror("table \"%s\" may not be used for from lookups", + t->t_name); + YYERROR; + } + + rule->flag_from = 1; + rule->table_from = strdup(anyhost->t_name); + rule->flag_smtp_mail_from = $1 ? -1 : 1; + rule->flag_smtp_mail_from_regex = 1; + rule->table_smtp_mail_from = strdup(t->t_name); +} + +| negation FOR LOCAL { + struct table *t = table_find(conf, "<localnames>"); + + if (rule->flag_for) { + yyerror("for already specified for this rule"); + YYERROR; + } + rule->flag_for = $1 ? -1 : 1; + rule->table_for = strdup(t->t_name); +} +| negation FOR ANY { + struct table *t = table_find(conf, "<anydestination>"); + + if (rule->flag_for) { + yyerror("for already specified for this rule"); + YYERROR; + } + rule->flag_for = $1 ? -1 : 1; + rule->table_for = strdup(t->t_name); +} +| negation FOR DOMAIN tables { + struct table *t = $4; + + if (rule->flag_for) { + yyerror("for already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_DOMAIN)) { + yyerror("table \"%s\" may not be used for 'for' lookups", + t->t_name); + YYERROR; + } + + rule->flag_for = $1 ? -1 : 1; + rule->table_for = strdup(t->t_name); +} +| negation FOR DOMAIN REGEX tables { + struct table *t = $5; + + if (rule->flag_for) { + yyerror("for already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { + yyerror("table \"%s\" may not be used for 'for' lookups", + t->t_name); + YYERROR; + } + + rule->flag_for = $1 ? -1 : 1; + rule->flag_for_regex = 1; + rule->table_for = strdup(t->t_name); +} +| negation FOR RCPT_TO tables { + struct table *anyhost = table_find(conf, "<anydestination>"); + struct table *t = $4; + + if (rule->flag_for) { + yyerror("for already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_MAILADDR)) { + yyerror("table \"%s\" may not be used for for lookups", + t->t_name); + YYERROR; + } + + rule->flag_for = 1; + rule->table_for = strdup(anyhost->t_name); + rule->flag_smtp_rcpt_to = $1 ? -1 : 1; + rule->table_smtp_rcpt_to = strdup(t->t_name); +} +| negation FOR RCPT_TO REGEX tables { + struct table *anyhost = table_find(conf, "<anydestination>"); + struct table *t = $5; + + if (rule->flag_for) { + yyerror("for already specified for this rule"); + YYERROR; + } + + if (!table_check_use(t, T_DYNAMIC|T_LIST, K_REGEX)) { + yyerror("table \"%s\" may not be used for for lookups", + t->t_name); + YYERROR; + } + + rule->flag_for = 1; + rule->table_for = strdup(anyhost->t_name); + rule->flag_smtp_rcpt_to = $1 ? -1 : 1; + rule->flag_smtp_rcpt_to_regex = 1; + rule->table_smtp_rcpt_to = strdup(t->t_name); +} +; + +match_options: +match_option match_options +| /* empty */ +; + +match_dispatcher: +STRING { + if (dict_get(conf->sc_dispatchers, $1) == NULL) { + yyerror("no such dispatcher: %s", $1); + YYERROR; + } + rule->dispatcher = $1; +} +; + +action: +REJECT { + rule->reject = 1; +} +| ACTION match_dispatcher +; + +match: +MATCH { + rule = xcalloc(1, sizeof *rule); +} match_options action { + if (!rule->flag_from) { + rule->table_from = strdup("<localhost>"); + rule->flag_from = 1; + } + if (!rule->flag_for) { + rule->table_for = strdup("<localnames>"); + rule->flag_for = 1; + } + TAILQ_INSERT_TAIL(conf->sc_rules, rule, r_entry); + rule = NULL; +} +; + +filter_action_builtin: +filter_action_builtin_nojunk +| JUNK { + filter_config->junk = 1; +} +| BYPASS { + filter_config->bypass = 1; +} +; + +filter_action_builtin_nojunk: +REJECT STRING { + filter_config->reject = $2; +} +| DISCONNECT STRING { + filter_config->disconnect = $2; +} +| REWRITE STRING { + filter_config->rewrite = $2; +} +| REPORT STRING { + filter_config->report = $2; +} +; + +filter_phase_check_fcrdns: +negation FCRDNS { + filter_config->not_fcrdns = $1 ? -1 : 1; + filter_config->fcrdns = 1; +} +; + +filter_phase_check_rdns: +negation RDNS { + filter_config->not_rdns = $1 ? -1 : 1; + filter_config->rdns = 1; +} +; + +filter_phase_check_rdns_table: +negation RDNS tables { + filter_config->not_rdns_table = $1 ? -1 : 1; + filter_config->rdns_table = $3; +} +; +filter_phase_check_rdns_regex: +negation RDNS REGEX tables { + filter_config->not_rdns_regex = $1 ? -1 : 1; + filter_config->rdns_regex = $4; +} +; + +filter_phase_check_src_table: +negation SRC tables { + filter_config->not_src_table = $1 ? -1 : 1; + filter_config->src_table = $3; +} +; +filter_phase_check_src_regex: +negation SRC REGEX tables { + filter_config->not_src_regex = $1 ? -1 : 1; + filter_config->src_regex = $4; +} +; + +filter_phase_check_helo_table: +negation HELO tables { + filter_config->not_helo_table = $1 ? -1 : 1; + filter_config->helo_table = $3; +} +; +filter_phase_check_helo_regex: +negation HELO REGEX tables { + filter_config->not_helo_regex = $1 ? -1 : 1; + filter_config->helo_regex = $4; +} +; + +filter_phase_check_auth: +negation AUTH { + filter_config->not_auth = $1 ? -1 : 1; + filter_config->auth = 1; +} +; +filter_phase_check_auth_table: +negation AUTH tables { + filter_config->not_auth_table = $1 ? -1 : 1; + filter_config->auth_table = $3; +} +; +filter_phase_check_auth_regex: +negation AUTH REGEX tables { + filter_config->not_auth_regex = $1 ? -1 : 1; + filter_config->auth_regex = $4; +} +; + +filter_phase_check_mail_from_table: +negation MAIL_FROM tables { + filter_config->not_mail_from_table = $1 ? -1 : 1; + filter_config->mail_from_table = $3; +} +; +filter_phase_check_mail_from_regex: +negation MAIL_FROM REGEX tables { + filter_config->not_mail_from_regex = $1 ? -1 : 1; + filter_config->mail_from_regex = $4; +} +; + +filter_phase_check_rcpt_to_table: +negation RCPT_TO tables { + filter_config->not_rcpt_to_table = $1 ? -1 : 1; + filter_config->rcpt_to_table = $3; +} +; +filter_phase_check_rcpt_to_regex: +negation RCPT_TO REGEX tables { + filter_config->not_rcpt_to_regex = $1 ? -1 : 1; + filter_config->rcpt_to_regex = $4; +} +; + +filter_phase_global_options: +filter_phase_check_fcrdns | +filter_phase_check_rdns | +filter_phase_check_rdns_regex | +filter_phase_check_rdns_table | +filter_phase_check_src_regex | +filter_phase_check_src_table; + +filter_phase_connect_options: +filter_phase_global_options; + +filter_phase_helo_options: +filter_phase_check_helo_table | +filter_phase_check_helo_regex | +filter_phase_global_options; + +filter_phase_auth_options: +filter_phase_check_helo_table | +filter_phase_check_helo_regex | +filter_phase_check_auth | +filter_phase_check_auth_table | +filter_phase_check_auth_regex | +filter_phase_global_options; + +filter_phase_mail_from_options: +filter_phase_check_helo_table | +filter_phase_check_helo_regex | +filter_phase_check_auth | +filter_phase_check_auth_table | +filter_phase_check_auth_regex | +filter_phase_check_mail_from_table | +filter_phase_check_mail_from_regex | +filter_phase_global_options; + +filter_phase_rcpt_to_options: +filter_phase_check_helo_table | +filter_phase_check_helo_regex | +filter_phase_check_auth | +filter_phase_check_auth_table | +filter_phase_check_auth_regex | +filter_phase_check_mail_from_table | +filter_phase_check_mail_from_regex | +filter_phase_check_rcpt_to_table | +filter_phase_check_rcpt_to_regex | +filter_phase_global_options; + +filter_phase_data_options: +filter_phase_check_helo_table | +filter_phase_check_helo_regex | +filter_phase_check_auth | +filter_phase_check_auth_table | +filter_phase_check_auth_regex | +filter_phase_check_mail_from_table | +filter_phase_check_mail_from_regex | +filter_phase_global_options; + +/* +filter_phase_quit_options: +filter_phase_check_helo_table | +filter_phase_check_helo_regex | +filter_phase_global_options; + +filter_phase_rset_options: +filter_phase_check_helo_table | +filter_phase_check_helo_regex | +filter_phase_global_options; + +filter_phase_noop_options: +filter_phase_check_helo_table | +filter_phase_check_helo_regex | +filter_phase_global_options; +*/ + +filter_phase_commit_options: +filter_phase_check_helo_table | +filter_phase_check_helo_regex | +filter_phase_check_auth | +filter_phase_check_auth_table | +filter_phase_check_auth_regex | +filter_phase_check_mail_from_table | +filter_phase_check_mail_from_regex | +filter_phase_global_options; + + +filter_phase_connect: +CONNECT { + filter_config->phase = FILTER_CONNECT; +} MATCH filter_phase_connect_options filter_action_builtin +; + + +filter_phase_helo: +HELO { + filter_config->phase = FILTER_HELO; +} MATCH filter_phase_helo_options filter_action_builtin +; + +filter_phase_ehlo: +EHLO { + filter_config->phase = FILTER_EHLO; +} MATCH filter_phase_helo_options filter_action_builtin +; + +filter_phase_auth: +AUTH { +} MATCH filter_phase_auth_options filter_action_builtin +; + +filter_phase_mail_from: +MAIL_FROM { + filter_config->phase = FILTER_MAIL_FROM; +} MATCH filter_phase_mail_from_options filter_action_builtin +; + +filter_phase_rcpt_to: +RCPT_TO { + filter_config->phase = FILTER_RCPT_TO; +} MATCH filter_phase_rcpt_to_options filter_action_builtin +; + +filter_phase_data: +DATA { + filter_config->phase = FILTER_DATA; +} MATCH filter_phase_data_options filter_action_builtin +; + +/* +filter_phase_data_line: +DATA_LINE { + filter_config->phase = FILTER_DATA_LINE; +} MATCH filter_action_builtin +; + +filter_phase_quit: +QUIT { + filter_config->phase = FILTER_QUIT; +} filter_phase_quit_options filter_action_builtin +; + +filter_phase_rset: +RSET { + filter_config->phase = FILTER_RSET; +} MATCH filter_phase_rset_options filter_action_builtin +; + +filter_phase_noop: +NOOP { + filter_config->phase = FILTER_NOOP; +} MATCH filter_phase_noop_options filter_action_builtin +; +*/ + +filter_phase_commit: +COMMIT { + filter_config->phase = FILTER_COMMIT; +} MATCH filter_phase_commit_options filter_action_builtin_nojunk +; + + + +filter_phase: +filter_phase_connect +| filter_phase_helo +| filter_phase_ehlo +| filter_phase_auth +| filter_phase_mail_from +| filter_phase_rcpt_to +| filter_phase_data +/*| filter_phase_data_line*/ +/*| filter_phase_quit*/ +/*| filter_phase_noop*/ +/*| filter_phase_rset*/ +| filter_phase_commit +; + + +filterel: +STRING { + struct filter_config *fr; + struct filter_proc *fp; + size_t i; + + if ((fr = dict_get(conf->sc_filters_dict, $1)) == NULL) { + yyerror("no filter exist with that name: %s", $1); + free($1); + YYERROR; + } + if (fr->filter_type == FILTER_TYPE_CHAIN) { + yyerror("no filter chain allowed within a filter chain: %s", $1); + free($1); + YYERROR; + } + + for (i = 0; i < filter_config->chain_size; i++) { + if (strcmp(filter_config->chain[i], $1) == 0) { + yyerror("no filter allowed twice within a filter chain: %s", $1); + free($1); + YYERROR; + } + } + + if (fr->proc) { + if ((fp = dict_get(&filter_config->chain_procs, fr->proc))) { + yyerror("no proc allowed twice within a filter chain: %s", fr->proc); + free($1); + YYERROR; + } + dict_set(&filter_config->chain_procs, fr->proc, NULL); + } + + fr->filter_subsystem |= filter_config->filter_subsystem; + filter_config->chain_size += 1; + filter_config->chain = reallocarray(filter_config->chain, filter_config->chain_size, sizeof(char *)); + if (filter_config->chain == NULL) + err(1, NULL); + filter_config->chain[filter_config->chain_size - 1] = $1; +} +; + +filter_list: +filterel +| filterel comma filter_list +; + +filter: +FILTER STRING PROC STRING { + struct filter_proc *fp; + + if (dict_get(conf->sc_filters_dict, $2)) { + yyerror("filter already exists with that name: %s", $2); + free($2); + free($4); + YYERROR; + } + if ((fp = dict_get(conf->sc_filter_processes_dict, $4)) == NULL) { + yyerror("no processor exist with that name: %s", $4); + free($4); + YYERROR; + } + + filter_config = xcalloc(1, sizeof *filter_config); + filter_config->filter_type = FILTER_TYPE_PROC; + filter_config->name = $2; + filter_config->proc = $4; + dict_set(conf->sc_filters_dict, $2, filter_config); + filter_config = NULL; +} +| +FILTER STRING PROC_EXEC STRING { + if (dict_get(conf->sc_filters_dict, $2)) { + yyerror("filter already exists with that name: %s", $2); + free($2); + free($4); + YYERROR; + } + + processor = xcalloc(1, sizeof *processor); + processor->command = $4; + + filter_config = xcalloc(1, sizeof *filter_config); + filter_config->filter_type = FILTER_TYPE_PROC; + filter_config->name = $2; + filter_config->proc = xstrdup($2); + dict_set(conf->sc_filters_dict, $2, filter_config); +} proc_params { + dict_set(conf->sc_filter_processes_dict, filter_config->proc, processor); + processor = NULL; + filter_config = NULL; +} +| +FILTER STRING PHASE { + if (dict_get(conf->sc_filters_dict, $2)) { + yyerror("filter already exists with that name: %s", $2); + free($2); + YYERROR; + } + filter_config = xcalloc(1, sizeof *filter_config); + filter_config->name = $2; + filter_config->filter_type = FILTER_TYPE_BUILTIN; + dict_set(conf->sc_filters_dict, $2, filter_config); +} filter_phase { + filter_config = NULL; +} +| +FILTER STRING CHAIN { + if (dict_get(conf->sc_filters_dict, $2)) { + yyerror("filter already exists with that name: %s", $2); + free($2); + YYERROR; + } + filter_config = xcalloc(1, sizeof *filter_config); + filter_config->filter_type = FILTER_TYPE_CHAIN; + dict_init(&filter_config->chain_procs); +} '{' filter_list '}' { + dict_set(conf->sc_filters_dict, $2, filter_config); + filter_config = NULL; +} +; + +size : NUMBER { + if ($1 < 0) { + yyerror("invalid size: %" PRId64, $1); + YYERROR; + } + $$ = $1; + } + | STRING { + long long result; + + if (scan_scaled($1, &result) == -1 || result < 0) { + yyerror("invalid size: %s", $1); + free($1); + YYERROR; + } + free($1); + $$ = result; + } + ; + +bouncedelay : STRING { + time_t d; + int i; + + d = delaytonum($1); + if (d < 0) { + yyerror("invalid bounce delay: %s", $1); + free($1); + YYERROR; + } + free($1); + for (i = 0; i < MAX_BOUNCE_WARN; i++) { + if (conf->sc_bounce_warn[i] != 0) + continue; + conf->sc_bounce_warn[i] = d; + break; + } + } + ; + +bouncedelays : bouncedelays ',' bouncedelay + | bouncedelay + ; + +opt_limit_mda : STRING NUMBER { + if (!strcmp($1, "max-session")) { + conf->sc_mda_max_session = $2; + } + else if (!strcmp($1, "max-session-per-user")) { + conf->sc_mda_max_user_session = $2; + } + else if (!strcmp($1, "task-lowat")) { + conf->sc_mda_task_lowat = $2; + } + else if (!strcmp($1, "task-hiwat")) { + conf->sc_mda_task_hiwat = $2; + } + else if (!strcmp($1, "task-release")) { + conf->sc_mda_task_release = $2; + } + else { + yyerror("invalid scheduler limit keyword: %s", $1); + free($1); + YYERROR; + } + free($1); + } + ; + +limits_smtp : opt_limit_smtp limits_smtp + | /* empty */ + ; + +opt_limit_smtp : STRING NUMBER { + if (!strcmp($1, "max-rcpt")) { + conf->sc_session_max_rcpt = $2; + } + else if (!strcmp($1, "max-mails")) { + conf->sc_session_max_mails = $2; + } + else { + yyerror("invalid session limit keyword: %s", $1); + free($1); + YYERROR; + } + free($1); + } + ; + +limits_mda : opt_limit_mda limits_mda + | /* empty */ + ; + +opt_limit_mta : INET4 { + limits->family = AF_INET; + } + | INET6 { + limits->family = AF_INET6; + } + | STRING NUMBER { + if (!limit_mta_set(limits, $1, $2)) { + yyerror("invalid mta limit keyword: %s", $1); + free($1); + YYERROR; + } + free($1); + } + ; + +limits_mta : opt_limit_mta limits_mta + | /* empty */ + ; + +opt_limit_scheduler : STRING NUMBER { + if (!strcmp($1, "max-inflight")) { + conf->sc_scheduler_max_inflight = $2; + } + else if (!strcmp($1, "max-evp-batch-size")) { + conf->sc_scheduler_max_evp_batch_size = $2; + } + else if (!strcmp($1, "max-msg-batch-size")) { + conf->sc_scheduler_max_msg_batch_size = $2; + } + else if (!strcmp($1, "max-schedule")) { + conf->sc_scheduler_max_schedule = $2; + } + else { + yyerror("invalid scheduler limit keyword: %s", $1); + free($1); + YYERROR; + } + free($1); + } + ; + +limits_scheduler: opt_limit_scheduler limits_scheduler + | /* empty */ + ; + + +opt_sock_listen : FILTER STRING { + struct filter_config *fc; + + if (listen_opts.options & LO_FILTER) { + yyerror("filter already specified"); + free($2); + YYERROR; + } + if ((fc = dict_get(conf->sc_filters_dict, $2)) == NULL) { + yyerror("no filter exist with that name: %s", $2); + free($2); + YYERROR; + } + fc->filter_subsystem |= FILTER_SUBSYSTEM_SMTP_IN; + listen_opts.options |= LO_FILTER; + listen_opts.filtername = $2; + } + | FILTER { + char buffer[128]; + + if (listen_opts.options & LO_FILTER) { + yyerror("filter already specified"); + YYERROR; + } + + do { + (void)snprintf(buffer, sizeof buffer, "<dynchain:%08x>", last_dynchain_id++); + } while (dict_check(conf->sc_filters_dict, buffer)); + + listen_opts.options |= LO_FILTER; + listen_opts.filtername = xstrdup(buffer); + filter_config = xcalloc(1, sizeof *filter_config); + filter_config->filter_type = FILTER_TYPE_CHAIN; + filter_config->filter_subsystem |= FILTER_SUBSYSTEM_SMTP_IN; + dict_init(&filter_config->chain_procs); + } '{' filter_list '}' { + dict_set(conf->sc_filters_dict, listen_opts.filtername, filter_config); + filter_config = NULL; + } + | MASK_SRC { + if (config_lo_mask_source(&listen_opts)) { + YYERROR; + } + } + | TAG STRING { + if (listen_opts.options & LO_TAG) { + yyerror("tag already specified"); + YYERROR; + } + listen_opts.options |= LO_TAG; + + if (strlen($2) >= SMTPD_TAG_SIZE) { + yyerror("tag name too long"); + free($2); + YYERROR; + } + listen_opts.tag = $2; + } + ; + +opt_if_listen : INET4 { + if (listen_opts.options & LO_FAMILY) { + yyerror("address family already specified"); + YYERROR; + } + listen_opts.options |= LO_FAMILY; + listen_opts.family = AF_INET; + } + | INET6 { + if (listen_opts.options & LO_FAMILY) { + yyerror("address family already specified"); + YYERROR; + } + listen_opts.options |= LO_FAMILY; + listen_opts.family = AF_INET6; + } + | PORT STRING { + struct servent *servent; + + if (listen_opts.options & LO_PORT) { + yyerror("port already specified"); + YYERROR; + } + listen_opts.options |= LO_PORT; + + servent = getservbyname($2, "tcp"); + if (servent == NULL) { + yyerror("invalid port: %s", $2); + free($2); + YYERROR; + } + free($2); + listen_opts.port = ntohs(servent->s_port); + } + | PORT SMTP { + struct servent *servent; + + if (listen_opts.options & LO_PORT) { + yyerror("port already specified"); + YYERROR; + } + listen_opts.options |= LO_PORT; + + servent = getservbyname("smtp", "tcp"); + if (servent == NULL) { + yyerror("invalid port: smtp"); + YYERROR; + } + listen_opts.port = ntohs(servent->s_port); + } + | PORT SMTPS { + struct servent *servent; + + if (listen_opts.options & LO_PORT) { + yyerror("port already specified"); + YYERROR; + } + listen_opts.options |= LO_PORT; + + servent = getservbyname("smtps", "tcp"); + if (servent == NULL) { + yyerror("invalid port: smtps"); + YYERROR; + } + listen_opts.port = ntohs(servent->s_port); + } + | PORT NUMBER { + if (listen_opts.options & LO_PORT) { + yyerror("port already specified"); + YYERROR; + } + listen_opts.options |= LO_PORT; + + if ($2 <= 0 || $2 > (int)USHRT_MAX) { + yyerror("invalid port: %" PRId64, $2); + YYERROR; + } + listen_opts.port = $2; + } + | FILTER STRING { + struct filter_config *fc; + + if (listen_opts.options & LO_FILTER) { + yyerror("filter already specified"); + YYERROR; + } + if ((fc = dict_get(conf->sc_filters_dict, $2)) == NULL) { + yyerror("no filter exist with that name: %s", $2); + free($2); + YYERROR; + } + fc->filter_subsystem |= FILTER_SUBSYSTEM_SMTP_IN; + listen_opts.options |= LO_FILTER; + listen_opts.filtername = $2; + } + | FILTER { + char buffer[128]; + + if (listen_opts.options & LO_FILTER) { + yyerror("filter already specified"); + YYERROR; + } + + do { + (void)snprintf(buffer, sizeof buffer, "<dynchain:%08x>", last_dynchain_id++); + } while (dict_check(conf->sc_filters_dict, buffer)); + + listen_opts.options |= LO_FILTER; + listen_opts.filtername = xstrdup(buffer); + filter_config = xcalloc(1, sizeof *filter_config); + filter_config->filter_type = FILTER_TYPE_CHAIN; + filter_config->filter_subsystem |= FILTER_SUBSYSTEM_SMTP_IN; + dict_init(&filter_config->chain_procs); + } '{' filter_list '}' { + dict_set(conf->sc_filters_dict, listen_opts.filtername, filter_config); + filter_config = NULL; + } + | SMTPS { + if (listen_opts.options & LO_SSL) { + yyerror("TLS mode already specified"); + YYERROR; + } + listen_opts.options |= LO_SSL; + listen_opts.ssl = F_SMTPS; + } + | SMTPS VERIFY { + if (listen_opts.options & LO_SSL) { + yyerror("TLS mode already specified"); + YYERROR; + } + listen_opts.options |= LO_SSL; + listen_opts.ssl = F_SMTPS|F_TLS_VERIFY; + } + | TLS { + if (listen_opts.options & LO_SSL) { + yyerror("TLS mode already specified"); + YYERROR; + } + listen_opts.options |= LO_SSL; + listen_opts.ssl = F_STARTTLS; + } + | TLS_REQUIRE { + if (listen_opts.options & LO_SSL) { + yyerror("TLS mode already specified"); + YYERROR; + } + listen_opts.options |= LO_SSL; + listen_opts.ssl = F_STARTTLS|F_STARTTLS_REQUIRE; + } + | TLS_REQUIRE VERIFY { + if (listen_opts.options & LO_SSL) { + yyerror("TLS mode already specified"); + YYERROR; + } + listen_opts.options |= LO_SSL; + listen_opts.ssl = F_STARTTLS|F_STARTTLS_REQUIRE|F_TLS_VERIFY; + } + | PKI STRING { + if (listen_opts.options & LO_PKI) { + yyerror("pki already specified"); + YYERROR; + } + listen_opts.options |= LO_PKI; + listen_opts.pki = $2; + } + | CA STRING { + if (listen_opts.options & LO_CA) { + yyerror("ca already specified"); + YYERROR; + } + listen_opts.options |= LO_CA; + listen_opts.ca = $2; + } + | AUTH { + if (listen_opts.options & LO_AUTH) { + yyerror("auth already specified"); + YYERROR; + } + listen_opts.options |= LO_AUTH; + listen_opts.auth = F_AUTH|F_AUTH_REQUIRE; + } + | AUTH_OPTIONAL { + if (listen_opts.options & LO_AUTH) { + yyerror("auth already specified"); + YYERROR; + } + listen_opts.options |= LO_AUTH; + listen_opts.auth = F_AUTH; + } + | AUTH tables { + if (listen_opts.options & LO_AUTH) { + yyerror("auth already specified"); + YYERROR; + } + listen_opts.options |= LO_AUTH; + listen_opts.authtable = $2; + listen_opts.auth = F_AUTH|F_AUTH_REQUIRE; + } + | AUTH_OPTIONAL tables { + if (listen_opts.options & LO_AUTH) { + yyerror("auth already specified"); + YYERROR; + } + listen_opts.options |= LO_AUTH; + listen_opts.authtable = $2; + listen_opts.auth = F_AUTH; + } + | TAG STRING { + if (listen_opts.options & LO_TAG) { + yyerror("tag already specified"); + YYERROR; + } + listen_opts.options |= LO_TAG; + + if (strlen($2) >= SMTPD_TAG_SIZE) { + yyerror("tag name too long"); + free($2); + YYERROR; + } + listen_opts.tag = $2; + } + | HOSTNAME STRING { + if (listen_opts.options & LO_HOSTNAME) { + yyerror("hostname already specified"); + YYERROR; + } + listen_opts.options |= LO_HOSTNAME; + + listen_opts.hostname = $2; + } + | HOSTNAMES tables { + struct table *t = $2; + + if (listen_opts.options & LO_HOSTNAMES) { + yyerror("hostnames already specified"); + YYERROR; + } + listen_opts.options |= LO_HOSTNAMES; + + if (!table_check_use(t, T_DYNAMIC|T_HASH, K_ADDRNAME)) { + yyerror("invalid use of table \"%s\" as " + "HOSTNAMES parameter", t->t_name); + YYERROR; + } + listen_opts.hostnametable = t; + } + | MASK_SRC { + if (config_lo_mask_source(&listen_opts)) { + YYERROR; + } + } + | RECEIVEDAUTH { + if (listen_opts.options & LO_RECEIVEDAUTH) { + yyerror("received-auth already specified"); + YYERROR; + } + listen_opts.options |= LO_RECEIVEDAUTH; + listen_opts.flags |= F_RECEIVEDAUTH; + } + | NO_DSN { + if (listen_opts.options & LO_NODSN) { + yyerror("no-dsn already specified"); + YYERROR; + } + listen_opts.options |= LO_NODSN; + listen_opts.flags &= ~F_EXT_DSN; + } + | PROXY_V2 { + if (listen_opts.options & LO_PROXY) { + yyerror("proxy-v2 already specified"); + YYERROR; + } + listen_opts.options |= LO_PROXY; + listen_opts.flags |= F_PROXY; + } + | SENDERS tables { + struct table *t = $2; + + if (listen_opts.options & LO_SENDERS) { + yyerror("senders already specified"); + YYERROR; + } + listen_opts.options |= LO_SENDERS; + + if (!table_check_use(t, T_DYNAMIC|T_HASH, K_MAILADDRMAP)) { + yyerror("invalid use of table \"%s\" as " + "SENDERS parameter", t->t_name); + YYERROR; + } + listen_opts.sendertable = t; + } + | SENDERS tables MASQUERADE { + struct table *t = $2; + + if (listen_opts.options & LO_SENDERS) { + yyerror("senders already specified"); + YYERROR; + } + listen_opts.options |= LO_SENDERS|LO_MASQUERADE; + + if (!table_check_use(t, T_DYNAMIC|T_HASH, K_MAILADDRMAP)) { + yyerror("invalid use of table \"%s\" as " + "SENDERS parameter", t->t_name); + YYERROR; + } + listen_opts.sendertable = t; + } + ; + +listener_type : socket_listener + | if_listener + ; + +socket_listener : SOCKET sock_listen { + if (conf->sc_sock_listener) { + yyerror("socket listener already configured"); + YYERROR; + } + create_sock_listener(&listen_opts); + } + ; + +if_listener : STRING if_listen { + listen_opts.ifx = $1; + create_if_listener(&listen_opts); + } + ; + +sock_listen : opt_sock_listen sock_listen + | /* empty */ + ; + +if_listen : opt_if_listen if_listen + | /* empty */ + ; + + +listen : LISTEN { + memset(&listen_opts, 0, sizeof listen_opts); + listen_opts.family = AF_UNSPEC; + listen_opts.flags |= F_EXT_DSN; + } ON listener_type + ; + +table : TABLE STRING STRING { + char *p, *backend, *config; + + p = $3; + if (*p == '/') { + backend = "static"; + config = $3; + } + else { + backend = $3; + config = NULL; + for (p = $3; *p && *p != ':'; p++) + ; + if (*p == ':') { + *p = '\0'; + backend = $3; + config = p+1; + } + } + if (config != NULL && *config != '/') { + yyerror("invalid backend parameter for table: %s", + $2); + free($2); + free($3); + YYERROR; + } + table = table_create(conf, backend, $2, config); + if (!table_config(table)) { + yyerror("invalid configuration file %s for table %s", + config, table->t_name); + free($2); + free($3); + YYERROR; + } + table = NULL; + free($2); + free($3); + } + | TABLE STRING { + table = table_create(conf, "static", $2, NULL); + free($2); + } '{' tableval_list '}' { + table = NULL; + } + ; + +tablenew : STRING { + struct table *t; + + t = table_create(conf, "static", NULL, NULL); + table_add(t, $1, NULL); + free($1); + $$ = t; + } + | '{' { + table = table_create(conf, "static", NULL, NULL); + } tableval_list '}' { + $$ = table; + table = NULL; + } + ; + +tableref : '<' STRING '>' { + struct table *t; + + if ((t = table_find(conf, $2)) == NULL) { + yyerror("no such table: %s", $2); + free($2); + YYERROR; + } + free($2); + $$ = t; + } + ; + +tables : tablenew { $$ = $1; } + | tableref { $$ = $1; } + ; + + +%% + +struct keywords { + const char *k_name; + int k_val; +}; + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + char *msg; + + file->errors++; + va_start(ap, fmt); + if (vasprintf(&msg, fmt, ap) == -1) + fatalx("yyerror vasprintf"); + va_end(ap); + logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg); + free(msg); + return (0); +} + +int +kw_cmp(const void *k, const void *e) +{ + return (strcmp(k, ((const struct keywords *)e)->k_name)); +} + +int +lookup(char *s) +{ + /* this has to be sorted always */ + static const struct keywords keywords[] = { + { "action", ACTION }, + { "alias", ALIAS }, + { "any", ANY }, + { "auth", AUTH }, + { "auth-optional", AUTH_OPTIONAL }, + { "backup", BACKUP }, + { "bounce", BOUNCE }, + { "bypass", BYPASS }, + { "ca", CA }, + { "cert", CERT }, + { "chain", CHAIN }, + { "chroot", CHROOT }, + { "ciphers", CIPHERS }, + { "commit", COMMIT }, + { "compression", COMPRESSION }, + { "connect", CONNECT }, + { "data", DATA }, + { "data-line", DATA_LINE }, + { "dhe", DHE }, + { "disconnect", DISCONNECT }, + { "domain", DOMAIN }, + { "ehlo", EHLO }, + { "encryption", ENCRYPTION }, + { "expand-only", EXPAND_ONLY }, + { "fcrdns", FCRDNS }, + { "filter", FILTER }, + { "for", FOR }, + { "forward-only", FORWARD_ONLY }, + { "from", FROM }, + { "group", GROUP }, + { "helo", HELO }, + { "helo-src", HELO_SRC }, + { "host", HOST }, + { "hostname", HOSTNAME }, + { "hostnames", HOSTNAMES }, + { "include", INCLUDE }, + { "inet4", INET4 }, + { "inet6", INET6 }, + { "junk", JUNK }, + { "key", KEY }, + { "limit", LIMIT }, + { "listen", LISTEN }, + { "lmtp", LMTP }, + { "local", LOCAL }, + { "mail-from", MAIL_FROM }, + { "maildir", MAILDIR }, + { "mask-src", MASK_SRC }, + { "masquerade", MASQUERADE }, + { "match", MATCH }, + { "max-deferred", MAX_DEFERRED }, + { "max-message-size", MAX_MESSAGE_SIZE }, + { "mbox", MBOX }, + { "mda", MDA }, + { "mta", MTA }, + { "mx", MX }, + { "no-dsn", NO_DSN }, + { "no-verify", NO_VERIFY }, + { "noop", NOOP }, + { "on", ON }, + { "phase", PHASE }, + { "pki", PKI }, + { "port", PORT }, + { "proc", PROC }, + { "proc-exec", PROC_EXEC }, + { "proxy-v2", PROXY_V2 }, + { "queue", QUEUE }, + { "quit", QUIT }, + { "rcpt-to", RCPT_TO }, + { "rdns", RDNS }, + { "received-auth", RECEIVEDAUTH }, + { "recipient", RECIPIENT }, + { "regex", REGEX }, + { "reject", REJECT }, + { "relay", RELAY }, + { "report", REPORT }, + { "rewrite", REWRITE }, + { "rset", RSET }, + { "scheduler", SCHEDULER }, + { "senders", SENDERS }, + { "smtp", SMTP }, + { "smtp-in", SMTP_IN }, + { "smtp-out", SMTP_OUT }, + { "smtps", SMTPS }, + { "socket", SOCKET }, + { "src", SRC }, + { "srs", SRS }, + { "sub-addr-delim", SUB_ADDR_DELIM }, + { "table", TABLE }, + { "tag", TAG }, + { "tagged", TAGGED }, + { "tls", TLS }, + { "tls-require", TLS_REQUIRE }, + { "ttl", TTL }, + { "user", USER }, + { "userbase", USERBASE }, + { "verify", VERIFY }, + { "virtual", VIRTUAL }, + { "warn-interval", WARN_INTERVAL }, + { "wrapper", WRAPPER }, + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + + if (p) + return (p->k_val); + else + return (STRING); +} + +#define START_EXPAND 1 +#define DONE_EXPAND 2 + +static int expanding; + +int +igetc(void) +{ + int c; + + while (1) { + if (file->ungetpos > 0) + c = file->ungetbuf[--file->ungetpos]; + else + c = getc(file->stream); + + if (c == START_EXPAND) + expanding = 1; + else if (c == DONE_EXPAND) + expanding = 0; + else + break; + } + return (c); +} + +int +lgetc(int quotec) +{ + int c, next; + + if (quotec) { + if ((c = igetc()) == EOF) { + yyerror("reached end of file while parsing " + "quoted string"); + if (file == topfile || popfile() == EOF) + return (EOF); + return (quotec); + } + return (c); + } + + while ((c = igetc()) == '\\') { + next = igetc(); + if (next != '\n') { + c = next; + break; + } + yylval.lineno = file->lineno; + file->lineno++; + } + + if (c == EOF) { + /* + * Fake EOL when hit EOF for the first time. This gets line + * count right if last line in included file is syntactically + * invalid and has no newline. + */ + if (file->eof_reached == 0) { + file->eof_reached = 1; + return ('\n'); + } + while (c == EOF) { + if (file == topfile || popfile() == EOF) + return (EOF); + c = igetc(); + } + } + return (c); +} + +void +lungetc(int c) +{ + if (c == EOF) + return; + + if (file->ungetpos >= file->ungetsize) { + void *p = reallocarray(file->ungetbuf, file->ungetsize, 2); + if (p == NULL) + err(1, "%s", __func__); + file->ungetbuf = p; + file->ungetsize *= 2; + } + file->ungetbuf[file->ungetpos++] = c; +} + +int +findeol(void) +{ + int c; + + /* skip to either EOF or the first real EOL */ + while (1) { + c = lgetc(0); + if (c == '\n') { + file->lineno++; + break; + } + if (c == EOF) + break; + } + return (ERROR); +} + +int +yylex(void) +{ + unsigned char buf[8096]; + unsigned char *p, *val; + int quotec, next, c; + int token; + +top: + p = buf; + while ((c = lgetc(0)) == ' ' || c == '\t') + ; /* nothing */ + + yylval.lineno = file->lineno; + if (c == '#') + while ((c = lgetc(0)) != '\n' && c != EOF) + ; /* nothing */ + if (c == '$' && !expanding) { + while (1) { + if ((c = lgetc(0)) == EOF) + return (0); + + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + if (isalnum(c) || c == '_') { + *p++ = c; + continue; + } + *p = '\0'; + lungetc(c); + break; + } + val = symget(buf); + if (val == NULL) { + yyerror("macro '%s' not defined", buf); + return (findeol()); + } + p = val + strlen(val) - 1; + lungetc(DONE_EXPAND); + while (p >= val) { + lungetc(*p); + p--; + } + lungetc(START_EXPAND); + goto top; + } + + switch (c) { + case '\'': + case '"': + quotec = c; + while (1) { + if ((c = lgetc(quotec)) == EOF) + return (0); + if (c == '\n') { + file->lineno++; + continue; + } else if (c == '\\') { + if ((next = lgetc(quotec)) == EOF) + return (0); + if (next == quotec || next == ' ' || + next == '\t') + c = next; + else if (next == '\n') { + file->lineno++; + continue; + } else + lungetc(next); + } else if (c == quotec) { + *p = '\0'; + break; + } else if (c == '\0') { + yyerror("syntax error"); + return (findeol()); + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + *p++ = c; + } + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + err(1, "%s", __func__); + return (STRING); + } + +#define allowed_to_end_number(x) \ + (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') + + if (c == '-' || isdigit(c)) { + do { + *p++ = c; + if ((size_t)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && isdigit(c)); + lungetc(c); + if (p == buf + 1 && buf[0] == '-') + goto nodigits; + if (c == EOF || allowed_to_end_number(c)) { + const char *errstr = NULL; + + *p = '\0'; + yylval.v.number = strtonum(buf, LLONG_MIN, + LLONG_MAX, &errstr); + if (errstr) { + yyerror("\"%s\" invalid number: %s", + buf, errstr); + return (findeol()); + } + return (NUMBER); + } else { +nodigits: + while (p > buf + 1) + lungetc(*--p); + c = *--p; + if (c == '-') + return (c); + } + } + + if (c == '=') { + if ((c = lgetc(0)) != EOF && c == '>') + return (ARROW); + lungetc(c); + c = '='; + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && x != '<' && x != '>' && \ + x != '!' && x != '=' && x != '#' && \ + x != ',')) + + if (isalnum(c) || c == ':' || c == '_') { + do { + *p++ = c; + if ((size_t)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + if ((token = lookup(buf)) == STRING) + if ((yylval.v.string = strdup(buf)) == NULL) + err(1, "%s", __func__); + return (token); + } + if (c == '\n') { + yylval.lineno = file->lineno; + file->lineno++; + } + if (c == EOF) + return (0); + return (c); +} + +int +check_file_secrecy(int fd, const char *fname) +{ + struct stat st; + + if (fstat(fd, &st)) { + log_warn("warn: cannot stat %s", fname); + return (-1); + } + if (st.st_uid != 0 && st.st_uid != getuid()) { + log_warnx("warn: %s: owner not root or current user", fname); + return (-1); + } + if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) { + log_warnx("warn: %s: group/world readable/writeable", fname); + return (-1); + } + return (0); +} + +struct file * +pushfile(const char *name, int secret) +{ + struct file *nfile; + + if ((nfile = calloc(1, sizeof(struct file))) == NULL) { + log_warn("%s", __func__); + return (NULL); + } + if ((nfile->name = strdup(name)) == NULL) { + log_warn("%s", __func__); + free(nfile); + return (NULL); + } + if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { + log_warn("%s: %s", __func__, nfile->name); + free(nfile->name); + free(nfile); + return (NULL); + } else if (secret && + check_file_secrecy(fileno(nfile->stream), nfile->name)) { + fclose(nfile->stream); + free(nfile->name); + free(nfile); + return (NULL); + } + nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0; + nfile->ungetsize = 16; + nfile->ungetbuf = malloc(nfile->ungetsize); + if (nfile->ungetbuf == NULL) { + log_warn("%s", __func__); + fclose(nfile->stream); + free(nfile->name); + free(nfile); + return (NULL); + } + TAILQ_INSERT_TAIL(&files, nfile, entry); + return (nfile); +} + +int +popfile(void) +{ + struct file *prev; + + if ((prev = TAILQ_PREV(file, files, entry)) != NULL) + prev->errors += file->errors; + + TAILQ_REMOVE(&files, file, entry); + fclose(file->stream); + free(file->name); + free(file->ungetbuf); + free(file); + file = prev; + return (file ? 0 : EOF); +} + +int +parse_config(struct smtpd *x_conf, const char *filename, int opts) +{ + struct sym *sym, *next; + + conf = x_conf; + errors = 0; + + if ((file = pushfile(filename, 0)) == NULL) { + purge_config(PURGE_EVERYTHING); + return (-1); + } + topfile = file; + + /* + * parse configuration + */ + setservent(1); + yyparse(); + errors = file->errors; + popfile(); + endservent(); + + /* If the socket listener was not configured, create a default one. */ + if (!conf->sc_sock_listener) { + memset(&listen_opts, 0, sizeof listen_opts); + create_sock_listener(&listen_opts); + } + + /* Free macros and check which have not been used. */ + TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) { + if ((conf->sc_opts & SMTPD_OPT_VERBOSE) && !sym->used) + fprintf(stderr, "warning: macro '%s' not " + "used\n", sym->nam); + if (!sym->persist) { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + + if (TAILQ_EMPTY(conf->sc_rules)) { + log_warnx("warn: no rules, nothing to do"); + errors++; + } + + if (errors) { + purge_config(PURGE_EVERYTHING); + return (-1); + } + + return (0); +} + +int +symset(const char *nam, const char *val, int persist) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) { + if (strcmp(nam, sym->nam) == 0) + break; + } + + if (sym != NULL) { + if (sym->persist == 1) + return (0); + else { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + if ((sym = calloc(1, sizeof(*sym))) == NULL) + return (-1); + + sym->nam = strdup(nam); + if (sym->nam == NULL) { + free(sym); + return (-1); + } + sym->val = strdup(val); + if (sym->val == NULL) { + free(sym->nam); + free(sym); + return (-1); + } + sym->used = 0; + sym->persist = persist; + TAILQ_INSERT_TAIL(&symhead, sym, entry); + return (0); +} + +int +cmdline_symset(char *s) +{ + char *sym, *val; + int ret; + + if ((val = strrchr(s, '=')) == NULL) + return (-1); + sym = strndup(s, val - s); + if (sym == NULL) + errx(1, "%s: strndup", __func__); + ret = symset(sym, val + 1, 1); + free(sym); + + return (ret); +} + +char * +symget(const char *nam) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) { + if (strcmp(nam, sym->nam) == 0) { + sym->used = 1; + return (sym->val); + } + } + return (NULL); +} + +static void +create_sock_listener(struct listen_opts *lo) +{ + struct listener *l = xcalloc(1, sizeof(*l)); + lo->hostname = conf->sc_hostname; + l->ss.ss_family = AF_LOCAL; +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN + l->ss.ss_len = sizeof(struct sockaddr *); +#endif + l->local = 1; + conf->sc_sock_listener = l; + config_listener(l, lo); +} + +static void +create_if_listener(struct listen_opts *lo) +{ + uint16_t flags; + + if (lo->port != 0 && lo->ssl == F_SSL) + errx(1, "invalid listen option: tls/smtps on same port"); + + if (lo->auth != 0 && !lo->ssl) + errx(1, "invalid listen option: auth requires tls/smtps"); + + if (lo->pki && !lo->ssl) + errx(1, "invalid listen option: pki requires tls/smtps"); + + flags = lo->flags; + + if (lo->port) { + lo->flags = lo->ssl|lo->auth|flags; + lo->port = htons(lo->port); + } + else { + if (lo->ssl & F_SMTPS) { + lo->port = htons(465); + lo->flags = F_SMTPS|lo->auth|flags; + } + + if (!lo->ssl || (lo->ssl & F_STARTTLS)) { + lo->port = htons(25); + lo->flags = lo->auth|flags; + if (lo->ssl & F_STARTTLS) + lo->flags |= F_STARTTLS; + } + } + + if (interface(lo)) + return; + if (host_v4(lo)) + return; + if (host_v6(lo)) + return; + if (host_dns(lo)) + return; + + errx(1, "invalid virtual ip or interface: %s", lo->ifx); +} + +static void +config_listener(struct listener *h, struct listen_opts *lo) +{ + h->fd = -1; + h->port = lo->port; + h->flags = lo->flags; + + if (lo->hostname == NULL) + lo->hostname = conf->sc_hostname; + + if (lo->options & LO_FILTER) { + h->flags |= F_FILTERED; + (void)strlcpy(h->filter_name, + lo->filtername, + sizeof(h->filter_name)); + } + + h->pki_name[0] = '\0'; + + if (lo->authtable != NULL) + (void)strlcpy(h->authtable, lo->authtable->t_name, sizeof(h->authtable)); + if (lo->pki != NULL) { + if (!lowercase(h->pki_name, lo->pki, sizeof(h->pki_name))) { + log_warnx("pki name too long: %s", lo->pki); + fatalx(NULL); + } + if (dict_get(conf->sc_pki_dict, h->pki_name) == NULL) { + log_warnx("pki name not found: %s", lo->pki); + fatalx(NULL); + } + } + + if (lo->ca != NULL) { + if (!lowercase(h->ca_name, lo->ca, sizeof(h->ca_name))) { + log_warnx("ca name too long: %s", lo->ca); + fatalx(NULL); + } + if (dict_get(conf->sc_ca_dict, h->ca_name) == NULL) { + log_warnx("ca name not found: %s", lo->ca); + fatalx(NULL); + } + } + if (lo->tag != NULL) + (void)strlcpy(h->tag, lo->tag, sizeof(h->tag)); + + (void)strlcpy(h->hostname, lo->hostname, sizeof(h->hostname)); + if (lo->hostnametable) + (void)strlcpy(h->hostnametable, lo->hostnametable->t_name, sizeof(h->hostnametable)); + if (lo->sendertable) { + (void)strlcpy(h->sendertable, lo->sendertable->t_name, sizeof(h->sendertable)); + if (lo->options & LO_MASQUERADE) + h->flags |= F_MASQUERADE; + } + + if (lo->ssl & F_TLS_VERIFY) + h->flags |= F_TLS_VERIFY; + + if (lo->ssl & F_STARTTLS_REQUIRE) + h->flags |= F_STARTTLS_REQUIRE; + + if (h != conf->sc_sock_listener) + TAILQ_INSERT_TAIL(conf->sc_listeners, h, entry); +} + +static int +host_v4(struct listen_opts *lo) +{ + struct in_addr ina; + struct sockaddr_in *sain; + struct listener *h; + + if (lo->family != AF_UNSPEC && lo->family != AF_INET) + return (0); + + memset(&ina, 0, sizeof(ina)); + if (inet_pton(AF_INET, lo->ifx, &ina) != 1) + return (0); + + h = xcalloc(1, sizeof(*h)); + sain = (struct sockaddr_in *)&h->ss; +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + sain->sin_len = sizeof(struct sockaddr_in); +#endif + sain->sin_family = AF_INET; + sain->sin_addr.s_addr = ina.s_addr; + sain->sin_port = lo->port; + + if (sain->sin_addr.s_addr == htonl(INADDR_LOOPBACK)) + h->local = 1; + config_listener(h, lo); + + return (1); +} + +static int +host_v6(struct listen_opts *lo) +{ + struct in6_addr ina6; + struct sockaddr_in6 *sin6; + struct listener *h; + + if (lo->family != AF_UNSPEC && lo->family != AF_INET6) + return (0); + + memset(&ina6, 0, sizeof(ina6)); + if (inet_pton(AF_INET6, lo->ifx, &ina6) != 1) + return (0); + + h = xcalloc(1, sizeof(*h)); + sin6 = (struct sockaddr_in6 *)&h->ss; +#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN + sin6->sin6_len = sizeof(struct sockaddr_in6); +#endif + sin6->sin6_family = AF_INET6; + sin6->sin6_port = lo->port; + memcpy(&sin6->sin6_addr, &ina6, sizeof(ina6)); + + if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr)) + h->local = 1; + config_listener(h, lo); + + return (1); +} + +static int +host_dns(struct listen_opts *lo) +{ + struct addrinfo hints, *res0, *res; + int error, cnt = 0; + struct sockaddr_in *sain; + struct sockaddr_in6 *sin6; + struct listener *h; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = lo->family; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_ADDRCONFIG; + error = getaddrinfo(lo->ifx, NULL, &hints, &res0); + if (error == EAI_AGAIN || error == EAI_NODATA || error == EAI_NONAME) + return (0); + if (error) { + log_warnx("warn: host_dns: could not parse \"%s\": %s", lo->ifx, + gai_strerror(error)); + return (-1); + } + + for (res = res0; res; res = res->ai_next) { + if (res->ai_family != AF_INET && + res->ai_family != AF_INET6) + continue; + h = xcalloc(1, sizeof(*h)); + + h->ss.ss_family = res->ai_family; + if (res->ai_family == AF_INET) { + sain = (struct sockaddr_in *)&h->ss; +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + sain->sin_len = sizeof(struct sockaddr_in); +#endif + sain->sin_addr.s_addr = ((struct sockaddr_in *) + res->ai_addr)->sin_addr.s_addr; + sain->sin_port = lo->port; + if (sain->sin_addr.s_addr == htonl(INADDR_LOOPBACK)) + h->local = 1; + } else { + sin6 = (struct sockaddr_in6 *)&h->ss; +#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN + sin6->sin6_len = sizeof(struct sockaddr_in6); +#endif + memcpy(&sin6->sin6_addr, &((struct sockaddr_in6 *) + res->ai_addr)->sin6_addr, sizeof(struct in6_addr)); + sin6->sin6_port = lo->port; + if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr)) + h->local = 1; + } + + config_listener(h, lo); + + cnt++; + } + + freeaddrinfo(res0); + return (cnt); +} + +static int +interface(struct listen_opts *lo) +{ + struct ifaddrs *ifap, *p; + struct sockaddr_in *sain; + struct sockaddr_in6 *sin6; + struct listener *h; + int ret = 0; + + if (getifaddrs(&ifap) == -1) + fatal("getifaddrs"); + + for (p = ifap; p != NULL; p = p->ifa_next) { + if (p->ifa_addr == NULL) + continue; + if (strcmp(p->ifa_name, lo->ifx) != 0 && + !is_if_in_group(p->ifa_name, lo->ifx)) + continue; + if (lo->family != AF_UNSPEC && lo->family != p->ifa_addr->sa_family) + continue; + + h = xcalloc(1, sizeof(*h)); + + switch (p->ifa_addr->sa_family) { + case AF_INET: + sain = (struct sockaddr_in *)&h->ss; + *sain = *(struct sockaddr_in *)p->ifa_addr; +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + sain->sin_len = sizeof(struct sockaddr_in); +#endif + sain->sin_port = lo->port; + if (sain->sin_addr.s_addr == htonl(INADDR_LOOPBACK)) + h->local = 1; + break; + + case AF_INET6: + sin6 = (struct sockaddr_in6 *)&h->ss; + *sin6 = *(struct sockaddr_in6 *)p->ifa_addr; +#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN + sin6->sin6_len = sizeof(struct sockaddr_in6); +#endif + sin6->sin6_port = lo->port; + if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr)) + h->local = 1; + break; + + default: + free(h); + continue; + } + + config_listener(h, lo); + ret = 1; + } + + freeifaddrs(ifap); + + return ret; +} + +int +delaytonum(char *str) +{ + unsigned int factor; + size_t len; + const char *errstr = NULL; + int delay; + + /* we need at least 1 digit and 1 unit */ + len = strlen(str); + if (len < 2) + goto bad; + + switch(str[len - 1]) { + + case 's': + factor = 1; + break; + + case 'm': + factor = 60; + break; + + case 'h': + factor = 60 * 60; + break; + + case 'd': + factor = 24 * 60 * 60; + break; + + default: + goto bad; + } + + str[len - 1] = '\0'; + delay = strtonum(str, 1, INT_MAX / factor, &errstr); + if (errstr) + goto bad; + + return (delay * factor); + +bad: + return (-1); +} + +int +is_if_in_group(const char *ifname, const char *groupname) +{ +#ifdef HAVE_STRUCT_IFGROUPREQ + unsigned int len; + struct ifgroupreq ifgr; + struct ifg_req *ifg; + int s; + int ret = 0; + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + err(1, "socket"); + + memset(&ifgr, 0, sizeof(ifgr)); + if (strlcpy(ifgr.ifgr_name, ifname, IFNAMSIZ) >= IFNAMSIZ) + errx(1, "interface name too large"); + + if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1) { + if (errno == EINVAL || errno == ENOTTY) + goto end; + err(1, "SIOCGIFGROUP"); + } + + len = ifgr.ifgr_len; + ifgr.ifgr_groups = xcalloc(len/sizeof(struct ifg_req), + sizeof(struct ifg_req)); + if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1) + err(1, "SIOCGIFGROUP"); + + ifg = ifgr.ifgr_groups; + for (; ifg && len >= sizeof(struct ifg_req); ifg++) { + len -= sizeof(struct ifg_req); + if (strcmp(ifg->ifgrq_group, groupname) == 0) { + ret = 1; + break; + } + } + free(ifgr.ifgr_groups); + +end: + close(s); + return ret; +#else + return (0); +#endif +} + +static int +config_lo_mask_source(struct listen_opts *lo) { + if (lo->options & LO_MASKSOURCE) { + yyerror("mask-source already specified"); + return -1; + } + lo->options |= LO_MASKSOURCE; + lo->flags |= F_MASK_SOURCE; + + return 0; +} + diff --git a/foobar/portable/smtpd/parser.c b/foobar/portable/smtpd/parser.c new file mode 100644 index 00000000..4c2321ec --- /dev/null +++ b/foobar/portable/smtpd/parser.c @@ -0,0 +1,341 @@ +/* $OpenBSD: parser.c,v 1.42 2020/01/06 11:02:38 gilles Exp $ */ + +/* + * Copyright (c) 2013 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/socket.h> + +#include <netinet/in.h> +#include <net/if.h> +#include <arpa/inet.h> + +#include <err.h> +#include <inttypes.h> +#include <limits.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "parser.h" + +uint64_t text_to_evpid(const char *); +uint32_t text_to_msgid(const char *); + +struct node { + int type; + const char *token; + struct node *parent; + TAILQ_ENTRY(node) entry; + TAILQ_HEAD(, node) children; + int (*cmd)(int, struct parameter*); +}; + +static struct node *root; + +static int text_to_sockaddr(struct sockaddr *, int, const char *); + +#define ARGVMAX 64 + +int +cmd_install(const char *pattern, int (*cmd)(int, struct parameter*)) +{ + struct node *node, *tmp; + char *s, *str, *argv[ARGVMAX], **ap; + int i, n; + + /* Tokenize */ + str = s = strdup(pattern); + if (str == NULL) + err(1, "strdup"); + n = 0; + for (ap = argv; n < ARGVMAX && (*ap = strsep(&str, " \t")) != NULL;) { + if (**ap != '\0') { + ap++; + n++; + } + } + *ap = NULL; + + if (root == NULL) { + root = calloc(1, sizeof (*root)); + TAILQ_INIT(&root->children); + } + node = root; + + for (i = 0; i < n; i++) { + TAILQ_FOREACH(tmp, &node->children, entry) { + if (!strcmp(tmp->token, argv[i])) { + node = tmp; + break; + } + } + if (tmp == NULL) { + tmp = calloc(1, sizeof (*tmp)); + TAILQ_INIT(&tmp->children); + if (!strcmp(argv[i], "<str>")) + tmp->type = P_STR; + else if (!strcmp(argv[i], "<int>")) + tmp->type = P_INT; + else if (!strcmp(argv[i], "<msgid>")) + tmp->type = P_MSGID; + else if (!strcmp(argv[i], "<evpid>")) + tmp->type = P_EVPID; + else if (!strcmp(argv[i], "<routeid>")) + tmp->type = P_ROUTEID; + else if (!strcmp(argv[i], "<addr>")) + tmp->type = P_ADDR; + else + tmp->type = P_TOKEN; + tmp->token = strdup(argv[i]); + tmp->parent = node; + TAILQ_INSERT_TAIL(&node->children, tmp, entry); + node = tmp; + } + } + + if (node->cmd) + errx(1, "duplicate pattern: %s", pattern); + node->cmd = cmd; + + free(s); + return (n); +} + +static int +cmd_check(const char *str, struct node *node, struct parameter *res) +{ + const char *e; + + switch (node->type) { + case P_TOKEN: + if (!strcmp(str, node->token)) + return (1); + return (0); + + case P_STR: + res->u.u_str = str; + return (1); + + case P_INT: + res->u.u_int = strtonum(str, INT_MIN, INT_MAX, &e); + if (e) + return (0); + return (1); + + case P_MSGID: + if (strlen(str) != 8) + return (0); + res->u.u_msgid = text_to_msgid(str); + if (res->u.u_msgid == 0) + return (0); + return (1); + + case P_EVPID: + if (strlen(str) != 16) + return (0); + res->u.u_evpid = text_to_evpid(str); + if (res->u.u_evpid == 0) + return (0); + return (1); + + case P_ROUTEID: + res->u.u_routeid = strtonum(str, 1, LLONG_MAX, &e); + if (e) + return (0); + return (1); + + case P_ADDR: + if (text_to_sockaddr((struct sockaddr *)&res->u.u_ss, PF_UNSPEC, str) == 0) + return (1); + return (0); + + default: + errx(1, "bad token type: %d", node->type); + return (0); + } +} + +int +cmd_run(int argc, char **argv) +{ + struct parameter param[ARGVMAX]; + struct node *node, *tmp, *stack[ARGVMAX], *best; + int i, j, np; + + node = root; + np = 0; + + for (i = 0; i < argc; i++) { + TAILQ_FOREACH(tmp, &node->children, entry) { + if (cmd_check(argv[i], tmp, ¶m[np])) { + stack[i] = tmp; + node = tmp; + param[np].type = node->type; + if (node->type != P_TOKEN) + np++; + break; + } + } + if (tmp == NULL) { + best = NULL; + TAILQ_FOREACH(tmp, &node->children, entry) { + if (tmp->type != P_TOKEN) + continue; + if (strstr(tmp->token, argv[i]) != tmp->token) + continue; + if (best) + goto fail; + best = tmp; + } + if (best == NULL) + goto fail; + stack[i] = best; + node = best; + param[np].type = node->type; + if (node->type != P_TOKEN) + np++; + } + } + + if (node->cmd == NULL) + goto fail; + + return (node->cmd(np, np ? param : NULL)); + +fail: + if (TAILQ_FIRST(&node->children) == NULL) { + fprintf(stderr, "invalid command\n"); + return (-1); + } + + fprintf(stderr, "possibilities are:\n"); + TAILQ_FOREACH(tmp, &node->children, entry) { + for (j = 0; j < i; j++) + fprintf(stderr, "%s%s", j?" ":"", stack[j]->token); + fprintf(stderr, "%s%s\n", i?" ":"", tmp->token); + } + + return (-1); +} + +int +cmd_show_params(int argc, struct parameter *argv) +{ + int i; + + for (i = 0; i < argc; i++) { + switch(argv[i].type) { + case P_STR: + printf(" str:\"%s\"", argv[i].u.u_str); + break; + case P_INT: + printf(" int:%d", argv[i].u.u_int); + break; + case P_MSGID: + printf(" msgid:%08"PRIx32, argv[i].u.u_msgid); + break; + case P_EVPID: + printf(" evpid:%016"PRIx64, argv[i].u.u_evpid); + break; + case P_ROUTEID: + printf(" routeid:%016"PRIx64, argv[i].u.u_routeid); + break; + default: + printf(" ???:%d", argv[i].type); + } + } + printf ("\n"); + return (1); +} + +static int +text_to_sockaddr(struct sockaddr *sa, int family, const char *str) +{ + struct in_addr ina; + struct in6_addr in6a; + struct sockaddr_in *in; + struct sockaddr_in6 *in6; + char *cp, *str2; + const char *errstr; + + switch (family) { + case PF_UNSPEC: + if (text_to_sockaddr(sa, PF_INET, str) == 0) + return (0); + return text_to_sockaddr(sa, PF_INET6, str); + + case PF_INET: + if (inet_pton(PF_INET, str, &ina) != 1) + return (-1); + + in = (struct sockaddr_in *)sa; + memset(in, 0, sizeof *in); +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + in->sin_len = sizeof(struct sockaddr_in); +#endif + in->sin_family = PF_INET; + in->sin_addr.s_addr = ina.s_addr; + return (0); + + case PF_INET6: + cp = strchr(str, SCOPE_DELIMITER); + if (cp) { + str2 = strdup(str); + if (str2 == NULL) + return (-1); + str2[cp - str] = '\0'; + if (inet_pton(PF_INET6, str2, &in6a) != 1) { + free(str2); + return (-1); + } + cp++; + free(str2); + } else if (inet_pton(PF_INET6, str, &in6a) != 1) + return (-1); + + in6 = (struct sockaddr_in6 *)sa; + memset(in6, 0, sizeof *in6); +#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN + in6->sin6_len = sizeof(struct sockaddr_in6); +#endif + in6->sin6_family = PF_INET6; + in6->sin6_addr = in6a; + + if (cp == NULL) + return (0); + + if (IN6_IS_ADDR_LINKLOCAL(&in6a) || + IN6_IS_ADDR_MC_LINKLOCAL(&in6a) || + IN6_IS_ADDR_MC_NODELOCAL(&in6a)) + if ((in6->sin6_scope_id = if_nametoindex(cp))) + return (0); + + in6->sin6_scope_id = strtonum(cp, 0, UINT32_MAX, &errstr); + if (errstr) + return (-1); + return (0); + + default: + break; + } + + return (-1); +} diff --git a/foobar/portable/smtpd/parser.h b/foobar/portable/smtpd/parser.h new file mode 100644 index 00000000..f0114e9e --- /dev/null +++ b/foobar/portable/smtpd/parser.h @@ -0,0 +1,43 @@ +/* $OpenBSD: parser.h,v 1.29 2014/02/04 15:22:39 eric Exp $ */ + +/* + * Copyright (c) 2013 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +enum { + P_TOKEN, + P_STR, + P_INT, + P_MSGID, + P_EVPID, + P_ROUTEID, + P_ADDR, +}; + +struct parameter { + int type; + union { + const char *u_str; + int u_int; + uint32_t u_msgid; + uint64_t u_evpid; + uint64_t u_routeid; + struct sockaddr_storage u_ss; + } u; +}; + +int cmd_install(const char *, int (*)(int, struct parameter *)); +int cmd_run(int, char **); +int cmd_show_params(int argc, struct parameter *argv); diff --git a/foobar/portable/smtpd/pony.c b/foobar/portable/smtpd/pony.c new file mode 100644 index 00000000..1865b339 --- /dev/null +++ b/foobar/portable/smtpd/pony.c @@ -0,0 +1,212 @@ +/* $OpenBSD: pony.c,v 1.27 2019/06/13 11:45:35 eric Exp $ */ + +/* + * Copyright (c) 2014 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <imsg.h> +#include <inttypes.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <limits.h> +#include <grp.h> + +#include "smtpd.h" +#include "log.h" + +void mda_imsg(struct mproc *, struct imsg *); +void mta_imsg(struct mproc *, struct imsg *); +void smtp_imsg(struct mproc *, struct imsg *); + +static void pony_shutdown(void); + +void +pony_imsg(struct mproc *p, struct imsg *imsg) +{ + struct msg m; + int v; + + if (imsg == NULL) + pony_shutdown(); + + switch (imsg->hdr.type) { + + case IMSG_GETADDRINFO: + case IMSG_GETADDRINFO_END: + case IMSG_GETNAMEINFO: + case IMSG_RES_QUERY: + resolver_dispatch_result(p, imsg); + return; + + case IMSG_CERT_INIT: + case IMSG_CERT_VERIFY: + cert_dispatch_result(p, imsg); + return; + + case IMSG_CONF_START: + return; + case IMSG_CONF_END: + smtp_configure(); + return; + case IMSG_CTL_VERBOSE: + m_msg(&m, imsg); + m_get_int(&m, &v); + m_end(&m); + log_trace_verbose(v); + return; + case IMSG_CTL_PROFILE: + m_msg(&m, imsg); + m_get_int(&m, &v); + m_end(&m); + profiling = v; + return; + + /* smtp imsg */ + case IMSG_SMTP_CHECK_SENDER: + case IMSG_SMTP_EXPAND_RCPT: + case IMSG_SMTP_LOOKUP_HELO: + case IMSG_SMTP_AUTHENTICATE: + case IMSG_SMTP_MESSAGE_COMMIT: + case IMSG_SMTP_MESSAGE_CREATE: + case IMSG_SMTP_MESSAGE_OPEN: + case IMSG_FILTER_SMTP_PROTOCOL: + case IMSG_FILTER_SMTP_DATA_BEGIN: + case IMSG_QUEUE_ENVELOPE_SUBMIT: + case IMSG_QUEUE_ENVELOPE_COMMIT: + case IMSG_QUEUE_SMTP_SESSION: + case IMSG_CTL_SMTP_SESSION: + case IMSG_CTL_PAUSE_SMTP: + case IMSG_CTL_RESUME_SMTP: + smtp_imsg(p, imsg); + return; + + /* mta imsg */ + case IMSG_QUEUE_TRANSFER: + case IMSG_MTA_OPEN_MESSAGE: + case IMSG_MTA_LOOKUP_CREDENTIALS: + case IMSG_MTA_LOOKUP_SMARTHOST: + case IMSG_MTA_LOOKUP_SOURCE: + case IMSG_MTA_LOOKUP_HELO: + case IMSG_MTA_DNS_HOST: + case IMSG_MTA_DNS_HOST_END: + case IMSG_MTA_DNS_MX_PREFERENCE: + case IMSG_CTL_RESUME_ROUTE: + case IMSG_CTL_MTA_SHOW_HOSTS: + case IMSG_CTL_MTA_SHOW_RELAYS: + case IMSG_CTL_MTA_SHOW_ROUTES: + case IMSG_CTL_MTA_SHOW_HOSTSTATS: + case IMSG_CTL_MTA_BLOCK: + case IMSG_CTL_MTA_UNBLOCK: + case IMSG_CTL_MTA_SHOW_BLOCK: + mta_imsg(p, imsg); + return; + + /* mda imsg */ + case IMSG_MDA_LOOKUP_USERINFO: + case IMSG_QUEUE_DELIVER: + case IMSG_MDA_OPEN_MESSAGE: + case IMSG_MDA_FORK: + case IMSG_MDA_DONE: + mda_imsg(p, imsg); + return; + default: + break; + } + + errx(1, "session_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); +} + +static void +pony_shutdown(void) +{ + log_debug("debug: pony agent exiting"); + _exit(0); +} + +int +pony(void) +{ + struct passwd *pw; + + mda_postfork(); + mta_postfork(); + smtp_postfork(); + + /* do not purge listeners and pki, they are purged + * in smtp_configure() + */ + purge_config(PURGE_TABLES|PURGE_RULES); + + if ((pw = getpwnam(SMTPD_USER)) == NULL) + fatalx("unknown user " SMTPD_USER); + + if (chroot(PATH_CHROOT) == -1) + fatal("pony: chroot"); + if (chdir("/") == -1) + fatal("pony: chdir(\"/\")"); + + config_process(PROC_PONY); + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("pony: cannot drop privileges"); + + imsg_callback = pony_imsg; + event_init(); + + mda_postprivdrop(); + mta_postprivdrop(); + smtp_postprivdrop(); + + signal(SIGINT, SIG_IGN); + signal(SIGTERM, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + + config_peer(PROC_PARENT); + config_peer(PROC_QUEUE); + config_peer(PROC_LKA); + config_peer(PROC_CONTROL); + config_peer(PROC_CA); + + ca_engine_init(); + +#if HAVE_PLEDGE + if (pledge("stdio inet unix recvfd sendfd", NULL) == -1) + err(1, "pledge"); +#endif + + event_dispatch(); + fatalx("exited event loop"); + + return (0); +} diff --git a/foobar/portable/smtpd/proxy.c b/foobar/portable/smtpd/proxy.c new file mode 100644 index 00000000..fdaf4f27 --- /dev/null +++ b/foobar/portable/smtpd/proxy.c @@ -0,0 +1,387 @@ +/* + * Copyright (c) 2017 Antoine Kaufmann <toni@famkaufmann.info> + * + * 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 "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/tree.h> +#include <sys/un.h> + +#include <err.h> +#include <errno.h> +#include <event.h> +#include <imsg.h> +#include <inttypes.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "log.h" +#include "smtpd.h" + + +/* + * The PROXYv2 protocol is described here: + * http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt + */ + +#define PROXY_CLOCAL 0x0 +#define PROXY_CPROXY 0x1 +#define PROXY_AF_UNSPEC 0x0 +#define PROXY_AF_INET 0x1 +#define PROXY_AF_INET6 0x2 +#define PROXY_AF_UNIX 0x3 +#define PROXY_TF_UNSPEC 0x0 +#define PROXY_TF_STREAM 0x1 +#define PROXY_TF_DGRAM 0x2 + +#define PROXY_SESSION_TIMEOUT 300 + +static const uint8_t pv2_signature[] = { + 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, + 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A +}; + +struct proxy_hdr_v2 { + uint8_t sig[12]; + uint8_t ver_cmd; + uint8_t fam; + uint16_t len; +} __attribute__((packed)); + + +struct proxy_addr_ipv4 { + uint32_t src_addr; + uint32_t dst_addr; + uint16_t src_port; + uint16_t dst_port; +} __attribute__((packed)); + +struct proxy_addr_ipv6 { + uint8_t src_addr[16]; + uint8_t dst_addr[16]; + uint16_t src_port; + uint16_t dst_port; +} __attribute__((packed)); + +struct proxy_addr_unix { + uint8_t src_addr[108]; + uint8_t dst_addr[108]; +} __attribute__((packed)); + +union proxy_addr { + struct proxy_addr_ipv4 ipv4; + struct proxy_addr_ipv6 ipv6; + struct proxy_addr_unix un; +} __attribute__((packed)); + +struct proxy_session { + struct listener *l; + struct io *io; + + uint64_t id; + int fd; + uint16_t header_len; + uint16_t header_total; + uint16_t addr_len; + uint16_t addr_total; + + struct sockaddr_storage ss; + struct proxy_hdr_v2 hdr; + union proxy_addr addr; + + void (*cb_accepted)(struct listener *, int, + const struct sockaddr_storage *, struct io *); + void (*cb_dropped)(struct listener *, int, + const struct sockaddr_storage *); +}; + +static void proxy_io(struct io *, int, void *); +static void proxy_error(struct proxy_session *, const char *, const char *); +static int proxy_header_validate(struct proxy_session *); +static int proxy_translate_ss(struct proxy_session *); + +int +proxy_session(struct listener *listener, int sock, + const struct sockaddr_storage *ss, + void (*accepted)(struct listener *, int, + const struct sockaddr_storage *, struct io *), + void (*dropped)(struct listener *, int, + const struct sockaddr_storage *)); + +int +proxy_session(struct listener *listener, int sock, + const struct sockaddr_storage *ss, + void (*accepted)(struct listener *, int, + const struct sockaddr_storage *, struct io *), + void (*dropped)(struct listener *, int, + const struct sockaddr_storage *)) +{ + struct proxy_session *s; + + if ((s = calloc(1, sizeof(*s))) == NULL) + return (-1); + + if ((s->io = io_new()) == NULL) { + free(s); + return (-1); + } + + s->id = generate_uid(); + s->l = listener; + s->fd = sock; + s->header_len = 0; + s->addr_len = 0; + s->ss = *ss; + s->cb_accepted = accepted; + s->cb_dropped = dropped; + + io_set_callback(s->io, proxy_io, s); + io_set_fd(s->io, sock); + io_set_timeout(s->io, PROXY_SESSION_TIMEOUT * 1000); + io_set_read(s->io); + + log_info("%016"PRIx64" smtp event=proxy address=%s", + s->id, ss_to_text(&s->ss)); + + return 0; +} + +static void +proxy_io(struct io *io, int evt, void *arg) +{ + struct proxy_session *s = arg; + struct proxy_hdr_v2 *h = &s->hdr; + uint8_t *buf; + size_t len, off; + + switch (evt) { + + case IO_DATAIN: + buf = io_data(io); + len = io_datalen(io); + + if (s->header_len < sizeof(s->hdr)) { + /* header is incomplete */ + off = sizeof(s->hdr) - s->header_len; + off = (len < off ? len : off); + memcpy((uint8_t *) &s->hdr + s->header_len, buf, off); + + s->header_len += off; + buf += off; + len -= off; + io_drop(s->io, off); + + if (s->header_len < sizeof(s->hdr)) { + /* header is still not complete */ + return; + } + + if (proxy_header_validate(s) != 0) + return; + } + + if (s->addr_len < s->addr_total) { + /* address is incomplete */ + off = s->addr_total - s->addr_len; + off = (len < off ? len : off); + memcpy((uint8_t *) &s->addr + s->addr_len, buf, off); + + s->header_len += off; + s->addr_len += off; + buf += off; + len -= off; + io_drop(s->io, off); + + if (s->addr_len < s->addr_total) { + /* address is still not complete */ + return; + } + } + + if (s->header_len < s->header_total) { + /* additional parameters not complete */ + /* these are ignored for now, but we still need to drop + * the bytes from the buffer */ + off = s->header_total - s->header_len; + off = (len < off ? len : off); + + s->header_len += off; + io_drop(s->io, off); + + if (s->header_len < s->header_total) + /* not complete yet */ + return; + } + + switch(h->ver_cmd & 0xF) { + case PROXY_CLOCAL: + /* local address, no need to modify ss */ + break; + + case PROXY_CPROXY: + if (proxy_translate_ss(s) != 0) + return; + break; + + default: + proxy_error(s, "protocol error", "unknown command"); + return; + } + + s->cb_accepted(s->l, s->fd, &s->ss, s->io); + /* we passed off s->io, so it does not need to be freed here */ + free(s); + break; + + case IO_TIMEOUT: + proxy_error(s, "timeout", NULL); + break; + + case IO_DISCONNECTED: + proxy_error(s, "disconnected", NULL); + break; + + case IO_ERROR: + proxy_error(s, "IO error", io_error(io)); + break; + + default: + fatalx("proxy_io()"); + } + +} + +static void +proxy_error(struct proxy_session *s, const char *reason, const char *extra) +{ + if (extra) + log_info("proxy %p event=closed address=%s reason=\"%s (%s)\"", + s, ss_to_text(&s->ss), reason, extra); + else + log_info("proxy %p event=closed address=%s reason=\"%s\"", + s, ss_to_text(&s->ss), reason); + + s->cb_dropped(s->l, s->fd, &s->ss); + io_free(s->io); + free(s); +} + +static int +proxy_header_validate(struct proxy_session *s) +{ + struct proxy_hdr_v2 *h = &s->hdr; + + if (memcmp(h->sig, pv2_signature, + sizeof(pv2_signature)) != 0) { + proxy_error(s, "protocol error", "invalid signature"); + return (-1); + } + + if ((h->ver_cmd >> 4) != 2) { + proxy_error(s, "protocol error", "invalid version"); + return (-1); + } + + switch (h->fam) { + case (PROXY_AF_UNSPEC << 4 | PROXY_TF_UNSPEC): + s->addr_total = 0; + break; + + case (PROXY_AF_INET << 4 | PROXY_TF_STREAM): + s->addr_total = sizeof(s->addr.ipv4); + break; + + case (PROXY_AF_INET6 << 4 | PROXY_TF_STREAM): + s->addr_total = sizeof(s->addr.ipv6); + break; + + case (PROXY_AF_UNIX << 4 | PROXY_TF_STREAM): + s->addr_total = sizeof(s->addr.un); + break; + + default: + proxy_error(s, "protocol error", "unsupported address family"); + return (-1); + } + + s->header_total = ntohs(h->len); + if (s->header_total > UINT16_MAX - sizeof(struct proxy_hdr_v2)) { + proxy_error(s, "protocol error", "header too long"); + return (-1); + } + s->header_total += sizeof(struct proxy_hdr_v2); + + if (s->header_total < sizeof(struct proxy_hdr_v2) + s->addr_total) { + proxy_error(s, "protocol error", "address info too short"); + return (-1); + } + + return 0; +} + +static int +proxy_translate_ss(struct proxy_session *s) +{ + struct sockaddr_in *sin = (struct sockaddr_in *) &s->ss; + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) &s->ss; + struct sockaddr_un *sun = (struct sockaddr_un *) &s->ss; + size_t sun_len; + + switch (s->hdr.fam) { + case (PROXY_AF_UNSPEC << 4 | PROXY_TF_UNSPEC): + /* unspec: only supported for local */ + proxy_error(s, "address translation", "UNSPEC family not " + "supported for PROXYing"); + return (-1); + + case (PROXY_AF_INET << 4 | PROXY_TF_STREAM): + memset(&s->ss, 0, sizeof(s->ss)); + sin->sin_family = AF_INET; + sin->sin_port = s->addr.ipv4.src_port; + sin->sin_addr.s_addr = s->addr.ipv4.src_addr; + break; + + case (PROXY_AF_INET6 << 4 | PROXY_TF_STREAM): + memset(&s->ss, 0, sizeof(s->ss)); + sin6->sin6_family = AF_INET6; + sin6->sin6_port = s->addr.ipv6.src_port; + memcpy(sin6->sin6_addr.s6_addr, s->addr.ipv6.src_addr, + sizeof(s->addr.ipv6.src_addr)); + break; + + case (PROXY_AF_UNIX << 4 | PROXY_TF_STREAM): + memset(&s->ss, 0, sizeof(s->ss)); + sun_len = strnlen(s->addr.un.src_addr, + sizeof(s->addr.un.src_addr)); + if (sun_len > sizeof(sun->sun_path)) { + proxy_error(s, "address translation", "Unix socket path" + " longer than supported"); + return (-1); + } + sun->sun_family = AF_UNIX; + memcpy(sun->sun_path, s->addr.un.src_addr, sun_len); + break; + + default: + fatalx("proxy_translate_ss()"); + } + + return 0; +} diff --git a/foobar/portable/smtpd/queue.c b/foobar/portable/smtpd/queue.c new file mode 100644 index 00000000..434e3647 --- /dev/null +++ b/foobar/portable/smtpd/queue.c @@ -0,0 +1,750 @@ +/* $OpenBSD: queue.c,v 1.190 2020/04/22 11:35:34 eric Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <err.h> +#include <event.h> +#include <fcntl.h> +#include <grp.h> /* needed for setgroups */ +#include <imsg.h> +#include <inttypes.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <limits.h> + +#include "smtpd.h" +#include "log.h" + +static void queue_imsg(struct mproc *, struct imsg *); +static void queue_timeout(int, short, void *); +static void queue_bounce(struct envelope *, struct delivery_bounce *); +static void queue_shutdown(void); +static void queue_log(const struct envelope *, const char *, const char *); +static void queue_msgid_walk(int, short, void *); + + +static void +queue_imsg(struct mproc *p, struct imsg *imsg) +{ + struct delivery_bounce bounce; + struct msg_walkinfo *wi; + struct timeval tv; + struct bounce_req_msg *req_bounce; + struct envelope evp; + struct msg m; + const char *reason; + uint64_t reqid, evpid, holdq; + uint32_t msgid; + time_t nexttry; + size_t n_evp; + int fd, mta_ext, ret, v, flags, code; + + if (imsg == NULL) + queue_shutdown(); + + memset(&bounce, 0, sizeof(struct delivery_bounce)); + + switch (imsg->hdr.type) { + case IMSG_SMTP_MESSAGE_CREATE: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_end(&m); + + ret = queue_message_create(&msgid); + + m_create(p, IMSG_SMTP_MESSAGE_CREATE, 0, 0, -1); + m_add_id(p, reqid); + if (ret == 0) + m_add_int(p, 0); + else { + m_add_int(p, 1); + m_add_msgid(p, msgid); + } + m_close(p); + return; + + case IMSG_SMTP_MESSAGE_ROLLBACK: + m_msg(&m, imsg); + m_get_msgid(&m, &msgid); + m_end(&m); + + queue_message_delete(msgid); + + m_create(p_scheduler, IMSG_QUEUE_MESSAGE_ROLLBACK, + 0, 0, -1); + m_add_msgid(p_scheduler, msgid); + m_close(p_scheduler); + return; + + case IMSG_SMTP_MESSAGE_COMMIT: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_msgid(&m, &msgid); + m_end(&m); + + ret = queue_message_commit(msgid); + + m_create(p, IMSG_SMTP_MESSAGE_COMMIT, 0, 0, -1); + m_add_id(p, reqid); + m_add_int(p, (ret == 0) ? 0 : 1); + m_close(p); + + if (ret) { + m_create(p_scheduler, IMSG_QUEUE_MESSAGE_COMMIT, + 0, 0, -1); + m_add_msgid(p_scheduler, msgid); + m_close(p_scheduler); + } + return; + + case IMSG_SMTP_MESSAGE_OPEN: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_msgid(&m, &msgid); + m_end(&m); + + fd = queue_message_fd_rw(msgid); + + m_create(p, IMSG_SMTP_MESSAGE_OPEN, 0, 0, fd); + m_add_id(p, reqid); + m_add_int(p, (fd == -1) ? 0 : 1); + m_close(p); + return; + + case IMSG_QUEUE_SMTP_SESSION: + bounce_fd(imsg->fd); + return; + + case IMSG_LKA_ENVELOPE_SUBMIT: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_envelope(&m, &evp); + m_end(&m); + + if (evp.id == 0) + log_warnx("warn: imsg_queue_submit_envelope: evpid=0"); + if (evpid_to_msgid(evp.id) == 0) + log_warnx("warn: imsg_queue_submit_envelope: msgid=0, " + "evpid=%016"PRIx64, evp.id); + ret = queue_envelope_create(&evp); + m_create(p_pony, IMSG_QUEUE_ENVELOPE_SUBMIT, 0, 0, -1); + m_add_id(p_pony, reqid); + if (ret == 0) + m_add_int(p_pony, 0); + else { + m_add_int(p_pony, 1); + m_add_evpid(p_pony, evp.id); + } + m_close(p_pony); + if (ret) { + m_create(p_scheduler, + IMSG_QUEUE_ENVELOPE_SUBMIT, 0, 0, -1); + m_add_envelope(p_scheduler, &evp); + m_close(p_scheduler); + } + return; + + case IMSG_LKA_ENVELOPE_COMMIT: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_end(&m); + m_create(p_pony, IMSG_QUEUE_ENVELOPE_COMMIT, 0, 0, -1); + m_add_id(p_pony, reqid); + m_add_int(p_pony, 1); + m_close(p_pony); + return; + + case IMSG_SCHED_ENVELOPE_REMOVE: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_end(&m); + + m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_ACK, 0, 0, -1); + m_add_evpid(p_scheduler, evpid); + m_close(p_scheduler); + + /* already removed by scheduler */ + if (queue_envelope_load(evpid, &evp) == 0) + return; + + queue_log(&evp, "Remove", "Removed by administrator"); + queue_envelope_delete(evpid); + return; + + case IMSG_SCHED_ENVELOPE_EXPIRE: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_end(&m); + + m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_ACK, 0, 0, -1); + m_add_evpid(p_scheduler, evpid); + m_close(p_scheduler); + + /* already removed by scheduler*/ + if (queue_envelope_load(evpid, &evp) == 0) + return; + + bounce.type = B_FAILED; + envelope_set_errormsg(&evp, "Envelope expired"); + envelope_set_esc_class(&evp, ESC_STATUS_TEMPFAIL); + envelope_set_esc_code(&evp, ESC_DELIVERY_TIME_EXPIRED); + queue_bounce(&evp, &bounce); + queue_log(&evp, "Expire", "Envelope expired"); + queue_envelope_delete(evpid); + return; + + case IMSG_SCHED_ENVELOPE_BOUNCE: + CHECK_IMSG_DATA_SIZE(imsg, sizeof *req_bounce); + req_bounce = imsg->data; + evpid = req_bounce->evpid; + + if (queue_envelope_load(evpid, &evp) == 0) { + log_warnx("queue: bounce: failed to load envelope"); + m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_REMOVE, 0, 0, -1); + m_add_evpid(p_scheduler, evpid); + m_add_u32(p_scheduler, 0); /* not in-flight */ + m_close(p_scheduler); + return; + } + queue_bounce(&evp, &req_bounce->bounce); + evp.lastbounce = req_bounce->timestamp; + if (!queue_envelope_update(&evp)) + log_warnx("warn: could not update envelope %016"PRIx64, evpid); + return; + + case IMSG_SCHED_ENVELOPE_DELIVER: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_end(&m); + if (queue_envelope_load(evpid, &evp) == 0) { + log_warnx("queue: deliver: failed to load envelope"); + m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_REMOVE, 0, 0, -1); + m_add_evpid(p_scheduler, evpid); + m_add_u32(p_scheduler, 1); /* in-flight */ + m_close(p_scheduler); + return; + } + evp.lasttry = time(NULL); + m_create(p_pony, IMSG_QUEUE_DELIVER, 0, 0, -1); + m_add_envelope(p_pony, &evp); + m_close(p_pony); + return; + + case IMSG_SCHED_ENVELOPE_INJECT: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_end(&m); + bounce_add(evpid); + return; + + case IMSG_SCHED_ENVELOPE_TRANSFER: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_end(&m); + if (queue_envelope_load(evpid, &evp) == 0) { + log_warnx("queue: failed to load envelope"); + m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_REMOVE, 0, 0, -1); + m_add_evpid(p_scheduler, evpid); + m_add_u32(p_scheduler, 1); /* in-flight */ + m_close(p_scheduler); + return; + } + evp.lasttry = time(NULL); + m_create(p_pony, IMSG_QUEUE_TRANSFER, 0, 0, -1); + m_add_envelope(p_pony, &evp); + m_close(p_pony); + return; + + case IMSG_CTL_LIST_ENVELOPES: + if (imsg->hdr.len == sizeof imsg->hdr) { + m_forward(p_control, imsg); + return; + } + + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_get_int(&m, &flags); + m_get_time(&m, &nexttry); + m_end(&m); + + if (queue_envelope_load(evpid, &evp) == 0) + return; /* Envelope is gone, drop it */ + + /* + * XXX consistency: The envelope might already be on + * its way back to the scheduler. We need to detect + * this properly and report that state. + */ + if (flags & EF_INFLIGHT) { + /* + * Not exactly correct but pretty close: The + * value is not recorded on the envelope unless + * a tempfail occurs. + */ + evp.lasttry = nexttry; + } + + m_create(p_control, IMSG_CTL_LIST_ENVELOPES, + imsg->hdr.peerid, 0, -1); + m_add_int(p_control, flags); + m_add_time(p_control, nexttry); + m_add_envelope(p_control, &evp); + m_close(p_control); + return; + + case IMSG_MDA_OPEN_MESSAGE: + case IMSG_MTA_OPEN_MESSAGE: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_msgid(&m, &msgid); + m_end(&m); + fd = queue_message_fd_r(msgid); + m_create(p, imsg->hdr.type, 0, 0, fd); + m_add_id(p, reqid); + m_close(p); + return; + + case IMSG_MDA_DELIVERY_OK: + case IMSG_MTA_DELIVERY_OK: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + if (imsg->hdr.type == IMSG_MTA_DELIVERY_OK) + m_get_int(&m, &mta_ext); + m_end(&m); + if (queue_envelope_load(evpid, &evp) == 0) { + log_warn("queue: dsn: failed to load envelope"); + return; + } + if (evp.dsn_notify & DSN_SUCCESS) { + bounce.type = B_DELIVERED; + bounce.dsn_ret = evp.dsn_ret; + envelope_set_esc_class(&evp, ESC_STATUS_OK); + if (imsg->hdr.type == IMSG_MDA_DELIVERY_OK) + queue_bounce(&evp, &bounce); + else if (imsg->hdr.type == IMSG_MTA_DELIVERY_OK && + (mta_ext & MTA_EXT_DSN) == 0) { + bounce.mta_without_dsn = 1; + queue_bounce(&evp, &bounce); + } + } + queue_envelope_delete(evpid); + m_create(p_scheduler, IMSG_QUEUE_DELIVERY_OK, 0, 0, -1); + m_add_evpid(p_scheduler, evpid); + m_close(p_scheduler); + return; + + case IMSG_MDA_DELIVERY_TEMPFAIL: + case IMSG_MTA_DELIVERY_TEMPFAIL: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_get_string(&m, &reason); + m_get_int(&m, &code); + m_end(&m); + if (queue_envelope_load(evpid, &evp) == 0) { + log_warnx("queue: tempfail: failed to load envelope"); + m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_REMOVE, 0, 0, -1); + m_add_evpid(p_scheduler, evpid); + m_add_u32(p_scheduler, 1); /* in-flight */ + m_close(p_scheduler); + return; + } + envelope_set_errormsg(&evp, "%s", reason); + envelope_set_esc_class(&evp, ESC_STATUS_TEMPFAIL); + envelope_set_esc_code(&evp, code); + evp.retry++; + if (!queue_envelope_update(&evp)) + log_warnx("warn: could not update envelope %016"PRIx64, evpid); + m_create(p_scheduler, IMSG_QUEUE_DELIVERY_TEMPFAIL, 0, 0, -1); + m_add_envelope(p_scheduler, &evp); + m_close(p_scheduler); + return; + + case IMSG_MDA_DELIVERY_PERMFAIL: + case IMSG_MTA_DELIVERY_PERMFAIL: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_get_string(&m, &reason); + m_get_int(&m, &code); + m_end(&m); + if (queue_envelope_load(evpid, &evp) == 0) { + log_warnx("queue: permfail: failed to load envelope"); + m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_REMOVE, 0, 0, -1); + m_add_evpid(p_scheduler, evpid); + m_add_u32(p_scheduler, 1); /* in-flight */ + m_close(p_scheduler); + return; + } + bounce.type = B_FAILED; + envelope_set_errormsg(&evp, "%s", reason); + envelope_set_esc_class(&evp, ESC_STATUS_PERMFAIL); + envelope_set_esc_code(&evp, code); + queue_bounce(&evp, &bounce); + queue_envelope_delete(evpid); + m_create(p_scheduler, IMSG_QUEUE_DELIVERY_PERMFAIL, 0, 0, -1); + m_add_evpid(p_scheduler, evpid); + m_close(p_scheduler); + return; + + case IMSG_MDA_DELIVERY_LOOP: + case IMSG_MTA_DELIVERY_LOOP: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_end(&m); + if (queue_envelope_load(evpid, &evp) == 0) { + log_warnx("queue: loop: failed to load envelope"); + m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_REMOVE, 0, 0, -1); + m_add_evpid(p_scheduler, evpid); + m_add_u32(p_scheduler, 1); /* in-flight */ + m_close(p_scheduler); + return; + } + envelope_set_errormsg(&evp, "%s", "Loop detected"); + envelope_set_esc_class(&evp, ESC_STATUS_TEMPFAIL); + envelope_set_esc_code(&evp, ESC_ROUTING_LOOP_DETECTED); + bounce.type = B_FAILED; + queue_bounce(&evp, &bounce); + queue_envelope_delete(evp.id); + m_create(p_scheduler, IMSG_QUEUE_DELIVERY_LOOP, 0, 0, -1); + m_add_evpid(p_scheduler, evp.id); + m_close(p_scheduler); + return; + + case IMSG_MTA_DELIVERY_HOLD: + case IMSG_MDA_DELIVERY_HOLD: + imsg->hdr.type = IMSG_QUEUE_HOLDQ_HOLD; + m_forward(p_scheduler, imsg); + return; + + case IMSG_MTA_SCHEDULE: + imsg->hdr.type = IMSG_QUEUE_ENVELOPE_SCHEDULE; + m_forward(p_scheduler, imsg); + return; + + case IMSG_MTA_HOLDQ_RELEASE: + case IMSG_MDA_HOLDQ_RELEASE: + m_msg(&m, imsg); + m_get_id(&m, &holdq); + m_get_int(&m, &v); + m_end(&m); + m_create(p_scheduler, IMSG_QUEUE_HOLDQ_RELEASE, 0, 0, -1); + if (imsg->hdr.type == IMSG_MTA_HOLDQ_RELEASE) + m_add_int(p_scheduler, D_MTA); + else + m_add_int(p_scheduler, D_MDA); + m_add_id(p_scheduler, holdq); + m_add_int(p_scheduler, v); + m_close(p_scheduler); + return; + + case IMSG_CTL_PAUSE_MDA: + case IMSG_CTL_PAUSE_MTA: + case IMSG_CTL_RESUME_MDA: + case IMSG_CTL_RESUME_MTA: + m_forward(p_scheduler, imsg); + return; + + case IMSG_CTL_VERBOSE: + m_msg(&m, imsg); + m_get_int(&m, &v); + m_end(&m); + log_trace_verbose(v); + return; + + case IMSG_CTL_PROFILE: + m_msg(&m, imsg); + m_get_int(&m, &v); + m_end(&m); + profiling = v; + return; + + case IMSG_CTL_DISCOVER_EVPID: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_end(&m); + if (queue_envelope_load(evpid, &evp) == 0) { + log_warnx("queue: discover: failed to load " + "envelope %016" PRIx64, evpid); + n_evp = 0; + m_compose(p_control, imsg->hdr.type, + imsg->hdr.peerid, 0, -1, + &n_evp, sizeof n_evp); + return; + } + + m_create(p_scheduler, IMSG_QUEUE_DISCOVER_EVPID, + 0, 0, -1); + m_add_envelope(p_scheduler, &evp); + m_close(p_scheduler); + + m_create(p_scheduler, IMSG_QUEUE_DISCOVER_MSGID, + 0, 0, -1); + m_add_msgid(p_scheduler, evpid_to_msgid(evpid)); + m_close(p_scheduler); + n_evp = 1; + m_compose(p_control, imsg->hdr.type, imsg->hdr.peerid, + 0, -1, &n_evp, sizeof n_evp); + return; + + case IMSG_CTL_DISCOVER_MSGID: + m_msg(&m, imsg); + m_get_msgid(&m, &msgid); + m_end(&m); + /* handle concurrent walk requests */ + wi = xcalloc(1, sizeof *wi); + wi->msgid = msgid; + wi->peerid = imsg->hdr.peerid; + evtimer_set(&wi->ev, queue_msgid_walk, wi); + tv.tv_sec = 0; + tv.tv_usec = 10; + evtimer_add(&wi->ev, &tv); + return; + } + + errx(1, "queue_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); +} + +static void +queue_msgid_walk(int fd, short event, void *arg) +{ + struct envelope evp; + struct timeval tv; + struct msg_walkinfo *wi = arg; + int r; + + r = queue_message_walk(&evp, wi->msgid, &wi->done, &wi->data); + if (r == -1) { + if (wi->n_evp) { + m_create(p_scheduler, IMSG_QUEUE_DISCOVER_MSGID, + 0, 0, -1); + m_add_msgid(p_scheduler, wi->msgid); + m_close(p_scheduler); + } + + m_compose(p_control, IMSG_CTL_DISCOVER_MSGID, wi->peerid, 0, -1, + &wi->n_evp, sizeof wi->n_evp); + evtimer_del(&wi->ev); + free(wi); + return; + } + + if (r) { + m_create(p_scheduler, IMSG_QUEUE_DISCOVER_EVPID, 0, 0, -1); + m_add_envelope(p_scheduler, &evp); + m_close(p_scheduler); + wi->n_evp += 1; + } + + tv.tv_sec = 0; + tv.tv_usec = 10; + evtimer_set(&wi->ev, queue_msgid_walk, wi); + evtimer_add(&wi->ev, &tv); +} + +static void +queue_bounce(struct envelope *e, struct delivery_bounce *d) +{ + struct envelope b; + + b = *e; + b.type = D_BOUNCE; + b.agent.bounce = *d; + b.retry = 0; + b.lasttry = 0; + b.creation = time(NULL); + b.ttl = 3600 * 24 * 7; + + if (e->dsn_notify & DSN_NEVER) + return; + + if (b.id == 0) + log_warnx("warn: queue_bounce: evpid=0"); + if (evpid_to_msgid(b.id) == 0) + log_warnx("warn: queue_bounce: msgid=0, evpid=%016"PRIx64, + b.id); + if (e->type == D_BOUNCE) { + log_warnx("warn: queue: double bounce!"); + } else if (e->sender.user[0] == '\0') { + log_warnx("warn: queue: no return path!"); + } else if (!queue_envelope_create(&b)) { + log_warnx("warn: queue: cannot bounce!"); + } else { + log_debug("debug: queue: bouncing evp:%016" PRIx64 + " as evp:%016" PRIx64, e->id, b.id); + + m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_SUBMIT, 0, 0, -1); + m_add_envelope(p_scheduler, &b); + m_close(p_scheduler); + + m_create(p_scheduler, IMSG_QUEUE_MESSAGE_COMMIT, 0, 0, -1); + m_add_msgid(p_scheduler, evpid_to_msgid(b.id)); + m_close(p_scheduler); + + stat_increment("queue.bounce", 1); + } +} + +static void +queue_shutdown(void) +{ + log_debug("debug: queue agent exiting"); + queue_close(); + _exit(0); +} + +int +queue(void) +{ + struct passwd *pw; + struct timeval tv; + struct event ev_qload; + + purge_config(PURGE_EVERYTHING & ~PURGE_DISPATCHERS); + + if ((pw = getpwnam(SMTPD_QUEUE_USER)) == NULL) + if ((pw = getpwnam(SMTPD_USER)) == NULL) + fatalx("unknown user " SMTPD_USER); + + env->sc_queue_flags |= QUEUE_EVPCACHE; + env->sc_queue_evpcache_size = 1024; + + if (chroot(PATH_SPOOL) == -1) + fatal("queue: chroot"); + if (chdir("/") == -1) + fatal("queue: chdir(\"/\")"); + + config_process(PROC_QUEUE); + + if (env->sc_queue_flags & QUEUE_COMPRESSION) + log_info("queue: queue compression enabled"); + + if (env->sc_queue_key) { + if (!crypto_setup(env->sc_queue_key, strlen(env->sc_queue_key))) + fatalx("crypto_setup: invalid key for queue encryption"); + log_info("queue: queue encryption enabled"); + } + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("queue: cannot drop privileges"); + + imsg_callback = queue_imsg; + event_init(); + + signal(SIGINT, SIG_IGN); + signal(SIGTERM, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + + config_peer(PROC_PARENT); + config_peer(PROC_CONTROL); + config_peer(PROC_LKA); + config_peer(PROC_SCHEDULER); + config_peer(PROC_PONY); + + /* setup queue loading task */ + evtimer_set(&ev_qload, queue_timeout, &ev_qload); + tv.tv_sec = 0; + tv.tv_usec = 10; + evtimer_add(&ev_qload, &tv); + +#if HAVE_PLEDGE + if (pledge("stdio rpath wpath cpath flock recvfd sendfd", NULL) == -1) + err(1, "pledge"); +#endif + + event_dispatch(); + fatalx("exited event loop"); + + return (0); +} + +static void +queue_timeout(int fd, short event, void *p) +{ + static uint32_t msgid = 0; + struct envelope evp; + struct event *ev = p; + struct timeval tv; + int r; + + r = queue_envelope_walk(&evp); + if (r == -1) { + if (msgid) { + m_create(p_scheduler, IMSG_QUEUE_MESSAGE_COMMIT, + 0, 0, -1); + m_add_msgid(p_scheduler, msgid); + m_close(p_scheduler); + } + log_debug("debug: queue: done loading queue into scheduler"); + return; + } + + if (r) { + if (msgid && evpid_to_msgid(evp.id) != msgid) { + m_create(p_scheduler, IMSG_QUEUE_MESSAGE_COMMIT, + 0, 0, -1); + m_add_msgid(p_scheduler, msgid); + m_close(p_scheduler); + } + msgid = evpid_to_msgid(evp.id); + m_create(p_scheduler, IMSG_QUEUE_ENVELOPE_SUBMIT, 0, 0, -1); + m_add_envelope(p_scheduler, &evp); + m_close(p_scheduler); + } + + tv.tv_sec = 0; + tv.tv_usec = 10; + evtimer_add(ev, &tv); +} + +static void +queue_log(const struct envelope *e, const char *prefix, const char *status) +{ + char rcpt[LINE_MAX]; + + (void)strlcpy(rcpt, "-", sizeof rcpt); + if (strcmp(e->rcpt.user, e->dest.user) || + strcmp(e->rcpt.domain, e->dest.domain)) + (void)snprintf(rcpt, sizeof rcpt, "%s@%s", + e->rcpt.user, e->rcpt.domain); + + log_info("%s: %s for %016" PRIx64 ": from=<%s@%s>, to=<%s@%s>, " + "rcpt=<%s>, delay=%s, stat=%s", + e->type == D_MDA ? "delivery" : "relay", + prefix, + e->id, e->sender.user, e->sender.domain, + e->dest.user, e->dest.domain, + rcpt, + duration_to_text(time(NULL) - e->creation), + status); +} diff --git a/foobar/portable/smtpd/queue_backend.c b/foobar/portable/smtpd/queue_backend.c new file mode 100644 index 00000000..fa945f47 --- /dev/null +++ b/foobar/portable/smtpd/queue_backend.c @@ -0,0 +1,806 @@ +/* $OpenBSD: queue_backend.c,v 1.66 2020/04/22 11:35:34 eric Exp $ */ + +/* + * Copyright (c) 2011 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <grp.h> +#include <imsg.h> +#include <limits.h> +#include <inttypes.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "smtpd.h" +#include "log.h" + +static const char* envelope_validate(struct envelope *); + +extern struct queue_backend queue_backend_fs; +extern struct queue_backend queue_backend_null; +extern struct queue_backend queue_backend_proc; +extern struct queue_backend queue_backend_ram; + +static void queue_envelope_cache_add(struct envelope *); +static void queue_envelope_cache_update(struct envelope *); +static void queue_envelope_cache_del(uint64_t evpid); + +TAILQ_HEAD(evplst, envelope); + +static struct tree evpcache_tree; +static struct evplst evpcache_list; +static struct queue_backend *backend; + +static int (*handler_close)(void); +static int (*handler_message_create)(uint32_t *); +static int (*handler_message_commit)(uint32_t, const char*); +static int (*handler_message_delete)(uint32_t); +static int (*handler_message_fd_r)(uint32_t); +static int (*handler_envelope_create)(uint32_t, const char *, size_t, uint64_t *); +static int (*handler_envelope_delete)(uint64_t); +static int (*handler_envelope_update)(uint64_t, const char *, size_t); +static int (*handler_envelope_load)(uint64_t, char *, size_t); +static int (*handler_envelope_walk)(uint64_t *, char *, size_t); +static int (*handler_message_walk)(uint64_t *, char *, size_t, + uint32_t, int *, void **); + +#ifdef QUEUE_PROFILING + +static struct { + struct timespec t0; + const char *name; +} profile; + +static inline void profile_enter(const char *name) +{ + if ((profiling & PROFILE_QUEUE) == 0) + return; + + profile.name = name; + clock_gettime(CLOCK_MONOTONIC, &profile.t0); +} + +static inline void profile_leave(void) +{ + struct timespec t1, dt; + + if ((profiling & PROFILE_QUEUE) == 0) + return; + + clock_gettime(CLOCK_MONOTONIC, &t1); + timespecsub(&t1, &profile.t0, &dt); + log_debug("profile-queue: %s %lld.%09ld", profile.name, + (long long)dt.tv_sec, dt.tv_nsec); +} +#else +#define profile_enter(x) do {} while (0) +#define profile_leave() do {} while (0) +#endif + +static int +queue_message_path(uint32_t msgid, char *buf, size_t len) +{ + return bsnprintf(buf, len, "%s/%08"PRIx32, PATH_TEMPORARY, msgid); +} + +int +queue_init(const char *name, int server) +{ + struct passwd *pwq; + struct group *gr; + int r; + + pwq = getpwnam(SMTPD_QUEUE_USER); + if (pwq == NULL) + errx(1, "unknown user %s", SMTPD_QUEUE_USER); + + gr = getgrnam(SMTPD_QUEUE_GROUP); + if (gr == NULL) + errx(1, "unknown group %s", SMTPD_QUEUE_GROUP); + + tree_init(&evpcache_tree); + TAILQ_INIT(&evpcache_list); + + if (!strcmp(name, "fs")) + backend = &queue_backend_fs; + else if (!strcmp(name, "null")) + backend = &queue_backend_null; + else if (!strcmp(name, "ram")) + backend = &queue_backend_ram; + else + backend = &queue_backend_proc; + + if (server) { + if (ckdir(PATH_SPOOL, 0711, 0, 0, 1) == 0) + errx(1, "error in spool directory setup"); + if (ckdir(PATH_SPOOL PATH_OFFLINE, 0770, 0, gr->gr_gid, 1) == 0) + errx(1, "error in offline directory setup"); + if (ckdir(PATH_SPOOL PATH_PURGE, 0700, pwq->pw_uid, 0, 1) == 0) + errx(1, "error in purge directory setup"); + + mvpurge(PATH_SPOOL PATH_TEMPORARY, PATH_SPOOL PATH_PURGE); + + if (ckdir(PATH_SPOOL PATH_TEMPORARY, 0700, pwq->pw_uid, 0, 1) == 0) + errx(1, "error in purge directory setup"); + } + + r = backend->init(pwq, server, name); + + log_trace(TRACE_QUEUE, "queue-backend: queue_init(%d) -> %d", server, r); + + return (r); +} + +int +queue_close(void) +{ + if (handler_close) + return (handler_close()); + + return (1); +} + +int +queue_message_create(uint32_t *msgid) +{ + int r; + + profile_enter("queue_message_create"); + r = handler_message_create(msgid); + profile_leave(); + + log_trace(TRACE_QUEUE, + "queue-backend: queue_message_create() -> %d (%08"PRIx32")", + r, *msgid); + + return (r); +} + +int +queue_message_delete(uint32_t msgid) +{ + char msgpath[PATH_MAX]; + uint64_t evpid; + void *iter; + int r; + + profile_enter("queue_message_delete"); + r = handler_message_delete(msgid); + profile_leave(); + + /* in case the message is incoming */ + queue_message_path(msgid, msgpath, sizeof(msgpath)); + unlink(msgpath); + + /* remove remaining envelopes from the cache if any (on rollback) */ + evpid = msgid_to_evpid(msgid); + for (;;) { + iter = NULL; + if (!tree_iterfrom(&evpcache_tree, &iter, evpid, &evpid, NULL)) + break; + if (evpid_to_msgid(evpid) != msgid) + break; + queue_envelope_cache_del(evpid); + } + + log_trace(TRACE_QUEUE, + "queue-backend: queue_message_delete(%08"PRIx32") -> %d", msgid, r); + + return (r); +} + +int +queue_message_commit(uint32_t msgid) +{ + int r; + char msgpath[PATH_MAX]; + char tmppath[PATH_MAX]; + FILE *ifp = NULL; + FILE *ofp = NULL; + + profile_enter("queue_message_commit"); + + queue_message_path(msgid, msgpath, sizeof(msgpath)); + + if (env->sc_queue_flags & QUEUE_COMPRESSION) { + bsnprintf(tmppath, sizeof tmppath, "%s.comp", msgpath); + ifp = fopen(msgpath, "r"); + ofp = fopen(tmppath, "w+"); + if (ifp == NULL || ofp == NULL) + goto err; + if (!compress_file(ifp, ofp)) + goto err; + fclose(ifp); + fclose(ofp); + ifp = NULL; + ofp = NULL; + + if (rename(tmppath, msgpath) == -1) { + if (errno == ENOSPC) + return (0); + unlink(tmppath); + log_warn("rename"); + return (0); + } + } + + if (env->sc_queue_flags & QUEUE_ENCRYPTION) { + bsnprintf(tmppath, sizeof tmppath, "%s.enc", msgpath); + ifp = fopen(msgpath, "r"); + ofp = fopen(tmppath, "w+"); + if (ifp == NULL || ofp == NULL) + goto err; + if (!crypto_encrypt_file(ifp, ofp)) + goto err; + fclose(ifp); + fclose(ofp); + ifp = NULL; + ofp = NULL; + + if (rename(tmppath, msgpath) == -1) { + if (errno == ENOSPC) + return (0); + unlink(tmppath); + log_warn("rename"); + return (0); + } + } + + r = handler_message_commit(msgid, msgpath); + profile_leave(); + + /* in case it's not done by the backend */ + unlink(msgpath); + + log_trace(TRACE_QUEUE, + "queue-backend: queue_message_commit(%08"PRIx32") -> %d", + msgid, r); + + return (r); + +err: + if (ifp) + fclose(ifp); + if (ofp) + fclose(ofp); + return 0; +} + +int +queue_message_fd_r(uint32_t msgid) +{ + int fdin = -1, fdout = -1, fd = -1; + FILE *ifp = NULL; + FILE *ofp = NULL; + + profile_enter("queue_message_fd_r"); + fdin = handler_message_fd_r(msgid); + profile_leave(); + + log_trace(TRACE_QUEUE, + "queue-backend: queue_message_fd_r(%08"PRIx32") -> %d", msgid, fdin); + + if (fdin == -1) + return (-1); + + if (env->sc_queue_flags & QUEUE_ENCRYPTION) { + if ((fdout = mktmpfile()) == -1) + goto err; + if ((fd = dup(fdout)) == -1) + goto err; + if ((ifp = fdopen(fdin, "r")) == NULL) + goto err; + fdin = fd; + fd = -1; + if ((ofp = fdopen(fdout, "w+")) == NULL) + goto err; + + if (!crypto_decrypt_file(ifp, ofp)) + goto err; + + fclose(ifp); + ifp = NULL; + fclose(ofp); + ofp = NULL; + lseek(fdin, SEEK_SET, 0); + } + + if (env->sc_queue_flags & QUEUE_COMPRESSION) { + if ((fdout = mktmpfile()) == -1) + goto err; + if ((fd = dup(fdout)) == -1) + goto err; + if ((ifp = fdopen(fdin, "r")) == NULL) + goto err; + fdin = fd; + fd = -1; + if ((ofp = fdopen(fdout, "w+")) == NULL) + goto err; + + if (!uncompress_file(ifp, ofp)) + goto err; + + fclose(ifp); + ifp = NULL; + fclose(ofp); + ofp = NULL; + lseek(fdin, SEEK_SET, 0); + } + + return (fdin); + +err: + if (fd != -1) + close(fd); + if (fdin != -1) + close(fdin); + if (fdout != -1) + close(fdout); + if (ifp) + fclose(ifp); + if (ofp) + fclose(ofp); + return -1; +} + +int +queue_message_fd_rw(uint32_t msgid) +{ + char buf[PATH_MAX]; + + queue_message_path(msgid, buf, sizeof(buf)); + + return open(buf, O_RDWR | O_CREAT | O_EXCL, 0600); +} + +static int +queue_envelope_dump_buffer(struct envelope *ep, char *evpbuf, size_t evpbufsize) +{ + char *evp; + size_t evplen; + size_t complen; + char compbuf[sizeof(struct envelope)]; + size_t enclen; + char encbuf[sizeof(struct envelope)]; + + evp = evpbuf; + evplen = envelope_dump_buffer(ep, evpbuf, evpbufsize); + if (evplen == 0) + return (0); + + if (env->sc_queue_flags & QUEUE_COMPRESSION) { + complen = compress_chunk(evp, evplen, compbuf, sizeof compbuf); + if (complen == 0) + return (0); + evp = compbuf; + evplen = complen; + } + + if (env->sc_queue_flags & QUEUE_ENCRYPTION) { + enclen = crypto_encrypt_buffer(evp, evplen, encbuf, sizeof encbuf); + if (enclen == 0) + return (0); + evp = encbuf; + evplen = enclen; + } + + memmove(evpbuf, evp, evplen); + + return (evplen); +} + +static int +queue_envelope_load_buffer(struct envelope *ep, char *evpbuf, size_t evpbufsize) +{ + char *evp; + size_t evplen; + char compbuf[sizeof(struct envelope)]; + size_t complen; + char encbuf[sizeof(struct envelope)]; + size_t enclen; + + evp = evpbuf; + evplen = evpbufsize; + + if (env->sc_queue_flags & QUEUE_ENCRYPTION) { + enclen = crypto_decrypt_buffer(evp, evplen, encbuf, sizeof encbuf); + if (enclen == 0) + return (0); + evp = encbuf; + evplen = enclen; + } + + if (env->sc_queue_flags & QUEUE_COMPRESSION) { + complen = uncompress_chunk(evp, evplen, compbuf, sizeof compbuf); + if (complen == 0) + return (0); + evp = compbuf; + evplen = complen; + } + + return (envelope_load_buffer(ep, evp, evplen)); +} + +static void +queue_envelope_cache_add(struct envelope *e) +{ + struct envelope *cached; + + while (tree_count(&evpcache_tree) >= env->sc_queue_evpcache_size) + queue_envelope_cache_del(TAILQ_LAST(&evpcache_list, evplst)->id); + + cached = xcalloc(1, sizeof *cached); + *cached = *e; + TAILQ_INSERT_HEAD(&evpcache_list, cached, entry); + tree_xset(&evpcache_tree, e->id, cached); + stat_increment("queue.evpcache.size", 1); +} + +static void +queue_envelope_cache_update(struct envelope *e) +{ + struct envelope *cached; + + if ((cached = tree_get(&evpcache_tree, e->id)) == NULL) { + queue_envelope_cache_add(e); + stat_increment("queue.evpcache.update.missed", 1); + } else { + TAILQ_REMOVE(&evpcache_list, cached, entry); + *cached = *e; + TAILQ_INSERT_HEAD(&evpcache_list, cached, entry); + stat_increment("queue.evpcache.update.hit", 1); + } +} + +static void +queue_envelope_cache_del(uint64_t evpid) +{ + struct envelope *cached; + + if ((cached = tree_pop(&evpcache_tree, evpid)) == NULL) + return; + + TAILQ_REMOVE(&evpcache_list, cached, entry); + free(cached); + stat_decrement("queue.evpcache.size", 1); +} + +int +queue_envelope_create(struct envelope *ep) +{ + int r; + char evpbuf[sizeof(struct envelope)]; + size_t evplen; + uint64_t evpid; + uint32_t msgid; + + ep->creation = time(NULL); + evplen = queue_envelope_dump_buffer(ep, evpbuf, sizeof evpbuf); + if (evplen == 0) + return (0); + + evpid = ep->id; + msgid = evpid_to_msgid(evpid); + + profile_enter("queue_envelope_create"); + r = handler_envelope_create(msgid, evpbuf, evplen, &ep->id); + profile_leave(); + + log_trace(TRACE_QUEUE, + "queue-backend: queue_envelope_create(%016"PRIx64", %zu) -> %d (%016"PRIx64")", + evpid, evplen, r, ep->id); + + if (!r) { + ep->creation = 0; + ep->id = 0; + } + + if (r && env->sc_queue_flags & QUEUE_EVPCACHE) + queue_envelope_cache_add(ep); + + return (r); +} + +int +queue_envelope_delete(uint64_t evpid) +{ + int r; + + if (env->sc_queue_flags & QUEUE_EVPCACHE) + queue_envelope_cache_del(evpid); + + profile_enter("queue_envelope_delete"); + r = handler_envelope_delete(evpid); + profile_leave(); + + log_trace(TRACE_QUEUE, + "queue-backend: queue_envelope_delete(%016"PRIx64") -> %d", + evpid, r); + + return (r); +} + +int +queue_envelope_load(uint64_t evpid, struct envelope *ep) +{ + const char *e; + char evpbuf[sizeof(struct envelope)]; + size_t evplen; + struct envelope *cached; + + if ((env->sc_queue_flags & QUEUE_EVPCACHE) && + (cached = tree_get(&evpcache_tree, evpid))) { + *ep = *cached; + stat_increment("queue.evpcache.load.hit", 1); + return (1); + } + + ep->id = evpid; + profile_enter("queue_envelope_load"); + evplen = handler_envelope_load(ep->id, evpbuf, sizeof evpbuf); + profile_leave(); + + log_trace(TRACE_QUEUE, + "queue-backend: queue_envelope_load(%016"PRIx64") -> %zu", + evpid, evplen); + + if (evplen == 0) + return (0); + + if (queue_envelope_load_buffer(ep, evpbuf, evplen)) { + if ((e = envelope_validate(ep)) == NULL) { + ep->id = evpid; + if (env->sc_queue_flags & QUEUE_EVPCACHE) { + queue_envelope_cache_add(ep); + stat_increment("queue.evpcache.load.missed", 1); + } + return (1); + } + log_warnx("warn: invalid envelope %016" PRIx64 ": %s", + evpid, e); + } + return (0); +} + +int +queue_envelope_update(struct envelope *ep) +{ + char evpbuf[sizeof(struct envelope)]; + size_t evplen; + int r; + + evplen = queue_envelope_dump_buffer(ep, evpbuf, sizeof evpbuf); + if (evplen == 0) + return (0); + + profile_enter("queue_envelope_update"); + r = handler_envelope_update(ep->id, evpbuf, evplen); + profile_leave(); + + if (r && env->sc_queue_flags & QUEUE_EVPCACHE) + queue_envelope_cache_update(ep); + + log_trace(TRACE_QUEUE, + "queue-backend: queue_envelope_update(%016"PRIx64") -> %d", + ep->id, r); + + return (r); +} + +int +queue_message_walk(struct envelope *ep, uint32_t msgid, int *done, void **data) +{ + char evpbuf[sizeof(struct envelope)]; + uint64_t evpid; + int r; + const char *e; + + profile_enter("queue_message_walk"); + r = handler_message_walk(&evpid, evpbuf, sizeof evpbuf, + msgid, done, data); + profile_leave(); + + log_trace(TRACE_QUEUE, + "queue-backend: queue_message_walk() -> %d (%016"PRIx64")", + r, evpid); + + if (r == -1) + return (r); + + if (r && queue_envelope_load_buffer(ep, evpbuf, (size_t)r)) { + if ((e = envelope_validate(ep)) == NULL) { + ep->id = evpid; + /* + * do not cache the envelope here, while discovering + * envelopes one could re-run discover on already + * scheduled envelopes which leads to triggering of + * strict checks in caching. Envelopes could anyway + * be loaded from backend if it isn't cached. + */ + return (1); + } + log_warnx("warn: invalid envelope %016" PRIx64 ": %s", + evpid, e); + } + return (0); +} + +int +queue_envelope_walk(struct envelope *ep) +{ + const char *e; + uint64_t evpid; + char evpbuf[sizeof(struct envelope)]; + int r; + + profile_enter("queue_envelope_walk"); + r = handler_envelope_walk(&evpid, evpbuf, sizeof evpbuf); + profile_leave(); + + log_trace(TRACE_QUEUE, + "queue-backend: queue_envelope_walk() -> %d (%016"PRIx64")", + r, evpid); + + if (r == -1) + return (r); + + if (r && queue_envelope_load_buffer(ep, evpbuf, (size_t)r)) { + if ((e = envelope_validate(ep)) == NULL) { + ep->id = evpid; + if (env->sc_queue_flags & QUEUE_EVPCACHE) + queue_envelope_cache_add(ep); + return (1); + } + log_warnx("warn: invalid envelope %016" PRIx64 ": %s", + evpid, e); + } + return (0); +} + +uint32_t +queue_generate_msgid(void) +{ + uint32_t msgid; + + while ((msgid = arc4random()) == 0) + ; + + return msgid; +} + +uint64_t +queue_generate_evpid(uint32_t msgid) +{ + uint32_t rnd; + uint64_t evpid; + + while ((rnd = arc4random()) == 0) + ; + + evpid = msgid; + evpid <<= 32; + evpid |= rnd; + + return evpid; +} + +static const char* +envelope_validate(struct envelope *ep) +{ + if (ep->version != SMTPD_ENVELOPE_VERSION) + return "version mismatch"; + + if (memchr(ep->helo, '\0', sizeof(ep->helo)) == NULL) + return "invalid helo"; + if (ep->helo[0] == '\0') + return "empty helo"; + + if (memchr(ep->hostname, '\0', sizeof(ep->hostname)) == NULL) + return "invalid hostname"; + if (ep->hostname[0] == '\0') + return "empty hostname"; + + if (memchr(ep->errorline, '\0', sizeof(ep->errorline)) == NULL) + return "invalid error line"; + + if (dict_get(env->sc_dispatchers, ep->dispatcher) == NULL) + return "unknown dispatcher"; + + return NULL; +} + +void +queue_api_on_close(int(*cb)(void)) +{ + handler_close = cb; +} + +void +queue_api_on_message_create(int(*cb)(uint32_t *)) +{ + handler_message_create = cb; +} + +void +queue_api_on_message_commit(int(*cb)(uint32_t, const char *)) +{ + handler_message_commit = cb; +} + +void +queue_api_on_message_delete(int(*cb)(uint32_t)) +{ + handler_message_delete = cb; +} + +void +queue_api_on_message_fd_r(int(*cb)(uint32_t)) +{ + handler_message_fd_r = cb; +} + +void +queue_api_on_envelope_create(int(*cb)(uint32_t, const char *, size_t, uint64_t *)) +{ + handler_envelope_create = cb; +} + +void +queue_api_on_envelope_delete(int(*cb)(uint64_t)) +{ + handler_envelope_delete = cb; +} + +void +queue_api_on_envelope_update(int(*cb)(uint64_t, const char *, size_t)) +{ + handler_envelope_update = cb; +} + +void +queue_api_on_envelope_load(int(*cb)(uint64_t, char *, size_t)) +{ + handler_envelope_load = cb; +} + +void +queue_api_on_envelope_walk(int(*cb)(uint64_t *, char *, size_t)) +{ + handler_envelope_walk = cb; +} + +void +queue_api_on_message_walk(int(*cb)(uint64_t *, char *, size_t, + uint32_t, int *, void **)) +{ + handler_message_walk = cb; +} diff --git a/foobar/portable/smtpd/queue_fs.c b/foobar/portable/smtpd/queue_fs.c new file mode 100644 index 00000000..097ba1e2 --- /dev/null +++ b/foobar/portable/smtpd/queue_fs.c @@ -0,0 +1,695 @@ +/* $OpenBSD: queue_fs.c,v 1.20 2020/02/25 17:03:13 millert Exp $ */ + +/* + * Copyright (c) 2011 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#if HAVE_SYS_MOUNT_H +#include <sys/mount.h> +#endif +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/stat.h> +#ifdef HAVE_SYS_STATFS_H +#include <sys/statfs.h> +#endif + +#include <ctype.h> +#include <dirent.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <fts.h> +#include <imsg.h> +#include <inttypes.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "smtpd.h" +#include "log.h" + +#define PATH_QUEUE "/queue" +#define PATH_INCOMING "/incoming" +#define PATH_EVPTMP PATH_INCOMING "/envelope.tmp" +#define PATH_MESSAGE "/message" + +/* percentage of remaining space / inodes required to accept new messages */ +#define MINSPACE 5 +#define MININODES 5 + +struct qwalk { + FTS *fts; + int depth; +}; + +static int fsqueue_check_space(void); +static void fsqueue_envelope_path(uint64_t, char *, size_t); +static void fsqueue_envelope_incoming_path(uint64_t, char *, size_t); +static int fsqueue_envelope_dump(char *, const char *, size_t, int, int); +static void fsqueue_message_path(uint32_t, char *, size_t); +static void fsqueue_message_incoming_path(uint32_t, char *, size_t); +static void *fsqueue_qwalk_new(void); +static int fsqueue_qwalk(void *, uint64_t *); +static void fsqueue_qwalk_close(void *); + +struct tree evpcount; +static struct timespec startup; + +#define REF (int*)0xf00 + +static int +queue_fs_message_create(uint32_t *msgid) +{ + char rootdir[PATH_MAX]; + struct stat sb; + + if (!fsqueue_check_space()) + return 0; + +again: + *msgid = queue_generate_msgid(); + + /* prevent possible collision later when moving to Q_QUEUE */ + fsqueue_message_path(*msgid, rootdir, sizeof(rootdir)); + if (stat(rootdir, &sb) != -1) + goto again; + + /* we hit an unexpected error, temporarily fail */ + if (errno != ENOENT) { + *msgid = 0; + return 0; + } + + fsqueue_message_incoming_path(*msgid, rootdir, sizeof(rootdir)); + if (mkdir(rootdir, 0700) == -1) { + if (errno == EEXIST) + goto again; + + if (errno == ENOSPC) { + *msgid = 0; + return 0; + } + + log_warn("warn: queue-fs: mkdir"); + *msgid = 0; + return 0; + } + + return (1); +} + +static int +queue_fs_message_commit(uint32_t msgid, const char *path) +{ + char incomingdir[PATH_MAX]; + char queuedir[PATH_MAX]; + char msgdir[PATH_MAX]; + char msgpath[PATH_MAX]; + + /* before-first, move the message content in the incoming directory */ + fsqueue_message_incoming_path(msgid, msgpath, sizeof(msgpath)); + if (strlcat(msgpath, PATH_MESSAGE, sizeof(msgpath)) + >= sizeof(msgpath)) + return (0); + if (rename(path, msgpath) == -1) + return (0); + + fsqueue_message_incoming_path(msgid, incomingdir, sizeof(incomingdir)); + fsqueue_message_path(msgid, msgdir, sizeof(msgdir)); + if (strlcpy(queuedir, msgdir, sizeof(queuedir)) + >= sizeof(queuedir)) + return (0); + + /* first attempt to rename */ + if (rename(incomingdir, msgdir) == 0) + return 1; + if (errno == ENOSPC) + return 0; + if (errno != ENOENT) { + log_warn("warn: queue-fs: rename"); + return 0; + } + + /* create the bucket */ + *strrchr(queuedir, '/') = '\0'; + if (mkdir(queuedir, 0700) == -1) { + if (errno == ENOSPC) + return 0; + if (errno != EEXIST) { + log_warn("warn: queue-fs: mkdir"); + return 0; + } + } + + /* rename */ + if (rename(incomingdir, msgdir) == -1) { + if (errno == ENOSPC) + return 0; + log_warn("warn: queue-fs: rename"); + return 0; + } + + return 1; +} + +static int +queue_fs_message_fd_r(uint32_t msgid) +{ + int fd; + char path[PATH_MAX]; + + fsqueue_message_path(msgid, path, sizeof(path)); + if (strlcat(path, PATH_MESSAGE, sizeof(path)) + >= sizeof(path)) + return -1; + + if ((fd = open(path, O_RDONLY)) == -1) { + log_warn("warn: queue-fs: open"); + return -1; + } + + return fd; +} + +static int +queue_fs_message_delete(uint32_t msgid) +{ + char path[PATH_MAX]; + struct stat sb; + + fsqueue_message_incoming_path(msgid, path, sizeof(path)); + if (stat(path, &sb) == -1) + fsqueue_message_path(msgid, path, sizeof(path)); + + if (rmtree(path, 0) == -1) + log_warn("warn: queue-fs: rmtree"); + + tree_pop(&evpcount, msgid); + + return 1; +} + +static int +queue_fs_envelope_create(uint32_t msgid, const char *buf, size_t len, + uint64_t *evpid) +{ + char path[PATH_MAX]; + int queued = 0, i, r = 0, *n; + struct stat sb; + + if (msgid == 0) { + log_warnx("warn: queue-fs: msgid=0, evpid=%016"PRIx64, *evpid); + goto done; + } + + fsqueue_message_incoming_path(msgid, path, sizeof(path)); + if (stat(path, &sb) == -1) + queued = 1; + + for (i = 0; i < 20; i ++) { + *evpid = queue_generate_evpid(msgid); + if (queued) + fsqueue_envelope_path(*evpid, path, sizeof(path)); + else + fsqueue_envelope_incoming_path(*evpid, path, + sizeof(path)); + + if ((r = fsqueue_envelope_dump(path, buf, len, 0, 0)) != 0) + goto done; + } + r = 0; + log_warnx("warn: queue-fs: could not allocate evpid"); + +done: + if (r) { + n = tree_pop(&evpcount, msgid); + if (n == NULL) + n = REF; + n += 1; + tree_xset(&evpcount, msgid, n); + } + return (r); +} + +static int +queue_fs_envelope_load(uint64_t evpid, char *buf, size_t len) +{ + char pathname[PATH_MAX]; + FILE *fp; + size_t r; + + fsqueue_envelope_path(evpid, pathname, sizeof(pathname)); + + fp = fopen(pathname, "r"); + if (fp == NULL) { + if (errno != ENOENT && errno != ENFILE) + log_warn("warn: queue-fs: fopen"); + return 0; + } + + r = fread(buf, 1, len, fp); + if (r) { + if (r == len) { + log_warn("warn: queue-fs: too large"); + r = 0; + } + else + buf[r] = '\0'; + } + fclose(fp); + + return (r); +} + +static int +queue_fs_envelope_update(uint64_t evpid, const char *buf, size_t len) +{ + char dest[PATH_MAX]; + + fsqueue_envelope_path(evpid, dest, sizeof(dest)); + + return (fsqueue_envelope_dump(dest, buf, len, 1, 1)); +} + +static int +queue_fs_envelope_delete(uint64_t evpid) +{ + char pathname[PATH_MAX]; + uint32_t msgid; + int *n; + + fsqueue_envelope_path(evpid, pathname, sizeof(pathname)); + if (unlink(pathname) == -1) + if (errno != ENOENT) + return 0; + + msgid = evpid_to_msgid(evpid); + n = tree_pop(&evpcount, msgid); + n -= 1; + + if (n - REF == 0) + queue_fs_message_delete(msgid); + else + tree_xset(&evpcount, msgid, n); + + return (1); +} + +static int +queue_fs_message_walk(uint64_t *evpid, char *buf, size_t len, + uint32_t msgid, int *done, void **data) +{ + struct dirent *dp; + DIR *dir = *data; + char path[PATH_MAX]; + char msgid_str[9]; + char *tmp; + int r, *n; + + if (*done) + return (-1); + + if (!bsnprintf(path, sizeof path, "%s/%02x/%08x", + PATH_QUEUE, (msgid & 0xff000000) >> 24, msgid)) + fatalx("queue_fs_message_walk: path does not fit buffer"); + + if (dir == NULL) { + if ((dir = opendir(path)) == NULL) { + log_warn("warn: queue_fs: opendir: %s", path); + *done = 1; + return (-1); + } + + *data = dir; + } + + (void)snprintf(msgid_str, sizeof msgid_str, "%08" PRIx32, msgid); + while ((dp = readdir(dir)) != NULL) { +#if defined(HAVE_STRUCT_DIR_D_TYPE) + if (dp->d_type != DT_REG) + continue; +#endif + + /* ignore files other than envelopes */ + if (strlen(dp->d_name) != 16 || + strncmp(dp->d_name, msgid_str, 8)) + continue; + + tmp = NULL; + *evpid = strtoull(dp->d_name, &tmp, 16); + if (tmp && *tmp != '\0') { + log_debug("debug: fsqueue: bogus file %s", dp->d_name); + continue; + } + + memset(buf, 0, len); + r = queue_fs_envelope_load(*evpid, buf, len); + if (r) { + n = tree_pop(&evpcount, msgid); + if (n == NULL) + n = REF; + + n += 1; + tree_xset(&evpcount, msgid, n); + } + + return (r); + } + + (void)closedir(dir); + *done = 1; + return (-1); +} + +static int +queue_fs_envelope_walk(uint64_t *evpid, char *buf, size_t len) +{ + static int done = 0; + static void *hdl = NULL; + int r, *n; + uint32_t msgid; + + if (done) + return (-1); + + if (hdl == NULL) + hdl = fsqueue_qwalk_new(); + + if (fsqueue_qwalk(hdl, evpid)) { + memset(buf, 0, len); + r = queue_fs_envelope_load(*evpid, buf, len); + if (r) { + msgid = evpid_to_msgid(*evpid); + n = tree_pop(&evpcount, msgid); + if (n == NULL) + n = REF; + n += 1; + tree_xset(&evpcount, msgid, n); + } + return (r); + } + + fsqueue_qwalk_close(hdl); + done = 1; + return (-1); +} + +static int +fsqueue_check_space(void) +{ +#ifdef __OpenBSD__ + struct statfs buf; + uint64_t used; + uint64_t total; + + if (statfs(PATH_QUEUE, &buf) == -1) { + log_warn("warn: queue-fs: statfs"); + return 0; + } + + /* + * f_bfree and f_ffree is not set on all filesystems. + * They could be signed or unsigned integers. + * Some systems will set them to 0, others will set them to -1. + */ + if (buf.f_bfree == 0 || buf.f_ffree == 0 || + (int64_t)buf.f_bfree == -1 || (int64_t)buf.f_ffree == -1) + return 1; + + used = buf.f_blocks - buf.f_bfree; + total = buf.f_bavail + used; + if (total != 0) + used = (float)used / (float)total * 100; + else + used = 100; + if (100 - used < MINSPACE) { + log_warnx("warn: not enough disk space: %llu%% left", + (unsigned long long) 100 - used); + log_warnx("warn: temporarily rejecting messages"); + return 0; + } + + used = buf.f_files - buf.f_ffree; + total = buf.f_favail + used; + if (total != 0) + used = (float)used / (float)total * 100; + else + used = 100; + if (100 - used < MININODES) { + log_warnx("warn: not enough inodes: %llu%% left", + (unsigned long long) 100 - used); + log_warnx("warn: temporarily rejecting messages"); + return 0; + } +#endif + return 1; +} + +static void +fsqueue_envelope_path(uint64_t evpid, char *buf, size_t len) +{ + if (!bsnprintf(buf, len, "%s/%02x/%08x/%016" PRIx64, + PATH_QUEUE, + (evpid_to_msgid(evpid) & 0xff000000) >> 24, + evpid_to_msgid(evpid), + evpid)) + fatalx("fsqueue_envelope_path: path does not fit buffer"); +} + +static void +fsqueue_envelope_incoming_path(uint64_t evpid, char *buf, size_t len) +{ + if (!bsnprintf(buf, len, "%s/%08x/%016" PRIx64, + PATH_INCOMING, + evpid_to_msgid(evpid), + evpid)) + fatalx("fsqueue_envelope_incoming_path: path does not fit buffer"); +} + +static int +fsqueue_envelope_dump(char *dest, const char *evpbuf, size_t evplen, + int do_atomic, int do_sync) +{ + const char *path = do_atomic ? PATH_EVPTMP : dest; + FILE *fp = NULL; + int fd; + size_t w; + + if ((fd = open(path, O_RDWR | O_CREAT | O_EXCL, 0600)) == -1) { + log_warn("warn: queue-fs: open"); + goto tempfail; + } + + if ((fp = fdopen(fd, "w")) == NULL) { + log_warn("warn: queue-fs: fdopen"); + goto tempfail; + } + + w = fwrite(evpbuf, 1, evplen, fp); + if (w < evplen) { + log_warn("warn: queue-fs: short write"); + goto tempfail; + } + if (fflush(fp)) { + log_warn("warn: queue-fs: fflush"); + goto tempfail; + } + if (do_sync && fsync(fileno(fp))) { + log_warn("warn: queue-fs: fsync"); + goto tempfail; + } + if (fclose(fp) != 0) { + log_warn("warn: queue-fs: fclose"); + fp = NULL; + goto tempfail; + } + fp = NULL; + fd = -1; + + if (do_atomic && rename(path, dest) == -1) { + log_warn("warn: queue-fs: rename"); + goto tempfail; + } + return (1); + +tempfail: + if (fp) + fclose(fp); + else if (fd != -1) + close(fd); + if (unlink(path) == -1) + log_warn("warn: queue-fs: unlink"); + return (0); +} + +static void +fsqueue_message_path(uint32_t msgid, char *buf, size_t len) +{ + if (!bsnprintf(buf, len, "%s/%02x/%08x", + PATH_QUEUE, + (msgid & 0xff000000) >> 24, + msgid)) + fatalx("fsqueue_message_path: path does not fit buffer"); +} + +static void +fsqueue_message_incoming_path(uint32_t msgid, char *buf, size_t len) +{ + if (!bsnprintf(buf, len, "%s/%08x", + PATH_INCOMING, + msgid)) + fatalx("fsqueue_message_incoming_path: path does not fit buffer"); +} + +static void * +fsqueue_qwalk_new(void) +{ + char path[PATH_MAX]; + char * const path_argv[] = { path, NULL }; + struct qwalk *q; + + q = xcalloc(1, sizeof(*q)); + (void)strlcpy(path, PATH_QUEUE, sizeof(path)); + q->fts = fts_open(path_argv, + FTS_PHYSICAL | FTS_NOCHDIR, NULL); + + if (q->fts == NULL) + err(1, "fsqueue_qwalk_new: fts_open: %s", path); + + return (q); +} + +static void +fsqueue_qwalk_close(void *hdl) +{ + struct qwalk *q = hdl; + + fts_close(q->fts); + + free(q); +} + +static int +fsqueue_qwalk(void *hdl, uint64_t *evpid) +{ + struct qwalk *q = hdl; + FTSENT *e; + char *tmp; + + while ((e = fts_read(q->fts)) != NULL) { + switch (e->fts_info) { + case FTS_D: + q->depth += 1; + if (q->depth == 2 && e->fts_namelen != 2) { + log_debug("debug: fsqueue: bogus directory %s", + e->fts_path); + fts_set(q->fts, e, FTS_SKIP); + break; + } + if (q->depth == 3 && e->fts_namelen != 8) { + log_debug("debug: fsqueue: bogus directory %s", + e->fts_path); + fts_set(q->fts, e, FTS_SKIP); + break; + } + break; + + case FTS_DP: + case FTS_DNR: + q->depth -= 1; + break; + + case FTS_F: + if (q->depth != 3) + break; + if (e->fts_namelen != 16) + break; +#if HAVE_STRUCT_STAT_ST_MTIM + if (timespeccmp(&e->fts_statp->st_mtim, &startup, >)) +#endif +#if HAVE_STRUCT_STAT_ST_MTIMSPEC + if (timespeccmp(&e->fts_statp->st_mtimspec, &startup, >)) +#endif + break; + tmp = NULL; + *evpid = strtoull(e->fts_name, &tmp, 16); + if (tmp && *tmp != '\0') { + log_debug("debug: fsqueue: bogus file %s", + e->fts_path); + break; + } + return (1); + default: + break; + } + } + + return (0); +} + +static int +queue_fs_init(struct passwd *pw, int server, const char *conf) +{ + unsigned int n; + char *paths[] = { PATH_QUEUE, PATH_INCOMING }; + char path[PATH_MAX]; + int ret; + + /* remove incoming/ if it exists */ + if (server) + mvpurge(PATH_SPOOL PATH_INCOMING, PATH_SPOOL PATH_PURGE); + + fsqueue_envelope_path(0, path, sizeof(path)); + + ret = 1; + for (n = 0; n < nitems(paths); n++) { + (void)strlcpy(path, PATH_SPOOL, sizeof(path)); + if (strlcat(path, paths[n], sizeof(path)) >= sizeof(path)) + errx(1, "path too long %s%s", PATH_SPOOL, paths[n]); + if (ckdir(path, 0700, pw->pw_uid, 0, server) == 0) + ret = 0; + } + + if (clock_gettime(CLOCK_REALTIME, &startup)) + err(1, "clock_gettime"); + + tree_init(&evpcount); + + queue_api_on_message_create(queue_fs_message_create); + queue_api_on_message_commit(queue_fs_message_commit); + queue_api_on_message_delete(queue_fs_message_delete); + queue_api_on_message_fd_r(queue_fs_message_fd_r); + queue_api_on_envelope_create(queue_fs_envelope_create); + queue_api_on_envelope_delete(queue_fs_envelope_delete); + queue_api_on_envelope_update(queue_fs_envelope_update); + queue_api_on_envelope_load(queue_fs_envelope_load); + queue_api_on_envelope_walk(queue_fs_envelope_walk); + queue_api_on_message_walk(queue_fs_message_walk); + + return (ret); +} + +struct queue_backend queue_backend_fs = { + queue_fs_init, +}; diff --git a/foobar/portable/smtpd/queue_null.c b/foobar/portable/smtpd/queue_null.c new file mode 100644 index 00000000..1e608be8 --- /dev/null +++ b/foobar/portable/smtpd/queue_null.c @@ -0,0 +1,120 @@ +/* $OpenBSD: queue_null.c,v 1.8 2018/12/30 23:09:58 guenther Exp $ */ + +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <imsg.h> +#include <inttypes.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <limits.h> + +#include "smtpd.h" +#include "log.h" + +static int +queue_null_message_create(uint32_t *msgid) +{ + *msgid = queue_generate_msgid(); + return (1); +} + +static int +queue_null_message_commit(uint32_t msgid, const char *path) +{ + return (1); +} + +static int +queue_null_message_delete(uint32_t msgid) +{ + return (1); +} + +static int +queue_null_message_fd_r(uint32_t msgid) +{ + return (-1); +} + +static int +queue_null_envelope_create(uint32_t msgid, const char *buf, size_t len, + uint64_t *evpid) +{ + *evpid = queue_generate_evpid(msgid); + return (1); +} + +static int +queue_null_envelope_delete(uint64_t evpid) +{ + return (1); +} + +static int +queue_null_envelope_update(uint64_t evpid, const char *buf, size_t len) +{ + return (1); +} + +static int +queue_null_envelope_load(uint64_t evpid, char *buf, size_t len) +{ + return (0); +} + +static int +queue_null_envelope_walk(uint64_t *evpid, char *buf, size_t len) +{ + return (-1); +} + +static int +queue_null_init(struct passwd *pw, int server, const char *conf) +{ + queue_api_on_message_create(queue_null_message_create); + queue_api_on_message_commit(queue_null_message_commit); + queue_api_on_message_delete(queue_null_message_delete); + queue_api_on_message_fd_r(queue_null_message_fd_r); + queue_api_on_envelope_create(queue_null_envelope_create); + queue_api_on_envelope_delete(queue_null_envelope_delete); + queue_api_on_envelope_update(queue_null_envelope_update); + queue_api_on_envelope_load(queue_null_envelope_load); + queue_api_on_envelope_walk(queue_null_envelope_walk); + + return (1); +} + +struct queue_backend queue_backend_null = { + queue_null_init, +}; diff --git a/foobar/portable/smtpd/queue_proc.c b/foobar/portable/smtpd/queue_proc.c new file mode 100644 index 00000000..d6e0f409 --- /dev/null +++ b/foobar/portable/smtpd/queue_proc.c @@ -0,0 +1,337 @@ +/* $OpenBSD: queue_proc.c,v 1.8 2018/12/30 23:09:58 guenther Exp $ */ + +/* + * Copyright (c) 2013 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <imsg.h> +#include <inttypes.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <limits.h> + +#include "smtpd.h" +#include "log.h" + +static struct imsgbuf ibuf; +static struct imsg imsg; +static size_t rlen; +static char *rdata; + +static void +queue_proc_call(void) +{ + ssize_t n; + + if (imsg_flush(&ibuf) == -1) { + log_warn("warn: queue-proc: imsg_flush"); + fatalx("queue-proc: exiting"); + } + + while (1) { + if ((n = imsg_get(&ibuf, &imsg)) == -1) { + log_warn("warn: queue-proc: imsg_get"); + break; + } + if (n) { + rlen = imsg.hdr.len - IMSG_HEADER_SIZE; + rdata = imsg.data; + + if (imsg.hdr.type != PROC_QUEUE_OK) { + log_warnx("warn: queue-proc: bad response"); + break; + } + return; + } + + if ((n = imsg_read(&ibuf)) == -1 && errno != EAGAIN) { + log_warn("warn: queue-proc: imsg_read"); + break; + } + + if (n == 0) { + log_warnx("warn: queue-proc: pipe closed"); + break; + } + } + + fatalx("queue-proc: exiting"); +} + +static void +queue_proc_read(void *dst, size_t len) +{ + if (len > rlen) { + log_warnx("warn: queue-proc: bad msg len"); + fatalx("queue-proc: exiting"); + } + + memmove(dst, rdata, len); + rlen -= len; + rdata += len; +} + +static void +queue_proc_end(void) +{ + if (rlen) { + log_warnx("warn: queue-proc: bogus data"); + fatalx("queue-proc: exiting"); + } + imsg_free(&imsg); +} + +/* + * API + */ + +static int +queue_proc_close(void) +{ + int r; + + imsg_compose(&ibuf, PROC_QUEUE_CLOSE, 0, 0, -1, NULL, 0); + + queue_proc_call(); + queue_proc_read(&r, sizeof(r)); + queue_proc_end(); + + return (r); +} + +static int +queue_proc_message_create(uint32_t *msgid) +{ + int r; + + imsg_compose(&ibuf, PROC_QUEUE_MESSAGE_CREATE, 0, 0, -1, NULL, 0); + + queue_proc_call(); + queue_proc_read(&r, sizeof(r)); + if (r == 1) + queue_proc_read(msgid, sizeof(*msgid)); + queue_proc_end(); + + return (r); +} + +static int +queue_proc_message_commit(uint32_t msgid, const char *path) +{ + int r, fd; + + fd = open(path, O_RDONLY); + if (fd == -1) { + log_warn("queue-proc: open: %s", path); + return (0); + } + + imsg_compose(&ibuf, PROC_QUEUE_MESSAGE_COMMIT, 0, 0, fd, &msgid, + sizeof(msgid)); + + queue_proc_call(); + queue_proc_read(&r, sizeof(r)); + queue_proc_end(); + + return (r); +} + +static int +queue_proc_message_delete(uint32_t msgid) +{ + int r; + + imsg_compose(&ibuf, PROC_QUEUE_MESSAGE_DELETE, 0, 0, -1, &msgid, + sizeof(msgid)); + + queue_proc_call(); + queue_proc_read(&r, sizeof(r)); + queue_proc_end(); + + return (r); +} + +static int +queue_proc_message_fd_r(uint32_t msgid) +{ + imsg_compose(&ibuf, PROC_QUEUE_MESSAGE_FD_R, 0, 0, -1, &msgid, + sizeof(msgid)); + + queue_proc_call(); + queue_proc_end(); + + return (imsg.fd); +} + +static int +queue_proc_envelope_create(uint32_t msgid, const char *buf, size_t len, + uint64_t *evpid) +{ + struct ibuf *b; + int r; + + msgid = evpid_to_msgid(*evpid); + b = imsg_create(&ibuf, PROC_QUEUE_ENVELOPE_CREATE, 0, 0, + sizeof(msgid) + len); + if (imsg_add(b, &msgid, sizeof(msgid)) == -1 || + imsg_add(b, buf, len) == -1) + return (0); + imsg_close(&ibuf, b); + + queue_proc_call(); + queue_proc_read(&r, sizeof(r)); + if (r == 1) + queue_proc_read(evpid, sizeof(*evpid)); + queue_proc_end(); + + return (r); +} + +static int +queue_proc_envelope_delete(uint64_t evpid) +{ + int r; + + imsg_compose(&ibuf, PROC_QUEUE_ENVELOPE_DELETE, 0, 0, -1, &evpid, + sizeof(evpid)); + + queue_proc_call(); + queue_proc_read(&r, sizeof(r)); + queue_proc_end(); + + return (r); +} + +static int +queue_proc_envelope_update(uint64_t evpid, const char *buf, size_t len) +{ + struct ibuf *b; + int r; + + b = imsg_create(&ibuf, PROC_QUEUE_ENVELOPE_UPDATE, 0, 0, + len + sizeof(evpid)); + if (imsg_add(b, &evpid, sizeof(evpid)) == -1 || + imsg_add(b, buf, len) == -1) + return (0); + imsg_close(&ibuf, b); + + queue_proc_call(); + queue_proc_read(&r, sizeof(r)); + queue_proc_end(); + + return (r); +} + +static int +queue_proc_envelope_load(uint64_t evpid, char *buf, size_t len) +{ + int r; + + imsg_compose(&ibuf, PROC_QUEUE_ENVELOPE_LOAD, 0, 0, -1, &evpid, + sizeof(evpid)); + + queue_proc_call(); + + if (rlen > len) { + log_warnx("warn: queue-proc: buf too small"); + fatalx("queue-proc: exiting"); + } + + r = rlen; + queue_proc_read(buf, rlen); + queue_proc_end(); + + return (r); +} + +static int +queue_proc_envelope_walk(uint64_t *evpid, char *buf, size_t len) +{ + int r; + + imsg_compose(&ibuf, PROC_QUEUE_ENVELOPE_WALK, 0, 0, -1, NULL, 0); + + queue_proc_call(); + queue_proc_read(&r, sizeof(r)); + + if (r > 0) { + queue_proc_read(evpid, sizeof(*evpid)); + if (rlen > len) { + log_warnx("warn: queue-proc: buf too small"); + fatalx("queue-proc: exiting"); + } + if (r != (int)rlen) { + log_warnx("warn: queue-proc: len mismatch"); + fatalx("queue-proc: exiting"); + } + queue_proc_read(buf, rlen); + } + queue_proc_end(); + + return (r); +} + +static int +queue_proc_init(struct passwd *pw, int server, const char *conf) +{ + uint32_t version; + int fd; + + fd = fork_proc_backend("queue", conf, "queue-proc"); + if (fd == -1) + fatalx("queue-proc: exiting"); + + imsg_init(&ibuf, fd); + + version = PROC_QUEUE_API_VERSION; + imsg_compose(&ibuf, PROC_QUEUE_INIT, 0, 0, -1, + &version, sizeof(version)); + + queue_api_on_close(queue_proc_close); + queue_api_on_message_create(queue_proc_message_create); + queue_api_on_message_commit(queue_proc_message_commit); + queue_api_on_message_delete(queue_proc_message_delete); + queue_api_on_message_fd_r(queue_proc_message_fd_r); + queue_api_on_envelope_create(queue_proc_envelope_create); + queue_api_on_envelope_delete(queue_proc_envelope_delete); + queue_api_on_envelope_update(queue_proc_envelope_update); + queue_api_on_envelope_load(queue_proc_envelope_load); + queue_api_on_envelope_walk(queue_proc_envelope_walk); + + queue_proc_call(); + queue_proc_end(); + + return (1); +} + +struct queue_backend queue_backend_proc = { + queue_proc_init, +}; diff --git a/foobar/portable/smtpd/queue_ram.c b/foobar/portable/smtpd/queue_ram.c new file mode 100644 index 00000000..50ce17e1 --- /dev/null +++ b/foobar/portable/smtpd/queue_ram.c @@ -0,0 +1,336 @@ +/* $OpenBSD: queue_ram.c,v 1.9 2018/12/30 23:09:58 guenther Exp $ */ + +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <imsg.h> +#include <inttypes.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <limits.h> + +#include "smtpd.h" +#include "log.h" + +struct qr_envelope { + char *buf; + size_t len; +}; + +struct qr_message { + char *buf; + size_t len; + struct tree envelopes; +}; + +static struct tree messages; + +static struct qr_message * +get_message(uint32_t msgid) +{ + struct qr_message *msg; + + msg = tree_get(&messages, msgid); + if (msg == NULL) + log_warn("warn: queue-ram: message not found"); + + return (msg); +} + +static int +queue_ram_message_create(uint32_t *msgid) +{ + struct qr_message *msg; + + msg = calloc(1, sizeof(*msg)); + if (msg == NULL) { + log_warn("warn: queue-ram: calloc"); + return (0); + } + tree_init(&msg->envelopes); + + do { + *msgid = queue_generate_msgid(); + } while (tree_check(&messages, *msgid)); + + tree_xset(&messages, *msgid, msg); + + return (1); +} + +static int +queue_ram_message_commit(uint32_t msgid, const char *path) +{ + struct qr_message *msg; + struct stat sb; + size_t n; + FILE *f; + int ret; + + if ((msg = tree_get(&messages, msgid)) == NULL) { + log_warnx("warn: queue-ram: msgid not found"); + return (0); + } + + f = fopen(path, "rb"); + if (f == NULL) { + log_warn("warn: queue-ram: fopen: %s", path); + return (0); + } + if (fstat(fileno(f), &sb) == -1) { + log_warn("warn: queue-ram: fstat"); + fclose(f); + return (0); + } + + msg->len = sb.st_size; + msg->buf = malloc(msg->len); + if (msg->buf == NULL) { + log_warn("warn: queue-ram: malloc"); + fclose(f); + return (0); + } + + ret = 0; + n = fread(msg->buf, 1, msg->len, f); + if (ferror(f)) + log_warn("warn: queue-ram: fread"); + else if ((off_t)n != sb.st_size) + log_warnx("warn: queue-ram: bad read"); + else { + ret = 1; + stat_increment("queue.ram.message.size", msg->len); + } + fclose(f); + + return (ret); +} + +static int +queue_ram_message_delete(uint32_t msgid) +{ + struct qr_message *msg; + struct qr_envelope *evp; + uint64_t evpid; + + if ((msg = tree_pop(&messages, msgid)) == NULL) { + log_warnx("warn: queue-ram: not found"); + return (0); + } + while (tree_poproot(&messages, &evpid, (void**)&evp)) { + stat_decrement("queue.ram.envelope.size", evp->len); + free(evp->buf); + free(evp); + } + stat_decrement("queue.ram.message.size", msg->len); + free(msg->buf); + free(msg); + return (0); +} + +static int +queue_ram_message_fd_r(uint32_t msgid) +{ + struct qr_message *msg; + size_t n; + FILE *f; + int fd, fd2; + + if ((msg = tree_get(&messages, msgid)) == NULL) { + log_warnx("warn: queue-ram: not found"); + return (-1); + } + + fd = mktmpfile(); + if (fd == -1) { + log_warn("warn: queue-ram: mktmpfile"); + return (-1); + } + + fd2 = dup(fd); + if (fd2 == -1) { + log_warn("warn: queue-ram: dup"); + close(fd); + return (-1); + } + f = fdopen(fd2, "w"); + if (f == NULL) { + log_warn("warn: queue-ram: fdopen"); + close(fd); + close(fd2); + return (-1); + } + n = fwrite(msg->buf, 1, msg->len, f); + if (n != msg->len) { + log_warn("warn: queue-ram: write"); + close(fd); + fclose(f); + return (-1); + } + fclose(f); + lseek(fd, 0, SEEK_SET); + return (fd); +} + +static int +queue_ram_envelope_create(uint32_t msgid, const char *buf, size_t len, + uint64_t *evpid) +{ + struct qr_envelope *evp; + struct qr_message *msg; + + if ((msg = get_message(msgid)) == NULL) + return (0); + + do { + *evpid = queue_generate_evpid(msgid); + } while (tree_check(&msg->envelopes, *evpid)); + evp = calloc(1, sizeof *evp); + if (evp == NULL) { + log_warn("warn: queue-ram: calloc"); + return (0); + } + evp->len = len; + evp->buf = malloc(len); + if (evp->buf == NULL) { + log_warn("warn: queue-ram: malloc"); + free(evp); + return (0); + } + memmove(evp->buf, buf, len); + tree_xset(&msg->envelopes, *evpid, evp); + stat_increment("queue.ram.envelope.size", len); + return (1); +} + +static int +queue_ram_envelope_delete(uint64_t evpid) +{ + struct qr_envelope *evp; + struct qr_message *msg; + + if ((msg = get_message(evpid_to_msgid(evpid))) == NULL) + return (0); + + if ((evp = tree_pop(&msg->envelopes, evpid)) == NULL) { + log_warnx("warn: queue-ram: not found"); + return (0); + } + stat_decrement("queue.ram.envelope.size", evp->len); + free(evp->buf); + free(evp); + if (tree_empty(&msg->envelopes)) { + tree_xpop(&messages, evpid_to_msgid(evpid)); + stat_decrement("queue.ram.message.size", msg->len); + free(msg->buf); + free(msg); + } + return (1); +} + +static int +queue_ram_envelope_update(uint64_t evpid, const char *buf, size_t len) +{ + struct qr_envelope *evp; + struct qr_message *msg; + void *tmp; + + if ((msg = get_message(evpid_to_msgid(evpid))) == NULL) + return (0); + + if ((evp = tree_get(&msg->envelopes, evpid)) == NULL) { + log_warn("warn: queue-ram: not found"); + return (0); + } + tmp = malloc(len); + if (tmp == NULL) { + log_warn("warn: queue-ram: malloc"); + return (0); + } + memmove(tmp, buf, len); + free(evp->buf); + evp->len = len; + evp->buf = tmp; + stat_decrement("queue.ram.envelope.size", evp->len); + stat_increment("queue.ram.envelope.size", len); + return (1); +} + +static int +queue_ram_envelope_load(uint64_t evpid, char *buf, size_t len) +{ + struct qr_envelope *evp; + struct qr_message *msg; + + if ((msg = get_message(evpid_to_msgid(evpid))) == NULL) + return (0); + + if ((evp = tree_get(&msg->envelopes, evpid)) == NULL) { + log_warn("warn: queue-ram: not found"); + return (0); + } + if (len < evp->len) { + log_warnx("warn: queue-ram: buffer too small"); + return (0); + } + memmove(buf, evp->buf, evp->len); + return (evp->len); +} + +static int +queue_ram_envelope_walk(uint64_t *evpid, char *buf, size_t len) +{ + return (-1); +} + +static int +queue_ram_init(struct passwd *pw, int server, const char * conf) +{ + tree_init(&messages); + + queue_api_on_message_create(queue_ram_message_create); + queue_api_on_message_commit(queue_ram_message_commit); + queue_api_on_message_delete(queue_ram_message_delete); + queue_api_on_message_fd_r(queue_ram_message_fd_r); + queue_api_on_envelope_create(queue_ram_envelope_create); + queue_api_on_envelope_delete(queue_ram_envelope_delete); + queue_api_on_envelope_update(queue_ram_envelope_update); + queue_api_on_envelope_load(queue_ram_envelope_load); + queue_api_on_envelope_walk(queue_ram_envelope_walk); + + return (1); +} + +struct queue_backend queue_backend_ram = { + queue_ram_init, +}; diff --git a/foobar/portable/smtpd/report_smtp.c b/foobar/portable/smtpd/report_smtp.c new file mode 100644 index 00000000..7802eaae --- /dev/null +++ b/foobar/portable/smtpd/report_smtp.c @@ -0,0 +1,335 @@ +/* $OpenBSD: report_smtp.c,v 1.11 2020/01/07 23:03:37 gilles Exp $ */ + +/* + * Copyright (c) 2018 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/uio.h> + +#include <netinet/in.h> + +#include <ctype.h> +#include <errno.h> +#include <event.h> +#include <imsg.h> +#include <limits.h> +#include <inttypes.h> +#include <openssl/ssl.h> +#include <resolv.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#if defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS) +#include <vis.h> +#else +#include "bsd-vis.h" +#endif + +#include "smtpd.h" +#include "log.h" +#include "ssl.h" +#include "rfc5322.h" + +void +report_smtp_link_connect(const char *direction, uint64_t qid, const char *rdns, int fcrdns, + const struct sockaddr_storage *ss_src, + const struct sockaddr_storage *ss_dest) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_LINK_CONNECT, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_string(p_lka, rdns); + m_add_int(p_lka, fcrdns); + m_add_sockaddr(p_lka, (const struct sockaddr *)ss_src); + m_add_sockaddr(p_lka, (const struct sockaddr *)ss_dest); + m_close(p_lka); +} + +void +report_smtp_link_greeting(const char *direction, uint64_t qid, + const char *domain) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_LINK_GREETING, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_string(p_lka, domain); + m_close(p_lka); +} + +void +report_smtp_link_identify(const char *direction, uint64_t qid, const char *method, const char *identity) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_LINK_IDENTIFY, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_string(p_lka, method); + m_add_string(p_lka, identity); + m_close(p_lka); +} + +void +report_smtp_link_tls(const char *direction, uint64_t qid, const char *ssl) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_LINK_TLS, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_string(p_lka, ssl); + m_close(p_lka); +} + +void +report_smtp_link_disconnect(const char *direction, uint64_t qid) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_LINK_DISCONNECT, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_close(p_lka); +} + +void +report_smtp_link_auth(const char *direction, uint64_t qid, const char *user, const char *result) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_LINK_AUTH, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_string(p_lka, user); + m_add_string(p_lka, result); + m_close(p_lka); +} + +void +report_smtp_tx_reset(const char *direction, uint64_t qid, uint32_t msgid) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_TX_RESET, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_u32(p_lka, msgid); + m_close(p_lka); +} + +void +report_smtp_tx_begin(const char *direction, uint64_t qid, uint32_t msgid) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_TX_BEGIN, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_u32(p_lka, msgid); + m_close(p_lka); +} + +void +report_smtp_tx_mail(const char *direction, uint64_t qid, uint32_t msgid, const char *address, int ok) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_TX_MAIL, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_u32(p_lka, msgid); + m_add_string(p_lka, address); + m_add_int(p_lka, ok); + m_close(p_lka); +} + +void +report_smtp_tx_rcpt(const char *direction, uint64_t qid, uint32_t msgid, const char *address, int ok) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_TX_RCPT, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_u32(p_lka, msgid); + m_add_string(p_lka, address); + m_add_int(p_lka, ok); + m_close(p_lka); +} + +void +report_smtp_tx_envelope(const char *direction, uint64_t qid, uint32_t msgid, uint64_t evpid) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_TX_ENVELOPE, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_u32(p_lka, msgid); + m_add_id(p_lka, evpid); + m_close(p_lka); +} + +void +report_smtp_tx_data(const char *direction, uint64_t qid, uint32_t msgid, int ok) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_TX_DATA, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_u32(p_lka, msgid); + m_add_int(p_lka, ok); + m_close(p_lka); +} + +void +report_smtp_tx_commit(const char *direction, uint64_t qid, uint32_t msgid, size_t msgsz) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_TX_COMMIT, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_u32(p_lka, msgid); + m_add_size(p_lka, msgsz); + m_close(p_lka); +} + +void +report_smtp_tx_rollback(const char *direction, uint64_t qid, uint32_t msgid) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_TX_ROLLBACK, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_u32(p_lka, msgid); + m_close(p_lka); +} + +void +report_smtp_protocol_client(const char *direction, uint64_t qid, const char *command) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_PROTOCOL_CLIENT, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_string(p_lka, command); + m_close(p_lka); +} + +void +report_smtp_protocol_server(const char *direction, uint64_t qid, const char *response) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_PROTOCOL_SERVER, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_string(p_lka, response); + m_close(p_lka); +} + +void +report_smtp_filter_response(const char *direction, uint64_t qid, int phase, int response, const char *param) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_FILTER_RESPONSE, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_add_int(p_lka, phase); + m_add_int(p_lka, response); + m_add_string(p_lka, param); + m_close(p_lka); +} + +void +report_smtp_timeout(const char *direction, uint64_t qid) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + m_create(p_lka, IMSG_REPORT_SMTP_TIMEOUT, 0, 0, -1); + m_add_string(p_lka, direction); + m_add_timeval(p_lka, &tv); + m_add_id(p_lka, qid); + m_close(p_lka); +} diff --git a/foobar/portable/smtpd/resolver.c b/foobar/portable/smtpd/resolver.c new file mode 100644 index 00000000..f0f0f8ea --- /dev/null +++ b/foobar/portable/smtpd/resolver.c @@ -0,0 +1,462 @@ +/* $OpenBSD: resolver.c,v 1.5 2019/06/13 11:45:35 eric Exp $ */ + +/* + * Copyright (c) 2017-2018 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/tree.h> +#include <sys/queue.h> +#include <netinet/in.h> + +#include <asr.h> +#include <ctype.h> +#include <errno.h> +#include <imsg.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "smtpd.h" +#include "log.h" + +#define p_resolver p_lka + +struct request { + SPLAY_ENTRY(request) entry; + uint32_t id; + void (*cb_ai)(void *, int, struct addrinfo *); + void (*cb_ni)(void *, int, const char *, const char *); + void (*cb_res)(void *, int, int, int, const void *, int); + void *arg; + struct addrinfo *ai; +}; + +struct session { + uint32_t reqid; + struct mproc *proc; + char *host; + char *serv; +}; + +SPLAY_HEAD(reqtree, request); + +static void resolver_init(void); +static void resolver_getaddrinfo_cb(struct asr_result *, void *); +static void resolver_getnameinfo_cb(struct asr_result *, void *); +static void resolver_res_query_cb(struct asr_result *, void *); + +static int request_cmp(struct request *, struct request *); +SPLAY_PROTOTYPE(reqtree, request, entry, request_cmp); + +/* musl work-around */ +void portable_freeaddrinfo(struct addrinfo *); + +static struct reqtree reqs; + +void +resolver_getaddrinfo(const char *hostname, const char *servname, + const struct addrinfo *hints, void (*cb)(void *, int, struct addrinfo *), + void *arg) +{ + struct request *req; + + resolver_init(); + + req = calloc(1, sizeof(*req)); + if (req == NULL) { + cb(arg, EAI_MEMORY, NULL); + return; + } + + while (req->id == 0 || SPLAY_FIND(reqtree, &reqs, req)) + req->id = arc4random(); + req->cb_ai = cb; + req->arg = arg; + + SPLAY_INSERT(reqtree, &reqs, req); + + m_create(p_resolver, IMSG_GETADDRINFO, req->id, 0, -1); + m_add_int(p_resolver, hints ? hints->ai_flags : 0); + m_add_int(p_resolver, hints ? hints->ai_family : 0); + m_add_int(p_resolver, hints ? hints->ai_socktype : 0); + m_add_int(p_resolver, hints ? hints->ai_protocol : 0); + m_add_string(p_resolver, hostname); + m_add_string(p_resolver, servname); + m_close(p_resolver); +} + +void +resolver_getnameinfo(const struct sockaddr *sa, int flags, + void(*cb)(void *, int, const char *, const char *), void *arg) +{ + struct request *req; + + resolver_init(); + + req = calloc(1, sizeof(*req)); + if (req == NULL) { + cb(arg, EAI_MEMORY, NULL, NULL); + return; + } + + while (req->id == 0 || SPLAY_FIND(reqtree, &reqs, req)) + req->id = arc4random(); + req->cb_ni = cb; + req->arg = arg; + + SPLAY_INSERT(reqtree, &reqs, req); + + m_create(p_resolver, IMSG_GETNAMEINFO, req->id, 0, -1); + m_add_sockaddr(p_resolver, sa); + m_add_int(p_resolver, flags); + m_close(p_resolver); +} + +void +resolver_res_query(const char *dname, int class, int type, + void (*cb)(void *, int, int, int, const void *, int), void *arg) +{ + struct request *req; + + resolver_init(); + + req = calloc(1, sizeof(*req)); + if (req == NULL) { + cb(arg, NETDB_INTERNAL, 0, 0, NULL, 0); + return; + } + + while (req->id == 0 || SPLAY_FIND(reqtree, &reqs, req)) + req->id = arc4random(); + req->cb_res = cb; + req->arg = arg; + + SPLAY_INSERT(reqtree, &reqs, req); + + m_create(p_resolver, IMSG_RES_QUERY, req->id, 0, -1); + m_add_string(p_resolver, dname); + m_add_int(p_resolver, class); + m_add_int(p_resolver, type); + m_close(p_resolver); +} + +void +resolver_dispatch_request(struct mproc *proc, struct imsg *imsg) +{ + const char *hostname, *servname, *dname; + struct session *s; + struct asr_query *q; + struct addrinfo hints; + struct sockaddr_storage ss; + struct sockaddr *sa; + struct msg m; + uint32_t reqid; + int class, type, flags, save_errno; + + reqid = imsg->hdr.peerid; + m_msg(&m, imsg); + + switch (imsg->hdr.type) { + + case IMSG_GETADDRINFO: + servname = NULL; + memset(&hints, 0 , sizeof(hints)); + m_get_int(&m, &hints.ai_flags); + m_get_int(&m, &hints.ai_family); + m_get_int(&m, &hints.ai_socktype); + m_get_int(&m, &hints.ai_protocol); + m_get_string(&m, &hostname); + m_get_string(&m, &servname); + m_end(&m); + + s = NULL; + q = NULL; + if ((s = calloc(1, sizeof(*s))) && + (q = getaddrinfo_async(hostname, servname, &hints, NULL)) && + (event_asr_run(q, resolver_getaddrinfo_cb, s))) { + s->reqid = reqid; + s->proc = proc; + break; + } + save_errno = errno; + + if (q) + asr_abort(q); + if (s) + free(s); + + m_create(proc, IMSG_GETADDRINFO_END, reqid, 0, -1); + m_add_int(proc, EAI_SYSTEM); + m_add_int(proc, save_errno); + m_close(proc); + break; + + case IMSG_GETNAMEINFO: + sa = (struct sockaddr*)&ss; + m_get_sockaddr(&m, sa); + m_get_int(&m, &flags); + m_end(&m); + + s = NULL; + q = NULL; + if ((s = calloc(1, sizeof(*s))) && + (s->host = malloc(NI_MAXHOST)) && + (s->serv = malloc(NI_MAXSERV)) && + (q = getnameinfo_async(sa, SA_LEN(sa), s->host, NI_MAXHOST, + s->serv, NI_MAXSERV, flags, NULL)) && + (event_asr_run(q, resolver_getnameinfo_cb, s))) { + s->reqid = reqid; + s->proc = proc; + break; + } + save_errno = errno; + + if (q) + asr_abort(q); + if (s) { + free(s->host); + free(s->serv); + free(s); + } + + m_create(proc, IMSG_GETNAMEINFO, reqid, 0, -1); + m_add_int(proc, EAI_SYSTEM); + m_add_int(proc, save_errno); + m_add_string(proc, NULL); + m_add_string(proc, NULL); + m_close(proc); + break; + + case IMSG_RES_QUERY: + m_get_string(&m, &dname); + m_get_int(&m, &class); + m_get_int(&m, &type); + m_end(&m); + + s = NULL; + q = NULL; + if ((s = calloc(1, sizeof(*s))) && + (q = res_query_async(dname, class, type, NULL)) && + (event_asr_run(q, resolver_res_query_cb, s))) { + s->reqid = reqid; + s->proc = proc; + break; + } + save_errno = errno; + + if (q) + asr_abort(q); + if (s) + free(s); + + m_create(proc, IMSG_RES_QUERY, reqid, 0, -1); + m_add_int(proc, NETDB_INTERNAL); + m_add_int(proc, save_errno); + m_add_int(proc, 0); + m_add_int(proc, 0); + m_add_data(proc, NULL, 0); + m_close(proc); + break; + + default: + fatalx("%s: %s", __func__, imsg_to_str(imsg->hdr.type)); + } +} + +void +resolver_dispatch_result(struct mproc *proc, struct imsg *imsg) +{ + struct request key, *req; + struct sockaddr_storage ss; + struct addrinfo *ai; + struct msg m; + const char *cname, *host, *serv; + const void *data; + size_t datalen; + int gai_errno, herrno, rcode, count; + + key.id = imsg->hdr.peerid; + req = SPLAY_FIND(reqtree, &reqs, &key); + if (req == NULL) + fatalx("%s: unknown request %08x", __func__, imsg->hdr.peerid); + + m_msg(&m, imsg); + + switch (imsg->hdr.type) { + + case IMSG_GETADDRINFO: + ai = calloc(1, sizeof(*ai)); + if (ai == NULL) { + log_warn("%s: calloc", __func__); + break; + } + m_get_int(&m, &ai->ai_flags); + m_get_int(&m, &ai->ai_family); + m_get_int(&m, &ai->ai_socktype); + m_get_int(&m, &ai->ai_protocol); + m_get_sockaddr(&m, (struct sockaddr *)&ss); + m_get_string(&m, &cname); + m_end(&m); + + ai->ai_addr = malloc(SS_LEN(&ss)); + if (ai->ai_addr == NULL) { + log_warn("%s: malloc", __func__); + free(ai); + break; + } + + memmove(ai->ai_addr, &ss, SS_LEN(&ss)); + + if (cname) { + ai->ai_canonname = strdup(cname); + if (ai->ai_canonname == NULL) { + log_warn("%s: strdup", __func__); + free(ai->ai_addr); + free(ai); + break; + } + } + + ai->ai_next = req->ai; + req->ai = ai; + break; + + case IMSG_GETADDRINFO_END: + m_get_int(&m, &gai_errno); + m_get_int(&m, &errno); + m_end(&m); + + SPLAY_REMOVE(reqtree, &reqs, req); + req->cb_ai(req->arg, gai_errno, req->ai); + free(req); + break; + + case IMSG_GETNAMEINFO: + m_get_int(&m, &gai_errno); + m_get_int(&m, &errno); + m_get_string(&m, &host); + m_get_string(&m, &serv); + m_end(&m); + + SPLAY_REMOVE(reqtree, &reqs, req); + req->cb_ni(req->arg, gai_errno, host, serv); + free(req); + break; + + case IMSG_RES_QUERY: + m_get_int(&m, &herrno); + m_get_int(&m, &errno); + m_get_int(&m, &rcode); + m_get_int(&m, &count); + m_get_data(&m, &data, &datalen); + m_end(&m); + + SPLAY_REMOVE(reqtree, &reqs, req); + req->cb_res(req->arg, herrno, rcode, count, data, datalen); + free(req); + break; + } +} + +static void +resolver_init(void) +{ + static int init = 0; + + if (init == 0) { + SPLAY_INIT(&reqs); + init = 1; + } +} + +static void +resolver_getaddrinfo_cb(struct asr_result *ar, void *arg) +{ + struct session *s = arg; + struct addrinfo *ai; + + for (ai = ar->ar_addrinfo; ai; ai = ai->ai_next) { + m_create(s->proc, IMSG_GETADDRINFO, s->reqid, 0, -1); + m_add_int(s->proc, ai->ai_flags); + m_add_int(s->proc, ai->ai_family); + m_add_int(s->proc, ai->ai_socktype); + m_add_int(s->proc, ai->ai_protocol); + m_add_sockaddr(s->proc, ai->ai_addr); + m_add_string(s->proc, ai->ai_canonname); + m_close(s->proc); + } + + m_create(s->proc, IMSG_GETADDRINFO_END, s->reqid, 0, -1); + m_add_int(s->proc, ar->ar_gai_errno); + m_add_int(s->proc, ar->ar_errno); + m_close(s->proc); + + if (ar->ar_addrinfo) + portable_freeaddrinfo(ar->ar_addrinfo); + free(s); +} + +static void +resolver_getnameinfo_cb(struct asr_result *ar, void *arg) +{ + struct session *s = arg; + + m_create(s->proc, IMSG_GETNAMEINFO, s->reqid, 0, -1); + m_add_int(s->proc, ar->ar_gai_errno); + m_add_int(s->proc, ar->ar_errno); + m_add_string(s->proc, ar->ar_gai_errno ? NULL : s->host); + m_add_string(s->proc, ar->ar_gai_errno ? NULL : s->serv); + m_close(s->proc); + + free(s->host); + free(s->serv); + free(s); +} + +static void +resolver_res_query_cb(struct asr_result *ar, void *arg) +{ + struct session *s = arg; + + m_create(s->proc, IMSG_RES_QUERY, s->reqid, 0, -1); + m_add_int(s->proc, ar->ar_h_errno); + m_add_int(s->proc, ar->ar_errno); + m_add_int(s->proc, ar->ar_rcode); + m_add_int(s->proc, ar->ar_count); + m_add_data(s->proc, ar->ar_data, ar->ar_datalen); + m_close(s->proc); + + free(ar->ar_data); + free(s); +} + +static int +request_cmp(struct request *a, struct request *b) +{ + if (a->id < b->id) + return -1; + if (a->id > b->id) + return 1; + return 0; +} + +SPLAY_GENERATE(reqtree, request, entry, request_cmp); diff --git a/foobar/portable/smtpd/rfc5322.c b/foobar/portable/smtpd/rfc5322.c new file mode 100644 index 00000000..0af66772 --- /dev/null +++ b/foobar/portable/smtpd/rfc5322.c @@ -0,0 +1,266 @@ +/* $OpenBSD: rfc5322.c,v 1.2 2018/10/24 18:59:29 gilles Exp $ */ + +/* + * Copyright (c) 2018 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> + +#include "rfc5322.h" + +struct buf { + char *buf; + size_t bufsz; + size_t buflen; + size_t bufmax; +}; + +static int buf_alloc(struct buf *, size_t); +static int buf_grow(struct buf *, size_t); +static int buf_cat(struct buf *, const char *); + +struct rfc5322_parser { + const char *line; + int state; /* last parser state */ + int next; /* parser needs data */ + int unfold; + const char *currhdr; + struct buf hdr; + struct buf val; +}; + +struct rfc5322_parser * +rfc5322_parser_new(void) +{ + struct rfc5322_parser *parser; + + parser = calloc(1, sizeof(*parser)); + if (parser == NULL) + return NULL; + + rfc5322_clear(parser); + parser->hdr.bufmax = 1024; + parser->val.bufmax = 65536; + + return parser; +} + +void +rfc5322_free(struct rfc5322_parser *parser) +{ + free(parser->hdr.buf); + free(parser->val.buf); + free(parser); +} + +void +rfc5322_clear(struct rfc5322_parser *parser) +{ + parser->line = NULL; + parser->state = RFC5322_NONE; + parser->next = 0; + parser->hdr.buflen = 0; + parser->val.buflen = 0; +} + +int +rfc5322_push(struct rfc5322_parser *parser, const char *line) +{ + if (parser->line) { + errno = EALREADY; + return -1; + } + + parser->line = line; + parser->next = 0; + + return 0; +} + +int +rfc5322_unfold_header(struct rfc5322_parser *parser) +{ + if (parser->unfold) { + errno = EALREADY; + return -1; + } + + if (parser->currhdr == NULL) { + errno = EOPNOTSUPP; + return -1; + } + + if (buf_cat(&parser->val, parser->currhdr) == -1) + return -1; + + parser->currhdr = NULL; + parser->unfold = 1; + + return 0; +} + +static int +_rfc5322_next(struct rfc5322_parser *parser, struct rfc5322_result *res) +{ + size_t len; + const char *pos, *line; + + line = parser->line; + + switch(parser->state) { + + case RFC5322_HEADER_START: + case RFC5322_HEADER_CONT: + res->hdr = parser->hdr.buf; + + if (line && (line[0] == ' ' || line[0] == '\t')) { + parser->line = NULL; + parser->next = 1; + if (parser->unfold) { + if (buf_cat(&parser->val, "\n") == -1 || + buf_cat(&parser->val, line) == -1) + return -1; + } + res->value = line; + return RFC5322_HEADER_CONT; + } + + if (parser->unfold) { + parser->val.buflen = 0; + parser->unfold = 0; + res->value = parser->val.buf; + } + return RFC5322_HEADER_END; + + case RFC5322_NONE: + case RFC5322_HEADER_END: + if (line && (pos = strchr(line, ':'))) { + len = pos - line; + if (buf_grow(&parser->hdr, len + 1) == -1) + return -1; + (void)memcpy(parser->hdr.buf, line, len); + parser->hdr.buf[len] = '\0'; + parser->hdr.buflen = len + 1; + parser->line = NULL; + parser->next = 1; + parser->currhdr = pos + 1; + res->hdr = parser->hdr.buf; + res->value = pos + 1; + return RFC5322_HEADER_START; + } + + return RFC5322_END_OF_HEADERS; + + case RFC5322_END_OF_HEADERS: + if (line == NULL) + return RFC5322_END_OF_MESSAGE; + + if (line[0] == '\0') { + parser->line = NULL; + parser->next = 1; + res->value = line; + return RFC5322_BODY_START; + } + + errno = EINVAL; + return -1; + + case RFC5322_BODY_START: + case RFC5322_BODY: + if (line == NULL) + return RFC5322_END_OF_MESSAGE; + + parser->line = NULL; + parser->next = 1; + res->value = line; + return RFC5322_BODY; + + case RFC5322_END_OF_MESSAGE: + errno = ENOMSG; + return -1; + + default: + errno = EINVAL; + return -1; + } +} + +int +rfc5322_next(struct rfc5322_parser *parser, struct rfc5322_result *res) +{ + memset(res, 0, sizeof(*res)); + + if (parser->next) + return RFC5322_NONE; + + return (parser->state = _rfc5322_next(parser, res)); +} + +static int +buf_alloc(struct buf *b, size_t need) +{ + char *buf; + size_t alloc; + + if (b->buf && b->bufsz >= need) + return 0; + + if (need >= b->bufmax) { + errno = ERANGE; + return -1; + } + +#define N 256 + alloc = N * (need / N) + ((need % N) ? N : 0); +#undef N + buf = reallocarray(b->buf, alloc, 1); + if (buf == NULL) + return -1; + + b->buf = buf; + b->bufsz = alloc; + + return 0; +} + +static int +buf_grow(struct buf *b, size_t sz) +{ + if (SIZE_T_MAX - b->buflen <= sz) { + errno = ERANGE; + return -1; + } + + return buf_alloc(b, b->buflen + sz); +} + +static int +buf_cat(struct buf *b, const char *s) +{ + size_t len = strlen(s); + + if (buf_grow(b, len + 1) == -1) + return -1; + + (void)memmove(b->buf + b->buflen, s, len + 1); + b->buflen += len; + return 0; +} diff --git a/foobar/portable/smtpd/rfc5322.h b/foobar/portable/smtpd/rfc5322.h new file mode 100644 index 00000000..0979bd4c --- /dev/null +++ b/foobar/portable/smtpd/rfc5322.h @@ -0,0 +1,41 @@ +/* $OpenBSD: rfc5322.h,v 1.1 2018/08/23 10:07:06 eric Exp $ */ + +/* + * Copyright (c) 2018 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +struct rfc5322_result { + const char *hdr; + const char *value; +}; + +#define RFC5322_ERR -1 +#define RFC5322_NONE 0 +#define RFC5322_HEADER_START 1 +#define RFC5322_HEADER_CONT 2 +#define RFC5322_HEADER_END 3 +#define RFC5322_END_OF_HEADERS 4 +#define RFC5322_BODY_START 5 +#define RFC5322_BODY 6 +#define RFC5322_END_OF_MESSAGE 7 + +struct rfc5322_parser; + +struct rfc5322_parser *rfc5322_parser_new(void); +void rfc5322_free(struct rfc5322_parser *); +void rfc5322_clear(struct rfc5322_parser *); +int rfc5322_push(struct rfc5322_parser *, const char *); +int rfc5322_next(struct rfc5322_parser *, struct rfc5322_result *); +int rfc5322_unfold_header(struct rfc5322_parser *); diff --git a/foobar/portable/smtpd/ruleset.c b/foobar/portable/smtpd/ruleset.c new file mode 100644 index 00000000..719a2913 --- /dev/null +++ b/foobar/portable/smtpd/ruleset.c @@ -0,0 +1,265 @@ +/* $OpenBSD: ruleset.c,v 1.47 2019/11/25 14:18:33 gilles Exp $ */ + +/* + * Copyright (c) 2009 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> + +#include <netinet/in.h> + +#include <errno.h> +#include <event.h> +#include <imsg.h> +#include <stdio.h> +#include <string.h> +#include <limits.h> + +#include "smtpd.h" +#include "log.h" + +#define MATCH_RESULT(r, neg) ((r) == -1 ? -1 : ((neg) < 0 ? !(r) : (r))) + +static int +ruleset_match_tag(struct rule *r, const struct envelope *evp) +{ + int ret; + struct table *table; + enum table_service service = K_STRING; + + if (!r->flag_tag) + return 1; + + if (r->flag_tag_regex) + service = K_REGEX; + + table = table_find(env, r->table_tag); + ret = table_match(table, service, evp->tag); + + return MATCH_RESULT(ret, r->flag_tag); +} + +static int +ruleset_match_from(struct rule *r, const struct envelope *evp) +{ + int ret; + int has_rdns; + const char *key; + struct table *table; + enum table_service service = K_NETADDR; + + if (!r->flag_from) + return 1; + + if (evp->flags & EF_INTERNAL) { + /* if expanded from an empty table_from, skip rule + * if no table + */ + if (r->table_from == NULL) + return 0; + key = "local"; + } + else if (r->flag_from_rdns) { + has_rdns = strcmp(evp->hostname, "<unknown>") != 0; + if (r->table_from == NULL) + return MATCH_RESULT(has_rdns, r->flag_from); + if (!has_rdns) + return 0; + key = evp->hostname; + } + else { + key = ss_to_text(&evp->ss); + if (r->flag_from_socket) { + if (strcmp(key, "local") == 0) + return MATCH_RESULT(1, r->flag_from); + else + return r->flag_from < 0 ? 1 : 0; + } + } + if (r->flag_from_regex) + service = K_REGEX; + + table = table_find(env, r->table_from); + ret = table_match(table, service, key); + + return MATCH_RESULT(ret, r->flag_from); +} + +static int +ruleset_match_to(struct rule *r, const struct envelope *evp) +{ + int ret; + struct table *table; + enum table_service service = K_DOMAIN; + + if (!r->flag_for) + return 1; + + if (r->flag_for_regex) + service = K_REGEX; + + table = table_find(env, r->table_for); + ret = table_match(table, service, evp->dest.domain); + + return MATCH_RESULT(ret, r->flag_for); +} + +static int +ruleset_match_smtp_helo(struct rule *r, const struct envelope *evp) +{ + int ret; + struct table *table; + enum table_service service = K_DOMAIN; + + if (!r->flag_smtp_helo) + return 1; + + if (r->flag_smtp_helo_regex) + service = K_REGEX; + + table = table_find(env, r->table_smtp_helo); + ret = table_match(table, service, evp->helo); + + return MATCH_RESULT(ret, r->flag_smtp_helo); +} + +static int +ruleset_match_smtp_starttls(struct rule *r, const struct envelope *evp) +{ + if (!r->flag_smtp_starttls) + return 1; + + /* XXX - not until TLS flag is added to envelope */ + return -1; +} + +static int +ruleset_match_smtp_auth(struct rule *r, const struct envelope *evp) +{ + int ret; + struct table *table; + enum table_service service; + + if (!r->flag_smtp_auth) + return 1; + + if (!(evp->flags & EF_AUTHENTICATED)) + ret = 0; + else if (r->table_smtp_auth) { + + if (r->flag_smtp_auth_regex) + service = K_REGEX; + else + service = strchr(evp->username, '@') ? + K_MAILADDR : K_STRING; + table = table_find(env, r->table_smtp_auth); + ret = table_match(table, service, evp->username); + } + else + ret = 1; + + return MATCH_RESULT(ret, r->flag_smtp_auth); +} + +static int +ruleset_match_smtp_mail_from(struct rule *r, const struct envelope *evp) +{ + int ret; + const char *key; + struct table *table; + enum table_service service = K_MAILADDR; + + if (!r->flag_smtp_mail_from) + return 1; + + if (r->flag_smtp_mail_from_regex) + service = K_REGEX; + + if ((key = mailaddr_to_text(&evp->sender)) == NULL) + return -1; + + table = table_find(env, r->table_smtp_mail_from); + ret = table_match(table, service, key); + + return MATCH_RESULT(ret, r->flag_smtp_mail_from); +} + +static int +ruleset_match_smtp_rcpt_to(struct rule *r, const struct envelope *evp) +{ + int ret; + const char *key; + struct table *table; + enum table_service service = K_MAILADDR; + + if (!r->flag_smtp_rcpt_to) + return 1; + + if (r->flag_smtp_rcpt_to_regex) + service = K_REGEX; + + if ((key = mailaddr_to_text(&evp->dest)) == NULL) + return -1; + + table = table_find(env, r->table_smtp_rcpt_to); + ret = table_match(table, service, key); + + return MATCH_RESULT(ret, r->flag_smtp_rcpt_to); +} + +struct rule * +ruleset_match(const struct envelope *evp) +{ + struct rule *r; + int i = 0; + +#define MATCH_EVAL(x) \ + switch ((x)) { \ + case -1: goto tempfail; \ + case 0: continue; \ + default: break; \ + } + TAILQ_FOREACH(r, env->sc_rules, r_entry) { + ++i; + MATCH_EVAL(ruleset_match_tag(r, evp)); + MATCH_EVAL(ruleset_match_from(r, evp)); + MATCH_EVAL(ruleset_match_to(r, evp)); + MATCH_EVAL(ruleset_match_smtp_helo(r, evp)); + MATCH_EVAL(ruleset_match_smtp_auth(r, evp)); + MATCH_EVAL(ruleset_match_smtp_starttls(r, evp)); + MATCH_EVAL(ruleset_match_smtp_mail_from(r, evp)); + MATCH_EVAL(ruleset_match_smtp_rcpt_to(r, evp)); + goto matched; + } +#undef MATCH_EVAL + + errno = 0; + log_trace(TRACE_RULES, "no rule matched"); + return (NULL); + +tempfail: + errno = EAGAIN; + log_trace(TRACE_RULES, "temporary failure in processing of a rule"); + return (NULL); + +matched: + log_trace(TRACE_RULES, "rule #%d matched: %s", i, rule_to_text(r)); + return r; +} diff --git a/foobar/portable/smtpd/runq.c b/foobar/portable/smtpd/runq.c new file mode 100644 index 00000000..786d36fb --- /dev/null +++ b/foobar/portable/smtpd/runq.c @@ -0,0 +1,183 @@ +/* $OpenBSD: runq.c,v 1.3 2019/06/14 19:55:25 eric Exp $ */ + +/* + * Copyright (c) 2013,2019 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/uio.h> + +#include <imsg.h> +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <time.h> + +#include "smtpd.h" + +struct job { + TAILQ_ENTRY(job) entry; + time_t when; + void *arg; +}; + +struct runq { + TAILQ_HEAD(, job) jobs; + void (*cb)(struct runq *, void *); + struct event ev; +}; + +static void runq_timeout(int, short, void *); + +static struct runq *active; + +static void +runq_reset(struct runq *runq) +{ + struct timeval tv; + struct job *job; + time_t now; + + job = TAILQ_FIRST(&runq->jobs); + if (job == NULL) + return; + + now = time(NULL); + if (job->when <= now) + tv.tv_sec = 0; + else + tv.tv_sec = job->when - now; + tv.tv_usec = 0; + evtimer_add(&runq->ev, &tv); +} + +static void +runq_timeout(int fd, short ev, void *arg) +{ + struct runq *runq = arg; + struct job *job; + time_t now; + + active = runq; + now = time(NULL); + + while((job = TAILQ_FIRST(&runq->jobs))) { + if (job->when > now) + break; + TAILQ_REMOVE(&runq->jobs, job, entry); + runq->cb(runq, job->arg); + free(job); + } + + active = NULL; + runq_reset(runq); +} + +int +runq_init(struct runq **runqp, void (*cb)(struct runq *, void *)) +{ + struct runq *runq; + + runq = malloc(sizeof(*runq)); + if (runq == NULL) + return (0); + + runq->cb = cb; + TAILQ_INIT(&runq->jobs); + evtimer_set(&runq->ev, runq_timeout, runq); + + *runqp = runq; + + return (1); +} + +int +runq_schedule(struct runq *runq, time_t delay, void *arg) +{ + time_t t; + + time(&t); + return runq_schedule_at(runq, t + delay, arg); +} + +int +runq_schedule_at(struct runq *runq, time_t when, void *arg) +{ + struct job *job, *tmpjob; + + job = malloc(sizeof(*job)); + if (job == NULL) + return (0); + + job->arg = arg; + job->when = when; + + TAILQ_FOREACH(tmpjob, &runq->jobs, entry) { + if (tmpjob->when > job->when) { + TAILQ_INSERT_BEFORE(tmpjob, job, entry); + goto done; + } + } + TAILQ_INSERT_TAIL(&runq->jobs, job, entry); + + done: + if (runq != active && job == TAILQ_FIRST(&runq->jobs)) { + evtimer_del(&runq->ev); + runq_reset(runq); + } + return (1); +} + +int +runq_cancel(struct runq *runq, void *arg) +{ + struct job *job, *first; + + first = TAILQ_FIRST(&runq->jobs); + TAILQ_FOREACH(job, &runq->jobs, entry) { + if (job->arg == arg) { + TAILQ_REMOVE(&runq->jobs, job, entry); + free(job); + if (runq != active && job == first) { + evtimer_del(&runq->ev); + runq_reset(runq); + } + return (1); + } + } + + return (0); +} + +int +runq_pending(struct runq *runq, void *arg, time_t *when) +{ + struct job *job; + + TAILQ_FOREACH(job, &runq->jobs, entry) { + if (job->arg == arg) { + if (when) + *when = job->when; + return (1); + } + } + + return (0); +} diff --git a/foobar/portable/smtpd/scheduler.c b/foobar/portable/smtpd/scheduler.c new file mode 100644 index 00000000..ea70a83d --- /dev/null +++ b/foobar/portable/smtpd/scheduler.c @@ -0,0 +1,618 @@ +/* $OpenBSD: scheduler.c,v 1.60 2018/12/30 23:09:58 guenther Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> + * Copyright (c) 2008-2009 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <dirent.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <grp.h> /* needed for setgroups */ +#include <imsg.h> +#include <inttypes.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <limits.h> + +#include "smtpd.h" +#include "log.h" + +static void scheduler_imsg(struct mproc *, struct imsg *); +static void scheduler_shutdown(void); +static void scheduler_reset_events(void); +static void scheduler_timeout(int, short, void *); + +static struct scheduler_backend *backend = NULL; +static struct event ev; +static size_t ninflight = 0; +static int *types; +static uint64_t *evpids; +static uint32_t *msgids; +static struct evpstate *state; + +extern const char *backend_scheduler; + +void +scheduler_imsg(struct mproc *p, struct imsg *imsg) +{ + struct bounce_req_msg req; + struct envelope evp; + struct scheduler_info si; + struct msg m; + uint64_t evpid, id, holdq; + uint32_t msgid; + uint32_t inflight; + size_t n, i; + time_t timestamp; + int v, r, type; + + if (imsg == NULL) + scheduler_shutdown(); + + switch (imsg->hdr.type) { + + case IMSG_QUEUE_ENVELOPE_SUBMIT: + m_msg(&m, imsg); + m_get_envelope(&m, &evp); + m_end(&m); + log_trace(TRACE_SCHEDULER, + "scheduler: inserting evp:%016" PRIx64, evp.id); + scheduler_info(&si, &evp); + stat_increment("scheduler.envelope.incoming", 1); + backend->insert(&si); + return; + + case IMSG_QUEUE_MESSAGE_COMMIT: + m_msg(&m, imsg); + m_get_msgid(&m, &msgid); + m_end(&m); + log_trace(TRACE_SCHEDULER, + "scheduler: committing msg:%08" PRIx32, msgid); + n = backend->commit(msgid); + stat_decrement("scheduler.envelope.incoming", n); + stat_increment("scheduler.envelope", n); + scheduler_reset_events(); + return; + + case IMSG_QUEUE_DISCOVER_EVPID: + m_msg(&m, imsg); + m_get_envelope(&m, &evp); + m_end(&m); + r = backend->query(evp.id); + if (r) { + log_debug("debug: scheduler: evp:%016" PRIx64 + " already scheduled", evp.id); + return; + } + log_trace(TRACE_SCHEDULER, + "scheduler: discovering evp:%016" PRIx64, evp.id); + scheduler_info(&si, &evp); + stat_increment("scheduler.envelope.incoming", 1); + backend->insert(&si); + return; + + case IMSG_QUEUE_DISCOVER_MSGID: + m_msg(&m, imsg); + m_get_msgid(&m, &msgid); + m_end(&m); + r = backend->query(msgid); + if (r) { + log_debug("debug: scheduler: msgid:%08" PRIx32 + " already scheduled", msgid); + return; + } + log_trace(TRACE_SCHEDULER, + "scheduler: committing msg:%08" PRIx32, msgid); + n = backend->commit(msgid); + stat_decrement("scheduler.envelope.incoming", n); + stat_increment("scheduler.envelope", n); + scheduler_reset_events(); + return; + + case IMSG_QUEUE_MESSAGE_ROLLBACK: + m_msg(&m, imsg); + m_get_msgid(&m, &msgid); + m_end(&m); + log_trace(TRACE_SCHEDULER, "scheduler: aborting msg:%08" PRIx32, + msgid); + n = backend->rollback(msgid); + stat_decrement("scheduler.envelope.incoming", n); + scheduler_reset_events(); + return; + + case IMSG_QUEUE_ENVELOPE_REMOVE: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_get_u32(&m, &inflight); + m_end(&m); + log_trace(TRACE_SCHEDULER, + "scheduler: queue requested removal of evp:%016" PRIx64, + evpid); + stat_decrement("scheduler.envelope", 1); + if (!inflight) + backend->remove(evpid); + else { + backend->delete(evpid); + ninflight -= 1; + stat_decrement("scheduler.envelope.inflight", 1); + } + + scheduler_reset_events(); + return; + + case IMSG_QUEUE_ENVELOPE_ACK: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_end(&m); + log_trace(TRACE_SCHEDULER, + "scheduler: queue ack removal of evp:%016" PRIx64, + evpid); + ninflight -= 1; + stat_decrement("scheduler.envelope.inflight", 1); + scheduler_reset_events(); + return; + + case IMSG_QUEUE_DELIVERY_OK: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_end(&m); + log_trace(TRACE_SCHEDULER, + "scheduler: deleting evp:%016" PRIx64 " (ok)", evpid); + backend->delete(evpid); + ninflight -= 1; + stat_increment("scheduler.delivery.ok", 1); + stat_decrement("scheduler.envelope.inflight", 1); + stat_decrement("scheduler.envelope", 1); + scheduler_reset_events(); + return; + + case IMSG_QUEUE_DELIVERY_TEMPFAIL: + m_msg(&m, imsg); + m_get_envelope(&m, &evp); + m_end(&m); + log_trace(TRACE_SCHEDULER, + "scheduler: updating evp:%016" PRIx64, evp.id); + scheduler_info(&si, &evp); + backend->update(&si); + ninflight -= 1; + stat_increment("scheduler.delivery.tempfail", 1); + stat_decrement("scheduler.envelope.inflight", 1); + + for (i = 0; i < MAX_BOUNCE_WARN; i++) { + if (env->sc_bounce_warn[i] == 0) + break; + timestamp = si.creation + env->sc_bounce_warn[i]; + if (si.nexttry >= timestamp && + si.lastbounce < timestamp) { + req.evpid = evp.id; + req.timestamp = timestamp; + req.bounce.type = B_DELAYED; + req.bounce.delay = env->sc_bounce_warn[i]; + req.bounce.ttl = si.ttl; + m_compose(p, IMSG_SCHED_ENVELOPE_BOUNCE, 0, 0, -1, + &req, sizeof req); + break; + } + } + scheduler_reset_events(); + return; + + case IMSG_QUEUE_DELIVERY_PERMFAIL: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_end(&m); + log_trace(TRACE_SCHEDULER, + "scheduler: deleting evp:%016" PRIx64 " (fail)", evpid); + backend->delete(evpid); + ninflight -= 1; + stat_increment("scheduler.delivery.permfail", 1); + stat_decrement("scheduler.envelope.inflight", 1); + stat_decrement("scheduler.envelope", 1); + scheduler_reset_events(); + return; + + case IMSG_QUEUE_DELIVERY_LOOP: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_end(&m); + log_trace(TRACE_SCHEDULER, + "scheduler: deleting evp:%016" PRIx64 " (loop)", evpid); + backend->delete(evpid); + ninflight -= 1; + stat_increment("scheduler.delivery.loop", 1); + stat_decrement("scheduler.envelope.inflight", 1); + stat_decrement("scheduler.envelope", 1); + scheduler_reset_events(); + return; + + case IMSG_QUEUE_HOLDQ_HOLD: + m_msg(&m, imsg); + m_get_evpid(&m, &evpid); + m_get_id(&m, &holdq); + m_end(&m); + log_trace(TRACE_SCHEDULER, + "scheduler: holding evp:%016" PRIx64 " on %016" PRIx64, + evpid, holdq); + backend->hold(evpid, holdq); + ninflight -= 1; + stat_decrement("scheduler.envelope.inflight", 1); + scheduler_reset_events(); + return; + + case IMSG_QUEUE_HOLDQ_RELEASE: + m_msg(&m, imsg); + m_get_int(&m, &type); + m_get_id(&m, &holdq); + m_get_int(&m, &r); + m_end(&m); + log_trace(TRACE_SCHEDULER, + "scheduler: releasing %d on holdq (%d, %016" PRIx64 ")", + r, type, holdq); + backend->release(type, holdq, r); + scheduler_reset_events(); + return; + + case IMSG_CTL_PAUSE_MDA: + log_trace(TRACE_SCHEDULER, "scheduler: pausing mda"); + env->sc_flags |= SMTPD_MDA_PAUSED; + return; + + case IMSG_CTL_RESUME_MDA: + log_trace(TRACE_SCHEDULER, "scheduler: resuming mda"); + env->sc_flags &= ~SMTPD_MDA_PAUSED; + scheduler_reset_events(); + return; + + case IMSG_CTL_PAUSE_MTA: + log_trace(TRACE_SCHEDULER, "scheduler: pausing mta"); + env->sc_flags |= SMTPD_MTA_PAUSED; + return; + + case IMSG_CTL_RESUME_MTA: + log_trace(TRACE_SCHEDULER, "scheduler: resuming mta"); + env->sc_flags &= ~SMTPD_MTA_PAUSED; + scheduler_reset_events(); + return; + + case IMSG_CTL_VERBOSE: + m_msg(&m, imsg); + m_get_int(&m, &v); + m_end(&m); + log_setverbose(v); + return; + + case IMSG_CTL_PROFILE: + m_msg(&m, imsg); + m_get_int(&m, &v); + m_end(&m); + profiling = v; + return; + + case IMSG_CTL_LIST_MESSAGES: + msgid = *(uint32_t *)(imsg->data); + n = backend->messages(msgid, msgids, env->sc_scheduler_max_msg_batch_size); + m_compose(p, IMSG_CTL_LIST_MESSAGES, imsg->hdr.peerid, 0, -1, + msgids, n * sizeof (*msgids)); + return; + + case IMSG_CTL_LIST_ENVELOPES: + id = *(uint64_t *)(imsg->data); + n = backend->envelopes(id, state, env->sc_scheduler_max_evp_batch_size); + for (i = 0; i < n; i++) { + m_create(p_queue, IMSG_CTL_LIST_ENVELOPES, + imsg->hdr.peerid, 0, -1); + m_add_evpid(p_queue, state[i].evpid); + m_add_int(p_queue, state[i].flags); + m_add_time(p_queue, state[i].time); + m_close(p_queue); + } + m_compose(p_queue, IMSG_CTL_LIST_ENVELOPES, + imsg->hdr.peerid, 0, -1, NULL, 0); + return; + + case IMSG_CTL_SCHEDULE: + id = *(uint64_t *)(imsg->data); + if (id <= 0xffffffffL) + log_debug("debug: scheduler: " + "scheduling msg:%08" PRIx64, id); + else + log_debug("debug: scheduler: " + "scheduling evp:%016" PRIx64, id); + r = backend->schedule(id); + scheduler_reset_events(); + m_compose(p, r ? IMSG_CTL_OK : IMSG_CTL_FAIL, imsg->hdr.peerid, + 0, -1, NULL, 0); + return; + + case IMSG_QUEUE_ENVELOPE_SCHEDULE: + id = *(uint64_t *)(imsg->data); + backend->schedule(id); + scheduler_reset_events(); + return; + + case IMSG_CTL_REMOVE: + id = *(uint64_t *)(imsg->data); + if (id <= 0xffffffffL) + log_debug("debug: scheduler: " + "removing msg:%08" PRIx64, id); + else + log_debug("debug: scheduler: " + "removing evp:%016" PRIx64, id); + r = backend->remove(id); + scheduler_reset_events(); + m_compose(p, r ? IMSG_CTL_OK : IMSG_CTL_FAIL, imsg->hdr.peerid, + 0, -1, NULL, 0); + return; + + case IMSG_CTL_PAUSE_EVP: + id = *(uint64_t *)(imsg->data); + if (id <= 0xffffffffL) + log_debug("debug: scheduler: " + "suspending msg:%08" PRIx64, id); + else + log_debug("debug: scheduler: " + "suspending evp:%016" PRIx64, id); + r = backend->suspend(id); + scheduler_reset_events(); + m_compose(p, r ? IMSG_CTL_OK : IMSG_CTL_FAIL, imsg->hdr.peerid, + 0, -1, NULL, 0); + return; + + case IMSG_CTL_RESUME_EVP: + id = *(uint64_t *)(imsg->data); + if (id <= 0xffffffffL) + log_debug("debug: scheduler: " + "resuming msg:%08" PRIx64, id); + else + log_debug("debug: scheduler: " + "resuming evp:%016" PRIx64, id); + r = backend->resume(id); + scheduler_reset_events(); + m_compose(p, r ? IMSG_CTL_OK : IMSG_CTL_FAIL, imsg->hdr.peerid, + 0, -1, NULL, 0); + return; + } + + errx(1, "scheduler_imsg: unexpected %s imsg", + imsg_to_str(imsg->hdr.type)); +} + +static void +scheduler_shutdown(void) +{ + log_debug("debug: scheduler agent exiting"); + _exit(0); +} + +static void +scheduler_reset_events(void) +{ + struct timeval tv; + + evtimer_del(&ev); + tv.tv_sec = 0; + tv.tv_usec = 0; + evtimer_add(&ev, &tv); +} + +int +scheduler(void) +{ + struct passwd *pw; + + backend = scheduler_backend_lookup(backend_scheduler); + if (backend == NULL) + errx(1, "cannot find scheduler backend \"%s\"", + backend_scheduler); + + purge_config(PURGE_EVERYTHING & ~PURGE_DISPATCHERS); + + if ((pw = getpwnam(SMTPD_USER)) == NULL) + fatalx("unknown user " SMTPD_USER); + + config_process(PROC_SCHEDULER); + + backend->init(backend_scheduler); + + if (chroot(PATH_CHROOT) == -1) + fatal("scheduler: chroot"); + if (chdir("/") == -1) + fatal("scheduler: chdir(\"/\")"); + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("scheduler: cannot drop privileges"); + + evpids = xcalloc(env->sc_scheduler_max_schedule, sizeof *evpids); + types = xcalloc(env->sc_scheduler_max_schedule, sizeof *types); + msgids = xcalloc(env->sc_scheduler_max_msg_batch_size, sizeof *msgids); + state = xcalloc(env->sc_scheduler_max_evp_batch_size, sizeof *state); + + imsg_callback = scheduler_imsg; + event_init(); + + signal(SIGINT, SIG_IGN); + signal(SIGTERM, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + + config_peer(PROC_CONTROL); + config_peer(PROC_QUEUE); + + evtimer_set(&ev, scheduler_timeout, NULL); + scheduler_reset_events(); + +#if HAVE_PLEDGE + if (pledge("stdio", NULL) == -1) + err(1, "pledge"); +#endif + + event_dispatch(); + fatalx("exited event loop"); + + return (0); +} + +static void +scheduler_timeout(int fd, short event, void *p) +{ + struct timeval tv; + size_t i; + size_t d_inflight; + size_t d_envelope; + size_t d_removed; + size_t d_expired; + size_t d_updated; + size_t count; + int mask, r, delay; + + tv.tv_sec = 0; + tv.tv_usec = 0; + + mask = SCHED_UPDATE; + + if (ninflight < env->sc_scheduler_max_inflight) { + mask |= SCHED_EXPIRE | SCHED_REMOVE | SCHED_BOUNCE; + if (!(env->sc_flags & SMTPD_MDA_PAUSED)) + mask |= SCHED_MDA; + if (!(env->sc_flags & SMTPD_MTA_PAUSED)) + mask |= SCHED_MTA; + } + + count = env->sc_scheduler_max_schedule; + + log_trace(TRACE_SCHEDULER, "scheduler: getting batch: mask=0x%x, count=%zu", mask, count); + + r = backend->batch(mask, &delay, &count, evpids, types); + + log_trace(TRACE_SCHEDULER, "scheduler: got r=%i, delay=%i, count=%zu", r, delay, count); + + if (r < 0) + fatalx("scheduler: error in batch handler"); + + if (r == 0) { + + if (delay < -1) + fatalx("scheduler: invalid delay %d", delay); + + if (delay == -1) { + log_trace(TRACE_SCHEDULER, "scheduler: sleeping"); + return; + } + + tv.tv_sec = delay; + tv.tv_usec = 0; + log_trace(TRACE_SCHEDULER, + "scheduler: waiting for %s", duration_to_text(tv.tv_sec)); + evtimer_add(&ev, &tv); + return; + } + + d_inflight = 0; + d_envelope = 0; + d_removed = 0; + d_expired = 0; + d_updated = 0; + + for (i = 0; i < count; i++) { + switch(types[i]) { + case SCHED_REMOVE: + log_debug("debug: scheduler: evp:%016" PRIx64 + " removed", evpids[i]); + m_create(p_queue, IMSG_SCHED_ENVELOPE_REMOVE, 0, 0, -1); + m_add_evpid(p_queue, evpids[i]); + m_close(p_queue); + d_envelope += 1; + d_removed += 1; + d_inflight += 1; + break; + + case SCHED_EXPIRE: + log_debug("debug: scheduler: evp:%016" PRIx64 + " expired", evpids[i]); + m_create(p_queue, IMSG_SCHED_ENVELOPE_EXPIRE, 0, 0, -1); + m_add_evpid(p_queue, evpids[i]); + m_close(p_queue); + d_envelope += 1; + d_expired += 1; + d_inflight += 1; + break; + + case SCHED_UPDATE: + log_debug("debug: scheduler: evp:%016" PRIx64 + " scheduled (update)", evpids[i]); + d_updated += 1; + break; + + case SCHED_BOUNCE: + log_debug("debug: scheduler: evp:%016" PRIx64 + " scheduled (bounce)", evpids[i]); + m_create(p_queue, IMSG_SCHED_ENVELOPE_INJECT, 0, 0, -1); + m_add_evpid(p_queue, evpids[i]); + m_close(p_queue); + d_inflight += 1; + break; + + case SCHED_MDA: + log_debug("debug: scheduler: evp:%016" PRIx64 + " scheduled (mda)", evpids[i]); + m_create(p_queue, IMSG_SCHED_ENVELOPE_DELIVER, 0, 0, -1); + m_add_evpid(p_queue, evpids[i]); + m_close(p_queue); + d_inflight += 1; + break; + + case SCHED_MTA: + log_debug("debug: scheduler: evp:%016" PRIx64 + " scheduled (mta)", evpids[i]); + m_create(p_queue, IMSG_SCHED_ENVELOPE_TRANSFER, 0, 0, -1); + m_add_evpid(p_queue, evpids[i]); + m_close(p_queue); + d_inflight += 1; + break; + } + } + + stat_decrement("scheduler.envelope", d_envelope); + stat_increment("scheduler.envelope.inflight", d_inflight); + stat_increment("scheduler.envelope.expired", d_expired); + stat_increment("scheduler.envelope.removed", d_removed); + stat_increment("scheduler.envelope.updated", d_updated); + + ninflight += d_inflight; + + tv.tv_sec = 0; + tv.tv_usec = 0; + evtimer_add(&ev, &tv); +} diff --git a/foobar/portable/smtpd/scheduler_backend.c b/foobar/portable/smtpd/scheduler_backend.c new file mode 100644 index 00000000..ad2b4cab --- /dev/null +++ b/foobar/portable/smtpd/scheduler_backend.c @@ -0,0 +1,82 @@ +/* $OpenBSD: scheduler_backend.c,v 1.16 2018/05/24 11:38:24 gilles Exp $ */ + +/* + * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> + +#include <ctype.h> +#include <err.h> +#include <event.h> +#include <fcntl.h> +#include <imsg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> + +#include "smtpd.h" +#include "log.h" + +extern struct scheduler_backend scheduler_backend_null; +extern struct scheduler_backend scheduler_backend_proc; +extern struct scheduler_backend scheduler_backend_ramqueue; + +struct scheduler_backend * +scheduler_backend_lookup(const char *name) +{ + if (!strcmp(name, "null")) + return &scheduler_backend_null; + if (!strcmp(name, "ramqueue")) + return &scheduler_backend_ramqueue; + + return &scheduler_backend_proc; +} + +void +scheduler_info(struct scheduler_info *sched, struct envelope *evp) +{ + struct dispatcher *disp; + + disp = evp->type == D_BOUNCE ? + env->sc_dispatcher_bounce : + dict_xget(env->sc_dispatchers, evp->dispatcher); + + switch (disp->type) { + case DISPATCHER_LOCAL: + sched->type = D_MDA; + break; + case DISPATCHER_REMOTE: + sched->type = D_MTA; + break; + case DISPATCHER_BOUNCE: + sched->type = D_BOUNCE; + break; + } + sched->ttl = disp->ttl ? disp->ttl : env->sc_ttl; + + sched->evpid = evp->id; + sched->creation = evp->creation; + sched->retry = evp->retry; + sched->lasttry = evp->lasttry; + sched->lastbounce = evp->lastbounce; + sched->nexttry = 0; +} diff --git a/foobar/portable/smtpd/scheduler_null.c b/foobar/portable/smtpd/scheduler_null.c new file mode 100644 index 00000000..40db6205 --- /dev/null +++ b/foobar/portable/smtpd/scheduler_null.c @@ -0,0 +1,164 @@ +/* $OpenBSD: scheduler_null.c,v 1.9 2015/01/20 17:37:54 deraadt Exp $ */ + +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> + +#include <ctype.h> +#include <err.h> +#include <event.h> +#include <fcntl.h> +#include <imsg.h> +#include <stdio.h> +#include <limits.h> + +#include "smtpd.h" + +static int scheduler_null_init(const char *); +static int scheduler_null_insert(struct scheduler_info *); +static size_t scheduler_null_commit(uint32_t); +static size_t scheduler_null_rollback(uint32_t); +static int scheduler_null_update(struct scheduler_info *); +static int scheduler_null_delete(uint64_t); +static int scheduler_null_hold(uint64_t, uint64_t); +static int scheduler_null_release(int, uint64_t, int); +static int scheduler_null_batch(int, int*, size_t*, uint64_t*, int*); +static size_t scheduler_null_messages(uint32_t, uint32_t *, size_t); +static size_t scheduler_null_envelopes(uint64_t, struct evpstate *, size_t); +static int scheduler_null_schedule(uint64_t); +static int scheduler_null_remove(uint64_t); +static int scheduler_null_suspend(uint64_t); +static int scheduler_null_resume(uint64_t); + +struct scheduler_backend scheduler_backend_null = { + scheduler_null_init, + + scheduler_null_insert, + scheduler_null_commit, + scheduler_null_rollback, + + scheduler_null_update, + scheduler_null_delete, + scheduler_null_hold, + scheduler_null_release, + + scheduler_null_batch, + + scheduler_null_messages, + scheduler_null_envelopes, + scheduler_null_schedule, + scheduler_null_remove, + scheduler_null_suspend, + scheduler_null_resume, +}; + +static int +scheduler_null_init(const char *arg) +{ + return (1); +} + +static int +scheduler_null_insert(struct scheduler_info *si) +{ + return (0); +} + +static size_t +scheduler_null_commit(uint32_t msgid) +{ + return (0); +} + +static size_t +scheduler_null_rollback(uint32_t msgid) +{ + return (0); +} + +static int +scheduler_null_update(struct scheduler_info *si) +{ + return (0); +} + +static int +scheduler_null_delete(uint64_t evpid) +{ + return (0); +} + +static int +scheduler_null_hold(uint64_t evpid, uint64_t holdq) +{ + return (0); +} + +static int +scheduler_null_release(int type, uint64_t holdq, int n) +{ + return (0); +} + +static int +scheduler_null_batch(int typemask, int *delay, size_t *count, uint64_t *evpids, int *types) +{ + *delay = 0; + + return (0); +} + +static int +scheduler_null_schedule(uint64_t evpid) +{ + return (0); +} + +static int +scheduler_null_remove(uint64_t evpid) +{ + return (0); +} + +static int +scheduler_null_suspend(uint64_t evpid) +{ + return (0); +} + +static int +scheduler_null_resume(uint64_t evpid) +{ + return (0); +} + +static size_t +scheduler_null_messages(uint32_t from, uint32_t *dst, size_t size) +{ + return (0); +} + +static size_t +scheduler_null_envelopes(uint64_t from, struct evpstate *dst, size_t size) +{ + return (0); +} diff --git a/foobar/portable/smtpd/scheduler_proc.c b/foobar/portable/smtpd/scheduler_proc.c new file mode 100644 index 00000000..5f4e8b70 --- /dev/null +++ b/foobar/portable/smtpd/scheduler_proc.c @@ -0,0 +1,446 @@ +/* $OpenBSD: scheduler_proc.c,v 1.8 2015/12/05 13:14:21 claudio Exp $ */ + +/* + * Copyright (c) 2013 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> + +#include <ctype.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <imsg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> + +#include "smtpd.h" +#include "log.h" + +static struct imsgbuf ibuf; +static struct imsg imsg; +static size_t rlen; +static char *rdata; + +static void +scheduler_proc_call(void) +{ + ssize_t n; + + if (imsg_flush(&ibuf) == -1) { + log_warn("warn: scheduler-proc: imsg_flush"); + fatalx("scheduler-proc: exiting"); + } + + while (1) { + if ((n = imsg_get(&ibuf, &imsg)) == -1) { + log_warn("warn: scheduler-proc: imsg_get"); + break; + } + if (n) { + rlen = imsg.hdr.len - IMSG_HEADER_SIZE; + rdata = imsg.data; + + if (imsg.hdr.type != PROC_SCHEDULER_OK) { + log_warnx("warn: scheduler-proc: bad response"); + break; + } + return; + } + + if ((n = imsg_read(&ibuf)) == -1 && errno != EAGAIN) { + log_warn("warn: scheduler-proc: imsg_read"); + break; + } + + if (n == 0) { + log_warnx("warn: scheduler-proc: pipe closed"); + break; + } + } + + fatalx("scheduler-proc: exiting"); +} + +static void +scheduler_proc_read(void *dst, size_t len) +{ + if (len > rlen) { + log_warnx("warn: scheduler-proc: bad msg len"); + fatalx("scheduler-proc: exiting"); + } + + memmove(dst, rdata, len); + rlen -= len; + rdata += len; +} + +static void +scheduler_proc_end(void) +{ + if (rlen) { + log_warnx("warn: scheduler-proc: bogus data"); + fatalx("scheduler-proc: exiting"); + } + imsg_free(&imsg); +} + +/* + * API + */ + +static int +scheduler_proc_init(const char *conf) +{ + int fd, r; + uint32_t version; + + fd = fork_proc_backend("scheduler", conf, "scheduler-proc"); + if (fd == -1) + fatalx("scheduler-proc: exiting"); + + imsg_init(&ibuf, fd); + + version = PROC_SCHEDULER_API_VERSION; + imsg_compose(&ibuf, PROC_SCHEDULER_INIT, 0, 0, -1, + &version, sizeof(version)); + scheduler_proc_call(); + scheduler_proc_read(&r, sizeof(r)); + scheduler_proc_end(); + + return (1); +} + +static int +scheduler_proc_insert(struct scheduler_info *si) +{ + int r; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_INSERT"); + + imsg_compose(&ibuf, PROC_SCHEDULER_INSERT, 0, 0, -1, si, sizeof(*si)); + + scheduler_proc_call(); + scheduler_proc_read(&r, sizeof(r)); + scheduler_proc_end(); + + return (r); +} + +static size_t +scheduler_proc_commit(uint32_t msgid) +{ + size_t s; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_COMMIT"); + + imsg_compose(&ibuf, PROC_SCHEDULER_COMMIT, 0, 0, -1, + &msgid, sizeof(msgid)); + + scheduler_proc_call(); + scheduler_proc_read(&s, sizeof(s)); + scheduler_proc_end(); + + return (s); +} + +static size_t +scheduler_proc_rollback(uint32_t msgid) +{ + size_t s; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_ROLLBACK"); + + imsg_compose(&ibuf, PROC_SCHEDULER_ROLLBACK, 0, 0, -1, + &msgid, sizeof(msgid)); + + scheduler_proc_call(); + scheduler_proc_read(&s, sizeof(s)); + scheduler_proc_end(); + + return (s); +} + +static int +scheduler_proc_update(struct scheduler_info *si) +{ + int r; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_UPDATE"); + + imsg_compose(&ibuf, PROC_SCHEDULER_UPDATE, 0, 0, -1, si, sizeof(*si)); + + scheduler_proc_call(); + scheduler_proc_read(&r, sizeof(r)); + if (r == 1) + scheduler_proc_read(si, sizeof(*si)); + scheduler_proc_end(); + + return (r); +} + +static int +scheduler_proc_delete(uint64_t evpid) +{ + int r; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_DELETE"); + + imsg_compose(&ibuf, PROC_SCHEDULER_DELETE, 0, 0, -1, + &evpid, sizeof(evpid)); + + scheduler_proc_call(); + scheduler_proc_read(&r, sizeof(r)); + scheduler_proc_end(); + + return (r); +} + +static int +scheduler_proc_hold(uint64_t evpid, uint64_t holdq) +{ + struct ibuf *buf; + int r; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_HOLD"); + + buf = imsg_create(&ibuf, PROC_SCHEDULER_HOLD, 0, 0, + sizeof(evpid) + sizeof(holdq)); + if (buf == NULL) + return (-1); + if (imsg_add(buf, &evpid, sizeof(evpid)) == -1) + return (-1); + if (imsg_add(buf, &holdq, sizeof(holdq)) == -1) + return (-1); + imsg_close(&ibuf, buf); + + scheduler_proc_call(); + + scheduler_proc_read(&r, sizeof(r)); + scheduler_proc_end(); + + return (r); +} + +static int +scheduler_proc_release(int type, uint64_t holdq, int n) +{ + struct ibuf *buf; + int r; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_RELEASE"); + + buf = imsg_create(&ibuf, PROC_SCHEDULER_RELEASE, 0, 0, + sizeof(holdq) + sizeof(n)); + if (buf == NULL) + return (-1); + if (imsg_add(buf, &type, sizeof(type)) == -1) + return (-1); + if (imsg_add(buf, &holdq, sizeof(holdq)) == -1) + return (-1); + if (imsg_add(buf, &n, sizeof(n)) == -1) + return (-1); + imsg_close(&ibuf, buf); + + scheduler_proc_call(); + + scheduler_proc_read(&r, sizeof(r)); + scheduler_proc_end(); + + return (r); +} + +static int +scheduler_proc_batch(int typemask, int *delay, size_t *count, uint64_t *evpids, int *types) +{ + struct ibuf *buf; + int r; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_BATCH"); + + buf = imsg_create(&ibuf, PROC_SCHEDULER_BATCH, 0, 0, + sizeof(typemask) + sizeof(*count)); + if (buf == NULL) + return (-1); + if (imsg_add(buf, &typemask, sizeof(typemask)) == -1) + return (-1); + if (imsg_add(buf, count, sizeof(*count)) == -1) + return (-1); + imsg_close(&ibuf, buf); + + scheduler_proc_call(); + scheduler_proc_read(&r, sizeof(r)); + scheduler_proc_read(delay, sizeof(*delay)); + scheduler_proc_read(count, sizeof(*count)); + if (r > 0) { + scheduler_proc_read(evpids, sizeof(*evpids) * (*count)); + scheduler_proc_read(types, sizeof(*types) * (*count)); + } + scheduler_proc_end(); + + return (r); +} + +static size_t +scheduler_proc_messages(uint32_t from, uint32_t *dst, size_t size) +{ + struct ibuf *buf; + size_t s; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_MESSAGES"); + + buf = imsg_create(&ibuf, PROC_SCHEDULER_MESSAGES, 0, 0, + sizeof(from) + sizeof(size)); + if (buf == NULL) + return (-1); + if (imsg_add(buf, &from, sizeof(from)) == -1) + return (-1); + if (imsg_add(buf, &size, sizeof(size)) == -1) + return (-1); + imsg_close(&ibuf, buf); + + scheduler_proc_call(); + + s = rlen / sizeof(*dst); + scheduler_proc_read(dst, s * sizeof(*dst)); + scheduler_proc_end(); + + return (s); +} + +static size_t +scheduler_proc_envelopes(uint64_t from, struct evpstate *dst, size_t size) +{ + struct ibuf *buf; + size_t s; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_ENVELOPES"); + + buf = imsg_create(&ibuf, PROC_SCHEDULER_ENVELOPES, 0, 0, + sizeof(from) + sizeof(size)); + if (buf == NULL) + return (-1); + if (imsg_add(buf, &from, sizeof(from)) == -1) + return (-1); + if (imsg_add(buf, &size, sizeof(size)) == -1) + return (-1); + imsg_close(&ibuf, buf); + + scheduler_proc_call(); + + s = rlen / sizeof(*dst); + scheduler_proc_read(dst, s * sizeof(*dst)); + scheduler_proc_end(); + + return (s); +} + +static int +scheduler_proc_schedule(uint64_t evpid) +{ + int r; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_SCHEDULE"); + + imsg_compose(&ibuf, PROC_SCHEDULER_SCHEDULE, 0, 0, -1, + &evpid, sizeof(evpid)); + + scheduler_proc_call(); + + scheduler_proc_read(&r, sizeof(r)); + scheduler_proc_end(); + + return (r); +} + +static int +scheduler_proc_remove(uint64_t evpid) +{ + int r; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_REMOVE"); + + imsg_compose(&ibuf, PROC_SCHEDULER_REMOVE, 0, 0, -1, + &evpid, sizeof(evpid)); + + scheduler_proc_call(); + + scheduler_proc_read(&r, sizeof(r)); + scheduler_proc_end(); + + return (r); +} + +static int +scheduler_proc_suspend(uint64_t evpid) +{ + int r; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_SUSPEND"); + + imsg_compose(&ibuf, PROC_SCHEDULER_SUSPEND, 0, 0, -1, + &evpid, sizeof(evpid)); + + scheduler_proc_call(); + + scheduler_proc_read(&r, sizeof(r)); + scheduler_proc_end(); + + return (r); +} + +static int +scheduler_proc_resume(uint64_t evpid) +{ + int r; + + log_debug("debug: scheduler-proc: PROC_SCHEDULER_RESUME"); + + imsg_compose(&ibuf, PROC_SCHEDULER_RESUME, 0, 0, -1, + &evpid, sizeof(evpid)); + + scheduler_proc_call(); + + scheduler_proc_read(&r, sizeof(r)); + scheduler_proc_end(); + + return (r); +} + +struct scheduler_backend scheduler_backend_proc = { + scheduler_proc_init, + scheduler_proc_insert, + scheduler_proc_commit, + scheduler_proc_rollback, + scheduler_proc_update, + scheduler_proc_delete, + scheduler_proc_hold, + scheduler_proc_release, + scheduler_proc_batch, + scheduler_proc_messages, + scheduler_proc_envelopes, + scheduler_proc_schedule, + scheduler_proc_remove, + scheduler_proc_suspend, + scheduler_proc_resume, +}; diff --git a/foobar/portable/smtpd/scheduler_ramqueue.c b/foobar/portable/smtpd/scheduler_ramqueue.c new file mode 100644 index 00000000..0c04fc0b --- /dev/null +++ b/foobar/portable/smtpd/scheduler_ramqueue.c @@ -0,0 +1,1204 @@ +/* $OpenBSD: scheduler_ramqueue.c,v 1.45 2018/05/31 21:06:12 gilles Exp $ */ + +/* + * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> + +#include <ctype.h> +#include <err.h> +#include <event.h> +#include <fcntl.h> +#include <imsg.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#include <time.h> + +#include "smtpd.h" +#include "log.h" + +TAILQ_HEAD(evplist, rq_envelope); + +struct rq_message { + uint32_t msgid; + struct tree envelopes; +}; + +struct rq_envelope { + TAILQ_ENTRY(rq_envelope) entry; + SPLAY_ENTRY(rq_envelope) t_entry; + + uint64_t evpid; + uint64_t holdq; + enum delivery_type type; + +#define RQ_EVPSTATE_PENDING 0 +#define RQ_EVPSTATE_SCHEDULED 1 +#define RQ_EVPSTATE_INFLIGHT 2 +#define RQ_EVPSTATE_HELD 3 + uint8_t state; + +#define RQ_ENVELOPE_EXPIRED 0x01 +#define RQ_ENVELOPE_REMOVED 0x02 +#define RQ_ENVELOPE_SUSPEND 0x04 +#define RQ_ENVELOPE_UPDATE 0x08 +#define RQ_ENVELOPE_OVERFLOW 0x10 + uint8_t flags; + + time_t ctime; + time_t sched; + time_t expire; + + struct rq_message *message; + + time_t t_inflight; + time_t t_scheduled; +}; + +struct rq_holdq { + struct evplist q; + size_t count; +}; + +struct rq_queue { + size_t evpcount; + struct tree messages; + SPLAY_HEAD(prioqtree, rq_envelope) q_priotree; + + struct evplist q_pending; + struct evplist q_inflight; + + struct evplist q_mta; + struct evplist q_mda; + struct evplist q_bounce; + struct evplist q_update; + struct evplist q_expired; + struct evplist q_removed; +}; + +static int rq_envelope_cmp(struct rq_envelope *, struct rq_envelope *); + +SPLAY_PROTOTYPE(prioqtree, rq_envelope, t_entry, rq_envelope_cmp); +static int scheduler_ram_init(const char *); +static int scheduler_ram_insert(struct scheduler_info *); +static size_t scheduler_ram_commit(uint32_t); +static size_t scheduler_ram_rollback(uint32_t); +static int scheduler_ram_update(struct scheduler_info *); +static int scheduler_ram_delete(uint64_t); +static int scheduler_ram_hold(uint64_t, uint64_t); +static int scheduler_ram_release(int, uint64_t, int); +static int scheduler_ram_batch(int, int *, size_t *, uint64_t *, int *); +static size_t scheduler_ram_messages(uint32_t, uint32_t *, size_t); +static size_t scheduler_ram_envelopes(uint64_t, struct evpstate *, size_t); +static int scheduler_ram_schedule(uint64_t); +static int scheduler_ram_remove(uint64_t); +static int scheduler_ram_suspend(uint64_t); +static int scheduler_ram_resume(uint64_t); +static int scheduler_ram_query(uint64_t); + +static void sorted_insert(struct rq_queue *, struct rq_envelope *); + +static void rq_queue_init(struct rq_queue *); +static void rq_queue_merge(struct rq_queue *, struct rq_queue *); +static void rq_queue_dump(struct rq_queue *, const char *); +static void rq_queue_schedule(struct rq_queue *rq); +static struct evplist *rq_envelope_list(struct rq_queue *, struct rq_envelope *); +static void rq_envelope_schedule(struct rq_queue *, struct rq_envelope *); +static int rq_envelope_remove(struct rq_queue *, struct rq_envelope *); +static int rq_envelope_suspend(struct rq_queue *, struct rq_envelope *); +static int rq_envelope_resume(struct rq_queue *, struct rq_envelope *); +static void rq_envelope_delete(struct rq_queue *, struct rq_envelope *); +static const char *rq_envelope_to_text(struct rq_envelope *); + +struct scheduler_backend scheduler_backend_ramqueue = { + scheduler_ram_init, + + scheduler_ram_insert, + scheduler_ram_commit, + scheduler_ram_rollback, + + scheduler_ram_update, + scheduler_ram_delete, + scheduler_ram_hold, + scheduler_ram_release, + + scheduler_ram_batch, + + scheduler_ram_messages, + scheduler_ram_envelopes, + scheduler_ram_schedule, + scheduler_ram_remove, + scheduler_ram_suspend, + scheduler_ram_resume, + scheduler_ram_query, +}; + +static struct rq_queue ramqueue; +static struct tree updates; +static struct tree holdqs[3]; /* delivery type */ + +static time_t currtime; + +#define BACKOFF_TRANSFER 400 +#define BACKOFF_DELIVERY 10 +#define BACKOFF_OVERFLOW 3 + +static time_t +scheduler_backoff(time_t t0, time_t base, uint32_t step) +{ + return (t0 + base * step * step); +} + +static time_t +scheduler_next(time_t t0, time_t base, uint32_t step) +{ + time_t t; + + /* XXX be more efficient */ + while ((t = scheduler_backoff(t0, base, step)) <= currtime) + step++; + + return (t); +} + +static int +scheduler_ram_init(const char *arg) +{ + rq_queue_init(&ramqueue); + tree_init(&updates); + tree_init(&holdqs[D_MDA]); + tree_init(&holdqs[D_MTA]); + tree_init(&holdqs[D_BOUNCE]); + + return (1); +} + +static int +scheduler_ram_insert(struct scheduler_info *si) +{ + struct rq_queue *update; + struct rq_message *message; + struct rq_envelope *envelope; + uint32_t msgid; + + currtime = time(NULL); + + msgid = evpid_to_msgid(si->evpid); + + /* find/prepare a ramqueue update */ + if ((update = tree_get(&updates, msgid)) == NULL) { + update = xcalloc(1, sizeof *update); + stat_increment("scheduler.ramqueue.update", 1); + rq_queue_init(update); + tree_xset(&updates, msgid, update); + } + + /* find/prepare the msgtree message in ramqueue update */ + if ((message = tree_get(&update->messages, msgid)) == NULL) { + message = xcalloc(1, sizeof *message); + message->msgid = msgid; + tree_init(&message->envelopes); + tree_xset(&update->messages, msgid, message); + stat_increment("scheduler.ramqueue.message", 1); + } + + /* create envelope in ramqueue message */ + envelope = xcalloc(1, sizeof *envelope); + envelope->evpid = si->evpid; + envelope->type = si->type; + envelope->message = message; + envelope->ctime = si->creation; + envelope->expire = si->creation + si->ttl; + envelope->sched = scheduler_backoff(si->creation, + (si->type == D_MTA) ? BACKOFF_TRANSFER : BACKOFF_DELIVERY, si->retry); + tree_xset(&message->envelopes, envelope->evpid, envelope); + + update->evpcount++; + stat_increment("scheduler.ramqueue.envelope", 1); + + envelope->state = RQ_EVPSTATE_PENDING; + TAILQ_INSERT_TAIL(&update->q_pending, envelope, entry); + + si->nexttry = envelope->sched; + + return (1); +} + +static size_t +scheduler_ram_commit(uint32_t msgid) +{ + struct rq_queue *update; + size_t r; + + currtime = time(NULL); + + update = tree_xpop(&updates, msgid); + r = update->evpcount; + + if (tracing & TRACE_SCHEDULER) + rq_queue_dump(update, "update to commit"); + + rq_queue_merge(&ramqueue, update); + + if (tracing & TRACE_SCHEDULER) + rq_queue_dump(&ramqueue, "resulting queue"); + + rq_queue_schedule(&ramqueue); + + free(update); + stat_decrement("scheduler.ramqueue.update", 1); + + return (r); +} + +static size_t +scheduler_ram_rollback(uint32_t msgid) +{ + struct rq_queue *update; + struct rq_envelope *evp; + size_t r; + + currtime = time(NULL); + + if ((update = tree_pop(&updates, msgid)) == NULL) + return (0); + r = update->evpcount; + + while ((evp = TAILQ_FIRST(&update->q_pending))) { + TAILQ_REMOVE(&update->q_pending, evp, entry); + rq_envelope_delete(update, evp); + } + + free(update); + stat_decrement("scheduler.ramqueue.update", 1); + + return (r); +} + +static int +scheduler_ram_update(struct scheduler_info *si) +{ + struct rq_message *msg; + struct rq_envelope *evp; + uint32_t msgid; + + currtime = time(NULL); + + msgid = evpid_to_msgid(si->evpid); + msg = tree_xget(&ramqueue.messages, msgid); + evp = tree_xget(&msg->envelopes, si->evpid); + + /* it *must* be in-flight */ + if (evp->state != RQ_EVPSTATE_INFLIGHT) + errx(1, "evp:%016" PRIx64 " not in-flight", si->evpid); + + TAILQ_REMOVE(&ramqueue.q_inflight, evp, entry); + + /* + * If the envelope was removed while inflight, schedule it for + * removal immediately. + */ + if (evp->flags & RQ_ENVELOPE_REMOVED) { + TAILQ_INSERT_TAIL(&ramqueue.q_removed, evp, entry); + evp->state = RQ_EVPSTATE_SCHEDULED; + evp->t_scheduled = currtime; + return (1); + } + + evp->sched = scheduler_next(evp->ctime, + (si->type == D_MTA) ? BACKOFF_TRANSFER : BACKOFF_DELIVERY, si->retry); + + evp->state = RQ_EVPSTATE_PENDING; + if (!(evp->flags & RQ_ENVELOPE_SUSPEND)) + sorted_insert(&ramqueue, evp); + + si->nexttry = evp->sched; + + return (1); +} + +static int +scheduler_ram_delete(uint64_t evpid) +{ + struct rq_message *msg; + struct rq_envelope *evp; + uint32_t msgid; + + currtime = time(NULL); + + msgid = evpid_to_msgid(evpid); + msg = tree_xget(&ramqueue.messages, msgid); + evp = tree_xget(&msg->envelopes, evpid); + + /* it *must* be in-flight */ + if (evp->state != RQ_EVPSTATE_INFLIGHT) + errx(1, "evp:%016" PRIx64 " not in-flight", evpid); + + TAILQ_REMOVE(&ramqueue.q_inflight, evp, entry); + + rq_envelope_delete(&ramqueue, evp); + + return (1); +} + +#define HOLDQ_MAXSIZE 1000 + +static int +scheduler_ram_hold(uint64_t evpid, uint64_t holdq) +{ + struct rq_holdq *hq; + struct rq_message *msg; + struct rq_envelope *evp; + uint32_t msgid; + + currtime = time(NULL); + + msgid = evpid_to_msgid(evpid); + msg = tree_xget(&ramqueue.messages, msgid); + evp = tree_xget(&msg->envelopes, evpid); + + /* it *must* be in-flight */ + if (evp->state != RQ_EVPSTATE_INFLIGHT) + errx(1, "evp:%016" PRIx64 " not in-flight", evpid); + + TAILQ_REMOVE(&ramqueue.q_inflight, evp, entry); + + /* If the envelope is suspended, just mark it as pending */ + if (evp->flags & RQ_ENVELOPE_SUSPEND) { + evp->state = RQ_EVPSTATE_PENDING; + return (0); + } + + hq = tree_get(&holdqs[evp->type], holdq); + if (hq == NULL) { + hq = xcalloc(1, sizeof(*hq)); + TAILQ_INIT(&hq->q); + tree_xset(&holdqs[evp->type], holdq, hq); + stat_increment("scheduler.ramqueue.holdq", 1); + } + + /* If the holdq is full, just "tempfail" the envelope */ + if (hq->count >= HOLDQ_MAXSIZE) { + evp->state = RQ_EVPSTATE_PENDING; + evp->flags |= RQ_ENVELOPE_UPDATE; + evp->flags |= RQ_ENVELOPE_OVERFLOW; + sorted_insert(&ramqueue, evp); + stat_increment("scheduler.ramqueue.hold-overflow", 1); + return (0); + } + + evp->state = RQ_EVPSTATE_HELD; + evp->holdq = holdq; + /* This is an optimization: upon release, the envelopes will be + * inserted in the pending queue from the first element to the last. + * Since elements already in the queue were received first, they + * were scheduled first, so they will be reinserted before the + * current element. + */ + TAILQ_INSERT_HEAD(&hq->q, evp, entry); + hq->count += 1; + stat_increment("scheduler.ramqueue.hold", 1); + + return (1); +} + +static int +scheduler_ram_release(int type, uint64_t holdq, int n) +{ + struct rq_holdq *hq; + struct rq_envelope *evp; + int i, update; + + currtime = time(NULL); + + hq = tree_get(&holdqs[type], holdq); + if (hq == NULL) + return (0); + + if (n == -1) { + n = 0; + update = 1; + } + else + update = 0; + + for (i = 0; n == 0 || i < n; i++) { + evp = TAILQ_FIRST(&hq->q); + if (evp == NULL) + break; + + TAILQ_REMOVE(&hq->q, evp, entry); + hq->count -= 1; + evp->holdq = 0; + + /* When released, all envelopes are put in the pending queue + * and will be rescheduled immediately. As an optimization, + * we could just schedule them directly. + */ + evp->state = RQ_EVPSTATE_PENDING; + if (update) + evp->flags |= RQ_ENVELOPE_UPDATE; + sorted_insert(&ramqueue, evp); + } + + if (TAILQ_EMPTY(&hq->q)) { + tree_xpop(&holdqs[type], holdq); + free(hq); + stat_decrement("scheduler.ramqueue.holdq", 1); + } + stat_decrement("scheduler.ramqueue.hold", i); + + return (i); +} + +static int +scheduler_ram_batch(int mask, int *delay, size_t *count, uint64_t *evpids, int *types) +{ + struct rq_envelope *evp; + size_t i, n; + time_t t; + + currtime = time(NULL); + + rq_queue_schedule(&ramqueue); + if (tracing & TRACE_SCHEDULER) + rq_queue_dump(&ramqueue, "scheduler_ram_batch()"); + + i = 0; + n = 0; + + for (;;) { + + if (mask & SCHED_REMOVE && (evp = TAILQ_FIRST(&ramqueue.q_removed))) { + TAILQ_REMOVE(&ramqueue.q_removed, evp, entry); + types[i] = SCHED_REMOVE; + evpids[i] = evp->evpid; + rq_envelope_delete(&ramqueue, evp); + + if (++i == *count) + break; + } + + if (mask & SCHED_EXPIRE && (evp = TAILQ_FIRST(&ramqueue.q_expired))) { + TAILQ_REMOVE(&ramqueue.q_expired, evp, entry); + types[i] = SCHED_EXPIRE; + evpids[i] = evp->evpid; + rq_envelope_delete(&ramqueue, evp); + + if (++i == *count) + break; + } + + if (mask & SCHED_UPDATE && (evp = TAILQ_FIRST(&ramqueue.q_update))) { + TAILQ_REMOVE(&ramqueue.q_update, evp, entry); + types[i] = SCHED_UPDATE; + evpids[i] = evp->evpid; + + if (evp->flags & RQ_ENVELOPE_OVERFLOW) + t = BACKOFF_OVERFLOW; + else if (evp->type == D_MTA) + t = BACKOFF_TRANSFER; + else + t = BACKOFF_DELIVERY; + + evp->sched = scheduler_next(evp->ctime, t, 0); + evp->flags &= ~(RQ_ENVELOPE_UPDATE|RQ_ENVELOPE_OVERFLOW); + evp->state = RQ_EVPSTATE_PENDING; + if (!(evp->flags & RQ_ENVELOPE_SUSPEND)) + sorted_insert(&ramqueue, evp); + + if (++i == *count) + break; + } + + if (mask & SCHED_BOUNCE && (evp = TAILQ_FIRST(&ramqueue.q_bounce))) { + TAILQ_REMOVE(&ramqueue.q_bounce, evp, entry); + types[i] = SCHED_BOUNCE; + evpids[i] = evp->evpid; + + TAILQ_INSERT_TAIL(&ramqueue.q_inflight, evp, entry); + evp->state = RQ_EVPSTATE_INFLIGHT; + evp->t_inflight = currtime; + + if (++i == *count) + break; + } + + if (mask & SCHED_MDA && (evp = TAILQ_FIRST(&ramqueue.q_mda))) { + TAILQ_REMOVE(&ramqueue.q_mda, evp, entry); + types[i] = SCHED_MDA; + evpids[i] = evp->evpid; + + TAILQ_INSERT_TAIL(&ramqueue.q_inflight, evp, entry); + evp->state = RQ_EVPSTATE_INFLIGHT; + evp->t_inflight = currtime; + + if (++i == *count) + break; + } + + if (mask & SCHED_MTA && (evp = TAILQ_FIRST(&ramqueue.q_mta))) { + TAILQ_REMOVE(&ramqueue.q_mta, evp, entry); + types[i] = SCHED_MTA; + evpids[i] = evp->evpid; + + TAILQ_INSERT_TAIL(&ramqueue.q_inflight, evp, entry); + evp->state = RQ_EVPSTATE_INFLIGHT; + evp->t_inflight = currtime; + + if (++i == *count) + break; + } + + /* nothing seen this round */ + if (i == n) + break; + + n = i; + } + + if (i) { + *count = i; + return (1); + } + + if ((evp = TAILQ_FIRST(&ramqueue.q_pending))) { + if (evp->sched < evp->expire) + t = evp->sched; + else + t = evp->expire; + *delay = (t < currtime) ? 0 : (t - currtime); + } + else + *delay = -1; + + return (0); +} + +static size_t +scheduler_ram_messages(uint32_t from, uint32_t *dst, size_t size) +{ + uint64_t id; + size_t n; + void *i; + + for (n = 0, i = NULL; n < size; n++) { + if (tree_iterfrom(&ramqueue.messages, &i, from, &id, NULL) == 0) + break; + dst[n] = id; + } + + return (n); +} + +static size_t +scheduler_ram_envelopes(uint64_t from, struct evpstate *dst, size_t size) +{ + struct rq_message *msg; + struct rq_envelope *evp; + void *i; + size_t n; + + if ((msg = tree_get(&ramqueue.messages, evpid_to_msgid(from))) == NULL) + return (0); + + for (n = 0, i = NULL; n < size; ) { + + if (tree_iterfrom(&msg->envelopes, &i, from, NULL, + (void**)&evp) == 0) + break; + + if (evp->flags & (RQ_ENVELOPE_REMOVED | RQ_ENVELOPE_EXPIRED)) + continue; + + dst[n].evpid = evp->evpid; + dst[n].flags = 0; + dst[n].retry = 0; + dst[n].time = 0; + + if (evp->state == RQ_EVPSTATE_PENDING) { + dst[n].time = evp->sched; + dst[n].flags = EF_PENDING; + } + else if (evp->state == RQ_EVPSTATE_SCHEDULED) { + dst[n].time = evp->t_scheduled; + dst[n].flags = EF_PENDING; + } + else if (evp->state == RQ_EVPSTATE_INFLIGHT) { + dst[n].time = evp->t_inflight; + dst[n].flags = EF_INFLIGHT; + } + else if (evp->state == RQ_EVPSTATE_HELD) { + /* same as scheduled */ + dst[n].time = evp->t_scheduled; + dst[n].flags = EF_PENDING; + dst[n].flags |= EF_HOLD; + } + if (evp->flags & RQ_ENVELOPE_SUSPEND) + dst[n].flags |= EF_SUSPEND; + + n++; + } + + return (n); +} + +static int +scheduler_ram_schedule(uint64_t evpid) +{ + struct rq_message *msg; + struct rq_envelope *evp; + uint32_t msgid; + void *i; + int r; + + currtime = time(NULL); + + if (evpid > 0xffffffff) { + msgid = evpid_to_msgid(evpid); + if ((msg = tree_get(&ramqueue.messages, msgid)) == NULL) + return (0); + if ((evp = tree_get(&msg->envelopes, evpid)) == NULL) + return (0); + if (evp->state == RQ_EVPSTATE_INFLIGHT) + return (0); + rq_envelope_schedule(&ramqueue, evp); + return (1); + } + else { + msgid = evpid; + if ((msg = tree_get(&ramqueue.messages, msgid)) == NULL) + return (0); + i = NULL; + r = 0; + while (tree_iter(&msg->envelopes, &i, NULL, (void*)(&evp))) { + if (evp->state == RQ_EVPSTATE_INFLIGHT) + continue; + rq_envelope_schedule(&ramqueue, evp); + r++; + } + return (r); + } +} + +static int +scheduler_ram_remove(uint64_t evpid) +{ + struct rq_message *msg; + struct rq_envelope *evp; + uint32_t msgid; + void *i; + int r; + + currtime = time(NULL); + + if (evpid > 0xffffffff) { + msgid = evpid_to_msgid(evpid); + if ((msg = tree_get(&ramqueue.messages, msgid)) == NULL) + return (0); + if ((evp = tree_get(&msg->envelopes, evpid)) == NULL) + return (0); + if (rq_envelope_remove(&ramqueue, evp)) + return (1); + return (0); + } + else { + msgid = evpid; + if ((msg = tree_get(&ramqueue.messages, msgid)) == NULL) + return (0); + i = NULL; + r = 0; + while (tree_iter(&msg->envelopes, &i, NULL, (void*)(&evp))) + if (rq_envelope_remove(&ramqueue, evp)) + r++; + return (r); + } +} + +static int +scheduler_ram_suspend(uint64_t evpid) +{ + struct rq_message *msg; + struct rq_envelope *evp; + uint32_t msgid; + void *i; + int r; + + currtime = time(NULL); + + if (evpid > 0xffffffff) { + msgid = evpid_to_msgid(evpid); + if ((msg = tree_get(&ramqueue.messages, msgid)) == NULL) + return (0); + if ((evp = tree_get(&msg->envelopes, evpid)) == NULL) + return (0); + if (rq_envelope_suspend(&ramqueue, evp)) + return (1); + return (0); + } + else { + msgid = evpid; + if ((msg = tree_get(&ramqueue.messages, msgid)) == NULL) + return (0); + i = NULL; + r = 0; + while (tree_iter(&msg->envelopes, &i, NULL, (void*)(&evp))) + if (rq_envelope_suspend(&ramqueue, evp)) + r++; + return (r); + } +} + +static int +scheduler_ram_resume(uint64_t evpid) +{ + struct rq_message *msg; + struct rq_envelope *evp; + uint32_t msgid; + void *i; + int r; + + currtime = time(NULL); + + if (evpid > 0xffffffff) { + msgid = evpid_to_msgid(evpid); + if ((msg = tree_get(&ramqueue.messages, msgid)) == NULL) + return (0); + if ((evp = tree_get(&msg->envelopes, evpid)) == NULL) + return (0); + if (rq_envelope_resume(&ramqueue, evp)) + return (1); + return (0); + } + else { + msgid = evpid; + if ((msg = tree_get(&ramqueue.messages, msgid)) == NULL) + return (0); + i = NULL; + r = 0; + while (tree_iter(&msg->envelopes, &i, NULL, (void*)(&evp))) + if (rq_envelope_resume(&ramqueue, evp)) + r++; + return (r); + } +} + +static int +scheduler_ram_query(uint64_t evpid) +{ + uint32_t msgid; + + if (evpid > 0xffffffff) + msgid = evpid_to_msgid(evpid); + else + msgid = evpid; + + if (tree_get(&ramqueue.messages, msgid) == NULL) + return (0); + + return (1); +} + +static void +sorted_insert(struct rq_queue *rq, struct rq_envelope *evp) +{ + struct rq_envelope *evp2; + + SPLAY_INSERT(prioqtree, &rq->q_priotree, evp); + evp2 = SPLAY_NEXT(prioqtree, &rq->q_priotree, evp); + if (evp2) + TAILQ_INSERT_BEFORE(evp2, evp, entry); + else + TAILQ_INSERT_TAIL(&rq->q_pending, evp, entry); +} + +static void +rq_queue_init(struct rq_queue *rq) +{ + memset(rq, 0, sizeof *rq); + tree_init(&rq->messages); + TAILQ_INIT(&rq->q_pending); + TAILQ_INIT(&rq->q_inflight); + TAILQ_INIT(&rq->q_mta); + TAILQ_INIT(&rq->q_mda); + TAILQ_INIT(&rq->q_bounce); + TAILQ_INIT(&rq->q_update); + TAILQ_INIT(&rq->q_expired); + TAILQ_INIT(&rq->q_removed); + SPLAY_INIT(&rq->q_priotree); +} + +static void +rq_queue_merge(struct rq_queue *rq, struct rq_queue *update) +{ + struct rq_message *message, *tomessage; + struct rq_envelope *envelope; + uint64_t id; + void *i; + + while (tree_poproot(&update->messages, &id, (void*)&message)) { + if ((tomessage = tree_get(&rq->messages, id)) == NULL) { + /* message does not exist. re-use structure */ + tree_xset(&rq->messages, id, message); + continue; + } + /* need to re-link all envelopes before merging them */ + i = NULL; + while ((tree_iter(&message->envelopes, &i, &id, + (void*)&envelope))) + envelope->message = tomessage; + tree_merge(&tomessage->envelopes, &message->envelopes); + free(message); + stat_decrement("scheduler.ramqueue.message", 1); + } + + /* Sorted insert in the pending queue */ + while ((envelope = TAILQ_FIRST(&update->q_pending))) { + TAILQ_REMOVE(&update->q_pending, envelope, entry); + sorted_insert(rq, envelope); + } + + rq->evpcount += update->evpcount; +} + +#define SCHEDULEMAX 1024 + +static void +rq_queue_schedule(struct rq_queue *rq) +{ + struct rq_envelope *evp; + size_t n; + + n = 0; + while ((evp = TAILQ_FIRST(&rq->q_pending))) { + if (evp->sched > currtime && evp->expire > currtime) + break; + + if (n == SCHEDULEMAX) + break; + + if (evp->state != RQ_EVPSTATE_PENDING) + errx(1, "evp:%016" PRIx64 " flags=0x%x", evp->evpid, + evp->flags); + + if (evp->expire <= currtime) { + TAILQ_REMOVE(&rq->q_pending, evp, entry); + SPLAY_REMOVE(prioqtree, &rq->q_priotree, evp); + TAILQ_INSERT_TAIL(&rq->q_expired, evp, entry); + evp->state = RQ_EVPSTATE_SCHEDULED; + evp->flags |= RQ_ENVELOPE_EXPIRED; + evp->t_scheduled = currtime; + continue; + } + rq_envelope_schedule(rq, evp); + n += 1; + } +} + +static struct evplist * +rq_envelope_list(struct rq_queue *rq, struct rq_envelope *evp) +{ + switch (evp->state) { + case RQ_EVPSTATE_PENDING: + return &rq->q_pending; + + case RQ_EVPSTATE_SCHEDULED: + if (evp->flags & RQ_ENVELOPE_EXPIRED) + return &rq->q_expired; + if (evp->flags & RQ_ENVELOPE_REMOVED) + return &rq->q_removed; + if (evp->flags & RQ_ENVELOPE_UPDATE) + return &rq->q_update; + if (evp->type == D_MTA) + return &rq->q_mta; + if (evp->type == D_MDA) + return &rq->q_mda; + if (evp->type == D_BOUNCE) + return &rq->q_bounce; + errx(1, "%016" PRIx64 " bad evp type %d", evp->evpid, evp->type); + + case RQ_EVPSTATE_INFLIGHT: + return &rq->q_inflight; + + case RQ_EVPSTATE_HELD: + return (NULL); + } + + errx(1, "%016" PRIx64 " bad state %d", evp->evpid, evp->state); + return (NULL); +} + +static void +rq_envelope_schedule(struct rq_queue *rq, struct rq_envelope *evp) +{ + struct rq_holdq *hq; + struct evplist *q = NULL; + + switch (evp->type) { + case D_MTA: + q = &rq->q_mta; + break; + case D_MDA: + q = &rq->q_mda; + break; + case D_BOUNCE: + q = &rq->q_bounce; + break; + } + + if (evp->flags & RQ_ENVELOPE_UPDATE) + q = &rq->q_update; + + if (evp->state == RQ_EVPSTATE_HELD) { + hq = tree_xget(&holdqs[evp->type], evp->holdq); + TAILQ_REMOVE(&hq->q, evp, entry); + hq->count -= 1; + if (TAILQ_EMPTY(&hq->q)) { + tree_xpop(&holdqs[evp->type], evp->holdq); + free(hq); + } + evp->holdq = 0; + stat_decrement("scheduler.ramqueue.hold", 1); + } + else if (!(evp->flags & RQ_ENVELOPE_SUSPEND)) { + TAILQ_REMOVE(&rq->q_pending, evp, entry); + SPLAY_REMOVE(prioqtree, &rq->q_priotree, evp); + } + + TAILQ_INSERT_TAIL(q, evp, entry); + evp->state = RQ_EVPSTATE_SCHEDULED; + evp->t_scheduled = currtime; +} + +static int +rq_envelope_remove(struct rq_queue *rq, struct rq_envelope *evp) +{ + struct rq_holdq *hq; + struct evplist *evl; + + if (evp->flags & (RQ_ENVELOPE_REMOVED | RQ_ENVELOPE_EXPIRED)) + return (0); + /* + * If envelope is inflight, mark it envelope for removal. + */ + if (evp->state == RQ_EVPSTATE_INFLIGHT) { + evp->flags |= RQ_ENVELOPE_REMOVED; + return (1); + } + + if (evp->state == RQ_EVPSTATE_HELD) { + hq = tree_xget(&holdqs[evp->type], evp->holdq); + TAILQ_REMOVE(&hq->q, evp, entry); + hq->count -= 1; + if (TAILQ_EMPTY(&hq->q)) { + tree_xpop(&holdqs[evp->type], evp->holdq); + free(hq); + } + evp->holdq = 0; + stat_decrement("scheduler.ramqueue.hold", 1); + } + else if (!(evp->flags & RQ_ENVELOPE_SUSPEND)) { + evl = rq_envelope_list(rq, evp); + TAILQ_REMOVE(evl, evp, entry); + if (evl == &rq->q_pending) + SPLAY_REMOVE(prioqtree, &rq->q_priotree, evp); + } + + TAILQ_INSERT_TAIL(&rq->q_removed, evp, entry); + evp->state = RQ_EVPSTATE_SCHEDULED; + evp->flags |= RQ_ENVELOPE_REMOVED; + evp->t_scheduled = currtime; + + return (1); +} + +static int +rq_envelope_suspend(struct rq_queue *rq, struct rq_envelope *evp) +{ + struct rq_holdq *hq; + struct evplist *evl; + + if (evp->flags & RQ_ENVELOPE_SUSPEND) + return (0); + + if (evp->state == RQ_EVPSTATE_HELD) { + hq = tree_xget(&holdqs[evp->type], evp->holdq); + TAILQ_REMOVE(&hq->q, evp, entry); + hq->count -= 1; + if (TAILQ_EMPTY(&hq->q)) { + tree_xpop(&holdqs[evp->type], evp->holdq); + free(hq); + } + evp->holdq = 0; + evp->state = RQ_EVPSTATE_PENDING; + stat_decrement("scheduler.ramqueue.hold", 1); + } + else if (evp->state != RQ_EVPSTATE_INFLIGHT) { + evl = rq_envelope_list(rq, evp); + TAILQ_REMOVE(evl, evp, entry); + if (evl == &rq->q_pending) + SPLAY_REMOVE(prioqtree, &rq->q_priotree, evp); + } + + evp->flags |= RQ_ENVELOPE_SUSPEND; + + return (1); +} + +static int +rq_envelope_resume(struct rq_queue *rq, struct rq_envelope *evp) +{ + struct evplist *evl; + + if (!(evp->flags & RQ_ENVELOPE_SUSPEND)) + return (0); + + if (evp->state != RQ_EVPSTATE_INFLIGHT) { + evl = rq_envelope_list(rq, evp); + if (evl == &rq->q_pending) + sorted_insert(rq, evp); + else + TAILQ_INSERT_TAIL(evl, evp, entry); + } + + evp->flags &= ~RQ_ENVELOPE_SUSPEND; + + return (1); +} + +static void +rq_envelope_delete(struct rq_queue *rq, struct rq_envelope *evp) +{ + tree_xpop(&evp->message->envelopes, evp->evpid); + if (tree_empty(&evp->message->envelopes)) { + tree_xpop(&rq->messages, evp->message->msgid); + free(evp->message); + stat_decrement("scheduler.ramqueue.message", 1); + } + + free(evp); + rq->evpcount--; + stat_decrement("scheduler.ramqueue.envelope", 1); +} + +static const char * +rq_envelope_to_text(struct rq_envelope *e) +{ + static char buf[256]; + char t[64]; + + (void)snprintf(buf, sizeof buf, "evp:%016" PRIx64 " [", e->evpid); + + if (e->type == D_BOUNCE) + (void)strlcat(buf, "bounce", sizeof buf); + else if (e->type == D_MDA) + (void)strlcat(buf, "mda", sizeof buf); + else if (e->type == D_MTA) + (void)strlcat(buf, "mta", sizeof buf); + + (void)snprintf(t, sizeof t, ",expire=%s", + duration_to_text(e->expire - currtime)); + (void)strlcat(buf, t, sizeof buf); + + + switch (e->state) { + case RQ_EVPSTATE_PENDING: + (void)snprintf(t, sizeof t, ",pending=%s", + duration_to_text(e->sched - currtime)); + (void)strlcat(buf, t, sizeof buf); + break; + + case RQ_EVPSTATE_SCHEDULED: + (void)snprintf(t, sizeof t, ",scheduled=%s", + duration_to_text(currtime - e->t_scheduled)); + (void)strlcat(buf, t, sizeof buf); + break; + + case RQ_EVPSTATE_INFLIGHT: + (void)snprintf(t, sizeof t, ",inflight=%s", + duration_to_text(currtime - e->t_inflight)); + (void)strlcat(buf, t, sizeof buf); + break; + + case RQ_EVPSTATE_HELD: + (void)snprintf(t, sizeof t, ",held=%s", + duration_to_text(currtime - e->t_inflight)); + (void)strlcat(buf, t, sizeof buf); + break; + default: + errx(1, "%016" PRIx64 " bad state %d", e->evpid, e->state); + } + + if (e->flags & RQ_ENVELOPE_REMOVED) + (void)strlcat(buf, ",removed", sizeof buf); + if (e->flags & RQ_ENVELOPE_EXPIRED) + (void)strlcat(buf, ",expired", sizeof buf); + if (e->flags & RQ_ENVELOPE_SUSPEND) + (void)strlcat(buf, ",suspended", sizeof buf); + + (void)strlcat(buf, "]", sizeof buf); + + return (buf); +} + +static void +rq_queue_dump(struct rq_queue *rq, const char * name) +{ + struct rq_message *message; + struct rq_envelope *envelope; + void *i, *j; + uint64_t id; + + log_debug("debug: /--- ramqueue: %s", name); + + i = NULL; + while ((tree_iter(&rq->messages, &i, &id, (void*)&message))) { + log_debug("debug: | msg:%08" PRIx32, message->msgid); + j = NULL; + while ((tree_iter(&message->envelopes, &j, &id, + (void*)&envelope))) + log_debug("debug: | %s", + rq_envelope_to_text(envelope)); + } + log_debug("debug: \\---"); +} + +static int +rq_envelope_cmp(struct rq_envelope *e1, struct rq_envelope *e2) +{ + time_t ref1, ref2; + + ref1 = (e1->sched < e1->expire) ? e1->sched : e1->expire; + ref2 = (e2->sched < e2->expire) ? e2->sched : e2->expire; + if (ref1 != ref2) + return (ref1 < ref2) ? -1 : 1; + + if (e1->evpid != e2->evpid) + return (e1->evpid < e2->evpid) ? -1 : 1; + + return 0; +} + +SPLAY_GENERATE(prioqtree, rq_envelope, t_entry, rq_envelope_cmp); diff --git a/foobar/portable/smtpd/sendmail.8 b/foobar/portable/smtpd/sendmail.8 new file mode 100644 index 00000000..1696a861 --- /dev/null +++ b/foobar/portable/smtpd/sendmail.8 @@ -0,0 +1,86 @@ +.\" $OpenBSD: sendmail.8,v 1.4 2015/10/23 15:48:16 jung Exp $ +.\" +.\" Copyright (C) 2013 Ryan Kavanagh <rak@debian.org> +.\" All rights reserved. +.\" +.\" 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. +.Dd $Mdocdate: October 23 2015 $ +.Dt SENDMAIL 8 +.Os +.Sh NAME +.Nm sendmail +.Nd a mail enqueuer for +.Xr smtpd 8 +.Sh SYNOPSIS +.Nm +.Op Fl tv +.Op Fl F Ar name +.Op Fl f Ar from +.Ar to ... +.Sh DESCRIPTION +The +.Nm +utility is a local enqueuer for the +.Xr smtpd 8 +daemon, +compatible with +.Xr mailwrapper 8 . +The message is read on standard input (stdin) until +.Nm +encounters an end-of-file. +The +.Nm +enqueuer is not intended to be used directly to send mail, +but rather via a frontend known as a mail user agent. +.Pp +Unless the optional +.Fl t +flag is specified, +one or more recipients must be specified on the command line. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl F Ar name +Set the sender's full name. +.It Fl f Ar from +Set the sender's address. +.It Fl t +Read the message's To:, Cc:, and Bcc: fields for recipients. +The Bcc: field will be deleted before sending. +.It Fl v +Enable verbose output. +.El +.Pp +To maintain compatibility with Sendmail, Inc.'s implementation of +.Nm , +various other flags are accepted, +but have no effect. +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr smtpctl 8 , +.Xr smtpd 8 +.Sh AUTHORS +.Sy OpenSMTPD +is primarily developed by Gilles Chehade, +Eric Faurot, +and Charles Longeau, +with contributions from various +.Ox +hackers. +It is distributed under the ISC license. +.Pp +This manpage was written by +.An Ryan Kavanagh +.Aq Mt rak@debian.org +for the Debian project and is distributed under the ISC license. diff --git a/foobar/portable/smtpd/smtp.1 b/foobar/portable/smtpd/smtp.1 new file mode 100644 index 00000000..3cc03844 --- /dev/null +++ b/foobar/portable/smtpd/smtp.1 @@ -0,0 +1,96 @@ +.\" $OpenBSD: smtp.1,v 1.7 2018/07/04 08:23:43 jmc Exp $ +.\" +.\" Copyright (c) 2018, Eric Faurot <eric@openbsd.org> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: July 4 2018 $ +.Dt SMTP 1 +.Os +.Sh NAME +.Nm smtp +.Nd Simple Mail Transfer Protocol client +.Sh SYNOPSIS +.Nm +.Op Fl Chnv +.Op Fl F Ar from +.Op Fl H Ar helo +.Op Fl s Ar server +.Op Ar recipient ... +.Sh DESCRIPTION +The +.Nm +utility is a Simple Mail Transfer Protocol +.Pq SMTP +client which can be used to run an SMTP transaction against an SMTP server. +.Pp +By default, +.Nm +reads the mail content from the standard input, establishes an SMTP session, +and runs an SMTP transaction for all the specified recipients. +The content is sent unaltered as mail data. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl C +Do not require server certificate to be valid. +.It Fl F Ar from +Set the return-path (MAIL FROM) for the SMTP transaction. +Default to the current username. +.It Fl H Ar helo +Define the hostname to advertise (HELO) when establishing the SMTP session. +.It Fl h +Display version and usage. +.It Fl n +Do not actually execute a transaction, +just try to establish an SMTP session and quit. +When this option is given, no message is read from the standard input. +.It Fl s Ar server +Specify the server to connect to and connection parameters. +The format is +.Sm off +.Op Ar proto No :// Op Ar user : pass No @ +.Ar host Op : Ar port . +.Sm on +The following protocols are available: +.Pp +.Bl -tag -width "smtp+notls" -compact +.It smtp +Normal SMTP session with opportunistic STARTTLS. +.It smtp+tls +Normal SMTP session with mandatory STARTTLS. +.It smtp+notls +Plain text SMTP session without TLS. +.It lmtp +LMTP session with opportunistic STARTTLS. +.It lmtp+tls +LMTP session with mandatory STARTTLS. +.It lmtp+notls +Plain text LMTP session without TLS. +.It smtps +SMTP session with forced TLS on connection. +.El +.Pp +Defaults to +.Dq smtp://localhost:25 . +.It Fl v +Be more verbose. +This option can be specified multiple times. +.El +.Sh SEE ALSO +.Xr smtpd 8 +.Sh HISTORY +The +.Nm +program first appeared in +.Ox 6.4 . diff --git a/foobar/portable/smtpd/smtp.c b/foobar/portable/smtpd/smtp.c new file mode 100644 index 00000000..602fd0d6 --- /dev/null +++ b/foobar/portable/smtpd/smtp.c @@ -0,0 +1,387 @@ +/* $OpenBSD: smtp.c,v 1.166 2019/08/10 16:07:01 gilles Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> + * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> + * + * 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 "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> + +#include <err.h> +#include <errno.h> +#include <event.h> +#include <grp.h> /* needed for setgroups */ +#include <imsg.h> +#include <netdb.h> +#include <pwd.h> +#include <signal.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <openssl/ssl.h> + +#include "smtpd.h" +#include "log.h" +#include "ssl.h" + +static void smtp_setup_events(void); +static void smtp_pause(void); +static void smtp_resume(void); +static void smtp_accept(int, short, void *); +static void smtp_dropped(struct listener *, int, const struct sockaddr_storage *); +static int smtp_enqueue(void); +static int smtp_can_accept(void); +static void smtp_setup_listeners(void); +static int smtp_sni_callback(SSL *, int *, void *); + +int +proxy_session(struct listener *listener, int sock, + const struct sockaddr_storage *ss, + void (*accepted)(struct listener *, int, + const struct sockaddr_storage *, struct io *), + void (*dropped)(struct listener *, int, + const struct sockaddr_storage *)); + +static void smtp_accepted(struct listener *, int, const struct sockaddr_storage *, struct io *); + + +#define SMTP_FD_RESERVE 5 +#define getdtablecount() 0 + +static size_t sessions; +static size_t maxsessions; + +void +smtp_imsg(struct mproc *p, struct imsg *imsg) +{ + switch (imsg->hdr.type) { + case IMSG_SMTP_CHECK_SENDER: + case IMSG_SMTP_EXPAND_RCPT: + case IMSG_SMTP_LOOKUP_HELO: + case IMSG_SMTP_AUTHENTICATE: + case IMSG_FILTER_SMTP_PROTOCOL: + case IMSG_FILTER_SMTP_DATA_BEGIN: + smtp_session_imsg(p, imsg); + return; + + case IMSG_SMTP_MESSAGE_COMMIT: + case IMSG_SMTP_MESSAGE_CREATE: + case IMSG_SMTP_MESSAGE_OPEN: + case IMSG_QUEUE_ENVELOPE_SUBMIT: + case IMSG_QUEUE_ENVELOPE_COMMIT: + smtp_session_imsg(p, imsg); + return; + + case IMSG_QUEUE_SMTP_SESSION: + m_compose(p, IMSG_QUEUE_SMTP_SESSION, 0, 0, smtp_enqueue(), + imsg->data, imsg->hdr.len - sizeof imsg->hdr); + return; + + case IMSG_CTL_SMTP_SESSION: + m_compose(p, IMSG_CTL_SMTP_SESSION, imsg->hdr.peerid, 0, + smtp_enqueue(), NULL, 0); + return; + + case IMSG_CTL_PAUSE_SMTP: + log_debug("debug: smtp: pausing listening sockets"); + smtp_pause(); + env->sc_flags |= SMTPD_SMTP_PAUSED; + return; + + case IMSG_CTL_RESUME_SMTP: + log_debug("debug: smtp: resuming listening sockets"); + env->sc_flags &= ~SMTPD_SMTP_PAUSED; + smtp_resume(); + return; + } + + errx(1, "smtp_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); +} + +void +smtp_postfork(void) +{ + smtp_setup_listeners(); +} + +void +smtp_postprivdrop(void) +{ +} + +void +smtp_configure(void) +{ + smtp_setup_events(); +} + +static void +smtp_setup_listeners(void) +{ + struct listener *l; + int opt; + + TAILQ_FOREACH(l, env->sc_listeners, entry) { + if ((l->fd = socket(l->ss.ss_family, SOCK_STREAM, 0)) == -1) { + if (errno == EAFNOSUPPORT) { + log_warn("smtpd: socket"); + continue; + } + fatal("smtpd: socket"); + } + opt = 1; +#ifdef SO_REUSEADDR + if (setsockopt(l->fd, SOL_SOCKET, SO_REUSEADDR, &opt, + sizeof(opt)) == -1) + fatal("smtpd: setsockopt"); +#else + if (setsockopt(l->fd, SOL_SOCKET, SO_REUSEPORT, &opt, + sizeof(opt)) < 0) + fatal("smtpd: setsockopt"); +#endif +#ifdef IPV6_V6ONLY + /* + * If using IPv6, bind only to IPv6 if possible. + * This avoids ambiguities with IPv4-mapped IPv6 addresses. + */ + if (l->ss.ss_family == AF_INET6) + if (setsockopt(l->fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, + sizeof(opt)) < 0) + fatal("smtpd: setsockopt"); +#endif + if (bind(l->fd, (struct sockaddr *)&l->ss, SS_LEN(&l->ss)) == -1) + fatal("smtpd: bind"); + } +} + +static void +smtp_setup_events(void) +{ + struct listener *l; + struct pki *pki; + SSL_CTX *ssl_ctx; + void *iter; + const char *k; + + TAILQ_FOREACH(l, env->sc_listeners, entry) { + log_debug("debug: smtp: listen on %s port %d flags 0x%01x" + " pki \"%s\"" + " ca \"%s\"", ss_to_text(&l->ss), ntohs(l->port), + l->flags, l->pki_name, l->ca_name); + + io_set_nonblocking(l->fd); + if (listen(l->fd, SMTPD_BACKLOG) == -1) + fatal("listen"); + event_set(&l->ev, l->fd, EV_READ|EV_PERSIST, smtp_accept, l); + + if (!(env->sc_flags & SMTPD_SMTP_PAUSED)) + event_add(&l->ev, NULL); + } + + iter = NULL; + while (dict_iter(env->sc_pki_dict, &iter, &k, (void **)&pki)) { + if (!ssl_setup((SSL_CTX **)&ssl_ctx, pki, smtp_sni_callback, + env->sc_tls_ciphers)) + fatal("smtp_setup_events: ssl_setup failure"); + dict_xset(env->sc_ssl_dict, k, ssl_ctx); + } + + purge_config(PURGE_PKI_KEYS); + + maxsessions = (getdtablesize() - getdtablecount()) / 2 - SMTP_FD_RESERVE; + log_debug("debug: smtp: will accept at most %zu clients", maxsessions); +} + +static void +smtp_pause(void) +{ + struct listener *l; + + if (env->sc_flags & (SMTPD_SMTP_DISABLED|SMTPD_SMTP_PAUSED)) + return; + + TAILQ_FOREACH(l, env->sc_listeners, entry) + event_del(&l->ev); +} + +static void +smtp_resume(void) +{ + struct listener *l; + + if (env->sc_flags & (SMTPD_SMTP_DISABLED|SMTPD_SMTP_PAUSED)) + return; + + TAILQ_FOREACH(l, env->sc_listeners, entry) + event_add(&l->ev, NULL); +} + +static int +smtp_enqueue(void) +{ + struct listener *listener = env->sc_sock_listener; + int fd[2]; + + /* + * Some enqueue requests buffered in IMSG may still arrive even after + * call to smtp_pause() because enqueue listener is not a real socket + * and thus cannot be paused properly. + */ + if (env->sc_flags & SMTPD_SMTP_PAUSED) + return (-1); + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, fd)) + return (-1); + + if ((smtp_session(listener, fd[0], &listener->ss, env->sc_hostname, NULL)) == -1) { + close(fd[0]); + close(fd[1]); + return (-1); + } + + sessions++; + stat_increment("smtp.session", 1); + stat_increment("smtp.session.local", 1); + + return (fd[1]); +} + +static void +smtp_accept(int fd, short event, void *p) +{ + struct listener *listener = p; + struct sockaddr_storage ss; + socklen_t len; + int sock; + + if (env->sc_flags & SMTPD_SMTP_PAUSED) + fatalx("smtp_session: unexpected client"); + + if (!smtp_can_accept()) { + log_warnx("warn: Disabling incoming SMTP connections: " + "Client limit reached"); + goto pause; + } + + len = sizeof(ss); + if ((sock = accept(fd, (struct sockaddr *)&ss, &len)) == -1) { + if (errno == ENFILE || errno == EMFILE) { + log_warn("warn: Disabling incoming SMTP connections"); + goto pause; + } + if (errno == EINTR || errno == EWOULDBLOCK || + errno == ECONNABORTED) + return; + fatal("smtp_accept"); + } + + if (listener->flags & F_PROXY) { + io_set_nonblocking(sock); + if (proxy_session(listener, sock, &ss, + smtp_accepted, smtp_dropped) == -1) { + close(sock); + return; + } + return; + } + + smtp_accepted(listener, sock, &ss, NULL); + return; + +pause: + smtp_pause(); + env->sc_flags |= SMTPD_SMTP_DISABLED; + return; +} + +static int +smtp_can_accept(void) +{ + if (sessions + 1 == maxsessions) + return 0; + return (getdtablesize() - getdtablecount() - SMTP_FD_RESERVE >= 2); +} + +void +smtp_collect(void) +{ + sessions--; + stat_decrement("smtp.session", 1); + + if (!smtp_can_accept()) + return; + + if (env->sc_flags & SMTPD_SMTP_DISABLED) { + log_warnx("warn: smtp: " + "fd exhaustion over, re-enabling incoming connections"); + env->sc_flags &= ~SMTPD_SMTP_DISABLED; + smtp_resume(); + } +} + +static int +smtp_sni_callback(SSL *ssl, int *ad, void *arg) +{ + const char *sn; + void *ssl_ctx; + + sn = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + if (sn == NULL) + return SSL_TLSEXT_ERR_NOACK; + ssl_ctx = dict_get(env->sc_ssl_dict, sn); + if (ssl_ctx == NULL) + return SSL_TLSEXT_ERR_NOACK; + SSL_set_SSL_CTX(ssl, ssl_ctx); + return SSL_TLSEXT_ERR_OK; +} + +static void +smtp_accepted(struct listener *listener, int sock, const struct sockaddr_storage *ss, struct io *io) +{ + int ret; + + ret = smtp_session(listener, sock, ss, NULL, io); + if (ret == -1) { + log_warn("warn: Failed to create SMTP session"); + close(sock); + return; + } + io_set_nonblocking(sock); + + sessions++; + stat_increment("smtp.session", 1); + if (listener->ss.ss_family == AF_LOCAL) + stat_increment("smtp.session.local", 1); + if (listener->ss.ss_family == AF_INET) + stat_increment("smtp.session.inet4", 1); + if (listener->ss.ss_family == AF_INET6) + stat_increment("smtp.session.inet6", 1); +} + +static void +smtp_dropped(struct listener *listener, int sock, const struct sockaddr_storage *ss) +{ + close(sock); + sessions--; +} diff --git a/foobar/portable/smtpd/smtp.h b/foobar/portable/smtpd/smtp.h new file mode 100644 index 00000000..dc91d878 --- /dev/null +++ b/foobar/portable/smtpd/smtp.h @@ -0,0 +1,95 @@ +/* $OpenBSD: smtp.h,v 1.3 2019/09/02 20:05:21 eric Exp $ */ + +/* + * Copyright (c) 2018 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define TLS_NO 0 +#define TLS_YES 1 +#define TLS_FORCE 2 +#define TLS_SMTPS 3 + +#define CERT_OK 0 +#define CERT_UNKNOWN 1 +#define CERT_INVALID 2 + +#define FAIL_INTERNAL 1 /* malloc, etc. (check errno) */ +#define FAIL_CONN 2 /* disconnect, timeout... (check errno) */ +#define FAIL_PROTO 3 /* protocol violation */ +#define FAIL_IMPL 4 /* server lacks a required feature */ +#define FAIL_RESP 5 /* rejected command */ + +struct smtp_params { + + /* Client options */ + size_t linemax; /* max input line size */ + size_t ibufmax; /* max input buffer size */ + size_t obufmax; /* max output buffer size */ + + /* Connection settings */ + const struct sockaddr *dst; /* address to connect to */ + const struct sockaddr *src; /* address to bind to */ + int timeout; /* timeout in seconds */ + + /* TLS options */ + int tls_req; /* requested TLS mode */ + int tls_verify; /* need valid server certificate */ + + /* SMTP options */ + int lmtp; /* use LMTP protocol */ + const char *helo; /* string to use with HELO */ + const char *auth_user; /* for AUTH */ + const char *auth_pass; /* for AUTH */ +}; + +struct smtp_rcpt { + const char *to; + const char *dsn_notify; + const char *dsn_orcpt; + int done; +}; + +struct smtp_mail { + const char *from; + const char *dsn_ret; + const char *dsn_envid; + struct smtp_rcpt *rcpt; + int rcptcount; + FILE *fp; +}; + +struct smtp_status { + struct smtp_rcpt *rcpt; + const char *cmd; + const char *status; +}; + +struct smtp_client; + +/* smtp_client.c */ +struct smtp_client *smtp_connect(const struct smtp_params *, void *); +void smtp_cert_verified(struct smtp_client *, int); +void smtp_set_tls(struct smtp_client *, void *); +void smtp_quit(struct smtp_client *); +void smtp_sendmail(struct smtp_client *, struct smtp_mail *); + +/* callbacks */ +void smtp_verify_server_cert(void *, struct smtp_client *, void *); +void smtp_require_tls(void *, struct smtp_client *); +void smtp_ready(void *, struct smtp_client *); +void smtp_failed(void *, struct smtp_client *, int, const char *); +void smtp_closed(void *, struct smtp_client *); +void smtp_status(void *, struct smtp_client *, struct smtp_status *); +void smtp_done(void *, struct smtp_client *, struct smtp_mail *); diff --git a/foobar/portable/smtpd/smtp/Makefile b/foobar/portable/smtpd/smtp/Makefile new file mode 100644 index 00000000..380e3ad6 --- /dev/null +++ b/foobar/portable/smtpd/smtp/Makefile @@ -0,0 +1,24 @@ +# $OpenBSD: Makefile,v 1.1 2018/04/26 13:57:13 eric Exp $ + +.PATH: ${.CURDIR}/.. + +PROG= smtp + +BINDIR= /usr/bin +MAN= smtp.1 + +SRCS+= iobuf.c +SRCS+= ioev.c +SRCS+= log.c +SRCS+= smtp_client.c +SRCS+= smtpc.c +SRCS+= ssl.c +SRCS+= ssl_smtpd.c +SRCS+= ssl_verify.c + +CPPFLAGS+= -DIO_TLS + +LDADD+= -levent -lutil -lssl -lcrypto -lm -lz +DPADD+= ${LIBEVENT} ${LIBUTIL} ${LIBSSL} ${LIBCRYPTO} ${LIBM} ${LIBZ} + +.include <bsd.prog.mk> diff --git a/foobar/portable/smtpd/smtp_client.c b/foobar/portable/smtpd/smtp_client.c new file mode 100644 index 00000000..8e146e1b --- /dev/null +++ b/foobar/portable/smtpd/smtp_client.c @@ -0,0 +1,923 @@ +/* $OpenBSD: smtp_client.c,v 1.14 2020/04/24 11:34:07 eric Exp $ */ + +/* + * Copyright (c) 2018 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/socket.h> + +#include <netinet/in.h> + +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <resolv.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "log.h" +#include "ioev.h" +#include "smtp.h" + +#define TRACE_SMTPCLT 2 +#define TRACE_IO 3 + +enum { + STATE_INIT = 0, + STATE_BANNER, + STATE_EHLO, + STATE_HELO, + STATE_LHLO, + STATE_STARTTLS, + STATE_AUTH, + STATE_AUTH_PLAIN, + STATE_AUTH_LOGIN, + STATE_AUTH_LOGIN_USER, + STATE_AUTH_LOGIN_PASS, + STATE_READY, + STATE_MAIL, + STATE_RCPT, + STATE_DATA, + STATE_BODY, + STATE_EOM, + STATE_RSET, + STATE_QUIT, + + STATE_LAST +}; + +#define base64_encode __b64_ntop +#define base64_decode __b64_pton + +#define FLAG_TLS 0x01 +#define FLAG_TLS_VERIFIED 0x02 + +#define SMTP_EXT_STARTTLS 0x01 +#define SMTP_EXT_PIPELINING 0x02 +#define SMTP_EXT_AUTH 0x04 +#define SMTP_EXT_AUTH_PLAIN 0x08 +#define SMTP_EXT_AUTH_LOGIN 0x10 +#define SMTP_EXT_DSN 0x20 +#define SMTP_EXT_SIZE 0x40 + +struct smtp_client { + void *tag; + struct smtp_params params; + + int state; + int flags; + int ext; + size_t ext_size; + + struct io *io; + char *reply; + size_t replysz; + + struct smtp_mail *mail; + int rcptidx; + int rcptok; +}; + +void log_trace_verbose(int); +void log_trace(int, const char *, ...) + __attribute__((format (printf, 2, 3))); + +static void smtp_client_io(struct io *, int, void *); +static void smtp_client_free(struct smtp_client *); +static void smtp_client_state(struct smtp_client *, int); +static void smtp_client_abort(struct smtp_client *, int, const char *); +static void smtp_client_cancel(struct smtp_client *, int, const char *); +static void smtp_client_sendcmd(struct smtp_client *, char *, ...); +static void smtp_client_sendbody(struct smtp_client *); +static int smtp_client_readline(struct smtp_client *); +static int smtp_client_replycat(struct smtp_client *, const char *); +static void smtp_client_response(struct smtp_client *, const char *); +static void smtp_client_mail_abort(struct smtp_client *); +static void smtp_client_mail_status(struct smtp_client *, const char *); +static void smtp_client_rcpt_status(struct smtp_client *, struct smtp_rcpt *, const char *); + +static const char *strstate[STATE_LAST] = { + "INIT", + "BANNER", + "EHLO", + "HELO", + "LHLO", + "STARTTLS", + "AUTH", + "AUTH_PLAIN", + "AUTH_LOGIN", + "AUTH_LOGIN_USER", + "AUTH_LOGIN_PASS", + "READY", + "MAIL", + "RCPT", + "DATA", + "BODY", + "EOM", + "RSET", + "QUIT", +}; + +struct smtp_client * +smtp_connect(const struct smtp_params *params, void *tag) +{ + struct smtp_client *proto; + + proto = calloc(1, sizeof *proto); + if (proto == NULL) + return NULL; + + memmove(&proto->params, params, sizeof(*params)); + proto->tag = tag; + proto->io = io_new(); + if (proto->io == NULL) { + free(proto); + return NULL; + } + io_set_callback(proto->io, smtp_client_io, proto); + io_set_timeout(proto->io, proto->params.timeout); + + if (io_connect(proto->io, proto->params.dst, proto->params.src) == -1) { + smtp_client_abort(proto, FAIL_CONN, io_error(proto->io)); + return NULL; + } + + return proto; +} + +void +smtp_cert_verified(struct smtp_client *proto, int verified) +{ + if (verified == CERT_OK) + proto->flags |= FLAG_TLS_VERIFIED; + + else if (proto->params.tls_verify) { + errno = EAUTH; + smtp_client_abort(proto, FAIL_CONN, + "Invalid server certificate"); + return; + } + + io_resume(proto->io, IO_IN); + + if (proto->state == STATE_INIT) + smtp_client_state(proto, STATE_BANNER); + else { + /* Clear extensions before re-issueing an EHLO command. */ + proto->ext = 0; + smtp_client_state(proto, STATE_EHLO); + } +} + +void +smtp_set_tls(struct smtp_client *proto, void *ctx) +{ + io_start_tls(proto->io, ctx); +} + +void +smtp_quit(struct smtp_client *proto) +{ + if (proto->state != STATE_READY) + fatalx("connection is not ready"); + + smtp_client_state(proto, STATE_QUIT); +} + +void +smtp_sendmail(struct smtp_client *proto, struct smtp_mail *mail) +{ + if (proto->state != STATE_READY) + fatalx("connection is not ready"); + + proto->mail = mail; + smtp_client_state(proto, STATE_MAIL); +} + +static void +smtp_client_free(struct smtp_client *proto) +{ + if (proto->mail) + fatalx("current task should have been deleted already"); + + smtp_closed(proto->tag, proto); + + if (proto->io) + io_free(proto->io); + + free(proto->reply); + free(proto); +} + +/* + * End the session immediatly. + */ +static void +smtp_client_abort(struct smtp_client *proto, int err, const char *reason) +{ + smtp_failed(proto->tag, proto, err, reason); + + if (proto->mail) + smtp_client_mail_abort(proto); + + smtp_client_free(proto); +} + +/* + * Properly close the session. + */ +static void +smtp_client_cancel(struct smtp_client *proto, int err, const char *reason) +{ + if (proto->mail) + fatal("not supposed to have a mail"); + + smtp_failed(proto->tag, proto, err, reason); + + smtp_client_state(proto, STATE_QUIT); +} + +static void +smtp_client_state(struct smtp_client *proto, int newstate) +{ + struct smtp_rcpt *rcpt; + char ibuf[LINE_MAX], obuf[LINE_MAX]; + size_t n; + int oldstate; + + if (proto->reply) + proto->reply[0] = '\0'; + + again: + oldstate = proto->state; + proto->state = newstate; + + log_trace(TRACE_SMTPCLT, "%p: %s -> %s", proto, + strstate[oldstate], + strstate[newstate]); + + /* don't try this at home! */ +#define smtp_client_state(_s, _st) do { newstate = _st; goto again; } while (0) + + switch (proto->state) { + case STATE_BANNER: + io_set_read(proto->io); + break; + + case STATE_EHLO: + smtp_client_sendcmd(proto, "EHLO %s", proto->params.helo); + break; + + case STATE_HELO: + smtp_client_sendcmd(proto, "HELO %s", proto->params.helo); + break; + + case STATE_LHLO: + smtp_client_sendcmd(proto, "LHLO %s", proto->params.helo); + break; + + case STATE_STARTTLS: + if (proto->params.tls_req == TLS_NO || proto->flags & FLAG_TLS) + smtp_client_state(proto, STATE_AUTH); + else if (proto->ext & SMTP_EXT_STARTTLS) + smtp_client_sendcmd(proto, "STARTTLS"); + else if (proto->params.tls_req == TLS_FORCE) + smtp_client_cancel(proto, FAIL_IMPL, + "TLS not supported by remote host"); + else + smtp_client_state(proto, STATE_AUTH); + break; + + case STATE_AUTH: + if (!proto->params.auth_user) + smtp_client_state(proto, STATE_READY); + else if ((proto->flags & FLAG_TLS) == 0) + smtp_client_cancel(proto, FAIL_IMPL, + "Authentication requires TLS"); + else if ((proto->ext & SMTP_EXT_AUTH) == 0) + smtp_client_cancel(proto, FAIL_IMPL, + "AUTH not supported by remote host"); + else if (proto->ext & SMTP_EXT_AUTH_PLAIN) + smtp_client_state(proto, STATE_AUTH_PLAIN); + else if (proto->ext & SMTP_EXT_AUTH_LOGIN) + smtp_client_state(proto, STATE_AUTH_LOGIN); + else + smtp_client_cancel(proto, FAIL_IMPL, + "No supported AUTH method"); + break; + + case STATE_AUTH_PLAIN: + (void)strlcpy(ibuf, "-", sizeof(ibuf)); + (void)strlcat(ibuf, proto->params.auth_user, sizeof(ibuf)); + if (strlcat(ibuf, ":", sizeof(ibuf)) >= sizeof(ibuf)) { + errno = EMSGSIZE; + smtp_client_cancel(proto, FAIL_INTERNAL, + "credentials too large"); + break; + } + n = strlcat(ibuf, proto->params.auth_pass, sizeof(ibuf)); + if (n >= sizeof(ibuf)) { + errno = EMSGSIZE; + smtp_client_cancel(proto, FAIL_INTERNAL, + "credentials too large"); + break; + } + *strchr(ibuf, ':') = '\0'; + ibuf[0] = '\0'; + if (base64_encode(ibuf, n, obuf, sizeof(obuf)) == -1) { + errno = EMSGSIZE; + smtp_client_cancel(proto, FAIL_INTERNAL, + "credentials too large"); + break; + } + smtp_client_sendcmd(proto, "AUTH PLAIN %s", obuf); + explicit_bzero(ibuf, sizeof ibuf); + explicit_bzero(obuf, sizeof obuf); + break; + + case STATE_AUTH_LOGIN: + smtp_client_sendcmd(proto, "AUTH LOGIN"); + break; + + case STATE_AUTH_LOGIN_USER: + if (base64_encode(proto->params.auth_user, + strlen(proto->params.auth_user), obuf, + sizeof(obuf)) == -1) { + errno = EMSGSIZE; + smtp_client_cancel(proto, FAIL_INTERNAL, + "credentials too large"); + break; + } + smtp_client_sendcmd(proto, "%s", obuf); + explicit_bzero(obuf, sizeof obuf); + break; + + case STATE_AUTH_LOGIN_PASS: + if (base64_encode(proto->params.auth_pass, + strlen(proto->params.auth_pass), obuf, + sizeof(obuf)) == -1) { + errno = EMSGSIZE; + smtp_client_cancel(proto, FAIL_INTERNAL, + "credentials too large"); + break; + } + smtp_client_sendcmd(proto, "%s", obuf); + explicit_bzero(obuf, sizeof obuf); + break; + + case STATE_READY: + smtp_ready(proto->tag, proto); + break; + + case STATE_MAIL: + if (proto->ext & SMTP_EXT_DSN) + smtp_client_sendcmd(proto, "MAIL FROM:<%s>%s%s%s%s", + proto->mail->from, + proto->mail->dsn_ret ? " RET=" : "", + proto->mail->dsn_ret ? proto->mail->dsn_ret : "", + proto->mail->dsn_envid ? " ENVID=" : "", + proto->mail->dsn_envid ? proto->mail->dsn_envid : ""); + else + smtp_client_sendcmd(proto, "MAIL FROM:<%s>", + proto->mail->from); + break; + + case STATE_RCPT: + if (proto->rcptidx == proto->mail->rcptcount) { + smtp_client_state(proto, STATE_DATA); + break; + } + rcpt = &proto->mail->rcpt[proto->rcptidx]; + if (proto->ext & SMTP_EXT_DSN) + smtp_client_sendcmd(proto, "RCPT TO:<%s>%s%s%s%s", + rcpt->to, + rcpt->dsn_notify ? " NOTIFY=" : "", + rcpt->dsn_notify ? rcpt->dsn_notify : "", + rcpt->dsn_orcpt ? " ORCPT=" : "", + rcpt->dsn_orcpt ? rcpt->dsn_orcpt : ""); + else + smtp_client_sendcmd(proto, "RCPT TO:<%s>", rcpt->to); + break; + + case STATE_DATA: + if (proto->rcptok == 0) { + smtp_client_mail_abort(proto); + smtp_client_state(proto, STATE_RSET); + } + else + smtp_client_sendcmd(proto, "DATA"); + break; + + case STATE_BODY: + fseek(proto->mail->fp, 0, SEEK_SET); + smtp_client_sendbody(proto); + break; + + case STATE_EOM: + smtp_client_sendcmd(proto, "."); + break; + + case STATE_RSET: + smtp_client_sendcmd(proto, "RSET"); + break; + + case STATE_QUIT: + smtp_client_sendcmd(proto, "QUIT"); + break; + + default: + fatalx("%s: bad state %d", __func__, proto->state); + } +#undef smtp_client_state +} + +/* + * Handle a response to an SMTP command + */ +static void +smtp_client_response(struct smtp_client *proto, const char *line) +{ + struct smtp_rcpt *rcpt; + int i, seen; + + switch (proto->state) { + case STATE_BANNER: + if (line[0] != '2') + smtp_client_abort(proto, FAIL_RESP, line); + else if (proto->params.lmtp) + smtp_client_state(proto, STATE_LHLO); + else + smtp_client_state(proto, STATE_EHLO); + break; + + case STATE_EHLO: + if (line[0] != '2') { + /* + * Either rejected or not implemented. If we want to + * use EHLO extensions, report an SMTP error. + * Otherwise, fallback to using HELO. + */ + if ((proto->params.tls_req == TLS_FORCE) || + (proto->params.auth_user)) + smtp_client_cancel(proto, FAIL_RESP, line); + else + smtp_client_state(proto, STATE_HELO); + break; + } + smtp_client_state(proto, STATE_STARTTLS); + break; + + case STATE_HELO: + if (line[0] != '2') + smtp_client_cancel(proto, FAIL_RESP, line); + else + smtp_client_state(proto, STATE_READY); + break; + + case STATE_LHLO: + if (line[0] != '2') + smtp_client_cancel(proto, FAIL_RESP, line); + else + smtp_client_state(proto, STATE_READY); + break; + + case STATE_STARTTLS: + if (line[0] != '2') { + if ((proto->params.tls_req == TLS_FORCE) || + (proto->params.auth_user)) { + smtp_client_cancel(proto, FAIL_RESP, line); + break; + } + smtp_client_state(proto, STATE_AUTH); + } + else + smtp_require_tls(proto->tag, proto); + break; + + case STATE_AUTH_PLAIN: + if (line[0] != '2') + smtp_client_cancel(proto, FAIL_RESP, line); + else + smtp_client_state(proto, STATE_READY); + break; + + case STATE_AUTH_LOGIN: + if (strncmp(line, "334 ", 4)) + smtp_client_cancel(proto, FAIL_RESP, line); + else + smtp_client_state(proto, STATE_AUTH_LOGIN_USER); + break; + + case STATE_AUTH_LOGIN_USER: + if (strncmp(line, "334 ", 4)) + smtp_client_cancel(proto, FAIL_RESP, line); + else + smtp_client_state(proto, STATE_AUTH_LOGIN_PASS); + break; + + case STATE_AUTH_LOGIN_PASS: + if (line[0] != '2') + smtp_client_cancel(proto, FAIL_RESP, line); + else + smtp_client_state(proto, STATE_READY); + break; + + case STATE_MAIL: + if (line[0] != '2') { + smtp_client_mail_status(proto, line); + smtp_client_state(proto, STATE_RSET); + } + else + smtp_client_state(proto, STATE_RCPT); + break; + + case STATE_RCPT: + rcpt = &proto->mail->rcpt[proto->rcptidx++]; + if (line[0] != '2') + smtp_client_rcpt_status(proto, rcpt, line); + else { + proto->rcptok++; + smtp_client_state(proto, STATE_RCPT); + } + break; + + case STATE_DATA: + if (line[0] != '2' && line[0] != '3') { + smtp_client_mail_status(proto, line); + smtp_client_state(proto, STATE_RSET); + } + else + smtp_client_state(proto, STATE_BODY); + break; + + case STATE_EOM: + if (proto->params.lmtp) { + /* + * LMTP reports a status of each accepted RCPT. + * Report status for the first pending RCPT and read + * more lines if another rcpt needs a status. + */ + for (i = 0, seen = 0; i < proto->mail->rcptcount; i++) { + rcpt = &proto->mail->rcpt[i]; + if (rcpt->done) + continue; + if (seen) { + io_set_read(proto->io); + return; + } + smtp_client_rcpt_status(proto, + &proto->mail->rcpt[i], line); + seen = 1; + } + } + smtp_client_mail_status(proto, line); + smtp_client_state(proto, STATE_READY); + break; + + case STATE_RSET: + if (line[0] != '2') + smtp_client_cancel(proto, FAIL_RESP, line); + else + smtp_client_state(proto, STATE_READY); + break; + + case STATE_QUIT: + smtp_client_free(proto); + break; + + default: + fatalx("%s: bad state %d", __func__, proto->state); + } +} + +static void +smtp_client_io(struct io *io, int evt, void *arg) +{ + struct smtp_client *proto = arg; + + log_trace(TRACE_IO, "%p: %s %s", proto, io_strevent(evt), io_strio(io)); + + switch (evt) { + case IO_CONNECTED: + if (proto->params.tls_req == TLS_SMTPS) { + io_set_write(io); + smtp_require_tls(proto->tag, proto); + } + else + smtp_client_state(proto, STATE_BANNER); + break; + + case IO_TLSREADY: + proto->flags |= FLAG_TLS; + io_pause(proto->io, IO_IN); + smtp_verify_server_cert(proto->tag, proto, io_tls(proto->io)); + break; + + case IO_DATAIN: + while (smtp_client_readline(proto)) + ; + break; + + case IO_LOWAT: + if (proto->state == STATE_BODY) + smtp_client_sendbody(proto); + else + io_set_read(io); + break; + + case IO_TIMEOUT: + errno = ETIMEDOUT; + smtp_client_abort(proto, FAIL_CONN, "Connection timeout"); + break; + + case IO_ERROR: + smtp_client_abort(proto, FAIL_CONN, io_error(io)); + break; + + case IO_TLSERROR: + smtp_client_abort(proto, FAIL_CONN, io_error(io)); + break; + + case IO_DISCONNECTED: + smtp_client_abort(proto, FAIL_CONN, io_error(io)); + break; + + default: + fatalx("%s: bad event %d", __func__, evt); + } +} + +/* + * return 1 if a new line is expected. + */ +static int +smtp_client_readline(struct smtp_client *proto) +{ + const char *e; + size_t len; + char *line, *msg, *p; + int cont; + + line = io_getline(proto->io, &len); + if (line == NULL) { + if (io_datalen(proto->io) >= proto->params.linemax) + smtp_client_abort(proto, FAIL_PROTO, "Line too long"); + return 0; + } + + /* Strip trailing '\r' */ + if (len && line[len - 1] == '\r') + line[--len] = '\0'; + + log_trace(TRACE_SMTPCLT, "%p: <<< %s", proto, line); + + /* Validate SMTP */ + if (len > 3) { + msg = line + 4; + cont = (line[3] == '-'); + } else if (len == 3) { + msg = line + 3; + cont = 0; + } else { + smtp_client_abort(proto, FAIL_PROTO, "Response too short"); + return 0; + } + + /* Validate reply code. */ + if (line[0] < '2' || line[0] > '5' || !isdigit((unsigned char)line[1]) || + !isdigit((unsigned char)line[2])) { + smtp_client_abort(proto, FAIL_PROTO, "Invalid reply code"); + return 0; + } + + /* Validate reply message. */ + for (p = msg; *p; p++) + if (!isprint((unsigned char)*p)) { + smtp_client_abort(proto, FAIL_PROTO, + "Non-printable characters in response"); + return 0; + } + + /* Read extensions. */ + if (proto->state == STATE_EHLO) { + if (strcmp(msg, "STARTTLS") == 0) + proto->ext |= SMTP_EXT_STARTTLS; + else if (strncmp(msg, "AUTH ", 5) == 0) { + proto->ext |= SMTP_EXT_AUTH; + if ((p = strstr(msg, " PLAIN")) && + (*(p+6) == '\0' || *(p+6) == ' ')) + proto->ext |= SMTP_EXT_AUTH_PLAIN; + if ((p = strstr(msg, " LOGIN")) && + (*(p+6) == '\0' || *(p+6) == ' ')) + proto->ext |= SMTP_EXT_AUTH_LOGIN; + } + else if (strcmp(msg, "PIPELINING") == 0) + proto->ext |= SMTP_EXT_PIPELINING; + else if (strcmp(msg, "DSN") == 0) + proto->ext |= SMTP_EXT_DSN; + else if (strncmp(msg, "SIZE ", 5) == 0) { + proto->ext_size = strtonum(msg + 5, 0, SIZE_T_MAX, &e); + if (e == NULL) + proto->ext |= SMTP_EXT_SIZE; + } + } + + if (smtp_client_replycat(proto, line) == -1) { + smtp_client_abort(proto, FAIL_INTERNAL, NULL); + return 0; + } + + if (cont) + return 1; + + if (io_datalen(proto->io)) { + /* + * There should be no pending data after a response is read, + * except for the multiple status lines after a LMTP message. + * It can also happen with pipelineing, but we don't do that + * for now. + */ + if (!(proto->params.lmtp && proto->state == STATE_EOM)) { + smtp_client_abort(proto, FAIL_PROTO, "Trailing data"); + return 0; + } + } + + io_set_write(proto->io); + smtp_client_response(proto, proto->reply); + return 0; +} + +/* + * Concatenate the given response line. + */ +static int +smtp_client_replycat(struct smtp_client *proto, const char *line) +{ + size_t len; + char *tmp; + int first; + + if (proto->reply && proto->reply[0]) { + /* + * If the line is the continuation of an multi-line response, + * skip the status and ESC parts. First, skip the status, then + * skip the separator amd ESC if found. + */ + first = 0; + line += 3; + if (line[0]) { + line += 1; + if (isdigit((unsigned char)line[0]) && line[1] == '.' && + isdigit((unsigned char)line[2]) && line[3] == '.' && + isdigit((unsigned char)line[4]) && + isspace((unsigned char)line[5])) + line += 5; + } + } else + first = 1; + + if (proto->reply) { + len = strlcat(proto->reply, line, proto->replysz); + if (len < proto->replysz) + return 0; + } + else + len = strlen(line); + + if (len > proto->params.ibufmax) { + errno = EMSGSIZE; + return -1; + } + + /* Allocate by multiples of 2^8 */ + len += (len % 256) ? (256 - (len % 256)) : 0; + + tmp = realloc(proto->reply, len); + if (tmp == NULL) + return -1; + if (proto->reply == NULL) + tmp[0] = '\0'; + + proto->reply = tmp; + proto->replysz = len; + (void)strlcat(proto->reply, line, proto->replysz); + + /* Replace the separator with a space for the first line. */ + if (first && proto->reply[3]) + proto->reply[3] = ' '; + + return 0; +} + +static void +smtp_client_sendbody(struct smtp_client *proto) +{ + ssize_t len; + size_t sz = 0, total, w; + char *ln = NULL; + int n; + + total = io_queued(proto->io); + w = 0; + + while (total < proto->params.obufmax) { + if ((len = getline(&ln, &sz, proto->mail->fp)) == -1) + break; + if (ln[len - 1] == '\n') + ln[len - 1] = '\0'; + n = io_printf(proto->io, "%s%s\r\n", *ln == '.'?".":"", ln); + if (n == -1) { + free(ln); + smtp_client_abort(proto, FAIL_INTERNAL, NULL); + return; + } + total += n; + w += n; + } + free(ln); + + if (ferror(proto->mail->fp)) { + smtp_client_abort(proto, FAIL_INTERNAL, "Cannot read message"); + return; + } + + log_trace(TRACE_SMTPCLT, "%p: >>> [...%zd bytes...]", proto, w); + + if (feof(proto->mail->fp)) + smtp_client_state(proto, STATE_EOM); +} + +static void +smtp_client_sendcmd(struct smtp_client *proto, char *fmt, ...) +{ + va_list ap; + char *p; + int len; + + va_start(ap, fmt); + len = vasprintf(&p, fmt, ap); + va_end(ap); + + if (len == -1) { + smtp_client_abort(proto, FAIL_INTERNAL, NULL); + return; + } + + log_trace(TRACE_SMTPCLT, "mta: %p: >>> %s", proto, p); + + len = io_printf(proto->io, "%s\r\n", p); + free(p); + + if (len == -1) + smtp_client_abort(proto, FAIL_INTERNAL, NULL); +} + +static void +smtp_client_mail_status(struct smtp_client *proto, const char *status) +{ + int i; + + for (i = 0; i < proto->mail->rcptcount; i++) + smtp_client_rcpt_status(proto, &proto->mail->rcpt[i], status); + + smtp_done(proto->tag, proto, proto->mail); + proto->mail = NULL; +} + +static void +smtp_client_mail_abort(struct smtp_client *proto) +{ + smtp_done(proto->tag, proto, proto->mail); + proto->mail = NULL; +} + +static void +smtp_client_rcpt_status(struct smtp_client *proto, struct smtp_rcpt *rcpt, const char *line) +{ + struct smtp_status status; + + if (rcpt->done) + return; + + rcpt->done = 1; + status.rcpt = rcpt; + status.cmd = strstate[proto->state]; + status.status = line; + smtp_status(proto->tag, proto, &status); +} diff --git a/foobar/portable/smtpd/smtp_session.c b/foobar/portable/smtpd/smtp_session.c new file mode 100644 index 00000000..aefce155 --- /dev/null +++ b/foobar/portable/smtpd/smtp_session.c @@ -0,0 +1,3223 @@ +/* $OpenBSD: smtp_session.c,v 1.426 2020/04/24 11:34:07 eric Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> + * Copyright (c) 2008-2009 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/uio.h> + +#include <netinet/in.h> + +#include <ctype.h> +#include <errno.h> +#include <event.h> +#include <imsg.h> +#include <limits.h> +#include <inttypes.h> +#include <openssl/ssl.h> +#include <resolv.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#if defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS) +#include <vis.h> +#else +#include "bsd-vis.h" +#endif + +#include "smtpd.h" +#include "log.h" +#include "ssl.h" +#include "rfc5322.h" + +#define SMTP_LINE_MAX 65535 +#define DATA_HIWAT 65535 +#define APPEND_DOMAIN_BUFFER_SIZE SMTP_LINE_MAX + +enum smtp_state { + STATE_NEW = 0, + STATE_CONNECTED, + STATE_TLS, + STATE_HELO, + STATE_AUTH_INIT, + STATE_AUTH_USERNAME, + STATE_AUTH_PASSWORD, + STATE_AUTH_FINALIZE, + STATE_BODY, + STATE_QUIT, +}; + +enum session_flags { + SF_EHLO = 0x0001, + SF_8BITMIME = 0x0002, + SF_SECURE = 0x0004, + SF_AUTHENTICATED = 0x0008, + SF_BOUNCE = 0x0010, + SF_VERIFIED = 0x0020, + SF_BADINPUT = 0x0080, +}; + +enum { + TX_OK = 0, + TX_ERROR_ENVELOPE, + TX_ERROR_SIZE, + TX_ERROR_IO, + TX_ERROR_LOOP, + TX_ERROR_MALFORMED, + TX_ERROR_RESOURCES, + TX_ERROR_INTERNAL, +}; + +enum smtp_command { + CMD_HELO = 0, + CMD_EHLO, + CMD_STARTTLS, + CMD_AUTH, + CMD_MAIL_FROM, + CMD_RCPT_TO, + CMD_DATA, + CMD_RSET, + CMD_QUIT, + CMD_HELP, + CMD_WIZ, + CMD_NOOP, + CMD_COMMIT, +}; + +struct smtp_rcpt { + TAILQ_ENTRY(smtp_rcpt) entry; + uint64_t evpid; + struct mailaddr maddr; + size_t destcount; +}; + +struct smtp_tx { + struct smtp_session *session; + uint32_t msgid; + + struct envelope evp; + size_t rcptcount; + size_t destcount; + TAILQ_HEAD(, smtp_rcpt) rcpts; + + time_t time; + int error; + size_t datain; + size_t odatalen; + FILE *ofile; + struct io *filter; + struct rfc5322_parser *parser; + int rcvcount; + int has_date; + int has_message_id; + + uint8_t junk; +}; + +struct smtp_session { + uint64_t id; + struct io *io; + struct listener *listener; + void *ssl_ctx; + struct sockaddr_storage ss; + char rdns[HOST_NAME_MAX+1]; + char smtpname[HOST_NAME_MAX+1]; + int fcrdns; + + int flags; + enum smtp_state state; + + uint8_t banner_sent; + char helo[LINE_MAX]; + char cmd[LINE_MAX]; + char username[SMTPD_MAXMAILADDRSIZE]; + + size_t mailcount; + struct event pause; + + struct smtp_tx *tx; + + enum smtp_command last_cmd; + enum filter_phase filter_phase; + const char *filter_param; + + uint8_t junk; +}; + +#define ADVERTISE_TLS(s) \ + ((s)->listener->flags & F_STARTTLS && !((s)->flags & SF_SECURE)) + +#define ADVERTISE_AUTH(s) \ + ((s)->listener->flags & F_AUTH && (s)->flags & SF_SECURE && \ + !((s)->flags & SF_AUTHENTICATED)) + +#define ADVERTISE_EXT_DSN(s) \ + ((s)->listener->flags & F_EXT_DSN) + +#define SESSION_FILTERED(s) \ + ((s)->listener->flags & F_FILTERED) + +#define SESSION_DATA_FILTERED(s) \ + ((s)->listener->flags & F_FILTERED) + + +static int smtp_mailaddr(struct mailaddr *, char *, int, char **, const char *); +static void smtp_session_init(void); +static void smtp_lookup_servername(struct smtp_session *); +static void smtp_getnameinfo_cb(void *, int, const char *, const char *); +static void smtp_getaddrinfo_cb(void *, int, struct addrinfo *); +static void smtp_connected(struct smtp_session *); +static void smtp_send_banner(struct smtp_session *); +static void smtp_tls_verified(struct smtp_session *); +static void smtp_io(struct io *, int, void *); +static void smtp_enter_state(struct smtp_session *, int); +static void smtp_reply(struct smtp_session *, char *, ...); +static void smtp_command(struct smtp_session *, char *); +static void smtp_rfc4954_auth_plain(struct smtp_session *, char *); +static void smtp_rfc4954_auth_login(struct smtp_session *, char *); +static void smtp_free(struct smtp_session *, const char *); +static const char *smtp_strstate(int); +static void smtp_cert_init(struct smtp_session *); +static void smtp_cert_init_cb(void *, int, const char *, const void *, size_t); +static void smtp_cert_verify(struct smtp_session *); +static void smtp_cert_verify_cb(void *, int); +static void smtp_auth_failure_pause(struct smtp_session *); +static void smtp_auth_failure_resume(int, short, void *); + +static int smtp_tx(struct smtp_session *); +static void smtp_tx_free(struct smtp_tx *); +static void smtp_tx_create_message(struct smtp_tx *); +static void smtp_tx_mail_from(struct smtp_tx *, const char *); +static void smtp_tx_rcpt_to(struct smtp_tx *, const char *); +static void smtp_tx_open_message(struct smtp_tx *); +static void smtp_tx_commit(struct smtp_tx *); +static void smtp_tx_rollback(struct smtp_tx *); +static int smtp_tx_dataline(struct smtp_tx *, const char *); +static int smtp_tx_filtered_dataline(struct smtp_tx *, const char *); +static void smtp_tx_eom(struct smtp_tx *); +static void smtp_filter_fd(struct smtp_tx *, int); +static int smtp_message_fd(struct smtp_tx *, int); +static void smtp_message_begin(struct smtp_tx *); +static void smtp_message_end(struct smtp_tx *); +static int smtp_filter_printf(struct smtp_tx *, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); +static int smtp_message_printf(struct smtp_tx *, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); + +static int smtp_check_rset(struct smtp_session *, const char *); +static int smtp_check_helo(struct smtp_session *, const char *); +static int smtp_check_ehlo(struct smtp_session *, const char *); +static int smtp_check_auth(struct smtp_session *s, const char *); +static int smtp_check_starttls(struct smtp_session *, const char *); +static int smtp_check_mail_from(struct smtp_session *, const char *); +static int smtp_check_rcpt_to(struct smtp_session *, const char *); +static int smtp_check_data(struct smtp_session *, const char *); +static int smtp_check_noparam(struct smtp_session *, const char *); + +static void smtp_filter_phase(enum filter_phase, struct smtp_session *, const char *); + +static void smtp_proceed_connected(struct smtp_session *); +static void smtp_proceed_rset(struct smtp_session *, const char *); +static void smtp_proceed_helo(struct smtp_session *, const char *); +static void smtp_proceed_ehlo(struct smtp_session *, const char *); +static void smtp_proceed_auth(struct smtp_session *, const char *); +static void smtp_proceed_starttls(struct smtp_session *, const char *); +static void smtp_proceed_mail_from(struct smtp_session *, const char *); +static void smtp_proceed_rcpt_to(struct smtp_session *, const char *); +static void smtp_proceed_data(struct smtp_session *, const char *); +static void smtp_proceed_noop(struct smtp_session *, const char *); +static void smtp_proceed_help(struct smtp_session *, const char *); +static void smtp_proceed_wiz(struct smtp_session *, const char *); +static void smtp_proceed_quit(struct smtp_session *, const char *); +static void smtp_proceed_commit(struct smtp_session *, const char *); +static void smtp_proceed_rollback(struct smtp_session *, const char *); + +static void smtp_filter_begin(struct smtp_session *); +static void smtp_filter_end(struct smtp_session *); +static void smtp_filter_data_begin(struct smtp_session *); +static void smtp_filter_data_end(struct smtp_session *); + +static void smtp_report_link_connect(struct smtp_session *, const char *, int, + const struct sockaddr_storage *, + const struct sockaddr_storage *); +static void smtp_report_link_greeting(struct smtp_session *, const char *); +static void smtp_report_link_identify(struct smtp_session *, const char *, const char *); +static void smtp_report_link_tls(struct smtp_session *, const char *); +static void smtp_report_link_disconnect(struct smtp_session *); +static void smtp_report_link_auth(struct smtp_session *, const char *, const char *); +static void smtp_report_tx_reset(struct smtp_session *, uint32_t); +static void smtp_report_tx_begin(struct smtp_session *, uint32_t); +static void smtp_report_tx_mail(struct smtp_session *, uint32_t, const char *, int); +static void smtp_report_tx_rcpt(struct smtp_session *, uint32_t, const char *, int); +static void smtp_report_tx_envelope(struct smtp_session *, uint32_t, uint64_t); +static void smtp_report_tx_data(struct smtp_session *, uint32_t, int); +static void smtp_report_tx_commit(struct smtp_session *, uint32_t, size_t); +static void smtp_report_tx_rollback(struct smtp_session *, uint32_t); +static void smtp_report_protocol_client(struct smtp_session *, const char *); +static void smtp_report_protocol_server(struct smtp_session *, const char *); +static void smtp_report_filter_response(struct smtp_session *, int, int, const char *); +static void smtp_report_timeout(struct smtp_session *); + + +/* musl work-around */ +void portable_freeaddrinfo(struct addrinfo *); + + +static struct { + int code; + enum filter_phase filter_phase; + const char *cmd; + + int (*check)(struct smtp_session *, const char *); + void (*proceed)(struct smtp_session *, const char *); +} commands[] = { + { CMD_HELO, FILTER_HELO, "HELO", smtp_check_helo, smtp_proceed_helo }, + { CMD_EHLO, FILTER_EHLO, "EHLO", smtp_check_ehlo, smtp_proceed_ehlo }, + { CMD_STARTTLS, FILTER_STARTTLS, "STARTTLS", smtp_check_starttls, smtp_proceed_starttls }, + { CMD_AUTH, FILTER_AUTH, "AUTH", smtp_check_auth, smtp_proceed_auth }, + { CMD_MAIL_FROM, FILTER_MAIL_FROM, "MAIL FROM", smtp_check_mail_from, smtp_proceed_mail_from }, + { CMD_RCPT_TO, FILTER_RCPT_TO, "RCPT TO", smtp_check_rcpt_to, smtp_proceed_rcpt_to }, + { CMD_DATA, FILTER_DATA, "DATA", smtp_check_data, smtp_proceed_data }, + { CMD_RSET, FILTER_RSET, "RSET", smtp_check_rset, smtp_proceed_rset }, + { CMD_QUIT, FILTER_QUIT, "QUIT", smtp_check_noparam, smtp_proceed_quit }, + { CMD_NOOP, FILTER_NOOP, "NOOP", smtp_check_noparam, smtp_proceed_noop }, + { CMD_HELP, FILTER_HELP, "HELP", smtp_check_noparam, smtp_proceed_help }, + { CMD_WIZ, FILTER_WIZ, "WIZ", smtp_check_noparam, smtp_proceed_wiz }, + { CMD_COMMIT, FILTER_COMMIT, ".", smtp_check_noparam, smtp_proceed_commit }, + { -1, 0, NULL, NULL }, +}; + +static struct tree wait_lka_helo; +static struct tree wait_lka_mail; +static struct tree wait_lka_rcpt; +static struct tree wait_parent_auth; +static struct tree wait_queue_msg; +static struct tree wait_queue_fd; +static struct tree wait_queue_commit; +static struct tree wait_ssl_init; +static struct tree wait_ssl_verify; +static struct tree wait_filters; +static struct tree wait_filter_fd; + +static void +header_append_domain_buffer(char *buffer, char *domain, size_t len) +{ + size_t i; + int escape, quote, comment, bracket; + int has_domain, has_bracket, has_group; + int pos_bracket, pos_component, pos_insert; + char copy[APPEND_DOMAIN_BUFFER_SIZE]; + + escape = quote = comment = bracket = 0; + has_domain = has_bracket = has_group = 0; + pos_bracket = pos_insert = pos_component = 0; + for (i = 0; buffer[i]; ++i) { + if (buffer[i] == '(' && !escape && !quote) + comment++; + if (buffer[i] == '"' && !escape && !comment) + quote = !quote; + if (buffer[i] == ')' && !escape && !quote && comment) + comment--; + if (buffer[i] == '\\' && !escape && !comment && !quote) + escape = 1; + else + escape = 0; + if (buffer[i] == '<' && !escape && !comment && !quote && !bracket) { + bracket++; + has_bracket = 1; + } + if (buffer[i] == '>' && !escape && !comment && !quote && bracket) { + bracket--; + pos_bracket = i; + } + if (buffer[i] == '@' && !escape && !comment && !quote) + has_domain = 1; + if (buffer[i] == ':' && !escape && !comment && !quote) + has_group = 1; + + /* update insert point if not in comment and not on a whitespace */ + if (!comment && buffer[i] != ')' && !isspace((unsigned char)buffer[i])) + pos_component = i; + } + + /* parse error, do not attempt to modify */ + if (escape || quote || comment || bracket) + return; + + /* domain already present, no need to modify */ + if (has_domain) + return; + + /* address is group, skip */ + if (has_group) + return; + + /* there's an address between brackets, just append domain */ + if (has_bracket) { + pos_bracket--; + while (isspace((unsigned char)buffer[pos_bracket])) + pos_bracket--; + if (buffer[pos_bracket] == '<') + return; + pos_insert = pos_bracket + 1; + } + else { + /* otherwise append address to last component */ + pos_insert = pos_component + 1; + + /* empty address */ + if (buffer[pos_component] == '\0' || + isspace((unsigned char)buffer[pos_component])) + return; + } + + if (snprintf(copy, sizeof copy, "%.*s@%s%s", + (int)pos_insert, buffer, + domain, + buffer+pos_insert) >= (int)sizeof copy) + return; + + memcpy(buffer, copy, len); +} + +static void +header_address_rewrite_buffer(char *buffer, const char *address, size_t len) +{ + size_t i; + int address_len; + int escape, quote, comment, bracket; + int has_bracket, has_group; + int pos_bracket_beg, pos_bracket_end, pos_component_beg, pos_component_end; + int insert_beg, insert_end; + char copy[APPEND_DOMAIN_BUFFER_SIZE]; + + escape = quote = comment = bracket = 0; + has_bracket = has_group = 0; + pos_bracket_beg = pos_bracket_end = pos_component_beg = pos_component_end = 0; + for (i = 0; buffer[i]; ++i) { + if (buffer[i] == '(' && !escape && !quote) + comment++; + if (buffer[i] == '"' && !escape && !comment) + quote = !quote; + if (buffer[i] == ')' && !escape && !quote && comment) + comment--; + if (buffer[i] == '\\' && !escape && !comment && !quote) + escape = 1; + else + escape = 0; + if (buffer[i] == '<' && !escape && !comment && !quote && !bracket) { + bracket++; + has_bracket = 1; + pos_bracket_beg = i+1; + } + if (buffer[i] == '>' && !escape && !comment && !quote && bracket) { + bracket--; + pos_bracket_end = i; + } + if (buffer[i] == ':' && !escape && !comment && !quote) + has_group = 1; + + /* update insert point if not in comment and not on a whitespace */ + if (!comment && buffer[i] != ')' && !isspace((unsigned char)buffer[i])) + pos_component_end = i; + } + + /* parse error, do not attempt to modify */ + if (escape || quote || comment || bracket) + return; + + /* address is group, skip */ + if (has_group) + return; + + /* there's an address between brackets, just replace everything brackets */ + if (has_bracket) { + insert_beg = pos_bracket_beg; + insert_end = pos_bracket_end; + } + else { + if (pos_component_end == 0) + pos_component_beg = 0; + else { + for (pos_component_beg = pos_component_end; pos_component_beg >= 0; --pos_component_beg) + if (buffer[pos_component_beg] == ')' || isspace((unsigned char)buffer[pos_component_beg])) + break; + pos_component_beg += 1; + pos_component_end += 1; + } + insert_beg = pos_component_beg; + insert_end = pos_component_end; + } + + /* check that masquerade won' t overflow */ + address_len = strlen(address); + if (strlen(buffer) - (insert_end - insert_beg) + address_len >= len) + return; + + (void)strlcpy(copy, buffer, sizeof copy); + (void)strlcpy(copy+insert_beg, address, sizeof (copy) - insert_beg); + (void)strlcat(copy, buffer+insert_end, sizeof (copy)); + memcpy(buffer, copy, len); +} + +static void +header_domain_append_callback(struct smtp_tx *tx, const char *hdr, + const char *val) +{ + size_t i, j, linelen; + int escape, quote, comment, skip; + char buffer[APPEND_DOMAIN_BUFFER_SIZE]; + const char *line, *end; + + if (smtp_message_printf(tx, "%s:", hdr) == -1) + return; + + j = 0; + escape = quote = comment = skip = 0; + memset(buffer, 0, sizeof buffer); + + for (line = val; line; line = end) { + end = strchr(line, '\n'); + if (end) { + linelen = end - line; + end++; + } + else + linelen = strlen(line); + + for (i = 0; i < linelen; ++i) { + if (line[i] == '(' && !escape && !quote) + comment++; + if (line[i] == '"' && !escape && !comment) + quote = !quote; + if (line[i] == ')' && !escape && !quote && comment) + comment--; + if (line[i] == '\\' && !escape && !comment && !quote) + escape = 1; + else + escape = 0; + + /* found a separator, buffer contains a full address */ + if (line[i] == ',' && !escape && !quote && !comment) { + if (!skip && j + strlen(tx->session->listener->hostname) + 1 < sizeof buffer) { + header_append_domain_buffer(buffer, tx->session->listener->hostname, sizeof buffer); + if (tx->session->flags & SF_AUTHENTICATED && + tx->session->listener->sendertable[0] && + tx->session->listener->flags & F_MASQUERADE && + !(strcasecmp(hdr, "From"))) + header_address_rewrite_buffer(buffer, mailaddr_to_text(&tx->evp.sender), + sizeof buffer); + } + if (smtp_message_printf(tx, "%s,", buffer) == -1) + return; + j = 0; + skip = 0; + memset(buffer, 0, sizeof buffer); + } + else { + if (skip) { + if (smtp_message_printf(tx, "%c", line[i]) == -1) + return; + } + else { + buffer[j++] = line[i]; + if (j == sizeof (buffer) - 1) { + if (smtp_message_printf(tx, "%s", buffer) == -1) + return; + skip = 1; + j = 0; + memset(buffer, 0, sizeof buffer); + } + } + } + } + if (skip) { + if (smtp_message_printf(tx, "\n") == -1) + return; + } + else { + buffer[j++] = '\n'; + if (j == sizeof (buffer) - 1) { + if (smtp_message_printf(tx, "%s", buffer) == -1) + return; + skip = 1; + j = 0; + memset(buffer, 0, sizeof buffer); + } + } + } + + /* end of header, if buffer is not empty we'll process it */ + if (buffer[0]) { + if (j + strlen(tx->session->listener->hostname) + 1 < sizeof buffer) { + header_append_domain_buffer(buffer, tx->session->listener->hostname, sizeof buffer); + if (tx->session->flags & SF_AUTHENTICATED && + tx->session->listener->sendertable[0] && + tx->session->listener->flags & F_MASQUERADE && + !(strcasecmp(hdr, "From"))) + header_address_rewrite_buffer(buffer, mailaddr_to_text(&tx->evp.sender), + sizeof buffer); + } + smtp_message_printf(tx, "%s", buffer); + } +} + +static void +smtp_session_init(void) +{ + static int init = 0; + + if (!init) { + tree_init(&wait_lka_helo); + tree_init(&wait_lka_mail); + tree_init(&wait_lka_rcpt); + tree_init(&wait_parent_auth); + tree_init(&wait_queue_msg); + tree_init(&wait_queue_fd); + tree_init(&wait_queue_commit); + tree_init(&wait_ssl_init); + tree_init(&wait_ssl_verify); + tree_init(&wait_filters); + tree_init(&wait_filter_fd); + init = 1; + } +} + +int +smtp_session(struct listener *listener, int sock, + const struct sockaddr_storage *ss, const char *hostname, struct io *io) +{ + struct smtp_session *s; + + smtp_session_init(); + + if ((s = calloc(1, sizeof(*s))) == NULL) + return (-1); + + s->id = generate_uid(); + s->listener = listener; + memmove(&s->ss, ss, sizeof(*ss)); + + if (io != NULL) + s->io = io; + else + s->io = io_new(); + + io_set_callback(s->io, smtp_io, s); + io_set_fd(s->io, sock); + io_set_timeout(s->io, SMTPD_SESSION_TIMEOUT * 1000); + io_set_write(s->io); + s->state = STATE_NEW; + + (void)strlcpy(s->smtpname, listener->hostname, sizeof(s->smtpname)); + + log_trace(TRACE_SMTP, "smtp: %p: connected to listener %p " + "[hostname=%s, port=%d, tag=%s]", s, listener, + listener->hostname, ntohs(listener->port), listener->tag); + + /* For local enqueueing, the hostname is already set */ + if (hostname) { + s->flags |= SF_AUTHENTICATED; + /* A bit of a hack */ + if (!strcmp(hostname, "localhost")) + s->flags |= SF_BOUNCE; + (void)strlcpy(s->rdns, hostname, sizeof(s->rdns)); + s->fcrdns = 1; + smtp_lookup_servername(s); + } else { + resolver_getnameinfo((struct sockaddr *)&s->ss, NI_NAMEREQD, + smtp_getnameinfo_cb, s); + } + + /* session may have been freed by now */ + + return (0); +} + +static void +smtp_getnameinfo_cb(void *arg, int gaierrno, const char *host, const char *serv) +{ + struct smtp_session *s = arg; + struct addrinfo hints; + + if (gaierrno) { + (void)strlcpy(s->rdns, "<unknown>", sizeof(s->rdns)); + + if (gaierrno == EAI_NODATA || gaierrno == EAI_NONAME) + s->fcrdns = 0; + else { + log_warnx("getnameinfo: %s: %s", ss_to_text(&s->ss), + gai_strerror(gaierrno)); + s->fcrdns = -1; + } + + smtp_lookup_servername(s); + return; + } + + (void)strlcpy(s->rdns, host, sizeof(s->rdns)); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = s->ss.ss_family; + hints.ai_socktype = SOCK_STREAM; + resolver_getaddrinfo(s->rdns, NULL, &hints, smtp_getaddrinfo_cb, s); +} + +static void +smtp_getaddrinfo_cb(void *arg, int gaierrno, struct addrinfo *ai0) +{ + struct smtp_session *s = arg; + struct addrinfo *ai; + char fwd[64], rev[64]; + + if (gaierrno) { + if (gaierrno == EAI_NODATA || gaierrno == EAI_NONAME) + s->fcrdns = 0; + else { + log_warnx("getaddrinfo: %s: %s", s->rdns, + gai_strerror(gaierrno)); + s->fcrdns = -1; + } + } + else { + strlcpy(rev, ss_to_text(&s->ss), sizeof(rev)); + for (ai = ai0; ai; ai = ai->ai_next) { + strlcpy(fwd, sa_to_text(ai->ai_addr), sizeof(fwd)); + if (!strcmp(fwd, rev)) { + s->fcrdns = 1; + break; + } + } + portable_freeaddrinfo(ai0); + } + + smtp_lookup_servername(s); +} + +void +smtp_session_imsg(struct mproc *p, struct imsg *imsg) +{ + struct smtp_session *s; + struct smtp_rcpt *rcpt; + char user[SMTPD_MAXMAILADDRSIZE]; + char tmp[SMTP_LINE_MAX]; + struct msg m; + const char *line, *helo; + uint64_t reqid, evpid; + uint32_t msgid; + int status, success; + int filter_response; + const char *filter_param; + uint8_t i; + + switch (imsg->hdr.type) { + + case IMSG_SMTP_CHECK_SENDER: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &status); + m_end(&m); + s = tree_xpop(&wait_lka_mail, reqid); + switch (status) { + case LKA_OK: + smtp_tx_create_message(s->tx); + break; + + case LKA_PERMFAIL: + smtp_tx_free(s->tx); + smtp_reply(s, "%d %s", 530, "Sender rejected"); + break; + case LKA_TEMPFAIL: + smtp_tx_free(s->tx); + smtp_reply(s, "421 %s Temporary Error", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + break; + } + return; + + case IMSG_SMTP_EXPAND_RCPT: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &status); + m_get_string(&m, &line); + m_end(&m); + s = tree_xpop(&wait_lka_rcpt, reqid); + + tmp[0] = '\0'; + if (s->tx->evp.rcpt.user[0]) { + (void)strlcpy(tmp, s->tx->evp.rcpt.user, sizeof tmp); + if (s->tx->evp.rcpt.domain[0]) { + (void)strlcat(tmp, "@", sizeof tmp); + (void)strlcat(tmp, s->tx->evp.rcpt.domain, + sizeof tmp); + } + } + + switch (status) { + case LKA_OK: + fatalx("unexpected ok"); + case LKA_PERMFAIL: + smtp_reply(s, "%s: <%s>", line, tmp); + break; + case LKA_TEMPFAIL: + smtp_reply(s, "%s: <%s>", line, tmp); + break; + } + return; + + case IMSG_SMTP_LOOKUP_HELO: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + s = tree_xpop(&wait_lka_helo, reqid); + m_get_int(&m, &status); + if (status == LKA_OK) { + m_get_string(&m, &helo); + (void)strlcpy(s->smtpname, helo, sizeof(s->smtpname)); + } + m_end(&m); + smtp_connected(s); + return; + + case IMSG_SMTP_MESSAGE_CREATE: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &success); + s = tree_xpop(&wait_queue_msg, reqid); + if (success) { + m_get_msgid(&m, &msgid); + s->tx->msgid = msgid; + s->tx->evp.id = msgid_to_evpid(msgid); + s->tx->rcptcount = 0; + smtp_reply(s, "250 %s Ok", + esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); + } else { + smtp_reply(s, "421 %s Temporary Error", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + smtp_tx_free(s->tx); + smtp_enter_state(s, STATE_QUIT); + } + m_end(&m); + return; + + case IMSG_SMTP_MESSAGE_OPEN: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &success); + m_end(&m); + + s = tree_xpop(&wait_queue_fd, reqid); + if (!success || imsg->fd == -1) { + if (imsg->fd != -1) + close(imsg->fd); + smtp_reply(s, "421 %s Temporary Error", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + smtp_enter_state(s, STATE_QUIT); + return; + } + + log_debug("smtp: %p: fd %d from queue", s, imsg->fd); + + if (smtp_message_fd(s->tx, imsg->fd)) { + if (!SESSION_DATA_FILTERED(s)) + smtp_message_begin(s->tx); + else + smtp_filter_data_begin(s); + } + return; + + case IMSG_FILTER_SMTP_DATA_BEGIN: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &success); + m_end(&m); + + s = tree_xpop(&wait_filter_fd, reqid); + if (!success || imsg->fd == -1) { + if (imsg->fd != -1) + close(imsg->fd); + smtp_reply(s, "421 %s Temporary Error", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + smtp_enter_state(s, STATE_QUIT); + return; + } + + log_debug("smtp: %p: fd %d from lka", s, imsg->fd); + + smtp_filter_fd(s->tx, imsg->fd); + smtp_message_begin(s->tx); + return; + + case IMSG_QUEUE_ENVELOPE_SUBMIT: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &success); + s = tree_xget(&wait_lka_rcpt, reqid); + if (success) { + m_get_evpid(&m, &evpid); + s->tx->evp.id = evpid; + s->tx->destcount++; + smtp_report_tx_envelope(s, s->tx->msgid, evpid); + } + else + s->tx->error = TX_ERROR_ENVELOPE; + m_end(&m); + return; + + case IMSG_QUEUE_ENVELOPE_COMMIT: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &success); + m_end(&m); + if (!success) + fatalx("commit evp failed: not supposed to happen"); + s = tree_xpop(&wait_lka_rcpt, reqid); + if (s->tx->error) { + /* + * If an envelope failed, we can't cancel the last + * RCPT only so we must cancel the whole transaction + * and close the connection. + */ + smtp_reply(s, "421 %s Temporary failure", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + smtp_enter_state(s, STATE_QUIT); + } + else { + rcpt = xcalloc(1, sizeof(*rcpt)); + rcpt->evpid = s->tx->evp.id; + rcpt->destcount = s->tx->destcount; + rcpt->maddr = s->tx->evp.rcpt; + TAILQ_INSERT_TAIL(&s->tx->rcpts, rcpt, entry); + + s->tx->destcount = 0; + s->tx->rcptcount++; + smtp_reply(s, "250 %s %s: Recipient ok", + esc_code(ESC_STATUS_OK, ESC_DESTINATION_ADDRESS_VALID), + esc_description(ESC_DESTINATION_ADDRESS_VALID)); + } + return; + + case IMSG_SMTP_MESSAGE_COMMIT: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &success); + m_end(&m); + s = tree_xpop(&wait_queue_commit, reqid); + if (!success) { + smtp_reply(s, "421 %s Temporary failure", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + smtp_tx_free(s->tx); + smtp_enter_state(s, STATE_QUIT); + return; + } + + smtp_reply(s, "250 %s %08x Message accepted for delivery", + esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS), + s->tx->msgid); + smtp_report_tx_commit(s, s->tx->msgid, s->tx->odatalen); + smtp_report_tx_reset(s, s->tx->msgid); + + log_info("%016"PRIx64" smtp message " + "msgid=%08x size=%zu nrcpt=%zu proto=%s", + s->id, + s->tx->msgid, + s->tx->odatalen, + s->tx->rcptcount, + s->flags & SF_EHLO ? "ESMTP" : "SMTP"); + TAILQ_FOREACH(rcpt, &s->tx->rcpts, entry) { + log_info("%016"PRIx64" smtp envelope " + "evpid=%016"PRIx64" from=<%s%s%s> to=<%s%s%s>", + s->id, + rcpt->evpid, + s->tx->evp.sender.user, + s->tx->evp.sender.user[0] == '\0' ? "" : "@", + s->tx->evp.sender.domain, + rcpt->maddr.user, + rcpt->maddr.user[0] == '\0' ? "" : "@", + rcpt->maddr.domain); + } + smtp_tx_free(s->tx); + s->mailcount++; + smtp_enter_state(s, STATE_HELO); + return; + + case IMSG_SMTP_AUTHENTICATE: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &success); + m_end(&m); + + s = tree_xpop(&wait_parent_auth, reqid); + strnvis(user, s->username, sizeof user, VIS_WHITE | VIS_SAFE); + if (success == LKA_OK) { + log_info("%016"PRIx64" smtp " + "authentication user=%s " + "result=ok", + s->id, user); + s->flags |= SF_AUTHENTICATED; + smtp_report_link_auth(s, user, "pass"); + smtp_reply(s, "235 %s Authentication succeeded", + esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); + } + else if (success == LKA_PERMFAIL) { + log_info("%016"PRIx64" smtp " + "authentication user=%s " + "result=permfail", + s->id, user); + smtp_report_link_auth(s, user, "fail"); + smtp_auth_failure_pause(s); + return; + } + else if (success == LKA_TEMPFAIL) { + log_info("%016"PRIx64" smtp " + "authentication user=%s " + "result=tempfail", + s->id, user); + smtp_report_link_auth(s, user, "error"); + smtp_reply(s, "421 %s Temporary failure", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + } + else + fatalx("bad lka response"); + + smtp_enter_state(s, STATE_HELO); + return; + + case IMSG_FILTER_SMTP_PROTOCOL: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_int(&m, &filter_response); + if (filter_response != FILTER_PROCEED && + filter_response != FILTER_JUNK) + m_get_string(&m, &filter_param); + else + filter_param = NULL; + m_end(&m); + + s = tree_xpop(&wait_filters, reqid); + + switch (filter_response) { + case FILTER_REJECT: + case FILTER_DISCONNECT: + if (!valid_smtp_response(filter_param) || + (filter_param[0] != '4' && filter_param[0] != '5')) + filter_param = "421 Internal server error"; + if (!strncmp(filter_param, "421", 3)) + filter_response = FILTER_DISCONNECT; + + smtp_report_filter_response(s, s->filter_phase, + filter_response, filter_param); + + smtp_reply(s, "%s", filter_param); + + if (filter_response == FILTER_DISCONNECT) + smtp_enter_state(s, STATE_QUIT); + else if (s->filter_phase == FILTER_COMMIT) + smtp_proceed_rollback(s, NULL); + break; + + + case FILTER_JUNK: + if (s->tx) + s->tx->junk = 1; + else + s->junk = 1; + /* fallthrough */ + + case FILTER_PROCEED: + filter_param = s->filter_param; + /* fallthrough */ + + case FILTER_REWRITE: + smtp_report_filter_response(s, s->filter_phase, + filter_response, + filter_param == s->filter_param ? NULL : filter_param); + if (s->filter_phase == FILTER_CONNECT) { + smtp_proceed_connected(s); + return; + } + for (i = 0; i < nitems(commands); ++i) + if (commands[i].filter_phase == s->filter_phase) { + if (filter_response == FILTER_REWRITE) + if (!commands[i].check(s, filter_param)) + break; + commands[i].proceed(s, filter_param); + break; + } + break; + } + return; + } + + log_warnx("smtp_session_imsg: unexpected %s imsg", + imsg_to_str(imsg->hdr.type)); + fatalx(NULL); +} + +static void +smtp_tls_verified(struct smtp_session *s) +{ + X509 *x; + + x = SSL_get_peer_certificate(io_tls(s->io)); + if (x) { + log_info("%016"PRIx64" smtp " + "client-cert-check result=\"%s\"", + s->id, + (s->flags & SF_VERIFIED) ? "success" : "failure"); + X509_free(x); + } + + if (s->listener->flags & F_SMTPS) { + stat_increment("smtp.smtps", 1); + io_set_write(s->io); + smtp_send_banner(s); + } + else { + stat_increment("smtp.tls", 1); + smtp_enter_state(s, STATE_HELO); + } +} + +static void +smtp_io(struct io *io, int evt, void *arg) +{ + struct smtp_session *s = arg; + char *line; + size_t len; + int eom; + + log_trace(TRACE_IO, "smtp: %p: %s %s", s, io_strevent(evt), + io_strio(io)); + + switch (evt) { + + case IO_TLSREADY: + log_info("%016"PRIx64" smtp tls ciphers=%s", + s->id, ssl_to_text(io_tls(s->io))); + + smtp_report_link_tls(s, ssl_to_text(io_tls(s->io))); + + s->flags |= SF_SECURE; + s->helo[0] = '\0'; + + smtp_cert_verify(s); + break; + + case IO_DATAIN: + nextline: + line = io_getline(s->io, &len); + if ((line == NULL && io_datalen(s->io) >= SMTP_LINE_MAX) || + (line && len >= SMTP_LINE_MAX)) { + s->flags |= SF_BADINPUT; + smtp_reply(s, "500 %s Line too long", + esc_code(ESC_STATUS_PERMFAIL, ESC_OTHER_STATUS)); + smtp_enter_state(s, STATE_QUIT); + io_set_write(io); + return; + } + + /* No complete line received */ + if (line == NULL) + return; + + /* Strip trailing '\r' */ + if (len && line[len - 1] == '\r') + line[--len] = '\0'; + + /* Message body */ + eom = 0; + if (s->state == STATE_BODY) { + if (strcmp(line, ".")) { + s->tx->datain += strlen(line) + 1; + if (s->tx->datain > env->sc_maxsize) + s->tx->error = TX_ERROR_SIZE; + } + eom = (s->tx->filter == NULL) ? + smtp_tx_dataline(s->tx, line) : + smtp_tx_filtered_dataline(s->tx, line); + if (eom == 0) + goto nextline; + } + + /* Pipelining not supported */ + if (io_datalen(s->io)) { + s->flags |= SF_BADINPUT; + smtp_reply(s, "500 %s %s: Pipelining not supported", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + smtp_enter_state(s, STATE_QUIT); + io_set_write(io); + return; + } + + if (eom) { + io_set_write(io); + if (s->tx->filter == NULL) + smtp_tx_eom(s->tx); + return; + } + + /* Must be a command */ + if (strlcpy(s->cmd, line, sizeof(s->cmd)) >= sizeof(s->cmd)) { + s->flags |= SF_BADINPUT; + smtp_reply(s, "500 %s Command line too long", + esc_code(ESC_STATUS_PERMFAIL, ESC_OTHER_STATUS)); + smtp_enter_state(s, STATE_QUIT); + io_set_write(io); + return; + } + io_set_write(io); + smtp_command(s, line); + break; + + case IO_LOWAT: + if (s->state == STATE_QUIT) { + log_info("%016"PRIx64" smtp disconnected " + "reason=quit", + s->id); + smtp_free(s, "done"); + break; + } + + /* Wait for the client to start tls */ + if (s->state == STATE_TLS) { + smtp_cert_init(s); + break; + } + + io_set_read(io); + break; + + case IO_TIMEOUT: + log_info("%016"PRIx64" smtp disconnected " + "reason=timeout", + s->id); + smtp_report_timeout(s); + smtp_free(s, "timeout"); + break; + + case IO_DISCONNECTED: + log_info("%016"PRIx64" smtp disconnected " + "reason=disconnect", + s->id); + smtp_free(s, "disconnected"); + break; + + case IO_ERROR: + log_info("%016"PRIx64" smtp disconnected " + "reason=\"io-error: %s\"", + s->id, io_error(io)); + smtp_free(s, "IO error"); + break; + + default: + fatalx("smtp_io()"); + } +} + +static void +smtp_command(struct smtp_session *s, char *line) +{ + char *args; + int cmd, i; + + log_trace(TRACE_SMTP, "smtp: %p: <<< %s", s, line); + + /* + * These states are special. + */ + if (s->state == STATE_AUTH_INIT) { + smtp_report_protocol_client(s, "********"); + smtp_rfc4954_auth_plain(s, line); + return; + } + if (s->state == STATE_AUTH_USERNAME || s->state == STATE_AUTH_PASSWORD) { + smtp_report_protocol_client(s, "********"); + smtp_rfc4954_auth_login(s, line); + return; + } + + if (s->state == STATE_HELO && strncasecmp(line, "AUTH PLAIN ", 11) == 0) + smtp_report_protocol_client(s, "AUTH PLAIN ********"); + else + smtp_report_protocol_client(s, line); + + + /* + * Unlike other commands, "mail from" and "rcpt to" contain a + * space in the command name. + */ + if (strncasecmp("mail from:", line, 10) == 0 || + strncasecmp("rcpt to:", line, 8) == 0) + args = strchr(line, ':'); + else + args = strchr(line, ' '); + + if (args) { + *args++ = '\0'; + while (isspace((unsigned char)*args)) + args++; + } + + cmd = -1; + for (i = 0; commands[i].code != -1; i++) + if (!strcasecmp(line, commands[i].cmd)) { + cmd = commands[i].code; + break; + } + + s->last_cmd = cmd; + switch (cmd) { + /* + * INIT + */ + case CMD_HELO: + if (!smtp_check_helo(s, args)) + break; + smtp_filter_phase(FILTER_HELO, s, args); + break; + + case CMD_EHLO: + if (!smtp_check_ehlo(s, args)) + break; + smtp_filter_phase(FILTER_EHLO, s, args); + break; + + /* + * SETUP + */ + case CMD_STARTTLS: + if (!smtp_check_starttls(s, args)) + break; + + smtp_filter_phase(FILTER_STARTTLS, s, NULL); + break; + + case CMD_AUTH: + if (!smtp_check_auth(s, args)) + break; + smtp_filter_phase(FILTER_AUTH, s, args); + break; + + case CMD_MAIL_FROM: + if (!smtp_check_mail_from(s, args)) + break; + smtp_filter_phase(FILTER_MAIL_FROM, s, args); + break; + + /* + * TRANSACTION + */ + case CMD_RCPT_TO: + if (!smtp_check_rcpt_to(s, args)) + break; + smtp_filter_phase(FILTER_RCPT_TO, s, args); + break; + + case CMD_RSET: + if (!smtp_check_rset(s, args)) + break; + smtp_filter_phase(FILTER_RSET, s, NULL); + break; + + case CMD_DATA: + if (!smtp_check_data(s, args)) + break; + smtp_filter_phase(FILTER_DATA, s, NULL); + break; + + /* + * ANY + */ + case CMD_QUIT: + if (!smtp_check_noparam(s, args)) + break; + smtp_filter_phase(FILTER_QUIT, s, NULL); + break; + + case CMD_NOOP: + if (!smtp_check_noparam(s, args)) + break; + smtp_filter_phase(FILTER_NOOP, s, NULL); + break; + + case CMD_HELP: + if (!smtp_check_noparam(s, args)) + break; + smtp_proceed_help(s, NULL); + break; + + case CMD_WIZ: + if (!smtp_check_noparam(s, args)) + break; + smtp_proceed_wiz(s, NULL); + break; + + default: + smtp_reply(s, "500 %s %s: Command unrecognized", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + break; + } +} + +static int +smtp_check_rset(struct smtp_session *s, const char *args) +{ + if (!smtp_check_noparam(s, args)) + return 0; + + if (s->helo[0] == '\0') { + smtp_reply(s, "503 %s %s: Command not allowed at this point.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + return 1; +} + +static int +smtp_check_helo(struct smtp_session *s, const char *args) +{ + if (!s->banner_sent) { + smtp_reply(s, "503 %s %s: Command not allowed at this point.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->helo[0]) { + smtp_reply(s, "503 %s %s: Already identified", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (args == NULL) { + smtp_reply(s, "501 %s %s: HELO requires domain name", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (!valid_domainpart(args)) { + smtp_reply(s, "501 %s %s: Invalid domain name", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), + esc_description(ESC_INVALID_COMMAND_ARGUMENTS)); + return 0; + } + + return 1; +} + +static int +smtp_check_ehlo(struct smtp_session *s, const char *args) +{ + if (!s->banner_sent) { + smtp_reply(s, "503 %s %s: Command not allowed at this point.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->helo[0]) { + smtp_reply(s, "503 %s %s: Already identified", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (args == NULL) { + smtp_reply(s, "501 %s %s: EHLO requires domain name", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (!valid_domainpart(args)) { + smtp_reply(s, "501 %s %s: Invalid domain name", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), + esc_description(ESC_INVALID_COMMAND_ARGUMENTS)); + return 0; + } + + return 1; +} + +static int +smtp_check_auth(struct smtp_session *s, const char *args) +{ + if (s->helo[0] == '\0' || s->tx) { + smtp_reply(s, "503 %s %s: Command not allowed at this point.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->flags & SF_AUTHENTICATED) { + smtp_reply(s, "503 %s %s: Already authenticated", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (!ADVERTISE_AUTH(s)) { + smtp_reply(s, "503 %s %s: Command not supported", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (args == NULL) { + smtp_reply(s, "501 %s %s: No parameters given", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), + esc_description(ESC_INVALID_COMMAND_ARGUMENTS)); + return 0; + } + + return 1; +} + +static int +smtp_check_starttls(struct smtp_session *s, const char *args) +{ + if (s->helo[0] == '\0' || s->tx) { + smtp_reply(s, "503 %s %s: Command not allowed at this point.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (!(s->listener->flags & F_STARTTLS)) { + smtp_reply(s, "503 %s %s: Command not supported", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->flags & SF_SECURE) { + smtp_reply(s, "503 %s %s: Channel already secured", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (args != NULL) { + smtp_reply(s, "501 %s %s: No parameters allowed", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), + esc_description(ESC_INVALID_COMMAND_ARGUMENTS)); + return 0; + } + + return 1; +} + +static int +smtp_check_mail_from(struct smtp_session *s, const char *args) +{ + char *copy; + char tmp[SMTP_LINE_MAX]; + struct mailaddr sender; + + (void)strlcpy(tmp, args, sizeof tmp); + copy = tmp; + + if (s->helo[0] == '\0' || s->tx) { + smtp_reply(s, "503 %s %s: Command not allowed at this point.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->listener->flags & F_STARTTLS_REQUIRE && + !(s->flags & SF_SECURE)) { + smtp_reply(s, + "530 %s %s: Must issue a STARTTLS command first", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->listener->flags & F_AUTH_REQUIRE && + !(s->flags & SF_AUTHENTICATED)) { + smtp_reply(s, + "530 %s %s: Must issue an AUTH command first", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->mailcount >= env->sc_session_max_mails) { + /* we can pretend we had too many recipients */ + smtp_reply(s, "452 %s %s: Too many messages sent", + esc_code(ESC_STATUS_TEMPFAIL, ESC_TOO_MANY_RECIPIENTS), + esc_description(ESC_TOO_MANY_RECIPIENTS)); + return 0; + } + + if (smtp_mailaddr(&sender, copy, 1, ©, + s->smtpname) == 0) { + smtp_reply(s, "553 %s Sender address syntax error", + esc_code(ESC_STATUS_PERMFAIL, ESC_OTHER_ADDRESS_STATUS)); + return 0; + } + + return 1; +} + +static int +smtp_check_rcpt_to(struct smtp_session *s, const char *args) +{ + char *copy; + char tmp[SMTP_LINE_MAX]; + + (void)strlcpy(tmp, args, sizeof tmp); + copy = tmp; + + if (s->tx == NULL) { + smtp_reply(s, "503 %s %s: Command not allowed at this point.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->tx->rcptcount >= env->sc_session_max_rcpt) { + smtp_reply(s->tx->session, "451 %s %s: Too many recipients", + esc_code(ESC_STATUS_TEMPFAIL, ESC_TOO_MANY_RECIPIENTS), + esc_description(ESC_TOO_MANY_RECIPIENTS)); + return 0; + } + + if (smtp_mailaddr(&s->tx->evp.rcpt, copy, 0, ©, + s->tx->session->smtpname) == 0) { + smtp_reply(s->tx->session, + "501 %s Recipient address syntax error", + esc_code(ESC_STATUS_PERMFAIL, + ESC_BAD_DESTINATION_MAILBOX_ADDRESS_SYNTAX)); + return 0; + } + + return 1; +} + +static int +smtp_check_data(struct smtp_session *s, const char *args) +{ + if (!smtp_check_noparam(s, args)) + return 0; + + if (s->tx == NULL) { + smtp_reply(s, "503 %s %s: Command not allowed at this point.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); + return 0; + } + + if (s->tx->rcptcount == 0) { + smtp_reply(s, "503 %s %s: No recipient specified", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), + esc_description(ESC_INVALID_COMMAND_ARGUMENTS)); + return 0; + } + + return 1; +} + +static int +smtp_check_noparam(struct smtp_session *s, const char *args) +{ + if (args != NULL) { + smtp_reply(s, "500 %s %s: command does not accept arguments.", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), + esc_description(ESC_INVALID_COMMAND_ARGUMENTS)); + return 0; + } + return 1; +} + +static void +smtp_query_filters(enum filter_phase phase, struct smtp_session *s, const char *args) +{ + m_create(p_lka, IMSG_FILTER_SMTP_PROTOCOL, 0, 0, -1); + m_add_id(p_lka, s->id); + m_add_int(p_lka, phase); + m_add_string(p_lka, args); + m_close(p_lka); + tree_xset(&wait_filters, s->id, s); +} + +static void +smtp_filter_begin(struct smtp_session *s) +{ + if (!SESSION_FILTERED(s)) + return; + + m_create(p_lka, IMSG_FILTER_SMTP_BEGIN, 0, 0, -1); + m_add_id(p_lka, s->id); + m_add_string(p_lka, s->listener->filter_name); + m_close(p_lka); +} + +static void +smtp_filter_end(struct smtp_session *s) +{ + if (!SESSION_FILTERED(s)) + return; + + m_create(p_lka, IMSG_FILTER_SMTP_END, 0, 0, -1); + m_add_id(p_lka, s->id); + m_close(p_lka); +} + +static void +smtp_filter_data_begin(struct smtp_session *s) +{ + if (!SESSION_FILTERED(s)) + return; + + m_create(p_lka, IMSG_FILTER_SMTP_DATA_BEGIN, 0, 0, -1); + m_add_id(p_lka, s->id); + m_close(p_lka); + tree_xset(&wait_filter_fd, s->id, s); +} + +static void +smtp_filter_data_end(struct smtp_session *s) +{ + if (!SESSION_FILTERED(s)) + return; + + if (s->tx->filter == NULL) + return; + + io_free(s->tx->filter); + s->tx->filter = NULL; + + m_create(p_lka, IMSG_FILTER_SMTP_DATA_END, 0, 0, -1); + m_add_id(p_lka, s->id); + m_close(p_lka); +} + +static void +smtp_filter_phase(enum filter_phase phase, struct smtp_session *s, const char *param) +{ + uint8_t i; + + s->filter_phase = phase; + s->filter_param = param; + + if (SESSION_FILTERED(s)) { + smtp_query_filters(phase, s, param ? param : ""); + return; + } + + if (s->filter_phase == FILTER_CONNECT) { + smtp_proceed_connected(s); + return; + } + + for (i = 0; i < nitems(commands); ++i) + if (commands[i].filter_phase == s->filter_phase) { + commands[i].proceed(s, param); + break; + } +} + +static void +smtp_proceed_rset(struct smtp_session *s, const char *args) +{ + smtp_reply(s, "250 %s Reset state", + esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); + + if (s->tx) { + if (s->tx->msgid) + smtp_tx_rollback(s->tx); + smtp_tx_free(s->tx); + } +} + +static void +smtp_proceed_helo(struct smtp_session *s, const char *args) +{ + (void)strlcpy(s->helo, args, sizeof(s->helo)); + s->flags &= SF_SECURE | SF_AUTHENTICATED | SF_VERIFIED; + + smtp_report_link_identify(s, "HELO", s->helo); + + smtp_enter_state(s, STATE_HELO); + + smtp_reply(s, "250 %s Hello %s %s%s%s, pleased to meet you", + s->smtpname, + s->helo, + s->ss.ss_family == AF_INET6 ? "" : "[", + ss_to_text(&s->ss), + s->ss.ss_family == AF_INET6 ? "" : "]"); +} + +static void +smtp_proceed_ehlo(struct smtp_session *s, const char *args) +{ + (void)strlcpy(s->helo, args, sizeof(s->helo)); + s->flags &= SF_SECURE | SF_AUTHENTICATED | SF_VERIFIED; + s->flags |= SF_EHLO; + s->flags |= SF_8BITMIME; + + smtp_report_link_identify(s, "EHLO", s->helo); + + smtp_enter_state(s, STATE_HELO); + smtp_reply(s, "250-%s Hello %s %s%s%s, pleased to meet you", + s->smtpname, + s->helo, + s->ss.ss_family == AF_INET6 ? "" : "[", + ss_to_text(&s->ss), + s->ss.ss_family == AF_INET6 ? "" : "]"); + + smtp_reply(s, "250-8BITMIME"); + smtp_reply(s, "250-ENHANCEDSTATUSCODES"); + smtp_reply(s, "250-SIZE %zu", env->sc_maxsize); + if (ADVERTISE_EXT_DSN(s)) + smtp_reply(s, "250-DSN"); + if (ADVERTISE_TLS(s)) + smtp_reply(s, "250-STARTTLS"); + if (ADVERTISE_AUTH(s)) + smtp_reply(s, "250-AUTH PLAIN LOGIN"); + smtp_reply(s, "250 HELP"); +} + +static void +smtp_proceed_auth(struct smtp_session *s, const char *args) +{ + char tmp[SMTP_LINE_MAX]; + char *eom, *method; + + (void)strlcpy(tmp, args, sizeof tmp); + + method = tmp; + eom = strchr(tmp, ' '); + if (eom == NULL) + eom = strchr(tmp, '\t'); + if (eom != NULL) + *eom++ = '\0'; + if (strcasecmp(method, "PLAIN") == 0) + smtp_rfc4954_auth_plain(s, eom); + else if (strcasecmp(method, "LOGIN") == 0) + smtp_rfc4954_auth_login(s, eom); + else + smtp_reply(s, "504 %s %s: AUTH method \"%s\" not supported", + esc_code(ESC_STATUS_PERMFAIL, ESC_SECURITY_FEATURES_NOT_SUPPORTED), + esc_description(ESC_SECURITY_FEATURES_NOT_SUPPORTED), + method); +} + +static void +smtp_proceed_starttls(struct smtp_session *s, const char *args) +{ + smtp_reply(s, "220 %s Ready to start TLS", + esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); + smtp_enter_state(s, STATE_TLS); +} + +static void +smtp_proceed_mail_from(struct smtp_session *s, const char *args) +{ + char *copy; + char tmp[SMTP_LINE_MAX]; + + (void)strlcpy(tmp, args, sizeof tmp); + copy = tmp; + + if (!smtp_tx(s)) { + smtp_reply(s, "421 %s Temporary Error", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + smtp_enter_state(s, STATE_QUIT); + return; + } + + if (smtp_mailaddr(&s->tx->evp.sender, copy, 1, ©, + s->smtpname) == 0) { + smtp_reply(s, "553 %s Sender address syntax error", + esc_code(ESC_STATUS_PERMFAIL, ESC_OTHER_ADDRESS_STATUS)); + smtp_tx_free(s->tx); + return; + } + + smtp_tx_mail_from(s->tx, args); +} + +static void +smtp_proceed_rcpt_to(struct smtp_session *s, const char *args) +{ + smtp_tx_rcpt_to(s->tx, args); +} + +static void +smtp_proceed_data(struct smtp_session *s, const char *args) +{ + smtp_tx_open_message(s->tx); +} + +static void +smtp_proceed_quit(struct smtp_session *s, const char *args) +{ + smtp_reply(s, "221 %s Bye", + esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); + smtp_enter_state(s, STATE_QUIT); +} + +static void +smtp_proceed_noop(struct smtp_session *s, const char *args) +{ + smtp_reply(s, "250 %s Ok", + esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS)); +} + +static void +smtp_proceed_help(struct smtp_session *s, const char *args) +{ + const char *code = esc_code(ESC_STATUS_OK, ESC_OTHER_STATUS); + + smtp_reply(s, "214-%s This is " SMTPD_NAME, code); + smtp_reply(s, "214-%s To report bugs in the implementation, " + "please contact bugs@openbsd.org", code); + smtp_reply(s, "214-%s with full details", code); + smtp_reply(s, "214 %s End of HELP info", code); +} + +static void +smtp_proceed_wiz(struct smtp_session *s, const char *args) +{ + smtp_reply(s, "500 %s %s: this feature is not supported yet ;-)", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND), + esc_description(ESC_INVALID_COMMAND)); +} + +static void +smtp_proceed_commit(struct smtp_session *s, const char *args) +{ + smtp_message_end(s->tx); +} + +static void +smtp_proceed_rollback(struct smtp_session *s, const char *args) +{ + struct smtp_tx *tx; + + tx = s->tx; + + fclose(tx->ofile); + tx->ofile = NULL; + + smtp_tx_rollback(tx); + smtp_tx_free(tx); + smtp_enter_state(s, STATE_HELO); +} + +static void +smtp_rfc4954_auth_plain(struct smtp_session *s, char *arg) +{ + char buf[1024], *user, *pass; + int len; + + switch (s->state) { + case STATE_HELO: + if (arg == NULL) { + smtp_enter_state(s, STATE_AUTH_INIT); + smtp_reply(s, "334 "); + return; + } + smtp_enter_state(s, STATE_AUTH_INIT); + /* FALLTHROUGH */ + + case STATE_AUTH_INIT: + /* String is not NUL terminated, leave room. */ + if ((len = base64_decode(arg, (unsigned char *)buf, + sizeof(buf) - 1)) == -1) + goto abort; + /* buf is a byte string, NUL terminate. */ + buf[len] = '\0'; + + /* + * Skip "foo" in "foo\0user\0pass", if present. + */ + user = memchr(buf, '\0', len); + if (user == NULL || user >= buf + len - 2) + goto abort; + user++; /* skip NUL */ + if (strlcpy(s->username, user, sizeof(s->username)) + >= sizeof(s->username)) + goto abort; + + pass = memchr(user, '\0', len - (user - buf)); + if (pass == NULL || pass >= buf + len - 2) + goto abort; + pass++; /* skip NUL */ + + m_create(p_lka, IMSG_SMTP_AUTHENTICATE, 0, 0, -1); + m_add_id(p_lka, s->id); + m_add_string(p_lka, s->listener->authtable); + m_add_string(p_lka, user); + m_add_string(p_lka, pass); + m_close(p_lka); + tree_xset(&wait_parent_auth, s->id, s); + return; + + default: + fatal("smtp_rfc4954_auth_plain: unknown state"); + } + +abort: + smtp_reply(s, "501 %s %s: Syntax error", + esc_code(ESC_STATUS_PERMFAIL, ESC_SYNTAX_ERROR), + esc_description(ESC_SYNTAX_ERROR)); + smtp_enter_state(s, STATE_HELO); +} + +static void +smtp_rfc4954_auth_login(struct smtp_session *s, char *arg) +{ + char buf[LINE_MAX]; + + switch (s->state) { + case STATE_HELO: + smtp_enter_state(s, STATE_AUTH_USERNAME); + if (arg != NULL && *arg != '\0') { + smtp_rfc4954_auth_login(s, arg); + return; + } + smtp_reply(s, "334 VXNlcm5hbWU6"); + return; + + case STATE_AUTH_USERNAME: + memset(s->username, 0, sizeof(s->username)); + if (base64_decode(arg, (unsigned char *)s->username, + sizeof(s->username) - 1) == -1) + goto abort; + + smtp_enter_state(s, STATE_AUTH_PASSWORD); + smtp_reply(s, "334 UGFzc3dvcmQ6"); + return; + + case STATE_AUTH_PASSWORD: + memset(buf, 0, sizeof(buf)); + if (base64_decode(arg, (unsigned char *)buf, + sizeof(buf)-1) == -1) + goto abort; + + m_create(p_lka, IMSG_SMTP_AUTHENTICATE, 0, 0, -1); + m_add_id(p_lka, s->id); + m_add_string(p_lka, s->listener->authtable); + m_add_string(p_lka, s->username); + m_add_string(p_lka, buf); + m_close(p_lka); + tree_xset(&wait_parent_auth, s->id, s); + return; + + default: + fatal("smtp_rfc4954_auth_login: unknown state"); + } + +abort: + smtp_reply(s, "501 %s %s: Syntax error", + esc_code(ESC_STATUS_PERMFAIL, ESC_SYNTAX_ERROR), + esc_description(ESC_SYNTAX_ERROR)); + smtp_enter_state(s, STATE_HELO); +} + +static void +smtp_lookup_servername(struct smtp_session *s) +{ + if (s->listener->hostnametable[0]) { + m_create(p_lka, IMSG_SMTP_LOOKUP_HELO, 0, 0, -1); + m_add_id(p_lka, s->id); + m_add_string(p_lka, s->listener->hostnametable); + m_add_sockaddr(p_lka, (struct sockaddr*)&s->listener->ss); + m_close(p_lka); + tree_xset(&wait_lka_helo, s->id, s); + return; + } + + smtp_connected(s); +} + +static void +smtp_connected(struct smtp_session *s) +{ + smtp_enter_state(s, STATE_CONNECTED); + + log_info("%016"PRIx64" smtp connected address=%s host=%s", + s->id, ss_to_text(&s->ss), s->rdns); + + smtp_filter_begin(s); + + smtp_report_link_connect(s, s->rdns, s->fcrdns, &s->ss, + &s->listener->ss); + + smtp_filter_phase(FILTER_CONNECT, s, ss_to_text(&s->ss)); +} + +static void +smtp_proceed_connected(struct smtp_session *s) +{ + if (s->listener->flags & F_SMTPS) + smtp_cert_init(s); + else + smtp_send_banner(s); +} + +static void +smtp_send_banner(struct smtp_session *s) +{ + smtp_reply(s, "220 %s ESMTP %s", s->smtpname, SMTPD_NAME); + s->banner_sent = 1; + smtp_report_link_greeting(s, s->smtpname); +} + +void +smtp_enter_state(struct smtp_session *s, int newstate) +{ + log_trace(TRACE_SMTP, "smtp: %p: %s -> %s", s, + smtp_strstate(s->state), + smtp_strstate(newstate)); + + s->state = newstate; +} + +static void +smtp_reply(struct smtp_session *s, char *fmt, ...) +{ + va_list ap; + int n; + char buf[LINE_MAX*2], tmp[LINE_MAX*2]; + + va_start(ap, fmt); + n = vsnprintf(buf, sizeof buf, fmt, ap); + va_end(ap); + if (n < 0) + fatalx("smtp_reply: response format error"); + if (n < 4) + fatalx("smtp_reply: response too short"); + if (n >= (int)sizeof buf) { + /* only first three bytes are used by SMTP logic, + * so if _our_ reply does not fit entirely in the + * buffer, it's ok to truncate. + */ + } + + log_trace(TRACE_SMTP, "smtp: %p: >>> %s", s, buf); + smtp_report_protocol_server(s, buf); + + switch (buf[0]) { + case '2': + if (s->tx) { + if (s->last_cmd == CMD_MAIL_FROM) { + smtp_report_tx_begin(s, s->tx->msgid); + smtp_report_tx_mail(s, s->tx->msgid, s->cmd + 10, 1); + } + else if (s->last_cmd == CMD_RCPT_TO) + smtp_report_tx_rcpt(s, s->tx->msgid, s->cmd + 8, 1); + } + break; + case '3': + if (s->tx) { + if (s->last_cmd == CMD_DATA) + smtp_report_tx_data(s, s->tx->msgid, 1); + } + break; + case '5': + case '4': + /* do not report smtp_tx_mail/smtp_tx_rcpt errors + * if they happened outside of a transaction. + */ + if (s->tx) { + if (s->last_cmd == CMD_MAIL_FROM) + smtp_report_tx_mail(s, s->tx->msgid, + s->cmd + 10, buf[0] == '4' ? -1 : 0); + else if (s->last_cmd == CMD_RCPT_TO) + smtp_report_tx_rcpt(s, + s->tx->msgid, s->cmd + 8, buf[0] == '4' ? -1 : 0); + else if (s->last_cmd == CMD_DATA && s->tx->rcptcount) + smtp_report_tx_data(s, s->tx->msgid, + buf[0] == '4' ? -1 : 0); + } + + if (s->flags & SF_BADINPUT) { + log_info("%016"PRIx64" smtp " + "bad-input result=\"%.*s\"", + s->id, n, buf); + } + else if (s->state == STATE_AUTH_INIT) { + log_info("%016"PRIx64" smtp " + "failed-command " + "command=\"AUTH PLAIN (...)\" result=\"%.*s\"", + s->id, n, buf); + } + else if (s->state == STATE_AUTH_USERNAME) { + log_info("%016"PRIx64" smtp " + "failed-command " + "command=\"AUTH LOGIN (username)\" result=\"%.*s\"", + s->id, n, buf); + } + else if (s->state == STATE_AUTH_PASSWORD) { + log_info("%016"PRIx64" smtp " + "failed-command " + "command=\"AUTH LOGIN (password)\" result=\"%.*s\"", + s->id, n, buf); + } + else { + strnvis(tmp, s->cmd, sizeof tmp, VIS_SAFE | VIS_CSTYLE); + log_info("%016"PRIx64" smtp " + "failed-command command=\"%s\" " + "result=\"%.*s\"", + s->id, tmp, n, buf); + } + break; + } + + io_xprintf(s->io, "%s\r\n", buf); +} + +static void +smtp_free(struct smtp_session *s, const char * reason) +{ + if (s->tx) { + if (s->tx->msgid) + smtp_tx_rollback(s->tx); + smtp_tx_free(s->tx); + } + + smtp_report_link_disconnect(s); + smtp_filter_end(s); + + if (s->flags & SF_SECURE && s->listener->flags & F_SMTPS) + stat_decrement("smtp.smtps", 1); + if (s->flags & SF_SECURE && s->listener->flags & F_STARTTLS) + stat_decrement("smtp.tls", 1); + + io_free(s->io); + free(s); + + smtp_collect(); +} + +static int +smtp_mailaddr(struct mailaddr *maddr, char *line, int mailfrom, char **args, + const char *domain) +{ + char *p, *e; + + if (line == NULL) + return (0); + + if (*line != '<') + return (0); + + e = strchr(line, '>'); + if (e == NULL) + return (0); + *e++ = '\0'; + while (*e == ' ') + e++; + *args = e; + + if (!text_to_mailaddr(maddr, line + 1)) + return (0); + + p = strchr(maddr->user, ':'); + if (p != NULL) { + p++; + memmove(maddr->user, p, strlen(p) + 1); + } + + /* accept empty return-path in MAIL FROM, required for bounces */ + if (mailfrom && maddr->user[0] == '\0' && maddr->domain[0] == '\0') + return (1); + + /* no or invalid user-part, reject */ + if (maddr->user[0] == '\0' || !valid_localpart(maddr->user)) + return (0); + + /* no domain part, local user */ + if (maddr->domain[0] == '\0') { + (void)strlcpy(maddr->domain, domain, + sizeof(maddr->domain)); + } + + if (!valid_domainpart(maddr->domain)) + return (0); + + return (1); +} + +static void +smtp_cert_init(struct smtp_session *s) +{ + const char *name; + int fallback; + + if (s->listener->pki_name[0]) { + name = s->listener->pki_name; + fallback = 0; + } + else { + name = s->smtpname; + fallback = 1; + } + + if (cert_init(name, fallback, smtp_cert_init_cb, s)) + tree_xset(&wait_ssl_init, s->id, s); +} + +static void +smtp_cert_init_cb(void *arg, int status, const char *name, const void *cert, + size_t cert_len) +{ + struct smtp_session *s = arg; + void *ssl, *ssl_ctx; + + tree_pop(&wait_ssl_init, s->id); + + if (status == CA_FAIL) { + log_info("%016"PRIx64" smtp disconnected " + "reason=ca-failure", + s->id); + smtp_free(s, "CA failure"); + return; + } + + ssl_ctx = dict_get(env->sc_ssl_dict, name); + ssl = ssl_smtp_init(ssl_ctx, s->listener->flags & F_TLS_VERIFY); + io_set_read(s->io); + io_start_tls(s->io, ssl); +} + +static void +smtp_cert_verify(struct smtp_session *s) +{ + const char *name; + int fallback; + + if (s->listener->ca_name[0]) { + name = s->listener->ca_name; + fallback = 0; + } + else { + name = s->smtpname; + fallback = 1; + } + + if (cert_verify(io_tls(s->io), name, fallback, smtp_cert_verify_cb, s)) { + tree_xset(&wait_ssl_verify, s->id, s); + io_pause(s->io, IO_IN); + } +} + +static void +smtp_cert_verify_cb(void *arg, int status) +{ + struct smtp_session *s = arg; + const char *reason = NULL; + int resume; + + resume = tree_pop(&wait_ssl_verify, s->id) != NULL; + + switch (status) { + case CERT_OK: + reason = "cert-ok"; + s->flags |= SF_VERIFIED; + break; + case CERT_NOCA: + reason = "no-ca"; + break; + case CERT_NOCERT: + reason = "no-client-cert"; + break; + case CERT_INVALID: + reason = "cert-invalid"; + break; + default: + reason = "cert-check-failed"; + break; + } + + log_debug("smtp: %p: smtp_cert_verify_cb: %s", s, reason); + + if (!(s->flags & SF_VERIFIED) && (s->listener->flags & F_TLS_VERIFY)) { + log_info("%016"PRIx64" smtp disconnected " + " reason=%s", s->id, + reason); + smtp_free(s, "SSL certificate check failed"); + return; + } + + smtp_tls_verified(s); + if (resume) + io_resume(s->io, IO_IN); +} + +static void +smtp_auth_failure_resume(int fd, short event, void *p) +{ + struct smtp_session *s = p; + + smtp_reply(s, "535 Authentication failed"); + smtp_enter_state(s, STATE_HELO); +} + +static void +smtp_auth_failure_pause(struct smtp_session *s) +{ + struct timeval tv; + + tv.tv_sec = 0; + tv.tv_usec = arc4random_uniform(1000000); + log_trace(TRACE_SMTP, "smtp: timing-attack protection triggered, " + "will defer answer for %lu microseconds", tv.tv_usec); + evtimer_set(&s->pause, smtp_auth_failure_resume, s); + evtimer_add(&s->pause, &tv); +} + +static int +smtp_tx(struct smtp_session *s) +{ + struct smtp_tx *tx; + + tx = calloc(1, sizeof(*tx)); + if (tx == NULL) + return 0; + + TAILQ_INIT(&tx->rcpts); + + s->tx = tx; + tx->session = s; + + /* setup the envelope */ + tx->evp.ss = s->ss; + (void)strlcpy(tx->evp.tag, s->listener->tag, sizeof(tx->evp.tag)); + (void)strlcpy(tx->evp.smtpname, s->smtpname, sizeof(tx->evp.smtpname)); + (void)strlcpy(tx->evp.hostname, s->rdns, sizeof tx->evp.hostname); + (void)strlcpy(tx->evp.helo, s->helo, sizeof(tx->evp.helo)); + (void)strlcpy(tx->evp.username, s->username, sizeof(tx->evp.username)); + + if (s->flags & SF_BOUNCE) + tx->evp.flags |= EF_BOUNCE; + if (s->flags & SF_AUTHENTICATED) + tx->evp.flags |= EF_AUTHENTICATED; + + if ((tx->parser = rfc5322_parser_new()) == NULL) { + free(tx); + return 0; + } + + return 1; +} + +static void +smtp_tx_free(struct smtp_tx *tx) +{ + struct smtp_rcpt *rcpt; + + rfc5322_free(tx->parser); + + while ((rcpt = TAILQ_FIRST(&tx->rcpts))) { + TAILQ_REMOVE(&tx->rcpts, rcpt, entry); + free(rcpt); + } + + if (tx->ofile) + fclose(tx->ofile); + + tx->session->tx = NULL; + + free(tx); +} + +static void +smtp_tx_mail_from(struct smtp_tx *tx, const char *line) +{ + char *opt; + char *copy; + char tmp[SMTP_LINE_MAX]; + + (void)strlcpy(tmp, line, sizeof tmp); + copy = tmp; + + if (smtp_mailaddr(&tx->evp.sender, copy, 1, ©, + tx->session->smtpname) == 0) { + smtp_reply(tx->session, "553 %s Sender address syntax error", + esc_code(ESC_STATUS_PERMFAIL, ESC_OTHER_ADDRESS_STATUS)); + smtp_tx_free(tx); + return; + } + + while ((opt = strsep(©, " "))) { + if (*opt == '\0') + continue; + + if (strncasecmp(opt, "AUTH=", 5) == 0) + log_debug("debug: smtp: AUTH in MAIL FROM command"); + else if (strncasecmp(opt, "SIZE=", 5) == 0) + log_debug("debug: smtp: SIZE in MAIL FROM command"); + else if (strcasecmp(opt, "BODY=7BIT") == 0) + /* XXX only for this transaction */ + tx->session->flags &= ~SF_8BITMIME; + else if (strcasecmp(opt, "BODY=8BITMIME") == 0) + ; + else if (ADVERTISE_EXT_DSN(tx->session) && strncasecmp(opt, "RET=", 4) == 0) { + opt += 4; + if (strcasecmp(opt, "HDRS") == 0) + tx->evp.dsn_ret = DSN_RETHDRS; + else if (strcasecmp(opt, "FULL") == 0) + tx->evp.dsn_ret = DSN_RETFULL; + } else if (ADVERTISE_EXT_DSN(tx->session) && strncasecmp(opt, "ENVID=", 6) == 0) { + opt += 6; + if (strlcpy(tx->evp.dsn_envid, opt, sizeof(tx->evp.dsn_envid)) + >= sizeof(tx->evp.dsn_envid)) { + smtp_reply(tx->session, + "503 %s %s: option too large, truncated: %s", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), + esc_description(ESC_INVALID_COMMAND_ARGUMENTS), opt); + smtp_tx_free(tx); + return; + } + } else { + smtp_reply(tx->session, "503 %s %s: Unsupported option %s", + esc_code(ESC_STATUS_PERMFAIL, ESC_INVALID_COMMAND_ARGUMENTS), + esc_description(ESC_INVALID_COMMAND_ARGUMENTS), opt); + smtp_tx_free(tx); + return; + } + } + + /* only check sendertable if defined and user has authenticated */ + if (tx->session->flags & SF_AUTHENTICATED && + tx->session->listener->sendertable[0]) { + m_create(p_lka, IMSG_SMTP_CHECK_SENDER, 0, 0, -1); + m_add_id(p_lka, tx->session->id); + m_add_string(p_lka, tx->session->listener->sendertable); + m_add_string(p_lka, tx->session->username); + m_add_mailaddr(p_lka, &tx->evp.sender); + m_close(p_lka); + tree_xset(&wait_lka_mail, tx->session->id, tx->session); + } + else + smtp_tx_create_message(tx); +} + +static void +smtp_tx_create_message(struct smtp_tx *tx) +{ + m_create(p_queue, IMSG_SMTP_MESSAGE_CREATE, 0, 0, -1); + m_add_id(p_queue, tx->session->id); + m_close(p_queue); + tree_xset(&wait_queue_msg, tx->session->id, tx->session); +} + +static void +smtp_tx_rcpt_to(struct smtp_tx *tx, const char *line) +{ + char *opt, *p; + char *copy; + char tmp[SMTP_LINE_MAX]; + + (void)strlcpy(tmp, line, sizeof tmp); + copy = tmp; + + if (tx->rcptcount >= env->sc_session_max_rcpt) { + smtp_reply(tx->session, "451 %s %s: Too many recipients", + esc_code(ESC_STATUS_TEMPFAIL, ESC_TOO_MANY_RECIPIENTS), + esc_description(ESC_TOO_MANY_RECIPIENTS)); + return; + } + + if (smtp_mailaddr(&tx->evp.rcpt, copy, 0, ©, + tx->session->smtpname) == 0) { + smtp_reply(tx->session, + "501 %s Recipient address syntax error", + esc_code(ESC_STATUS_PERMFAIL, + ESC_BAD_DESTINATION_MAILBOX_ADDRESS_SYNTAX)); + return; + } + + while ((opt = strsep(©, " "))) { + if (*opt == '\0') + continue; + + if (ADVERTISE_EXT_DSN(tx->session) && strncasecmp(opt, "NOTIFY=", 7) == 0) { + opt += 7; + while ((p = strsep(&opt, ","))) { + if (strcasecmp(p, "SUCCESS") == 0) + tx->evp.dsn_notify |= DSN_SUCCESS; + else if (strcasecmp(p, "FAILURE") == 0) + tx->evp.dsn_notify |= DSN_FAILURE; + else if (strcasecmp(p, "DELAY") == 0) + tx->evp.dsn_notify |= DSN_DELAY; + else if (strcasecmp(p, "NEVER") == 0) + tx->evp.dsn_notify |= DSN_NEVER; + } + + if (tx->evp.dsn_notify & DSN_NEVER && + tx->evp.dsn_notify & (DSN_SUCCESS | DSN_FAILURE | + DSN_DELAY)) { + smtp_reply(tx->session, + "553 NOTIFY option NEVER cannot be" + " combined with other options"); + return; + } + } else if (ADVERTISE_EXT_DSN(tx->session) && strncasecmp(opt, "ORCPT=", 6) == 0) { + opt += 6; + + if (strncasecmp(opt, "rfc822;", 7) == 0) + opt += 7; + + if (!text_to_mailaddr(&tx->evp.dsn_orcpt, opt) || + !valid_localpart(tx->evp.dsn_orcpt.user) || + !valid_domainpart(tx->evp.dsn_orcpt.domain)) { + smtp_reply(tx->session, + "553 ORCPT address syntax error"); + return; + } + } else { + smtp_reply(tx->session, "503 Unsupported option %s", opt); + return; + } + } + + m_create(p_lka, IMSG_SMTP_EXPAND_RCPT, 0, 0, -1); + m_add_id(p_lka, tx->session->id); + m_add_envelope(p_lka, &tx->evp); + m_close(p_lka); + tree_xset(&wait_lka_rcpt, tx->session->id, tx->session); +} + +static void +smtp_tx_open_message(struct smtp_tx *tx) +{ + m_create(p_queue, IMSG_SMTP_MESSAGE_OPEN, 0, 0, -1); + m_add_id(p_queue, tx->session->id); + m_add_msgid(p_queue, tx->msgid); + m_close(p_queue); + tree_xset(&wait_queue_fd, tx->session->id, tx->session); +} + +static void +smtp_tx_commit(struct smtp_tx *tx) +{ + m_create(p_queue, IMSG_SMTP_MESSAGE_COMMIT, 0, 0, -1); + m_add_id(p_queue, tx->session->id); + m_add_msgid(p_queue, tx->msgid); + m_close(p_queue); + tree_xset(&wait_queue_commit, tx->session->id, tx->session); + smtp_filter_data_end(tx->session); +} + +static void +smtp_tx_rollback(struct smtp_tx *tx) +{ + m_create(p_queue, IMSG_SMTP_MESSAGE_ROLLBACK, 0, 0, -1); + m_add_msgid(p_queue, tx->msgid); + m_close(p_queue); + smtp_report_tx_rollback(tx->session, tx->msgid); + smtp_report_tx_reset(tx->session, tx->msgid); + smtp_filter_data_end(tx->session); +} + +static int +smtp_tx_dataline(struct smtp_tx *tx, const char *line) +{ + struct rfc5322_result res; + int r; + + log_trace(TRACE_SMTP, "<<< [MSG] %s", line); + + if (!strcmp(line, ".")) { + smtp_report_protocol_client(tx->session, "."); + log_trace(TRACE_SMTP, "<<< [EOM]"); + if (tx->error) + return 1; + line = NULL; + } + else { + /* ignore data line if an error is set */ + if (tx->error) + return 0; + + /* escape lines starting with a '.' */ + if (line[0] == '.') + line += 1; + } + + if (rfc5322_push(tx->parser, line) == -1) { + log_warnx("failed to push dataline"); + tx->error = TX_ERROR_INTERNAL; + return 0; + } + + for(;;) { + r = rfc5322_next(tx->parser, &res); + switch (r) { + case -1: + if (errno == ENOMEM) + tx->error = TX_ERROR_INTERNAL; + else + tx->error = TX_ERROR_MALFORMED; + return 0; + + case RFC5322_NONE: + /* Need more data */ + return 0; + + case RFC5322_HEADER_START: + /* ignore bcc */ + if (!strcasecmp("Bcc", res.hdr)) + continue; + + if (!strcasecmp("To", res.hdr) || + !strcasecmp("Cc", res.hdr) || + !strcasecmp("From", res.hdr)) { + rfc5322_unfold_header(tx->parser); + continue; + } + + if (!strcasecmp("Received", res.hdr)) { + if (++tx->rcvcount >= MAX_HOPS_COUNT) { + log_warnx("warn: loop detected"); + tx->error = TX_ERROR_LOOP; + return 0; + } + } + else if (!tx->has_date && !strcasecmp("Date", res.hdr)) + tx->has_date = 1; + else if (!tx->has_message_id && + !strcasecmp("Message-Id", res.hdr)) + tx->has_message_id = 1; + + smtp_message_printf(tx, "%s:%s\n", res.hdr, res.value); + break; + + case RFC5322_HEADER_CONT: + + if (!strcasecmp("Bcc", res.hdr) || + !strcasecmp("To", res.hdr) || + !strcasecmp("Cc", res.hdr) || + !strcasecmp("From", res.hdr)) + continue; + + smtp_message_printf(tx, "%s\n", res.value); + break; + + case RFC5322_HEADER_END: + if (!strcasecmp("To", res.hdr) || + !strcasecmp("Cc", res.hdr) || + !strcasecmp("From", res.hdr)) + header_domain_append_callback(tx, res.hdr, + res.value); + break; + + case RFC5322_END_OF_HEADERS: + if (tx->session->listener->local || + tx->session->listener->port == 587) { + + if (!tx->has_date) { + log_debug("debug: %p: adding Date", tx); + smtp_message_printf(tx, "Date: %s\n", + time_to_text(tx->time)); + } + + if (!tx->has_message_id) { + log_debug("debug: %p: adding Message-ID", tx); + smtp_message_printf(tx, + "Message-ID: <%016"PRIx64"@%s>\n", + generate_uid(), + tx->session->listener->hostname); + } + } + break; + + case RFC5322_BODY_START: + case RFC5322_BODY: + smtp_message_printf(tx, "%s\n", res.value); + break; + + case RFC5322_END_OF_MESSAGE: + return 1; + + default: + fatalx("%s", __func__); + } + } +} + +static int +smtp_tx_filtered_dataline(struct smtp_tx *tx, const char *line) +{ + if (!strcmp(line, ".")) + line = NULL; + else { + /* ignore data line if an error is set */ + if (tx->error) + return 0; + } + io_printf(tx->filter, "%s\n", line ? line : "."); + return line ? 0 : 1; +} + +static void +smtp_tx_eom(struct smtp_tx *tx) +{ + smtp_filter_phase(FILTER_COMMIT, tx->session, NULL); +} + +static int +smtp_message_fd(struct smtp_tx *tx, int fd) +{ + struct smtp_session *s; + + s = tx->session; + + log_debug("smtp: %p: message fd %d", s, fd); + + if ((tx->ofile = fdopen(fd, "w")) == NULL) { + close(fd); + smtp_reply(s, "421 %s Temporary Error", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + smtp_enter_state(s, STATE_QUIT); + return 0; + } + return 1; +} + +static void +filter_session_io(struct io *io, int evt, void *arg) +{ + struct smtp_tx*tx = arg; + char*line = NULL; + ssize_t len; + + log_trace(TRACE_IO, "filter session io (smtp): %p: %s %s", tx, io_strevent(evt), + io_strio(io)); + + switch (evt) { + case IO_DATAIN: + nextline: + line = io_getline(tx->filter, &len); + /* No complete line received */ + if (line == NULL) + return; + + if (smtp_tx_dataline(tx, line)) { + smtp_tx_eom(tx); + return; + } + + goto nextline; + } +} + +static void +smtp_filter_fd(struct smtp_tx *tx, int fd) +{ + struct smtp_session *s; + + s = tx->session; + + log_debug("smtp: %p: filter fd %d", s, fd); + + tx->filter = io_new(); + io_set_fd(tx->filter, fd); + io_set_callback(tx->filter, filter_session_io, tx); +} + +static void +smtp_message_begin(struct smtp_tx *tx) +{ + struct smtp_session *s; + X509 *x; + int (*m_printf)(struct smtp_tx *, const char *, ...); + + m_printf = smtp_message_printf; + if (tx->filter) + m_printf = smtp_filter_printf; + + s = tx->session; + + log_debug("smtp: %p: message begin", s); + + smtp_reply(s, "354 Enter mail, end with \".\"" + " on a line by itself"); + + if (s->junk || (s->tx && s->tx->junk)) + m_printf(tx, "X-Spam: Yes\n"); + + m_printf(tx, "Received: "); + if (!(s->listener->flags & F_MASK_SOURCE)) { + m_printf(tx, "from %s (%s %s%s%s)", + s->helo, + s->rdns, + s->ss.ss_family == AF_INET6 ? "" : "[", + ss_to_text(&s->ss), + s->ss.ss_family == AF_INET6 ? "" : "]"); + } + m_printf(tx, "\n\tby %s (%s) with %sSMTP%s%s id %08x", + s->smtpname, + SMTPD_NAME, + s->flags & SF_EHLO ? "E" : "", + s->flags & SF_SECURE ? "S" : "", + s->flags & SF_AUTHENTICATED ? "A" : "", + tx->msgid); + + if (s->flags & SF_SECURE) { + x = SSL_get_peer_certificate(io_tls(s->io)); + m_printf(tx, " (%s:%s:%d:%s)", + SSL_get_version(io_tls(s->io)), + SSL_get_cipher_name(io_tls(s->io)), + SSL_get_cipher_bits(io_tls(s->io), NULL), + (s->flags & SF_VERIFIED) ? "YES" : (x ? "FAIL" : "NO")); + X509_free(x); + + if (s->listener->flags & F_RECEIVEDAUTH) { + m_printf(tx, " auth=%s", + s->username[0] ? "yes" : "no"); + if (s->username[0]) + m_printf(tx, " user=%s", s->username); + } + } + + if (tx->rcptcount == 1) { + m_printf(tx, "\n\tfor <%s@%s>", + tx->evp.rcpt.user, + tx->evp.rcpt.domain); + } + + m_printf(tx, ";\n\t%s\n", time_to_text(time(&tx->time))); + + smtp_enter_state(s, STATE_BODY); +} + +static void +smtp_message_end(struct smtp_tx *tx) +{ + struct smtp_session *s; + + s = tx->session; + + log_debug("debug: %p: end of message, error=%d", s, tx->error); + + fclose(tx->ofile); + tx->ofile = NULL; + + switch(tx->error) { + case TX_OK: + smtp_tx_commit(tx); + return; + + case TX_ERROR_SIZE: + smtp_reply(s, "554 %s %s: Transaction failed, message too big", + esc_code(ESC_STATUS_PERMFAIL, ESC_MESSAGE_TOO_BIG_FOR_SYSTEM), + esc_description(ESC_MESSAGE_TOO_BIG_FOR_SYSTEM)); + break; + + case TX_ERROR_LOOP: + smtp_reply(s, "500 %s %s: Loop detected", + esc_code(ESC_STATUS_PERMFAIL, ESC_ROUTING_LOOP_DETECTED), + esc_description(ESC_ROUTING_LOOP_DETECTED)); + break; + + case TX_ERROR_MALFORMED: + smtp_reply(s, "550 %s %s: Message is not RFC 2822 compliant", + esc_code(ESC_STATUS_PERMFAIL, ESC_DELIVERY_NOT_AUTHORIZED_MESSAGE_REFUSED), + esc_description(ESC_DELIVERY_NOT_AUTHORIZED_MESSAGE_REFUSED)); + break; + + case TX_ERROR_IO: + case TX_ERROR_RESOURCES: + smtp_reply(s, "421 %s Temporary Error", + esc_code(ESC_STATUS_TEMPFAIL, ESC_OTHER_MAIL_SYSTEM_STATUS)); + break; + + default: + /* fatal? */ + smtp_reply(s, "421 Internal server error"); + } + + smtp_tx_rollback(tx); + smtp_tx_free(tx); + smtp_enter_state(s, STATE_HELO); +} + +static int +smtp_filter_printf(struct smtp_tx *tx, const char *fmt, ...) +{ + va_list ap; + int len; + + if (tx->error) + return -1; + + va_start(ap, fmt); + len = io_vprintf(tx->filter, fmt, ap); + va_end(ap); + + if (len < 0) { + log_warn("smtp-in: session %016"PRIx64": vfprintf", tx->session->id); + tx->error = TX_ERROR_IO; + } + else + tx->odatalen += len; + + return len; +} + +static int +smtp_message_printf(struct smtp_tx *tx, const char *fmt, ...) +{ + va_list ap; + int len; + + if (tx->error) + return -1; + + va_start(ap, fmt); + len = vfprintf(tx->ofile, fmt, ap); + va_end(ap); + + if (len == -1) { + log_warn("smtp-in: session %016"PRIx64": vfprintf", tx->session->id); + tx->error = TX_ERROR_IO; + } + else + tx->odatalen += len; + + return len; +} + +#define CASE(x) case x : return #x + +const char * +smtp_strstate(int state) +{ + static char buf[32]; + + switch (state) { + CASE(STATE_NEW); + CASE(STATE_CONNECTED); + CASE(STATE_TLS); + CASE(STATE_HELO); + CASE(STATE_AUTH_INIT); + CASE(STATE_AUTH_USERNAME); + CASE(STATE_AUTH_PASSWORD); + CASE(STATE_AUTH_FINALIZE); + CASE(STATE_BODY); + CASE(STATE_QUIT); + default: + (void)snprintf(buf, sizeof(buf), "STATE_??? (%d)", state); + return (buf); + } +} + + +static void +smtp_report_link_connect(struct smtp_session *s, const char *rdns, int fcrdns, + const struct sockaddr_storage *ss_src, + const struct sockaddr_storage *ss_dest) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_connect("smtp-in", s->id, rdns, fcrdns, ss_src, ss_dest); +} + +static void +smtp_report_link_greeting(struct smtp_session *s, + const char *domain) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_greeting("smtp-in", s->id, domain); +} + +static void +smtp_report_link_identify(struct smtp_session *s, const char *method, const char *identity) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_identify("smtp-in", s->id, method, identity); +} + +static void +smtp_report_link_tls(struct smtp_session *s, const char *ssl) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_tls("smtp-in", s->id, ssl); +} + +static void +smtp_report_link_disconnect(struct smtp_session *s) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_disconnect("smtp-in", s->id); +} + +static void +smtp_report_link_auth(struct smtp_session *s, const char *user, const char *result) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_link_auth("smtp-in", s->id, user, result); +} + +static void +smtp_report_tx_reset(struct smtp_session *s, uint32_t msgid) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_reset("smtp-in", s->id, msgid); +} + +static void +smtp_report_tx_begin(struct smtp_session *s, uint32_t msgid) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_begin("smtp-in", s->id, msgid); +} + +static void +smtp_report_tx_mail(struct smtp_session *s, uint32_t msgid, const char *address, int ok) +{ + char mailaddr[SMTPD_MAXMAILADDRSIZE]; + char *p; + + if (! SESSION_FILTERED(s)) + return; + + if ((p = strchr(address, '<')) == NULL) + return; + (void)strlcpy(mailaddr, p + 1, sizeof mailaddr); + if ((p = strchr(mailaddr, '>')) == NULL) + return; + *p = '\0'; + + report_smtp_tx_mail("smtp-in", s->id, msgid, mailaddr, ok); +} + +static void +smtp_report_tx_rcpt(struct smtp_session *s, uint32_t msgid, const char *address, int ok) +{ + char mailaddr[SMTPD_MAXMAILADDRSIZE]; + char *p; + + if (! SESSION_FILTERED(s)) + return; + + if ((p = strchr(address, '<')) == NULL) + return; + (void)strlcpy(mailaddr, p + 1, sizeof mailaddr); + if ((p = strchr(mailaddr, '>')) == NULL) + return; + *p = '\0'; + + report_smtp_tx_rcpt("smtp-in", s->id, msgid, mailaddr, ok); +} + +static void +smtp_report_tx_envelope(struct smtp_session *s, uint32_t msgid, uint64_t evpid) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_envelope("smtp-in", s->id, msgid, evpid); +} + +static void +smtp_report_tx_data(struct smtp_session *s, uint32_t msgid, int ok) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_data("smtp-in", s->id, msgid, ok); +} + +static void +smtp_report_tx_commit(struct smtp_session *s, uint32_t msgid, size_t msgsz) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_commit("smtp-in", s->id, msgid, msgsz); +} + +static void +smtp_report_tx_rollback(struct smtp_session *s, uint32_t msgid) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_tx_rollback("smtp-in", s->id, msgid); +} + +static void +smtp_report_protocol_client(struct smtp_session *s, const char *command) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_protocol_client("smtp-in", s->id, command); +} + +static void +smtp_report_protocol_server(struct smtp_session *s, const char *response) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_protocol_server("smtp-in", s->id, response); +} + +static void +smtp_report_filter_response(struct smtp_session *s, int phase, int response, const char *param) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_filter_response("smtp-in", s->id, phase, response, param); +} + +static void +smtp_report_timeout(struct smtp_session *s) +{ + if (! SESSION_FILTERED(s)) + return; + + report_smtp_timeout("smtp-in", s->id); +} diff --git a/foobar/portable/smtpd/smtpc.c b/foobar/portable/smtpd/smtpc.c new file mode 100644 index 00000000..59479703 --- /dev/null +++ b/foobar/portable/smtpd/smtpc.c @@ -0,0 +1,465 @@ +/* $OpenBSD: smtpc.c,v 1.10 2019/09/21 09:04:08 semarie Exp $ */ + +/* + * Copyright (c) 2018 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> + +#include <event.h> +#include <limits.h> +#include <netdb.h> +#include <pwd.h> +#include <resolv.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include <openssl/ssl.h> + +#include "smtp.h" +#include "ssl.h" +#include "log.h" + +static void parse_server(char *); +static void parse_message(FILE *); +static void resume(void); + +static int verbose = 1; +static int done = 0; +static int noaction = 0; +static struct addrinfo *res0, *ai; +static struct smtp_params params; +static struct smtp_mail mail; +static const char *servname = NULL; + +static SSL_CTX *ssl_ctx; + +static void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, + "usage: %s [-Chnv] [-F from] [-H helo] [-s server] [-S name] rcpt ...\n", + __progname); + exit(1); +} + +int +main(int argc, char **argv) +{ + char hostname[256]; + int ch, i; + char *server = "localhost"; + struct passwd *pw; + + log_init(1, 0); + + if (gethostname(hostname, sizeof(hostname)) == -1) + fatal("gethostname"); + + if ((pw = getpwuid(getuid())) == NULL) + fatal("getpwuid"); + + memset(¶ms, 0, sizeof(params)); + + params.linemax = 16392; + params.ibufmax = 65536; + params.obufmax = 65536; + params.timeout = 100000; + params.helo = hostname; + + params.tls_verify = 1; + + memset(&mail, 0, sizeof(mail)); + mail.from = pw->pw_name; + + while ((ch = getopt(argc, argv, "CF:H:S:hns:v")) != -1) { + switch (ch) { + case 'C': + params.tls_verify = 0; + break; + case 'F': + mail.from = optarg; + break; + case 'H': + params.helo = optarg; + break; + case 'S': + servname = optarg; + break; + case 'h': + usage(); + break; + case 'n': + noaction = 1; + break; + case 's': + server = optarg; + break; + case 'v': + verbose++; + break; + default: + usage(); + } + } + + log_setverbose(verbose); + + argc -= optind; + argv += optind; + + if (argc) { + mail.rcpt = calloc(argc, sizeof(*mail.rcpt)); + if (mail.rcpt == NULL) + fatal("calloc"); + for (i = 0; i < argc; i++) + mail.rcpt[i].to = argv[i]; + mail.rcptcount = argc; + } + + ssl_init(); + event_init(); + + ssl_ctx = ssl_ctx_create(NULL, NULL, 0, NULL); + if (!SSL_CTX_load_verify_locations(ssl_ctx, + X509_get_default_cert_file(), NULL)) + fatal("SSL_CTX_load_verify_locations"); + if (!SSL_CTX_set_ssl_version(ssl_ctx, SSLv23_client_method())) + fatal("SSL_CTX_set_ssl_version"); + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE , NULL); + +#if HAVE_PLEDGE + if (pledge("stdio inet dns tmppath", NULL) == -1) + fatal("pledge"); +#endif + + if (!noaction) + parse_message(stdin); + +#if HAVE_PLEDGE + if (pledge("stdio inet dns", NULL) == -1) + fatal("pledge"); +#endif + + parse_server(server); + +#if HAVE_PLEDGE + if (pledge("stdio inet", NULL) == -1) + fatal("pledge"); +#endif + + resume(); + + log_debug("done..."); + + return 0; +} + +static void +parse_server(char *server) +{ + struct addrinfo hints; + char *scheme, *creds, *host, *port, *p, *c; + int error; + + creds = NULL; + host = NULL; + port = NULL; + scheme = server; + + p = strstr(server, "://"); + if (p) { + *p = '\0'; + p += 3; + /* check for credentials */ + c = strchr(p, '@'); + if (c) { + creds = p; + *c = '\0'; + host = c + 1; + } else + host = p; + } else { + /* Assume a simple server name */ + scheme = "smtp"; + host = server; + } + + if (host[0] == '[') { + /* IPV6 address? */ + p = strchr(host, ']'); + if (p) { + if (p[1] == ':' || p[1] == '\0') { + *p++ = '\0'; /* remove ']' */ + host++; /* skip '[' */ + if (*p == ':') + port = p + 1; + } + } + } + else { + port = strchr(host, ':'); + if (port) + *port++ = '\0'; + } + + if (port && port[0] == '\0') + port = NULL; + + if (creds) { + p = strchr(creds, ':'); + if (p == NULL) + fatalx("invalid credentials"); + *p = '\0'; + + params.auth_user = creds; + params.auth_pass = p + 1; + } + params.tls_req = TLS_YES; + + if (!strcmp(scheme, "lmtp")) { + params.lmtp = 1; + } + else if (!strcmp(scheme, "lmtp+tls")) { + params.lmtp = 1; + params.tls_req = TLS_FORCE; + } + else if (!strcmp(scheme, "lmtp+notls")) { + params.lmtp = 1; + params.tls_req = TLS_NO; + } + else if (!strcmp(scheme, "smtps")) { + params.tls_req = TLS_SMTPS; + if (port == NULL) + port = "smtps"; + } + else if (!strcmp(scheme, "smtp")) { + } + else if (!strcmp(scheme, "smtp+tls")) { + params.tls_req = TLS_FORCE; + } + else if (!strcmp(scheme, "smtp+notls")) { + params.tls_req = TLS_NO; + } + else + fatalx("invalid url scheme %s", scheme); + + if (port == NULL) + port = "smtp"; + + if (servname == NULL) + servname = host; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + error = getaddrinfo(host, port, &hints, &res0); + if (error) + fatalx("%s: %s", host, gai_strerror(error)); + ai = res0; +} + +void +parse_message(FILE *ifp) +{ + char *line = NULL; + size_t linesz = 0; + ssize_t len; + + if ((mail.fp = tmpfile()) == NULL) + fatal("tmpfile"); + + for (;;) { + if ((len = getline(&line, &linesz, ifp)) == -1) { + if (feof(ifp)) + break; + fatal("getline"); + } + + if (len >= 2 && line[len - 2] == '\r' && line[len - 1] == '\n') + line[--len - 1] = '\n'; + + if (fwrite(line, 1, len, mail.fp) != (size_t)len) + fatal("fwrite"); + + if (line[len - 1] != '\n' && fputc('\n', mail.fp) == EOF) + fatal("fputc"); + } + + fclose(ifp); + rewind(mail.fp); +} + +void +resume(void) +{ + static int started = 0; + char host[256]; + char serv[16]; + + if (done) { + event_loopexit(NULL); + return; + } + + if (ai == NULL) + fatalx("no more host"); + + getnameinfo(ai->ai_addr, SA_LEN(ai->ai_addr), + host, sizeof(host), serv, sizeof(serv), + NI_NUMERICHOST | NI_NUMERICSERV); + log_debug("trying host %s port %s...", host, serv); + + params.dst = ai->ai_addr; + if (smtp_connect(¶ms, NULL) == NULL) + fatal("smtp_connect"); + + if (started == 0) { + started = 1; + event_loop(0); + } +} + +void +log_trace(int lvl, const char *emsg, ...) +{ + va_list ap; + + if (verbose > lvl) { + va_start(ap, emsg); + vlog(LOG_DEBUG, emsg, ap); + va_end(ap); + } +} + +void +smtp_verify_server_cert(void *tag, struct smtp_client *proto, void *ctx) +{ + SSL *ssl = ctx; + X509 *cert; + long res; + int match; + + if ((cert = SSL_get_peer_certificate(ssl))) { + (void)ssl_check_name(cert, servname, &match); + X509_free(cert); + res = SSL_get_verify_result(ssl); + if (res == X509_V_OK) { + if (match) { + log_debug("valid certificate"); + smtp_cert_verified(proto, CERT_OK); + } + else { + log_debug("certificate does not match hostname"); + smtp_cert_verified(proto, CERT_INVALID); + } + return; + } + log_debug("certificate validation error %ld", res); + } + else + log_debug("no certificate provided"); + + smtp_cert_verified(proto, CERT_INVALID); +} + +void +smtp_require_tls(void *tag, struct smtp_client *proto) +{ + SSL *ssl = NULL; + + if ((ssl = SSL_new(ssl_ctx)) == NULL) + fatal("SSL_new"); + smtp_set_tls(proto, ssl); +} + +void +smtp_ready(void *tag, struct smtp_client *proto) +{ + log_debug("connection ready..."); + + if (done || noaction) + smtp_quit(proto); + else + smtp_sendmail(proto, &mail); +} + +void +smtp_failed(void *tag, struct smtp_client *proto, int failure, const char *detail) +{ + switch (failure) { + case FAIL_INTERNAL: + log_warnx("internal error: %s", detail); + break; + case FAIL_CONN: + log_warnx("connection error: %s", detail); + break; + case FAIL_PROTO: + log_warnx("protocol error: %s", detail); + break; + case FAIL_IMPL: + log_warnx("missing feature: %s", detail); + break; + case FAIL_RESP: + log_warnx("rejected by server: %s", detail); + break; + default: + fatalx("unknown failure %d: %s", failure, detail); + } +} + +void +smtp_status(void *tag, struct smtp_client *proto, struct smtp_status *status) +{ + log_info("%s: %s: %s", status->rcpt->to, status->cmd, status->status); +} + +void +smtp_done(void *tag, struct smtp_client *proto, struct smtp_mail *mail) +{ + int i; + + log_debug("mail done..."); + + if (noaction) + return; + + for (i = 0; i < mail->rcptcount; i++) + if (!mail->rcpt[i].done) + return; + + done = 1; +} + +void +smtp_closed(void *tag, struct smtp_client *proto) +{ + log_debug("connection closed..."); + + ai = ai->ai_next; + if (noaction && ai == NULL) + done = 1; + + resume(); +} diff --git a/foobar/portable/smtpd/smtpctl.8 b/foobar/portable/smtpd/smtpctl.8 new file mode 100644 index 00000000..1efcff63 --- /dev/null +++ b/foobar/portable/smtpd/smtpctl.8 @@ -0,0 +1,336 @@ +.\" $OpenBSD: smtpctl.8,v 1.64 2018/09/18 06:21:45 miko Exp $ +.\" +.\" Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org> +.\" Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: September 18 2018 $ +.Dt SMTPCTL 8 +.Os +.Sh NAME +.Nm smtpctl , +.Nm mailq +.Nd control the Simple Mail Transfer Protocol daemon +.Sh SYNOPSIS +.Nm +.Ar command +.Op Ar argument ... +.Nm mailq +.Sh DESCRIPTION +The +.Nm +program controls +.Xr smtpd 8 . +Commands may be abbreviated to the minimum unambiguous prefix; for example, +.Cm sh ro +for +.Cm show routes . +.Pp +The +.Nm mailq +command is provided for compatibility with other MTAs +and is simply a shortcut for +.Cm show queue . +.Pp +The following commands are available: +.Bl -tag -width Ds +.It Cm discover Ar envelope-id | message-id +Schedule a single envelope, or all envelopes with the same message ID +that were manually moved to the queue. +.It Cm encrypt Op Ar string +Encrypt the password +.Ar string +to a representation suitable for user credentials and print it to the +standard output. +If +.Ar string +is not provided, cleartext passwords are read from standard input. +.Pp +It is advised to avoid providing the password as a parameter as it will be +visible from +.Xr top 1 +and +.Xr ps 1 +output. +.It Cm log brief +Disable verbose debug logging. +.It Cm log verbose +Enable verbose debug logging. +.It Cm monitor +Display updates of some +.Xr smtpd 8 +internal counters in one second intervals. +Each line reports the increment of all counters since the last update, +except for some counters which are always absolute values. +The first line reports the current value of each counter. +The fields are: +.Pp +.Bl -bullet -compact +.It +Current number of active SMTP clients (absolute value). +.It +New SMTP clients. +.It +Disconnected clients. +.It +Current number of envelopes in the queue (absolute value). +.It +Newly enqueued envelopes. +.It +Dequeued envelopes. +.It +Successful deliveries. +.It +Temporary failures. +.It +Permanent failures. +.It +Message loops. +.It +Expired envelopes. +.It +Envelopes removed by the administrator. +.It +Generated bounces. +.El +.It Cm pause envelope Ar envelope-id | message-id | Cm all +Temporarily suspend scheduling for the envelope with the given ID, +envelopes with the given message ID, +or all envelopes. +.It Cm pause mda +Temporarily stop deliveries to local users. +.It Cm pause mta +Temporarily stop relaying and deliveries to +remote users. +.It Cm pause smtp +Temporarily stop accepting incoming sessions. +.It Cm profile Ar subsystem +Enables real-time profiling of +.Ar subsystem . +Supported subsystems are: +.Pp +.Bl -bullet -compact +.It +queue, to profile cost of queue IO +.It +imsg, to profile cost of event handlers +.El +.It Cm remove Ar envelope-id | message-id | Cm all +Remove a single envelope, +envelopes with the given message ID, +or all envelopes. +.It Cm resume envelope Ar envelope-id | message-id | Cm all +Resume scheduling for the envelope with the given ID, +envelopes with the given message ID, +or all envelopes. +.It Cm resume mda +Resume deliveries to local users. +.It Cm resume mta +Resume relaying and deliveries to remote users. +.It Cm resume route Ar route-id +Resume routing on disabled route +.Ar route-id . +.It Cm resume smtp +Resume accepting incoming sessions. +.It Cm schedule Ar envelope-id | message-id | Cm all +Mark as ready for immediate delivery +a single envelope, +envelopes with the given message ID, +or all envelopes. +.It Cm show envelope Ar envelope-id +Display envelope content for the given ID. +.It Cm show hosts +Display the list of known remote MX hosts. +For each of them, it shows the IP address, the canonical hostname, +a reference count, the number of active connections to this host, +and the elapsed time since the last connection. +.It Cm show hoststats +Display status of last delivery for domains that have been active in the +last 4 hours. +It consists of the following fields, separated by a "|": +.Pp +.Bl -bullet -compact +.It +Domain. +.It +.Ux +timestamp of last delivery. +.It +Status of last delivery. +.El +.It Cm show message Ar envelope-id +Display message content for the given ID. +.It Cm show queue +Display information concerning envelopes that are currently in the queue. +Each line of output describes a single envelope. +It consists of the following fields, separated by a "|": +.Pp +.Bl -bullet -compact +.It +Envelope ID. +.It +Address family of the client which enqueued the mail. +.It +Type of delivery: one of "mta", "mda" or "bounce". +.It +Various flags on the envelope. +.It +Sender address (return path). +.It +The original recipient address. +.It +The destination address. +.It +Time of creation. +.It +Time of expiration. +.It +Time of last delivery or relaying attempt. +.It +Number of delivery or relaying attempts. +.It +Current runstate: either "pending" or "inflight" if +.Xr smtpd 8 +is running, or "offline" otherwise. +.It +Delay in seconds before the next attempt if pending, or time elapsed +if currently running. +This field is blank if +.Xr smtpd 8 +is not running. +.It +Error string for the last failed delivery or relay attempt. +.El +.It Cm show relays +Display the list of currently active relays and associated connectors. +For each relay, it shows a number of counters and information on its +internal state on a single line. +Then comes the list of connectors +(source addresses to connect from for this relay). +.It Cm show routes +Display status of routes currently known by +.Xr smtpd 8 . +Each line consists of a route number, a source address, a destination +address, a set of flags, the number of connections on this +route, the current penalty level which determines the amount of time +the route is disabled if an error occurs, and the delay before it +gets reactivated. +The following flags are defined: +.Pp +.Bl -tag -width xx -compact +.It D +The route is currently disabled. +.It N +The route is new. +No SMTP session has been established yet. +.It Q +The route has a timeout registered to lower its penalty level and possibly +reactivate or discard it. +.El +.It Cm show stats +Displays runtime statistics concerning +.Xr smtpd 8 . +.It Cm show status +Shows if MTA, MDA and SMTP systems are currently running or paused. +.It Cm spf walk +Recursively look up SPF records for the domains read from stdin. +For example: +.Bd -literal -offset indent +# smtpctl spf walk < domains.txt +.Ed +.It Cm trace Ar subsystem +Enables real-time tracing of +.Ar subsystem . +Supported subsystems are: +.Pp +.Bl -bullet -compact +.It +imsg +.It +io +.It +smtp (incoming sessions) +.It +filters +.It +mta (outgoing sessions) +.It +bounce +.It +scheduler +.It +expand (aliases/virtual/forward expansion) +.It +lookup (user/credentials lookups) +.It +stat +.It +rules (matched by incoming sessions) +.It +mproc +.It +all +.El +.It Cm unprofile Ar subsystem +Disables real-time profiling of +.Ar subsystem . +.It Cm untrace Ar subsystem +Disables real-time tracing of +.Ar subsystem . +.It Cm update table Ar name +Updates the contents of table +.Ar name , +for tables using the +.Dq file +backend. +.El +.Pp +When +.Nm smtpd +receives a message, it generates a +.Ar message-id +for the message, and one +.Ar envelope-id +per recipient. +The +.Ar message-id +is a 32-bit random identifier that is guaranteed to be +unique on the host system. +The +.Ar envelope-id +is a 64-bit unique identifier that encodes the +.Ar message-id +in the 32 upper bits and a random envelope identifier +in the 32 lower bits. +.Pp +A command which specifies a +.Ar message-id +applies to all recipients of a message; +a command which specifies an +.Ar envelope-id +applies to a specific recipient of a message. +.Sh FILES +.Bl -tag -width "/var/run/smtpd.sockXXX" -compact +.It Pa /var/run/smtpd.sock +.Ux Ns -domain +socket used for communication with +.Xr smtpd 8 . +.El +.Sh SEE ALSO +.Xr smtpd 8 +.Sh HISTORY +The +.Nm +program first appeared in +.Ox 4.6 . diff --git a/foobar/portable/smtpd/smtpctl.c b/foobar/portable/smtpd/smtpctl.c new file mode 100644 index 00000000..7dba4224 --- /dev/null +++ b/foobar/portable/smtpd/smtpctl.c @@ -0,0 +1,1469 @@ +/* $OpenBSD: smtpctl.c,v 1.167 2020/02/24 16:16:07 millert Exp $ */ + +/* + * Copyright (c) 2013 Eric Faurot <eric@openbsd.org> + * Copyright (c) 2006 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org> + * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org> + * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org> + * Copyright (c) 2003 Henning Brauer <henning@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/un.h> +#include <sys/wait.h> +#include <sys/stat.h> + +#include <net/if.h> +/* #include <net/if_media.h> */ +/* #include <net/if_types.h> */ +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <err.h> +#include <errno.h> +#include <event.h> +#include <fts.h> +#include <grp.h> +#include <imsg.h> +#include <inttypes.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <time.h> +#include <unistd.h> +#if defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS) +#include <vis.h> +#else +#include "bsd-vis.h" +#endif +#include <limits.h> + +#include "smtpd.h" +#include "parser.h" +#include "log.h" + +#ifndef PATH_GZCAT +#define PATH_GZCAT "/usr/bin/gzcat" +#endif +#define PATH_CAT "/bin/cat" +#define PATH_QUEUE "/queue" +#ifndef PATH_ENCRYPT +#define PATH_ENCRYPT "/usr/bin/encrypt" +#endif + +#ifndef HAVE_DB_API +#define makemap(x, y, z) 1 +#endif + + +int srv_connect(void); +int srv_connected(void); + +void usage(void); +static void show_queue_envelope(struct envelope *, int); +static void getflag(uint *, int, char *, char *, size_t); +static void display(const char *); +static int str_to_trace(const char *); +static int str_to_profile(const char *); +static void show_offline_envelope(uint64_t); +static int is_gzip_fp(FILE *); +static int is_encrypted_fp(FILE *); +static int is_encrypted_buffer(const char *); +static int is_gzip_buffer(const char *); +static FILE *offline_file(void); +static void sendmail_compat(int, char **); + +extern int spfwalk(int, struct parameter *); + +extern char *__progname; +int sendmail; +struct smtpd *env; +struct imsgbuf *ibuf; +struct imsg imsg; +char *rdata; +size_t rlen; +time_t now; + +struct queue_backend queue_backend_null; +struct queue_backend queue_backend_proc; +struct queue_backend queue_backend_ram; + +__dead void +usage(void) +{ + if (sendmail) + fprintf(stderr, "usage: %s [-tv] [-f from] [-F name] to ...\n", + __progname); + else + fprintf(stderr, "usage: %s command [argument ...]\n", + __progname); + exit(1); +} + +void stat_increment(const char *k, size_t v) +{ +} + +void stat_decrement(const char *k, size_t v) +{ +} + +int +srv_connect(void) +{ + struct sockaddr_un s_un; + int ctl_sock, saved_errno; + + /* connect to smtpd control socket */ + if ((ctl_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + err(1, "socket"); + + memset(&s_un, 0, sizeof(s_un)); + s_un.sun_family = AF_UNIX; + (void)strlcpy(s_un.sun_path, SMTPD_SOCKET, sizeof(s_un.sun_path)); + if (connect(ctl_sock, (struct sockaddr *)&s_un, sizeof(s_un)) == -1) { + saved_errno = errno; + close(ctl_sock); + errno = saved_errno; + return (0); + } + + ibuf = xcalloc(1, sizeof(struct imsgbuf)); + imsg_init(ibuf, ctl_sock); + + return (1); +} + +int +srv_connected(void) +{ + return ibuf != NULL ? 1 : 0; +} + +FILE * +offline_file(void) +{ + char path[PATH_MAX]; + int fd; + FILE *fp; + + if (!bsnprintf(path, sizeof(path), "%s%s/%lld.XXXXXXXXXX", PATH_SPOOL, + PATH_OFFLINE, (long long int) time(NULL))) + err(EX_UNAVAILABLE, "snprintf"); + + if ((fd = mkstemp(path)) == -1 || (fp = fdopen(fd, "w+")) == NULL) { + if (fd != -1) + unlink(path); + err(EX_UNAVAILABLE, "cannot create temporary file %s", path); + } + + if (fchmod(fd, 0600) == -1) { + unlink(path); + err(EX_SOFTWARE, "fchmod"); + } + + return fp; +} + + +static void +srv_flush(void) +{ + if (imsg_flush(ibuf) == -1) + err(1, "write error"); +} + +static void +srv_send(int msg, const void *data, size_t len) +{ + if (ibuf == NULL && !srv_connect()) + errx(1, "smtpd doesn't seem to be running"); + imsg_compose(ibuf, msg, IMSG_VERSION, 0, -1, data, len); +} + +static void +srv_recv(int type) +{ + ssize_t n; + + srv_flush(); + + while (1) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + errx(1, "imsg_get error"); + if (n) { + if (imsg.hdr.type == IMSG_CTL_FAIL && + imsg.hdr.peerid != 0 && + imsg.hdr.peerid != IMSG_VERSION) + errx(1, "incompatible smtpctl and smtpd"); + if (type != -1 && type != (int)imsg.hdr.type) + errx(1, "bad message type"); + rdata = imsg.data; + rlen = imsg.hdr.len - sizeof(imsg.hdr); + break; + } + + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + errx(1, "imsg_read error"); + if (n == 0) + errx(1, "pipe closed"); + } +} + +static void +srv_read(void *dst, size_t sz) +{ + if (sz == 0) + return; + if (rlen < sz) + errx(1, "message too short"); + if (dst) + memmove(dst, rdata, sz); + rlen -= sz; + rdata += sz; +} + +static void +srv_get_int(int *i) +{ + srv_read(i, sizeof(*i)); +} + +static void +srv_get_time(time_t *t) +{ + srv_read(t, sizeof(*t)); +} + +static void +srv_get_evpid(uint64_t *evpid) +{ + srv_read(evpid, sizeof(*evpid)); +} + +static void +srv_get_string(const char **s) +{ + const char *end; + size_t len; + + if (rlen == 0) + errx(1, "message too short"); + + rlen -= 1; + if (*rdata++ == '\0') { + *s = NULL; + return; + } + + if (rlen == 0) + errx(1, "bogus string"); + + end = memchr(rdata, 0, rlen); + if (end == NULL) + errx(1, "unterminated string"); + + len = end + 1 - rdata; + + *s = rdata; + rlen -= len; + rdata += len; +} + +static void +srv_get_envelope(struct envelope *evp) +{ + uint64_t evpid; + const char *str; + + srv_get_evpid(&evpid); + srv_get_string(&str); + + envelope_load_buffer(evp, str, strlen(str)); + evp->id = evpid; +} + +static void +srv_end(void) +{ + if (rlen) + errx(1, "bogus data"); + imsg_free(&imsg); +} + +static int +srv_check_result(int verbose_) +{ + srv_recv(-1); + srv_end(); + + switch (imsg.hdr.type) { + case IMSG_CTL_OK: + if (verbose_) + printf("command succeeded\n"); + return (0); + case IMSG_CTL_FAIL: + if (verbose_) { + if (rlen) + printf("command failed: %s\n", rdata); + else + printf("command failed\n"); + } + return (1); + default: + errx(1, "wrong message in response: %u", imsg.hdr.type); + } + return (0); +} + +static int +srv_iter_messages(uint32_t *res) +{ + static uint32_t *msgids = NULL, from = 0; + static size_t n, curr; + static int done = 0; + + if (done) + return (0); + + if (msgids == NULL) { + srv_send(IMSG_CTL_LIST_MESSAGES, &from, sizeof(from)); + srv_recv(IMSG_CTL_LIST_MESSAGES); + if (rlen == 0) { + srv_end(); + done = 1; + return (0); + } + msgids = malloc(rlen); + n = rlen / sizeof(*msgids); + srv_read(msgids, rlen); + srv_end(); + + curr = 0; + from = msgids[n - 1] + 1; + if (from == 0) + done = 1; + } + + *res = msgids[curr++]; + if (curr == n) { + free(msgids); + msgids = NULL; + } + + return (1); +} + +static int +srv_iter_envelopes(uint32_t msgid, struct envelope *evp) +{ + static uint32_t currmsgid = 0; + static uint64_t from = 0; + static int done = 0, need_send = 1, found; + int flags; + time_t nexttry; + + if (currmsgid != msgid) { + if (currmsgid != 0 && !done) + errx(1, "must finish current iteration first"); + currmsgid = msgid; + from = msgid_to_evpid(msgid); + done = 0; + found = 0; + need_send = 1; + } + + if (done) + return (0); + + again: + if (need_send) { + found = 0; + srv_send(IMSG_CTL_LIST_ENVELOPES, &from, sizeof(from)); + } + need_send = 0; + + srv_recv(IMSG_CTL_LIST_ENVELOPES); + if (rlen == 0) { + srv_end(); + if (!found || evpid_to_msgid(from) != msgid) { + done = 1; + return (0); + } + need_send = 1; + goto again; + } + + srv_get_int(&flags); + srv_get_time(&nexttry); + srv_get_envelope(evp); + srv_end(); + + evp->flags |= flags; + evp->nexttry = nexttry; + + from = evp->id + 1; + found++; + return (1); +} + +static int +srv_iter_evpids(uint32_t msgid, uint64_t *evpid, int *offset) +{ + static uint64_t *evpids = NULL, *tmp; + static int n, tmpalloc, alloc = 0; + struct envelope evp; + + if (*offset == 0) { + n = 0; + while (srv_iter_envelopes(msgid, &evp)) { + if (n == alloc) { + tmpalloc = alloc ? (alloc * 2) : 128; + tmp = recallocarray(evpids, alloc, tmpalloc, + sizeof(*evpids)); + if (tmp == NULL) + err(1, "recallocarray"); + evpids = tmp; + alloc = tmpalloc; + } + evpids[n++] = evp.id; + } + } + + if (*offset >= n) + return (0); + *evpid = evpids[*offset]; + *offset += 1; + return (1); +} + +static void +srv_foreach_envelope(struct parameter *argv, int ctl, size_t *total, size_t *ok) +{ + uint32_t msgid; + uint64_t evpid; + int i; + + *total = 0; + *ok = 0; + + if (argv == NULL) { + while (srv_iter_messages(&msgid)) { + i = 0; + while (srv_iter_evpids(msgid, &evpid, &i)) { + *total += 1; + srv_send(ctl, &evpid, sizeof(evpid)); + if (srv_check_result(0) == 0) + *ok += 1; + } + } + } else if (argv->type == P_MSGID) { + i = 0; + while (srv_iter_evpids(argv->u.u_msgid, &evpid, &i)) { + srv_send(ctl, &evpid, sizeof(evpid)); + if (srv_check_result(0) == 0) + *ok += 1; + } + } else { + *total += 1; + srv_send(ctl, &argv->u.u_evpid, sizeof(evpid)); + if (srv_check_result(0) == 0) + *ok += 1; + } +} + +static void +srv_show_cmd(int cmd, const void *data, size_t len) +{ + int done = 0; + + srv_send(cmd, data, len); + + do { + srv_recv(cmd); + if (rlen) { + printf("%s\n", rdata); + srv_read(NULL, rlen); + } + else + done = 1; + srv_end(); + } while (!done); +} + +static void +droppriv(void) +{ + struct passwd *pw; + + if (geteuid()) + return; + + if ((pw = getpwnam(SMTPD_USER)) == NULL) + errx(1, "unknown user " SMTPD_USER); + + if ((setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))) + err(1, "cannot drop privileges"); +} + +static int +do_permission_denied(int argc, struct parameter *argv) +{ + errx(1, "need root privileges"); +} + +static int +do_log_brief(int argc, struct parameter *argv) +{ + int v = 0; + + srv_send(IMSG_CTL_VERBOSE, &v, sizeof(v)); + return srv_check_result(1); +} + +static int +do_log_verbose(int argc, struct parameter *argv) +{ + int v = TRACE_DEBUG; + + srv_send(IMSG_CTL_VERBOSE, &v, sizeof(v)); + return srv_check_result(1); +} + +static int +do_monitor(int argc, struct parameter *argv) +{ + struct stat_digest last, digest; + size_t count; + + memset(&last, 0, sizeof(last)); + count = 0; + + while (1) { + srv_send(IMSG_CTL_GET_DIGEST, NULL, 0); + srv_recv(IMSG_CTL_GET_DIGEST); + srv_read(&digest, sizeof(digest)); + srv_end(); + + if (count % 25 == 0) { + if (count != 0) + printf("\n"); + printf("--- client --- " + "-- envelope -- " + "---- relay/delivery --- " + "------- misc -------\n" + "curr conn disc " + "curr enq deq " + "ok tmpfail prmfail loop " + "expire remove bounce\n"); + } + printf("%4zu %4zu %4zu " + "%4zu %4zu %4zu " + "%4zu %4zu %4zu %4zu " + "%4zu %4zu %4zu\n", + digest.clt_connect - digest.clt_disconnect, + digest.clt_connect - last.clt_connect, + digest.clt_disconnect - last.clt_disconnect, + + digest.evp_enqueued - digest.evp_dequeued, + digest.evp_enqueued - last.evp_enqueued, + digest.evp_dequeued - last.evp_dequeued, + + digest.dlv_ok - last.dlv_ok, + digest.dlv_tempfail - last.dlv_tempfail, + digest.dlv_permfail - last.dlv_permfail, + digest.dlv_loop - last.dlv_loop, + + digest.evp_expired - last.evp_expired, + digest.evp_removed - last.evp_removed, + digest.evp_bounce - last.evp_bounce); + + last = digest; + count++; + sleep(1); + } + + return (0); +} + +static int +do_pause_envelope(int argc, struct parameter *argv) +{ + size_t total, ok; + + srv_foreach_envelope(argv, IMSG_CTL_PAUSE_EVP, &total, &ok); + printf("%zu envelope%s paused\n", ok, (ok > 1) ? "s" : ""); + + return (0); +} + +static int +do_pause_mda(int argc, struct parameter *argv) +{ + srv_send(IMSG_CTL_PAUSE_MDA, NULL, 0); + return srv_check_result(1); +} + +static int +do_pause_mta(int argc, struct parameter *argv) +{ + srv_send(IMSG_CTL_PAUSE_MTA, NULL, 0); + return srv_check_result(1); +} + +static int +do_pause_smtp(int argc, struct parameter *argv) +{ + srv_send(IMSG_CTL_PAUSE_SMTP, NULL, 0); + return srv_check_result(1); +} + +static int +do_profile(int argc, struct parameter *argv) +{ + int v; + + v = str_to_profile(argv[0].u.u_str); + + srv_send(IMSG_CTL_PROFILE_ENABLE, &v, sizeof(v)); + return srv_check_result(1); +} + +static int +do_remove(int argc, struct parameter *argv) +{ + size_t total, ok; + + srv_foreach_envelope(argv, IMSG_CTL_REMOVE, &total, &ok); + printf("%zu envelope%s removed\n", ok, (ok > 1) ? "s" : ""); + + return (0); +} + +static int +do_resume_envelope(int argc, struct parameter *argv) +{ + size_t total, ok; + + srv_foreach_envelope(argv, IMSG_CTL_RESUME_EVP, &total, &ok); + printf("%zu envelope%s resumed\n", ok, (ok > 1) ? "s" : ""); + + return (0); +} + +static int +do_resume_mda(int argc, struct parameter *argv) +{ + srv_send(IMSG_CTL_RESUME_MDA, NULL, 0); + return srv_check_result(1); +} + +static int +do_resume_mta(int argc, struct parameter *argv) +{ + srv_send(IMSG_CTL_RESUME_MTA, NULL, 0); + return srv_check_result(1); +} + +static int +do_resume_route(int argc, struct parameter *argv) +{ + uint64_t v; + + if (argc == 0) + v = 0; + else + v = argv[0].u.u_routeid; + + srv_send(IMSG_CTL_RESUME_ROUTE, &v, sizeof(v)); + return srv_check_result(1); +} + +static int +do_resume_smtp(int argc, struct parameter *argv) +{ + srv_send(IMSG_CTL_RESUME_SMTP, NULL, 0); + return srv_check_result(1); +} + +static int +do_schedule(int argc, struct parameter *argv) +{ + size_t total, ok; + + srv_foreach_envelope(argv, IMSG_CTL_SCHEDULE, &total, &ok); + printf("%zu envelope%s scheduled\n", ok, (ok > 1) ? "s" : ""); + + return (0); +} + +static int +do_show_envelope(int argc, struct parameter *argv) +{ + char buf[PATH_MAX]; + + if (!bsnprintf(buf, sizeof(buf), "%s%s/%02x/%08x/%016" PRIx64, + PATH_SPOOL, + PATH_QUEUE, + (evpid_to_msgid(argv[0].u.u_evpid) & 0xff000000) >> 24, + evpid_to_msgid(argv[0].u.u_evpid), + argv[0].u.u_evpid)) + errx(1, "unable to retrieve envelope"); + + display(buf); + + return (0); +} + +static int +do_show_hoststats(int argc, struct parameter *argv) +{ + srv_show_cmd(IMSG_CTL_MTA_SHOW_HOSTSTATS, NULL, 0); + + return (0); +} + +static int +do_show_message(int argc, struct parameter *argv) +{ + char buf[PATH_MAX]; + uint32_t msgid; + + if (argv[0].type == P_EVPID) + msgid = evpid_to_msgid(argv[0].u.u_evpid); + else + msgid = argv[0].u.u_msgid; + + if (!bsnprintf(buf, sizeof(buf), "%s%s/%02x/%08x/message", + PATH_SPOOL, + PATH_QUEUE, + (msgid & 0xff000000) >> 24, + msgid)) + errx(1, "unable to retrieve message"); + + display(buf); + + return (0); +} + +static int +do_show_queue(int argc, struct parameter *argv) +{ + struct envelope evp; + uint32_t msgid; + FTS *fts; + FTSENT *ftse; + char *qpath[] = {"/queue", NULL}; + char *tmp; + uint64_t evpid; + + now = time(NULL); + + if (!srv_connect()) { + log_init(1, LOG_MAIL); + queue_init("fs", 0); + if (chroot(PATH_SPOOL) == -1 || chdir("/") == -1) + err(1, "%s", PATH_SPOOL); + fts = fts_open(qpath, FTS_PHYSICAL|FTS_NOCHDIR, NULL); + if (fts == NULL) + err(1, "%s/queue", PATH_SPOOL); + + while ((ftse = fts_read(fts)) != NULL) { + switch (ftse->fts_info) { + case FTS_DP: + case FTS_DNR: + break; + case FTS_F: + tmp = NULL; + evpid = strtoull(ftse->fts_name, &tmp, 16); + if (tmp && *tmp != '\0') + break; + show_offline_envelope(evpid); + } + } + + fts_close(fts); + return (0); + } + + if (argc == 0) { + msgid = 0; + while (srv_iter_messages(&msgid)) + while (srv_iter_envelopes(msgid, &evp)) + show_queue_envelope(&evp, 1); + } else if (argv[0].type == P_MSGID) { + while (srv_iter_envelopes(argv[0].u.u_msgid, &evp)) + show_queue_envelope(&evp, 1); + } + + return (0); +} + +static int +do_show_hosts(int argc, struct parameter *argv) +{ + srv_show_cmd(IMSG_CTL_MTA_SHOW_HOSTS, NULL, 0); + + return (0); +} + +static int +do_show_relays(int argc, struct parameter *argv) +{ + srv_show_cmd(IMSG_CTL_MTA_SHOW_RELAYS, NULL, 0); + + return (0); +} + +static int +do_show_routes(int argc, struct parameter *argv) +{ + srv_show_cmd(IMSG_CTL_MTA_SHOW_ROUTES, NULL, 0); + + return (0); +} + +static int +do_show_stats(int argc, struct parameter *argv) +{ + struct stat_kv kv; + time_t duration; + + memset(&kv, 0, sizeof kv); + + while (1) { + srv_send(IMSG_CTL_GET_STATS, &kv, sizeof kv); + srv_recv(IMSG_CTL_GET_STATS); + srv_read(&kv, sizeof(kv)); + srv_end(); + + if (kv.iter == NULL) + break; + + if (strcmp(kv.key, "uptime") == 0) { + duration = time(NULL) - kv.val.u.counter; + printf("uptime=%lld\n", (long long)duration); + printf("uptime.human=%s\n", + duration_to_text(duration)); + } + else { + switch (kv.val.type) { + case STAT_COUNTER: + printf("%s=%zd\n", + kv.key, kv.val.u.counter); + break; + case STAT_TIMESTAMP: + printf("%s=%" PRId64 "\n", + kv.key, (int64_t)kv.val.u.timestamp); + break; + case STAT_TIMEVAL: + printf("%s=%lld.%lld\n", + kv.key, (long long)kv.val.u.tv.tv_sec, + (long long)kv.val.u.tv.tv_usec); + break; + case STAT_TIMESPEC: + printf("%s=%lld.%06ld\n", + kv.key, + (long long)kv.val.u.ts.tv_sec * 1000000 + + kv.val.u.ts.tv_nsec / 1000000, + kv.val.u.ts.tv_nsec % 1000000); + break; + } + } + } + + return (0); +} + +static int +do_show_status(int argc, struct parameter *argv) +{ + uint32_t sc_flags; + + srv_send(IMSG_CTL_SHOW_STATUS, NULL, 0); + srv_recv(IMSG_CTL_SHOW_STATUS); + srv_read(&sc_flags, sizeof(sc_flags)); + srv_end(); + printf("MDA %s\n", + (sc_flags & SMTPD_MDA_PAUSED) ? "paused" : "running"); + printf("MTA %s\n", + (sc_flags & SMTPD_MTA_PAUSED) ? "paused" : "running"); + printf("SMTP %s\n", + (sc_flags & SMTPD_SMTP_PAUSED) ? "paused" : "running"); + return (0); +} + +static int +do_trace(int argc, struct parameter *argv) +{ + int v; + + v = str_to_trace(argv[0].u.u_str); + + srv_send(IMSG_CTL_TRACE_ENABLE, &v, sizeof(v)); + return srv_check_result(1); +} + +static int +do_unprofile(int argc, struct parameter *argv) +{ + int v; + + v = str_to_profile(argv[0].u.u_str); + + srv_send(IMSG_CTL_PROFILE_DISABLE, &v, sizeof(v)); + return srv_check_result(1); +} + +static int +do_untrace(int argc, struct parameter *argv) +{ + int v; + + v = str_to_trace(argv[0].u.u_str); + + srv_send(IMSG_CTL_TRACE_DISABLE, &v, sizeof(v)); + return srv_check_result(1); +} + +static int +do_update_table(int argc, struct parameter *argv) +{ + const char *name = argv[0].u.u_str; + + srv_send(IMSG_CTL_UPDATE_TABLE, name, strlen(name) + 1); + return srv_check_result(1); +} + +static int +do_encrypt(int argc, struct parameter *argv) +{ + const char *p = NULL; + + droppriv(); + + if (argv) + p = argv[0].u.u_str; + execl(PATH_ENCRYPT, "encrypt", "--", p, (char *)NULL); + errx(1, "execl"); +} + +static int +do_block_mta(int argc, struct parameter *argv) +{ + struct ibuf *m; + + if (ibuf == NULL && !srv_connect()) + errx(1, "smtpd doesn't seem to be running"); + m = imsg_create(ibuf, IMSG_CTL_MTA_BLOCK, IMSG_VERSION, 0, + sizeof(argv[0].u.u_ss) + strlen(argv[1].u.u_str) + 1); + if (imsg_add(m, &argv[0].u.u_ss, sizeof(argv[0].u.u_ss)) == -1) + errx(1, "imsg_add"); + if (imsg_add(m, argv[1].u.u_str, strlen(argv[1].u.u_str) + 1) == -1) + errx(1, "imsg_add"); + imsg_close(ibuf, m); + + return srv_check_result(1); +} + +static int +do_unblock_mta(int argc, struct parameter *argv) +{ + struct ibuf *m; + + if (ibuf == NULL && !srv_connect()) + errx(1, "smtpd doesn't seem to be running"); + + m = imsg_create(ibuf, IMSG_CTL_MTA_UNBLOCK, IMSG_VERSION, 0, + sizeof(argv[0].u.u_ss) + strlen(argv[1].u.u_str) + 1); + if (imsg_add(m, &argv[0].u.u_ss, sizeof(argv[0].u.u_ss)) == -1) + errx(1, "imsg_add"); + if (imsg_add(m, argv[1].u.u_str, strlen(argv[1].u.u_str) + 1) == -1) + errx(1, "imsg_add"); + imsg_close(ibuf, m); + + return srv_check_result(1); +} + +static int +do_show_mta_block(int argc, struct parameter *argv) +{ + srv_show_cmd(IMSG_CTL_MTA_SHOW_BLOCK, NULL, 0); + + return (0); +} + +static int +do_discover(int argc, struct parameter *argv) +{ + uint64_t evpid; + uint32_t msgid; + size_t n_evp; + + if (ibuf == NULL && !srv_connect()) + errx(1, "smtpd doesn't seem to be running"); + + if (argv[0].type == P_EVPID) { + evpid = argv[0].u.u_evpid; + srv_send(IMSG_CTL_DISCOVER_EVPID, &evpid, sizeof evpid); + srv_recv(IMSG_CTL_DISCOVER_EVPID); + } else { + msgid = argv[0].u.u_msgid; + srv_send(IMSG_CTL_DISCOVER_MSGID, &msgid, sizeof msgid); + srv_recv(IMSG_CTL_DISCOVER_MSGID); + } + + if (rlen == 0) { + srv_end(); + return (0); + } else { + srv_read(&n_evp, sizeof n_evp); + srv_end(); + } + + printf("%zu envelope%s discovered\n", n_evp, (n_evp != 1) ? "s" : ""); + return (0); +} + +static int +do_spf_walk(int argc, struct parameter *argv) +{ + droppriv(); + + return spfwalk(argc, argv); +} + +#define cmd_install_priv(s, f) \ + cmd_install((s), privileged ? (f) : do_permission_denied) + +int +main(int argc, char **argv) +{ + gid_t gid; + struct group *gr; + int privileged; + char *argv_mailq[] = { "show", "queue", NULL }; + +#ifndef HAVE___PROGNAME + __progname = ssh_get_progname(argv[0]); +#endif + + /* check that smtpctl was installed setgid */ + if ((gr = getgrnam(SMTPD_QUEUE_GROUP)) == NULL) + errx(1, "unknown group %s", SMTPD_QUEUE_GROUP); + else if (gr->gr_gid != getegid()) + errx(1, "this program must be setgid %s", SMTPD_QUEUE_GROUP); + + sendmail_compat(argc, argv); + privileged = geteuid() == 0; + + gid = getgid(); + if (setresgid(gid, gid, gid) == -1) + err(1, "setresgid"); + + /* Privileged commands */ + cmd_install_priv("discover <evpid>", do_discover); + cmd_install_priv("discover <msgid>", do_discover); + cmd_install_priv("pause mta from <addr> for <str>", do_block_mta); + cmd_install_priv("resume mta from <addr> for <str>", do_unblock_mta); + cmd_install_priv("show mta paused", do_show_mta_block); + cmd_install_priv("log brief", do_log_brief); + cmd_install_priv("log verbose", do_log_verbose); + cmd_install_priv("monitor", do_monitor); + cmd_install_priv("pause envelope <evpid>", do_pause_envelope); + cmd_install_priv("pause envelope <msgid>", do_pause_envelope); + cmd_install_priv("pause envelope all", do_pause_envelope); + cmd_install_priv("pause mda", do_pause_mda); + cmd_install_priv("pause mta", do_pause_mta); + cmd_install_priv("pause smtp", do_pause_smtp); + cmd_install_priv("profile <str>", do_profile); + cmd_install_priv("remove <evpid>", do_remove); + cmd_install_priv("remove <msgid>", do_remove); + cmd_install_priv("remove all", do_remove); + cmd_install_priv("resume envelope <evpid>", do_resume_envelope); + cmd_install_priv("resume envelope <msgid>", do_resume_envelope); + cmd_install_priv("resume envelope all", do_resume_envelope); + cmd_install_priv("resume mda", do_resume_mda); + cmd_install_priv("resume mta", do_resume_mta); + cmd_install_priv("resume route <routeid>", do_resume_route); + cmd_install_priv("resume smtp", do_resume_smtp); + cmd_install_priv("schedule <msgid>", do_schedule); + cmd_install_priv("schedule <evpid>", do_schedule); + cmd_install_priv("schedule all", do_schedule); + cmd_install_priv("show envelope <evpid>", do_show_envelope); + cmd_install_priv("show hoststats", do_show_hoststats); + cmd_install_priv("show message <msgid>", do_show_message); + cmd_install_priv("show message <evpid>", do_show_message); + cmd_install_priv("show queue", do_show_queue); + cmd_install_priv("show queue <msgid>", do_show_queue); + cmd_install_priv("show hosts", do_show_hosts); + cmd_install_priv("show relays", do_show_relays); + cmd_install_priv("show routes", do_show_routes); + cmd_install_priv("show stats", do_show_stats); + cmd_install_priv("show status", do_show_status); + cmd_install_priv("trace <str>", do_trace); + cmd_install_priv("unprofile <str>", do_unprofile); + cmd_install_priv("untrace <str>", do_untrace); + cmd_install_priv("update table <str>", do_update_table); + + /* Unprivileged commands */ + cmd_install("encrypt", do_encrypt); + cmd_install("encrypt <str>", do_encrypt); + cmd_install("spf walk", do_spf_walk); + + if (strcmp(__progname, "mailq") == 0) + return cmd_run(2, argv_mailq); + if (strcmp(__progname, "smtpctl") == 0) + return cmd_run(argc - 1, argv + 1); + + errx(1, "unsupported mode"); + return (0); +} + +void +sendmail_compat(int argc, char **argv) +{ + FILE *offlinefp = NULL; + gid_t gid; + int i, r; + + if (strcmp(__progname, "sendmail") == 0 || + strcmp(__progname, "send-mail") == 0) { + /* + * determine whether we are called with flags + * that should invoke makemap/newaliases. + */ + for (i = 1; i < argc; i++) + if (strncmp(argv[i], "-bi", 3) == 0) + exit(makemap(P_SENDMAIL, argc, argv)); + + if (!srv_connect()) + offlinefp = offline_file(); + + gid = getgid(); + if (setresgid(gid, gid, gid) == -1) + err(1, "setresgid"); + +#if HAVE_PLEDGE + /* we'll reduce further down the road */ + if (pledge("stdio rpath wpath cpath tmppath flock " + "dns getpw recvfd", NULL) == -1) + err(1, "pledge"); +#endif + + sendmail = 1; + exit(enqueue(argc, argv, offlinefp)); + } else if (strcmp(__progname, "makemap") == 0) + exit(makemap(P_MAKEMAP, argc, argv)); + else if (strcmp(__progname, "newaliases") == 0) { + r = makemap(P_NEWALIASES, argc, argv); + /* + * if server is available, notify of table update. + * only makes sense for static tables AND if server is up. + */ + if (srv_connect()) { + srv_send(IMSG_CTL_UPDATE_TABLE, "aliases", strlen("aliases") + 1); + srv_check_result(0); + } + exit(r); + } +} + +static void +show_queue_envelope(struct envelope *e, int online) +{ + const char *src = "?", *agent = "?"; + char status[128], runstate[128], errline[LINE_MAX]; + + status[0] = '\0'; + + getflag(&e->flags, EF_BOUNCE, "bounce", status, sizeof(status)); + getflag(&e->flags, EF_AUTHENTICATED, "auth", status, sizeof(status)); + getflag(&e->flags, EF_INTERNAL, "internal", status, sizeof(status)); + getflag(&e->flags, EF_SUSPEND, "suspend", status, sizeof(status)); + getflag(&e->flags, EF_HOLD, "hold", status, sizeof(status)); + + if (online) { + if (e->flags & EF_PENDING) + (void)snprintf(runstate, sizeof runstate, "pending|%zd", + (ssize_t)(e->nexttry - now)); + else if (e->flags & EF_INFLIGHT) + (void)snprintf(runstate, sizeof runstate, + "inflight|%zd", (ssize_t)(now - e->lasttry)); + else + (void)snprintf(runstate, sizeof runstate, "invalid|"); + e->flags &= ~(EF_PENDING|EF_INFLIGHT); + } + else + (void)strlcpy(runstate, "offline|", sizeof runstate); + + if (e->flags) + errx(1, "%016" PRIx64 ": unexpected flags 0x%04x", e->id, + e->flags); + + if (status[0]) + status[strlen(status) - 1] = '\0'; + + if (e->type == D_MDA) + agent = "mda"; + else if (e->type == D_MTA) + agent = "mta"; + else if (e->type == D_BOUNCE) + agent = "bounce"; + + if (e->ss.ss_family == AF_LOCAL) + src = "local"; + else if (e->ss.ss_family == AF_INET) + src = "inet4"; + else if (e->ss.ss_family == AF_INET6) + src = "inet6"; + + strnvis(errline, e->errorline, sizeof(errline), 0); + + printf("%016"PRIx64 + "|%s|%s|%s|%s@%s|%s@%s|%s@%s" + "|%zu|%zu|%zu|%zu|%s|%s\n", + + e->id, + + src, + agent, + status, + e->sender.user, e->sender.domain, + e->rcpt.user, e->rcpt.domain, + e->dest.user, e->dest.domain, + + (size_t) e->creation, + (size_t) (e->creation + e->ttl), + (size_t) e->lasttry, + (size_t) e->retry, + runstate, + errline); +} + +static void +getflag(uint *bitmap, int bit, char *bitstr, char *buf, size_t len) +{ + if (*bitmap & bit) { + *bitmap &= ~bit; + (void)strlcat(buf, bitstr, len); + (void)strlcat(buf, ",", len); + } +} + +static void +show_offline_envelope(uint64_t evpid) +{ + FILE *fp = NULL; + char pathname[PATH_MAX]; + size_t plen; + char *p; + size_t buflen; + char buffer[sizeof(struct envelope)]; + + struct envelope evp; + + if (!bsnprintf(pathname, sizeof pathname, + "/queue/%02x/%08x/%016"PRIx64, + (evpid_to_msgid(evpid) & 0xff000000) >> 24, + evpid_to_msgid(evpid), evpid)) + goto end; + fp = fopen(pathname, "r"); + if (fp == NULL) + goto end; + + buflen = fread(buffer, 1, sizeof (buffer) - 1, fp); + p = buffer; + plen = buflen; + buffer[buflen] = '\0'; + + if (is_encrypted_buffer(p)) { + warnx("offline encrypted queue is not supported yet"); + goto end; + } + + if (is_gzip_buffer(p)) { + warnx("offline compressed queue is not supported yet"); + goto end; + } + + if (!envelope_load_buffer(&evp, p, plen)) + goto end; + evp.id = evpid; + show_queue_envelope(&evp, 0); + +end: + if (fp) + fclose(fp); +} + +static void +display(const char *s) +{ + FILE *fp; + char *key; + int gzipped; + char *gzcat_argv0 = strrchr(PATH_GZCAT, '/') + 1; + + if ((fp = fopen(s, "r")) == NULL) + err(1, "fopen"); + + if (is_encrypted_fp(fp)) { + int i; + FILE *ofp = NULL; + + if ((ofp = tmpfile()) == NULL) + err(1, "tmpfile"); + + for (i = 0; i < 3; i++) { + key = getpass("key> "); + if (crypto_setup(key, strlen(key))) + break; + } + if (i == 3) + errx(1, "crypto-setup: invalid key"); + + if (!crypto_decrypt_file(fp, ofp)) { + printf("object is encrypted: %s\n", key); + exit(1); + } + + fclose(fp); + fp = ofp; + fseek(fp, 0, SEEK_SET); + } + gzipped = is_gzip_fp(fp); + + lseek(fileno(fp), 0, SEEK_SET); + (void)dup2(fileno(fp), STDIN_FILENO); + if (gzipped) + execl(PATH_GZCAT, gzcat_argv0, (char *)NULL); + else + execl(PATH_CAT, "cat", (char *)NULL); + err(1, "execl"); +} + +static int +str_to_trace(const char *str) +{ + if (!strcmp(str, "imsg")) + return TRACE_IMSG; + if (!strcmp(str, "io")) + return TRACE_IO; + if (!strcmp(str, "smtp")) + return TRACE_SMTP; + if (!strcmp(str, "filters")) + return TRACE_FILTERS; + if (!strcmp(str, "mta")) + return TRACE_MTA; + if (!strcmp(str, "bounce")) + return TRACE_BOUNCE; + if (!strcmp(str, "scheduler")) + return TRACE_SCHEDULER; + if (!strcmp(str, "lookup")) + return TRACE_LOOKUP; + if (!strcmp(str, "stat")) + return TRACE_STAT; + if (!strcmp(str, "rules")) + return TRACE_RULES; + if (!strcmp(str, "mproc")) + return TRACE_MPROC; + if (!strcmp(str, "expand")) + return TRACE_EXPAND; + if (!strcmp(str, "all")) + return ~TRACE_DEBUG; + errx(1, "invalid trace keyword: %s", str); + return (0); +} + +static int +str_to_profile(const char *str) +{ + if (!strcmp(str, "imsg")) + return PROFILE_IMSG; + if (!strcmp(str, "queue")) + return PROFILE_QUEUE; + errx(1, "invalid profile keyword: %s", str); + return (0); +} + +static int +is_gzip_buffer(const char *buffer) +{ + uint16_t magic; + + memcpy(&magic, buffer, sizeof magic); +#define GZIP_MAGIC 0x8b1f + return (magic == GZIP_MAGIC); +} + +static int +is_gzip_fp(FILE *fp) +{ + uint8_t magic[2]; + int ret = 0; + + if (fread(&magic, 1, sizeof magic, fp) != sizeof magic) + goto end; + + ret = is_gzip_buffer((const char *)&magic); +end: + fseek(fp, 0, SEEK_SET); + return ret; +} + + +/* XXX */ +/* + * queue supports transparent encryption. + * encrypted chunks are prefixed with an API version byte + * which we ensure is unambiguous with gzipped / plain + * objects. + */ + +static int +is_encrypted_buffer(const char *buffer) +{ + uint8_t magic; + + magic = *buffer; +#define ENCRYPTION_MAGIC 0x1 + return (magic == ENCRYPTION_MAGIC); +} + +static int +is_encrypted_fp(FILE *fp) +{ + uint8_t magic; + int ret = 0; + + if (fread(&magic, 1, sizeof magic, fp) != sizeof magic) + goto end; + + ret = is_encrypted_buffer((const char *)&magic); +end: + fseek(fp, 0, SEEK_SET); + return ret; +} diff --git a/foobar/portable/smtpd/smtpctl/Makefile b/foobar/portable/smtpd/smtpctl/Makefile new file mode 100644 index 00000000..ef8148be --- /dev/null +++ b/foobar/portable/smtpd/smtpctl/Makefile @@ -0,0 +1,56 @@ +# $OpenBSD: Makefile,v 1.47 2018/07/03 01:34:43 mortimer Exp $ + +.PATH: ${.CURDIR}/.. + +PROG= smtpctl +BINOWN= root +BINGRP= _smtpq + +BINMODE?=2555 + +BINDIR= /usr/sbin +MAN= smtpctl.8 aliases.5 forward.5 makemap.8 newaliases.8 + +CFLAGS+= -fstack-protector-all +CFLAGS+= -I${.CURDIR}/.. +CFLAGS+= -Wall -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wmissing-declarations +CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual +CFLAGS+= -Wsign-compare +CFLAGS+= -Werror-implicit-function-declaration +CFLAGS+= -DNO_IO +CFLAGS+= -DCONFIG_MINIMUM +YFLAGS= + +SRCS= enqueue.c +SRCS+= parser.c +SRCS+= log.c +SRCS+= envelope.c +SRCS+= crypto.c +SRCS+= queue_backend.c +SRCS+= queue_fs.c +SRCS+= smtpctl.c +SRCS+= util.c +SRCS+= compress_backend.c +SRCS+= compress_gzip.c +SRCS+= to.c +SRCS+= expand.c +SRCS+= tree.c +SRCS+= config.c +SRCS+= dict.c +SRCS+= aliases.c +SRCS+= limit.c +SRCS+= makemap.c +SRCS+= parse.y +SRCS+= mailaddr.c +SRCS+= table.c +SRCS+= table_static.c +SRCS+= table_db.c +SRCS+= table_getpwnam.c +SRCS+= table_proc.c +SRCS+= unpack_dns.c +SRCS+= spfwalk.c + +LDADD+= -levent -lutil -lz -lcrypto +DPADD+= ${LIBEVENT} ${LIBUTIL} ${LIBZ} ${LIBCRYPTO} +.include <bsd.prog.mk> diff --git a/foobar/portable/smtpd/smtpd-api.h b/foobar/portable/smtpd/smtpd-api.h new file mode 100644 index 00000000..f83edd05 --- /dev/null +++ b/foobar/portable/smtpd/smtpd-api.h @@ -0,0 +1,290 @@ +/* $OpenBSD: smtpd-api.h,v 1.36 2018/12/23 16:06:24 gilles Exp $ */ + +/* + * Copyright (c) 2013 Eric Faurot <eric@openbsd.org> + * Copyright (c) 2011 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _SMTPD_API_H_ +#define _SMTPD_API_H_ + +#include "dict.h" +#include "tree.h" + +struct mailaddr { + char user[SMTPD_MAXLOCALPARTSIZE]; + char domain[SMTPD_MAXDOMAINPARTSIZE]; +}; + +#define PROC_QUEUE_API_VERSION 2 + +enum { + PROC_QUEUE_OK, + PROC_QUEUE_FAIL, + PROC_QUEUE_INIT, + PROC_QUEUE_CLOSE, + PROC_QUEUE_MESSAGE_CREATE, + PROC_QUEUE_MESSAGE_DELETE, + PROC_QUEUE_MESSAGE_COMMIT, + PROC_QUEUE_MESSAGE_FD_R, + PROC_QUEUE_ENVELOPE_CREATE, + PROC_QUEUE_ENVELOPE_DELETE, + PROC_QUEUE_ENVELOPE_LOAD, + PROC_QUEUE_ENVELOPE_UPDATE, + PROC_QUEUE_ENVELOPE_WALK, +}; + +#define PROC_SCHEDULER_API_VERSION 2 + +struct scheduler_info; + +enum { + PROC_SCHEDULER_OK, + PROC_SCHEDULER_FAIL, + PROC_SCHEDULER_INIT, + PROC_SCHEDULER_INSERT, + PROC_SCHEDULER_COMMIT, + PROC_SCHEDULER_ROLLBACK, + PROC_SCHEDULER_UPDATE, + PROC_SCHEDULER_DELETE, + PROC_SCHEDULER_HOLD, + PROC_SCHEDULER_RELEASE, + PROC_SCHEDULER_BATCH, + PROC_SCHEDULER_MESSAGES, + PROC_SCHEDULER_ENVELOPES, + PROC_SCHEDULER_SCHEDULE, + PROC_SCHEDULER_REMOVE, + PROC_SCHEDULER_SUSPEND, + PROC_SCHEDULER_RESUME, +}; + +enum envelope_flags { + EF_AUTHENTICATED = 0x01, + EF_BOUNCE = 0x02, + EF_INTERNAL = 0x04, /* Internal expansion forward */ + + /* runstate, not saved on disk */ + + EF_PENDING = 0x10, + EF_INFLIGHT = 0x20, + EF_SUSPEND = 0x40, + EF_HOLD = 0x80, +}; + +struct evpstate { + uint64_t evpid; + uint16_t flags; + uint16_t retry; + time_t time; +}; + +enum delivery_type { + D_MDA, + D_MTA, + D_BOUNCE, +}; + +struct scheduler_info { + uint64_t evpid; + enum delivery_type type; + uint16_t retry; + time_t creation; + time_t ttl; + time_t lasttry; + time_t lastbounce; + time_t nexttry; +}; + +#define SCHED_REMOVE 0x01 +#define SCHED_EXPIRE 0x02 +#define SCHED_UPDATE 0x04 +#define SCHED_BOUNCE 0x08 +#define SCHED_MDA 0x10 +#define SCHED_MTA 0x20 + +#define PROC_TABLE_API_VERSION 2 + +struct table_open_params { + uint32_t version; + char name[LINE_MAX]; +}; + +enum table_service { + K_NONE = 0x000, + K_ALIAS = 0x001, /* returns struct expand */ + K_DOMAIN = 0x002, /* returns struct destination */ + K_CREDENTIALS = 0x004, /* returns struct credentials */ + K_NETADDR = 0x008, /* returns struct netaddr */ + K_USERINFO = 0x010, /* returns struct userinfo */ + K_SOURCE = 0x020, /* returns struct source */ + K_MAILADDR = 0x040, /* returns struct mailaddr */ + K_ADDRNAME = 0x080, /* returns struct addrname */ + K_MAILADDRMAP = 0x100, /* returns struct maddrmap */ + K_RELAYHOST = 0x200, /* returns struct relayhost */ + K_STRING = 0x400, + K_REGEX = 0x800, +}; +#define K_ANY 0xfff + +enum { + PROC_TABLE_OK, + PROC_TABLE_FAIL, + PROC_TABLE_OPEN, + PROC_TABLE_CLOSE, + PROC_TABLE_UPDATE, + PROC_TABLE_CHECK, + PROC_TABLE_LOOKUP, + PROC_TABLE_FETCH, +}; + +enum enhanced_status_code { + /* 0.0 */ + ESC_OTHER_STATUS = 00, + + /* 1.x */ + ESC_OTHER_ADDRESS_STATUS = 10, + ESC_BAD_DESTINATION_MAILBOX_ADDRESS = 11, + ESC_BAD_DESTINATION_SYSTEM_ADDRESS = 12, + ESC_BAD_DESTINATION_MAILBOX_ADDRESS_SYNTAX = 13, + ESC_DESTINATION_MAILBOX_ADDRESS_AMBIGUOUS = 14, + ESC_DESTINATION_ADDRESS_VALID = 15, + ESC_DESTINATION_MAILBOX_HAS_MOVED = 16, + ESC_BAD_SENDER_MAILBOX_ADDRESS_SYNTAX = 17, + ESC_BAD_SENDER_SYSTEM_ADDRESS = 18, + + /* 2.x */ + ESC_OTHER_MAILBOX_STATUS = 20, + ESC_MAILBOX_DISABLED = 21, + ESC_MAILBOX_FULL = 22, + ESC_MESSAGE_LENGTH_TOO_LARGE = 23, + ESC_MAILING_LIST_EXPANSION_PROBLEM = 24, + + /* 3.x */ + ESC_OTHER_MAIL_SYSTEM_STATUS = 30, + ESC_MAIL_SYSTEM_FULL = 31, + ESC_SYSTEM_NOT_ACCEPTING_MESSAGES = 32, + ESC_SYSTEM_NOT_CAPABLE_OF_SELECTED_FEATURES = 33, + ESC_MESSAGE_TOO_BIG_FOR_SYSTEM = 34, + ESC_SYSTEM_INCORRECTLY_CONFIGURED = 35, + + /* 4.x */ + ESC_OTHER_NETWORK_ROUTING_STATUS = 40, + ESC_NO_ANSWER_FROM_HOST = 41, + ESC_BAD_CONNECTION = 42, + ESC_DIRECTORY_SERVER_FAILURE = 43, + ESC_UNABLE_TO_ROUTE = 44, + ESC_MAIL_SYSTEM_CONGESTION = 45, + ESC_ROUTING_LOOP_DETECTED = 46, + ESC_DELIVERY_TIME_EXPIRED = 47, + + /* 5.x */ + ESC_INVALID_RECIPIENT = 50, + ESC_INVALID_COMMAND = 51, + ESC_SYNTAX_ERROR = 52, + ESC_TOO_MANY_RECIPIENTS = 53, + ESC_INVALID_COMMAND_ARGUMENTS = 54, + ESC_WRONG_PROTOCOL_VERSION = 55, + + /* 6.x */ + ESC_OTHER_MEDIA_ERROR = 60, + ESC_MEDIA_NOT_SUPPORTED = 61, + ESC_CONVERSION_REQUIRED_AND_PROHIBITED = 62, + ESC_CONVERSION_REQUIRED_BUT_NOT_SUPPORTED = 63, + ESC_CONVERSION_WITH_LOSS_PERFORMED = 64, + ESC_CONVERSION_FAILED = 65, + + /* 7.x */ + ESC_OTHER_SECURITY_STATUS = 70, + ESC_DELIVERY_NOT_AUTHORIZED_MESSAGE_REFUSED = 71, + ESC_MAILING_LIST_EXPANSION_PROHIBITED = 72, + ESC_SECURITY_CONVERSION_REQUIRED_NOT_POSSIBLE = 73, + ESC_SECURITY_FEATURES_NOT_SUPPORTED = 74, + ESC_CRYPTOGRAPHIC_FAILURE = 75, + ESC_CRYPTOGRAPHIC_ALGORITHM_NOT_SUPPORTED = 76, + ESC_MESSAGE_INTEGRITY_FAILURE = 77, +}; + +enum enhanced_status_class { + ESC_STATUS_OK = 2, + ESC_STATUS_TEMPFAIL = 4, + ESC_STATUS_PERMFAIL = 5, +}; + +static inline uint32_t +evpid_to_msgid(uint64_t evpid) +{ + return (evpid >> 32); +} + +static inline uint64_t +msgid_to_evpid(uint32_t msgid) +{ + return ((uint64_t)msgid << 32); +} + + +/* esc.c */ +const char *esc_code(enum enhanced_status_class, enum enhanced_status_code); +const char *esc_description(enum enhanced_status_code); + + +/* queue */ +void queue_api_on_close(int(*)(void)); +void queue_api_on_message_create(int(*)(uint32_t *)); +void queue_api_on_message_commit(int(*)(uint32_t, const char*)); +void queue_api_on_message_delete(int(*)(uint32_t)); +void queue_api_on_message_fd_r(int(*)(uint32_t)); +void queue_api_on_envelope_create(int(*)(uint32_t, const char *, size_t, uint64_t *)); +void queue_api_on_envelope_delete(int(*)(uint64_t)); +void queue_api_on_envelope_update(int(*)(uint64_t, const char *, size_t)); +void queue_api_on_envelope_load(int(*)(uint64_t, char *, size_t)); +void queue_api_on_envelope_walk(int(*)(uint64_t *, char *, size_t)); +void queue_api_on_message_walk(int(*)(uint64_t *, char *, size_t, + uint32_t, int *, void **)); +void queue_api_no_chroot(void); +void queue_api_set_chroot(const char *); +void queue_api_set_user(const char *); +int queue_api_dispatch(void); + +/* scheduler */ +void scheduler_api_on_init(int(*)(void)); +void scheduler_api_on_insert(int(*)(struct scheduler_info *)); +void scheduler_api_on_commit(size_t(*)(uint32_t)); +void scheduler_api_on_rollback(size_t(*)(uint32_t)); +void scheduler_api_on_update(int(*)(struct scheduler_info *)); +void scheduler_api_on_delete(int(*)(uint64_t)); +void scheduler_api_on_hold(int(*)(uint64_t, uint64_t)); +void scheduler_api_on_release(int(*)(int, uint64_t, int)); +void scheduler_api_on_batch(int(*)(int, int *, size_t *, uint64_t *, int *)); +void scheduler_api_on_messages(size_t(*)(uint32_t, uint32_t *, size_t)); +void scheduler_api_on_envelopes(size_t(*)(uint64_t, struct evpstate *, size_t)); +void scheduler_api_on_schedule(int(*)(uint64_t)); +void scheduler_api_on_remove(int(*)(uint64_t)); +void scheduler_api_on_suspend(int(*)(uint64_t)); +void scheduler_api_on_resume(int(*)(uint64_t)); +void scheduler_api_no_chroot(void); +void scheduler_api_set_chroot(const char *); +void scheduler_api_set_user(const char *); +int scheduler_api_dispatch(void); + +/* table */ +void table_api_on_update(int(*)(void)); +void table_api_on_check(int(*)(int, struct dict *, const char *)); +void table_api_on_lookup(int(*)(int, struct dict *, const char *, char *, size_t)); +void table_api_on_fetch(int(*)(int, struct dict *, char *, size_t)); +int table_api_dispatch(void); +const char *table_api_get_name(void); + +#endif diff --git a/foobar/portable/smtpd/smtpd-defines.h b/foobar/portable/smtpd/smtpd-defines.h new file mode 100644 index 00000000..f22a546f --- /dev/null +++ b/foobar/portable/smtpd/smtpd-defines.h @@ -0,0 +1,68 @@ +/* $OpenBSD: smtpd-defines.h,v 1.12 2020/02/24 16:16:08 millert Exp $ */ + +/* + * Copyright (c) 2013 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + +#define SMTPD_TABLENAME_SIZE (64 + 1) +#define SMTPD_TAG_SIZE (32 + 1) + +/* buffer sizes for email address components */ +#define SMTPD_MAXLOCALPARTSIZE (255 + 1) +#define SMTPD_MAXDOMAINPARTSIZE (255 + 1) +#define SMTPD_MAXMAILADDRSIZE (255 + 1) + +/* buffer size for virtual username (can be email addresses) */ +#define SMTPD_VUSERNAME_SIZE (255 + 1) +#define SMTPD_SUBADDRESS_SIZE (255 + 1) + +#ifndef SMTPD_USER +#define SMTPD_USER "_smtpd" +#endif +#ifndef PATH_CHROOT +#define PATH_CHROOT "/var/empty" +#endif +#ifndef SMTPD_QUEUE_USER +#define SMTPD_QUEUE_USER "_smtpq" +#endif +#ifndef SMTPD_QUEUE_GROUP +#define SMTPD_QUEUE_GROUP "_smtpq" +#endif +#ifndef PATH_SPOOL +#define PATH_SPOOL "/var/spool/smtpd" +#endif + +#ifndef PATH_MAILLOCAL +#define PATH_MAILLOCAL PATH_LIBEXEC "/mail.local" +#endif + +#ifndef PATH_MAKEMAP +#define PATH_MAKEMAP "/usr/sbin/makemap" +#endif + +#define SUBADDRESSING_DELIMITER "+" + + +/* sendmail compat */ + +#define EX_OK 0 +#define EX_NOHOST 68 +#define EX_UNAVAILABLE 69 +#define EX_SOFTWARE 70 +#define EX_TEMPFAIL 75 diff --git a/foobar/portable/smtpd/smtpd-filters.7 b/foobar/portable/smtpd/smtpd-filters.7 new file mode 100644 index 00000000..5af7008e --- /dev/null +++ b/foobar/portable/smtpd/smtpd-filters.7 @@ -0,0 +1,653 @@ +.\" $OpenBSD: smtpd-filters.7,v 1.6 2020/04/25 09:44:02 eric Exp $ +.\" +.\" Copyright (c) 2008 Janne Johansson <jj@openbsd.org> +.\" Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> +.\" Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.\" +.Dd $Mdocdate: April 25 2020 $ +.Dt FILTERS 7 +.Os +.Sh NAME +.Nm filters +.Nd filtering API for the +.Xr smtpd 8 +daemon +.Sh DESCRIPTION +The +.Xr smtpd 8 +daemon provides a Simple Mail Transfer Protocol (SMTP) implementation +that allows an ordinary machine to become Mail eXchangers (MX). +Many features that are commonly used by MX, +such as delivery reporting or Spam filtering, +are outside the scope of SMTP and too complex to fit in +.Xr smtpd 8 . +.Pp +Because an MX needs to provide these features, +.Xr smtpd 8 +provides an API to extend its behavior through pluggable +.Nm . +.Pp +At runtime, +.Xr smtpd 8 +can report events to +.Nm +and query what it should answer to these events. +This allows the decision logic to rely on third-party programs. +.Sh DESIGN +The +.Nm +are programs that run as unique standalone processes, +they do not share +.Xr smtpd 8 +memory space. +They are executed by +.Xr smtpd 8 +at startup and expected to run in an infinite loop, +reading events and filtering requests from +.Xr stdin 4 , +writing responses to +.Xr stdout 4 +and logging to +.Xr stderr 4 . +They are not allowed to terminate. +.Pp +Because +.Nm +are standalone programs that communicate with +.Xr smtpd 8 +through +.Xr fd 4 , +they may run as different users than +.Xr smtpd 8 +and may be written in any language. +The +.Nm +must not use blocking I/O, +they must support answering asynchronously to +.Xr smtpd 8 . +.Sh REPORT AND FILTER +The API relies on two streams, +report and filter. +.Pp +The report stream is a one-way stream which allows +.Xr smtpd 8 +to inform +.Nm +in real-time about events that are occurring in the daemon. +The report events do not expect an answer from +.Nm , +it is just meant to provide them with informations. +A filter should be able to replicate the +.Xr smtpd 8 +state for a session by gathering informations coming from report events. +No decision is ever taken by the report stream. +.Pp +The filter stream is a two-way stream which allows +.Xr smtpd 8 +to query +.Nm +about what it should do with a session at a given phase. +The filter requests expects an answer from +.Nm , +.Xr smtpd 8 +will not let the session move forward until then. +A decision must always be taken by the filter stream. +.Pp +It is sometimes possible to rely on filter requests to gather information, +but because a reponse is expected by +.Xr smtpd 8 , +this is more costly than using report events. +The correct pattern for writing filters is to use the report events to +create a local state for a session, +then use filter requests to take decisions based on this state. +The only case when using filter request instead of report events is correct, +is when a decision is required for the filter request and there is no need for +more information than that of the event. +.Sh PROTOCOL +The protocol is straightforward, +it consists of a human-readable line exchanges between +.Nm +and +.Xr smtpd 8 +through +.Xr fd 4 . +.Pp +The protocol begins with a handshake. +First, +.Xr smtpd 8 +provides +.Nm +with general configuration information in the form of key-value lines: +.Bd -literal -offset indent +config|smtpd-version|6.6.1 +config|smtp-session-timeout|300 +config|subsystem|smtp-in +config|ready +.Ed +.Pp +Then, +.Nm +register the stream, +subsystem and event they want to handle: +.Bd -literal -offset indent +register|report|smtp-in|link-connect +register|ready +.Ed +.Pp +Finally, +.Xr smtpd 8 +will emit report events and filter requests, +expecting +.Nm +to react accordingly either by responding or not depending on the stream: +.Bd -literal -offset indent +report|0.5|1576146008.006099|smtp-in|link-connect|7641df9771b4ed00|mail.openbsd.org|pass|199.185.178.25:33174|45.77.67.80:25 +report|0.5|1576147242.200225|smtp-in|link-connect|7641dfb3798eb5bf|mail.openbsd.org|pass|199.185.178.25:31205|45.77.67.80:25 +report|0.5|1576148447.982572|smtp-in|link-connect|7641dfc063102cbd|mail.openbsd.org|pass|199.185.178.25:24786|45.77.67.80:25 +.Ed +.Pp +The char +.Dq | +may only appear in the last field of a payload, +in which case it should be considered a regular char and not a separator. +Other fields have strict formatting excluding the possibility of having a +.Dq | . +.Pp +The list of subsystems and events, +as well as the format of requests and reponses, +will be documented in the sections below. +.Sh CONFIGURATION +During the initial handshake, +.Xr smtpd 8 +will emit a serie of configuration keys and values. +The list is meant to be ignored by +.Nm +that do not require it and consumed gracefully by filters that do. +.Pp +There are currently three keys: +.Bd -literal -offset indent +config|smtpd-version|6.6.1 +config|smtp-session-timeout|300 +config|subsystem|smtp-in +.Ed +.Pp +When +.Xr smtpd 8 +has sent all configuration keys it emits the following line: +.Bd -literal -offset indent +config|ready +.Ed +.Sh REPORT EVENTS +There is currently only one subsystem supported in the API: +smtp-in. +.Pp +Each report event is generated by +.Xr smtpd 8 +as a single line similar to the one below: +.Bd -literal -offset indent +report|0.5|1576146008.006099|smtp-in|link-connect|7641df9771b4ed00|mail.openbsd.org|pass|199.185.178.25:33174|45.77.67.80:25 +.Ed +.Pp +The format consists of a protocol prefix containing the stream, +the protocol version, +the timestamp, +the subsystem, +the event and the unique session identifier separated by +.Dq | : +.Bd -literal -offset indent +report|0.5|1576146008.006099|smtp-in|link-connect|7641df9771b4ed00 +.Ed +.Pp +It is followed by a suffix containing the event-specific parameters, +also separated by +.Dq | : +.Bd -literal -offset indent +mail.openbsd.org|pass|199.185.178.25:33174|45.77.67.80:25 +.Ed +.Pp +The list of events and event-specific parameters are provided here for smtp-in: +.Bl -tag -width Ds +.It Ic link-connect : Ar rdns fcrdns src dest +This event is generated upon connection. +.Pp +.Ar rdns +contains the reverse DNS hostname for the remote end or an empty string if none. +.Pp +.Ar fcrdns +contains the string +.Dq pass +or +.Dq fail +depending on if the remote end validates FCrDNS. +.Pp +.Ar src +holds either the IP address and port from source address, +in the format +.Dq address:port +or the path to a UNIX socket in the format +.Dq unix:/path . +.Pp +.Ar dest +holds either the IP address and port from destination address, +in the format +.Dq address:port +or the path to a UNIX socket in the format +.Dq unix:/path . +.It Ic link-greeting : Ar hostname +This event is generated upon display of the server banner. +.Pp +.Ar hostname +contains the hostname displayed in the banner. +.It Ic link-identify : Ar method identity +This event is generated upon +.Dq HELO +or +.Dq EHLO +command from the client. +.Pp +.Ar method +contains the string +.Dq HELO +or +.Dq EHLO +indicating the method used by the client. +.Pp +.Ar identity +contains the identity provided by the client. +.It Ic link-tls : Ar tls-string +This event is generated upon successful negotiation of TLS. +.Pp +.Ar tls-string +contains a colon-separated list of TLS properties including the TLS version, +the cipher suite used by the session and the cipher strenght in bits. +.It Ic link-disconnect +This event is generated upon disconnection of the client. +.It Ic link-auth : Ar username result +This event is generated upon authentication attempt of the client. +.Pp +.Ar username +contains the username used for the authentication attempt. +.Pp +.Ar result +contains the string +.Dq pass , +.Dq fail +or +.Dq error +depending on the result of the authentication attempt. +.It Ic tx-reset : Op message-id +This event is generated when a transaction is reset. +.Pp +If reset happend while in a transaction, +.Ar message-id +contains the identifier of the transaction being reset. +.It Ic tx-begin : Ar message-id +This event is generated when a transaction is initiated. +.Pp +.Ar message-id +contains the identifier for the transaction. +.It Ic tx-mail : Ar message-id Ar result address +This event is generated when client emits +.Dq MAIL FROM . +.Pp +.Ar message-id +contains the identifier for the transaction. +.Pp +.Ar result +contains +.Dq ok +if the sender was accepted, +.Dq permfail +if it was rejected +or +.Dq tempfail +if it was rejected for a transient error. +.Pp +.Ar address +contains the e-mail address of the sender. +The address is normalized and sanitized, +the protocol +.Dq < +and +.Dq > +are removed and so are parameters to +.Dq MAIL FROM . +.It Ic tx-rcpt : Ar message-id Ar result address +This event is generated when client emits +.Dq RCPT TO . +.Pp +.Ar message-id +contains the identifier for the transaction. +.Pp +.Ar result +contains +.Dq ok +if the recipient was accepted, +.Dq permfail +if it was rejected +or +.Dq tempfail +if it was rejected for a transient error. +.Pp +.Ar address +contains the e-mail address of the recipient. +The address is normalized and sanitized, +the protocol +.Dq < +and +.Dq > +are removed and so are parameters to +.Dq RCPT TO . +.It Ic tx-envelope : Ar message-id Ar envelope-id +This event is generated when an envelope is accepted. +.Pp +.Ar envelope-id +contains the unique identifier for the envelope. +.It Ic tx-data : Ar message-id Ar result +This event is generated when client has emitted +.Dq DATA . +.Pp +.Ar message-id +contains the unique identifier for the transaction. +.Pp +.Ar result +contains +.Dq ok +if server accepted to process the message, +.Dq permfail +if it has not accepted and +.Dq tempfail +if a transient error is preventing the processing of message. +.It Ic tx-commit : Ar message-id Ar message-size +This event is generated when a transaction has been accepted by the server. +.Pp +.Ar message-id +contains the unique identifier for the SMTP transaction. +.Pp +.Ar message-size +contains the size of the message submitted in the +.Dq DATA +phase of the SMTP transaction. +.It Ic tx-rollback : Ar message-id +This event is generated when a transaction has been rejected by the server. +.Pp +.Ar message-id +contains the unique identifier for the SMTP transaction. +.It Ic protocol-client : Ar command +This event is generated for every command submitted by the client. +It contains the raw command as received by the server. +.Pp +.Ar command +contains the command emitted by the client to the server. +.It Ic protocol-server : Ar response +This event is generated for every response emitted by the server. +It contains the raw response as emitted by the server. +.Pp +.Ar response +contains the response emitted by the server to the client. +.It Ic filter-report : Ar filter-kind Ar name message +This event is generated when a filter emits a report. +.Pp +.Ar filter-kind may be either +.Dq builtin +or +.Dq proc +depending on if the filter is an +.Xr smtpd 8 +builtin filter or a proc filter implementing the API. +.Pp +.Ar name +is the name of the filter that generated the report. +.Pp +.Ar message +is a filter-specific message. +.It Ic filter-response : Ar phase response Op param +This event is generated when a filter responds to a filtering request. +.Pp +.Ar phase +contains the phase name for the request. +The phases are documented in the next section. +.Pp +.Ar response +contains the response of the filter to the request, +it is either one of +.Dq proceed , +.Dq report , +.Dq reject , +.Dq disconnect , +.Dq junk or +.Dq rewrite . +.Pp +If specified, +.Ar param +is the parameter to the response. +.It Ic timeout +This event is generated when a timeout happens for a session. +.El +.Sh FILTER REQUESTS +There is currently only one subsystem supported in the API: +smtp-in. +.Pp +The filter requests allow +.Xr smtpd 8 +to query +.Nm +about what to do with a session at a particular phase. +In addition, +they allow +.Nm +to alter the content of a message by adding, +modifying, +or suppressing lines of input in a way that is similar to what program like +.Xr sed 1 +or +.Xr grep 1 +would do. +.Pp +Each filter request is generated by +.Xr smtpd 8 +as a single line similar to the one below: +.Bd -literal -offset indent +filter|0.5|1576146008.006099|smtp-in|connect|7641df9771b4ed00|1ef1c203cc576e5d|mail.openbsd.org|pass|199.185.178.25:33174|45.77.67.80:25 +.Ed +.Pp +The format consists of a protocol prefix containing the stream, +the protocol version, +the timestamp, +the subsystem, +the filtering phase, +the unique session identifier and an opaque token separated by +.Dq | +that the filter should provide in its response: +.Bd -literal -offset indent +filter|0.5|1576146008.006099|smtp-in|connect|7641df9771b4ed00|1ef1c203cc576e5d +.Ed +.Pp +It is followed by a suffix containing the phase-specific parameters to the +filter request, +also separated by +.Dq | : +.Bd -literal -offset indent +mail.openbsd.org|pass|199.185.178.25:33174|45.77.67.80:25 +.Ed +.Pp +Unlike with report events, +.Xr smtpd 8 +expects answers from filter requests and will not allow a session to move +forward before the filter has instructed +.Xr smtpd 8 +what to do with it. +.Pp +For all phases, +excepted +.Dq data-line , +the responses must follow the same construct, +a message type +.Dq filter-result , +followed by the unique session id, +the opaque token, +a decision and optional decision-specific parameters: +.Bd -literal -offset indent +filter-result|7641df9771b4ed00|1ef1c203cc576e5d|proceed +filter-result|7641df9771b4ed00|1ef1c203cc576e5d|reject|550 nope +.Ed +.Pp +The possible decisions to a +.Dq filter-result +message will be described below. +.Pp +For the +.Dq data-line +phase, +.Nm +are fed with a stream of lines corresponding to the message to filter, +and terminated by a single dot: +.Bd -literal -offset indent +filter|0.5|1576146008.006099|smtp-in|data-line|7641df9771b4ed00|1ef1c203cc576e5d|line 1 +filter|0.5|1576146008.006103|smtp-in|data-line|7641df9771b4ed00|1ef1c203cc576e5d|line 2 +filter|0.5|1576146008.006105|smtp-in|data-line|7641df9771b4ed00|1ef1c203cc576e5d|. +.Ed +.Pp +They are expected to produce an output stream similarly terminate by a single +dot. +A filter may inject, +suppress, +modify or echo back the lines it receives. +Ultimately, +.Xr smtpd 8 +will assume that the message consists of the output from +.Nm . +.Pp +Note that filters may be chained and the lines that are input into a filter +are the lines that are output from previous filter. +.Pp +The response to +.Dq data-line +requests use their own construct. +A +.Dq filter-dataline +prefix, +followed by the unique session identifier, +the opaque token and the output line as follows: +.Bd -literal -offset indent +filter-dataline|7641df9771b4ed00|1ef1c203cc576e5d|line 1 +filter-dataline|7641df9771b4ed00|1ef1c203cc576e5d|line 2 +filter-dataline|7641df9771b4ed00|1ef1c203cc576e5d|. +.Ed +.Pp +The list of events and event-specific parameters are provided here for smtp-in: +.Bl -tag -width Ds +.It Ic connect : Ar rdns fcrdns src dest +This request is emitted after connection, +before the banner is displayed. +.It Ic helo : Ar identity +This request is emitted after the client has emitted +.Dq HELO . +.It Ic ehlo : Ar identity +This request is emitted after the client has emitted +.Dq EHLO . +.It Ic starttls : Ar tls-string +This request is emitted after the client has requested +.Dq STARTTLS . +.It Ic auth : Ar auth +This request is emitted after the client has requested +.Dq AUTH . +.It Ic mail-from : Ar address +This request is emitted after the client has requested +.Dq MAIL FROM . +.It Ic rcpt-to : Ar address +This request is emitted after the client has requested +.Dq RCPT TO . +.It Ic data +This request is emitted after the client has requested +.Dq DATA . +.It Ic data-line : Ar line +This request is emitted for each line of input in the +.Dq DATA +phase. +The lines are raw dot-escaped SMTP DATA input, +terminated with a single dot. +.It Ic commit +This request is emitted after the final single dot is received. +.El +.Pp +For every filtering phase, +excepted +.Dq data-line , +the following decisions may be taken by a filter: +.Bl -tag -width Ds +.It Ic proceed +No action is taken, +session or transaction may be passed to the next filter. +.It Ic junk +The session or transaction is marked as Spam. +.Xr smtpd 8 +will prepend a +.Dq X-Spam +header to the message. +.It Ic reject Ar error +The command is rejected with the message +.Ar error . +The message must be a valid SMTP message including status code, +5xx or 4xx. +.Pp +Messages starting with a 5xx status result in a permanent failure, +those starting with a 4xx status result in a temporary failure. +.Pp +Messages starting with a 421 status will result in a client disconnect. +.It Ic disconnect Ar error +The client is disconnected with the message +.Ar error . +The message must be a valid SMTP message including status code, +5xx or 4xx. +.Pp +Messages starting with a 5xx status result in a permanent failure, +those starting with a 4xx status result in a temporary failure. +.It Ic rewrite Ar parameter +The command parameter is rewritten. +.Pp +This decision allows a filter to perform a rewrite of client-submitted +commands before they are processed by the SMTP engine. +.Ar parameter +is expected to be a valid SMTP parameter for the command. +.It Ic report Ar parameter +Generates a report with +.Ar parameter +for this filter. +.El +.\".Sh EXAMPLES +.\"This example filter written in +.\".Xr sh 1 +.\"will echo back... +.\".Bd -literal -offset indent +.\"XXX +.\".Ed +.\".Pp +.\"This example filter will filter... +.\".Bd -literal -offset indent +.\"XXX +.\".Ed +.\".Pp +.\"Note that libraries may provide a simpler interface to +.\".Nm +.\"that does not require implementing the protocol itself. +.\".Ed +.Sh SEE ALSO +.Xr smtpd 8 +.Sh HISTORY +.Nm +first appeared in +.Ox 6.6 . diff --git a/foobar/portable/smtpd/smtpd.8 b/foobar/portable/smtpd/smtpd.8 new file mode 100644 index 00000000..e3429f07 --- /dev/null +++ b/foobar/portable/smtpd/smtpd.8 @@ -0,0 +1,167 @@ +.\" $OpenBSD: smtpd.8,v 1.32 2017/01/03 22:11:39 jmc Exp $ +.\" +.\" Copyright (c) 2012, Eric Faurot <eric@openbsd.org> +.\" Copyright (c) 2008, Gilles Chehade <gilles@poolp.org> +.\" Copyright (c) 2008, Pierre-Yves Ritschard <pyr@openbsd.org> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: January 3 2017 $ +.Dt SMTPD 8 +.Os +.Sh NAME +.Nm smtpd +.Nd Simple Mail Transfer Protocol daemon +.Sh SYNOPSIS +.Nm +.Op Fl dFhnv +.Op Fl D Ar macro Ns = Ns Ar value +.Op Fl f Ar file +.Op Fl P Ar system +.Op Fl T Ar trace +.Sh DESCRIPTION +.Nm +is a Simple Mail Transfer Protocol +.Pq SMTP +daemon which can be used as a machine's primary mail system. +.Nm +can listen on a network interface and handle SMTP +transactions; it can also be fed messages through the standard +.Xr sendmail 8 +interface. +It can relay messages through remote mail transfer agents or store them +locally using either the mbox or maildir format. +This implementation supports SMTP as defined by RFC 5321 as well as several +extensions. +A running +.Nm +can be controlled through +.Xr smtpctl 8 . +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl D Ar macro Ns = Ns Ar value +Define +.Ar macro +to be set to +.Ar value +on the command line. +Overrides the definition of +.Ar macro +in the configuration file. +.It Fl d +Do not daemonize. +If this option is specified, +.Nm +will run in the foreground and log to +.Em stderr . +.It Fl F +Do not daemonize. +If this option is specified, +.Nm +will run in the foreground and log to +.Xr syslogd 8 . +.It Fl f Ar file +Specify an alternative configuration file. +.It Fl h +Display version and usage. +.It Fl n +Configtest mode. +Only check the configuration file for validity. +.It Fl P Ar system +Pause a specific subsystem at startup. +Normal operation can be resumed using +.Xr smtpctl 8 . +This option can be used multiple times. +The accepted values are: +.Pp +.Bl -tag -width "smtpXXX" -compact +.It mda +Do not schedule local deliveries. +.It mta +Do not schedule remote transfers. +.It smtp +Do not listen on SMTP sockets. +.El +.It Fl T Ar trace +Enables real-time tracing at startup. +Normal operation can be resumed using +.Xr smtpctl 8 . +This option can be used multiple times. +The accepted values are: +.Pp +.Bl -bullet -compact +.It +imsg +.It +io +.It +smtp (incoming sessions) +.It +filters +.It +transfer (outgoing sessions) +.It +bounce +.It +scheduler +.It +expand (aliases/virtual/forward expansion) +.It +lookup (user/credentials lookups) +.It +stat +.It +rules (matched by incoming sessions) +.It +mproc +.It +all +.El +.It Fl v +Produce more verbose output. +.El +.Sh FILES +.Bl -tag -width "/etc/mail/smtpd.confXXX" -compact +.It Pa /etc/mail/mailname +Alternate server name to use. +.It Pa /etc/mail/smtpd.conf +Default +.Nm +configuration file. +.It Pa /var/run/smtpd.sock +.Ux Ns -domain +socket used for communication with +.Xr smtpctl 8 . +.It Pa /var/spool/smtpd/ +Spool directories for mail during processing. +.It Pa ~/.forward +User email forwarding information. +.El +.Sh SEE ALSO +.Xr forward 5 , +.Xr smtpd.conf 5 , +.Xr mailwrapper 8 , +.Xr smtpctl 8 +.Sh STANDARDS +.Rs +.%A J. Klensin +.%D October 2008 +.%R RFC 5321 +.%T Simple Mail Transfer Protocol +.Re +.Sh HISTORY +The +.Nm +program first appeared in +.Ox 4.6 . diff --git a/foobar/portable/smtpd/smtpd.c b/foobar/portable/smtpd/smtpd.c new file mode 100644 index 00000000..e8b361fd --- /dev/null +++ b/foobar/portable/smtpd/smtpd.c @@ -0,0 +1,2326 @@ +/* $OpenBSD: smtpd.c,v 1.332 2020/02/24 16:16:08 millert Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> + * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> + * + * 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 "includes.h" + +#include <sys/file.h> /* Needed for flock */ +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <sys/uio.h> +#include <sys/mman.h> + +#ifdef BSD_AUTH +#include <bsd_auth.h> +#endif + +#ifdef USE_PAM +#if defined(HAVE_SECURITY_PAM_APPL_H) +#include <security/pam_appl.h> +#elif defined (HAVE_PAM_PAM_APPL_H) +#include <pam/pam_appl.h> +#endif +#endif + +#ifdef HAVE_CRYPT_H +#include <crypt.h> /* needed for crypt() */ +#endif +#include <dirent.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <grp.h> /* needed for setgroups */ +#include <fts.h> +#include <grp.h> +#include <imsg.h> +#include <inttypes.h> +#include <libgen.h> +#ifdef HAVE_LOGIN_CAP_H +#include <login_cap.h> +#endif +#ifdef HAVE_PATHS_H +#include <paths.h> +#endif +#include <poll.h> +#include <pwd.h> +#include <signal.h> +#ifdef HAVE_SHADOW_H +#include <shadow.h> /* needed for getspnam() */ +#endif +#include <stdio.h> +#include <syslog.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <time.h> +#include <unistd.h> +#ifdef HAVE_UTIL_H +#include <util.h> +#endif + +#include <openssl/ssl.h> +#include <openssl/evp.h> + +#include "smtpd.h" +#include "log.h" +#include "ssl.h" + +extern char *__progname; + +#define SMTPD_MAXARG 32 + +static void parent_imsg(struct mproc *, struct imsg *); +static void usage(void); +static int smtpd(void); +static void parent_shutdown(void); +static void parent_send_config(int, short, void *); +static void parent_send_config_lka(void); +static void parent_send_config_pony(void); +static void parent_send_config_ca(void); +static void parent_sig_handler(int, short, void *); +static void forkmda(struct mproc *, uint64_t, struct deliver *); +static int parent_forward_open(char *, char *, uid_t, gid_t); +static struct child *child_add(pid_t, int, const char *); +static struct mproc *start_child(int, char **, char *); +static struct mproc *setup_peer(enum smtp_proc_type, pid_t, int); +static void setup_peers(struct mproc *, struct mproc *); +static void setup_done(struct mproc *); +static void setup_proc(void); +static struct mproc *setup_peer(enum smtp_proc_type, pid_t, int); +static int imsg_wait(struct imsgbuf *, struct imsg *, int); + +static void offline_scan(int, short, void *); +static int offline_add(char *, uid_t, gid_t); +static void offline_done(void); +static int offline_enqueue(char *, uid_t, gid_t); + +static void purge_task(void); +static int parent_auth_user(const char *, const char *); +static void load_pki_tree(void); +static void load_pki_keys(void); + +static void fork_filter_processes(void); +static void fork_filter_process(const char *, const char *, const char *, const char *, const char *, uint32_t); + +enum child_type { + CHILD_DAEMON, + CHILD_MDA, + CHILD_PROCESSOR, + CHILD_ENQUEUE_OFFLINE, +}; + +struct child { + pid_t pid; + enum child_type type; + const char *title; + int mda_out; + uint64_t mda_id; + char *path; + char *cause; +}; + +struct offline { + TAILQ_ENTRY(offline) entry; + uid_t uid; + gid_t gid; + char *path; +}; + +#define OFFLINE_READMAX 20 +#define OFFLINE_QUEUEMAX 5 +static size_t offline_running = 0; +TAILQ_HEAD(, offline) offline_q; + +static struct event config_ev; +static struct event offline_ev; +static struct timeval offline_timeout; + +static pid_t purge_pid = -1; + +extern char **environ; +void (*imsg_callback)(struct mproc *, struct imsg *); + +enum smtp_proc_type smtpd_process; + +struct smtpd *env = NULL; + +struct mproc *p_control = NULL; +struct mproc *p_lka = NULL; +struct mproc *p_parent = NULL; +struct mproc *p_queue = NULL; +struct mproc *p_scheduler = NULL; +struct mproc *p_pony = NULL; +struct mproc *p_ca = NULL; + +const char *backend_queue = "fs"; +const char *backend_scheduler = "ramqueue"; +const char *backend_stat = "ram"; + +int profiling = 0; +int debug = 0; +int foreground = 0; +int control_socket = -1; + +struct tree children; + +/* Saved arguments to main(). */ +char **saved_argv; +int saved_argc; + +static void +parent_imsg(struct mproc *p, struct imsg *imsg) +{ + struct forward_req *fwreq; + struct filter_proc *processor; + struct deliver deliver; + struct child *c; + struct msg m; + const void *data; + const char *username, *password, *cause, *procname; + uint64_t reqid; + size_t sz; + void *i; + int fd, n, v, ret; + + if (imsg == NULL) + fatalx("process %s socket closed", p->name); + + switch (imsg->hdr.type) { + case IMSG_LKA_OPEN_FORWARD: + CHECK_IMSG_DATA_SIZE(imsg, sizeof *fwreq); + fwreq = imsg->data; + fd = parent_forward_open(fwreq->user, fwreq->directory, + fwreq->uid, fwreq->gid); + fwreq->status = 0; + if (fd == -1 && errno != ENOENT) { + if (errno == EAGAIN) + fwreq->status = -1; + } + else + fwreq->status = 1; + m_compose(p, IMSG_LKA_OPEN_FORWARD, 0, 0, fd, + fwreq, sizeof *fwreq); + return; + + case IMSG_LKA_AUTHENTICATE: + /* + * If we reached here, it means we want root to lookup + * system user. + */ + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_string(&m, &username); + m_get_string(&m, &password); + m_end(&m); + + ret = parent_auth_user(username, password); + + m_create(p, IMSG_LKA_AUTHENTICATE, 0, 0, -1); + m_add_id(p, reqid); + m_add_int(p, ret); + m_close(p); + return; + + case IMSG_MDA_FORK: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_data(&m, &data, &sz); + m_end(&m); + if (sz != sizeof(deliver)) + fatalx("expected deliver"); + memmove(&deliver, data, sz); + forkmda(p, reqid, &deliver); + return; + + case IMSG_MDA_KILL: + m_msg(&m, imsg); + m_get_id(&m, &reqid); + m_get_string(&m, &cause); + m_end(&m); + + i = NULL; + while ((n = tree_iter(&children, &i, NULL, (void**)&c))) + if (c->type == CHILD_MDA && + c->mda_id == reqid && + c->cause == NULL) + break; + if (!n) { + log_debug("debug: smtpd: " + "kill request: proc not found"); + return; + } + + c->cause = xstrdup(cause); + log_debug("debug: smtpd: kill requested for %u: %s", + c->pid, c->cause); + kill(c->pid, SIGTERM); + return; + + case IMSG_CTL_VERBOSE: + m_msg(&m, imsg); + m_get_int(&m, &v); + m_end(&m); + log_trace_verbose(v); + return; + + case IMSG_CTL_PROFILE: + m_msg(&m, imsg); + m_get_int(&m, &v); + m_end(&m); + profiling = v; + return; + + case IMSG_LKA_PROCESSOR_ERRFD: + m_msg(&m, imsg); + m_get_string(&m, &procname); + m_end(&m); + + processor = dict_xget(env->sc_filter_processes_dict, procname); + m_create(p_lka, IMSG_LKA_PROCESSOR_ERRFD, 0, 0, processor->errfd); + m_add_string(p_lka, procname); + m_close(p_lka); + return; + } + + errx(1, "parent_imsg: unexpected %s imsg from %s", + imsg_to_str(imsg->hdr.type), proc_title(p->proc)); +} + +static void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, "usage: %s [-dFhnv] [-D macro=value] " + "[-f file] [-P system] [-T trace]\n", __progname); + exit(1); +} + +static void +parent_shutdown(void) +{ + pid_t pid; + + mproc_clear(p_ca); + mproc_clear(p_pony); + mproc_clear(p_control); + mproc_clear(p_lka); + mproc_clear(p_scheduler); + mproc_clear(p_queue); + + do { + pid = waitpid(WAIT_MYPGRP, NULL, 0); + } while (pid != -1 || (pid == -1 && errno == EINTR)); + + unlink(SMTPD_SOCKET); + + log_info("Exiting"); + exit(0); +} + +static void +parent_send_config(int fd, short event, void *p) +{ + parent_send_config_lka(); + parent_send_config_pony(); + parent_send_config_ca(); + purge_config(PURGE_PKI); +} + +static void +parent_send_config_pony(void) +{ + log_debug("debug: parent_send_config: configuring pony process"); + m_compose(p_pony, IMSG_CONF_START, 0, 0, -1, NULL, 0); + m_compose(p_pony, IMSG_CONF_END, 0, 0, -1, NULL, 0); +} + +void +parent_send_config_lka() +{ + log_debug("debug: parent_send_config_ruleset: reloading"); + m_compose(p_lka, IMSG_CONF_START, 0, 0, -1, NULL, 0); + m_compose(p_lka, IMSG_CONF_END, 0, 0, -1, NULL, 0); +} + +static void +parent_send_config_ca(void) +{ + log_debug("debug: parent_send_config: configuring ca process"); + m_compose(p_ca, IMSG_CONF_START, 0, 0, -1, NULL, 0); + m_compose(p_ca, IMSG_CONF_END, 0, 0, -1, NULL, 0); +} + +static void +parent_sig_handler(int sig, short event, void *p) +{ + struct child *child; + int status, fail; + pid_t pid; + char *cause; + + switch (sig) { + case SIGTERM: + case SIGINT: + log_debug("debug: got signal %d", sig); + parent_shutdown(); + /* NOT REACHED */ + + case SIGCHLD: + do { + int len; + enum mda_resp_status mda_status; + int mda_sysexit; + + pid = waitpid(-1, &status, WNOHANG); + if (pid <= 0) + continue; + + fail = 0; + if (WIFSIGNALED(status)) { + fail = 1; + len = asprintf(&cause, "terminated; signal %d", + WTERMSIG(status)); + mda_status = MDA_TEMPFAIL; + mda_sysexit = 0; + } else if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != 0) { + fail = 1; + len = asprintf(&cause, + "exited abnormally"); + mda_sysexit = WEXITSTATUS(status); + if (mda_sysexit == EX_OSERR || + mda_sysexit == EX_TEMPFAIL) + mda_status = MDA_TEMPFAIL; + else + mda_status = MDA_PERMFAIL; + } else { + len = asprintf(&cause, "exited okay"); + mda_status = MDA_OK; + mda_sysexit = 0; + } + } else + /* WIFSTOPPED or WIFCONTINUED */ + continue; + + if (len == -1) + fatal("asprintf"); + + if (pid == purge_pid) + purge_pid = -1; + + child = tree_pop(&children, pid); + if (child == NULL) + goto skip; + + switch (child->type) { + case CHILD_PROCESSOR: + if (fail) { + log_warnx("warn: lost processor: %s %s", + child->title, cause); + parent_shutdown(); + } + break; + + case CHILD_DAEMON: + if (fail) + log_warnx("warn: lost child: %s %s", + child->title, cause); + break; + + case CHILD_MDA: + if (WIFSIGNALED(status) && + WTERMSIG(status) == SIGALRM) { + char *tmp; + if (asprintf(&tmp, + "terminated; timeout") != -1) { + free(cause); + cause = tmp; + } + } + else if (child->cause && + WIFSIGNALED(status) && + WTERMSIG(status) == SIGTERM) { + free(cause); + cause = child->cause; + child->cause = NULL; + } + free(child->cause); + log_debug("debug: smtpd: mda process done " + "for session %016"PRIx64 ": %s", + child->mda_id, cause); + + m_create(p_pony, IMSG_MDA_DONE, 0, 0, + child->mda_out); + m_add_id(p_pony, child->mda_id); + m_add_int(p_pony, mda_status); + m_add_int(p_pony, mda_sysexit); + m_add_string(p_pony, cause); + m_close(p_pony); + + break; + + case CHILD_ENQUEUE_OFFLINE: + if (fail) + log_warnx("warn: smtpd: " + "couldn't enqueue offline " + "message %s; smtpctl %s", + child->path, cause); + else + unlink(child->path); + free(child->path); + offline_done(); + break; + + default: + fatalx("smtpd: unexpected child type"); + } + free(child); + skip: + free(cause); + } while (pid > 0 || (pid == -1 && errno == EINTR)); + + break; + default: + fatalx("smtpd: unexpected signal"); + } +} + +int +main(int argc, char *argv[]) +{ + int c, i; + int opts, flags; + const char *conffile = CONF_FILE; + int save_argc = argc; + char **save_argv = argv; + char *rexec = NULL; + struct smtpd *conf; + +#ifndef HAVE___PROGNAME + __progname = ssh_get_progname(argv[0]); +#endif + +#ifndef HAVE_SETPROCTITLE + /* Save argv. Duplicate so setproctitle emulation doesn't clobber it */ + saved_argc = argc; + saved_argv = xcalloc(argc + 1, sizeof(*saved_argv)); + for (i = 0; i < argc; i++) + saved_argv[i] = xstrdup(argv[i]); + saved_argv[i] = NULL; + + /* Prepare for later setproctitle emulation */ + compat_init_setproctitle(argc, argv); + argv = saved_argv; + + /* this is to work around GNU getopt + portable setproctitle() fuckery */ + save_argc = saved_argc; + save_argv = saved_argv; +#endif + + if ((conf = config_default()) == NULL) + err(1, NULL); + + env = conf; + + flags = 0; + opts = 0; + debug = 0; + tracing = 0; + + log_init(1, LOG_MAIL); + + TAILQ_INIT(&offline_q); + + while ((c = getopt(argc, argv, "B:dD:hnP:f:FT:vx:")) != -1) { + switch (c) { + case 'B': + if (strstr(optarg, "queue=") == optarg) + backend_queue = strchr(optarg, '=') + 1; + else if (strstr(optarg, "scheduler=") == optarg) + backend_scheduler = strchr(optarg, '=') + 1; + else if (strstr(optarg, "stat=") == optarg) + backend_stat = strchr(optarg, '=') + 1; + else + log_warnx("warn: " + "invalid backend specifier %s", + optarg); + break; + case 'd': + foreground = 1; + foreground_log = 1; + break; + case 'D': + if (cmdline_symset(optarg) < 0) + log_warnx("warn: " + "could not parse macro definition %s", + optarg); + break; + case 'h': + log_info("version: " SMTPD_NAME " " SMTPD_VERSION); + usage(); + break; + case 'n': + debug = 2; + opts |= SMTPD_OPT_NOACTION; + break; + case 'f': + conffile = optarg; + break; + case 'F': + foreground = 1; + break; + + case 'T': + if (!strcmp(optarg, "imsg")) + tracing |= TRACE_IMSG; + else if (!strcmp(optarg, "io")) + tracing |= TRACE_IO; + else if (!strcmp(optarg, "smtp")) + tracing |= TRACE_SMTP; + else if (!strcmp(optarg, "filters")) + tracing |= TRACE_FILTERS; + else if (!strcmp(optarg, "mta") || + !strcmp(optarg, "transfer")) + tracing |= TRACE_MTA; + else if (!strcmp(optarg, "bounce") || + !strcmp(optarg, "bounces")) + tracing |= TRACE_BOUNCE; + else if (!strcmp(optarg, "scheduler")) + tracing |= TRACE_SCHEDULER; + else if (!strcmp(optarg, "lookup")) + tracing |= TRACE_LOOKUP; + else if (!strcmp(optarg, "stat") || + !strcmp(optarg, "stats")) + tracing |= TRACE_STAT; + else if (!strcmp(optarg, "rules")) + tracing |= TRACE_RULES; + else if (!strcmp(optarg, "mproc")) + tracing |= TRACE_MPROC; + else if (!strcmp(optarg, "expand")) + tracing |= TRACE_EXPAND; + else if (!strcmp(optarg, "table") || + !strcmp(optarg, "tables")) + tracing |= TRACE_TABLES; + else if (!strcmp(optarg, "queue")) + tracing |= TRACE_QUEUE; + else if (!strcmp(optarg, "all")) + tracing |= ~TRACE_DEBUG; + else if (!strcmp(optarg, "profstat")) + profiling |= PROFILE_TOSTAT; + else if (!strcmp(optarg, "profile-imsg")) + profiling |= PROFILE_IMSG; + else if (!strcmp(optarg, "profile-queue")) + profiling |= PROFILE_QUEUE; + else + log_warnx("warn: unknown trace flag \"%s\"", + optarg); + break; + case 'P': + if (!strcmp(optarg, "smtp")) + flags |= SMTPD_SMTP_PAUSED; + else if (!strcmp(optarg, "mta")) + flags |= SMTPD_MTA_PAUSED; + else if (!strcmp(optarg, "mda")) + flags |= SMTPD_MDA_PAUSED; + break; + case 'v': + tracing |= TRACE_DEBUG; + break; + case 'x': + rexec = optarg; + break; + default: + usage(); + } + } + + argv += optind; + argc -= optind; + + if (argc || *argv) + usage(); + + env->sc_opts |= opts; + + ssl_init(); + + if (parse_config(conf, conffile, opts)) + exit(1); + + if (RAND_status() != 1) + errx(1, "PRNG is not seeded"); + + if (strlcpy(env->sc_conffile, conffile, PATH_MAX) + >= PATH_MAX) + errx(1, "config file exceeds PATH_MAX"); + + if (env->sc_opts & SMTPD_OPT_NOACTION) { + if (env->sc_queue_key && + crypto_setup(env->sc_queue_key, + strlen(env->sc_queue_key)) == 0) { + fatalx("crypto_setup:" + "invalid key for queue encryption"); + } + load_pki_tree(); + load_pki_keys(); + fprintf(stderr, "configuration OK\n"); + exit(0); + } + + env->sc_flags |= flags; + + /* check for root privileges */ + if (geteuid()) + errx(1, "need root privileges"); + + log_init(foreground_log, LOG_MAIL); + log_trace_verbose(tracing); + load_pki_tree(); + load_pki_keys(); + + log_debug("debug: using \"%s\" queue backend", backend_queue); + log_debug("debug: using \"%s\" scheduler backend", backend_scheduler); + log_debug("debug: using \"%s\" stat backend", backend_stat); + + if (env->sc_hostname[0] == '\0') + errx(1, "machine does not have a hostname set"); + env->sc_uptime = time(NULL); + + if (rexec == NULL) { + smtpd_process = PROC_PARENT; + + if (env->sc_queue_flags & QUEUE_ENCRYPTION) { + if (env->sc_queue_key == NULL) { + char *password; + + password = getpass("queue key: "); + if (password == NULL) + err(1, "getpass"); + + env->sc_queue_key = strdup(password); + explicit_bzero(password, strlen(password)); + if (env->sc_queue_key == NULL) + err(1, "strdup"); + } + else { + char *buf = NULL; + size_t sz = 0; + ssize_t len; + + if (strcasecmp(env->sc_queue_key, "stdin") == 0) { + if ((len = getline(&buf, &sz, stdin)) == -1) + err(1, "getline"); + if (buf[len - 1] == '\n') + buf[len - 1] = '\0'; + env->sc_queue_key = buf; + } + } + } + + log_info("info: %s %s starting", SMTPD_NAME, SMTPD_VERSION); + + if (!foreground) + if (daemon(0, 0) == -1) + err(1, "failed to daemonize"); + + /* setup all processes */ + + p_ca = start_child(save_argc, save_argv, "ca"); + p_ca->proc = PROC_CA; + + p_control = start_child(save_argc, save_argv, "control"); + p_control->proc = PROC_CONTROL; + + p_lka = start_child(save_argc, save_argv, "lka"); + p_lka->proc = PROC_LKA; + + p_pony = start_child(save_argc, save_argv, "pony"); + p_pony->proc = PROC_PONY; + + p_queue = start_child(save_argc, save_argv, "queue"); + p_queue->proc = PROC_QUEUE; + + p_scheduler = start_child(save_argc, save_argv, "scheduler"); + p_scheduler->proc = PROC_SCHEDULER; + + setup_peers(p_control, p_ca); + setup_peers(p_control, p_lka); + setup_peers(p_control, p_pony); + setup_peers(p_control, p_queue); + setup_peers(p_control, p_scheduler); + setup_peers(p_pony, p_ca); + setup_peers(p_pony, p_lka); + setup_peers(p_pony, p_queue); + setup_peers(p_queue, p_lka); + setup_peers(p_queue, p_scheduler); + + if (env->sc_queue_key) { + if (imsg_compose(&p_queue->imsgbuf, IMSG_SETUP_KEY, 0, + 0, -1, env->sc_queue_key, strlen(env->sc_queue_key) + + 1) == -1) + fatal("imsg_compose"); + if (imsg_flush(&p_queue->imsgbuf) == -1) + fatal("imsg_flush"); + } + + setup_done(p_ca); + setup_done(p_control); + setup_done(p_lka); + setup_done(p_pony); + setup_done(p_queue); + setup_done(p_scheduler); + + log_debug("smtpd: setup done"); + + return smtpd(); + } + + if (!strcmp(rexec, "ca")) { + smtpd_process = PROC_CA; + setup_proc(); + + return ca(); + } + + else if (!strcmp(rexec, "control")) { + smtpd_process = PROC_CONTROL; + setup_proc(); + + /* the control socket ensures that only one smtpd instance is running */ + control_socket = control_create_socket(); + + env->sc_stat = stat_backend_lookup(backend_stat); + if (env->sc_stat == NULL) + errx(1, "could not find stat backend \"%s\"", backend_stat); + + return control(); + } + + else if (!strcmp(rexec, "lka")) { + smtpd_process = PROC_LKA; + setup_proc(); + + return lka(); + } + + else if (!strcmp(rexec, "pony")) { + smtpd_process = PROC_PONY; + setup_proc(); + + return pony(); + } + + else if (!strcmp(rexec, "queue")) { + smtpd_process = PROC_QUEUE; + setup_proc(); + + if (env->sc_queue_flags & QUEUE_COMPRESSION) + env->sc_comp = compress_backend_lookup("gzip"); + + if (!queue_init(backend_queue, 1)) + errx(1, "could not initialize queue backend"); + + return queue(); + } + + else if (!strcmp(rexec, "scheduler")) { + smtpd_process = PROC_SCHEDULER; + setup_proc(); + + for (i = 0; i < MAX_BOUNCE_WARN; i++) { + if (env->sc_bounce_warn[i] == 0) + break; + log_debug("debug: bounce warning after %s", + duration_to_text(env->sc_bounce_warn[i])); + } + + return scheduler(); + } + + fatalx("bad rexec: %s", rexec); + + return (1); +} + +static struct mproc * +start_child(int save_argc, char **save_argv, char *rexec) +{ + struct mproc *p; + char *argv[SMTPD_MAXARG]; + int sp[2], argc = 0; + pid_t pid; + + if (save_argc >= SMTPD_MAXARG - 2) + fatalx("too many arguments"); + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1) + fatal("socketpair"); + + io_set_nonblocking(sp[0]); + io_set_nonblocking(sp[1]); + + switch (pid = fork()) { + case -1: + fatal("%s: fork", save_argv[0]); + case 0: + break; + default: + close(sp[0]); + p = calloc(1, sizeof(*p)); + if (p == NULL) + fatal("calloc"); + if((p->name = strdup(rexec)) == NULL) + fatal("strdup"); + mproc_init(p, sp[1]); + p->pid = pid; + p->handler = parent_imsg; + return p; + } + + if (sp[0] != 3) { + if (dup2(sp[0], 3) == -1) + fatal("%s: dup2", rexec); + } else if (fcntl(sp[0], F_SETFD, 0) == -1) + fatal("%s: fcntl", rexec); + + xclosefrom(4); + + for (argc = 0; argc < save_argc; argc++) + argv[argc] = save_argv[argc]; + argv[argc++] = "-x"; + argv[argc++] = rexec; + argv[argc++] = NULL; + + execvp(argv[0], argv); + fatal("%s: execvp", rexec); +} + +static void +setup_peers(struct mproc *a, struct mproc *b) +{ + int sp[2]; + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1) + fatal("socketpair"); + + io_set_nonblocking(sp[0]); + io_set_nonblocking(sp[1]); + + if (imsg_compose(&a->imsgbuf, IMSG_SETUP_PEER, b->proc, b->pid, sp[0], + NULL, 0) == -1) + fatal("imsg_compose"); + if (imsg_flush(&a->imsgbuf) == -1) + fatal("imsg_flush"); + + if (imsg_compose(&b->imsgbuf, IMSG_SETUP_PEER, a->proc, a->pid, sp[1], + NULL, 0) == -1) + fatal("imsg_compose"); + if (imsg_flush(&b->imsgbuf) == -1) + fatal("imsg_flush"); +} + +static void +setup_done(struct mproc *p) +{ + struct imsg imsg; + + if (imsg_compose(&p->imsgbuf, IMSG_SETUP_DONE, 0, 0, -1, NULL, 0) == -1) + fatal("imsg_compose"); + if (imsg_flush(&p->imsgbuf) == -1) + fatal("imsg_flush"); + + if (imsg_wait(&p->imsgbuf, &imsg, 10000) == -1) + fatal("imsg_wait"); + + if (imsg.hdr.type != IMSG_SETUP_DONE) + fatalx("expect IMSG_SETUP_DONE"); + + log_debug("setup_done: %s[%d] done", p->name, p->pid); + + imsg_free(&imsg); +} + +static void +setup_proc(void) +{ + struct imsgbuf *ibuf; + struct imsg imsg; + int setup = 1; + + log_procinit(proc_title(smtpd_process)); + + p_parent = calloc(1, sizeof(*p_parent)); + if (p_parent == NULL) + fatal("calloc"); + if((p_parent->name = strdup("parent")) == NULL) + fatal("strdup"); + p_parent->proc = PROC_PARENT; + p_parent->handler = imsg_dispatch; + mproc_init(p_parent, 3); + + ibuf = &p_parent->imsgbuf; + + while (setup) { + if (imsg_wait(ibuf, &imsg, 10000) == -1) + fatal("imsg_wait"); + + switch (imsg.hdr.type) { + case IMSG_SETUP_KEY: + env->sc_queue_key = strdup(imsg.data); + break; + case IMSG_SETUP_PEER: + setup_peer(imsg.hdr.peerid, imsg.hdr.pid, imsg.fd); + break; + case IMSG_SETUP_DONE: + setup = 0; + break; + default: + fatal("bad imsg %d", imsg.hdr.type); + } + imsg_free(&imsg); + } + + if (imsg_compose(ibuf, IMSG_SETUP_DONE, 0, 0, -1, NULL, 0) == -1) + fatal("imsg_compose"); + + if (imsg_flush(ibuf) == -1) + fatal("imsg_flush"); + + log_debug("setup_proc: %s done", proc_title(smtpd_process)); +} + +static struct mproc * +setup_peer(enum smtp_proc_type proc, pid_t pid, int sock) +{ + struct mproc *p, **pp; + + log_debug("setup_peer: %s -> %s[%u] fd=%d", proc_title(smtpd_process), + proc_title(proc), pid, sock); + + if (sock == -1) + fatalx("peer socket not received"); + + switch (proc) { + case PROC_LKA: + pp = &p_lka; + break; + case PROC_QUEUE: + pp = &p_queue; + break; + case PROC_CONTROL: + pp = &p_control; + break; + case PROC_SCHEDULER: + pp = &p_scheduler; + break; + case PROC_PONY: + pp = &p_pony; + break; + case PROC_CA: + pp = &p_ca; + break; + default: + fatalx("unknown peer"); + } + + if (*pp) + fatalx("peer already set"); + + p = calloc(1, sizeof(*p)); + if (p == NULL) + fatal("calloc"); + if((p->name = strdup(proc_title(proc))) == NULL) + fatal("strdup"); + mproc_init(p, sock); + p->pid = pid; + p->proc = proc; + p->handler = imsg_dispatch; + + *pp = p; + + return p; +} + +static int +imsg_wait(struct imsgbuf *ibuf, struct imsg *imsg, int timeout) +{ + struct pollfd pfd[1]; + ssize_t n; + + pfd[0].fd = ibuf->fd; + pfd[0].events = POLLIN; + + while (1) { + if ((n = imsg_get(ibuf, imsg)) == -1) + return -1; + if (n) + return 1; + + n = poll(pfd, 1, timeout); + if (n == -1) + return -1; + if (n == 0) { + errno = ETIMEDOUT; + return -1; + } + + if (((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) || n == 0) + return -1; + } +} + +int +smtpd(void) { + struct event ev_sigint; + struct event ev_sigterm; + struct event ev_sigchld; + struct event ev_sighup; + struct timeval tv; + + imsg_callback = parent_imsg; + + tree_init(&children); + + child_add(p_queue->pid, CHILD_DAEMON, proc_title(PROC_QUEUE)); + child_add(p_control->pid, CHILD_DAEMON, proc_title(PROC_CONTROL)); + child_add(p_lka->pid, CHILD_DAEMON, proc_title(PROC_LKA)); + child_add(p_scheduler->pid, CHILD_DAEMON, proc_title(PROC_SCHEDULER)); + child_add(p_pony->pid, CHILD_DAEMON, proc_title(PROC_PONY)); + child_add(p_ca->pid, CHILD_DAEMON, proc_title(PROC_CA)); + + event_init(); + + signal_set(&ev_sigint, SIGINT, parent_sig_handler, NULL); + signal_set(&ev_sigterm, SIGTERM, parent_sig_handler, NULL); + signal_set(&ev_sigchld, SIGCHLD, parent_sig_handler, NULL); + signal_set(&ev_sighup, SIGHUP, parent_sig_handler, NULL); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + signal_add(&ev_sigchld, NULL); + signal_add(&ev_sighup, NULL); + signal(SIGPIPE, SIG_IGN); + + config_peer(PROC_CONTROL); + config_peer(PROC_LKA); + config_peer(PROC_QUEUE); + config_peer(PROC_CA); + config_peer(PROC_PONY); + + evtimer_set(&config_ev, parent_send_config, NULL); + memset(&tv, 0, sizeof(tv)); + evtimer_add(&config_ev, &tv); + + /* defer offline scanning for a second */ + evtimer_set(&offline_ev, offline_scan, NULL); + offline_timeout.tv_sec = 1; + offline_timeout.tv_usec = 0; + evtimer_add(&offline_ev, &offline_timeout); + + if (pidfile(NULL) < 0) + err(1, "pidfile"); + + fork_filter_processes(); + + purge_task(); + +#if HAVE_PLEDGE + if (pledge("stdio rpath wpath cpath fattr tmppath " + "getpw sendfd proc exec id inet chown unix", NULL) == -1) + err(1, "pledge"); +#endif + + event_dispatch(); + fatalx("exited event loop"); + + return (0); +} + +static void +load_pki_tree(void) +{ + struct pki *pki; + struct ca *sca; + const char *k; + void *iter_dict; + + log_debug("debug: init ssl-tree"); + iter_dict = NULL; + while (dict_iter(env->sc_pki_dict, &iter_dict, &k, (void **)&pki)) { + log_debug("info: loading pki information for %s", k); + if (pki->pki_cert_file == NULL) + fatalx("load_pki_tree: missing certificate file"); + if (pki->pki_key_file == NULL) + fatalx("load_pki_tree: missing key file"); + + if (!ssl_load_certificate(pki, pki->pki_cert_file)) + fatalx("load_pki_tree: failed to load certificate file"); + } + + log_debug("debug: init ca-tree"); + iter_dict = NULL; + while (dict_iter(env->sc_ca_dict, &iter_dict, &k, (void **)&sca)) { + log_debug("info: loading CA information for %s", k); + if (!ssl_load_cafile(sca, sca->ca_cert_file)) + fatalx("load_pki_tree: failed to load CA file"); + } +} + +void +load_pki_keys(void) +{ + struct pki *pki; + const char *k; + void *iter_dict; + + log_debug("debug: init ssl-tree"); + iter_dict = NULL; + while (dict_iter(env->sc_pki_dict, &iter_dict, &k, (void **)&pki)) { + log_debug("info: loading pki keys for %s", k); + + if (!ssl_load_keyfile(pki, pki->pki_key_file, k)) + fatalx("load_pki_keys: failed to load key file"); + } +} + +int +fork_proc_backend(const char *key, const char *conf, const char *procname) +{ + pid_t pid; + int sp[2]; + char path[PATH_MAX]; + char name[PATH_MAX]; + char *arg; + + if (strlcpy(name, conf, sizeof(name)) >= sizeof(name)) { + log_warnx("warn: %s-proc: conf too long", key); + return (0); + } + + arg = strchr(name, ':'); + if (arg) + *arg++ = '\0'; + + if (snprintf(path, sizeof(path), PATH_LIBEXEC "/%s-%s", key, name) >= + (ssize_t)sizeof(path)) { + log_warn("warn: %s-proc: exec path too long", key); + return (-1); + } + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1) { + log_warn("warn: %s-proc: socketpair", key); + return (-1); + } + + if ((pid = fork()) == -1) { + log_warn("warn: %s-proc: fork", key); + close(sp[0]); + close(sp[1]); + return (-1); + } + + if (pid == 0) { + /* child process */ + dup2(sp[0], STDIN_FILENO); + closefrom(STDERR_FILENO + 1); + + if (procname == NULL) + procname = name; + + execl(path, procname, arg, (char *)NULL); + err(1, "execl: %s", path); + } + + /* parent process */ + close(sp[0]); + + return (sp[1]); +} + +struct child * +child_add(pid_t pid, int type, const char *title) +{ + struct child *child; + + if ((child = calloc(1, sizeof(*child))) == NULL) + fatal("smtpd: child_add: calloc"); + + child->pid = pid; + child->type = type; + child->title = title; + + tree_xset(&children, pid, child); + + return (child); +} + +static void +purge_task(void) +{ + struct passwd *pw; + DIR *d; + int n; + uid_t uid; + gid_t gid; + + n = 0; + if ((d = opendir(PATH_SPOOL PATH_PURGE))) { + while (readdir(d) != NULL) + n++; + closedir(d); + } else + log_warn("warn: purge_task: opendir"); + + if (n > 2) { + switch (purge_pid = fork()) { + case -1: + log_warn("warn: purge_task: fork"); + break; + case 0: + if ((pw = getpwnam(SMTPD_QUEUE_USER)) == NULL) + fatalx("unknown user " SMTPD_QUEUE_USER); + if (chroot(PATH_SPOOL PATH_PURGE) == -1) + fatal("smtpd: chroot"); + if (chdir("/") == -1) + fatal("smtpd: chdir"); + uid = pw->pw_uid; + gid = pw->pw_gid; + if (setgroups(1, &gid) || + setresgid(gid, gid, gid) || + setresuid(uid, uid, uid)) + fatal("smtpd: cannot drop privileges"); + rmtree("/", 1); + _exit(0); + break; + default: + break; + } + } +} + +static void +fork_filter_processes(void) +{ + const char *name; + void *iter; + const char *fn; + struct filter_config *fc; + struct filter_config *fcs; + struct filter_proc *fp; + size_t i; + + /* For each filter chain, assign the registered subsystem to subfilters */ + iter = NULL; + while (dict_iter(env->sc_filters_dict, &iter, (const char **)&fn, (void **)&fc)) { + if (fc->chain) { + for (i = 0; i < fc->chain_size; ++i) { + fcs = dict_xget(env->sc_filters_dict, fc->chain[i]); + fcs->filter_subsystem |= fc->filter_subsystem; + } + } + } + + /* For each filter, assign the registered subsystem to underlying proc */ + iter = NULL; + while (dict_iter(env->sc_filters_dict, &iter, (const char **)&fn, (void **)&fc)) { + if (fc->proc) { + fp = dict_xget(env->sc_filter_processes_dict, fc->proc); + fp->filter_subsystem |= fc->filter_subsystem; + } + } + + iter = NULL; + while (dict_iter(env->sc_filter_processes_dict, &iter, &name, (void **)&fp)) + fork_filter_process(name, fp->command, fp->user, fp->group, fp->chroot, fp->filter_subsystem); +} + +static void +fork_filter_process(const char *name, const char *command, const char *user, const char *group, const char *chroot_path, uint32_t subsystems) +{ + pid_t pid; + struct filter_proc *processor; + char buf; + int sp[2], errfd[2]; + struct passwd *pw; + struct group *gr; + char exec[_POSIX_ARG_MAX]; + int execr; + + if (user == NULL) + user = SMTPD_USER; + if ((pw = getpwnam(user)) == NULL) + err(1, "getpwnam"); + + if (group) { + if ((gr = getgrnam(group)) == NULL) + err(1, "getgrnam"); + } + else { + if ((gr = getgrgid(pw->pw_gid)) == NULL) + err(1, "getgrgid"); + } + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sp) == -1) + err(1, "socketpair"); + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, errfd) == -1) + err(1, "socketpair"); + + if ((pid = fork()) == -1) + err(1, "fork"); + + /* parent passes the child fd over to lka */ + if (pid > 0) { + processor = dict_xget(env->sc_filter_processes_dict, name); + processor->errfd = errfd[1]; + child_add(pid, CHILD_PROCESSOR, name); + close(sp[0]); + close(errfd[0]); + m_create(p_lka, IMSG_LKA_PROCESSOR_FORK, 0, 0, sp[1]); + m_add_string(p_lka, name); + m_add_u32(p_lka, (uint32_t)subsystems); + m_close(p_lka); + return; + } + + close(sp[1]); + close(errfd[1]); + dup2(sp[0], STDIN_FILENO); + dup2(sp[0], STDOUT_FILENO); + dup2(errfd[0], STDERR_FILENO); + + if (chroot_path) { + if (chroot(chroot_path) != 0 || chdir("/") != 0) + err(1, "chroot: %s", chroot_path); + } + + if (setgroups(1, &gr->gr_gid) || + setresgid(gr->gr_gid, gr->gr_gid, gr->gr_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + err(1, "fork_filter_process: cannot drop privileges"); + + xclosefrom(STDERR_FILENO + 1); + + if (setsid() < 0) + err(1, "setsid"); + if (signal(SIGPIPE, SIG_DFL) == SIG_ERR || + signal(SIGINT, SIG_DFL) == SIG_ERR || + signal(SIGTERM, SIG_DFL) == SIG_ERR || + signal(SIGCHLD, SIG_DFL) == SIG_ERR || + signal(SIGHUP, SIG_DFL) == SIG_ERR) + err(1, "signal"); + + if (command[0] == '/') + execr = snprintf(exec, sizeof(exec), "exec %s", command); + else + execr = snprintf(exec, sizeof(exec), "exec %s/%s", + PATH_LIBEXEC, command); + if (execr >= (int) sizeof(exec)) + errx(1, "%s: exec path too long", name); + + /* + * Wait for lka to acknowledge that it received the fd. + * This prevents a race condition between the filter sending an error + * message, and exiting and lka not being able to log it because of + * SIGCHLD. + * (Ab)use read to determine if the fd is installed; since stderr is + * never going to be read from we can shutdown(2) the write-end in lka. + */ + if (read(STDERR_FILENO, &buf, 1) != 0) + errx(1, "lka didn't properly close write end of error socket"); + if (system(exec) == -1) + err(1, NULL); + + /* there's no successful exit from a processor */ + _exit(1); +} + +static void +forkmda(struct mproc *p, uint64_t id, struct deliver *deliver) +{ + char ebuf[128], sfn[32]; + struct dispatcher *dsp; + struct child *child; + pid_t pid; + int allout, pipefd[2]; + struct passwd *pw; + const char *pw_name; + uid_t pw_uid; + gid_t pw_gid; + const char *pw_dir; + + dsp = dict_xget(env->sc_dispatchers, deliver->dispatcher); + + log_debug("debug: smtpd: forking mda for session %016"PRIx64 + ": %s as %s", id, deliver->userinfo.username, + dsp->u.local.user ? dsp->u.local.user : deliver->userinfo.username); + + if (dsp->u.local.user) { + if ((pw = getpwnam(dsp->u.local.user)) == NULL) { + (void)snprintf(ebuf, sizeof ebuf, + "delivery user '%s' does not exist", + dsp->u.local.user); + m_create(p_pony, IMSG_MDA_DONE, 0, 0, -1); + m_add_id(p_pony, id); + m_add_int(p_pony, MDA_PERMFAIL); + m_add_int(p_pony, EX_NOUSER); + m_add_string(p_pony, ebuf); + m_close(p_pony); + return; + } + pw_name = pw->pw_name; + pw_uid = pw->pw_uid; + pw_gid = pw->pw_gid; + pw_dir = pw->pw_dir; + } + else { + pw_name = deliver->userinfo.username; + pw_uid = deliver->userinfo.uid; + pw_gid = deliver->userinfo.gid; + pw_dir = deliver->userinfo.directory; + } + + if (pw_uid == 0 && deliver->mda_exec[0]) { + pw_name = deliver->userinfo.username; + pw_uid = deliver->userinfo.uid; + pw_gid = deliver->userinfo.gid; + pw_dir = deliver->userinfo.directory; + } + + if (pw_uid == 0 && !dsp->u.local.is_mbox) { + (void)snprintf(ebuf, sizeof ebuf, "not allowed to deliver to: %s", + deliver->userinfo.username); + m_create(p_pony, IMSG_MDA_DONE, 0, 0, -1); + m_add_id(p_pony, id); + m_add_int(p_pony, MDA_PERMFAIL); + m_add_int(p_pony, EX_NOPERM); + m_add_string(p_pony, ebuf); + m_close(p_pony); + return; + } + + if (pipe(pipefd) == -1) { + (void)snprintf(ebuf, sizeof ebuf, "pipe: %s", strerror(errno)); + m_create(p_pony, IMSG_MDA_DONE, 0, 0, -1); + m_add_id(p_pony, id); + m_add_int(p_pony, MDA_TEMPFAIL); + m_add_int(p_pony, EX_OSERR); + m_add_string(p_pony, ebuf); + m_close(p_pony); + return; + } + + /* prepare file which captures stdout and stderr */ + (void)strlcpy(sfn, "/tmp/smtpd.out.XXXXXXXXXXX", sizeof(sfn)); + allout = mkstemp(sfn); + if (allout == -1) { + (void)snprintf(ebuf, sizeof ebuf, "mkstemp: %s", strerror(errno)); + m_create(p_pony, IMSG_MDA_DONE, 0, 0, -1); + m_add_id(p_pony, id); + m_add_int(p_pony, MDA_TEMPFAIL); + m_add_int(p_pony, EX_OSERR); + m_add_string(p_pony, ebuf); + m_close(p_pony); + close(pipefd[0]); + close(pipefd[1]); + return; + } + unlink(sfn); + + pid = fork(); + if (pid == -1) { + (void)snprintf(ebuf, sizeof ebuf, "fork: %s", strerror(errno)); + m_create(p_pony, IMSG_MDA_DONE, 0, 0, -1); + m_add_id(p_pony, id); + m_add_int(p_pony, MDA_TEMPFAIL); + m_add_int(p_pony, EX_OSERR); + m_add_string(p_pony, ebuf); + m_close(p_pony); + close(pipefd[0]); + close(pipefd[1]); + close(allout); + return; + } + + /* parent passes the child fd over to mda */ + if (pid > 0) { + child = child_add(pid, CHILD_MDA, NULL); + child->mda_out = allout; + child->mda_id = id; + close(pipefd[0]); + m_create(p, IMSG_MDA_FORK, 0, 0, pipefd[1]); + m_add_id(p, id); + m_close(p); + return; + } + + /* mbox helper, create mailbox before privdrop if it doesn't exist */ + if (dsp->u.local.is_mbox) + mda_mbox_init(deliver); + + if (chdir(pw_dir) == -1 && chdir("/") == -1) + err(1, "chdir"); + if (setgroups(1, &pw_gid) || + setresgid(pw_gid, pw_gid, pw_gid) || + setresuid(pw_uid, pw_uid, pw_uid)) + err(1, "forkmda: cannot drop privileges"); + if (dup2(pipefd[0], STDIN_FILENO) == -1 || + dup2(allout, STDOUT_FILENO) == -1 || + dup2(allout, STDERR_FILENO) == -1) + err(1, "forkmda: dup2"); + closefrom(STDERR_FILENO + 1); + if (setsid() < 0) + err(1, "setsid"); + if (signal(SIGPIPE, SIG_DFL) == SIG_ERR || + signal(SIGINT, SIG_DFL) == SIG_ERR || + signal(SIGTERM, SIG_DFL) == SIG_ERR || + signal(SIGCHLD, SIG_DFL) == SIG_ERR || + signal(SIGHUP, SIG_DFL) == SIG_ERR) + err(1, "signal"); + + /* avoid hangs by setting 5m timeout */ + alarm(300); + + if (dsp->u.local.is_mbox && + dsp->u.local.mda_wrapper == NULL && + deliver->mda_exec[0] == '\0') + mda_mbox(deliver); + else + mda_unpriv(dsp, deliver, pw_name, pw_dir); +} + +static void +offline_scan(int fd, short ev, void *arg) +{ + char *path_argv[2]; + FTS *fts = arg; + FTSENT *e; + int n = 0; + + path_argv[0] = PATH_SPOOL PATH_OFFLINE; + path_argv[1] = NULL; + + if (fts == NULL) { + log_debug("debug: smtpd: scanning offline queue..."); + fts = fts_open(path_argv, FTS_PHYSICAL | FTS_NOCHDIR, NULL); + if (fts == NULL) { + log_warn("fts_open: %s", path_argv[0]); + return; + } + } + + while ((e = fts_read(fts)) != NULL) { + if (e->fts_info != FTS_F) + continue; + + /* offline files must be at depth 1 */ + if (e->fts_level != 1) + continue; + + /* offline file group must match parent directory group */ + if (e->fts_statp->st_gid != e->fts_parent->fts_statp->st_gid) + continue; + + if (e->fts_statp->st_size == 0) { + if (unlink(e->fts_accpath) == -1) + log_warnx("warn: smtpd: could not unlink %s", e->fts_accpath); + continue; + } + + if (offline_add(e->fts_name, e->fts_statp->st_uid, + e->fts_statp->st_gid)) { + log_warnx("warn: smtpd: " + "could not add offline message %s", e->fts_name); + continue; + } + + if ((n++) == OFFLINE_READMAX) { + evtimer_set(&offline_ev, offline_scan, fts); + offline_timeout.tv_sec = 0; + offline_timeout.tv_usec = 100000; + evtimer_add(&offline_ev, &offline_timeout); + return; + } + } + + log_debug("debug: smtpd: offline scanning done"); + fts_close(fts); +} + +static int +offline_enqueue(char *name, uid_t uid, gid_t gid) +{ + char *path; + struct stat sb; + pid_t pid; + struct child *child; + struct passwd *pw; + int pathlen; + + pathlen = asprintf(&path, "%s/%s", PATH_SPOOL PATH_OFFLINE, name); + if (pathlen == -1) { + log_warnx("warn: smtpd: asprintf"); + return (-1); + } + + if (pathlen >= PATH_MAX) { + log_warnx("warn: smtpd: pathname exceeds PATH_MAX"); + free(path); + return (-1); + } + + log_debug("debug: smtpd: enqueueing offline message %s", path); + + if ((pid = fork()) == -1) { + log_warn("warn: smtpd: fork"); + free(path); + return (-1); + } + + if (pid == 0) { + char *envp[2], *p = NULL, *tmp; + int fd; + FILE *fp; + size_t sz = 0; + ssize_t len; + arglist args; + + closefrom(STDERR_FILENO + 1); + + memset(&args, 0, sizeof(args)); + + if ((fd = open(path, O_RDONLY|O_NOFOLLOW|O_NONBLOCK)) == -1) { + log_warn("warn: smtpd: open: %s", path); + _exit(1); + } + + if (fstat(fd, &sb) == -1) { + log_warn("warn: smtpd: fstat: %s", path); + _exit(1); + } + + if (!S_ISREG(sb.st_mode)) { + log_warnx("warn: smtpd: file %s (uid %d) not regular", + path, sb.st_uid); + _exit(1); + } + + if (sb.st_nlink != 1) { + log_warnx("warn: smtpd: file %s is hard-link", path); + _exit(1); + } + + if (sb.st_uid != uid) { + log_warnx("warn: smtpd: file %s has bad uid %d", + path, sb.st_uid); + _exit(1); + } + + if (sb.st_gid != gid) { + log_warnx("warn: smtpd: file %s has bad gid %d", + path, sb.st_gid); + _exit(1); + } + + pw = getpwuid(sb.st_uid); + if (pw == NULL) { + log_warnx("warn: smtpd: getpwuid for uid %d failed", + sb.st_uid); + _exit(1); + } + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + _exit(1); + + if ((fp = fdopen(fd, "r")) == NULL) + _exit(1); + + if (chdir(pw->pw_dir) == -1 && chdir("/") == -1) + _exit(1); + + if (setsid() == -1 || + signal(SIGPIPE, SIG_DFL) == SIG_ERR || + dup2(fileno(fp), STDIN_FILENO) == -1) + _exit(1); + + if ((len = getline(&p, &sz, fp)) == -1) + _exit(1); + + if (p[len - 1] != '\n') + _exit(1); + p[len - 1] = '\0'; + + addargs(&args, "%s", "sendmail"); + addargs(&args, "%s", "-S"); + + while ((tmp = strsep(&p, "|")) != NULL) + addargs(&args, "%s", tmp); + + free(p); + if (lseek(fileno(fp), len, SEEK_SET) == -1) + _exit(1); + + envp[0] = "PATH=" _PATH_DEFPATH; + envp[1] = (char *)NULL; + environ = envp; + + execvp(PATH_SMTPCTL, args.list); + _exit(1); + } + + offline_running++; + child = child_add(pid, CHILD_ENQUEUE_OFFLINE, NULL); + child->path = path; + + return (0); +} + +static int +offline_add(char *path, uid_t uid, gid_t gid) +{ + struct offline *q; + + if (offline_running < OFFLINE_QUEUEMAX) + /* skip queue */ + return offline_enqueue(path, uid, gid); + + q = malloc(sizeof(*q) + strlen(path) + 1); + if (q == NULL) + return (-1); + q->uid = uid; + q->gid = gid; + q->path = (char *)q + sizeof(*q); + memmove(q->path, path, strlen(path) + 1); + TAILQ_INSERT_TAIL(&offline_q, q, entry); + + return (0); +} + +static void +offline_done(void) +{ + struct offline *q; + + offline_running--; + + while (offline_running < OFFLINE_QUEUEMAX) { + if ((q = TAILQ_FIRST(&offline_q)) == NULL) + break; /* all done */ + TAILQ_REMOVE(&offline_q, q, entry); + offline_enqueue(q->path, q->uid, q->gid); + free(q); + } +} + +static int +parent_forward_open(char *username, char *directory, uid_t uid, gid_t gid) +{ + char pathname[PATH_MAX]; + int fd; + struct stat sb; + + if (!bsnprintf(pathname, sizeof (pathname), "%s/.forward", + directory)) { + log_warnx("warn: smtpd: %s: pathname too large", pathname); + return -1; + } + + if (stat(directory, &sb) == -1) { + log_warn("warn: smtpd: parent_forward_open: %s", directory); + return -1; + } + if (sb.st_mode & S_ISVTX) { + log_warnx("warn: smtpd: parent_forward_open: %s is sticky", + directory); + errno = EAGAIN; + return -1; + } + + do { + fd = open(pathname, O_RDONLY|O_NOFOLLOW|O_NONBLOCK); + } while (fd == -1 && errno == EINTR); + if (fd == -1) { + if (errno == ENOENT) + return -1; + if (errno == EMFILE || errno == ENFILE || errno == EIO) { + errno = EAGAIN; + return -1; + } + if (errno == ELOOP) + log_warnx("warn: smtpd: parent_forward_open: %s: " + "cannot follow symbolic links", pathname); + else + log_warn("warn: smtpd: parent_forward_open: %s", pathname); + return -1; + } + + if (!secure_file(fd, pathname, directory, uid, 1)) { + log_warnx("warn: smtpd: %s: unsecure file", pathname); + close(fd); + return -1; + } + + return fd; +} + +void +imsg_dispatch(struct mproc *p, struct imsg *imsg) +{ + struct timespec t0, t1, dt; + int msg; + + if (imsg == NULL) { + imsg_callback(p, imsg); + return; + } + + log_imsg(smtpd_process, p->proc, imsg); + + if (profiling & PROFILE_IMSG) + clock_gettime(CLOCK_MONOTONIC, &t0); + + msg = imsg->hdr.type; + imsg_callback(p, imsg); + + if (profiling & PROFILE_IMSG) { + clock_gettime(CLOCK_MONOTONIC, &t1); + timespecsub(&t1, &t0, &dt); + + log_debug("profile-imsg: %s %s %s %d %lld.%09ld", + proc_name(smtpd_process), + proc_name(p->proc), + imsg_to_str(msg), + (int)imsg->hdr.len, + (long long)dt.tv_sec, + dt.tv_nsec); + + if (profiling & PROFILE_TOSTAT) { + char key[STAT_KEY_SIZE]; + /* can't profstat control process yet */ + if (smtpd_process == PROC_CONTROL) + return; + + if (!bsnprintf(key, sizeof key, + "profiling.imsg.%s.%s.%s", + proc_name(smtpd_process), + proc_name(p->proc), + imsg_to_str(msg))) + return; + stat_set(key, stat_timespec(&dt)); + } + } +} + +void +log_imsg(int to, int from, struct imsg *imsg) +{ + + if (to == PROC_CONTROL && imsg->hdr.type == IMSG_STAT_SET) + return; + + if (imsg->fd != -1) + log_trace(TRACE_IMSG, "imsg: %s <- %s: %s (len=%zu, fd=%d)", + proc_name(to), + proc_name(from), + imsg_to_str(imsg->hdr.type), + imsg->hdr.len - IMSG_HEADER_SIZE, + imsg->fd); + else + log_trace(TRACE_IMSG, "imsg: %s <- %s: %s (len=%zu)", + proc_name(to), + proc_name(from), + imsg_to_str(imsg->hdr.type), + imsg->hdr.len - IMSG_HEADER_SIZE); +} + +const char * +proc_title(enum smtp_proc_type proc) +{ + switch (proc) { + case PROC_PARENT: + return "[priv]"; + case PROC_LKA: + return "lookup"; + case PROC_QUEUE: + return "queue"; + case PROC_CONTROL: + return "control"; + case PROC_SCHEDULER: + return "scheduler"; + case PROC_PONY: + return "pony express"; + case PROC_CA: + return "klondike"; + case PROC_CLIENT: + return "client"; + case PROC_PROCESSOR: + return "processor"; + } + return "unknown"; +} + +const char * +proc_name(enum smtp_proc_type proc) +{ + switch (proc) { + case PROC_PARENT: + return "parent"; + case PROC_LKA: + return "lka"; + case PROC_QUEUE: + return "queue"; + case PROC_CONTROL: + return "control"; + case PROC_SCHEDULER: + return "scheduler"; + case PROC_PONY: + return "pony"; + case PROC_CA: + return "ca"; + case PROC_CLIENT: + return "client-proc"; + default: + return "unknown"; + } +} + +#define CASE(x) case x : return #x + +const char * +imsg_to_str(int type) +{ + static char buf[32]; + + switch (type) { + CASE(IMSG_NONE); + + CASE(IMSG_CTL_OK); + CASE(IMSG_CTL_FAIL); + + CASE(IMSG_CTL_GET_DIGEST); + CASE(IMSG_CTL_GET_STATS); + CASE(IMSG_CTL_LIST_MESSAGES); + CASE(IMSG_CTL_LIST_ENVELOPES); + CASE(IMSG_CTL_MTA_SHOW_HOSTS); + CASE(IMSG_CTL_MTA_SHOW_RELAYS); + CASE(IMSG_CTL_MTA_SHOW_ROUTES); + CASE(IMSG_CTL_MTA_SHOW_HOSTSTATS); + CASE(IMSG_CTL_MTA_BLOCK); + CASE(IMSG_CTL_MTA_UNBLOCK); + CASE(IMSG_CTL_MTA_SHOW_BLOCK); + CASE(IMSG_CTL_PAUSE_EVP); + CASE(IMSG_CTL_PAUSE_MDA); + CASE(IMSG_CTL_PAUSE_MTA); + CASE(IMSG_CTL_PAUSE_SMTP); + CASE(IMSG_CTL_PROFILE); + CASE(IMSG_CTL_PROFILE_DISABLE); + CASE(IMSG_CTL_PROFILE_ENABLE); + CASE(IMSG_CTL_RESUME_EVP); + CASE(IMSG_CTL_RESUME_MDA); + CASE(IMSG_CTL_RESUME_MTA); + CASE(IMSG_CTL_RESUME_SMTP); + CASE(IMSG_CTL_RESUME_ROUTE); + CASE(IMSG_CTL_REMOVE); + CASE(IMSG_CTL_SCHEDULE); + CASE(IMSG_CTL_SHOW_STATUS); + CASE(IMSG_CTL_TRACE_DISABLE); + CASE(IMSG_CTL_TRACE_ENABLE); + CASE(IMSG_CTL_UPDATE_TABLE); + CASE(IMSG_CTL_VERBOSE); + CASE(IMSG_CTL_DISCOVER_EVPID); + CASE(IMSG_CTL_DISCOVER_MSGID); + + CASE(IMSG_CTL_SMTP_SESSION); + + CASE(IMSG_GETADDRINFO); + CASE(IMSG_GETADDRINFO_END); + CASE(IMSG_GETNAMEINFO); + CASE(IMSG_RES_QUERY); + + CASE(IMSG_CERT_INIT); + CASE(IMSG_CERT_CERTIFICATE); + CASE(IMSG_CERT_VERIFY); + + CASE(IMSG_SETUP_KEY); + CASE(IMSG_SETUP_PEER); + CASE(IMSG_SETUP_DONE); + + CASE(IMSG_CONF_START); + CASE(IMSG_CONF_END); + + CASE(IMSG_STAT_INCREMENT); + CASE(IMSG_STAT_DECREMENT); + CASE(IMSG_STAT_SET); + + CASE(IMSG_LKA_AUTHENTICATE); + CASE(IMSG_LKA_OPEN_FORWARD); + CASE(IMSG_LKA_ENVELOPE_SUBMIT); + CASE(IMSG_LKA_ENVELOPE_COMMIT); + + CASE(IMSG_QUEUE_DELIVER); + CASE(IMSG_QUEUE_DELIVERY_OK); + CASE(IMSG_QUEUE_DELIVERY_TEMPFAIL); + CASE(IMSG_QUEUE_DELIVERY_PERMFAIL); + CASE(IMSG_QUEUE_DELIVERY_LOOP); + CASE(IMSG_QUEUE_DISCOVER_EVPID); + CASE(IMSG_QUEUE_DISCOVER_MSGID); + CASE(IMSG_QUEUE_ENVELOPE_ACK); + CASE(IMSG_QUEUE_ENVELOPE_COMMIT); + CASE(IMSG_QUEUE_ENVELOPE_REMOVE); + CASE(IMSG_QUEUE_ENVELOPE_SCHEDULE); + CASE(IMSG_QUEUE_ENVELOPE_SUBMIT); + CASE(IMSG_QUEUE_HOLDQ_HOLD); + CASE(IMSG_QUEUE_HOLDQ_RELEASE); + CASE(IMSG_QUEUE_MESSAGE_COMMIT); + CASE(IMSG_QUEUE_MESSAGE_ROLLBACK); + CASE(IMSG_QUEUE_SMTP_SESSION); + CASE(IMSG_QUEUE_TRANSFER); + + CASE(IMSG_MDA_DELIVERY_OK); + CASE(IMSG_MDA_DELIVERY_TEMPFAIL); + CASE(IMSG_MDA_DELIVERY_PERMFAIL); + CASE(IMSG_MDA_DELIVERY_LOOP); + CASE(IMSG_MDA_DELIVERY_HOLD); + CASE(IMSG_MDA_DONE); + CASE(IMSG_MDA_FORK); + CASE(IMSG_MDA_HOLDQ_RELEASE); + CASE(IMSG_MDA_LOOKUP_USERINFO); + CASE(IMSG_MDA_KILL); + CASE(IMSG_MDA_OPEN_MESSAGE); + + CASE(IMSG_MTA_DELIVERY_OK); + CASE(IMSG_MTA_DELIVERY_TEMPFAIL); + CASE(IMSG_MTA_DELIVERY_PERMFAIL); + CASE(IMSG_MTA_DELIVERY_LOOP); + CASE(IMSG_MTA_DELIVERY_HOLD); + CASE(IMSG_MTA_DNS_HOST); + CASE(IMSG_MTA_DNS_HOST_END); + CASE(IMSG_MTA_DNS_MX); + CASE(IMSG_MTA_DNS_MX_PREFERENCE); + CASE(IMSG_MTA_HOLDQ_RELEASE); + CASE(IMSG_MTA_LOOKUP_CREDENTIALS); + CASE(IMSG_MTA_LOOKUP_SOURCE); + CASE(IMSG_MTA_LOOKUP_HELO); + CASE(IMSG_MTA_LOOKUP_SMARTHOST); + CASE(IMSG_MTA_OPEN_MESSAGE); + CASE(IMSG_MTA_SCHEDULE); + + CASE(IMSG_SCHED_ENVELOPE_BOUNCE); + CASE(IMSG_SCHED_ENVELOPE_DELIVER); + CASE(IMSG_SCHED_ENVELOPE_EXPIRE); + CASE(IMSG_SCHED_ENVELOPE_INJECT); + CASE(IMSG_SCHED_ENVELOPE_REMOVE); + CASE(IMSG_SCHED_ENVELOPE_TRANSFER); + + CASE(IMSG_SMTP_AUTHENTICATE); + CASE(IMSG_SMTP_MESSAGE_COMMIT); + CASE(IMSG_SMTP_MESSAGE_CREATE); + CASE(IMSG_SMTP_MESSAGE_ROLLBACK); + CASE(IMSG_SMTP_MESSAGE_OPEN); + CASE(IMSG_SMTP_CHECK_SENDER); + CASE(IMSG_SMTP_EXPAND_RCPT); + CASE(IMSG_SMTP_LOOKUP_HELO); + + CASE(IMSG_SMTP_REQ_CONNECT); + CASE(IMSG_SMTP_REQ_HELO); + CASE(IMSG_SMTP_REQ_MAIL); + CASE(IMSG_SMTP_REQ_RCPT); + CASE(IMSG_SMTP_REQ_DATA); + CASE(IMSG_SMTP_REQ_EOM); + CASE(IMSG_SMTP_EVENT_RSET); + CASE(IMSG_SMTP_EVENT_COMMIT); + CASE(IMSG_SMTP_EVENT_ROLLBACK); + CASE(IMSG_SMTP_EVENT_DISCONNECT); + + CASE(IMSG_LKA_PROCESSOR_FORK); + CASE(IMSG_LKA_PROCESSOR_ERRFD); + + CASE(IMSG_REPORT_SMTP_LINK_CONNECT); + CASE(IMSG_REPORT_SMTP_LINK_DISCONNECT); + CASE(IMSG_REPORT_SMTP_LINK_TLS); + CASE(IMSG_REPORT_SMTP_LINK_GREETING); + CASE(IMSG_REPORT_SMTP_LINK_IDENTIFY); + CASE(IMSG_REPORT_SMTP_LINK_AUTH); + + CASE(IMSG_REPORT_SMTP_TX_RESET); + CASE(IMSG_REPORT_SMTP_TX_BEGIN); + CASE(IMSG_REPORT_SMTP_TX_ENVELOPE); + CASE(IMSG_REPORT_SMTP_TX_COMMIT); + CASE(IMSG_REPORT_SMTP_TX_ROLLBACK); + + CASE(IMSG_REPORT_SMTP_PROTOCOL_CLIENT); + CASE(IMSG_REPORT_SMTP_PROTOCOL_SERVER); + + CASE(IMSG_FILTER_SMTP_BEGIN); + CASE(IMSG_FILTER_SMTP_END); + CASE(IMSG_FILTER_SMTP_PROTOCOL); + CASE(IMSG_FILTER_SMTP_DATA_BEGIN); + CASE(IMSG_FILTER_SMTP_DATA_END); + + CASE(IMSG_CA_RSA_PRIVENC); + CASE(IMSG_CA_RSA_PRIVDEC); + CASE(IMSG_CA_ECDSA_SIGN); + default: + (void)snprintf(buf, sizeof(buf), "IMSG_??? (%d)", type); + + return buf; + } +} + +#ifdef BSD_AUTH +int +parent_auth_bsd(const char *username, const char *password) +{ + char user[LOGIN_NAME_MAX]; + char pass[LINE_MAX]; + int ret; + + (void)strlcpy(user, username, sizeof(user)); + (void)strlcpy(pass, password, sizeof(pass)); + + ret = auth_userokay(user, NULL, "auth-smtp", pass); + if (ret) + return LKA_OK; + return LKA_PERMFAIL; +} +#endif + +#ifdef USE_PAM +int +pam_conv_password(int num_msg, const struct pam_message **msg, + struct pam_response **respp, void *password) +{ + struct pam_response *response; + + if (num_msg != 1) + return PAM_CONV_ERR; + + response = calloc(1, sizeof(struct pam_response)); + if (response == NULL || (response->resp = strdup(password)) == NULL) { + free(response); + return PAM_BUF_ERR; + } + + *respp = response; + return PAM_SUCCESS; +} +int +parent_auth_pam(const char *username, const char *password) +{ + int rc; + pam_handle_t *pamh = NULL; + struct pam_conv conv = { pam_conv_password, (char *)password }; + + if ((rc = pam_start(USE_PAM_SERVICE, username, &conv, &pamh)) != PAM_SUCCESS) + goto end; + if ((rc = pam_authenticate(pamh, 0)) != PAM_SUCCESS) + goto end; + if ((rc = pam_acct_mgmt(pamh, 0)) != PAM_SUCCESS) + goto end; + +end: + pam_end(pamh, rc); + + switch (rc) { + case PAM_SUCCESS: + return LKA_OK; + case PAM_SYSTEM_ERR: + case PAM_ABORT: + case PAM_AUTHINFO_UNAVAIL: + return LKA_TEMPFAIL; + default: + return LKA_PERMFAIL; + } +} +#endif + +#ifdef HAVE_GETSPNAM +int +parent_auth_getspnam(const char *username, const char *password) +{ + struct spwd *pw; + char *ep; + + errno = 0; + do { + pw = getspnam(username); + } while (pw == NULL && errno == EINTR); + + if (pw == NULL) { + if (errno) + return LKA_TEMPFAIL; + return LKA_PERMFAIL; + } + + if ((ep = crypt(password, pw->sp_pwdp)) == NULL) + return LKA_PERMFAIL; + + if (strcmp(pw->sp_pwdp, ep) == 0) + return LKA_OK; + + return LKA_PERMFAIL; +} +#endif + +int +parent_auth_pwd(const char *username, const char *password) +{ + struct passwd *pw; + char *ep; + + errno = 0; + do { + pw = getpwnam(username); + } while (pw == NULL && errno == EINTR); + + if (pw == NULL) { + if (errno) + return LKA_TEMPFAIL; + return LKA_PERMFAIL; + } + + if ((ep = crypt(password, pw->pw_passwd)) == NULL) + return LKA_PERMFAIL; + + if (strcmp(pw->pw_passwd, ep) == 0) + return LKA_OK; + + return LKA_PERMFAIL; +} + +int +parent_auth_user(const char *username, const char *password) +{ +#if defined(BSD_AUTH) + return (parent_auth_bsd(username, password)); +#elif defined(USE_PAM) + return (parent_auth_pam(username, password)); +#elif defined(HAVE_GETSPNAM) + return (parent_auth_getspnam(username, password)); +#else + return (parent_auth_pwd(username, password)); +#endif +} diff --git a/foobar/portable/smtpd/smtpd.conf b/foobar/portable/smtpd/smtpd.conf new file mode 100644 index 00000000..a7ba6c64 --- /dev/null +++ b/foobar/portable/smtpd/smtpd.conf @@ -0,0 +1,19 @@ +# $OpenBSD: smtpd.conf,v 1.10 2018/05/24 11:40:17 gilles Exp $ + +# This is the smtpd server system-wide configuration file. +# See smtpd.conf(5) for more information. + +table aliases file:/etc/mail/aliases + +# To accept external mail, replace with: listen on all +# +listen on localhost + +action "local" maildir alias <aliases> +action "relay" relay + +# Uncomment the following to accept external mail for domain "example.org" +# +# match from any for domain "example.org" action "local" +match for local action "local" +match from local for any action "relay" diff --git a/foobar/portable/smtpd/smtpd.conf.5 b/foobar/portable/smtpd/smtpd.conf.5 new file mode 100644 index 00000000..c543c662 --- /dev/null +++ b/foobar/portable/smtpd/smtpd.conf.5 @@ -0,0 +1,1240 @@ +.\" $OpenBSD: smtpd.conf.5,v 1.250 2020/04/25 09:20:38 eric Exp $ +.\" +.\" Copyright (c) 2008 Janne Johansson <jj@openbsd.org> +.\" Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> +.\" Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.\" +.Dd $Mdocdate: April 25 2020 $ +.Dt SMTPD.CONF 5 +.Os +.Sh NAME +.Nm smtpd.conf +.Nd Simple Mail Transfer Protocol daemon configuration file +.Sh DESCRIPTION +.Nm +is the configuration file for the mail daemon +.Xr smtpd 8 . +.Pp +When mail arrives, +each +.Dq RCPT TO: +command generates a mail envelope. +If an envelope matches +any of a pre-designated set of criteria +(using the +.Ic match +directive), +the message is accepted for delivery. +A copy of the message, as well as its associated envelopes, +is saved in the mail queue and later dispatched +according to an associated set of actions +(using the +.Ic action +directive). +If an envelope does not match any options, +it is rejected. +The match rules are evaluated sequentially, +with the first match winning. +.Pp +The format of the configuration file is fairly flexible. +The current line can be extended over multiple lines using a backslash +.Pq Sq \e . +Comments can be put anywhere in the file using a hash mark +.Pq Sq # , +and extend to the end of the current line. +Care should be taken when commenting out multi-line text: +the comment is effective until the end of the entire block. +Argument names not beginning with a letter, digit, or underscore, +as well as reserved words +(such as +.Ic listen , +.Ic match , +and +.Cm port ) , +must be quoted. +Arguments containing whitespace should be surrounded by double quotes +.Pq \&" . +.Pp +Macros can be defined that are later expanded in context. +Macro names must start with a letter, digit, or underscore, +and may contain any of those characters, +but may not be reserved words. +Macros are not expanded inside quotes. +For example: +.Bd -literal -offset indent +lan_addr = "192.168.0.1" +listen on $lan_addr +listen on $lan_addr tls auth +.Ed +.Pp +The syntax of +.Nm +is described below. +.Bl -tag -width Ds +.It Ic action Ar name method Op Ar options +When the queue runner processes an envelope from the mail queue, +it carries out the +.Ic action +.Ar name , +selected by the +.Ic match No ... Cm action +directive when the message was received. +The +.Ic action +directive provides configuration data for delivery attempts. +Required lookups are performed at the time of each delivery attempt. +Consequently, changing an +.Ic action +directive or the files it references and restarting the +.Xr smtpd 8 +daemon causes the changes to take effect for subsequent delivery +attempts for the respective dispatcher +.Ar name , +even for messages that were already stuck in the queue +prior to the configuration changes. +.Pp +The delivery +.Ar method +parameter may be one of the following: +.Bl -tag -width Ds +.It Cm expand-only +Only accept the message if a delivery method was specified +in an aliases or +.Pa .forward +file. +.It Cm forward-only +Only accept the message if the recipient results in a remote address +after the processing of aliases or forward file. +.It Cm lmtp Ar destination Op Ar rcpt-to +Deliver the message to an LMTP server at +.Ar destination . +The location may be expressed as host:port or as a UNIX socket. +.Pp +Optionally, +.Ar rcpt-to +might be specified to use the +recipient email address (after expansion) instead of the +local user in the LMTP session as RCPT TO. +.It Cm maildir Op Ar pathname Op Cm junk +Deliver the message to the maildir in +.Ar pathname +if specified, or by default to +.Pa ~/Maildir . +.Pp +The +.Ar pathname +may contain format specifiers that are expanded before use +.Pq see Sx FORMAT SPECIFIERS . +.Pp +If the +.Cm junk +argument is provided, the message will be moved to the +.Ql Junk +folder if it contains a positive +.Ql X-Spam +header. +This folder will be created under +.Ar pathname +if it does not yet exist. +.It Cm mbox +Deliver the message to the user's mbox with +.Xr mail.local 8 . +.It Cm mda Ar command +Delegate the delivery to a +.Ar command +that receives the message on its standard input. +.Pp +The +.Ar command +may contain format specifiers that are expanded before use +.Pq see Sx FORMAT SPECIFIERS . +.It Cm relay +Relay the message to another SMTP server. +.El +.Pp +The local delivery methods support additional options: +.Bl -tag -width Ds +.It Cm alias Pf < Ar table Ns > +Use the mapping +.Ar table +for +.Xr aliases 5 +expansion. +.It Xo +.Cm ttl +.Sm off +.Ar n +.Brq Cm s | m | h | d +.Sm on +.Xc +Specify how long a message may remain in the queue. +.It Cm user Ar username +Specify the +.Ar username +for performing the delivery, to be looked up with +.Xr getpwnam 3 . +.Pp +This is used for virtual hosting where a single username +is in charge of handling delivery for all virtual users. +.Pp +This option is not usable with the +.Cm mbox +delivery method. +.It Cm userbase Pf < Ar table Ns > +Use the mapping +.Ar table +for user lookups instead of the +.Xr getpwnam 3 +function. +.Pp +The +.Cm userbase +does not apply for the +.Cm user +option. +.It Cm virtual Pf < Ar table Ns > +Use the mapping +.Ar table +for virtual expansion. +The aliasing table format is described in +.Xr table 5 . +.It Cm wrapper Ar name +Use the wrapper specified in +.Cm mda wrapper . +.El +.Pp +The relay delivery methods also support additional options: +.Bl -tag -width Ds +.It Cm backup +Operate as a backup mail exchanger delivering messages to any mail exchanger +with higher priority. +.It Cm backup mx Ar name +Operate as a backup mail exchanger delivering messages to any mail exchanger +with higher priority than mail exchanger identified as +.Ar name . +.It Cm helo Ar heloname +Advertise +.Ar heloname +as the hostname to other mail exchangers during the HELO phase. +.It Cm helo-src Pf < Ar table Ns > +Use the mapping +.Ar table +to look up a hostname matching the source address, +to advertise during the HELO phase. +.It Cm domain Pf < Ar domains Ns > +Do not perform MX lookups but look up destination domain in +.Ar domains +and use matching relay url as relay host. +.It Cm host Ar relay-url +Do not perform MX lookups but relay messages to the relay host described by +.Ar relay-url . +The format for +.Ar relay-url +is +.Sm off +.Op Ar proto No :// Op Ar label No @ +.Ar host Op : Ar port . +.Sm on +The following protocols are available: +.Pp +.Bl -tag -width "smtp+notls" -compact +.It smtp +Normal SMTP session with opportunistic STARTTLS +(the default). +.It smtp+tls +Normal SMTP session with mandatory STARTTLS. +.It smtp+notls +Plain text SMTP session without TLS. +.It lmtp +LMTP session. +.Ar port +is required. +.It smtps +SMTP session with forced TLS on connection, default port is 465. +.El +Unless noted, +.Ar port +defaults to 25. +.Pp +The +.Ar label +corresponds to an entry in a credentials table, +as documented in +.Xr table 5 . +It is used with the +.Dq smtp+tls +and +.Dq smtps +protocols for authentication. +Server certificates for those protocols are verified by default. +.It Cm srs +When relaying a mail resulting from a forward, +use the Sender Rewriting Scheme to rewrite sender address. +.It Cm tls Op Cm no-verify +Require TLS to be used when relaying, using mandatory STARTTLS by default. +When used with a smarthost, the protocol must not be +.Dq smtp+notls:// . +If +.Cm no-verify +is specified, do not require a valid certificate. +.It Cm auth Pf < Ar table Ns > +Use the mapping +.Ar table +for connecting to +.Ar relay-url +using credentials. +This option is usable only with +.Cm host +option. +The credential table format is described in +.Xr table 5 . +.It Cm mail-from Ar mailaddr +Use +.Ar mailaddr +as the MAIL FROM address within the SMTP transaction. +.It Cm src Ar sourceaddr | Pf < Ar sourceaddr Ns > +Use the string or list table +.Ar sourceaddr +for the source IP address, +which is useful on machines with multiple interfaces. +If the list contains more than one address, all of them are used +in such a way that traffic is routed as efficiently as possible. +.El +.It Ic bounce Cm warn-interval Ar delay Op , Ar delay ... +Send warning messages to the envelope sender when temporary delivery +failures cause a message to remain on the queue for longer than +.Ar delay . +Each +.Ar delay +parameter consists of a positive decimal integer and a unit +.Cm s , m , h , +or +.Cm d . +At most four +.Ar delay +parameters can be specified. +The default is +.Qq Ic bounce Cm warn-interval No 4h , +sending a single warning after four hours. +.It Ic ca Ar caname Cm cert Ar cafile +Associate the Certificate Authority (CA) certificate file +.Ar cafile +with host +.Ar caname , +and use that file as the CA certificate for that host. +.Ar caname +is the server's name, +derived from the default hostname +or set using either +.Pa /etc/mail/mailname +or using the +.Ic hostname +directive. +.It Ic filter Ar chain-name Ic chain Brq Ar filter-name Op , Ar ... +Register a chain of filters +.Ar chain-name , +consisting of the filters listed from +.Ar filter-name . +Filters part of a filter chain are executed in order of declaration for +each phase that they are registered for. +A filter chain may be used in place of a filter for any directive but +filter chains themselves. +.It Ic filter Ar filter-name Ic phase Ar phase-name Ic match Ar conditions decision +Register a filter +.Ar filter-name . +A +.Ar decision +about what to do with the mail is taken at phase +.Ar phase-name +when matching +.Ar conditions . +Phases, matching conditions, and decisions are described in +.Sx MAIL FILTERING , +below. +.It Ic filter Ar filter-name Ic proc Ar proc-name +Register +.Qq proc +filter +.Ar filter-name +backed by the +.Ar proc-name +process. +.It Ic filter Ar filter-name Ic proc-exec Ar command +Register and execute +.Qq proc +filter +.Ar filter-name +from +.Ar command . +If +.Ar command +starts with a slash it is executed with an absolute path, +else it will be run from +.Dq /usr/local/libexec/smtpd/ . +.It Ic include Qq Ar pathname +Replace this directive with the content of the additional configuration +file at the absolute +.Ar pathname . +.It Ic listen on Ar interface Oo Ar family Oc Op Ar options +Listen on the +.Ar interface +for incoming connections, using the same syntax as for +.Xr ifconfig 8 . +The +.Ar interface +parameter may also be an interface group, an IP address, or a domain name. +Listening can optionally be restricted to a specific address +.Ar family , +which can be either +.Cm inet4 +or +.Cm inet6 . +.Pp +The +.Ar options +are as follows: +.Bl -tag -width Ds +.It Cm auth Op Pf < Ar authtable Ns > +Support SMTPAUTH: clients may only start SMTP transactions +after successful authentication. +Users are authenticated against either their own normal login credentials +or a credentials table +.Ar authtable , +the format of which is described in +.Xr table 5 . +.It Cm auth-optional Op Pf < Ar authtable Ns > +Support SMTPAUTH optionally: +clients need not authenticate, but may do so. +This allows a +.Ic listen on +directive to both accept incoming mail from untrusted senders +and permit outgoing mail from authenticated users +(using +.Cm match auth ) . +It can be used in situations where it is not possible to listen on a separate port +(usually the submission port, 587) +for users to authenticate. +.It Ic ca Ar caname +For secure connections, +use the CA certificate associated with +.Ar caname +(declared in a +.Ic ca +directive) +as the CA certificate when verifying client certificates. +.It Ic filter Ar name +Apply filter +.Ar name +on connections handled by this listener. +.It Cm hostname Ar hostname +Use +.Ar hostname +in the greeting banner instead of the default server name. +.It Cm hostnames Pf < Ar names Ns > +Override the server name for specific addresses. +The +.Ar names +table contains a mapping of IP addresses to hostnames. +If the address on which the connection arrives appears in the mapping, +the associated hostname is used. +.It Cm mask-src +Omit the +.Sy from +part when prepending +.Dq Received +headers. +.It Cm no-dsn +Disable the DSN (Delivery Status Notification) extension. +.It Cm pki Ar pkiname +For secure connections, +use the certificate associated with +.Ar pkiname +(declared in a +.Ic pki +directive) +to prove a mail server's identity. +.It Cm port Op Ar port +Listen on the given +.Ar port +instead of the default port 25. +.It Cm proxy-v2 +Support the PROXYv2 protocol, +rewriting appropriately source address received from proxy. +.It Cm received-auth +In +.Dq Received +headers, report whether the session was authenticated +and by which local user. +.It Cm senders Pf < Ar users Ns > Op Cm masquerade +Look up the authenticated user in the +.Ar users +mapping table to find the email addresses that user is allowed +to submit mail as. +In addition, if the +.Cm masquerade +option is provided, +the From header is rewritten +to match the sender provided in the SMTP session. +.It Cm smtps +Support SMTPS, by default on port 465. +Mutually exclusive with +.Cm tls . +.It Cm tag Ar tag +Clients connecting to the listener are tagged with the given +.Ar tag . +.It Cm tls +Support STARTTLS, by default on port 25. +Mutually exclusive with +.Cm smtps . +.It Cm tls-require Op Cm verify +Like +.Cm tls , +but force clients to establish a secure connection +before being allowed to start an SMTP transaction. +With the +.Cm verify +option, clients must also provide a valid certificate +to establish an SMTP session. +.El +.It Ic listen on Cm socket Op Ar options +Listen for incoming SMTP connections on the Unix domain socket +.Pa /var/run/smtpd.sock . +This is done by default, even if the directive is absent. +.Pp +The +.Ar options +are as follows: +.Bl -tag -width Ds +.It Ic filter Ar name +Apply filter +.Ar name +on connections handled by this listener. +.It Cm mask-src +Omit the +.Sy from +part when prepending +.Dq Received +headers. +.It Cm tag Ar tag +Clients connecting to the listener are tagged with the given +.Ar tag . +.El +.It Ic match Ar options Cm action Ar name +If at least one mail envelope matches the +.Ar options +of one +.Ic match Cm action +directive, receive the incoming message, put a copy into each +matching envelope, and atomically save the envelopes to the mail +spool for later processing by the respective dispatcher +.Ar name . +.Pp +The following matching options are supported and can all be negated: +.Bl -tag -width Ds +.It Xo +.Op Ic \&! +.Cm for any +.Xc +Specify that session may address any destination. +.It Xo +.Op Ic \&! +.Cm for local +.Xc +Specify that session may address any local domain. +This is the default, and may be omitted. +.It Xo +.Op Ic \&! +.Cm for domain +.Ar domain | Pf < Ar domain Ns > +.Xc +Specify that session may address the string or list table +.Ar domain . +.It Xo +.Op Ic \&! +.Cm for domain regex +.Ar domain | Pf < Ar domain Ns > +.Xc +Specify that session may address the regex or regex table +.Ar domain . +.It Xo +.Op Ic \&! +.Cm for rcpt-to +.Ar recipient | Pf < Ar recipient Ns > +.Xc +Specify that session may address the string or list table +.Ar recipient . +.It Xo +.Op Ic \&! +.Cm for rcpt-to regex +.Ar recipient | Pf < Ar recipient Ns > +.Xc +Specify that session may address the regex or regex table +.Ar recipient . +.It Xo +.Op Ic \&! +.Cm from any +.Xc +Specify that session may originate from any source. +.It Xo +.Op Ic \&! +.Cm from auth +.Xc +Specify that session may originate from any authenticated user, +no matter the source IP address. +.It Xo +.Op Ic \&! +.Cm from auth +.Ar user | Pf < Ar user Ns > +.Xc +Specify that session may originate from authenticated user or user list +.Ar user , +no matter the source IP address. +.It Xo +.Op Ic \&! +.Cm from auth +.Ar user | Pf < Ar user Ns > +.Xc +Specify that session may originate from authenticated regex or regex list +.Ar user , +no matter the source IP address. +.It Xo +.Op Ic \&! +.Cm from local +.Xc +Specify that session may only originate from a local IP address, +or from the local enqueuer. +This is the default, and may be omitted. +.It Xo +.Op Ic \&! +.Cm from mail-from +.Ar sender | Pf < Ar sender Ns > +.Xc +Specify that session may originate from sender or sender list +.Ar sender , +no matter the source IP address. +.It Xo +.Op Ic \&! +.Cm from mail-from regex +.Ar sender | Pf < Ar sender Ns > +.Xc +Specify that session may originate from regex or regex list +.Ar sender , +no matter the source IP address. +.It Xo +.Op Ic \&! +.Cm from rdns +.Xc +Specify that session may only originate from an IP address that +resolves to a reverse DNS. +.It Xo +.Op Ic \&! +.Cm from rdns +.Ar hostname | Pf < Ar hostname Ns > +.Xc +Specify that session may only originate from an IP address that +resolves to a reverse DNS matching string or list string +.Ar hostname . +.It Xo +.Op Ic \&! +.Cm from rdns regex +.Ar hostname | Pf < Ar hostname Ns > +.Xc +Specify that session may only originate from an IP address that +resolves to a reverse DNS matching regex or list regex +.Ar hostname . +.It Xo +.Op Ic \&! +.Cm from socket +.Xc +Specify that session may only originate from the local enqueuer. +.It Xo +.Op Ic \&! +.Cm from src +.Ar address | Pf < Ar address Ns > +.Xc +Specify that session may only originate from string or list table +.Ar address +which can be a specific address or a subnet expressed in CIDR-notation. +.It Xo +.Op Ic \&! +.Cm from src regex +.Ar address | Pf < Ar address Ns > +.Xc +Specify that session may only originate from regex or regex table +.Ar address +which can be a specific address or a subnet expressed in CIDR-notation. +.El +.Pp +In addition, the following transaction options: +.Bl -tag -width Ds +.It Xo +.Op Ic \&! +.Cm auth +.Xc +Matches transactions which have been authenticated. +.It Xo +.Op Ic \&! +.Cm auth +.Ar username | Pf < Ar username Ns > +.Xc +Matches transactions which have been authenticated for user or user list +.Ar username . +.It Xo +.Op Ic \&! +.Cm auth regex +.Ar username | Pf < Ar username Ns > +.Xc +Matches transactions which have been authenticated for regex or regex list +.Ar username . +.It Xo +.Op Ic \&! +.Cm helo +.Ar helo-name | Pf < Ar helo-name Ns > +.Xc +Specify that session's HELO / EHLO should match the string or list table +.Ar helo-name . +.It Xo +.Op Ic \&! +.Cm helo regex +.Ar helo-name | Pf < Ar helo-name Ns > +.Xc +Specify that session's HELO / EHLO should match the regex or regex table +.Ar helo-name . +.It Xo +.Op Ic \&! +.Cm mail-from +.Ar sender | Pf < Ar sender Ns > +.Xc +Specify that transactions's MAIL FROM should match the string or list table +.Ar sender . +.It Xo +.Op Ic \&! +.Cm mail-from regex +.Ar sender | Pf < Ar sender Ns > +.Xc +Specify that transactions's MAIL FROM should match the regex or regex table +.Ar sender . +.It Xo +.Op Ic \&! +.Cm rcpt-to +.Ar recipient | Pf < Ar recipient Ns > +.Xc +Specify that transaction's RCPT TO should match the string or list table +.Ar recipient . +.It Xo +.Op Ic \&! +.Cm rcpt-to regex +.Ar recipient | Pf < Ar recipient Ns > +.Xc +Specify that transaction's RCPT TO should match the regex or regex table +.Ar recipient . +.It Xo +.Op Ic \&! +.Cm tag Ar tag +.Xc +Matches transactions tagged with the given +.Ar tag . +.It Xo +.Op Ic \&! +.Cm tag regex Ar tag +.Xc +Matches transactions tagged with the given +.Ar tag +regex. +.It Xo +.Op Ic \&! +.Cm tls +.Xc +Specify that transaction should take place in a TLS channel. +.El +.It Ic match Ar options Cm reject +Reject the incoming message during the SMTP dialogue. +The same +.Ar options +are supported as for the +.Ic match Cm action +directive. +.It Ic mda Cm wrapper Ar name command +Associate +.Ar command +with the mail delivery agent wrapper named +.Ar name . +When a local delivery specifies a wrapper, the +.Ar command +associated with the wrapper will be executed instead. +The command may contain format specifiers +.Pq see Sx FORMAT SPECIFIERS . +.It Ic mta Cm max-deferred Ar number +When delivery to a given host is suspended due to temporary failures, +cache at most +.Ar number +envelopes for that host such that they can be delivered +as soon as another delivery succeeds to that host. +The default is 100. +.It Ic pki Ar pkiname Cm cert Ar certfile +Associate certificate file +.Ar certfile +with host +.Ar pkiname , +and use that file to prove the identity of the mail server to clients. +.Ar pkiname +is the server's name, +derived from the default hostname +or set using either +.Pa /etc/mail/mailname +or using the +.Ic hostname +directive. +If a fallback certificate or SNI is wanted, the +.Sq * +wildcard may be used as +.Ar pkiname . +.Pp +A certificate chain may be created by appending one or many certificates, +including a Certificate Authority certificate, +to +.Ar certfile . +The creation of certificates is documented in +.Xr starttls 8 . +.It Ic pki Ar pkiname Cm key Ar keyfile +Associate the key located in +.Ar keyfile +with host +.Ar pkiname . +.It Ic pki Ar pkiname Cm dhe Ar params +Specify the DHE parameters to use for DHE cipher suites with host +.Ar pkiname . +Valid parameter values are +.Cm none , +.Cm legacy , +and +.Cm auto . +For +.Cm legacy , +a fixed key length of 1024 bits is used, whereas for +.Cm auto , +the key length is determined automatically. +The default is +.Cm none , +which disables DHE cipher suites. +.It Ic proc Ar proc-name Ar command +Register an external process named +.Ar proc-name +from +.Ar command . +Such processes may be used to share the same instance between multiple filters. +If +.Ar command +starts with a slash it is executed with an absolute path, +else it will be run from +.Dq /usr/local/libexec/smtpd/ . +.It Ic queue Cm compression +Store queue files in a compressed format. +This may be useful to save disk space. +.It Ic queue Cm encryption Op Ar key +Encrypt queue files with +.Xr EVP_aes_256_gcm 3 . +If no +.Ar key +is specified, it is read with +.Xr getpass 3 . +If the string +.Cm stdin +or a single dash +.Pq Ql - +is given instead of a +.Ar key , +the key is read from the standard input. +.It Ic queue Cm ttl Ar delay +Set the default expiration time for temporarily undeliverable +messages, given as a positive decimal integer followed by a unit +.Cm s , m , h , +or +.Cm d . +The default is four days +.Pq 4d . +.It Ic smtp Cm ciphers Ar control +Set the +.Ar control +string for +.Xr SSL_CTX_set_cipher_list 3 . +The default is +.Qq HIGH:!aNULL:!MD5 . +.It Ic smtp limit Cm max-mails Ar count +Limit the number of messages to +.Ar count +for each session. +The default is 100. +.It Ic smtp limit Cm max-rcpt Ar count +Limit the number of recipients to +.Ar count +for each transaction. +The default is 1000. +.It Ic smtp Cm max-message-size Ar size +Reject messages larger than +.Ar size , +given as a positive number of bytes or as a string to be parsed with +.Xr scan_scaled 3 . +The default is +.Qq 35M . +.It Ic smtp Cm sub-addr-delim Ar character +When resolving the local part of a local email address, ignore the ASCII +.Ar character +and all characters following it. +The default is +.Ql + . +.It Ic srs Cm key Ar secret +Set the secret key to use for SRS, +the Sender Rewriting Scheme. +.It Ic srs Cm key backup Ar secret +Set a backup secret key to use as a fallback for SRS. +This can be used to implement SRS key rotation. +.It Ic srs Cm ttl Ar delay +Set the time-to-live delay for SRS envelopes. +After this delay, +a bounce reply to the SRS address will be discarded to limit risks of forged addresses. +The default is four days +.Pq 4d . +.It Ic table Ar name Oo Ar type : Oc Ns Ar pathname +Tables provide additional configuration information for +.Xr smtpd 8 +in the form of lists or key-value mappings. +The format of the entries depends on what the table is used for. +Refer to +.Xr table 5 +for the exhaustive documentation. +.Pp +Each table is identified by an arbitrary, unique +.Ar name . +.Pp +If the +.Ar type +is +.Cm db , +information is stored in a file created with +.Xr makemap 8 ; +if it is +.Cm file +or omitted, information is stored in a plain text file +using the format described in +.Xr table 5 . +The +.Ar pathname +to the file must be absolute. +.It Ic table Ar name Brq Ar value Op , Ar ... +Instead of using a separate file, declare a list table +containing the given static +.Ar value Ns s . +The table must contain at least one value and may declare multiple values as a +comma-separated (whitespace optional) list. +.It Ic table Ar name Brq Ar key Ns = Ns Ar value Op , Ar ... +Instead of using a separate file, declare a mapping table +containing the given static +.Ar key Ns - Ns Ar value +pairs. +The table must contain at least one key-value pair and may declare +multiple pairs as a comma-separated (whitespace optional) list. +.El +.Ss MAIL FILTERING +In a regular workflow, +.Xr smtpd 8 +may accept or reject a message based only on the content of envelopes. +Its decisions are about the handling of the message, +not about the handling of an active session. +.Pp +Filtering extends the decision making process by allowing +.Xr smtpd 8 +to stop at each phase of an SMTP session, +check that conditions are met, +then decide if a session is allowed to move forward. +.Pp +With filtering, +a session may be interrupted at any phase before an envelope is complete. +A message may also be rejected after being submitted, +regardless of whether the envelope was accepted or not. +.Pp +The following phases are currently supported: +.Bl -column mail-from -offset indent +.It connect Ta upon connection, before a banner is displayed +.It helo Ta after HELO command is submitted +.It ehlo Ta after EHLO command is submitted +.It mail-from Ta after MAIL FROM command is submitted +.It rcpt-to Ta after RCPT TO command is submitted +.It data Ta after DATA command is submitted +.It commit Ta after message is fully is submitted +.El +.Pp +At each phase, various conditions may be matched. +The fcrdns, rdns, and src data are available in all phases, +but other data must have been already submitted before they are available. +.Bl -column XXXXXXXXXXXXXXXXXXXXX -offset indent +.It fcrdns Ta forward-confirmed reverse DNS is valid +.It rdns Ta session has a reverse DNS +.It rdns Pf < Ar table Ns > Ta session has a reverse DNS in table +.It src Pf < Ar table Ns > Ta source address is in table +.It helo Pf < Ar table Ns > Ta helo name is in table +.It auth Ta session is authenticated +.It auth Pf < Ar table Ns > Ta session username is in table +.It mail-from Pf < Ar table Ns > Ta sender address is in table +.It rcpt-to Pf < Ar table Ns > Ta recipient address is in table +.El +.Pp +These conditions may all be negated by prefixing them with an exclamation mark: +.Bl -column XXXXXXXXXXXXXXXXXXXXX -offset indent +.It !fcrdns Ta forward-confirmed reverse DNS is invalid +.El +.Pp +Any conditions using a table may indicate that tables hold regex by +prefixing the table name with the keyword regex. +.Bl -column XXXXXXXXXXXXXXXXXXXXX -offset indent +.It helo regex Pf < Ar table Ns > Ta helo name matches a regex in table +.El +.Pp +Finally, a number of decisions may be taken: +.Bl -column XXXXXXXXXXXXXXXXXXXXX -offset indent +.It bypass Ta the session or transaction bypasses filters +.It disconnect Ar message Ta the session is disconnected with message +.It junk Ta the session or transaction is junked, i.e., an +.Ql X-Spam: yes +header is added to any messages +.It reject Ar message Ta the command is rejected with message +.It rewrite Ar value Ta the command parameter is rewritten with value +.El +.Pp +Decisions that involve a message require that the message be RFC valid, +meaning that they should either start with a 4xx or 5xx status code. +Descisions can be taken at any phase, +though junking can only happen before a message is committed. +.Ss FORMAT SPECIFIERS +Some configuration directives support expansion of their parameters at runtime. +Such directives (for example +.Ic action Cm maildir , +.Ic action Cm mda ) +may use format specifiers which are expanded before delivery or +relaying. +The following formats are currently supported: +.Bl -column %{user.directory} -offset indent +.It %{sender} Ta sender email address, may be empty string +.It %{sender.user} Ta user part of the sender email address, may be empty +.It %{sender.domain} Ta domain part of the sender email address, may be empty +.It %{rcpt} Ta recipient email address +.It %{rcpt.user} Ta user part of the recipient email address +.It %{rcpt.domain} Ta domain part of the recipient email address +.It %{dest} Ta recipient email address after expansion +.It %{dest.user} Ta user part after expansion +.It %{dest.domain} Ta domain part after expansion +.It %{user.username} Ta local user +.It %{user.directory} Ta home directory of the local user +.It %{mbox.from} Ta name used in mbox From separator lines +.It %{mda} Ta mda command, only available for mda wrappers +.El +.Pp +Expansion formats also support partial expansion using the optional +bracket notations with substring offset. +For example, with recipient domain +.Dq example.org : +.Bl -column %{rcpt.domain[0:-4]} -offset indent +.It %{rcpt.domain[0]} Ta expands to Dq e +.It %{rcpt.domain[1]} Ta expands to Dq x +.It %{rcpt.domain[8:]} Ta expands to Dq org +.It %{rcpt.domain[-3:]} Ta expands to Dq org +.It %{rcpt.domain[0:6]} Ta expands to Dq example +.It %{rcpt.domain[0:-4]} Ta expands to Dq example +.El +.Pp +In addition, modifiers may be applied to the token. +For example, with recipient +.Dq User+Tag@Example.org : +.Bl -column %{rcpt:lowercase|strip} -offset indent +.It %{rcpt:lowercase} Ta expands to Dq user+tag@example.org +.It %{rcpt:uppercase} Ta expands to Dq USER+TAG@EXAMPLE.ORG +.It %{rcpt:strip} Ta expands to Dq User@Example.org +.It %{rcpt:lowercase|strip} Ta expands to Dq user@example.org +.El +.Pp +For security concerns, expanded values are sanitized and potentially +dangerous characters are replaced with +.Sq \&: . +In situations where they are desirable, the +.Dq raw +modifier may be applied. +For example, with recipient +.Dq user+t?g@example.org : +.Bl -column %{rcpt:raw} -offset indent +.It %{rcpt} Ta expands to Dq user+t:g@example.org +.It %{rcpt:raw} Ta expands to Dq user+t?g@example.org +.El +.Sh FILES +.Bl -tag -width "/etc/mail/smtpd.confXXX" -compact +.It Pa /etc/mail/smtpd.conf +Default +.Xr smtpd 8 +configuration file. +.It Pa /etc/mail/mailname +If this file exists, +the first line is used as the server name. +Otherwise, the server name is derived from the local hostname returned by +.Xr gethostname 3 , +either directly if it is a fully qualified domain name, +or by retrieving the associated canonical name through +.Xr getaddrinfo 3 . +.It Pa /var/run/smtpd.sock +Unix domain socket for incoming SMTP connections. +.It Pa /var/spool/smtpd/ +Spool directories for mail during processing. +.El +.Sh EXAMPLES +The default +.Nm +file which ships with +.Ox +listens on the loopback network interface +.Pq Pa lo0 +and allows for mail from users and daemons on the local machine, +as well as permitting email to remote servers. +Some more complex configurations are given below. +.Pp +This first example is the same as the default configuration, +but all outgoing mail is forwarded to a remote SMTP server. +A secrets file is needed to specify a username and password: +.Bd -literal -offset indent +# touch /etc/mail/secrets +# chmod 640 /etc/mail/secrets +# chown root:_smtpd /etc/mail/secrets +# echo "bob username:password" > /etc/mail/secrets +.Ed +.Pp +.Nm +would look like this: +.Bd -literal -offset indent +table aliases file:/etc/mail/aliases +table secrets file:/etc/mail/secrets + +listen on lo0 + +action "local_mail" mbox alias <aliases> +action "outbound" relay host smtp+tls://bob@smtp.example.com \e + auth <secrets> + +match from local for local action "local_mail" +match from local for any action "outbound" +.Ed +.Pp +In this second example, +the aim is to permit mail delivery and relaying only for users that can authenticate +(using their normal login credentials). +An RSA certificate must be provided to prove the server's identity. +The mail server listens on all interfaces the default routes point to. +Mail with a local destination is sent to an external MDA. +First, the RSA certificate is created: +.Bd -literal -offset indent +# openssl genrsa \-out /etc/ssl/private/mail.example.com.key 4096 +# openssl req \-new \-x509 \-key /etc/ssl/private/mail.example.com.key \e + \-out /etc/ssl/mail.example.com.crt \-days 365 +# chmod 600 /etc/ssl/mail.example.com.crt +# chmod 600 /etc/ssl/private/mail.example.com.key +.Ed +.Pp +In the example above, +a certificate valid for one year was created. +The configuration file would look like this: +.Bd -literal -offset indent +pki mail.example.com cert "/etc/ssl/mail.example.com.crt" +pki mail.example.com key "/etc/ssl/private/mail.example.com.key" + +table aliases file:/etc/mail/aliases + +listen on lo0 +listen on egress tls pki mail.example.com auth + +action mda_with_aliases mda "/path/to/mda \-f \-" alias <aliases> +action mda_without_aliases mda "/path/to/mda \-f \-" +action "outbound" relay + +match for local action mda_with_aliases +match from any for domain example.com action mda_without_aliases +match for any action "outbound" +match auth from any for any action "outbound" +.Ed +.Pp +For sites that wish to sign messages using DKIM, +the following example uses +.Sy opensmtpd-filter-dkimsign +for DKIM signing: +.Bd -literal -offset indent +table aliases file:/etc/mail/aliases + +filter "dkimsign" proc-exec "filter-dkimsign -d <domain> -s <selector> \e + -k /etc/mail/dkim/private.key" user _dkimsign group _dkimsign + +listen on socket filter "dkimsign" +listen on lo0 filter "dkimsign" + +action "local_mail" mbox alias <aliases> +action "outbound" relay + +match for local action "local_mail" +match for any action "outbound" +.Ed +.Pp +Alternatively, the +.Sy opensmtpd-filter-rspamd +package may be used to provide integration with +.Sy rspamd , +a third-party daemon which provides multiple antispam features +as well as DKIM signing. +As well as configuring +.Sy rspamd +itself, +it requires use of the +.Cm proc-exec +keyword: +.Bd -literal -offset indent +filter "rspamd" proc-exec "filter-rspamd" +.Ed +.Pp +Sites that accept non-local messages may be able to cut down on the +volume of spam received by rejecting forged messages that claim +to be from the local domain. +The following example uses a list table +.Em other-relays +to specify the IP addresses of relays that may legitimately +originate mail with the owner's domain as the sender. +.Bd -literal -offset indent +table aliases file:/etc/mail/aliases +table other-relays file:/etc/mail/other-relays + +listen on lo0 +listen on egress + +action "local_mail" mbox alias <aliases> +action "outbound" relay + +match for local action "local_mail" +match for any action "outbound" +match !from src <other-relays> mail\-from "@example.com" for any \e + reject +match from any for domain example.com action "local_mail" +.Ed +.Sh SEE ALSO +.Xr mailer.conf 5 , +.Xr table 5 , +.Xr makemap 8 , +.Xr smtpd 8 +.Sh HISTORY +.Xr smtpd 8 +first appeared in +.Ox 4.6 . diff --git a/foobar/portable/smtpd/smtpd.h b/foobar/portable/smtpd/smtpd.h new file mode 100644 index 00000000..4385f747 --- /dev/null +++ b/foobar/portable/smtpd/smtpd.h @@ -0,0 +1,1784 @@ +/* $OpenBSD: smtpd.h,v 1.656 2020/04/08 07:30:44 eric Exp $ */ + +/* + * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <event.h> + +#include <imsg.h> + +#include "openbsd-compat.h" + +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + +#include <netinet/in.h> +#include <netdb.h> +#include <event.h> + +#include "smtpd-defines.h" +#include "smtpd-api.h" +#include "ioev.h" + +#define CHECK_IMSG_DATA_SIZE(imsg, expected_sz) do { \ + if ((imsg)->hdr.len - IMSG_HEADER_SIZE != (expected_sz)) \ + fatalx("smtpd: imsg %d: data size expected %zd got %zd",\ + (imsg)->hdr.type, \ + (expected_sz), (imsg)->hdr.len - IMSG_HEADER_SIZE); \ +} while (0) + +#ifndef SMTPD_CONFDIR +#define SMTPD_CONFDIR "/etc" +#endif +#define CONF_FILE SMTPD_CONFDIR "/smtpd.conf" +#define MAILNAME_FILE SMTPD_CONFDIR "/mailname" +#ifndef CA_FILE +#define CA_FILE "/etc/ssl/cert.pem" +#endif + +#define PROC_COUNT 7 + +#define MAX_HOPS_COUNT 100 +#define DEFAULT_MAX_BODY_SIZE (35*1024*1024) + +#define EXPAND_BUFFER 1024 + +#define SMTPD_QUEUE_EXPIRY (4 * 24 * 60 * 60) +#ifndef SMTPD_USER +#define SMTPD_USER "_smtpd" +#endif +#ifndef SMTPD_QUEUE_USER +#define SMTPD_QUEUE_USER "_smtpq" +#endif +#ifndef SMTPD_SOCKDIR +#define SMTPD_SOCKDIR "/var/run" +#endif +#define SMTPD_SOCKET SMTPD_SOCKDIR "/smtpd.sock" +#ifndef SMTPD_NAME +#define SMTPD_NAME "OpenSMTPD" +#endif +#define SMTPD_VERSION "6.7.0-portable" +#define SMTPD_SESSION_TIMEOUT 300 +#define SMTPD_BACKLOG 5 + +#ifndef PATH_SMTPCTL +#define PATH_SMTPCTL "/usr/sbin/smtpctl" +#endif + +#define PATH_OFFLINE "/offline" +#define PATH_PURGE "/purge" +#define PATH_TEMPORARY "/temporary" + +#ifndef PATH_LIBEXEC +#define PATH_LIBEXEC "/usr/local/libexec/smtpd" +#endif + + +/* + * RFC 5322 defines these characters as valid, some of them are + * potentially dangerous and need to be escaped. + */ +#define MAILADDR_ALLOWED "!#$%&'*/?^`{|}~+-=_" +#define MAILADDR_ESCAPE "!#$%&'*?`{|}~" + + +#define F_STARTTLS 0x01 +#define F_SMTPS 0x02 +#define F_SSL (F_STARTTLS | F_SMTPS) +#define F_AUTH 0x08 +#define F_STARTTLS_REQUIRE 0x20 +#define F_AUTH_REQUIRE 0x40 +#define F_MASK_SOURCE 0x100 +#define F_TLS_VERIFY 0x200 +#define F_EXT_DSN 0x400 +#define F_RECEIVEDAUTH 0x800 +#define F_MASQUERADE 0x1000 +#define F_FILTERED 0x2000 +#define F_PROXY 0x4000 + +#define RELAY_TLS_OPPORTUNISTIC 0 +#define RELAY_TLS_STARTTLS 1 +#define RELAY_TLS_SMTPS 2 +#define RELAY_TLS_NO 3 + +#define RELAY_AUTH 0x08 +#define RELAY_LMTP 0x80 +#define RELAY_TLS_VERIFY 0x200 + +#define MTA_EXT_DSN 0x400 + + +#define P_SENDMAIL 0 +#define P_NEWALIASES 1 +#define P_MAKEMAP 2 + +#define CERT_ERROR -1 +#define CERT_OK 0 +#define CERT_NOCA 1 +#define CERT_NOCERT 2 +#define CERT_INVALID 3 + +struct userinfo { + char username[SMTPD_VUSERNAME_SIZE]; + char directory[PATH_MAX]; + uid_t uid; + gid_t gid; +}; + +struct netaddr { + struct sockaddr_storage ss; + int bits; +}; + +struct relayhost { + uint16_t flags; + int tls; + char hostname[HOST_NAME_MAX+1]; + uint16_t port; + char authlabel[PATH_MAX]; +}; + +struct credentials { + char username[LINE_MAX]; + char password[LINE_MAX]; +}; + +struct destination { + char name[HOST_NAME_MAX+1]; +}; + +struct source { + struct sockaddr_storage addr; +}; + +struct addrname { + struct sockaddr_storage addr; + char name[HOST_NAME_MAX+1]; +}; + +union lookup { + struct expand *expand; + struct credentials creds; + struct netaddr netaddr; + struct source source; + struct destination domain; + struct userinfo userinfo; + struct mailaddr mailaddr; + struct addrname addrname; + struct maddrmap *maddrmap; + char relayhost[LINE_MAX]; +}; + +/* + * Bump IMSG_VERSION whenever a change is made to enum imsg_type. + * This will ensure that we can never use a wrong version of smtpctl with smtpd. + */ +#define IMSG_VERSION 16 + +enum imsg_type { + IMSG_NONE, + + IMSG_CTL_OK, + IMSG_CTL_FAIL, + + IMSG_CTL_GET_DIGEST, + IMSG_CTL_GET_STATS, + IMSG_CTL_LIST_MESSAGES, + IMSG_CTL_LIST_ENVELOPES, + IMSG_CTL_MTA_SHOW_HOSTS, + IMSG_CTL_MTA_SHOW_RELAYS, + IMSG_CTL_MTA_SHOW_ROUTES, + IMSG_CTL_MTA_SHOW_HOSTSTATS, + IMSG_CTL_MTA_BLOCK, + IMSG_CTL_MTA_UNBLOCK, + IMSG_CTL_MTA_SHOW_BLOCK, + IMSG_CTL_PAUSE_EVP, + IMSG_CTL_PAUSE_MDA, + IMSG_CTL_PAUSE_MTA, + IMSG_CTL_PAUSE_SMTP, + IMSG_CTL_PROFILE, + IMSG_CTL_PROFILE_DISABLE, + IMSG_CTL_PROFILE_ENABLE, + IMSG_CTL_RESUME_EVP, + IMSG_CTL_RESUME_MDA, + IMSG_CTL_RESUME_MTA, + IMSG_CTL_RESUME_SMTP, + IMSG_CTL_RESUME_ROUTE, + IMSG_CTL_REMOVE, + IMSG_CTL_SCHEDULE, + IMSG_CTL_SHOW_STATUS, + IMSG_CTL_TRACE_DISABLE, + IMSG_CTL_TRACE_ENABLE, + IMSG_CTL_UPDATE_TABLE, + IMSG_CTL_VERBOSE, + IMSG_CTL_DISCOVER_EVPID, + IMSG_CTL_DISCOVER_MSGID, + + IMSG_CTL_SMTP_SESSION, + + IMSG_GETADDRINFO, + IMSG_GETADDRINFO_END, + IMSG_GETNAMEINFO, + IMSG_RES_QUERY, + + IMSG_CERT_INIT, + IMSG_CERT_CERTIFICATE, + IMSG_CERT_VERIFY, + + IMSG_SETUP_KEY, + IMSG_SETUP_PEER, + IMSG_SETUP_DONE, + + IMSG_CONF_START, + IMSG_CONF_END, + + IMSG_STAT_INCREMENT, + IMSG_STAT_DECREMENT, + IMSG_STAT_SET, + + IMSG_LKA_AUTHENTICATE, + IMSG_LKA_OPEN_FORWARD, + IMSG_LKA_ENVELOPE_SUBMIT, + IMSG_LKA_ENVELOPE_COMMIT, + + IMSG_QUEUE_DELIVER, + IMSG_QUEUE_DELIVERY_OK, + IMSG_QUEUE_DELIVERY_TEMPFAIL, + IMSG_QUEUE_DELIVERY_PERMFAIL, + IMSG_QUEUE_DELIVERY_LOOP, + IMSG_QUEUE_DISCOVER_EVPID, + IMSG_QUEUE_DISCOVER_MSGID, + IMSG_QUEUE_ENVELOPE_ACK, + IMSG_QUEUE_ENVELOPE_COMMIT, + IMSG_QUEUE_ENVELOPE_REMOVE, + IMSG_QUEUE_ENVELOPE_SCHEDULE, + IMSG_QUEUE_ENVELOPE_SUBMIT, + IMSG_QUEUE_HOLDQ_HOLD, + IMSG_QUEUE_HOLDQ_RELEASE, + IMSG_QUEUE_MESSAGE_COMMIT, + IMSG_QUEUE_MESSAGE_ROLLBACK, + IMSG_QUEUE_SMTP_SESSION, + IMSG_QUEUE_TRANSFER, + + IMSG_MDA_DELIVERY_OK, + IMSG_MDA_DELIVERY_TEMPFAIL, + IMSG_MDA_DELIVERY_PERMFAIL, + IMSG_MDA_DELIVERY_LOOP, + IMSG_MDA_DELIVERY_HOLD, + IMSG_MDA_DONE, + IMSG_MDA_FORK, + IMSG_MDA_HOLDQ_RELEASE, + IMSG_MDA_LOOKUP_USERINFO, + IMSG_MDA_KILL, + IMSG_MDA_OPEN_MESSAGE, + + IMSG_MTA_DELIVERY_OK, + IMSG_MTA_DELIVERY_TEMPFAIL, + IMSG_MTA_DELIVERY_PERMFAIL, + IMSG_MTA_DELIVERY_LOOP, + IMSG_MTA_DELIVERY_HOLD, + IMSG_MTA_DNS_HOST, + IMSG_MTA_DNS_HOST_END, + IMSG_MTA_DNS_MX, + IMSG_MTA_DNS_MX_PREFERENCE, + IMSG_MTA_HOLDQ_RELEASE, + IMSG_MTA_LOOKUP_CREDENTIALS, + IMSG_MTA_LOOKUP_SOURCE, + IMSG_MTA_LOOKUP_HELO, + IMSG_MTA_LOOKUP_SMARTHOST, + IMSG_MTA_OPEN_MESSAGE, + IMSG_MTA_SCHEDULE, + + IMSG_SCHED_ENVELOPE_BOUNCE, + IMSG_SCHED_ENVELOPE_DELIVER, + IMSG_SCHED_ENVELOPE_EXPIRE, + IMSG_SCHED_ENVELOPE_INJECT, + IMSG_SCHED_ENVELOPE_REMOVE, + IMSG_SCHED_ENVELOPE_TRANSFER, + + IMSG_SMTP_AUTHENTICATE, + IMSG_SMTP_MESSAGE_COMMIT, + IMSG_SMTP_MESSAGE_CREATE, + IMSG_SMTP_MESSAGE_ROLLBACK, + IMSG_SMTP_MESSAGE_OPEN, + IMSG_SMTP_CHECK_SENDER, + IMSG_SMTP_EXPAND_RCPT, + IMSG_SMTP_LOOKUP_HELO, + + IMSG_SMTP_REQ_CONNECT, + IMSG_SMTP_REQ_HELO, + IMSG_SMTP_REQ_MAIL, + IMSG_SMTP_REQ_RCPT, + IMSG_SMTP_REQ_DATA, + IMSG_SMTP_REQ_EOM, + IMSG_SMTP_EVENT_RSET, + IMSG_SMTP_EVENT_COMMIT, + IMSG_SMTP_EVENT_ROLLBACK, + IMSG_SMTP_EVENT_DISCONNECT, + + IMSG_LKA_PROCESSOR_FORK, + IMSG_LKA_PROCESSOR_ERRFD, + + IMSG_REPORT_SMTP_LINK_CONNECT, + IMSG_REPORT_SMTP_LINK_DISCONNECT, + IMSG_REPORT_SMTP_LINK_GREETING, + IMSG_REPORT_SMTP_LINK_IDENTIFY, + IMSG_REPORT_SMTP_LINK_TLS, + IMSG_REPORT_SMTP_LINK_AUTH, + IMSG_REPORT_SMTP_TX_RESET, + IMSG_REPORT_SMTP_TX_BEGIN, + IMSG_REPORT_SMTP_TX_MAIL, + IMSG_REPORT_SMTP_TX_RCPT, + IMSG_REPORT_SMTP_TX_ENVELOPE, + IMSG_REPORT_SMTP_TX_DATA, + IMSG_REPORT_SMTP_TX_COMMIT, + IMSG_REPORT_SMTP_TX_ROLLBACK, + IMSG_REPORT_SMTP_PROTOCOL_CLIENT, + IMSG_REPORT_SMTP_PROTOCOL_SERVER, + IMSG_REPORT_SMTP_FILTER_RESPONSE, + IMSG_REPORT_SMTP_TIMEOUT, + + IMSG_FILTER_SMTP_BEGIN, + IMSG_FILTER_SMTP_END, + IMSG_FILTER_SMTP_PROTOCOL, + IMSG_FILTER_SMTP_DATA_BEGIN, + IMSG_FILTER_SMTP_DATA_END, + + IMSG_CA_RSA_PRIVENC, + IMSG_CA_RSA_PRIVDEC, + IMSG_CA_ECDSA_SIGN, +}; + +enum smtp_proc_type { + PROC_PARENT = 0, + PROC_LKA, + PROC_QUEUE, + PROC_CONTROL, + PROC_SCHEDULER, + PROC_PONY, + PROC_CA, + PROC_PROCESSOR, + PROC_CLIENT, +}; + +enum table_type { + T_NONE = 0, + T_DYNAMIC = 0x01, /* table with external source */ + T_LIST = 0x02, /* table holding a list */ + T_HASH = 0x04, /* table holding a hash table */ +}; + +struct table { + char t_name[LINE_MAX]; + enum table_type t_type; + char t_config[PATH_MAX]; + + void *t_handle; + struct table_backend *t_backend; +}; + +struct table_backend { + const char *name; + const unsigned int services; + int (*config)(struct table *); + int (*add)(struct table *, const char *, const char *); + void (*dump)(struct table *); + int (*open)(struct table *); + int (*update)(struct table *); + void (*close)(struct table *); + int (*lookup)(struct table *, enum table_service, const char *, char **); + int (*fetch)(struct table *, enum table_service, char **); +}; + + +enum bounce_type { + B_FAILED, + B_DELAYED, + B_DELIVERED +}; + +enum dsn_ret { + DSN_RETFULL = 1, + DSN_RETHDRS +}; + +struct delivery_bounce { + enum bounce_type type; + time_t delay; + time_t ttl; + enum dsn_ret dsn_ret; + int mta_without_dsn; +}; + +enum expand_type { + EXPAND_INVALID, + EXPAND_USERNAME, + EXPAND_FILENAME, + EXPAND_FILTER, + EXPAND_INCLUDE, + EXPAND_ADDRESS, + EXPAND_ERROR, +}; + +enum filter_phase { + FILTER_CONNECT, + FILTER_HELO, + FILTER_EHLO, + FILTER_STARTTLS, + FILTER_AUTH, + FILTER_MAIL_FROM, + FILTER_RCPT_TO, + FILTER_DATA, + FILTER_DATA_LINE, + FILTER_RSET, + FILTER_QUIT, + FILTER_NOOP, + FILTER_HELP, + FILTER_WIZ, + FILTER_COMMIT, + FILTER_PHASES_COUNT /* must be last */ +}; + +struct expandnode { + RB_ENTRY(expandnode) entry; + TAILQ_ENTRY(expandnode) tq_entry; + enum expand_type type; + int sameuser; + int realuser; + int forwarded; + struct rule *rule; + struct expandnode *parent; + unsigned int depth; + union { + /* + * user field handles both expansion user and system user + * so we MUST make it large enough to fit a mailaddr user + */ + char user[SMTPD_MAXLOCALPARTSIZE]; + char buffer[EXPAND_BUFFER]; + struct mailaddr mailaddr; + } u; + char subaddress[SMTPD_SUBADDRESS_SIZE]; +}; + +struct expand { + RB_HEAD(expandtree, expandnode) tree; + TAILQ_HEAD(xnodes, expandnode) *queue; + size_t nb_nodes; + struct rule *rule; + struct expandnode *parent; +}; + +struct maddrnode { + TAILQ_ENTRY(maddrnode) entries; + struct mailaddr mailaddr; +}; + +struct maddrmap { + TAILQ_HEAD(xmaddr, maddrnode) queue; +}; + +#define DSN_SUCCESS 0x01 +#define DSN_FAILURE 0x02 +#define DSN_DELAY 0x04 +#define DSN_NEVER 0x08 + +#define DSN_ENVID_LEN 100 + +#define SMTPD_ENVELOPE_VERSION 3 +struct envelope { + TAILQ_ENTRY(envelope) entry; + + char dispatcher[HOST_NAME_MAX+1]; + + char tag[SMTPD_TAG_SIZE]; + + uint32_t version; + uint64_t id; + enum envelope_flags flags; + + char smtpname[HOST_NAME_MAX+1]; + char helo[HOST_NAME_MAX+1]; + char hostname[HOST_NAME_MAX+1]; + char username[SMTPD_MAXMAILADDRSIZE]; + char errorline[LINE_MAX]; + struct sockaddr_storage ss; + + struct mailaddr sender; + struct mailaddr rcpt; + struct mailaddr dest; + + char mda_user[SMTPD_VUSERNAME_SIZE]; + char mda_subaddress[SMTPD_SUBADDRESS_SIZE]; + char mda_exec[LINE_MAX]; + + enum delivery_type type; + union { + struct delivery_bounce bounce; + } agent; + + uint16_t retry; + time_t creation; + time_t ttl; + time_t lasttry; + time_t nexttry; + time_t lastbounce; + + struct mailaddr dsn_orcpt; + char dsn_envid[DSN_ENVID_LEN+1]; + uint8_t dsn_notify; + enum dsn_ret dsn_ret; + + uint8_t esc_class; + uint8_t esc_code; +}; + +struct listener { + uint16_t flags; + int fd; + struct sockaddr_storage ss; + in_port_t port; + struct timeval timeout; + struct event ev; + char filter_name[PATH_MAX]; + char pki_name[PATH_MAX]; + char ca_name[PATH_MAX]; + char tag[SMTPD_TAG_SIZE]; + char authtable[LINE_MAX]; + char hostname[HOST_NAME_MAX+1]; + char hostnametable[PATH_MAX]; + char sendertable[PATH_MAX]; + + TAILQ_ENTRY(listener) entry; + + int local; /* there must be a better way */ +}; + +struct smtpd { + char sc_conffile[PATH_MAX]; + size_t sc_maxsize; + +#define SMTPD_OPT_VERBOSE 0x00000001 +#define SMTPD_OPT_NOACTION 0x00000002 + uint32_t sc_opts; + +#define SMTPD_EXITING 0x00000001 /* unused */ +#define SMTPD_MDA_PAUSED 0x00000002 +#define SMTPD_MTA_PAUSED 0x00000004 +#define SMTPD_SMTP_PAUSED 0x00000008 +#define SMTPD_MDA_BUSY 0x00000010 +#define SMTPD_MTA_BUSY 0x00000020 +#define SMTPD_BOUNCE_BUSY 0x00000040 +#define SMTPD_SMTP_DISABLED 0x00000080 + uint32_t sc_flags; + +#define QUEUE_COMPRESSION 0x00000001 +#define QUEUE_ENCRYPTION 0x00000002 +#define QUEUE_EVPCACHE 0x00000004 + uint32_t sc_queue_flags; + char *sc_queue_key; + size_t sc_queue_evpcache_size; + + size_t sc_session_max_rcpt; + size_t sc_session_max_mails; + + struct dict *sc_mda_wrappers; + size_t sc_mda_max_session; + size_t sc_mda_max_user_session; + size_t sc_mda_task_hiwat; + size_t sc_mda_task_lowat; + size_t sc_mda_task_release; + + size_t sc_mta_max_deferred; + + size_t sc_scheduler_max_inflight; + size_t sc_scheduler_max_evp_batch_size; + size_t sc_scheduler_max_msg_batch_size; + size_t sc_scheduler_max_schedule; + + struct dict *sc_filter_processes_dict; + + int sc_ttl; +#define MAX_BOUNCE_WARN 4 + time_t sc_bounce_warn[MAX_BOUNCE_WARN]; + char sc_hostname[HOST_NAME_MAX+1]; + struct stat_backend *sc_stat; + struct compress_backend *sc_comp; + + time_t sc_uptime; + + /* This is a listener for a local socket used by smtp_enqueue(). */ + struct listener *sc_sock_listener; + + TAILQ_HEAD(listenerlist, listener) *sc_listeners; + + TAILQ_HEAD(rulelist, rule) *sc_rules; + + + struct dict *sc_filters_dict; + struct dict *sc_dispatchers; + struct dispatcher *sc_dispatcher_bounce; + + struct dict *sc_ca_dict; + struct dict *sc_pki_dict; + struct dict *sc_ssl_dict; + + struct dict *sc_tables_dict; /* keyed lookup */ + + struct dict *sc_limits_dict; + + char *sc_tls_ciphers; + + char *sc_subaddressing_delim; + + char *sc_srs_key; + char *sc_srs_key_backup; + int sc_srs_ttl; +}; + +#define TRACE_DEBUG 0x0001 +#define TRACE_IMSG 0x0002 +#define TRACE_IO 0x0004 +#define TRACE_SMTP 0x0008 +#define TRACE_FILTERS 0x0010 +#define TRACE_MTA 0x0020 +#define TRACE_BOUNCE 0x0040 +#define TRACE_SCHEDULER 0x0080 +#define TRACE_LOOKUP 0x0100 +#define TRACE_STAT 0x0200 +#define TRACE_RULES 0x0400 +#define TRACE_MPROC 0x0800 +#define TRACE_EXPAND 0x1000 +#define TRACE_TABLES 0x2000 +#define TRACE_QUEUE 0x4000 + +#define PROFILE_TOSTAT 0x0001 +#define PROFILE_IMSG 0x0002 +#define PROFILE_QUEUE 0x0004 + +struct forward_req { + uint64_t id; + uint8_t status; + + char user[SMTPD_VUSERNAME_SIZE]; + uid_t uid; + gid_t gid; + char directory[PATH_MAX]; +}; + +struct deliver { + char dispatcher[EXPAND_BUFFER]; + + struct mailaddr sender; + struct mailaddr rcpt; + struct mailaddr dest; + + char mda_subaddress[SMTPD_SUBADDRESS_SIZE]; + char mda_exec[LINE_MAX]; + + struct userinfo userinfo; +}; + +struct mta_host { + SPLAY_ENTRY(mta_host) entry; + struct sockaddr *sa; + char *ptrname; + int refcount; + size_t nconn; + time_t lastconn; + time_t lastptrquery; + +#define HOST_IGNORE 0x01 + int flags; +}; + +struct mta_mx { + TAILQ_ENTRY(mta_mx) entry; + struct mta_host *host; + char *mxname; + int preference; +}; + +struct mta_domain { + SPLAY_ENTRY(mta_domain) entry; + char *name; + int as_host; + TAILQ_HEAD(, mta_mx) mxs; + int mxstatus; + int refcount; + size_t nconn; + time_t lastconn; + time_t lastmxquery; +}; + +struct mta_source { + SPLAY_ENTRY(mta_source) entry; + struct sockaddr *sa; + int refcount; + size_t nconn; + time_t lastconn; +}; + +struct mta_connector { + struct mta_source *source; + struct mta_relay *relay; + +#define CONNECTOR_ERROR_FAMILY 0x0001 +#define CONNECTOR_ERROR_SOURCE 0x0002 +#define CONNECTOR_ERROR_MX 0x0004 +#define CONNECTOR_ERROR_ROUTE_NET 0x0008 +#define CONNECTOR_ERROR_ROUTE_SMTP 0x0010 +#define CONNECTOR_ERROR_ROUTE 0x0018 +#define CONNECTOR_ERROR_BLOCKED 0x0020 +#define CONNECTOR_ERROR 0x00ff + +#define CONNECTOR_LIMIT_HOST 0x0100 +#define CONNECTOR_LIMIT_ROUTE 0x0200 +#define CONNECTOR_LIMIT_SOURCE 0x0400 +#define CONNECTOR_LIMIT_RELAY 0x0800 +#define CONNECTOR_LIMIT_CONN 0x1000 +#define CONNECTOR_LIMIT_DOMAIN 0x2000 +#define CONNECTOR_LIMIT 0xff00 + +#define CONNECTOR_NEW 0x10000 +#define CONNECTOR_WAIT 0x20000 + int flags; + + int refcount; + size_t nconn; + time_t lastconn; +}; + +struct mta_route { + SPLAY_ENTRY(mta_route) entry; + uint64_t id; + struct mta_source *src; + struct mta_host *dst; +#define ROUTE_NEW 0x01 +#define ROUTE_RUNQ 0x02 +#define ROUTE_KEEPALIVE 0x04 +#define ROUTE_DISABLED 0xf0 +#define ROUTE_DISABLED_NET 0x10 +#define ROUTE_DISABLED_SMTP 0x20 + int flags; + int nerror; + int penalty; + int refcount; + size_t nconn; + time_t lastconn; + time_t lastdisc; + time_t lastpenalty; +}; + +struct mta_limits { + size_t maxconn_per_host; + size_t maxconn_per_route; + size_t maxconn_per_source; + size_t maxconn_per_connector; + size_t maxconn_per_relay; + size_t maxconn_per_domain; + + time_t conndelay_host; + time_t conndelay_route; + time_t conndelay_source; + time_t conndelay_connector; + time_t conndelay_relay; + time_t conndelay_domain; + + time_t discdelay_route; + + size_t max_mail_per_session; + time_t sessdelay_transaction; + time_t sessdelay_keepalive; + + size_t max_failures_per_session; + + int family; + + int task_hiwat; + int task_lowat; + int task_release; +}; + +struct mta_relay { + SPLAY_ENTRY(mta_relay) entry; + uint64_t id; + + struct dispatcher *dispatcher; + struct mta_domain *domain; + struct mta_limits *limits; + int tls; + int flags; + char *backupname; + int backuppref; + char *sourcetable; + uint16_t port; + char *pki_name; + char *ca_name; + char *authtable; + char *authlabel; + char *helotable; + char *heloname; + char *secret; + int srs; + + int state; + size_t ntask; + TAILQ_HEAD(, mta_task) tasks; + + struct tree connectors; + size_t sourceloop; + time_t lastsource; + time_t nextsource; + + int fail; + char *failstr; + +#define RELAY_WAIT_MX 0x01 +#define RELAY_WAIT_PREFERENCE 0x02 +#define RELAY_WAIT_SECRET 0x04 +#define RELAY_WAIT_LIMITS 0x08 +#define RELAY_WAIT_SOURCE 0x10 +#define RELAY_WAIT_CONNECTOR 0x20 +#define RELAY_WAIT_SMARTHOST 0x40 +#define RELAY_WAITMASK 0x7f + int status; + + int refcount; + size_t nconn; + size_t nconn_ready; + time_t lastconn; +}; + +struct mta_envelope { + TAILQ_ENTRY(mta_envelope) entry; + uint64_t id; + uint64_t session; + time_t creation; + char *smtpname; + char *dest; + char *rcpt; + struct mta_task *task; + int delivery; + + int ext; + char *dsn_orcpt; + char dsn_envid[DSN_ENVID_LEN+1]; + uint8_t dsn_notify; + enum dsn_ret dsn_ret; + + char status[LINE_MAX]; +}; + +struct mta_task { + TAILQ_ENTRY(mta_task) entry; + struct mta_relay *relay; + uint32_t msgid; + TAILQ_HEAD(, mta_envelope) envelopes; + char *sender; +}; + +struct passwd; + +struct queue_backend { + int (*init)(struct passwd *, int, const char *); +}; + +struct compress_backend { + size_t (*compress_chunk)(void *, size_t, void *, size_t); + size_t (*uncompress_chunk)(void *, size_t, void *, size_t); + int (*compress_file)(FILE *, FILE *); + int (*uncompress_file)(FILE *, FILE *); +}; + +/* auth structures */ +enum auth_type { + AUTH_BSD, + AUTH_PWD, +}; + +struct auth_backend { + int (*authenticate)(char *, char *); +}; + +struct scheduler_backend { + int (*init)(const char *); + + int (*insert)(struct scheduler_info *); + size_t (*commit)(uint32_t); + size_t (*rollback)(uint32_t); + + int (*update)(struct scheduler_info *); + int (*delete)(uint64_t); + int (*hold)(uint64_t, uint64_t); + int (*release)(int, uint64_t, int); + + int (*batch)(int, int*, size_t*, uint64_t*, int*); + + size_t (*messages)(uint32_t, uint32_t *, size_t); + size_t (*envelopes)(uint64_t, struct evpstate *, size_t); + int (*schedule)(uint64_t); + int (*remove)(uint64_t); + int (*suspend)(uint64_t); + int (*resume)(uint64_t); + int (*query)(uint64_t); +}; + +enum stat_type { + STAT_COUNTER, + STAT_TIMESTAMP, + STAT_TIMEVAL, + STAT_TIMESPEC, +}; + +struct stat_value { + enum stat_type type; + union stat_v { + size_t counter; + time_t timestamp; + struct timeval tv; + struct timespec ts; + } u; +}; + +#define STAT_KEY_SIZE 1024 +struct stat_kv { + void *iter; + char key[STAT_KEY_SIZE]; + struct stat_value val; +}; + +struct stat_backend { + void (*init)(void); + void (*close)(void); + void (*increment)(const char *, size_t); + void (*decrement)(const char *, size_t); + void (*set)(const char *, const struct stat_value *); + int (*iter)(void **, char **, struct stat_value *); +}; + +struct stat_digest { + time_t startup; + time_t timestamp; + + size_t clt_connect; + size_t clt_disconnect; + + size_t evp_enqueued; + size_t evp_dequeued; + + size_t evp_expired; + size_t evp_removed; + size_t evp_bounce; + + size_t dlv_ok; + size_t dlv_permfail; + size_t dlv_tempfail; + size_t dlv_loop; +}; + + +struct mproc { + pid_t pid; + char *name; + int proc; + void (*handler)(struct mproc *, struct imsg *); + struct imsgbuf imsgbuf; + + char *m_buf; + size_t m_alloc; + size_t m_pos; + uint32_t m_type; + uint32_t m_peerid; + pid_t m_pid; + int m_fd; + + int enable; + short events; + struct event ev; + void *data; +}; + +struct msg { + const uint8_t *pos; + const uint8_t *end; +}; + +extern enum smtp_proc_type smtpd_process; + +extern int tracing; +extern int foreground_log; +extern int profiling; + +extern struct mproc *p_control; +extern struct mproc *p_parent; +extern struct mproc *p_lka; +extern struct mproc *p_queue; +extern struct mproc *p_scheduler; +extern struct mproc *p_pony; +extern struct mproc *p_ca; + +extern struct smtpd *env; +extern void (*imsg_callback)(struct mproc *, struct imsg *); + +/* inter-process structures */ + +struct bounce_req_msg { + uint64_t evpid; + time_t timestamp; + struct delivery_bounce bounce; +}; + +enum dns_error { + DNS_OK = 0, + DNS_RETRY, + DNS_EINVAL, + DNS_ENONAME, + DNS_ENOTFOUND, +}; + +enum lka_resp_status { + LKA_OK, + LKA_TEMPFAIL, + LKA_PERMFAIL +}; + +enum filter_type { + FILTER_TYPE_BUILTIN, + FILTER_TYPE_PROC, + FILTER_TYPE_CHAIN, +}; + +enum filter_subsystem { + FILTER_SUBSYSTEM_SMTP_IN = 1<<0, + FILTER_SUBSYSTEM_SMTP_OUT = 1<<1, +}; + +struct filter_proc { + const char *command; + const char *user; + const char *group; + const char *chroot; + int errfd; + enum filter_subsystem filter_subsystem; +}; + +struct filter_config { + char *name; + enum filter_subsystem filter_subsystem; + enum filter_type filter_type; + enum filter_phase phase; + char *reject; + char *disconnect; + char *rewrite; + char *report; + uint8_t junk; + uint8_t bypass; + char *proc; + + const char **chain; + size_t chain_size; + struct dict chain_procs; + + int8_t not_fcrdns; + int8_t fcrdns; + + int8_t not_rdns; + int8_t rdns; + + int8_t not_rdns_table; + struct table *rdns_table; + + int8_t not_rdns_regex; + struct table *rdns_regex; + + int8_t not_src_table; + struct table *src_table; + + int8_t not_src_regex; + struct table *src_regex; + + int8_t not_helo_table; + struct table *helo_table; + + int8_t not_helo_regex; + struct table *helo_regex; + + int8_t not_auth; + int8_t auth; + + int8_t not_auth_table; + struct table *auth_table; + + int8_t not_auth_regex; + struct table *auth_regex; + + int8_t not_mail_from_table; + struct table *mail_from_table; + + int8_t not_mail_from_regex; + struct table *mail_from_regex; + + int8_t not_rcpt_to_table; + struct table *rcpt_to_table; + + int8_t not_rcpt_to_regex; + struct table *rcpt_to_regex; + +}; + +enum filter_status { + FILTER_PROCEED, + FILTER_REWRITE, + FILTER_REJECT, + FILTER_DISCONNECT, + FILTER_JUNK, +}; + +enum ca_resp_status { + CA_OK, + CA_FAIL +}; + +enum mda_resp_status { + MDA_OK, + MDA_TEMPFAIL, + MDA_PERMFAIL +}; + +struct msg_walkinfo { + struct event ev; + uint32_t msgid; + uint32_t peerid; + size_t n_evp; + void *data; + int done; +}; + + +enum dispatcher_type { + DISPATCHER_LOCAL, + DISPATCHER_REMOTE, + DISPATCHER_BOUNCE, +}; + +struct dispatcher_local { + uint8_t is_mbox; /* only for MBOX */ + + uint8_t expand_only; + uint8_t forward_only; + + char *mda_wrapper; + char *command; + + char *table_alias; + char *table_virtual; + char *table_userbase; + + char *user; +}; + +struct dispatcher_remote { + char *helo; + char *helo_source; + + char *source; + + char *ca; + char *pki; + + char *mail_from; + + char *smarthost; + int smarthost_domain; + + char *auth; + int tls_required; + int tls_noverify; + + int backup; + char *backupmx; + + char *filtername; + + int srs; +}; + +struct dispatcher_bounce { +}; + +struct dispatcher { + enum dispatcher_type type; + union dispatcher_agent { + struct dispatcher_local local; + struct dispatcher_remote remote; + struct dispatcher_bounce bounce; + } u; + + time_t ttl; +}; + +struct rule { + TAILQ_ENTRY(rule) r_entry; + + uint8_t reject; + + int8_t flag_tag; + int8_t flag_from; + int8_t flag_for; + int8_t flag_from_rdns; + int8_t flag_from_socket; + + int8_t flag_tag_regex; + int8_t flag_from_regex; + int8_t flag_for_regex; + + int8_t flag_smtp_helo; + int8_t flag_smtp_starttls; + int8_t flag_smtp_auth; + int8_t flag_smtp_mail_from; + int8_t flag_smtp_rcpt_to; + + int8_t flag_smtp_helo_regex; + int8_t flag_smtp_starttls_regex; + int8_t flag_smtp_auth_regex; + int8_t flag_smtp_mail_from_regex; + int8_t flag_smtp_rcpt_to_regex; + + + char *table_tag; + char *table_from; + char *table_for; + + char *table_smtp_helo; + char *table_smtp_auth; + char *table_smtp_mail_from; + char *table_smtp_rcpt_to; + + char *dispatcher; +}; + + +/* aliases.c */ +int aliases_get(struct expand *, const char *); +int aliases_virtual_get(struct expand *, const struct mailaddr *); +int alias_parse(struct expandnode *, const char *); + + +/* auth.c */ +struct auth_backend *auth_backend_lookup(enum auth_type); + + +/* bounce.c */ +void bounce_add(uint64_t); +void bounce_fd(int); + + +/* ca.c */ +int ca(void); +int ca_X509_verify(void *, void *, const char *, const char *, const char **); +void ca_imsg(struct mproc *, struct imsg *); +void ca_init(void); +void ca_engine_init(void); + + +/* cert.c */ +int cert_init(const char *, int, + void (*)(void *, int, const char *, const void *, size_t), void *); +int cert_verify(const void *, const char *, int, void (*)(void *, int), void *); +void cert_dispatch_request(struct mproc *, struct imsg *); +void cert_dispatch_result(struct mproc *, struct imsg *); + + +/* compress_backend.c */ +struct compress_backend *compress_backend_lookup(const char *); +size_t compress_chunk(void *, size_t, void *, size_t); +size_t uncompress_chunk(void *, size_t, void *, size_t); +int compress_file(FILE *, FILE *); +int uncompress_file(FILE *, FILE *); + +/* config.c */ +#define PURGE_LISTENERS 0x01 +#define PURGE_TABLES 0x02 +#define PURGE_RULES 0x04 +#define PURGE_PKI 0x08 +#define PURGE_PKI_KEYS 0x10 +#define PURGE_DISPATCHERS 0x20 +#define PURGE_EVERYTHING 0xff +struct smtpd *config_default(void); +void purge_config(uint8_t); +void config_process(enum smtp_proc_type); +void config_peer(enum smtp_proc_type); + + +/* control.c */ +int control(void); +int control_create_socket(void); + + +/* crypto.c */ +int crypto_setup(const char *, size_t); +int crypto_encrypt_file(FILE *, FILE *); +int crypto_decrypt_file(FILE *, FILE *); +size_t crypto_encrypt_buffer(const char *, size_t, char *, size_t); +size_t crypto_decrypt_buffer(const char *, size_t, char *, size_t); + + +/* dns.c */ +void dns_imsg(struct mproc *, struct imsg *); + + +/* enqueue.c */ +int enqueue(int, char **, FILE *); + + +/* envelope.c */ +void envelope_set_errormsg(struct envelope *, char *, ...); +void envelope_set_esc_class(struct envelope *, enum enhanced_status_class); +void envelope_set_esc_code(struct envelope *, enum enhanced_status_code); +int envelope_load_buffer(struct envelope *, const char *, size_t); +int envelope_dump_buffer(const struct envelope *, char *, size_t); + + +/* expand.c */ +int expand_cmp(struct expandnode *, struct expandnode *); +void expand_insert(struct expand *, struct expandnode *); +struct expandnode *expand_lookup(struct expand *, struct expandnode *); +void expand_clear(struct expand *); +void expand_free(struct expand *); +int expand_line(struct expand *, const char *, int); +int expand_to_text(struct expand *, char *, size_t); +RB_PROTOTYPE(expandtree, expandnode, nodes, expand_cmp); + + +/* forward.c */ +int forwards_get(int, struct expand *); + + +/* limit.c */ +void limit_mta_set_defaults(struct mta_limits *); +int limit_mta_set(struct mta_limits *, const char*, int64_t); + + +/* lka.c */ +int lka(void); + + +/* lka_proc.c */ +int lka_proc_ready(void); +void lka_proc_forked(const char *, uint32_t, int); +void lka_proc_errfd(const char *, int); +struct io *lka_proc_get_io(const char *); + + +/* lka_report.c */ +void lka_report_init(void); +void lka_report_register_hook(const char *, const char *); +void lka_report_smtp_link_connect(const char *, struct timeval *, uint64_t, const char *, int, + const struct sockaddr_storage *, const struct sockaddr_storage *); +void lka_report_smtp_link_disconnect(const char *, struct timeval *, uint64_t); +void lka_report_smtp_link_greeting(const char *, uint64_t, struct timeval *, + const char *); +void lka_report_smtp_link_identify(const char *, struct timeval *, uint64_t, const char *, const char *); +void lka_report_smtp_link_tls(const char *, struct timeval *, uint64_t, const char *); +void lka_report_smtp_link_auth(const char *, struct timeval *, uint64_t, const char *, const char *); +void lka_report_smtp_tx_reset(const char *, struct timeval *, uint64_t, uint32_t); +void lka_report_smtp_tx_begin(const char *, struct timeval *, uint64_t, uint32_t); +void lka_report_smtp_tx_mail(const char *, struct timeval *, uint64_t, uint32_t, const char *, int); +void lka_report_smtp_tx_rcpt(const char *, struct timeval *, uint64_t, uint32_t, const char *, int); +void lka_report_smtp_tx_envelope(const char *, struct timeval *, uint64_t, uint32_t, uint64_t); +void lka_report_smtp_tx_commit(const char *, struct timeval *, uint64_t, uint32_t, size_t); +void lka_report_smtp_tx_data(const char *, struct timeval *, uint64_t, uint32_t, int); +void lka_report_smtp_tx_rollback(const char *, struct timeval *, uint64_t, uint32_t); +void lka_report_smtp_protocol_client(const char *, struct timeval *, uint64_t, const char *); +void lka_report_smtp_protocol_server(const char *, struct timeval *, uint64_t, const char *); +void lka_report_smtp_filter_response(const char *, struct timeval *, uint64_t, + int, int, const char *); +void lka_report_smtp_timeout(const char *, struct timeval *, uint64_t); +void lka_report_filter_report(uint64_t, const char *, int, const char *, + struct timeval *, const char *); +void lka_report_proc(const char *, const char *); + + +/* lka_filter.c */ +void lka_filter_init(void); +void lka_filter_register_hook(const char *, const char *); +void lka_filter_ready(void); +int lka_filter_proc_in_session(uint64_t, const char *); +void lka_filter_begin(uint64_t, const char *); +void lka_filter_end(uint64_t); +void lka_filter_protocol(uint64_t, enum filter_phase, const char *); +void lka_filter_data_begin(uint64_t); +void lka_filter_data_end(uint64_t); +int lka_filter_response(uint64_t, const char *, const char *); + + +/* lka_session.c */ +void lka_session(uint64_t, struct envelope *); +void lka_session_forward_reply(struct forward_req *, int); + + +/* log.c */ +void vlog(int, const char *, va_list); +void logit(int, const char *, ...) __attribute__((format (printf, 2, 3))); + + +/* mda.c */ +void mda_postfork(void); +void mda_postprivdrop(void); +void mda_imsg(struct mproc *, struct imsg *); + + +/* mda_mbox.c */ +void mda_mbox_init(struct deliver *); +void mda_mbox(struct deliver *); + + +/* mda_unpriv.c */ +void mda_unpriv(struct dispatcher *, struct deliver *, const char *, const char *); + + +/* mda_variables.c */ +ssize_t mda_expand_format(char *, size_t, const struct deliver *, + const struct userinfo *, const char *); + + +/* makemap.c */ +int makemap(int, int, char **); + + +/* mailaddr.c */ +int mailaddr_line(struct maddrmap *, const char *); +void maddrmap_init(struct maddrmap *); +void maddrmap_insert(struct maddrmap *, struct maddrnode *); +void maddrmap_free(struct maddrmap *); + + +/* mproc.c */ +int mproc_fork(struct mproc *, const char*, char **); +void mproc_init(struct mproc *, int); +void mproc_clear(struct mproc *); +void mproc_enable(struct mproc *); +void mproc_disable(struct mproc *); +void mproc_event_add(struct mproc *); +void m_compose(struct mproc *, uint32_t, uint32_t, pid_t, int, void *, size_t); +void m_composev(struct mproc *, uint32_t, uint32_t, pid_t, int, + const struct iovec *, int); +void m_forward(struct mproc *, struct imsg *); +void m_create(struct mproc *, uint32_t, uint32_t, pid_t, int); +void m_add(struct mproc *, const void *, size_t); +void m_add_int(struct mproc *, int); +void m_add_u32(struct mproc *, uint32_t); +void m_add_size(struct mproc *, size_t); +void m_add_time(struct mproc *, time_t); +void m_add_timeval(struct mproc *, struct timeval *tv); +void m_add_string(struct mproc *, const char *); +void m_add_data(struct mproc *, const void *, size_t); +void m_add_evpid(struct mproc *, uint64_t); +void m_add_msgid(struct mproc *, uint32_t); +void m_add_id(struct mproc *, uint64_t); +void m_add_sockaddr(struct mproc *, const struct sockaddr *); +void m_add_mailaddr(struct mproc *, const struct mailaddr *); +void m_add_envelope(struct mproc *, const struct envelope *); +void m_add_params(struct mproc *, struct dict *); +void m_close(struct mproc *); +void m_flush(struct mproc *); + +void m_msg(struct msg *, struct imsg *); +int m_is_eom(struct msg *); +void m_end(struct msg *); +void m_get_int(struct msg *, int *); +void m_get_size(struct msg *, size_t *); +void m_get_u32(struct msg *, uint32_t *); +void m_get_time(struct msg *, time_t *); +void m_get_timeval(struct msg *, struct timeval *); +void m_get_string(struct msg *, const char **); +void m_get_data(struct msg *, const void **, size_t *); +void m_get_evpid(struct msg *, uint64_t *); +void m_get_msgid(struct msg *, uint32_t *); +void m_get_id(struct msg *, uint64_t *); +void m_get_sockaddr(struct msg *, struct sockaddr *); +void m_get_mailaddr(struct msg *, struct mailaddr *); +void m_get_envelope(struct msg *, struct envelope *); +void m_get_params(struct msg *, struct dict *); +void m_clear_params(struct dict *); + + +/* mta.c */ +void mta_postfork(void); +void mta_postprivdrop(void); +void mta_imsg(struct mproc *, struct imsg *); +void mta_route_ok(struct mta_relay *, struct mta_route *); +void mta_route_error(struct mta_relay *, struct mta_route *); +void mta_route_down(struct mta_relay *, struct mta_route *); +void mta_route_collect(struct mta_relay *, struct mta_route *); +void mta_source_error(struct mta_relay *, struct mta_route *, const char *); +void mta_delivery_log(struct mta_envelope *, const char *, const char *, int, const char *); +void mta_delivery_notify(struct mta_envelope *); +struct mta_task *mta_route_next_task(struct mta_relay *, struct mta_route *); +const char *mta_host_to_text(struct mta_host *); +const char *mta_relay_to_text(struct mta_relay *); + + +/* mta_session.c */ +void mta_session(struct mta_relay *, struct mta_route *, const char *); +void mta_session_imsg(struct mproc *, struct imsg *); + + +/* parse.y */ +int parse_config(struct smtpd *, const char *, int); +int cmdline_symset(char *); + + +/* queue.c */ +int queue(void); + + +/* queue_backend.c */ +uint32_t queue_generate_msgid(void); +uint64_t queue_generate_evpid(uint32_t); +int queue_init(const char *, int); +int queue_close(void); +int queue_message_create(uint32_t *); +int queue_message_delete(uint32_t); +int queue_message_commit(uint32_t); +int queue_message_fd_r(uint32_t); +int queue_message_fd_rw(uint32_t); +int queue_envelope_create(struct envelope *); +int queue_envelope_delete(uint64_t); +int queue_envelope_load(uint64_t, struct envelope *); +int queue_envelope_update(struct envelope *); +int queue_envelope_walk(struct envelope *); +int queue_message_walk(struct envelope *, uint32_t, int *, void **); + + +/* report_smtp.c */ +void report_smtp_link_connect(const char *, uint64_t, const char *, int, + const struct sockaddr_storage *, const struct sockaddr_storage *); +void report_smtp_link_disconnect(const char *, uint64_t); +void report_smtp_link_greeting(const char *, uint64_t, const char *); +void report_smtp_link_identify(const char *, uint64_t, const char *, const char *); +void report_smtp_link_tls(const char *, uint64_t, const char *); +void report_smtp_link_auth(const char *, uint64_t, const char *, const char *); +void report_smtp_tx_reset(const char *, uint64_t, uint32_t); +void report_smtp_tx_begin(const char *, uint64_t, uint32_t); +void report_smtp_tx_mail(const char *, uint64_t, uint32_t, const char *, int); +void report_smtp_tx_rcpt(const char *, uint64_t, uint32_t, const char *, int); +void report_smtp_tx_envelope(const char *, uint64_t, uint32_t, uint64_t); +void report_smtp_tx_data(const char *, uint64_t, uint32_t, int); +void report_smtp_tx_commit(const char *, uint64_t, uint32_t, size_t); +void report_smtp_tx_rollback(const char *, uint64_t, uint32_t); +void report_smtp_protocol_client(const char *, uint64_t, const char *); +void report_smtp_protocol_server(const char *, uint64_t, const char *); +void report_smtp_filter_response(const char *, uint64_t, int, int, const char *); +void report_smtp_timeout(const char *, uint64_t); + + +/* ruleset.c */ +struct rule *ruleset_match(const struct envelope *); + + +/* scheduler.c */ +int scheduler(void); + + +/* scheduler_bakend.c */ +struct scheduler_backend *scheduler_backend_lookup(const char *); +void scheduler_info(struct scheduler_info *, struct envelope *); + + +/* pony.c */ +int pony(void); +void pony_imsg(struct mproc *, struct imsg *); + + +/* resolver.c */ +void resolver_getaddrinfo(const char *, const char *, const struct addrinfo *, + void(*)(void *, int, struct addrinfo*), void *); +void resolver_getnameinfo(const struct sockaddr *, int, + void(*)(void *, int, const char *, const char *), void *); +void resolver_res_query(const char *, int, int, + void (*cb)(void *, int, int, int, const void *, int), void *); +void resolver_dispatch_request(struct mproc *, struct imsg *); +void resolver_dispatch_result(struct mproc *, struct imsg *); + + +/* smtp.c */ +void smtp_postfork(void); +void smtp_postprivdrop(void); +void smtp_imsg(struct mproc *, struct imsg *); +void smtp_configure(void); +void smtp_collect(void); + + +/* smtp_session.c */ +int smtp_session(struct listener *, int, const struct sockaddr_storage *, + const char *, struct io *); +void smtp_session_imsg(struct mproc *, struct imsg *); + + +/* smtpf_session.c */ +int smtpf_session(struct listener *, int, const struct sockaddr_storage *, + const char *); +void smtpf_session_imsg(struct mproc *, struct imsg *); + + +/* smtpd.c */ +void imsg_dispatch(struct mproc *, struct imsg *); +const char *proc_name(enum smtp_proc_type); +const char *proc_title(enum smtp_proc_type); +const char *imsg_to_str(int); +void log_imsg(int, int, struct imsg *); +int fork_proc_backend(const char *, const char *, const char *); + + +/* srs.c */ +const char *srs_encode(const char *, const char *); +const char *srs_decode(const char *); + + +/* ssl_smtpd.c */ +void *ssl_mta_init(void *, char *, off_t, const char *); +void *ssl_smtp_init(void *, int); + + +/* stat_backend.c */ +struct stat_backend *stat_backend_lookup(const char *); +void stat_increment(const char *, size_t); +void stat_decrement(const char *, size_t); +void stat_set(const char *, const struct stat_value *); +struct stat_value *stat_counter(size_t); +struct stat_value *stat_timestamp(time_t); +struct stat_value *stat_timeval(struct timeval *); +struct stat_value *stat_timespec(struct timespec *); + + +/* table.c */ +struct table *table_find(struct smtpd *, const char *); +struct table *table_create(struct smtpd *, const char *, const char *, + const char *); +int table_config(struct table *); +int table_open(struct table *); +int table_update(struct table *); +void table_close(struct table *); +void table_dump(struct table *); +int table_check_use(struct table *, uint32_t, uint32_t); +int table_check_type(struct table *, uint32_t); +int table_check_service(struct table *, uint32_t); +int table_match(struct table *, enum table_service, const char *); +int table_lookup(struct table *, enum table_service, const char *, + union lookup *); +int table_fetch(struct table *, enum table_service, union lookup *); +void table_destroy(struct smtpd *, struct table *); +void table_add(struct table *, const char *, const char *); +int table_domain_match(const char *, const char *); +int table_netaddr_match(const char *, const char *); +int table_mailaddr_match(const char *, const char *); +int table_regex_match(const char *, const char *); +void table_open_all(struct smtpd *); +void table_dump_all(struct smtpd *); +void table_close_all(struct smtpd *); + + +/* to.c */ +int email_to_mailaddr(struct mailaddr *, char *); +int text_to_netaddr(struct netaddr *, const char *); +int text_to_mailaddr(struct mailaddr *, const char *); +int text_to_relayhost(struct relayhost *, const char *); +int text_to_userinfo(struct userinfo *, const char *); +int text_to_credentials(struct credentials *, const char *); +int text_to_expandnode(struct expandnode *, const char *); +uint64_t text_to_evpid(const char *); +uint32_t text_to_msgid(const char *); +const char *sa_to_text(const struct sockaddr *); +const char *ss_to_text(const struct sockaddr_storage *); +const char *time_to_text(time_t); +const char *duration_to_text(time_t); +const char *rule_to_text(struct rule *); +const char *sockaddr_to_text(struct sockaddr *); +const char *mailaddr_to_text(const struct mailaddr *); +const char *expandnode_to_text(struct expandnode *); + + +/* util.c */ +typedef struct arglist arglist; +struct arglist { + char **list; + uint num; + uint nalloc; +}; +void addargs(arglist *, char *, ...) + __attribute__((format(printf, 2, 3))); +int bsnprintf(char *, size_t, const char *, ...) + __attribute__((format (printf, 3, 4))); +int safe_fclose(FILE *); +int hostname_match(const char *, const char *); +int mailaddr_match(const struct mailaddr *, const struct mailaddr *); +int valid_localpart(const char *); +int valid_domainpart(const char *); +int valid_domainname(const char *); +int valid_smtp_response(const char *); +int secure_file(int, char *, char *, uid_t, int); +int lowercase(char *, const char *, size_t); +void xlowercase(char *, const char *, size_t); +int uppercase(char *, const char *, size_t); +uint64_t generate_uid(void); +int availdesc(void); +int ckdir(const char *, mode_t, uid_t, gid_t, int); +int rmtree(char *, int); +int mvpurge(char *, char *); +int mktmpfile(void); +const char *parse_smtp_response(char *, size_t, char **, int *); +int xasprintf(char **, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); +void *xmalloc(size_t); +void *xcalloc(size_t, size_t); +char *xstrdup(const char *); +void *xmemdup(const void *, size_t); +char *strip(char *); +int io_xprint(struct io *, const char *); +int io_xprintf(struct io *, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); +void log_envelope(const struct envelope *, const char *, const char *, + const char *); +int session_socket_error(int); +int getmailname(char *, size_t); +int base64_encode(unsigned char const *, size_t, char *, size_t); +int base64_decode(char const *, unsigned char *, size_t); +int base64_encode_rfc3548(unsigned char const *, size_t, + char *, size_t); +void xclosefrom(int); + +void log_trace_verbose(int); +void log_trace(int, const char *, ...) + __attribute__((format (printf, 2, 3))); + +/* waitq.c */ +int waitq_wait(void *, void (*)(void *, void *, void *), void *); +void waitq_run(void *, void *); + + +/* runq.c */ +struct runq; + +int runq_init(struct runq **, void (*)(struct runq *, void *)); +int runq_schedule(struct runq *, time_t, void *); +int runq_schedule_at(struct runq *, time_t, void *); +int runq_cancel(struct runq *, void *); +int runq_pending(struct runq *, void *, time_t *); diff --git a/foobar/portable/smtpd/smtpd/Makefile b/foobar/portable/smtpd/smtpd/Makefile new file mode 100644 index 00000000..34a4dc74 --- /dev/null +++ b/foobar/portable/smtpd/smtpd/Makefile @@ -0,0 +1,102 @@ +# $OpenBSD: Makefile,v 1.91 2018/06/03 14:04:06 gilles Exp $ + +.PATH: ${.CURDIR}/.. + +PROG= smtpd + +SRCS= aliases.c +SRCS+= bounce.c +SRCS+= ca.c +SRCS+= cert.c +SRCS+= compress_backend.c +SRCS+= config.c +SRCS+= control.c +SRCS+= crypto.c +SRCS+= dict.c +SRCS+= dns.c +SRCS+= unpack_dns.c +SRCS+= envelope.c +SRCS+= esc.c +SRCS+= expand.c +SRCS+= forward.c +SRCS+= iobuf.c +SRCS+= ioev.c +SRCS+= limit.c +SRCS+= lka.c +SRCS+= lka_filter.c +SRCS+= lka_session.c +SRCS+= log.c +SRCS+= mailaddr.c +SRCS+= mda.c +SRCS+= mda_mbox.c +SRCS+= mda_unpriv.c +SRCS+= mda_variables.c +SRCS+= mproc.c +SRCS+= mta.c +SRCS+= mta_session.c +SRCS+= parse.y +SRCS+= pony.c +SRCS+= proxy.c +SRCS+= queue.c +SRCS+= queue_backend.c +SRCS+= report_smtp.c +SRCS+= resolver.c +SRCS+= ruleset.c +SRCS+= runq.c +SRCS+= scheduler.c +SRCS+= scheduler_backend.c +SRCS+= smtp.c +SRCS+= smtp_session.c +SRCS+= smtpd.c +SRCS+= srs.c +SRCS+= ssl.c +SRCS+= ssl_smtpd.c +SRCS+= ssl_verify.c +SRCS+= stat_backend.c +SRCS+= table.c +SRCS+= to.c +SRCS+= tree.c +SRCS+= util.c +SRCS+= waitq.c + +# RFC parsers +SRCS+= rfc5322.c + +# backends +SRCS+= compress_gzip.c + +SRCS+= table_db.c +SRCS+= table_getpwnam.c +SRCS+= table_proc.c +SRCS+= table_static.c + +SRCS+= queue_fs.c +SRCS+= queue_null.c +SRCS+= queue_proc.c +SRCS+= queue_ram.c + +SRCS+= scheduler_ramqueue.c +SRCS+= scheduler_null.c +SRCS+= scheduler_proc.c + +SRCS+= stat_ramstat.c + +MAN= sendmail.8 smtpd.8 smtpd.conf.5 table.5 +BINDIR= /usr/sbin + +LDADD+= -levent -lutil -lssl -lcrypto -lm -lz +DPADD+= ${LIBEVENT} ${LIBUTIL} ${LIBSSL} ${LIBCRYPTO} ${LIBM} ${LIBZ} + +CFLAGS+= -fstack-protector-all +CFLAGS+= -I${.CURDIR}/.. +CFLAGS+= -Wall -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wmissing-declarations +CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual +CFLAGS+= -Wsign-compare +CFLAGS+= -Werror-implicit-function-declaration +#CFLAGS+= -Werror # during development phase (breaks some archs) +CFLAGS+= -DIO_TLS +CFLAGS+= -DQUEUE_PROFILING +YFLAGS= + +.include <bsd.prog.mk> diff --git a/foobar/portable/smtpd/spfwalk.c b/foobar/portable/smtpd/spfwalk.c new file mode 100644 index 00000000..0832d1bc --- /dev/null +++ b/foobar/portable/smtpd/spfwalk.c @@ -0,0 +1,391 @@ +/* + * Copyright (c) 2017 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/tree.h> + +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +#include <arpa/nameser_compat.h> +#endif +#include <arpa/inet.h> +#include <arpa/nameser.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <asr.h> +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <imsg.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <unistd.h> + +#include "smtpd-defines.h" +#include "smtpd-api.h" +#include "unpack_dns.h" +#include "parser.h" + +struct target { + void (*dispatch)(struct dns_rr *, struct target *); + int cidr4; + int cidr6; +}; + +int spfwalk(int, struct parameter *); + +static void dispatch_txt(struct dns_rr *, struct target *); +static void dispatch_mx(struct dns_rr *, struct target *); +static void dispatch_a(struct dns_rr *, struct target *); +static void dispatch_aaaa(struct dns_rr *, struct target *); +static void lookup_record(int, const char *, struct target *); +static void dispatch_record(struct asr_result *, void *); +static ssize_t parse_txt(const char *, size_t, char *, size_t); +static int parse_target(char *, struct target *); +void *xmalloc(size_t size); + +int ip_v4 = 0; +int ip_v6 = 0; +int ip_both = 1; + +struct dict seen; + +int +spfwalk(int argc, struct parameter *argv) +{ + struct target tgt; + const char *ip_family = NULL; + char *line = NULL; + size_t linesize = 0; + ssize_t linelen; + + if (argv) + ip_family = argv[0].u.u_str; + + if (ip_family) { + if (strcmp(ip_family, "-4") == 0) { + ip_both = 0; + ip_v4 = 1; + } else if (strcmp(ip_family, "-6") == 0) { + ip_both = 0; + ip_v6 = 1; + } else + errx(1, "invalid ip_family"); + } + + dict_init(&seen); + event_init(); + + tgt.cidr4 = tgt.cidr6 = -1; + tgt.dispatch = dispatch_txt; + + while ((linelen = getline(&line, &linesize, stdin)) != -1) { + while (linelen-- > 0 && isspace((unsigned char)line[linelen])) + line[linelen] = '\0'; + + if (linelen > 0) + lookup_record(T_TXT, line, &tgt); + } + + free(line); + +#if HAVE_PLEDGE + if (pledge("dns stdio", NULL) == -1) + err(1, "pledge"); +#endif + + event_dispatch(); + + return 0; +} + +void +lookup_record(int type, const char *record, struct target *tgt) +{ + struct asr_query *as; + struct target *ntgt; + + as = res_query_async(record, C_IN, type, NULL); + if (as == NULL) + err(1, "res_query_async"); + ntgt = xmalloc(sizeof(*ntgt)); + *ntgt = *tgt; + event_asr_run(as, dispatch_record, (void *)ntgt); +} + +void +dispatch_record(struct asr_result *ar, void *arg) +{ + struct target *tgt = arg; + struct unpack pack; + struct dns_header h; + struct dns_query q; + struct dns_rr rr; + + /* best effort */ + if (ar->ar_h_errno && ar->ar_h_errno != NO_DATA) + goto end; + + unpack_init(&pack, ar->ar_data, ar->ar_datalen); + unpack_header(&pack, &h); + unpack_query(&pack, &q); + + for (; h.ancount; h.ancount--) { + unpack_rr(&pack, &rr); + /**/ + tgt->dispatch(&rr, tgt); + } +end: + free(tgt); +} + +void +dispatch_txt(struct dns_rr *rr, struct target *tgt) +{ + char buf[4096]; + char *argv[512]; + char buf2[512]; + struct target ltgt; + struct in6_addr ina; + char **ap = argv; + char *in = buf; + char *record, *end; + ssize_t n; + + if (rr->rr_type != T_TXT) + return; + n = parse_txt(rr->rr.other.rdata, rr->rr.other.rdlen, buf, sizeof(buf)); + if (n == -1 || n == sizeof(buf)) + return; + buf[n] = '\0'; + + if (strncasecmp("v=spf1 ", buf, 7)) + return; + + while ((*ap = strsep(&in, " ")) != NULL) { + if (strcasecmp(*ap, "v=spf1") == 0) + continue; + + end = *ap + strlen(*ap)-1; + if (*end == '.') + *end = '\0'; + + if (dict_set(&seen, *ap, &seen)) + continue; + + if (**ap == '-' || **ap == '~') + continue; + + if (**ap == '+' || **ap == '?') + (*ap)++; + + ltgt.cidr4 = ltgt.cidr6 = -1; + + if (strncasecmp("ip4:", *ap, 4) == 0) { + if ((ip_v4 == 1 || ip_both == 1) && + inet_net_pton(AF_INET, *(ap) + 4, + &ina, sizeof(ina)) != -1) + printf("%s\n", *(ap) + 4); + continue; + } + if (strncasecmp("ip6:", *ap, 4) == 0) { + if ((ip_v6 == 1 || ip_both == 1) && + inet_net_pton(AF_INET6, *(ap) + 4, + &ina, sizeof(ina)) != -1) + printf("%s\n", *(ap) + 4); + continue; + } + if (strcasecmp("a", *ap) == 0) { + print_dname(rr->rr_dname, buf2, sizeof(buf2)); + buf2[strlen(buf2) - 1] = '\0'; + ltgt.dispatch = dispatch_a; + lookup_record(T_A, buf2, <gt); + ltgt.dispatch = dispatch_aaaa; + lookup_record(T_AAAA, buf2, <gt); + continue; + } + if (strncasecmp("a:", *ap, 2) == 0) { + record = *(ap) + 2; + if (parse_target(record, <gt) < 0) + continue; + ltgt.dispatch = dispatch_a; + lookup_record(T_A, record, <gt); + ltgt.dispatch = dispatch_aaaa; + lookup_record(T_AAAA, record, <gt); + continue; + } + if (strncasecmp("exists:", *ap, 7) == 0) { + ltgt.dispatch = dispatch_a; + lookup_record(T_A, *(ap) + 7, <gt); + continue; + } + if (strncasecmp("include:", *ap, 8) == 0) { + ltgt.dispatch = dispatch_txt; + lookup_record(T_TXT, *(ap) + 8, <gt); + continue; + } + if (strncasecmp("redirect=", *ap, 9) == 0) { + ltgt.dispatch = dispatch_txt; + lookup_record(T_TXT, *(ap) + 9, <gt); + continue; + } + if (strcasecmp("mx", *ap) == 0) { + print_dname(rr->rr_dname, buf2, sizeof(buf2)); + buf2[strlen(buf2) - 1] = '\0'; + ltgt.dispatch = dispatch_mx; + lookup_record(T_MX, buf2, <gt); + continue; + } + if (strncasecmp("mx:", *ap, 3) == 0) { + record = *(ap) + 3; + if (parse_target(record, <gt) < 0) + continue; + ltgt.dispatch = dispatch_mx; + lookup_record(T_MX, record, <gt); + continue; + } + } + *ap = NULL; +} + +void +dispatch_mx(struct dns_rr *rr, struct target *tgt) +{ + char buf[512]; + struct target ltgt; + + if (rr->rr_type != T_MX) + return; + + print_dname(rr->rr.mx.exchange, buf, sizeof(buf)); + buf[strlen(buf) - 1] = '\0'; + if (buf[strlen(buf) - 1] == '.') + buf[strlen(buf) - 1] = '\0'; + + ltgt = *tgt; + ltgt.dispatch = dispatch_a; + lookup_record(T_A, buf, <gt); + ltgt.dispatch = dispatch_aaaa; + lookup_record(T_AAAA, buf, <gt); +} + +void +dispatch_a(struct dns_rr *rr, struct target *tgt) +{ + char buffer[512]; + const char *ptr; + + if (rr->rr_type != T_A) + return; + + if ((ptr = inet_ntop(AF_INET, &rr->rr.in_a.addr, + buffer, sizeof buffer))) { + if (tgt->cidr4 >= 0) + printf("%s/%d\n", ptr, tgt->cidr4); + else + printf("%s\n", ptr); + } +} + +void +dispatch_aaaa(struct dns_rr *rr, struct target *tgt) +{ + char buffer[512]; + const char *ptr; + + if (rr->rr_type != T_AAAA) + return; + + if ((ptr = inet_ntop(AF_INET6, &rr->rr.in_aaaa.addr6, + buffer, sizeof buffer))) { + if (tgt->cidr6 >= 0) + printf("%s/%d\n", ptr, tgt->cidr6); + else + printf("%s\n", ptr); + } +} + +ssize_t +parse_txt(const char *rdata, size_t rdatalen, char *dst, size_t dstsz) +{ + size_t len; + ssize_t r = 0; + + while (rdatalen) { + len = *(const unsigned char *)rdata; + if (len >= rdatalen) { + errno = EINVAL; + return -1; + } + + rdata++; + rdatalen--; + + if (len == 0) + continue; + + if (len >= dstsz) { + errno = EOVERFLOW; + return -1; + } + memmove(dst, rdata, len); + dst += len; + dstsz -= len; + + rdata += len; + rdatalen -= len; + r += len; + } + + return r; +} + +int +parse_target(char *record, struct target *tgt) +{ + const char *err; + char *m4, *m6; + + m4 = record; + strsep(&m4, "/"); + if (m4 == NULL) + return 0; + + m6 = m4; + strsep(&m6, "/"); + + if (*m4) { + tgt->cidr4 = strtonum(m4, 0, 32, &err); + if (err) + return tgt->cidr4 = -1; + } + + if (m6 == NULL) + return 0; + + tgt->cidr6 = strtonum(m6, 0, 128, &err); + if (err) + return tgt->cidr6 = -1; + + return 0; +} diff --git a/foobar/portable/smtpd/srs.c b/foobar/portable/smtpd/srs.c new file mode 100644 index 00000000..bb4f4d9e --- /dev/null +++ b/foobar/portable/smtpd/srs.c @@ -0,0 +1,379 @@ +/* $OpenBSD: srs.c,v 1.3 2019/09/29 10:03:49 gilles Exp $ */ + +/* + * Copyright (c) 2019 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <imsg.h> +#include <inttypes.h> +#include <netdb.h> +#include <limits.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include <openssl/sha.h> + +#include "smtpd.h" +#include "log.h" + +static uint8_t base32[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + +static int +minrange(uint16_t tref, uint16_t t2, int drift, int mod) +{ + if (tref > drift) { + /* t2 must fall in between tref and tref - drift */ + if (t2 <= tref && t2>= tref - drift) + return 1; + } + else { + /* t2 must fall in between 0 and tref, or wrap */ + if (t2 <= tref || t2 >= mod - (drift - tref)) + return 1; + } + return 0; +} + +static int +maxrange(uint16_t tref, uint16_t t2, int drift, int mod) +{ + if (tref + drift < 1024) { + /* t2 must fall in between tref and tref + drift */ + if (t2 >= tref && t2 <= tref + drift) + return 1; + } + else { + /* t2 must fall in between tref + drift, or wrap */ + if (t2 >= tref || t2 <= (tref + drift) % 1024) + return 1; + } + return 0; +} + +static int +timestamp_check_range(uint16_t tref, uint16_t t2) +{ + if (! minrange(tref, t2, env->sc_srs_ttl, 1024) && + ! maxrange(tref, t2, 1, 1024)) + return 0; + + return 1; +} + +static const unsigned char * +srs_hash(const char *key, const char *value) +{ + SHA_CTX c; + static unsigned char md[SHA_DIGEST_LENGTH]; + + SHA1_Init(&c); + SHA1_Update(&c, key, strlen(key)); + SHA1_Update(&c, value, strlen(value)); + SHA1_Final(md, &c); + return md; +} + +static const char * +srs0_encode(const char *sender, const char *rcpt_domain) +{ + static char dest[SMTPD_MAXMAILADDRSIZE]; + char tmp[SMTPD_MAXMAILADDRSIZE]; + char md[SHA_DIGEST_LENGTH*4+1]; + struct mailaddr maddr; + uint16_t timestamp; + int ret; + + /* compute 10 bits timestamp according to spec */ + timestamp = (time(NULL) / (60 * 60 * 24)) % 1024; + + /* parse sender into user and domain */ + if (! text_to_mailaddr(&maddr, sender)) + return sender; + + /* TT=<orig_domainpart>=<orig_userpart>@<new_domainpart> */ + ret = snprintf(tmp, sizeof tmp, "%c%c=%s=%s@%s", + base32[(timestamp>>5) & 0x1F], + base32[timestamp & 0x1F], + maddr.domain, maddr.user, rcpt_domain); + if (ret == -1 || ret >= (int)sizeof tmp) + return sender; + + /* compute HHHH */ + base64_encode_rfc3548(srs_hash(env->sc_srs_key, tmp), SHA_DIGEST_LENGTH, + md, sizeof md); + + /* prepend SRS0=HHHH= prefix */ + ret = snprintf(dest, sizeof dest, "SRS0=%c%c%c%c=%s", + md[0], md[1], md[2], md[3], tmp); + if (ret == -1 || ret >= (int)sizeof dest) + return sender; + + return dest; +} + +static const char * +srs1_encode_srs0(const char *sender, const char *rcpt_domain) +{ + static char dest[SMTPD_MAXMAILADDRSIZE]; + char tmp[SMTPD_MAXMAILADDRSIZE]; + char md[SHA_DIGEST_LENGTH*4+1]; + struct mailaddr maddr; + int ret; + + /* parse sender into user and domain */ + if (! text_to_mailaddr(&maddr, sender)) + return sender; + + /* <last_domainpart>==<SRS0_userpart>@<new_domainpart> */ + ret = snprintf(tmp, sizeof tmp, "%s==%s@%s", + maddr.domain, maddr.user, rcpt_domain); + if (ret == -1 || ret >= (int)sizeof tmp) + return sender; + + /* compute HHHH */ + base64_encode_rfc3548(srs_hash(env->sc_srs_key, tmp), SHA_DIGEST_LENGTH, + md, sizeof md); + + /* prepend SRS1=HHHH= prefix */ + ret = snprintf(dest, sizeof dest, "SRS1=%c%c%c%c=%s", + md[0], md[1], md[2], md[3], tmp); + if (ret == -1 || ret >= (int)sizeof dest) + return sender; + + return dest; +} + +static const char * +srs1_encode_srs1(const char *sender, const char *rcpt_domain) +{ + static char dest[SMTPD_MAXMAILADDRSIZE]; + char tmp[SMTPD_MAXMAILADDRSIZE]; + char md[SHA_DIGEST_LENGTH*4+1]; + struct mailaddr maddr; + int ret; + + /* parse sender into user and domain */ + if (! text_to_mailaddr(&maddr, sender)) + return sender; + + /* <SRS1_userpart>@<new_domainpart> */ + ret = snprintf(tmp, sizeof tmp, "%s@%s", maddr.user, rcpt_domain); + if (ret == -1 || ret >= (int)sizeof tmp) + return sender; + + /* sanity check: there's at least room for a checksum + * with allowed delimiter =, + or - + */ + if (strlen(tmp) < 5) + return sender; + if (tmp[4] != '=' && tmp[4] != '+' && tmp[4] != '-') + return sender; + + /* compute HHHH */ + base64_encode_rfc3548(srs_hash(env->sc_srs_key, tmp + 5), SHA_DIGEST_LENGTH, + md, sizeof md); + + /* prepend SRS1=HHHH= prefix skipping previous hops' HHHH */ + ret = snprintf(dest, sizeof dest, "SRS1=%c%c%c%c=%s", + md[0], md[1], md[2], md[3], tmp + 5); + if (ret == -1 || ret >= (int)sizeof dest) + return sender; + + return dest; +} + +const char * +srs_encode(const char *sender, const char *rcpt_domain) +{ + if (strncasecmp(sender, "SRS0=", 5) == 0) + return srs1_encode_srs0(sender+5, rcpt_domain); + if (strncasecmp(sender, "SRS1=", 5) == 0) + return srs1_encode_srs1(sender+5, rcpt_domain); + return srs0_encode(sender, rcpt_domain); +} + +static const char * +srs0_decode(const char *rcpt) +{ + static char dest[SMTPD_MAXMAILADDRSIZE]; + char md[SHA_DIGEST_LENGTH*4+1]; + struct mailaddr maddr; + char *p; + uint8_t *idx; + int ret; + uint16_t timestamp, srs_timestamp; + + /* sanity check: we have room for a checksum and delimiter */ + if (strlen(rcpt) < 5) + return NULL; + + /* compute checksum */ + base64_encode_rfc3548(srs_hash(env->sc_srs_key, rcpt+5), SHA_DIGEST_LENGTH, + md, sizeof md); + + /* compare prefix checksum with computed checksum */ + if (strncmp(md, rcpt, 4) != 0) { + if (env->sc_srs_key_backup == NULL) + return NULL; + base64_encode_rfc3548(srs_hash(env->sc_srs_key_backup, rcpt+5), + SHA_DIGEST_LENGTH, md, sizeof md); + if (strncmp(md, rcpt, 4) != 0) + return NULL; + } + rcpt += 5; + + /* sanity check: we have room for a timestamp and delimiter */ + if (strlen(rcpt) < 3) + return NULL; + + /* decode timestamp */ + if ((idx = strchr(base32, rcpt[0])) == NULL) + return NULL; + srs_timestamp = ((idx - base32) << 5); + + if ((idx = strchr(base32, rcpt[1])) == NULL) + return NULL; + srs_timestamp |= (idx - base32); + rcpt += 3; + + /* compute current 10 bits timestamp */ + timestamp = (time(NULL) / (60 * 60 * 24)) % 1024; + + /* check that SRS timestamp isn't too far from current */ + if (timestamp != srs_timestamp) + if (! timestamp_check_range(timestamp, srs_timestamp)) + return NULL; + + if (! text_to_mailaddr(&maddr, rcpt)) + return NULL; + + /* sanity check: we have at least one SRS separator */ + if ((p = strchr(maddr.user, '=')) == NULL) + return NULL; + *p++ = '\0'; + + /* maddr.user holds "domain\0user", with p pointing at user */ + ret = snprintf(dest, sizeof dest, "%s@%s", p, maddr.user); + if (ret == -1 || ret >= (int)sizeof dest) + return NULL; + + return dest; +} + +static const char * +srs1_decode(const char *rcpt) +{ + static char dest[SMTPD_MAXMAILADDRSIZE]; + char md[SHA_DIGEST_LENGTH*4+1]; + struct mailaddr maddr; + char *p; + uint8_t *idx; + int ret; + uint16_t timestamp, srs_timestamp; + + /* sanity check: we have room for a checksum and delimiter */ + if (strlen(rcpt) < 5) + return NULL; + + /* compute checksum */ + base64_encode_rfc3548(srs_hash(env->sc_srs_key, rcpt+5), SHA_DIGEST_LENGTH, + md, sizeof md); + + /* compare prefix checksum with computed checksum */ + if (strncmp(md, rcpt, 4) != 0) { + if (env->sc_srs_key_backup == NULL) + return NULL; + base64_encode_rfc3548(srs_hash(env->sc_srs_key_backup, rcpt+5), + SHA_DIGEST_LENGTH, md, sizeof md); + if (strncmp(md, rcpt, 4) != 0) + return NULL; + } + rcpt += 5; + + if (! text_to_mailaddr(&maddr, rcpt)) + return NULL; + + /* sanity check: we have at least one SRS separator */ + if ((p = strchr(maddr.user, '=')) == NULL) + return NULL; + *p++ = '\0'; + + /* maddr.user holds "domain\0user", with p pointing at user */ + ret = snprintf(dest, sizeof dest, "SRS0%s@%s", p, maddr.user); + if (ret == -1 || ret >= (int)sizeof dest) + return NULL; + + + /* we're ready to return decoded address, but let's check if + * SRS0 timestamp is valid. + */ + + /* first, get rid of SRS0 checksum (=HHHH=), we can't check it */ + if (strlen(p) < 6) + return NULL; + p += 6; + + /* we should be pointing to a timestamp, check that we're indeed */ + if (strlen(p) < 3) + return NULL; + if (p[2] != '=' && p[2] != '+' && p[2] != '-') + return NULL; + p[2] = '\0'; + + if ((idx = strchr(base32, p[0])) == NULL) + return NULL; + srs_timestamp = ((idx - base32) << 5); + + if ((idx = strchr(base32, p[1])) == NULL) + return NULL; + srs_timestamp |= (idx - base32); + + /* compute current 10 bits timestamp */ + timestamp = (time(NULL) / (60 * 60 * 24)) % 1024; + + /* check that SRS timestamp isn't too far from current */ + if (timestamp != srs_timestamp) + if (! timestamp_check_range(timestamp, srs_timestamp)) + return NULL; + + return dest; +} + +const char * +srs_decode(const char *rcpt) +{ + if (strncasecmp(rcpt, "SRS0=", 5) == 0) + return srs0_decode(rcpt + 5); + if (strncasecmp(rcpt, "SRS1=", 5) == 0) + return srs1_decode(rcpt + 5); + + return NULL; +} diff --git a/foobar/portable/smtpd/ssl.c b/foobar/portable/smtpd/ssl.c new file mode 100644 index 00000000..a37d6fea --- /dev/null +++ b/foobar/portable/smtpd/ssl.c @@ -0,0 +1,458 @@ +/* $OpenBSD: ssl.c,v 1.93 2019/06/05 06:40:13 gilles Exp $ */ + +/* + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> + * Copyright (c) 2008 Reyk Floeter <reyk@openbsd.org> + * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <event.h> +#include <fcntl.h> +#include <imsg.h> +#include <limits.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <openssl/ssl.h> +#include <openssl/engine.h> +#include <openssl/err.h> +#include <openssl/rsa.h> +#include <openssl/ecdsa.h> +#include <openssl/dh.h> +#include <openssl/bn.h> + +#include "log.h" +#include "ssl.h" + +void +ssl_init(void) +{ + static int inited = 0; + + if (inited) + return; + + SSL_library_init(); + SSL_load_error_strings(); + + OpenSSL_add_all_algorithms(); + + /* Init hardware crypto engines. */ + ENGINE_load_builtin_engines(); + ENGINE_register_all_complete(); + inited = 1; +} + +int +ssl_setup(SSL_CTX **ctxp, struct pki *pki, + int (*sni_cb)(SSL *,int *,void *), const char *ciphers) +{ + SSL_CTX *ctx; + uint8_t sid[SSL_MAX_SID_CTX_LENGTH]; + + ctx = ssl_ctx_create(pki->pki_name, pki->pki_cert, pki->pki_cert_len, ciphers); + + /* + * Set session ID context to a random value. We don't support + * persistent caching of sessions so it is OK to set a temporary + * session ID context that is valid during run time. + */ + arc4random_buf(sid, sizeof(sid)); + if (!SSL_CTX_set_session_id_context(ctx, sid, sizeof(sid))) + goto err; + + if (sni_cb) + SSL_CTX_set_tlsext_servername_callback(ctx, sni_cb); + + SSL_CTX_set_dh_auto(ctx, 0); + + SSL_CTX_set_ecdh_auto(ctx, 1); + + *ctxp = ctx; + return 1; + +err: + SSL_CTX_free(ctx); + ssl_error("ssl_setup"); + return 0; +} + +char * +ssl_load_file(const char *name, off_t *len, mode_t perm) +{ + struct stat st; + off_t size; + char *buf = NULL; + int fd, saved_errno; + char mode[12]; + + if ((fd = open(name, O_RDONLY)) == -1) + return (NULL); + if (fstat(fd, &st) != 0) + goto fail; + if (st.st_uid != 0) { + log_warnx("warn: %s: not owned by uid 0", name); + errno = EACCES; + goto fail; + } + if (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO) & ~perm) { + strmode(perm, mode); + log_warnx("warn: %s: insecure permissions: must be at most %s", + name, &mode[1]); + errno = EACCES; + goto fail; + } + size = st.st_size; + if ((buf = calloc(1, size + 1)) == NULL) + goto fail; + if (read(fd, buf, size) != size) + goto fail; + close(fd); + + *len = size + 1; + return (buf); + +fail: + free(buf); + saved_errno = errno; + close(fd); + errno = saved_errno; + return (NULL); +} + +#if 0 +static int +ssl_password_cb(char *buf, int size, int rwflag, void *u) +{ + size_t len; + if (u == NULL) { + explicit_bzero(buf, size); + return (0); + } + if ((len = strlcpy(buf, u, size)) >= (size_t)size) + return (0); + return (len); +} +#endif + +static int +ssl_password_cb(char *buf, int size, int rwflag, void *u) +{ + int ret = 0; + size_t len; + char *pass; + + pass = getpass((const char *)u); + if (pass == NULL) + return 0; + len = strlen(pass); + if (strlcpy(buf, pass, size) >= (size_t)size) + goto end; + ret = len; +end: + if (len) + explicit_bzero(pass, len); + return ret; +} + +char * +ssl_load_key(const char *name, off_t *len, char *pass, mode_t perm, const char *pkiname) +{ + FILE *fp = NULL; + EVP_PKEY *key = NULL; + BIO *bio = NULL; + long size; + char *data, *buf, *filebuf; + struct stat st; + char mode[12]; + char prompt[2048]; + + /* Initialize SSL library once */ + ssl_init(); + + /* + * Read (possibly) encrypted key from file + */ + if ((fp = fopen(name, "r")) == NULL) + return (NULL); + if ((filebuf = malloc_conceal(BUFSIZ)) == NULL) + goto fail; + setvbuf(fp, filebuf, _IOFBF, BUFSIZ); + + if (fstat(fileno(fp), &st) != 0) + goto fail; + if (st.st_uid != 0) { + log_warnx("warn: %s: not owned by uid 0", name); + errno = EACCES; + goto fail; + } + if (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO) & ~perm) { + strmode(perm, mode); + log_warnx("warn: %s: insecure permissions: must be at most %s", + name, &mode[1]); + errno = EACCES; + goto fail; + } + + (void)snprintf(prompt, sizeof prompt, "passphrase for %s: ", pkiname); + key = PEM_read_PrivateKey(fp, NULL, ssl_password_cb, prompt); + fclose(fp); + fp = NULL; + freezero(filebuf, BUFSIZ); + filebuf = NULL; + if (key == NULL) + goto fail; + /* + * Write unencrypted key to memory buffer + */ + if ((bio = BIO_new(BIO_s_mem())) == NULL) + goto fail; + if (!PEM_write_bio_PrivateKey(bio, key, NULL, NULL, 0, NULL, NULL)) + goto fail; + if ((size = BIO_get_mem_data(bio, &data)) <= 0) + goto fail; + if ((buf = calloc_conceal(1, size + 1)) == NULL) + goto fail; + memcpy(buf, data, size); + + BIO_free_all(bio); + EVP_PKEY_free(key); + + *len = (off_t)size + 1; + return (buf); + +fail: + ssl_error("ssl_load_key"); + BIO_free_all(bio); + EVP_PKEY_free(key); + if (fp) + fclose(fp); + freezero(filebuf, BUFSIZ); + return (NULL); +} + +SSL_CTX * +ssl_ctx_create(const char *pkiname, char *cert, off_t cert_len, const char *ciphers) +{ + SSL_CTX *ctx; + size_t pkinamelen = 0; + + ctx = SSL_CTX_new(SSLv23_method()); + if (ctx == NULL) { + ssl_error("ssl_ctx_create"); + fatal("ssl_ctx_create: could not create SSL context"); + } + + SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF); + SSL_CTX_set_timeout(ctx, SSL_SESSION_TIMEOUT); + SSL_CTX_set_options(ctx, + SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TICKET); + SSL_CTX_set_options(ctx, + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + SSL_CTX_set_options(ctx, SSL_OP_NO_CLIENT_RENEGOTIATION); + SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); + + if (ciphers == NULL) + ciphers = SSL_CIPHERS; + if (!SSL_CTX_set_cipher_list(ctx, ciphers)) { + ssl_error("ssl_ctx_create"); + fatal("ssl_ctx_create: could not set cipher list"); + } + + if (cert != NULL) { + if (pkiname != NULL) + pkinamelen = strlen(pkiname) + 1; + if (!SSL_CTX_use_certificate_chain_mem(ctx, cert, cert_len)) { + ssl_error("ssl_ctx_create"); + fatal("ssl_ctx_create: invalid certificate chain"); + } else if (!ssl_ctx_fake_private_key(ctx, + pkiname, pkinamelen, cert, cert_len, NULL, NULL)) { + ssl_error("ssl_ctx_create"); + fatal("ssl_ctx_create: could not fake private key"); + } else if (!SSL_CTX_check_private_key(ctx)) { + ssl_error("ssl_ctx_create"); + fatal("ssl_ctx_create: invalid private key"); + } + } + + return (ctx); +} + +int +ssl_load_certificate(struct pki *p, const char *pathname) +{ + p->pki_cert = ssl_load_file(pathname, &p->pki_cert_len, 0755); + if (p->pki_cert == NULL) + return 0; + return 1; +} + +int +ssl_load_keyfile(struct pki *p, const char *pathname, const char *pkiname) +{ + char pass[1024]; + + p->pki_key = ssl_load_key(pathname, &p->pki_key_len, pass, 0740, pkiname); + if (p->pki_key == NULL) + return 0; + return 1; +} + +int +ssl_load_cafile(struct ca *c, const char *pathname) +{ + c->ca_cert = ssl_load_file(pathname, &c->ca_cert_len, 0755); + if (c->ca_cert == NULL) + return 0; + return 1; +} + +const char * +ssl_to_text(const SSL *ssl) +{ + static char buf[256]; + + (void)snprintf(buf, sizeof buf, "%s:%s:%d", + SSL_get_version(ssl), + SSL_get_cipher_name(ssl), + SSL_get_cipher_bits(ssl, NULL)); + + return (buf); +} + +void +ssl_error(const char *where) +{ + unsigned long code; + char errbuf[128]; + + for (; (code = ERR_get_error()) != 0 ;) { + ERR_error_string_n(code, errbuf, sizeof(errbuf)); + log_debug("debug: SSL library error: %s: %s", where, errbuf); + } +} + +int +ssl_load_pkey(const void *data, size_t datalen, char *buf, off_t len, + X509 **x509ptr, EVP_PKEY **pkeyptr) +{ + BIO *in; + X509 *x509 = NULL; + EVP_PKEY *pkey = NULL; + RSA *rsa = NULL; + EC_KEY *eckey = NULL; + void *exdata = NULL; + + if ((in = BIO_new_mem_buf(buf, len)) == NULL) { + SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY, ERR_R_BUF_LIB); + return (0); + } + + if ((x509 = PEM_read_bio_X509(in, NULL, + ssl_password_cb, NULL)) == NULL) { + SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY, ERR_R_PEM_LIB); + goto fail; + } + + if ((pkey = X509_get_pubkey(x509)) == NULL) { + SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY, ERR_R_X509_LIB); + goto fail; + } + + BIO_free(in); + in = NULL; + + if (data != NULL && datalen) { + if (((rsa = EVP_PKEY_get1_RSA(pkey)) == NULL && + (eckey = EVP_PKEY_get1_EC_KEY(pkey)) == NULL) || + (exdata = malloc(datalen)) == NULL) { + SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY, ERR_R_EVP_LIB); + goto fail; + } + + memcpy(exdata, data, datalen); + if (rsa) + RSA_set_ex_data(rsa, 0, exdata); +#if defined(SUPPORT_ECDSA) + if (eckey) + ECDSA_set_ex_data(eckey, 0, exdata); +#endif + RSA_free(rsa); /* dereference, will be cleaned up with pkey */ +#if defined(SUPPORT_ECDSA) + EC_KEY_free(eckey); /* dereference, will be cleaned up with pkey */ +#endif + } + + *x509ptr = x509; + *pkeyptr = pkey; + + return (1); + + fail: + RSA_free(rsa); + EC_KEY_free(eckey); + BIO_free(in); + EVP_PKEY_free(pkey); + X509_free(x509); + free(exdata); + + return (0); +} + +int +ssl_ctx_fake_private_key(SSL_CTX *ctx, const void *data, size_t datalen, + char *buf, off_t len, X509 **x509ptr, EVP_PKEY **pkeyptr) +{ + int ret = 0; + EVP_PKEY *pkey = NULL; + X509 *x509 = NULL; + + if (!ssl_load_pkey(data, datalen, buf, len, &x509, &pkey)) + return (0); + + /* + * Use the public key as the "private" key - the secret key + * parameters are hidden in an extra process that will be + * contacted by the RSA engine. The SSL/TLS library needs at + * least the public key parameters in the current process. + */ + ret = SSL_CTX_use_PrivateKey(ctx, pkey); + if (!ret) + SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY, ERR_LIB_SSL); + + if (pkeyptr != NULL) + *pkeyptr = pkey; + else + EVP_PKEY_free(pkey); + + if (x509ptr != NULL) + *x509ptr = x509; + else + X509_free(x509); + + return (ret); +} diff --git a/foobar/portable/smtpd/ssl.h b/foobar/portable/smtpd/ssl.h new file mode 100644 index 00000000..11c80c68 --- /dev/null +++ b/foobar/portable/smtpd/ssl.h @@ -0,0 +1,71 @@ +/* $OpenBSD: ssl.h,v 1.21 2019/09/18 11:26:30 eric Exp $ */ +/* + * Copyright (c) 2013 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define SSL_CIPHERS "HIGH:!aNULL:!MD5" +#define SSL_SESSION_TIMEOUT 300 + +struct pki { + char pki_name[HOST_NAME_MAX+1]; + + char *pki_cert_file; + char *pki_cert; + off_t pki_cert_len; + + char *pki_key_file; + char *pki_key; + off_t pki_key_len; + + EVP_PKEY *pki_pkey; + + int pki_dhe; +}; + +struct ca { + char ca_name[HOST_NAME_MAX+1]; + + char *ca_cert_file; + char *ca_cert; + off_t ca_cert_len; +}; + + +/* ssl.c */ +void ssl_init(void); +int ssl_setup(SSL_CTX **, struct pki *, + int (*)(SSL *, int *, void *), const char *); +SSL_CTX *ssl_ctx_create(const char *, char *, off_t, const char *); +int ssl_cmp(struct pki *, struct pki *); +char *ssl_load_file(const char *, off_t *, mode_t); +char *ssl_load_key(const char *, off_t *, char *, mode_t, const char *); + +const char *ssl_to_text(const SSL *); +void ssl_error(const char *); + +int ssl_load_certificate(struct pki *, const char *); +int ssl_load_keyfile(struct pki *, const char *, const char *); +int ssl_load_cafile(struct ca *, const char *); +int ssl_load_pkey(const void *, size_t, char *, off_t, + X509 **, EVP_PKEY **); +int ssl_ctx_fake_private_key(SSL_CTX *, const void *, size_t, + char *, off_t, X509 **, EVP_PKEY **); + +/* ssl_privsep.c */ +int ssl_by_mem_ctrl(X509_LOOKUP *, int, const char *, long, char **); +int SSL_CTX_use_certificate_chain_mem(SSL_CTX *, void *, int); + +/* ssl_verify.c */ +int ssl_check_name(X509 *, const char *, int *); diff --git a/foobar/portable/smtpd/ssl_smtpd.c b/foobar/portable/smtpd/ssl_smtpd.c new file mode 100644 index 00000000..4e5b7e75 --- /dev/null +++ b/foobar/portable/smtpd/ssl_smtpd.c @@ -0,0 +1,105 @@ +/* $OpenBSD: ssl_smtpd.c,v 1.13 2015/12/30 16:02:08 benno Exp $ */ + +/* + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> + * Copyright (c) 2008 Reyk Floeter <reyk@openbsd.org> + * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <event.h> +#include <fcntl.h> +#include <limits.h> +#include <imsg.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <openssl/ssl.h> +#include <openssl/engine.h> +#include <openssl/err.h> + +#include "smtpd.h" +#include "log.h" +#include "ssl.h" + + +void * +ssl_mta_init(void *pkiname, char *cert, off_t cert_len, const char *ciphers) +{ + SSL_CTX *ctx = NULL; + SSL *ssl = NULL; + + ctx = ssl_ctx_create(pkiname, cert, cert_len, ciphers); + + if ((ssl = SSL_new(ctx)) == NULL) + goto err; + if (!SSL_set_ssl_method(ssl, SSLv23_client_method())) + goto err; + + SSL_CTX_free(ctx); + return (void *)(ssl); + +err: + SSL_free(ssl); + SSL_CTX_free(ctx); + ssl_error("ssl_mta_init"); + return (NULL); +} + +/* dummy_verify */ +static int +dummy_verify(int ok, X509_STORE_CTX *store) +{ + /* + * We *want* SMTP to request an optional client certificate, however we don't want the + * verification to take place in the SMTP process. This dummy verify will allow us to + * asynchronously verify in the lookup process. + */ + return 1; +} + +void * +ssl_smtp_init(void *ssl_ctx, int verify) +{ + SSL *ssl = NULL; + + log_debug("debug: session_start_ssl: switching to SSL"); + + if (verify) + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, dummy_verify); + + if ((ssl = SSL_new(ssl_ctx)) == NULL) + goto err; + if (!SSL_set_ssl_method(ssl, SSLv23_server_method())) + goto err; + + return (void *)(ssl); + +err: + SSL_free(ssl); + ssl_error("ssl_smtp_init"); + return (NULL); +} diff --git a/foobar/portable/smtpd/ssl_verify.c b/foobar/portable/smtpd/ssl_verify.c new file mode 100644 index 00000000..2e784b97 --- /dev/null +++ b/foobar/portable/smtpd/ssl_verify.c @@ -0,0 +1,297 @@ +/* $OpenBSD: ssl_verify.c,v 1.2 2019/11/02 03:16:45 gilles Exp $ */ +/* + * Copyright (c) 2014 Jeremie Courreges-Anglas <jca@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* Adapted from lib/libtls/tls_verify.c */ + +#include "includes.h" + +#include <sys/socket.h> + +#include <arpa/inet.h> +#include <netinet/in.h> + +#include <limits.h> +#include <string.h> + +#include <openssl/x509v3.h> + +#if 0 +#include <tls.h> +#include "tls_internal.h" +#endif + +#include "ssl.h" +#include "log.h" + +struct tls; +#define tls_set_errorx(ctx, ...) log_warnx(__VA_ARGS__) +union tls_addr { + struct in_addr in; + struct in6_addr in6; +}; + +static int +tls_match_name(const char *cert_name, const char *name) +{ + const char *cert_domain, *domain, *next_dot; + + if (strcasecmp(cert_name, name) == 0) + return 0; + + /* Wildcard match? */ + if (cert_name[0] == '*') { + /* + * Valid wildcards: + * - "*.domain.tld" + * - "*.sub.domain.tld" + * - etc. + * Reject "*.tld". + * No attempt to prevent the use of eg. "*.co.uk". + */ + cert_domain = &cert_name[1]; + /* Disallow "*" */ + if (cert_domain[0] == '\0') + return -1; + /* Disallow "*foo" */ + if (cert_domain[0] != '.') + return -1; + /* Disallow "*.." */ + if (cert_domain[1] == '.') + return -1; + next_dot = strchr(&cert_domain[1], '.'); + /* Disallow "*.bar" */ + if (next_dot == NULL) + return -1; + /* Disallow "*.bar.." */ + if (next_dot[1] == '.') + return -1; + + domain = strchr(name, '.'); + + /* No wildcard match against a name with no host part. */ + if (name[0] == '.') + return -1; + /* No wildcard match against a name with no domain part. */ + if (domain == NULL || strlen(domain) == 1) + return -1; + + if (strcasecmp(cert_domain, domain) == 0) + return 0; + } + + return -1; +} + +/* + * See RFC 5280 section 4.2.1.6 for SubjectAltName details. + * alt_match is set to 1 if a matching alternate name is found. + * alt_exists is set to 1 if any known alternate name exists in the certificate. + */ +static int +tls_check_subject_altname(struct tls *ctx, X509 *cert, const char *name, + int *alt_match, int *alt_exists) +{ + STACK_OF(GENERAL_NAME) *altname_stack = NULL; + union tls_addr addrbuf; + int addrlen, type; + int count, i; + int rv = 0; + + *alt_match = 0; + *alt_exists = 0; + + altname_stack = X509_get_ext_d2i(cert, NID_subject_alt_name, + NULL, NULL); + if (altname_stack == NULL) + return 0; + + if (inet_pton(AF_INET, name, &addrbuf) == 1) { + type = GEN_IPADD; + addrlen = 4; + } else if (inet_pton(AF_INET6, name, &addrbuf) == 1) { + type = GEN_IPADD; + addrlen = 16; + } else { + type = GEN_DNS; + addrlen = 0; + } + + count = sk_GENERAL_NAME_num(altname_stack); + for (i = 0; i < count; i++) { + GENERAL_NAME *altname; + + altname = sk_GENERAL_NAME_value(altname_stack, i); + + if (altname->type == GEN_DNS || altname->type == GEN_IPADD) + *alt_exists = 1; + + if (altname->type != type) + continue; + + if (type == GEN_DNS) { + const unsigned char *data; + int format, len; + + format = ASN1_STRING_type(altname->d.dNSName); + if (format == V_ASN1_IA5STRING) { + data = ASN1_STRING_get0_data(altname->d.dNSName); + len = ASN1_STRING_length(altname->d.dNSName); + + if (len < 0 || (size_t)len != strlen(data)) { + tls_set_errorx(ctx, + "error verifying name '%s': " + "NUL byte in subjectAltName, " + "probably a malicious certificate", + name); + rv = -1; + break; + } + + /* + * Per RFC 5280 section 4.2.1.6: + * " " is a legal domain name, but that + * dNSName must be rejected. + */ + if (strcmp(data, " ") == 0) { + tls_set_errorx(ctx, + "error verifying name '%s': " + "a dNSName of \" \" must not be " + "used", name); + rv = -1; + break; + } + + if (tls_match_name(data, name) == 0) { + *alt_match = 1; + break; + } + } else { +#ifdef DEBUG + fprintf(stdout, "%s: unhandled subjectAltName " + "dNSName encoding (%d)\n", getprogname(), + format); +#endif + } + + } else if (type == GEN_IPADD) { + const unsigned char *data; + int datalen; + + datalen = ASN1_STRING_length(altname->d.iPAddress); + data = ASN1_STRING_get0_data(altname->d.iPAddress); + + if (datalen < 0) { + tls_set_errorx(ctx, + "Unexpected negative length for an " + "IP address: %d", datalen); + rv = -1; + break; + } + + /* + * Per RFC 5280 section 4.2.1.6: + * IPv4 must use 4 octets and IPv6 must use 16 octets. + */ + if (datalen == addrlen && + memcmp(data, &addrbuf, addrlen) == 0) { + *alt_match = 1; + break; + } + } + } + + sk_GENERAL_NAME_pop_free(altname_stack, GENERAL_NAME_free); + return rv; +} + +static int +tls_check_common_name(struct tls *ctx, X509 *cert, const char *name, + int *cn_match) +{ + X509_NAME *subject_name; + char *common_name = NULL; + union tls_addr addrbuf; + int common_name_len; + int rv = 0; + + *cn_match = 0; + + subject_name = X509_get_subject_name(cert); + if (subject_name == NULL) + goto done; + + common_name_len = X509_NAME_get_text_by_NID(subject_name, + NID_commonName, NULL, 0); + if (common_name_len < 0) + goto done; + + common_name = calloc(common_name_len + 1, 1); + if (common_name == NULL) + goto done; + + X509_NAME_get_text_by_NID(subject_name, NID_commonName, common_name, + common_name_len + 1); + + /* NUL bytes in CN? */ + if (common_name_len < 0 || + (size_t)common_name_len != strlen(common_name)) { + tls_set_errorx(ctx, "error verifying name '%s': " + "NUL byte in Common Name field, " + "probably a malicious certificate", name); + rv = -1; + goto done; + } + + /* + * We don't want to attempt wildcard matching against IP addresses, + * so perform a simple comparison here. + */ + if (inet_pton(AF_INET, name, &addrbuf) == 1 || + inet_pton(AF_INET6, name, &addrbuf) == 1) { + if (strcmp(common_name, name) == 0) + *cn_match = 1; + goto done; + } + + if (tls_match_name(common_name, name) == 0) + *cn_match = 1; + + done: + free(common_name); + return rv; +} + +int +ssl_check_name(X509 *cert, const char *name, int *match) +{ + int alt_exists; + + *match = 0; + + if (tls_check_subject_altname(NULL, cert, name, match, + &alt_exists) == -1) + return -1; + + /* + * As per RFC 6125 section 6.4.4, if any known alternate name existed + * in the certificate, we do not attempt to match on the CN. + */ + if (*match || alt_exists) + return 0; + + return tls_check_common_name(NULL, cert, name, match); +} diff --git a/foobar/portable/smtpd/stat_backend.c b/foobar/portable/smtpd/stat_backend.c new file mode 100644 index 00000000..30cb299b --- /dev/null +++ b/foobar/portable/smtpd/stat_backend.c @@ -0,0 +1,124 @@ +/* $OpenBSD: stat_backend.c,v 1.11 2018/12/27 10:35:26 gilles Exp $ */ + +/* + * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/queue.h> +#include <sys/tree.h> + +#include <event.h> +#include <imsg.h> +#include <stdio.h> +#include <string.h> +#include <limits.h> + +#include "log.h" +#include "smtpd.h" + +extern struct stat_backend stat_backend_ramstat; + +struct stat_backend * +stat_backend_lookup(const char *name) +{ + return &stat_backend_ramstat; +} + +void +stat_increment(const char *key, size_t count) +{ + struct stat_value *value; + + if (count == 0) + return; + + value = stat_counter(count); + + m_create(p_control, IMSG_STAT_INCREMENT, 0, 0, -1); + m_add_string(p_control, key); + m_add_data(p_control, value, sizeof(*value)); + m_close(p_control); +} + +void +stat_decrement(const char *key, size_t count) +{ + struct stat_value *value; + + if (count == 0) + return; + + value = stat_counter(count); + + m_create(p_control, IMSG_STAT_DECREMENT, 0, 0, -1); + m_add_string(p_control, key); + m_add_data(p_control, value, sizeof(*value)); + m_close(p_control); +} + +void +stat_set(const char *key, const struct stat_value *value) +{ + m_create(p_control, IMSG_STAT_SET, 0, 0, -1); + m_add_string(p_control, key); + m_add_data(p_control, value, sizeof(*value)); + m_close(p_control); +} + +/* helpers */ + +struct stat_value * +stat_counter(size_t counter) +{ + static struct stat_value value; + + value.type = STAT_COUNTER; + value.u.counter = counter; + return &value; +} + +struct stat_value * +stat_timestamp(time_t timestamp) +{ + static struct stat_value value; + + value.type = STAT_TIMESTAMP; + value.u.timestamp = timestamp; + return &value; +} + +struct stat_value * +stat_timeval(struct timeval *tv) +{ + static struct stat_value value; + + value.type = STAT_TIMEVAL; + value.u.tv = *tv; + return &value; +} + +struct stat_value * +stat_timespec(struct timespec *ts) +{ + static struct stat_value value; + + value.type = STAT_TIMESPEC; + value.u.ts = *ts; + return &value; +} diff --git a/foobar/portable/smtpd/stat_ramstat.c b/foobar/portable/smtpd/stat_ramstat.c new file mode 100644 index 00000000..bbf1541a --- /dev/null +++ b/foobar/portable/smtpd/stat_ramstat.c @@ -0,0 +1,162 @@ +/* $OpenBSD: stat_ramstat.c,v 1.11 2018/05/31 21:06:12 gilles Exp $ */ + +/* + * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/queue.h> +#include <sys/tree.h> + +#include <event.h> +#include <imsg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> + +#include "smtpd.h" +#include "log.h" + + +static void ramstat_init(void); +static void ramstat_close(void); +static void ramstat_increment(const char *, size_t); +static void ramstat_decrement(const char *, size_t); +static void ramstat_set(const char *, const struct stat_value *); +static int ramstat_iter(void **, char **, struct stat_value *); + +struct ramstat_entry { + RB_ENTRY(ramstat_entry) entry; + char key[STAT_KEY_SIZE]; + struct stat_value value; +}; +RB_HEAD(stats_tree, ramstat_entry) stats; +RB_PROTOTYPE(stats_tree, ramstat_entry, entry, ramstat_entry_cmp); + +struct stat_backend stat_backend_ramstat = { + ramstat_init, + ramstat_close, + ramstat_increment, + ramstat_decrement, + ramstat_set, + ramstat_iter +}; + +static void +ramstat_init(void) +{ + log_trace(TRACE_STAT, "ramstat: init"); + + RB_INIT(&stats); + + /* ramstat_set() should be called for each key we want + * to have displayed by smtpctl show stats at startup. + */ + ramstat_set("uptime", stat_timestamp(env->sc_uptime)); +} + +static void +ramstat_close(void) +{ + log_trace(TRACE_STAT, "ramstat: close"); +} + +static void +ramstat_increment(const char *name, size_t val) +{ + struct ramstat_entry *np, lk; + + log_trace(TRACE_STAT, "ramstat: increment: %s", name); + (void)strlcpy(lk.key, name, sizeof (lk.key)); + np = RB_FIND(stats_tree, &stats, &lk); + if (np == NULL) { + np = xcalloc(1, sizeof *np); + (void)strlcpy(np->key, name, sizeof (np->key)); + RB_INSERT(stats_tree, &stats, np); + } + log_trace(TRACE_STAT, "ramstat: %s (%p): %zd -> %zd", + name, name, np->value.u.counter, np->value.u.counter + val); + np->value.u.counter += val; +} + +static void +ramstat_decrement(const char *name, size_t val) +{ + struct ramstat_entry *np, lk; + + log_trace(TRACE_STAT, "ramstat: decrement: %s", name); + (void)strlcpy(lk.key, name, sizeof (lk.key)); + np = RB_FIND(stats_tree, &stats, &lk); + if (np == NULL) { + np = xcalloc(1, sizeof *np); + (void)strlcpy(np->key, name, sizeof (np->key)); + RB_INSERT(stats_tree, &stats, np); + } + log_trace(TRACE_STAT, "ramstat: %s (%p): %zd -> %zd", + name, name, np->value.u.counter, np->value.u.counter - val); + np->value.u.counter -= val; +} + +static void +ramstat_set(const char *name, const struct stat_value *val) +{ + struct ramstat_entry *np, lk; + + log_trace(TRACE_STAT, "ramstat: set: %s", name); + (void)strlcpy(lk.key, name, sizeof (lk.key)); + np = RB_FIND(stats_tree, &stats, &lk); + if (np == NULL) { + np = xcalloc(1, sizeof *np); + (void)strlcpy(np->key, name, sizeof (np->key)); + RB_INSERT(stats_tree, &stats, np); + } + log_trace(TRACE_STAT, "ramstat: %s: n/a -> n/a", name); + np->value = *val; +} + +static int +ramstat_iter(void **iter, char **name, struct stat_value *val) +{ + struct ramstat_entry *np; + + log_trace(TRACE_STAT, "ramstat: iter"); + if (RB_EMPTY(&stats)) + return 0; + + if (*iter == NULL) + np = RB_MIN(stats_tree, &stats); + else + np = RB_NEXT(stats_tree, &stats, *iter); + + *iter = np; + if (np == NULL) + return 0; + + *name = np->key; + *val = np->value; + return 1; +} + + +static int +ramstat_entry_cmp(struct ramstat_entry *e1, struct ramstat_entry *e2) +{ + return strcmp(e1->key, e2->key); +} + +RB_GENERATE(stats_tree, ramstat_entry, entry, ramstat_entry_cmp); diff --git a/foobar/portable/smtpd/table.5 b/foobar/portable/smtpd/table.5 new file mode 100644 index 00000000..e9d4fa4b --- /dev/null +++ b/foobar/portable/smtpd/table.5 @@ -0,0 +1,258 @@ +.\" $OpenBSD: table.5,v 1.11 2019/08/11 13:00:57 gilles Exp $ +.\" +.\" Copyright (c) 2013 Eric Faurot <eric@openbsd.org> +.\" Copyright (c) 2013 Gilles Chehade <gilles@poolp.org> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.\" +.Dd $Mdocdate: August 11 2019 $ +.Dt TABLE 5 +.Os +.Sh NAME +.Nm table +.Nd format description for smtpd tables +.Sh DESCRIPTION +This manual page documents the file format for the various tables used in the +.Xr smtpd 8 +mail daemon. +.Pp +The format described here applies to tables as defined in +.Xr smtpd.conf 5 . +.Sh TABLE TYPES +There are two types of tables: lists and mappings. +A list consists of a series of values, +while a mapping consists of a series of keys and their associated values. +The following illustrates how to declare them as static tables: +.Bd -literal -offset indent +table mylist { value1, value2, value3 } +table mymapping { key1 = value1, key2 = value2, key3 = value3 } +.Ed +.Pp +When using a +.Ql file +table, a list will be written with each value on a line by itself. +Comments can be put anywhere in the file using a hash mark +.Pq Sq # , +and extend to the end of the current line. +.Bd -literal -offset indent +value1 +value2 +value3 +.Ed +.Pp +A mapping will be written with each key and value on a line, +whitespaces separating both columns: +.Bd -literal -offset indent +key1 value1 +key2 value2 +key3 value3 +.Ed +.Pp +A file table can be converted to a Berkeley database using the +.Xr makemap 8 +utility with no syntax change. +.Pp +Tables using a +.Ql file +or Berkeley DB backend will be referenced as follows: +.Bd -unfilled -offset indent +.Ic table Ar name Cm file : Ns Pa /path/to/file +.Ic table Ar name Cm db : Ns Pa /path/to/file.db +.Ed +.Ss Aliasing tables +Aliasing tables are mappings that associate a recipient to one or many +destinations. +They can be used in two contexts: primary domain aliases and virtual domain +mapping. +.Bd -unfilled -offset indent +.Ic action Ar name method Cm alias Pf < table Ns > +.Ic action Ar name method Cm virtual Pf < table Ns > +.Ed +.Pp +In a primary domain context, the key is the user part of the recipient address, +whilst the value is one or many recipients as described in +.Xr aliases 5 : +.Bd -literal -offset indent +user1 otheruser +user2 otheruser1,otheruser2 +user3 otheruser@example.com +.Ed +.Pp +In a virtual domain context, the key is either a user part, a full email +address or a catch all, following selection rules described in +.Xr smtpd.conf 5 , +and the value is one or many recipients as described in +.Xr aliases 5 : +.Bd -literal -offset indent +user1 otheruser +user2@example.org otheruser1,otheruser2 +@example.org otheruser@example.com +@ catchall@example.com +.Ed +.Pp +The following directive shares the same table format, +but with a different meaning. +Here, the user is allowed to send mail from the listed addresses: +.Bd -unfilled -offset indent +.Ic listen on Ar interface Cm auth Oo Ar ... Oc Cm senders Pf < Ar table Ns > +.Ed +.Ss Domain tables +Domain tables are simple lists of domains or hosts. +.Bd -unfilled -offset indent +.Ic match Cm for domain Pf < table Ns > Cm action Ar name +.Ic match Cm helo Pf < table Ns > Oo Ar ... Oc Cm action Ar name +.Ed +.Pp +In that context, the list of domains will be matched against the recipient +domain or against the HELO name advertised by the sending host, +respectively. +For +.Ql static , +.Ql file +and +.Xr dbopen 3 +backends, a wildcard may be used so the domain table may contain: +.Bd -literal -offset indent +example.org +*.example.org +.Ed +.Ss Credentials tables +Credentials tables are mappings of credentials. +They can be used in two contexts: +.Bd -unfilled -offset indent +.Ic listen on Ar interface Cm tls Oo Ar ... Oc Cm auth Pf < Ar table Ns > +.Ic action Ar name Cm relay host Ar relay-url Cm auth Pf < Ar table Ns > +.Ed +.Pp +In a listener context, the credentials are a mapping of username and encrypted +passwords: +.Bd -literal -offset indent +user1 $2b$10$hIJ4QfMcp.90nJwKqGbKM.MybArjHOTpEtoTV.DgLYAiThuoYmTSe +user2 $2b$10$bwSmUOBGcZGamIfRuXGTvuTo3VLbPG9k5yeKNMBtULBhksV5KdGsK +.Ed +.Pp +The passwords are to be encrypted using the +.Xr smtpctl 8 +encrypt subcommand. +.Pp +In a relay context, the credentials are a mapping of labels and +username:password pairs: +.Bd -literal -offset indent +label1 user:password +.Ed +.Pp +The label must be unique and is used as a selector for the proper credentials +when multiple credentials are valid for a single destination. +The password is not encrypted as it must be provided to the remote host. +.Ss Netaddr tables +Netaddr tables are lists of IPv4 and IPv6 network addresses. +They can only be used in the following context: +.Pp +.D1 Ic match Cm from src Pf < Ar table Ns > Cm action Ar name +.Pp +When used as a "from source", the address of a client is compared to the list +of addresses in the table until a match is found. +.Pp +A netaddr table can contain exact addresses or netmasks, and looks as follow: +.Bd -literal -offset indent +192.168.1.1 +::1 +ipv6:::1 +192.168.1.0/24 +.Ed +.Ss Userinfo tables +User info tables are used in rule context to specify an alternate user base, +mapping virtual users to local system users by UID, GID and home directory. +.Pp +.D1 Ic action Ar name method Cm userbase Pf < Ar table Ns > +.Pp +A userinfo table looks as follows: +.Bd -literal -offset indent +joe 1000:100:/home/virtual/joe +jack 1000:100:/home/virtual/jack +.Ed +.Pp +In this example, both joe and jack are virtual users mapped to the local +system user with UID 1000 and GID 100, but different home directories. +These directories may contain a +.Xr forward 5 +file. +This can be used in conjunction with an alias table +that maps an email address or the domain part to the desired virtual +username. +For example: +.Bd -literal -offset indent +joe@example.org joe +jack@example.com jack +.Ed +.Ss Source tables +Source tables are lists of IPv4 and IPv6 addresses. +They can only be used in the following context: +.Pp +.D1 Ic action Ar name Cm relay src Pf < Ar table Ns > +.Pp +Successive queries to the source table will return the elements one by one. +.Pp +A source table looks as follow: +.Bd -literal -offset indent +192.168.1.2 +192.168.1.3 +::1 +::2 +ipv6:::3 +ipv6:::4 +.Ed +.Ss Mailaddr tables +Mailaddr tables are lists of email addresses. +They can be used in the following contexts: +.Bd -unfilled -offset indent +.Ic match Cm mail\-from Pf < Ar table Ns > Cm action Ar name +.Ic match Cm rcpt\-to Pf < Ar table Ns > Cm action Ar name +.Ed +.Pp +A mailaddr entry is used to match an email address against a username, +a domain or a full email address. +A "*" wildcard may be used in part of the domain name. +.Pp +A mailaddr table looks as follow: +.Bd -literal -offset indent +user +@domain +user@domain +user@*.domain +.Ed +.Ss Addrname tables +Addrname tables are used to map IP addresses to hostnames. +They can be used in both listen context and relay context: +.Bd -unfilled -offset indent +.Ic listen on Ar interface Cm hostnames Pf < Ar table Ns > +.Ic action Ar name Cm relay helo\-src Pf < Ar table Ns > +.Ed +.Pp +In listen context, the table is used to look up the server name to advertise +depending on the local address of the socket on which a connection is accepted. +In relay context, the table is used to determine the hostname for the HELO +sequence of the SMTP protocol, depending on the local address used for the +outgoing connection. +.Pp +The format is a mapping from inet4 or inet6 addresses to hostnames: +.Bd -literal -offset indent +::1 localhost +127.0.0.1 localhost +88.190.23.165 www.opensmtpd.org +.Ed +.Sh SEE ALSO +.Xr smtpd.conf 5 , +.Xr makemap 8 , +.Xr smtpd 8 diff --git a/foobar/portable/smtpd/table.c b/foobar/portable/smtpd/table.c new file mode 100644 index 00000000..469eeee1 --- /dev/null +++ b/foobar/portable/smtpd/table.c @@ -0,0 +1,709 @@ +/* $OpenBSD: table.c,v 1.48 2019/01/10 07:40:52 eric Exp $ */ + +/* + * Copyright (c) 2013 Eric Faurot <eric@openbsd.org> + * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <netinet/in.h> +#include <arpa/inet.h> +#include <net/if.h> + +#include <errno.h> +#include <event.h> +#include <imsg.h> +#include <stdio.h> +#include <stdlib.h> +#include <regex.h> +#include <limits.h> +#include <string.h> +#include <unistd.h> + +#include "smtpd.h" +#include "log.h" + +struct table_backend *table_backend_lookup(const char *); + +extern struct table_backend table_backend_static; +#ifdef HAVE_DB_API +extern struct table_backend table_backend_db; +#endif +extern struct table_backend table_backend_getpwnam; +extern struct table_backend table_backend_proc; + +static const char * table_service_name(enum table_service); +static int table_parse_lookup(enum table_service, const char *, const char *, + union lookup *); +static int parse_sockaddr(struct sockaddr *, int, const char *); + +static unsigned int last_table_id = 0; + +static struct table_backend *backends[] = { + &table_backend_static, +#ifdef HAVE_DB_API + &table_backend_db, +#endif + &table_backend_getpwnam, + &table_backend_proc, + NULL +}; + +struct table_backend * +table_backend_lookup(const char *backend) +{ + int i; + + if (!strcmp(backend, "file")) + backend = "static"; + + for (i = 0; backends[i]; i++) + if (!strcmp(backends[i]->name, backend)) + return (backends[i]); + + return NULL; +} + +static const char * +table_service_name(enum table_service s) +{ + switch (s) { + case K_NONE: return "NONE"; + case K_ALIAS: return "ALIAS"; + case K_DOMAIN: return "DOMAIN"; + case K_CREDENTIALS: return "CREDENTIALS"; + case K_NETADDR: return "NETADDR"; + case K_USERINFO: return "USERINFO"; + case K_SOURCE: return "SOURCE"; + case K_MAILADDR: return "MAILADDR"; + case K_ADDRNAME: return "ADDRNAME"; + case K_MAILADDRMAP: return "MAILADDRMAP"; + case K_RELAYHOST: return "RELAYHOST"; + case K_STRING: return "STRING"; + case K_REGEX: return "REGEX"; + } + return "???"; +} + +struct table * +table_find(struct smtpd *conf, const char *name) +{ + return dict_get(conf->sc_tables_dict, name); +} + +int +table_match(struct table *table, enum table_service kind, const char *key) +{ + return table_lookup(table, kind, key, NULL); +} + +int +table_lookup(struct table *table, enum table_service kind, const char *key, + union lookup *lk) +{ + char lkey[1024], *buf = NULL; + int r; + + r = -1; + if (table->t_backend->lookup == NULL) + errno = ENOTSUP; + else if (!lowercase(lkey, key, sizeof lkey)) { + log_warnx("warn: lookup key too long: %s", key); + errno = EINVAL; + } + else + r = table->t_backend->lookup(table, kind, lkey, lk ? &buf : NULL); + + if (r == 1) { + log_trace(TRACE_LOOKUP, "lookup: %s \"%s\" as %s in table %s:%s -> %s%s%s", + lk ? "lookup" : "match", + key, + table_service_name(kind), + table->t_backend->name, + table->t_name, + lk ? "\"" : "", + lk ? buf : "true", + lk ? "\"" : ""); + if (buf) + r = table_parse_lookup(kind, lkey, buf, lk); + } + else + log_trace(TRACE_LOOKUP, "lookup: %s \"%s\" as %s in table %s:%s -> %s%s", + lk ? "lookup" : "match", + key, + table_service_name(kind), + table->t_backend->name, + table->t_name, + (r == -1) ? "error: " : (lk ? "none" : "false"), + (r == -1) ? strerror(errno) : ""); + + free(buf); + + return (r); +} + +int +table_fetch(struct table *table, enum table_service kind, union lookup *lk) +{ + char *buf = NULL; + int r; + + r = -1; + if (table->t_backend->fetch == NULL) + errno = ENOTSUP; + else + r = table->t_backend->fetch(table, kind, &buf); + + if (r == 1) { + log_trace(TRACE_LOOKUP, "lookup: fetch %s from table %s:%s -> \"%s\"", + table_service_name(kind), + table->t_backend->name, + table->t_name, + buf); + r = table_parse_lookup(kind, NULL, buf, lk); + } + else + log_trace(TRACE_LOOKUP, "lookup: fetch %s from table %s:%s -> %s%s", + table_service_name(kind), + table->t_backend->name, + table->t_name, + (r == -1) ? "error: " : "none", + (r == -1) ? strerror(errno) : ""); + + free(buf); + + return (r); +} + +struct table * +table_create(struct smtpd *conf, const char *backend, const char *name, + const char *config) +{ + struct table *t; + struct table_backend *tb; + char path[LINE_MAX]; + size_t n; + struct stat sb; + + if (name && table_find(conf, name)) + fatalx("table_create: table \"%s\" already defined", name); + + if ((tb = table_backend_lookup(backend)) == NULL) { + if ((size_t)snprintf(path, sizeof(path), PATH_LIBEXEC"/table-%s", + backend) >= sizeof(path)) { + fatalx("table_create: path too long \"" + PATH_LIBEXEC"/table-%s\"", backend); + } + if (stat(path, &sb) == 0) { + tb = table_backend_lookup("proc"); + (void)strlcpy(path, backend, sizeof(path)); + if (config) { + (void)strlcat(path, ":", sizeof(path)); + if (strlcat(path, config, sizeof(path)) + >= sizeof(path)) + fatalx("table_create: config file path too long"); + } + config = path; + } + } + + if (tb == NULL) + fatalx("table_create: backend \"%s\" does not exist", backend); + + t = xcalloc(1, sizeof(*t)); + t->t_backend = tb; + + if (config) { + if (strlcpy(t->t_config, config, sizeof t->t_config) + >= sizeof t->t_config) + fatalx("table_create: table config \"%s\" too large", + t->t_config); + } + + if (strcmp(tb->name, "static") != 0) + t->t_type = T_DYNAMIC; + + if (name == NULL) + (void)snprintf(t->t_name, sizeof(t->t_name), "<dynamic:%u>", + last_table_id++); + else { + n = strlcpy(t->t_name, name, sizeof(t->t_name)); + if (n >= sizeof(t->t_name)) + fatalx("table_create: table name too long"); + } + + dict_set(conf->sc_tables_dict, t->t_name, t); + + return (t); +} + +void +table_destroy(struct smtpd *conf, struct table *t) +{ + dict_xpop(conf->sc_tables_dict, t->t_name); + free(t); +} + +int +table_config(struct table *t) +{ + if (t->t_backend->config == NULL) + return (1); + return (t->t_backend->config(t)); +} + +void +table_add(struct table *t, const char *key, const char *val) +{ + if (t->t_backend->add == NULL) + fatalx("table_add: cannot add to table"); + + if (t->t_backend->add(t, key, val) == 0) + log_warnx("warn: failed to add \"%s\" in table \"%s\"", key, t->t_name); +} + +void +table_dump(struct table *t) +{ + const char *type; + char buf[LINE_MAX]; + + switch(t->t_type) { + case T_NONE: + type = "NONE"; + break; + case T_DYNAMIC: + type = "DYNAMIC"; + break; + case T_LIST: + type = "LIST"; + break; + case T_HASH: + type = "HASH"; + break; + default: + type = "???"; + break; + } + + if (t->t_config[0]) + snprintf(buf, sizeof(buf), " config=\"%s\"", t->t_config); + else + buf[0] = '\0'; + + log_debug("TABLE \"%s\" backend=%s type=%s%s", t->t_name, + t->t_backend->name, type, buf); + + if (t->t_backend->dump) + t->t_backend->dump(t); +} + +int +table_check_type(struct table *t, uint32_t mask) +{ + return t->t_type & mask; +} + +int +table_check_service(struct table *t, uint32_t mask) +{ + return t->t_backend->services & mask; +} + +int +table_check_use(struct table *t, uint32_t tmask, uint32_t smask) +{ + return table_check_type(t, tmask) && table_check_service(t, smask); +} + +int +table_open(struct table *t) +{ + if (t->t_backend->open == NULL) + return (1); + return (t->t_backend->open(t)); +} + +void +table_close(struct table *t) +{ + if (t->t_backend->close) + t->t_backend->close(t); +} + +int +table_update(struct table *t) +{ + if (t->t_backend->update == NULL) + return (1); + return (t->t_backend->update(t)); +} + + +/* + * quick reminder: + * in *_match() s1 comes from session, s2 comes from table + */ + +int +table_domain_match(const char *s1, const char *s2) +{ + return hostname_match(s1, s2); +} + +int +table_mailaddr_match(const char *s1, const char *s2) +{ + struct mailaddr m1; + struct mailaddr m2; + + if (!text_to_mailaddr(&m1, s1)) + return 0; + if (!text_to_mailaddr(&m2, s2)) + return 0; + return mailaddr_match(&m1, &m2); +} + +static int table_match_mask(struct sockaddr_storage *, struct netaddr *); +static int table_inet4_match(struct sockaddr_in *, struct netaddr *); +static int table_inet6_match(struct sockaddr_in6 *, struct netaddr *); + +int +table_netaddr_match(const char *s1, const char *s2) +{ + struct netaddr n1; + struct netaddr n2; + + if (strcasecmp(s1, s2) == 0) + return 1; + if (!text_to_netaddr(&n1, s1)) + return 0; + if (!text_to_netaddr(&n2, s2)) + return 0; + if (n1.ss.ss_family != n2.ss.ss_family) + return 0; + if (SS_LEN(&n1.ss) != SS_LEN(&n2.ss)) + return 0; + return table_match_mask(&n1.ss, &n2); +} + +static int +table_match_mask(struct sockaddr_storage *ss, struct netaddr *ssmask) +{ + if (ss->ss_family == AF_INET) + return table_inet4_match((struct sockaddr_in *)ss, ssmask); + + if (ss->ss_family == AF_INET6) + return table_inet6_match((struct sockaddr_in6 *)ss, ssmask); + + return (0); +} + +static int +table_inet4_match(struct sockaddr_in *ss, struct netaddr *ssmask) +{ + in_addr_t mask; + int i; + + /* a.b.c.d/8 -> htonl(0xff000000) */ + mask = 0; + for (i = 0; i < ssmask->bits; ++i) + mask = (mask >> 1) | 0x80000000; + mask = htonl(mask); + + /* (addr & mask) == (net & mask) */ + if ((ss->sin_addr.s_addr & mask) == + (((struct sockaddr_in *)ssmask)->sin_addr.s_addr & mask)) + return 1; + + return 0; +} + +static int +table_inet6_match(struct sockaddr_in6 *ss, struct netaddr *ssmask) +{ + struct in6_addr *in; + struct in6_addr *inmask; + struct in6_addr mask; + int i; + + memset(&mask, 0, sizeof(mask)); + for (i = 0; i < ssmask->bits / 8; i++) + mask.s6_addr[i] = 0xff; + i = ssmask->bits % 8; + if (i) + mask.s6_addr[ssmask->bits / 8] = 0xff00 >> i; + + in = &ss->sin6_addr; + inmask = &((struct sockaddr_in6 *)&ssmask->ss)->sin6_addr; + + for (i = 0; i < 16; i++) { + if ((in->s6_addr[i] & mask.s6_addr[i]) != + (inmask->s6_addr[i] & mask.s6_addr[i])) + return (0); + } + + return (1); +} + +int +table_regex_match(const char *string, const char *pattern) +{ + regex_t preg; + int cflags = REG_EXTENDED|REG_NOSUB; + + if (strncmp(pattern, "(?i)", 4) == 0) { + cflags |= REG_ICASE; + pattern += 4; + } + + if (regcomp(&preg, pattern, cflags) != 0) + return (0); + + if (regexec(&preg, string, 0, NULL, 0) != 0) + return (0); + + return (1); +} + +void +table_dump_all(struct smtpd *conf) +{ + struct table *t; + void *iter; + + iter = NULL; + while (dict_iter(conf->sc_tables_dict, &iter, NULL, (void **)&t)) + table_dump(t); +} + +void +table_open_all(struct smtpd *conf) +{ + struct table *t; + void *iter; + + iter = NULL; + while (dict_iter(conf->sc_tables_dict, &iter, NULL, (void **)&t)) + if (!table_open(t)) + fatalx("failed to open table %s", t->t_name); +} + +void +table_close_all(struct smtpd *conf) +{ + struct table *t; + void *iter; + + iter = NULL; + while (dict_iter(conf->sc_tables_dict, &iter, NULL, (void **)&t)) + table_close(t); +} + +static int +table_parse_lookup(enum table_service service, const char *key, + const char *line, union lookup *lk) +{ + char buffer[LINE_MAX], *p; + size_t len; + + len = strlen(line); + + switch (service) { + case K_ALIAS: + lk->expand = calloc(1, sizeof(*lk->expand)); + if (lk->expand == NULL) + return (-1); + if (!expand_line(lk->expand, line, 1)) { + expand_free(lk->expand); + return (-1); + } + return (1); + + case K_DOMAIN: + if (strlcpy(lk->domain.name, line, sizeof(lk->domain.name)) + >= sizeof(lk->domain.name)) + return (-1); + return (1); + + case K_CREDENTIALS: + + /* credentials are stored as user:password */ + if (len < 3) + return (-1); + + /* too big to fit in a smtp session line */ + if (len >= LINE_MAX) + return (-1); + + p = strchr(line, ':'); + if (p == NULL) { + if (strlcpy(lk->creds.username, key, sizeof (lk->creds.username)) + >= sizeof (lk->creds.username)) + return (-1); + if (strlcpy(lk->creds.password, line, sizeof(lk->creds.password)) + >= sizeof(lk->creds.password)) + return (-1); + return (1); + } + + if (p == line || p == line + len - 1) + return (-1); + + memmove(lk->creds.username, line, p - line); + lk->creds.username[p - line] = '\0'; + + if (strlcpy(lk->creds.password, p+1, sizeof(lk->creds.password)) + >= sizeof(lk->creds.password)) + return (-1); + + return (1); + + case K_NETADDR: + if (!text_to_netaddr(&lk->netaddr, line)) + return (-1); + return (1); + + case K_USERINFO: + if (!bsnprintf(buffer, sizeof(buffer), "%s:%s", key, line)) + return (-1); + if (!text_to_userinfo(&lk->userinfo, buffer)) + return (-1); + return (1); + + case K_SOURCE: + if (parse_sockaddr((struct sockaddr *)&lk->source.addr, + PF_UNSPEC, line) == -1) + return (-1); + return (1); + + case K_MAILADDR: + if (!text_to_mailaddr(&lk->mailaddr, line)) + return (-1); + return (1); + + case K_MAILADDRMAP: + lk->maddrmap = calloc(1, sizeof(*lk->maddrmap)); + if (lk->maddrmap == NULL) + return (-1); + maddrmap_init(lk->maddrmap); + if (!mailaddr_line(lk->maddrmap, line)) { + maddrmap_free(lk->maddrmap); + return (-1); + } + return (1); + + case K_ADDRNAME: + if (parse_sockaddr((struct sockaddr *)&lk->addrname.addr, + PF_UNSPEC, key) == -1) + return (-1); + if (strlcpy(lk->addrname.name, line, sizeof(lk->addrname.name)) + >= sizeof(lk->addrname.name)) + return (-1); + return (1); + + case K_RELAYHOST: + if (strlcpy(lk->relayhost, line, sizeof(lk->relayhost)) + >= sizeof(lk->relayhost)) + return (-1); + return (1); + + default: + return (-1); + } +} + +static int +parse_sockaddr(struct sockaddr *sa, int family, const char *str) +{ + struct in_addr ina; + struct in6_addr in6a; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + char *cp, *str2; + const char *errstr; + + switch (family) { + case PF_UNSPEC: + if (parse_sockaddr(sa, PF_INET, str) == 0) + return (0); + return parse_sockaddr(sa, PF_INET6, str); + + case PF_INET: + if (inet_pton(PF_INET, str, &ina) != 1) + return (-1); + + sin = (struct sockaddr_in *)sa; + memset(sin, 0, sizeof *sin); +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + sin->sin_len = sizeof(struct sockaddr_in); +#endif + sin->sin_family = PF_INET; + sin->sin_addr.s_addr = ina.s_addr; + return (0); + + case PF_INET6: + if (strncasecmp("ipv6:", str, 5) == 0) + str += 5; + cp = strchr(str, SCOPE_DELIMITER); + if (cp) { + str2 = strdup(str); + if (str2 == NULL) + return (-1); + str2[cp - str] = '\0'; + if (inet_pton(PF_INET6, str2, &in6a) != 1) { + free(str2); + return (-1); + } + cp++; + free(str2); + } else if (inet_pton(PF_INET6, str, &in6a) != 1) + return (-1); + + sin6 = (struct sockaddr_in6 *)sa; + memset(sin6, 0, sizeof *sin6); +#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN + sin6->sin6_len = sizeof(struct sockaddr_in6); +#endif + sin6->sin6_family = PF_INET6; + sin6->sin6_addr = in6a; + + if (cp == NULL) + return (0); + + if (IN6_IS_ADDR_LINKLOCAL(&in6a) || + IN6_IS_ADDR_MC_LINKLOCAL(&in6a) || + IN6_IS_ADDR_MC_NODELOCAL(&in6a)) + if ((sin6->sin6_scope_id = if_nametoindex(cp))) + return (0); + + sin6->sin6_scope_id = strtonum(cp, 0, UINT32_MAX, &errstr); + if (errstr) + return (-1); + return (0); + + default: + break; + } + + return (-1); +} diff --git a/foobar/portable/smtpd/table_db.c b/foobar/portable/smtpd/table_db.c new file mode 100644 index 00000000..f7d766dd --- /dev/null +++ b/foobar/portable/smtpd/table_db.c @@ -0,0 +1,282 @@ +/* $OpenBSD: table_db.c,v 1.21 2019/06/28 13:32:51 deraadt Exp $ */ + +/* + * Copyright (c) 2011 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> + +#include <netinet/in.h> +#include <arpa/inet.h> +#ifdef HAVE_DB_H +#include <db.h> +#elif defined(HAVE_DB1_DB_H) +#include <db1/db.h> +#elif defined(HAVE_DB_185_H) +#include <db_185.h> +#endif +#include <ctype.h> +#include <err.h> +#include <event.h> +#include <fcntl.h> +#include <imsg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "smtpd.h" +#include "log.h" + + +/* db(3) backend */ +static int table_db_config(struct table *); +static int table_db_update(struct table *); +static int table_db_open(struct table *); +static void *table_db_open2(struct table *); +static int table_db_lookup(struct table *, enum table_service, const char *, char **); +static int table_db_fetch(struct table *, enum table_service, char **); +static void table_db_close(struct table *); +static void table_db_close2(void *); + +static char *table_db_get_entry(void *, const char *, size_t *); +static char *table_db_get_entry_match(void *, const char *, size_t *, + int(*)(const char *, const char *)); + +struct table_backend table_backend_db = { + "db", + K_ALIAS|K_CREDENTIALS|K_DOMAIN|K_NETADDR|K_USERINFO|K_SOURCE|K_MAILADDR|K_ADDRNAME|K_MAILADDRMAP, + table_db_config, + NULL, + NULL, + table_db_open, + table_db_update, + table_db_close, + table_db_lookup, + table_db_fetch, +}; + +static struct keycmp { + enum table_service service; + int (*func)(const char *, const char *); +} keycmp[] = { + { K_DOMAIN, table_domain_match }, + { K_NETADDR, table_netaddr_match }, + { K_MAILADDR, table_mailaddr_match } +}; + +struct dbhandle { + DB *db; + char pathname[PATH_MAX]; + time_t mtime; + int iter; +}; + +static int +table_db_config(struct table *table) +{ + struct dbhandle *handle; + + handle = table_db_open2(table); + if (handle == NULL) + return 0; + + table_db_close2(handle); + return 1; +} + +static int +table_db_update(struct table *table) +{ + struct dbhandle *handle; + + handle = table_db_open2(table); + if (handle == NULL) + return 0; + + table_db_close2(table->t_handle); + table->t_handle = handle; + return 1; +} + +static int +table_db_open(struct table *table) +{ + table->t_handle = table_db_open2(table); + if (table->t_handle == NULL) + return 0; + return 1; +} + +static void +table_db_close(struct table *table) +{ + table_db_close2(table->t_handle); + table->t_handle = NULL; +} + +static void * +table_db_open2(struct table *table) +{ + struct dbhandle *handle; + struct stat sb; + + handle = xcalloc(1, sizeof *handle); + if (strlcpy(handle->pathname, table->t_config, sizeof handle->pathname) + >= sizeof handle->pathname) + goto error; + + if (stat(handle->pathname, &sb) == -1) + goto error; + + handle->mtime = sb.st_mtime; + handle->db = dbopen(table->t_config, O_RDONLY, 0600, DB_HASH, NULL); + if (handle->db == NULL) + goto error; + + return handle; + +error: + if (handle->db) + handle->db->close(handle->db); + free(handle); + return NULL; +} + +static void +table_db_close2(void *hdl) +{ + struct dbhandle *handle = hdl; + handle->db->close(handle->db); + free(handle); +} + +static int +table_db_lookup(struct table *table, enum table_service service, const char *key, + char **dst) +{ + struct dbhandle *handle = table->t_handle; + char *line; + size_t len = 0; + int ret; + int (*match)(const char *, const char *) = NULL; + size_t i; + struct stat sb; + + if (stat(handle->pathname, &sb) == -1) + return -1; + + /* DB has changed, close and reopen */ + if (sb.st_mtime != handle->mtime) { + table_db_update(table); + handle = table->t_handle; + } + + for (i = 0; i < nitems(keycmp); ++i) + if (keycmp[i].service == service) + match = keycmp[i].func; + + if (match == NULL) + line = table_db_get_entry(handle, key, &len); + else + line = table_db_get_entry_match(handle, key, &len, match); + if (line == NULL) + return 0; + + ret = 1; + if (dst) + *dst = line; + else + free(line); + + return ret; +} + +static int +table_db_fetch(struct table *table, enum table_service service, char **dst) +{ + struct dbhandle *handle = table->t_handle; + DBT dbk; + DBT dbd; + int r; + + if (handle->iter == 0) + r = handle->db->seq(handle->db, &dbk, &dbd, R_FIRST); + else + r = handle->db->seq(handle->db, &dbk, &dbd, R_NEXT); + handle->iter = 1; + if (!r) { + r = handle->db->seq(handle->db, &dbk, &dbd, R_FIRST); + if (!r) + return 0; + } + + *dst = strdup(dbk.data); + if (*dst == NULL) + return -1; + + return 1; +} + + +static char * +table_db_get_entry_match(void *hdl, const char *key, size_t *len, + int(*func)(const char *, const char *)) +{ + struct dbhandle *handle = hdl; + DBT dbk; + DBT dbd; + int r; + char *buf = NULL; + + for (r = handle->db->seq(handle->db, &dbk, &dbd, R_FIRST); !r; + r = handle->db->seq(handle->db, &dbk, &dbd, R_NEXT)) { + buf = xmemdup(dbk.data, dbk.size); + if (func(key, buf)) { + *len = dbk.size; + return buf; + } + free(buf); + } + return NULL; +} + +static char * +table_db_get_entry(void *hdl, const char *key, size_t *len) +{ + struct dbhandle *handle = hdl; + int ret; + DBT dbk; + DBT dbv; + char pkey[LINE_MAX]; + + /* workaround the stupidity of the DB interface */ + if (strlcpy(pkey, key, sizeof pkey) >= sizeof pkey) + errx(1, "table_db_get_entry: key too long"); + dbk.data = pkey; + dbk.size = strlen(pkey) + 1; + + if ((ret = handle->db->get(handle->db, &dbk, &dbv, 0)) != 0) + return NULL; + + *len = dbv.size; + + return xmemdup(dbv.data, dbv.size); +} diff --git a/foobar/portable/smtpd/table_getpwnam.c b/foobar/portable/smtpd/table_getpwnam.c new file mode 100644 index 00000000..ccf889be --- /dev/null +++ b/foobar/portable/smtpd/table_getpwnam.c @@ -0,0 +1,120 @@ +/* $OpenBSD: table_getpwnam.c,v 1.12 2018/12/27 14:23:41 eric Exp $ */ + +/* + * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <imsg.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <string.h> + +#include "smtpd.h" +#include "log.h" + + +/* getpwnam(3) backend */ +static int table_getpwnam_config(struct table *); +static int table_getpwnam_update(struct table *); +static int table_getpwnam_open(struct table *); +static int table_getpwnam_lookup(struct table *, enum table_service, const char *, + char **); +static void table_getpwnam_close(struct table *); + +struct table_backend table_backend_getpwnam = { + "getpwnam", + K_USERINFO, + table_getpwnam_config, + NULL, + NULL, + table_getpwnam_open, + table_getpwnam_update, + table_getpwnam_close, + table_getpwnam_lookup, +}; + + +static int +table_getpwnam_config(struct table *table) +{ + if (table->t_config[0]) + return 0; + return 1; +} + +static int +table_getpwnam_update(struct table *table) +{ + return 1; +} + +static int +table_getpwnam_open(struct table *table) +{ + return 1; +} + +static void +table_getpwnam_close(struct table *table) +{ + return; +} + +static int +table_getpwnam_lookup(struct table *table, enum table_service kind, const char *key, + char **dst) +{ + struct passwd *pw; + + if (kind != K_USERINFO) + return -1; + + errno = 0; + do { + pw = getpwnam(key); + } while (pw == NULL && errno == EINTR); + + if (pw == NULL) { + if (errno) + return -1; + return 0; + } + if (dst == NULL) + return 1; + + if (asprintf(dst, "%d:%d:%s", + pw->pw_uid, + pw->pw_gid, + pw->pw_dir) == -1) { + *dst = NULL; + return -1; + } + + return (1); +} diff --git a/foobar/portable/smtpd/table_proc.c b/foobar/portable/smtpd/table_proc.c new file mode 100644 index 00000000..44589bd7 --- /dev/null +++ b/foobar/portable/smtpd/table_proc.c @@ -0,0 +1,283 @@ +/* $OpenBSD: table_proc.c,v 1.16 2019/10/03 04:51:15 gilles Exp $ */ + +/* + * Copyright (c) 2013 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> + +#include <ctype.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <imsg.h> +#ifdef HAVE_PATHS_H +#include <paths.h> +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#include <unistd.h> + +#include "smtpd.h" +#include "log.h" + +struct table_proc_priv { + pid_t pid; + struct imsgbuf ibuf; +}; + +static struct imsg imsg; +static size_t rlen; +static char *rdata; + +extern char **environ; + +static void +table_proc_call(struct table_proc_priv *p) +{ + ssize_t n; + + if (imsg_flush(&p->ibuf) == -1) { + log_warn("warn: table-proc: imsg_flush"); + fatalx("table-proc: exiting"); + } + + while (1) { + if ((n = imsg_get(&p->ibuf, &imsg)) == -1) { + log_warn("warn: table-proc: imsg_get"); + break; + } + if (n) { + rlen = imsg.hdr.len - IMSG_HEADER_SIZE; + rdata = imsg.data; + + if (imsg.hdr.type != PROC_TABLE_OK) { + log_warnx("warn: table-proc: bad response"); + break; + } + return; + } + + if ((n = imsg_read(&p->ibuf)) == -1 && errno != EAGAIN) { + log_warn("warn: table-proc: imsg_read"); + break; + } + + if (n == 0) { + log_warnx("warn: table-proc: pipe closed"); + break; + } + } + + fatalx("table-proc: exiting"); +} + +static void +table_proc_read(void *dst, size_t len) +{ + if (len > rlen) { + log_warnx("warn: table-proc: bad msg len"); + fatalx("table-proc: exiting"); + } + + if (dst) + memmove(dst, rdata, len); + + rlen -= len; + rdata += len; +} + +static void +table_proc_end(void) +{ + if (rlen) { + log_warnx("warn: table-proc: bogus data"); + fatalx("table-proc: exiting"); + } + imsg_free(&imsg); +} + +/* + * API + */ + +static int +table_proc_open(struct table *table) +{ + struct table_proc_priv *priv; + struct table_open_params op; + int fd; + + priv = xcalloc(1, sizeof(*priv)); + + fd = fork_proc_backend("table", table->t_config, table->t_name); + if (fd == -1) + fatalx("table-proc: exiting"); + + imsg_init(&priv->ibuf, fd); + + memset(&op, 0, sizeof op); + op.version = PROC_TABLE_API_VERSION; + (void)strlcpy(op.name, table->t_name, sizeof op.name); + imsg_compose(&priv->ibuf, PROC_TABLE_OPEN, 0, 0, -1, &op, sizeof op); + + table_proc_call(priv); + table_proc_end(); + + table->t_handle = priv; + + return (1); +} + +static int +table_proc_update(struct table *table) +{ + struct table_proc_priv *priv = table->t_handle; + int r; + + imsg_compose(&priv->ibuf, PROC_TABLE_UPDATE, 0, 0, -1, NULL, 0); + + table_proc_call(priv); + table_proc_read(&r, sizeof(r)); + table_proc_end(); + + return (r); +} + +static void +table_proc_close(struct table *table) +{ + struct table_proc_priv *priv = table->t_handle; + + imsg_compose(&priv->ibuf, PROC_TABLE_CLOSE, 0, 0, -1, NULL, 0); + if (imsg_flush(&priv->ibuf) == -1) + fatal("imsg_flush"); + + table->t_handle = NULL; +} + +static int +imsg_add_params(struct ibuf *buf) +{ + size_t count = 0; + + if (imsg_add(buf, &count, sizeof(count)) == -1) + return (-1); + + return (0); +} + +static int +table_proc_lookup(struct table *table, enum table_service s, const char *k, char **dst) +{ + struct table_proc_priv *priv = table->t_handle; + struct ibuf *buf; + int r; + + buf = imsg_create(&priv->ibuf, + dst ? PROC_TABLE_LOOKUP : PROC_TABLE_CHECK, 0, 0, + sizeof(s) + strlen(k) + 1); + + if (buf == NULL) + return (-1); + if (imsg_add(buf, &s, sizeof(s)) == -1) + return (-1); + if (imsg_add_params(buf) == -1) + return (-1); + if (imsg_add(buf, k, strlen(k) + 1) == -1) + return (-1); + imsg_close(&priv->ibuf, buf); + + table_proc_call(priv); + table_proc_read(&r, sizeof(r)); + + if (r == 1 && dst) { + if (rlen == 0) { + log_warnx("warn: table-proc: empty response"); + fatalx("table-proc: exiting"); + } + if (rdata[rlen - 1] != '\0') { + log_warnx("warn: table-proc: not NUL-terminated"); + fatalx("table-proc: exiting"); + } + *dst = strdup(rdata); + if (*dst == NULL) + r = -1; + table_proc_read(NULL, rlen); + } + + table_proc_end(); + + return (r); +} + +static int +table_proc_fetch(struct table *table, enum table_service s, char **dst) +{ + struct table_proc_priv *priv = table->t_handle; + struct ibuf *buf; + int r; + + buf = imsg_create(&priv->ibuf, PROC_TABLE_FETCH, 0, 0, sizeof(s)); + if (buf == NULL) + return (-1); + if (imsg_add(buf, &s, sizeof(s)) == -1) + return (-1); + if (imsg_add_params(buf) == -1) + return (-1); + imsg_close(&priv->ibuf, buf); + + table_proc_call(priv); + table_proc_read(&r, sizeof(r)); + + if (r == 1) { + if (rlen == 0) { + log_warnx("warn: table-proc: empty response"); + fatalx("table-proc: exiting"); + } + if (rdata[rlen - 1] != '\0') { + log_warnx("warn: table-proc: not NUL-terminated"); + fatalx("table-proc: exiting"); + } + *dst = strdup(rdata); + if (*dst == NULL) + r = -1; + table_proc_read(NULL, rlen); + } + + table_proc_end(); + + return (r); +} + +struct table_backend table_backend_proc = { + "proc", + K_ANY, + NULL, + NULL, + NULL, + table_proc_open, + table_proc_update, + table_proc_close, + table_proc_lookup, + table_proc_fetch, +}; diff --git a/foobar/portable/smtpd/table_static.c b/foobar/portable/smtpd/table_static.c new file mode 100644 index 00000000..8f78ae11 --- /dev/null +++ b/foobar/portable/smtpd/table_static.c @@ -0,0 +1,398 @@ +/* $OpenBSD: table_static.c,v 1.32 2018/12/28 14:21:02 eric Exp $ */ + +/* + * Copyright (c) 2013 Eric Faurot <eric@openbsd.org> + * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <ctype.h> +#include <errno.h> + +#include <event.h> +#include <fcntl.h> +#include <imsg.h> +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <string.h> + +#include "smtpd.h" +#include "log.h" + +struct table_static_priv { + int type; + struct dict dict; + void *iter; +}; + +/* static backend */ +static int table_static_config(struct table *); +static int table_static_add(struct table *, const char *, const char *); +static void table_static_dump(struct table *); +static int table_static_update(struct table *); +static int table_static_open(struct table *); +static int table_static_lookup(struct table *, enum table_service, const char *, + char **); +static int table_static_fetch(struct table *, enum table_service, char **); +static void table_static_close(struct table *); + +struct table_backend table_backend_static = { + "static", + K_ALIAS|K_CREDENTIALS|K_DOMAIN|K_NETADDR|K_USERINFO| + K_SOURCE|K_MAILADDR|K_ADDRNAME|K_MAILADDRMAP|K_RELAYHOST| + K_STRING|K_REGEX, + table_static_config, + table_static_add, + table_static_dump, + table_static_open, + table_static_update, + table_static_close, + table_static_lookup, + table_static_fetch +}; + +static struct keycmp { + enum table_service service; + int (*func)(const char *, const char *); +} keycmp[] = { + { K_DOMAIN, table_domain_match }, + { K_NETADDR, table_netaddr_match }, + { K_MAILADDR, table_mailaddr_match }, + { K_REGEX, table_regex_match }, +}; + + +static void +table_static_priv_free(struct table_static_priv *priv) +{ + void *p; + + while (dict_poproot(&priv->dict, (void **)&p)) + if (p != priv) + free(p); + free(priv); +} + +static int +table_static_priv_add(struct table_static_priv *priv, const char *key, const char *val) +{ + char lkey[1024]; + void *old, *new = NULL; + + if (!lowercase(lkey, key, sizeof lkey)) { + errno = ENAMETOOLONG; + return (-1); + } + + if (val) { + new = strdup(val); + if (new == NULL) + return (-1); + } + + /* use priv if value is null, so we can detect duplicate entries */ + old = dict_set(&priv->dict, lkey, new ? new : priv); + if (old) { + if (old != priv) + free(old); + return (1); + } + + return (0); +} + +static int +table_static_priv_load(struct table_static_priv *priv, const char *path) +{ + FILE *fp; + char *buf = NULL, *p; + int lineno = 0; + size_t sz = 0; + ssize_t flen; + char *keyp; + char *valp; + int ret = 0; + + if ((fp = fopen(path, "r")) == NULL) { + log_warn("%s: fopen", path); + return 0; + } + + while ((flen = getline(&buf, &sz, fp)) != -1) { + lineno++; + if (buf[flen - 1] == '\n') + buf[--flen] = '\0'; + + keyp = buf; + while (isspace((unsigned char)*keyp)) { + ++keyp; + --flen; + } + if (*keyp == '\0') + continue; + while (isspace((unsigned char)keyp[flen - 1])) + keyp[--flen] = '\0'; + if (*keyp == '#') { + if (priv->type == T_NONE) { + keyp++; + while (isspace((unsigned char)*keyp)) + ++keyp; + if (!strcmp(keyp, "@list")) + priv->type = T_LIST; + } + continue; + } + + if (priv->type == T_NONE) { + for (p = keyp; *p; p++) { + if (*p == ' ' || *p == '\t' || *p == ':') { + priv->type = T_HASH; + break; + } + } + if (priv->type == T_NONE) + priv->type = T_LIST; + } + + if (priv->type == T_LIST) { + table_static_priv_add(priv, keyp, NULL); + continue; + } + + /* T_HASH */ + valp = keyp; + strsep(&valp, " \t:"); + if (valp) { + while (*valp) { + if (!isspace((unsigned char)*valp) && + !(*valp == ':' && + isspace((unsigned char)*(valp + 1)))) + break; + ++valp; + } + if (*valp == '\0') + valp = NULL; + } + if (valp == NULL) { + log_warnx("%s: invalid map entry line %d", + path, lineno); + goto end; + } + + table_static_priv_add(priv, keyp, valp); + } + + if (ferror(fp)) { + log_warn("%s: getline", path); + goto end; + } + + /* Accept empty alias files; treat them as hashes */ + if (priv->type == T_NONE) + priv->type = T_HASH; + + ret = 1; +end: + free(buf); + fclose(fp); + return ret; +} + +static int +table_static_config(struct table *t) +{ + struct table_static_priv *priv, *old; + + /* already up, and no config file? ok */ + if (t->t_handle && *t->t_config == '\0') + return 1; + + /* new config */ + priv = calloc(1, sizeof(*priv)); + if (priv == NULL) + return 0; + priv->type = t->t_type; + dict_init(&priv->dict); + + if (*t->t_config) { + /* load the config file */ + if (table_static_priv_load(priv, t->t_config) == 0) { + table_static_priv_free(priv); + return 0; + } + } + + if ((old = t->t_handle)) + table_static_priv_free(old); + t->t_handle = priv; + t->t_type = priv->type; + + return 1; +} + +static int +table_static_add(struct table *table, const char *key, const char *val) +{ + struct table_static_priv *priv = table->t_handle; + int r; + + /* cannot add to a table read from a file */ + if (*table->t_config) + return 0; + + if (table->t_type == T_NONE) + table->t_type = val ? T_HASH : T_LIST; + else if (table->t_type == T_LIST && val) + return 0; + else if (table->t_type == T_HASH && val == NULL) + return 0; + + if (priv == NULL) { + if (table_static_config(table) == 0) + return 0; + priv = table->t_handle; + } + + r = table_static_priv_add(priv, key, val); + if (r == -1) + return 0; + return 1; +} + +static void +table_static_dump(struct table *table) +{ + struct table_static_priv *priv = table->t_handle; + const char *key; + char *value; + void *iter; + + iter = NULL; + while (dict_iter(&priv->dict, &iter, &key, (void**)&value)) { + if (value && (void*)value != (void*)priv) + log_debug(" \"%s\" -> \"%s\"", key, value); + else + log_debug(" \"%s\"", key); + } +} + +static int +table_static_update(struct table *table) +{ + if (table_static_config(table) == 1) { + log_info("info: Table \"%s\" successfully updated", table->t_name); + return 1; + } + + log_info("info: Failed to update table \"%s\"", table->t_name); + return 0; +} + +static int +table_static_open(struct table *table) +{ + if (table->t_handle == NULL) + return table_static_config(table); + return 1; +} + +static void +table_static_close(struct table *table) +{ + struct table_static_priv *priv = table->t_handle; + + if (priv) + table_static_priv_free(priv); + table->t_handle = NULL; +} + +static int +table_static_lookup(struct table *table, enum table_service service, const char *key, + char **dst) +{ + struct table_static_priv *priv = table->t_handle; + char *line; + int ret; + int (*match)(const char *, const char *) = NULL; + size_t i; + void *iter; + const char *k; + char *v; + + for (i = 0; i < nitems(keycmp); ++i) + if (keycmp[i].service == service) + match = keycmp[i].func; + + line = NULL; + iter = NULL; + ret = 0; + while (dict_iter(&priv->dict, &iter, &k, (void **)&v)) { + if (match) { + if (match(key, k)) { + line = v; + ret = 1; + } + } + else { + if (strcmp(key, k) == 0) { + line = v; + ret = 1; + } + } + if (ret) + break; + } + + if (dst == NULL) + return ret ? 1 : 0; + + if (ret == 0) + return 0; + + *dst = strdup(line); + if (*dst == NULL) + return -1; + + return 1; +} + +static int +table_static_fetch(struct table *t, enum table_service service, char **dst) +{ + struct table_static_priv *priv = t->t_handle; + const char *k; + + if (!dict_iter(&priv->dict, &priv->iter, &k, (void **)NULL)) { + priv->iter = NULL; + if (!dict_iter(&priv->dict, &priv->iter, &k, (void **)NULL)) + return 0; + } + + *dst = strdup(k); + if (*dst == NULL) + return -1; + + return 1; +} diff --git a/foobar/portable/smtpd/to.c b/foobar/portable/smtpd/to.c new file mode 100644 index 00000000..81a1bb54 --- /dev/null +++ b/foobar/portable/smtpd/to.c @@ -0,0 +1,880 @@ +/* $OpenBSD: to.c,v 1.44 2019/11/12 20:21:46 gilles Exp $ */ + +/* + * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/resource.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <imsg.h> +#include <limits.h> +#include <inttypes.h> +#include <netdb.h> +#include <pwd.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "smtpd.h" +#include "log.h" + +static const char *in6addr_to_text(const struct in6_addr *); +static int alias_is_filter(struct expandnode *, const char *, size_t); +static int alias_is_username(struct expandnode *, const char *, size_t); +static int alias_is_address(struct expandnode *, const char *, size_t); +static int alias_is_filename(struct expandnode *, const char *, size_t); +static int alias_is_include(struct expandnode *, const char *, size_t); +static int alias_is_error(struct expandnode *, const char *, size_t); + +static int broken_inet_net_pton_ipv6(const char *, void *, size_t); + +const char * +sockaddr_to_text(struct sockaddr *sa) +{ + static char buf[NI_MAXHOST]; + + if (getnameinfo(sa, SA_LEN(sa), buf, sizeof(buf), NULL, 0, + NI_NUMERICHOST)) + return ("(unknown)"); + else + return (buf); +} + +static const char * +in6addr_to_text(const struct in6_addr *addr) +{ + struct sockaddr_in6 sa_in6; + uint16_t tmp16; + + memset(&sa_in6, 0, sizeof(sa_in6)); +#ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN + sa_in6.sin6_len = sizeof(sa_in6); +#endif + sa_in6.sin6_family = AF_INET6; + memcpy(&sa_in6.sin6_addr, addr, sizeof(sa_in6.sin6_addr)); + + /* XXX thanks, KAME, for this ugliness... adopted from route/show.c */ + if (IN6_IS_ADDR_LINKLOCAL(&sa_in6.sin6_addr) || + IN6_IS_ADDR_MC_LINKLOCAL(&sa_in6.sin6_addr)) { + memcpy(&tmp16, &sa_in6.sin6_addr.s6_addr[2], sizeof(tmp16)); + sa_in6.sin6_scope_id = ntohs(tmp16); + sa_in6.sin6_addr.s6_addr[2] = 0; + sa_in6.sin6_addr.s6_addr[3] = 0; + } + + return (sockaddr_to_text((struct sockaddr *)&sa_in6)); +} + +int +text_to_mailaddr(struct mailaddr *maddr, const char *email) +{ + char *username; + char *hostname; + char buffer[LINE_MAX]; + + if (strlcpy(buffer, email, sizeof buffer) >= sizeof buffer) + return 0; + + memset(maddr, 0, sizeof *maddr); + + username = buffer; + hostname = strrchr(username, '@'); + + if (hostname == NULL) { + if (strlcpy(maddr->user, username, sizeof maddr->user) + >= sizeof maddr->user) + return 0; + } + else if (username == hostname) { + *hostname++ = '\0'; + if (strlcpy(maddr->domain, hostname, sizeof maddr->domain) + >= sizeof maddr->domain) + return 0; + } + else { + *hostname++ = '\0'; + if (strlcpy(maddr->user, username, sizeof maddr->user) + >= sizeof maddr->user) + return 0; + if (strlcpy(maddr->domain, hostname, sizeof maddr->domain) + >= sizeof maddr->domain) + return 0; + } + + return 1; +} + +const char * +mailaddr_to_text(const struct mailaddr *maddr) +{ + static char buffer[LINE_MAX]; + + (void)strlcpy(buffer, maddr->user, sizeof buffer); + (void)strlcat(buffer, "@", sizeof buffer); + if (strlcat(buffer, maddr->domain, sizeof buffer) >= sizeof buffer) + return NULL; + + return buffer; +} + + +const char * +sa_to_text(const struct sockaddr *sa) +{ + static char buf[NI_MAXHOST + 5]; + char *p; + + buf[0] = '\0'; + p = buf; + + if (sa->sa_family == AF_LOCAL) + (void)strlcpy(buf, "local", sizeof buf); + else if (sa->sa_family == AF_INET) { + in_addr_t addr; + + addr = ((const struct sockaddr_in *)sa)->sin_addr.s_addr; + addr = ntohl(addr); + (void)bsnprintf(p, NI_MAXHOST, "%d.%d.%d.%d", + (addr >> 24) & 0xff, (addr >> 16) & 0xff, + (addr >> 8) & 0xff, addr & 0xff); + } + else if (sa->sa_family == AF_INET6) { + const struct sockaddr_in6 *in6; + const struct in6_addr *in6_addr; + + in6 = (const struct sockaddr_in6 *)sa; + p = buf; + in6_addr = &in6->sin6_addr; + (void)bsnprintf(p, NI_MAXHOST, "[%s]", in6addr_to_text(in6_addr)); + } + + return (buf); +} + +const char * +ss_to_text(const struct sockaddr_storage *ss) +{ + return (sa_to_text((const struct sockaddr*)ss)); +} + +const char * +time_to_text(time_t when) +{ + struct tm *lt; + static char buf[40]; + char *day[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + char *month[] = {"Jan","Feb","Mar","Apr","May","Jun", + "Jul","Aug","Sep","Oct","Nov","Dec"}; + const char *tz; + long offset; + + lt = localtime(&when); + if (lt == NULL || when == 0) + fatalx("time_to_text: localtime"); + +#if HAVE_STRUCT_TM_TM_GMTOFF + offset = lt->tm_gmtoff; + tz = lt->tm_zone; +#elif defined HAVE_DECL_ALTZONE && defined HAVE_DECL_TIMEZONE + offset = lt->tm_isdst > 0 ? altzone : timezone; + tz = lt->tm_isdst > 0 ? tzname[1] : tzname[0]; +#endif + + /* We do not use strftime because it is subject to locale substitution*/ + if (!bsnprintf(buf, sizeof(buf), + "%s, %d %s %d %02d:%02d:%02d %c%02d%02d (%s)", + day[lt->tm_wday], lt->tm_mday, month[lt->tm_mon], + lt->tm_year + 1900, + lt->tm_hour, lt->tm_min, lt->tm_sec, + offset >= 0 ? '+' : '-', + abs((int)offset / 3600), + abs((int)offset % 3600) / 60, + tz)) + fatalx("time_to_text: bsnprintf"); + + return buf; +} + +const char * +duration_to_text(time_t t) +{ + static char dst[64]; + char buf[64]; + int h, m, s; + long long d; + + if (t == 0) { + (void)strlcpy(dst, "0s", sizeof dst); + return (dst); + } + + dst[0] = '\0'; + if (t < 0) { + (void)strlcpy(dst, "-", sizeof dst); + t = -t; + } + + s = t % 60; + t /= 60; + m = t % 60; + t /= 60; + h = t % 24; + d = t / 24; + + if (d) { + (void)snprintf(buf, sizeof buf, "%lldd", d); + (void)strlcat(dst, buf, sizeof dst); + } + if (h) { + (void)snprintf(buf, sizeof buf, "%dh", h); + (void)strlcat(dst, buf, sizeof dst); + } + if (m) { + (void)snprintf(buf, sizeof buf, "%dm", m); + (void)strlcat(dst, buf, sizeof dst); + } + if (s) { + (void)snprintf(buf, sizeof buf, "%ds", s); + (void)strlcat(dst, buf, sizeof dst); + } + + return (dst); +} + +int +text_to_netaddr(struct netaddr *netaddr, const char *s) +{ + struct sockaddr_storage ss; + struct sockaddr_in ssin; + struct sockaddr_in6 ssin6; + int bits; + char buf[NI_MAXHOST]; + size_t len; + + memset(&ssin, 0, sizeof(struct sockaddr_in)); + memset(&ssin6, 0, sizeof(struct sockaddr_in6)); + + if (strncasecmp("IPv6:", s, 5) == 0) + s += 5; + + bits = inet_net_pton(AF_INET, s, &ssin.sin_addr, + sizeof(struct in_addr)); + if (bits != -1) { + ssin.sin_family = AF_INET; + memcpy(&ss, &ssin, sizeof(ssin)); +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN + ss.ss_len = sizeof(struct sockaddr_in); +#endif + } else { + if (s[0] != '[') { + if ((len = strlcpy(buf, s, sizeof buf)) >= sizeof buf) + return 0; + } + else { + s++; + if (strncasecmp("IPv6:", s, 5) == 0) + s += 5; + if ((len = strlcpy(buf, s, sizeof buf)) >= sizeof buf) + return 0; + if (buf[len-1] != ']') + return 0; + buf[len-1] = 0; + } + bits = inet_net_pton(AF_INET6, buf, &ssin6.sin6_addr, + sizeof(struct in6_addr)); + if (bits == -1) { + if (errno != EAFNOSUPPORT) + return 0; + bits = broken_inet_net_pton_ipv6(buf, &ssin6.sin6_addr, + sizeof(struct in6_addr)); + if (bits == -1) + return 0; + } + ssin6.sin6_family = AF_INET6; + memcpy(&ss, &ssin6, sizeof(ssin6)); +#ifdef HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN + ss.ss_len = sizeof(struct sockaddr_in6); +#endif + } + + netaddr->ss = ss; + netaddr->bits = bits; + return 1; +} + +int +text_to_relayhost(struct relayhost *relay, const char *s) +{ + static const struct schema { + const char *name; + int tls; + uint16_t flags; + uint16_t port; + } schemas [] = { + /* + * new schemas should be *appended* otherwise the default + * schema index needs to be updated later in this function. + */ + { "smtp://", RELAY_TLS_OPPORTUNISTIC, 0, 25 }, + { "smtp+tls://", RELAY_TLS_STARTTLS, 0, 25 }, + { "smtp+notls://", RELAY_TLS_NO, 0, 25 }, + /* need to specify an explicit port for LMTP */ + { "lmtp://", RELAY_TLS_NO, RELAY_LMTP, 0 }, + { "smtps://", RELAY_TLS_SMTPS, 0, 465 } + }; + const char *errstr = NULL; + char *p, *q; + char buffer[1024]; + char *beg, *end; + size_t i; + size_t len; + + memset(buffer, 0, sizeof buffer); + if (strlcpy(buffer, s, sizeof buffer) >= sizeof buffer) + return 0; + + for (i = 0; i < nitems(schemas); ++i) + if (strncasecmp(schemas[i].name, s, + strlen(schemas[i].name)) == 0) + break; + + if (i == nitems(schemas)) { + /* there is a schema, but it's not recognized */ + if (strstr(buffer, "://")) + return 0; + + /* no schema, default to smtp:// */ + i = 0; + p = buffer; + } + else + p = buffer + strlen(schemas[i].name); + + relay->tls = schemas[i].tls; + relay->flags = schemas[i].flags; + relay->port = schemas[i].port; + + /* first, we extract the label if any */ + if ((q = strchr(p, '@')) != NULL) { + *q = 0; + if (strlcpy(relay->authlabel, p, sizeof (relay->authlabel)) + >= sizeof (relay->authlabel)) + return 0; + p = q + 1; + } + + /* then, we extract the mail exchanger */ + beg = end = p; + if (*beg == '[') { + if ((end = strchr(beg, ']')) == NULL) + return 0; + /* skip ']', it has to be included in the relay hostname */ + ++end; + len = end - beg; + } + else { + for (end = beg; *end; ++end) + if (!isalnum((unsigned char)*end) && + *end != '_' && *end != '.' && *end != '-') + break; + len = end - beg; + } + if (len >= sizeof relay->hostname) + return 0; + for (i = 0; i < len; ++i) + relay->hostname[i] = beg[i]; + relay->hostname[i] = 0; + + /* finally, we extract the port */ + p = beg + len; + if (*p == ':') { + relay->port = strtonum(p+1, 1, IPPORT_HILASTAUTO, &errstr); + if (errstr) + return 0; + } + + if (!valid_domainpart(relay->hostname)) + return 0; + if ((relay->flags & RELAY_LMTP) && (relay->port == 0)) + return 0; + if (relay->authlabel[0]) { + /* disallow auth on non-tls scheme. */ + if (relay->tls != RELAY_TLS_STARTTLS && + relay->tls != RELAY_TLS_SMTPS) + return 0; + relay->flags |= RELAY_AUTH; + } + + return 1; +} + +uint64_t +text_to_evpid(const char *s) +{ + uint64_t ulval; + char *ep; + + errno = 0; + ulval = strtoull(s, &ep, 16); + if (s[0] == '\0' || *ep != '\0') + return 0; + if (errno == ERANGE && ulval == ULLONG_MAX) + return 0; + if (ulval == 0) + return 0; + return (ulval); +} + +uint32_t +text_to_msgid(const char *s) +{ + uint64_t ulval; + char *ep; + + errno = 0; + ulval = strtoull(s, &ep, 16); + if (s[0] == '\0' || *ep != '\0') + return 0; + if (errno == ERANGE && ulval == ULLONG_MAX) + return 0; + if (ulval == 0) + return 0; + if (ulval > 0xffffffff) + return 0; + return (ulval & 0xffffffff); +} + +const char * +rule_to_text(struct rule *r) +{ + static char buf[4096]; + + memset(buf, 0, sizeof buf); + (void)strlcpy(buf, "match", sizeof buf); + if (r->flag_tag) { + if (r->flag_tag < 0) + (void)strlcat(buf, " !", sizeof buf); + (void)strlcat(buf, " tag ", sizeof buf); + (void)strlcat(buf, r->table_tag, sizeof buf); + } + + if (r->flag_from) { + if (r->flag_from < 0) + (void)strlcat(buf, " !", sizeof buf); + if (r->flag_from_socket) + (void)strlcat(buf, " from socket", sizeof buf); + else if (r->flag_from_rdns) { + (void)strlcat(buf, " from rdns", sizeof buf); + if (r->table_from) { + (void)strlcat(buf, " ", sizeof buf); + (void)strlcat(buf, r->table_from, sizeof buf); + } + } + else if (strcmp(r->table_from, "<anyhost>") == 0) + (void)strlcat(buf, " from any", sizeof buf); + else if (strcmp(r->table_from, "<localhost>") == 0) + (void)strlcat(buf, " from local", sizeof buf); + else { + (void)strlcat(buf, " from src ", sizeof buf); + (void)strlcat(buf, r->table_from, sizeof buf); + } + } + + if (r->flag_for) { + if (r->flag_for < 0) + (void)strlcat(buf, " !", sizeof buf); + if (strcmp(r->table_for, "<anydestination>") == 0) + (void)strlcat(buf, " for any", sizeof buf); + else if (strcmp(r->table_for, "<localnames>") == 0) + (void)strlcat(buf, " for local", sizeof buf); + else { + (void)strlcat(buf, " for domain ", sizeof buf); + (void)strlcat(buf, r->table_for, sizeof buf); + } + } + + if (r->flag_smtp_helo) { + if (r->flag_smtp_helo < 0) + (void)strlcat(buf, " !", sizeof buf); + (void)strlcat(buf, " helo ", sizeof buf); + (void)strlcat(buf, r->table_smtp_helo, sizeof buf); + } + + if (r->flag_smtp_auth) { + if (r->flag_smtp_auth < 0) + (void)strlcat(buf, " !", sizeof buf); + (void)strlcat(buf, " auth", sizeof buf); + if (r->table_smtp_auth) { + (void)strlcat(buf, " ", sizeof buf); + (void)strlcat(buf, r->table_smtp_auth, sizeof buf); + } + } + + if (r->flag_smtp_starttls) { + if (r->flag_smtp_starttls < 0) + (void)strlcat(buf, " !", sizeof buf); + (void)strlcat(buf, " tls", sizeof buf); + } + + if (r->flag_smtp_mail_from) { + if (r->flag_smtp_mail_from < 0) + (void)strlcat(buf, " !", sizeof buf); + (void)strlcat(buf, " mail-from ", sizeof buf); + (void)strlcat(buf, r->table_smtp_mail_from, sizeof buf); + } + + if (r->flag_smtp_rcpt_to) { + if (r->flag_smtp_rcpt_to < 0) + (void)strlcat(buf, " !", sizeof buf); + (void)strlcat(buf, " rcpt-to ", sizeof buf); + (void)strlcat(buf, r->table_smtp_rcpt_to, sizeof buf); + } + (void)strlcat(buf, " action ", sizeof buf); + if (r->reject) + (void)strlcat(buf, "reject", sizeof buf); + else + (void)strlcat(buf, r->dispatcher, sizeof buf); + return buf; +} + + +int +text_to_userinfo(struct userinfo *userinfo, const char *s) +{ + char buf[PATH_MAX]; + char *p; + const char *errstr; + + memset(buf, 0, sizeof buf); + p = buf; + while (*s && *s != ':') + *p++ = *s++; + if (*s++ != ':') + goto error; + + if (strlcpy(userinfo->username, buf, + sizeof userinfo->username) >= sizeof userinfo->username) + goto error; + + memset(buf, 0, sizeof buf); + p = buf; + while (*s && *s != ':') + *p++ = *s++; + if (*s++ != ':') + goto error; + userinfo->uid = strtonum(buf, 0, UID_MAX, &errstr); + if (errstr) + goto error; + + memset(buf, 0, sizeof buf); + p = buf; + while (*s && *s != ':') + *p++ = *s++; + if (*s++ != ':') + goto error; + userinfo->gid = strtonum(buf, 0, GID_MAX, &errstr); + if (errstr) + goto error; + + if (strlcpy(userinfo->directory, s, + sizeof userinfo->directory) >= sizeof userinfo->directory) + goto error; + + return 1; + +error: + return 0; +} + +int +text_to_credentials(struct credentials *creds, const char *s) +{ + char *p; + char buffer[LINE_MAX]; + size_t offset; + + p = strchr(s, ':'); + if (p == NULL) { + creds->username[0] = '\0'; + if (strlcpy(creds->password, s, sizeof creds->password) + >= sizeof creds->password) + return 0; + return 1; + } + + offset = p - s; + + memset(buffer, 0, sizeof buffer); + if (strlcpy(buffer, s, sizeof buffer) >= sizeof buffer) + return 0; + p = buffer + offset; + *p = '\0'; + + if (strlcpy(creds->username, buffer, sizeof creds->username) + >= sizeof creds->username) + return 0; + if (strlcpy(creds->password, p+1, sizeof creds->password) + >= sizeof creds->password) + return 0; + + return 1; +} + +int +text_to_expandnode(struct expandnode *expandnode, const char *s) +{ + size_t l; + + l = strlen(s); + if (alias_is_error(expandnode, s, l) || + alias_is_include(expandnode, s, l) || + alias_is_filter(expandnode, s, l) || + alias_is_filename(expandnode, s, l) || + alias_is_address(expandnode, s, l) || + alias_is_username(expandnode, s, l)) + return (1); + + return (0); +} + +const char * +expandnode_to_text(struct expandnode *expandnode) +{ + switch (expandnode->type) { + case EXPAND_FILTER: + case EXPAND_FILENAME: + case EXPAND_INCLUDE: + case EXPAND_ERROR: + case EXPAND_USERNAME: + return expandnode->u.user; + case EXPAND_ADDRESS: + return mailaddr_to_text(&expandnode->u.mailaddr); + case EXPAND_INVALID: + break; + } + + return NULL; +} + +/******/ +static int +alias_is_filter(struct expandnode *alias, const char *line, size_t len) +{ + int v = 0; + + if (*line == '"') + v = 1; + if (*(line+v) == '|') { + if (strlcpy(alias->u.buffer, line + v + 1, + sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer)) + return 0; + if (v) { + v = strlen(alias->u.buffer); + if (v == 0) + return (0); + if (alias->u.buffer[v-1] != '"') + return (0); + alias->u.buffer[v-1] = '\0'; + } + alias->type = EXPAND_FILTER; + return (1); + } + return (0); +} + +static int +alias_is_username(struct expandnode *alias, const char *line, size_t len) +{ + memset(alias, 0, sizeof *alias); + + if (strlcpy(alias->u.user, line, + sizeof(alias->u.user)) >= sizeof(alias->u.user)) + return 0; + + while (*line) { + if (!isalnum((unsigned char)*line) && + *line != '_' && *line != '.' && *line != '-' && *line != '+') + return 0; + ++line; + } + + alias->type = EXPAND_USERNAME; + return 1; +} + +static int +alias_is_address(struct expandnode *alias, const char *line, size_t len) +{ + char *domain; + + memset(alias, 0, sizeof *alias); + + if (len < 3) /* x@y */ + return 0; + + domain = strchr(line, '@'); + if (domain == NULL) + return 0; + + /* @ cannot start or end an address */ + if (domain == line || domain == line + len - 1) + return 0; + + /* scan pre @ for disallowed chars */ + *domain++ = '\0'; + (void)strlcpy(alias->u.mailaddr.user, line, sizeof(alias->u.mailaddr.user)); + (void)strlcpy(alias->u.mailaddr.domain, domain, + sizeof(alias->u.mailaddr.domain)); + + while (*line) { + char allowedset[] = "!#$%*/?|^{}`~&'+-=_."; + if (!isalnum((unsigned char)*line) && + strchr(allowedset, *line) == NULL) + return 0; + ++line; + } + + while (*domain) { + char allowedset[] = "-."; + if (!isalnum((unsigned char)*domain) && + strchr(allowedset, *domain) == NULL) + return 0; + ++domain; + } + + alias->type = EXPAND_ADDRESS; + return 1; +} + +static int +alias_is_filename(struct expandnode *alias, const char *line, size_t len) +{ + memset(alias, 0, sizeof *alias); + + if (*line != '/') + return 0; + + if (strlcpy(alias->u.buffer, line, + sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer)) + return 0; + alias->type = EXPAND_FILENAME; + return 1; +} + +static int +alias_is_include(struct expandnode *alias, const char *line, size_t len) +{ + size_t skip; + + memset(alias, 0, sizeof *alias); + + if (strncasecmp(":include:", line, 9) == 0) + skip = 9; + else if (strncasecmp("include:", line, 8) == 0) + skip = 8; + else + return 0; + + if (!alias_is_filename(alias, line + skip, len - skip)) + return 0; + + alias->type = EXPAND_INCLUDE; + return 1; +} + +static int +alias_is_error(struct expandnode *alias, const char *line, size_t len) +{ + size_t skip; + + memset(alias, 0, sizeof *alias); + + if (strncasecmp(":error:", line, 7) == 0) + skip = 7; + else if (strncasecmp("error:", line, 6) == 0) + skip = 6; + else + return 0; + + if (strlcpy(alias->u.buffer, line + skip, + sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer)) + return 0; + + if (strlen(alias->u.buffer) < 5) + return 0; + + /* [45][0-9]{2} [a-zA-Z0-9].* */ + if (alias->u.buffer[3] != ' ' || + !isalnum((unsigned char)alias->u.buffer[4]) || + (alias->u.buffer[0] != '4' && alias->u.buffer[0] != '5') || + !isdigit((unsigned char)alias->u.buffer[1]) || + !isdigit((unsigned char)alias->u.buffer[2])) + return 0; + + alias->type = EXPAND_ERROR; + return 1; +} + +static int +broken_inet_net_pton_ipv6(const char *src, void *dst, size_t size) +{ + int ret; + int bits; + char buf[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255:255:255:255/128")]; + char *sep; + const char *errstr; + + if (strlcpy(buf, src, sizeof buf) >= sizeof buf) { + errno = EMSGSIZE; + return (-1); + } + + sep = strchr(buf, '/'); + if (sep != NULL) + *sep++ = '\0'; + + ret = inet_pton(AF_INET6, buf, dst); + if (ret != 1) + return (-1); + + if (sep == NULL) + return 128; + + bits = strtonum(sep, 0, 128, &errstr); + if (errstr) + return (-1); + + return bits; +} diff --git a/foobar/portable/smtpd/tree.c b/foobar/portable/smtpd/tree.c new file mode 100644 index 00000000..1d720a59 --- /dev/null +++ b/foobar/portable/smtpd/tree.c @@ -0,0 +1,259 @@ +/* $OpenBSD: tree.c,v 1.6 2018/12/23 16:06:24 gilles Exp $ */ + +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/tree.h> + +#include <err.h> +#include <inttypes.h> +#include <stdlib.h> +#include <limits.h> + +#include "tree.h" + +struct treeentry { + SPLAY_ENTRY(treeentry) entry; + uint64_t id; + void *data; +}; + +static int treeentry_cmp(struct treeentry *, struct treeentry *); + +SPLAY_PROTOTYPE(_tree, treeentry, entry, treeentry_cmp); + +int +tree_check(struct tree *t, uint64_t id) +{ + struct treeentry key; + + key.id = id; + return (SPLAY_FIND(_tree, &t->tree, &key) != NULL); +} + +void * +tree_set(struct tree *t, uint64_t id, void *data) +{ + struct treeentry *entry, key; + char *old; + + key.id = id; + if ((entry = SPLAY_FIND(_tree, &t->tree, &key)) == NULL) { + if ((entry = malloc(sizeof *entry)) == NULL) + err(1, "tree_set: malloc"); + entry->id = id; + SPLAY_INSERT(_tree, &t->tree, entry); + old = NULL; + t->count += 1; + } else + old = entry->data; + + entry->data = data; + + return (old); +} + +void +tree_xset(struct tree *t, uint64_t id, void *data) +{ + struct treeentry *entry; + + if ((entry = malloc(sizeof *entry)) == NULL) + err(1, "tree_xset: malloc"); + entry->id = id; + entry->data = data; + if (SPLAY_INSERT(_tree, &t->tree, entry)) + errx(1, "tree_xset(%p, 0x%016"PRIx64 ")", t, id); + t->count += 1; +} + +void * +tree_get(struct tree *t, uint64_t id) +{ + struct treeentry key, *entry; + + key.id = id; + if ((entry = SPLAY_FIND(_tree, &t->tree, &key)) == NULL) + return (NULL); + + return (entry->data); +} + +void * +tree_xget(struct tree *t, uint64_t id) +{ + struct treeentry key, *entry; + + key.id = id; + if ((entry = SPLAY_FIND(_tree, &t->tree, &key)) == NULL) + errx(1, "tree_get(%p, 0x%016"PRIx64 ")", t, id); + + return (entry->data); +} + +void * +tree_pop(struct tree *t, uint64_t id) +{ + struct treeentry key, *entry; + void *data; + + key.id = id; + if ((entry = SPLAY_FIND(_tree, &t->tree, &key)) == NULL) + return (NULL); + + data = entry->data; + SPLAY_REMOVE(_tree, &t->tree, entry); + free(entry); + t->count -= 1; + + return (data); +} + +void * +tree_xpop(struct tree *t, uint64_t id) +{ + struct treeentry key, *entry; + void *data; + + key.id = id; + if ((entry = SPLAY_FIND(_tree, &t->tree, &key)) == NULL) + errx(1, "tree_xpop(%p, 0x%016" PRIx64 ")", t, id); + + data = entry->data; + SPLAY_REMOVE(_tree, &t->tree, entry); + free(entry); + t->count -= 1; + + return (data); +} + +int +tree_poproot(struct tree *t, uint64_t *id, void **data) +{ + struct treeentry *entry; + + entry = SPLAY_ROOT(&t->tree); + if (entry == NULL) + return (0); + if (id) + *id = entry->id; + if (data) + *data = entry->data; + SPLAY_REMOVE(_tree, &t->tree, entry); + free(entry); + t->count -= 1; + + return (1); +} + +int +tree_root(struct tree *t, uint64_t *id, void **data) +{ + struct treeentry *entry; + + entry = SPLAY_ROOT(&t->tree); + if (entry == NULL) + return (0); + if (id) + *id = entry->id; + if (data) + *data = entry->data; + return (1); +} + +int +tree_iter(struct tree *t, void **hdl, uint64_t *id, void **data) +{ + struct treeentry *curr = *hdl; + + if (curr == NULL) + curr = SPLAY_MIN(_tree, &t->tree); + else + curr = SPLAY_NEXT(_tree, &t->tree, curr); + + if (curr) { + *hdl = curr; + if (id) + *id = curr->id; + if (data) + *data = curr->data; + return (1); + } + + return (0); +} + +int +tree_iterfrom(struct tree *t, void **hdl, uint64_t k, uint64_t *id, void **data) +{ + struct treeentry *curr = *hdl, key; + + if (curr == NULL) { + if (k == 0) + curr = SPLAY_MIN(_tree, &t->tree); + else { + key.id = k; + curr = SPLAY_FIND(_tree, &t->tree, &key); + if (curr == NULL) { + SPLAY_INSERT(_tree, &t->tree, &key); + curr = SPLAY_NEXT(_tree, &t->tree, &key); + SPLAY_REMOVE(_tree, &t->tree, &key); + } + } + } else + curr = SPLAY_NEXT(_tree, &t->tree, curr); + + if (curr) { + *hdl = curr; + if (id) + *id = curr->id; + if (data) + *data = curr->data; + return (1); + } + + return (0); +} + +void +tree_merge(struct tree *dst, struct tree *src) +{ + struct treeentry *entry; + + while (!SPLAY_EMPTY(&src->tree)) { + entry = SPLAY_ROOT(&src->tree); + SPLAY_REMOVE(_tree, &src->tree, entry); + if (SPLAY_INSERT(_tree, &dst->tree, entry)) + errx(1, "tree_merge: duplicate"); + } + dst->count += src->count; + src->count = 0; +} + +static int +treeentry_cmp(struct treeentry *a, struct treeentry *b) +{ + if (a->id < b->id) + return (-1); + if (a->id > b->id) + return (1); + return (0); +} + +SPLAY_GENERATE(_tree, treeentry, entry, treeentry_cmp); diff --git a/foobar/portable/smtpd/tree.h b/foobar/portable/smtpd/tree.h new file mode 100644 index 00000000..3d719f09 --- /dev/null +++ b/foobar/portable/smtpd/tree.h @@ -0,0 +1,48 @@ +/* $OpenBSD: tree.h,v 1.1 2018/12/23 16:06:24 gilles Exp $ */ + +/* + * Copyright (c) 2013 Eric Faurot <eric@openbsd.org> + * Copyright (c) 2011 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _TREE_H_ +#define _TREE_H_ + +SPLAY_HEAD(_tree, treeentry); + +struct tree { + struct _tree tree; + size_t count; +}; + + +/* tree.c */ +#define tree_init(t) do { SPLAY_INIT(&((t)->tree)); (t)->count = 0; } while(0) +#define tree_empty(t) SPLAY_EMPTY(&((t)->tree)) +#define tree_count(t) ((t)->count) +int tree_check(struct tree *, uint64_t); +void *tree_set(struct tree *, uint64_t, void *); +void tree_xset(struct tree *, uint64_t, void *); +void *tree_get(struct tree *, uint64_t); +void *tree_xget(struct tree *, uint64_t); +void *tree_pop(struct tree *, uint64_t); +void *tree_xpop(struct tree *, uint64_t); +int tree_poproot(struct tree *, uint64_t *, void **); +int tree_root(struct tree *, uint64_t *, void **); +int tree_iter(struct tree *, void **, uint64_t *, void **); +int tree_iterfrom(struct tree *, void **, uint64_t, uint64_t *, void **); +void tree_merge(struct tree *, struct tree *); + +#endif diff --git a/foobar/portable/smtpd/unpack_dns.c b/foobar/portable/smtpd/unpack_dns.c new file mode 100644 index 00000000..974d5727 --- /dev/null +++ b/foobar/portable/smtpd/unpack_dns.c @@ -0,0 +1,300 @@ +/* $OpenBSD: unpack_dns.c,v 1.1 2018/01/06 07:57:53 sunil Exp $ */ + +/* + * Copyright (c) 2011-2014 Eric Faurot <eric@faurot.net> + * + * 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 "includes.h" + +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +#include <arpa/nameser_compat.h> +#endif +#include <arpa/inet.h> + +#include <string.h> + +#include "unpack_dns.h" + +static int unpack_data(struct unpack *, void *, size_t); +static int unpack_u16(struct unpack *, uint16_t *); +static int unpack_u32(struct unpack *, uint32_t *); +static int unpack_inaddr(struct unpack *, struct in_addr *); +static int unpack_in6addr(struct unpack *, struct in6_addr *); +static int unpack_dname(struct unpack *, char *, size_t); + +void +unpack_init(struct unpack *unpack, const char *buf, size_t len) +{ + unpack->buf = buf; + unpack->len = len; + unpack->offset = 0; + unpack->err = NULL; +} + +int +unpack_header(struct unpack *p, struct dns_header *h) +{ + if (unpack_data(p, h, HFIXEDSZ) == -1) + return (-1); + + h->flags = ntohs(h->flags); + h->qdcount = ntohs(h->qdcount); + h->ancount = ntohs(h->ancount); + h->nscount = ntohs(h->nscount); + h->arcount = ntohs(h->arcount); + + return (0); +} + +int +unpack_query(struct unpack *p, struct dns_query *q) +{ + unpack_dname(p, q->q_dname, sizeof(q->q_dname)); + unpack_u16(p, &q->q_type); + unpack_u16(p, &q->q_class); + + return (p->err) ? (-1) : (0); +} + +int +unpack_rr(struct unpack *p, struct dns_rr *rr) +{ + uint16_t rdlen; + size_t save_offset; + + unpack_dname(p, rr->rr_dname, sizeof(rr->rr_dname)); + unpack_u16(p, &rr->rr_type); + unpack_u16(p, &rr->rr_class); + unpack_u32(p, &rr->rr_ttl); + unpack_u16(p, &rdlen); + + if (p->err) + return (-1); + + if (p->len - p->offset < rdlen) { + p->err = "too short"; + return (-1); + } + + save_offset = p->offset; + + switch (rr->rr_type) { + + case T_CNAME: + unpack_dname(p, rr->rr.cname.cname, sizeof(rr->rr.cname.cname)); + break; + + case T_MX: + unpack_u16(p, &rr->rr.mx.preference); + unpack_dname(p, rr->rr.mx.exchange, sizeof(rr->rr.mx.exchange)); + break; + + case T_NS: + unpack_dname(p, rr->rr.ns.nsname, sizeof(rr->rr.ns.nsname)); + break; + + case T_PTR: + unpack_dname(p, rr->rr.ptr.ptrname, sizeof(rr->rr.ptr.ptrname)); + break; + + case T_SOA: + unpack_dname(p, rr->rr.soa.mname, sizeof(rr->rr.soa.mname)); + unpack_dname(p, rr->rr.soa.rname, sizeof(rr->rr.soa.rname)); + unpack_u32(p, &rr->rr.soa.serial); + unpack_u32(p, &rr->rr.soa.refresh); + unpack_u32(p, &rr->rr.soa.retry); + unpack_u32(p, &rr->rr.soa.expire); + unpack_u32(p, &rr->rr.soa.minimum); + break; + + case T_A: + if (rr->rr_class != C_IN) + goto other; + unpack_inaddr(p, &rr->rr.in_a.addr); + break; + + case T_AAAA: + if (rr->rr_class != C_IN) + goto other; + unpack_in6addr(p, &rr->rr.in_aaaa.addr6); + break; + default: + other: + rr->rr.other.rdata = p->buf + p->offset; + rr->rr.other.rdlen = rdlen; + p->offset += rdlen; + } + + if (p->err) + return (-1); + + /* make sure that the advertised rdlen is really ok */ + if (p->offset - save_offset != rdlen) + p->err = "bad dlen"; + + return (p->err) ? (-1) : (0); +} + +ssize_t +dname_expand(const unsigned char *data, size_t len, size_t offset, + size_t *newoffset, char *dst, size_t max) +{ + size_t n, count, end, ptr, start; + ssize_t res; + + if (offset >= len) + return (-1); + + res = 0; + end = start = offset; + + for (; (n = data[offset]); ) { + if ((n & 0xc0) == 0xc0) { + if (offset + 2 > len) + return (-1); + ptr = 256 * (n & ~0xc0) + data[offset + 1]; + if (ptr >= start) + return (-1); + if (end < offset + 2) + end = offset + 2; + offset = start = ptr; + continue; + } + if (offset + n + 1 > len) + return (-1); + + /* copy n + at offset+1 */ + if (dst != NULL && max != 0) { + count = (max < n + 1) ? (max) : (n + 1); + memmove(dst, data + offset, count); + dst += count; + max -= count; + } + res += n + 1; + offset += n + 1; + if (end < offset) + end = offset; + } + if (end < offset + 1) + end = offset + 1; + + if (dst != NULL && max != 0) + dst[0] = 0; + if (newoffset) + *newoffset = end; + return (res + 1); +} + +char * +print_dname(const char *_dname, char *buf, size_t max) +{ + const unsigned char *dname = _dname; + char *res; + size_t left, n, count; + + if (_dname[0] == 0) { + (void)strlcpy(buf, ".", max); + return buf; + } + + res = buf; + left = max - 1; + for (n = 0; dname[0] && left; n += dname[0]) { + count = (dname[0] < (left - 1)) ? dname[0] : (left - 1); + memmove(buf, dname + 1, count); + dname += dname[0] + 1; + left -= count; + buf += count; + if (left) { + left -= 1; + *buf++ = '.'; + } + } + buf[0] = 0; + + return (res); +} + +static int +unpack_data(struct unpack *p, void *data, size_t len) +{ + if (p->err) + return (-1); + + if (p->len - p->offset < len) { + p->err = "too short"; + return (-1); + } + + memmove(data, p->buf + p->offset, len); + p->offset += len; + + return (0); +} + +static int +unpack_u16(struct unpack *p, uint16_t *u16) +{ + if (unpack_data(p, u16, 2) == -1) + return (-1); + + *u16 = ntohs(*u16); + + return (0); +} + +static int +unpack_u32(struct unpack *p, uint32_t *u32) +{ + if (unpack_data(p, u32, 4) == -1) + return (-1); + + *u32 = ntohl(*u32); + + return (0); +} + +static int +unpack_inaddr(struct unpack *p, struct in_addr *a) +{ + return (unpack_data(p, a, 4)); +} + +static int +unpack_in6addr(struct unpack *p, struct in6_addr *a6) +{ + return (unpack_data(p, a6, 16)); +} + +static int +unpack_dname(struct unpack *p, char *dst, size_t max) +{ + ssize_t e; + + if (p->err) + return (-1); + + e = dname_expand(p->buf, p->len, p->offset, &p->offset, dst, max); + if (e == -1) { + p->err = "bad domain name"; + return (-1); + } + if (e < 0 || e > MAXDNAME) { + p->err = "domain name too long"; + return (-1); + } + + return (0); +} diff --git a/foobar/portable/smtpd/unpack_dns.h b/foobar/portable/smtpd/unpack_dns.h new file mode 100644 index 00000000..2318a0c5 --- /dev/null +++ b/foobar/portable/smtpd/unpack_dns.h @@ -0,0 +1,96 @@ +/* $OpenBSD: unpack_dns.h,v 1.1 2018/01/06 07:57:53 sunil Exp $ */ + +/* + * Copyright (c) 2011-2014 Eric Faurot <eric@faurot.net> + * + * 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 <sys/types.h> + +#include <netinet/in.h> + +#include <arpa/inet.h> +#include <arpa/nameser.h> + +struct unpack { + const char *buf; + size_t len; + size_t offset; + const char *err; +}; + +struct dns_header { + uint16_t id; + uint16_t flags; + uint16_t qdcount; + uint16_t ancount; + uint16_t nscount; + uint16_t arcount; +}; + +struct dns_query { + char q_dname[MAXDNAME]; + uint16_t q_type; + uint16_t q_class; +}; + +struct dns_rr { + char rr_dname[MAXDNAME]; + uint16_t rr_type; + uint16_t rr_class; + uint32_t rr_ttl; + union { + struct { + char cname[MAXDNAME]; + } cname; + struct { + uint16_t preference; + char exchange[MAXDNAME]; + } mx; + struct { + char nsname[MAXDNAME]; + } ns; + struct { + char ptrname[MAXDNAME]; + } ptr; + struct { + char mname[MAXDNAME]; + char rname[MAXDNAME]; + uint32_t serial; + uint32_t refresh; + uint32_t retry; + uint32_t expire; + uint32_t minimum; + } soa; + struct { + struct in_addr addr; + } in_a; + struct { + struct in6_addr addr6; + } in_aaaa; + struct { + uint16_t rdlen; + const void *rdata; + } other; + } rr; +}; + +void unpack_init(struct unpack *, const char *, size_t); +int unpack_header(struct unpack *, struct dns_header *); +int unpack_rr(struct unpack *, struct dns_rr *); +int unpack_query(struct unpack *, struct dns_query *); +char *print_dname(const char *, char *, size_t); +ssize_t dname_expand(const unsigned char *, size_t, size_t, size_t *, + char *, size_t); + diff --git a/foobar/portable/smtpd/util.c b/foobar/portable/smtpd/util.c new file mode 100644 index 00000000..b2b1458c --- /dev/null +++ b/foobar/portable/smtpd/util.c @@ -0,0 +1,870 @@ +/* $OpenBSD: util.c,v 1.151 2020/02/24 23:54:28 millert Exp $ */ + +/* + * Copyright (c) 2000,2001 Markus Friedl. All rights reserved. + * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> + * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/resource.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <ctype.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <fts.h> +#include <imsg.h> +#include <inttypes.h> +#include <libgen.h> +#include <netdb.h> +#include <pwd.h> +#include <limits.h> +#include <resolv.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <time.h> +#include <unistd.h> + +#include "smtpd.h" +#include "log.h" + +const char *log_in6addr(const struct in6_addr *); +const char *log_sockaddr(struct sockaddr *); +static int parse_mailname_file(char *, size_t); + +int tracing = 0; +int foreground_log = 0; + +void * +xmalloc(size_t size) +{ + void *r; + + if ((r = malloc(size)) == NULL) + fatal("malloc"); + + return (r); +} + +void * +xcalloc(size_t nmemb, size_t size) +{ + void *r; + + if ((r = calloc(nmemb, size)) == NULL) + fatal("calloc"); + + return (r); +} + +char * +xstrdup(const char *str) +{ + char *r; + + if ((r = strdup(str)) == NULL) + fatal("strdup"); + + return (r); +} + +void * +xmemdup(const void *ptr, size_t size) +{ + void *r; + + if ((r = malloc(size)) == NULL) + fatal("malloc"); + + memmove(r, ptr, size); + + return (r); +} + +int +xasprintf(char **ret, const char *format, ...) +{ + int r; + va_list ap; + + va_start(ap, format); + r = vasprintf(ret, format, ap); + va_end(ap); + if (r == -1) + fatal("vasprintf"); + + return (r); +} + + +#if !defined(NO_IO) +int +io_xprintf(struct io *io, const char *fmt, ...) +{ + va_list ap; + int len; + + va_start(ap, fmt); + len = io_vprintf(io, fmt, ap); + va_end(ap); + if (len == -1) + fatal("io_xprintf(%p, %s, ...)", io, fmt); + + return len; +} + +int +io_xprint(struct io *io, const char *str) +{ + int len; + + len = io_print(io, str); + if (len == -1) + fatal("io_xprint(%p, %s, ...)", io, str); + + return len; +} +#endif + +char * +strip(char *s) +{ + size_t l; + + while (isspace((unsigned char)*s)) + s++; + + for (l = strlen(s); l; l--) { + if (!isspace((unsigned char)s[l-1])) + break; + s[l-1] = '\0'; + } + + return (s); +} + +int +bsnprintf(char *str, size_t size, const char *format, ...) +{ + int ret; + va_list ap; + + va_start(ap, format); + ret = vsnprintf(str, size, format, ap); + va_end(ap); + if (ret < 0 || ret >= (int)size) + return 0; + + return 1; +} + + +int +ckdir(const char *path, mode_t mode, uid_t owner, gid_t group, int create) +{ + char mode_str[12]; + int ret; + struct stat sb; + + if (stat(path, &sb) == -1) { + if (errno != ENOENT || create == 0) { + log_warn("stat: %s", path); + return (0); + } + + /* chmod is deferred to avoid umask effect */ + if (mkdir(path, 0) == -1) { + log_warn("mkdir: %s", path); + return (0); + } + + if (chown(path, owner, group) == -1) { + log_warn("chown: %s", path); + return (0); + } + + if (chmod(path, mode) == -1) { + log_warn("chmod: %s", path); + return (0); + } + + if (stat(path, &sb) == -1) { + log_warn("stat: %s", path); + return (0); + } + } + + ret = 1; + + /* check if it's a directory */ + if (!S_ISDIR(sb.st_mode)) { + ret = 0; + log_warnx("%s is not a directory", path); + } + + /* check that it is owned by owner/group */ + if (sb.st_uid != owner) { + ret = 0; + log_warnx("%s is not owned by uid %d", path, owner); + } + if (sb.st_gid != group) { + ret = 0; + log_warnx("%s is not owned by gid %d", path, group); + } + + /* check permission */ + if ((sb.st_mode & 07777) != mode) { + ret = 0; + strmode(mode, mode_str); + mode_str[10] = '\0'; + log_warnx("%s must be %s (%o)", path, mode_str + 1, mode); + } + + return ret; +} + +int +rmtree(char *path, int keepdir) +{ + char *path_argv[2]; + FTS *fts; + FTSENT *e; + int ret, depth; + + path_argv[0] = path; + path_argv[1] = NULL; + ret = 0; + depth = 0; + + fts = fts_open(path_argv, FTS_PHYSICAL | FTS_NOCHDIR, NULL); + if (fts == NULL) { + log_warn("fts_open: %s", path); + return (-1); + } + + while ((e = fts_read(fts)) != NULL) { + switch (e->fts_info) { + case FTS_D: + depth++; + break; + case FTS_DP: + case FTS_DNR: + depth--; + if (keepdir && depth == 0) + continue; + if (rmdir(e->fts_path) == -1) { + log_warn("rmdir: %s", e->fts_path); + ret = -1; + } + break; + + case FTS_F: + if (unlink(e->fts_path) == -1) { + log_warn("unlink: %s", e->fts_path); + ret = -1; + } + } + } + + fts_close(fts); + + return (ret); +} + +int +mvpurge(char *from, char *to) +{ + size_t n; + int retry; + const char *sep; + char buf[PATH_MAX]; + + if ((n = strlen(to)) == 0) + fatalx("to is empty"); + + sep = (to[n - 1] == '/') ? "" : "/"; + retry = 0; + +again: + (void)snprintf(buf, sizeof buf, "%s%s%u", to, sep, arc4random()); + if (rename(from, buf) == -1) { + /* ENOTDIR has actually 2 meanings, and incorrect input + * could lead to an infinite loop. Consider that after + * 20 tries something is hopelessly wrong. + */ + if (errno == ENOTEMPTY || errno == EISDIR || errno == ENOTDIR) { + if ((retry++) >= 20) + return (-1); + goto again; + } + return -1; + } + + return 0; +} + + +int +mktmpfile(void) +{ + char path[PATH_MAX]; + int fd; + + if (!bsnprintf(path, sizeof(path), "%s/smtpd.XXXXXXXXXX", + PATH_TEMPORARY)) { + log_warn("snprintf"); + fatal("exiting"); + } + + if ((fd = mkstemp(path)) == -1) { + log_warn("cannot create temporary file %s", path); + fatal("exiting"); + } + unlink(path); + return (fd); +} + + +/* Close file, signifying temporary error condition (if any) to the caller. */ +int +safe_fclose(FILE *fp) +{ + if (ferror(fp)) { + fclose(fp); + return 0; + } + if (fflush(fp)) { + fclose(fp); + if (errno == ENOSPC) + return 0; + fatal("safe_fclose: fflush"); + } + if (fsync(fileno(fp))) + fatal("safe_fclose: fsync"); + if (fclose(fp)) + fatal("safe_fclose: fclose"); + + return 1; +} + +int +hostname_match(const char *hostname, const char *pattern) +{ + while (*pattern != '\0' && *hostname != '\0') { + if (*pattern == '*') { + while (*pattern == '*') + pattern++; + while (*hostname != '\0' && + tolower((unsigned char)*hostname) != + tolower((unsigned char)*pattern)) + hostname++; + continue; + } + + if (tolower((unsigned char)*pattern) != + tolower((unsigned char)*hostname)) + return 0; + pattern++; + hostname++; + } + + return (*hostname == '\0' && *pattern == '\0'); +} + +int +mailaddr_match(const struct mailaddr *maddr1, const struct mailaddr *maddr2) +{ + struct mailaddr m1 = *maddr1; + struct mailaddr m2 = *maddr2; + char *p; + + /* catchall */ + if (m2.user[0] == '\0' && m2.domain[0] == '\0') + return 1; + + if (m2.domain[0] && !hostname_match(m1.domain, m2.domain)) + return 0; + + if (m2.user[0]) { + /* if address from table has a tag, we must respect it */ + if (strchr(m2.user, *env->sc_subaddressing_delim) == NULL) { + /* otherwise, strip tag from session address if any */ + p = strchr(m1.user, *env->sc_subaddressing_delim); + if (p) + *p = '\0'; + } + if (strcasecmp(m1.user, m2.user)) + return 0; + } + return 1; +} + +int +valid_localpart(const char *s) +{ +#define IS_ATEXT(c) (isalnum((unsigned char)(c)) || strchr(MAILADDR_ALLOWED, (c))) +nextatom: + if (!IS_ATEXT(*s) || *s == '\0') + return 0; + while (*(++s) != '\0') { + if (*s == '.') + break; + if (IS_ATEXT(*s)) + continue; + return 0; + } + if (*s == '.') { + s++; + goto nextatom; + } + return 1; +} + +int +valid_domainpart(const char *s) +{ + struct in_addr ina; + struct in6_addr ina6; + char *c, domain[SMTPD_MAXDOMAINPARTSIZE]; + const char *p; + size_t dlen; + + if (*s == '[') { + if (strncasecmp("[IPv6:", s, 6) == 0) + p = s + 6; + else + p = s + 1; + + if (strlcpy(domain, p, sizeof domain) >= sizeof domain) + return 0; + + c = strchr(domain, ']'); + if (!c || c[1] != '\0') + return 0; + + *c = '\0'; + + if (inet_pton(AF_INET6, domain, &ina6) == 1) + return 1; + if (inet_pton(AF_INET, domain, &ina) == 1) + return 1; + + return 0; + } + + if (*s == '\0') + return 0; + + dlen = strlen(s); + if (dlen >= sizeof domain) + return 0; + + if (s[dlen - 1] == '.') + return 0; + + return res_hnok(s); +} + +#define LABELCHR(c) ((c) == '-' || (c) == '_' || isalpha((unsigned char)(c)) || isdigit((unsigned char)(c))) +#define LABELMAX 63 +#define DNAMEMAX 253 + +int +valid_domainname(const char *str) +{ + const char *label, *s; + + /* + * Expect a sequence of dot-separated labels, possibly with a trailing + * dot. The empty string is rejected, as well a single dot. + */ + for (s = str; *s; s++) { + + /* Start of a new label. */ + label = s; + while (LABELCHR(*s)) + s++; + + /* Must have at least one char and at most LABELMAX. */ + if (s == label || s - label > LABELMAX) + return 0; + + /* If last label, stop here. */ + if (*s == '\0') + break; + + /* Expect a dot as label separator or last char. */ + if (*s != '.') + return 0; + } + + /* Must have at leat one label and no more than DNAMEMAX chars. */ + if (s == str || s - str > DNAMEMAX) + return 0; + + return 1; +} + +int +valid_smtp_response(const char *s) +{ + if (strlen(s) < 5) + return 0; + + if ((s[0] < '2' || s[0] > '5') || + (s[1] < '0' || s[1] > '9') || + (s[2] < '0' || s[2] > '9') || + (s[3] != ' ')) + return 0; + + return 1; +} + +int +secure_file(int fd, char *path, char *userdir, uid_t uid, int mayread) +{ + char buf[PATH_MAX]; + char homedir[PATH_MAX]; + struct stat st; + char *cp; + + if (realpath(path, buf) == NULL) + return 0; + + if (realpath(userdir, homedir) == NULL) + homedir[0] = '\0'; + + /* Check the open file to avoid races. */ + if (fstat(fd, &st) == -1 || + !S_ISREG(st.st_mode) || + st.st_uid != uid || + (st.st_mode & (mayread ? 022 : 066)) != 0) + return 0; + + /* For each component of the canonical path, walking upwards. */ + for (;;) { + if ((cp = dirname(buf)) == NULL) + return 0; + (void)strlcpy(buf, cp, sizeof(buf)); + + if (stat(buf, &st) == -1 || + (st.st_uid != 0 && st.st_uid != uid) || + (st.st_mode & 022) != 0) + return 0; + + /* We can stop checking after reaching homedir level. */ + if (strcmp(homedir, buf) == 0) + break; + + /* + * dirname should always complete with a "/" path, + * but we can be paranoid and check for "." too + */ + if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0)) + break; + } + + return 1; +} + +void +addargs(arglist *args, char *fmt, ...) +{ + va_list ap; + char *cp; + uint nalloc; + int r; + char **tmp; + + va_start(ap, fmt); + r = vasprintf(&cp, fmt, ap); + va_end(ap); + if (r == -1) + fatal("addargs: argument too long"); + + nalloc = args->nalloc; + if (args->list == NULL) { + nalloc = 32; + args->num = 0; + } else if (args->num+2 >= nalloc) + nalloc *= 2; + + tmp = reallocarray(args->list, nalloc, sizeof(char *)); + if (tmp == NULL) + fatal("addargs: reallocarray"); + args->list = tmp; + args->nalloc = nalloc; + args->list[args->num++] = cp; + args->list[args->num] = NULL; +} + +int +lowercase(char *buf, const char *s, size_t len) +{ + if (len == 0) + return 0; + + if (strlcpy(buf, s, len) >= len) + return 0; + + while (*buf != '\0') { + *buf = tolower((unsigned char)*buf); + buf++; + } + + return 1; +} + +int +uppercase(char *buf, const char *s, size_t len) +{ + if (len == 0) + return 0; + + if (strlcpy(buf, s, len) >= len) + return 0; + + while (*buf != '\0') { + *buf = toupper((unsigned char)*buf); + buf++; + } + + return 1; +} + +void +xlowercase(char *buf, const char *s, size_t len) +{ + if (len == 0) + fatalx("lowercase: len == 0"); + + if (!lowercase(buf, s, len)) + fatalx("lowercase: truncation"); +} + +uint64_t +generate_uid(void) +{ + static uint32_t id; + static uint8_t inited; + uint64_t uid; + + if (!inited) { + id = arc4random(); + inited = 1; + } + while ((uid = ((uint64_t)(id++) << 32 | arc4random())) == 0) + ; + + return (uid); +} + +int +session_socket_error(int fd) +{ + int error; + socklen_t len; + + len = sizeof(error); + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) == -1) + fatal("session_socket_error: getsockopt"); + + return (error); +} + +const char * +parse_smtp_response(char *line, size_t len, char **msg, int *cont) +{ + if (len >= LINE_MAX) + return "line too long"; + + if (len > 3) { + if (msg) + *msg = line + 4; + if (cont) + *cont = (line[3] == '-'); + } else if (len == 3) { + if (msg) + *msg = line + 3; + if (cont) + *cont = 0; + } else + return "line too short"; + + /* validate reply code */ + if (line[0] < '2' || line[0] > '5' || !isdigit((unsigned char)line[1]) || + !isdigit((unsigned char)line[2])) + return "reply code out of range"; + + return NULL; +} + +static int +parse_mailname_file(char *hostname, size_t len) +{ + FILE *fp; + char *buf = NULL; + size_t bufsz = 0; + ssize_t buflen; + + if ((fp = fopen(MAILNAME_FILE, "r")) == NULL) + return 1; + + if ((buflen = getline(&buf, &bufsz, fp)) == -1) + goto error; + + if (buf[buflen - 1] == '\n') + buf[buflen - 1] = '\0'; + + if (strlcpy(hostname, buf, len) >= len) { + fprintf(stderr, MAILNAME_FILE " entry too long"); + goto error; + } + + return 0; +error: + fclose(fp); + free(buf); + return 1; +} + +int +getmailname(char *hostname, size_t len) +{ + struct addrinfo hints, *res = NULL; + int error; + + /* Try MAILNAME_FILE first */ + if (parse_mailname_file(hostname, len) == 0) + return 0; + + /* Next, gethostname(3) */ + if (gethostname(hostname, len) == -1) { + fprintf(stderr, "getmailname: gethostname() failed\n"); + return -1; + } + + if (strchr(hostname, '.') != NULL) + return 0; + + /* Canonicalize if domain part is missing */ + memset(&hints, 0, sizeof hints); + hints.ai_family = PF_UNSPEC; + hints.ai_flags = AI_CANONNAME; + error = getaddrinfo(hostname, NULL, &hints, &res); + if (error) + return 0; /* Continue with non-canon hostname */ + + if (strlcpy(hostname, res->ai_canonname, len) >= len) { + fprintf(stderr, "hostname too long"); + freeaddrinfo(res); + return -1; + } + + freeaddrinfo(res); + return 0; +} + +int +base64_encode(unsigned char const *src, size_t srclen, + char *dest, size_t destsize) +{ + return __b64_ntop(src, srclen, dest, destsize); +} + +int +base64_decode(char const *src, unsigned char *dest, size_t destsize) +{ + return __b64_pton(src, dest, destsize); +} + +int +base64_encode_rfc3548(unsigned char const *src, size_t srclen, + char *dest, size_t destsize) +{ + size_t i; + int ret; + + if ((ret = base64_encode(src, srclen, dest, destsize)) == -1) + return -1; + + for (i = 0; i < destsize; ++i) { + if (dest[i] == '/') + dest[i] = '_'; + else if (dest[i] == '+') + dest[i] = '-'; + } + + return ret; +} + +void +log_trace(int mask, const char *emsg, ...) +{ + va_list ap; + + if (tracing & mask) { + va_start(ap, emsg); + vlog(LOG_DEBUG, emsg, ap); + va_end(ap); + } +} + +void +log_trace_verbose(int v) +{ + tracing = v; + + /* Set debug logging in log.c */ + log_setverbose(v & TRACE_DEBUG ? 2 : foreground_log); +} + +void +xclosefrom(int lowfd) +{ +#if defined HAVE_CLOSEFROM_INT + if (closefrom(lowfd) == -1) + err(1, "closefrom"); +#else + closefrom(lowfd); +#endif +} + +void +portable_freeaddrinfo(struct addrinfo *ai) +{ + struct addrinfo *p; + + do { + p = ai; + ai = ai->ai_next; + free(p->ai_canonname); + free(p); + } while (ai); +} diff --git a/foobar/portable/smtpd/waitq.c b/foobar/portable/smtpd/waitq.c new file mode 100644 index 00000000..082a1e51 --- /dev/null +++ b/foobar/portable/smtpd/waitq.c @@ -0,0 +1,104 @@ +/* $OpenBSD: waitq.c,v 1.6 2018/05/31 21:06:12 gilles Exp $ */ + +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/uio.h> + +#include <imsg.h> +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> + +#include "smtpd.h" + +struct waiter { + TAILQ_ENTRY(waiter) entry; + void (*cb)(void *, void *, void *); + void *arg; +}; + +struct waitq { + SPLAY_ENTRY(waitq) entry; + void *tag; + TAILQ_HEAD(, waiter) waiters; +}; + +static int waitq_cmp(struct waitq *, struct waitq *); + +SPLAY_HEAD(waitqtree, waitq); +SPLAY_PROTOTYPE(waitqtree, waitq, entry, waitq_cmp); + +static struct waitqtree waitqs = SPLAY_INITIALIZER(&waitqs); + +static int +waitq_cmp(struct waitq *a, struct waitq *b) +{ + if (a->tag < b->tag) + return (-1); + if (a->tag > b->tag) + return (1); + return (0); +} + +SPLAY_GENERATE(waitqtree, waitq, entry, waitq_cmp); + +int +waitq_wait(void *tag, void (*cb)(void *, void *, void *), void *arg) +{ + struct waitq *wq, key; + struct waiter *w; + + key.tag = tag; + wq = SPLAY_FIND(waitqtree, &waitqs, &key); + if (wq == NULL) { + wq = xmalloc(sizeof *wq); + wq->tag = tag; + TAILQ_INIT(&wq->waiters); + SPLAY_INSERT(waitqtree, &waitqs, wq); + } + + w = xmalloc(sizeof *w); + w->cb = cb; + w->arg = arg; + TAILQ_INSERT_TAIL(&wq->waiters, w, entry); + + return (w == TAILQ_FIRST(&wq->waiters)); +} + +void +waitq_run(void *tag, void *result) +{ + struct waitq *wq, key; + struct waiter *w; + + key.tag = tag; + wq = SPLAY_FIND(waitqtree, &waitqs, &key); + SPLAY_REMOVE(waitqtree, &waitqs, wq); + + while ((w = TAILQ_FIRST(&wq->waiters))) { + TAILQ_REMOVE(&wq->waiters, w, entry); + w->cb(tag, w->arg, result); + free(w); + } + free(wq); +} diff --git a/foobar/portable/smtpscript/LICENSE b/foobar/portable/smtpscript/LICENSE new file mode 100644 index 00000000..92859014 --- /dev/null +++ b/foobar/portable/smtpscript/LICENSE @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2013-2014 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ diff --git a/foobar/portable/smtpscript/Makefile b/foobar/portable/smtpscript/Makefile new file mode 100644 index 00000000..6a628f2a --- /dev/null +++ b/foobar/portable/smtpscript/Makefile @@ -0,0 +1,3 @@ +SUBDIR+= smtpscript + +.include <bsd.subdir.mk> diff --git a/foobar/portable/smtpscript/Makefile.inc b/foobar/portable/smtpscript/Makefile.inc new file mode 100644 index 00000000..be93057b --- /dev/null +++ b/foobar/portable/smtpscript/Makefile.inc @@ -0,0 +1,3 @@ +CFLAGS= -Wall -W + +BINDIR?= /usr/bin diff --git a/foobar/portable/smtpscript/README.md b/foobar/portable/smtpscript/README.md new file mode 100644 index 00000000..18f5fe9e --- /dev/null +++ b/foobar/portable/smtpscript/README.md @@ -0,0 +1,40 @@ +smtpscript +========== + +smtpscript is a tool to write SMTP scenarios and easily implement regression tests for SMTP server-side implementations. + +A smtpscript will look like: + + + # this is a function init-helo that we want to call in all our regress tests + proc init-helo { + expect smtp ok + writeln "HELO regress" + expect smtp helo + } + + # each of the test-case will be called sequentially + test-case name "mailfrom.empty" { + call init-helo + writeln "MAIL FROM:<>" + expect smtp ok + } + + test-case name "mailfrom.broken" { + call init-helo + writeln "MAIL FROM:< @bleh>" + expect smtp permfail + } + + +which once executed, produces the output: + + $ smtpscript foo + ===> running test-case "mailfrom.empty" ok + ===> running test-case "mailfrom.broken" ok + ===> all run + passed: 2/2 (skipped: 0, failed: 0, error: 0) + $ + + +The scripting language also supports TLS, randomization and loops, so fairly complex scenarios can be achieved. diff --git a/foobar/portable/smtpscript/iobuf.c b/foobar/portable/smtpscript/iobuf.c new file mode 100644 index 00000000..05a9cd59 --- /dev/null +++ b/foobar/portable/smtpscript/iobuf.c @@ -0,0 +1,466 @@ +/* $OpenBSD$ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/uio.h> + +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#ifdef IO_TLS +#include <openssl/err.h> +#include <openssl/ssl.h> +#endif + +#include "iobuf.h" + +#define IOBUF_MAX 65536 +#define IOBUFQ_MIN 4096 + +struct ioqbuf *ioqbuf_alloc(struct iobuf *, size_t); +void iobuf_drain(struct iobuf *, size_t); + +int +iobuf_init(struct iobuf *io, size_t size, size_t max) +{ + memset(io, 0, sizeof *io); + + if (max == 0) + max = IOBUF_MAX; + + if (size == 0) + size = max; + + if (size > max) + return (-1); + + if ((io->buf = malloc(size)) == NULL) + return (-1); + + io->size = size; + io->max = max; + + return (0); +} + +void +iobuf_clear(struct iobuf *io) +{ + struct ioqbuf *q; + + if (io->buf) + free(io->buf); + + while ((q = io->outq)) { + io->outq = q->next; + free(q); + } + + memset(io, 0, sizeof (*io)); +} + +void +iobuf_drain(struct iobuf *io, size_t n) +{ + struct ioqbuf *q; + size_t left = n; + + while ((q = io->outq) && left) { + if ((q->wpos - q->rpos) > left) { + q->rpos += left; + left = 0; + } else { + left -= q->wpos - q->rpos; + io->outq = q->next; + free(q); + } + } + + io->queued -= (n - left); + if (io->outq == NULL) + io->outqlast = NULL; +} + +int +iobuf_extend(struct iobuf *io, size_t n) +{ + char *t; + + if (n > io->max) + return (-1); + + if (io->max - io->size < n) + return (-1); + + t = realloc(io->buf, io->size + n); + if (t == NULL) + return (-1); + + io->size += n; + io->buf = t; + + return (0); +} + +size_t +iobuf_left(struct iobuf *io) +{ + return io->size - io->wpos; +} + +size_t +iobuf_space(struct iobuf *io) +{ + return io->size - (io->wpos - io->rpos); +} + +size_t +iobuf_len(struct iobuf *io) +{ + return io->wpos - io->rpos; +} + +char * +iobuf_data(struct iobuf *io) +{ + return io->buf + io->rpos; +} + +void +iobuf_drop(struct iobuf *io, size_t n) +{ + if (n >= iobuf_len(io)) { + io->rpos = io->wpos = 0; + return; + } + + io->rpos += n; +} + +char * +iobuf_getline(struct iobuf *iobuf, size_t *rlen) +{ + char *buf; + size_t len, i; + + buf = iobuf_data(iobuf); + len = iobuf_len(iobuf); + + for (i = 0; i + 1 <= len; i++) + if (buf[i] == '\n') { + /* Note: the returned address points into the iobuf + * buffer. We NUL-end it for convenience, and discard + * the data from the iobuf, so that the caller doesn't + * have to do it. The data remains "valid" as long + * as the iobuf does not overwrite it, that is until + * the next call to iobuf_normalize() or iobuf_extend(). + */ + iobuf_drop(iobuf, i + 1); + len = (i && buf[i - 1] == '\r') ? i - 1 : i; + buf[len] = '\0'; + if (rlen) + *rlen = len; + return (buf); + } + + return (NULL); +} + +void +iobuf_normalize(struct iobuf *io) +{ + if (io->rpos == 0) + return; + + if (io->rpos == io->wpos) { + io->rpos = io->wpos = 0; + return; + } + + memmove(io->buf, io->buf + io->rpos, io->wpos - io->rpos); + io->wpos -= io->rpos; + io->rpos = 0; +} + +ssize_t +iobuf_read(struct iobuf *io, int fd) +{ + ssize_t n; + + n = read(fd, io->buf + io->wpos, iobuf_left(io)); + if (n == -1) { + /* XXX is this really what we want? */ + if (errno == EAGAIN || errno == EINTR) + return (IOBUF_WANT_READ); + return (IOBUF_ERROR); + } + if (n == 0) + return (IOBUF_CLOSED); + + io->wpos += n; + + return (n); +} + +struct ioqbuf * +ioqbuf_alloc(struct iobuf *io, size_t len) +{ + struct ioqbuf *q; + + if (len < IOBUFQ_MIN) + len = IOBUFQ_MIN; + + if ((q = malloc(sizeof(*q) + len)) == NULL) + return (NULL); + + q->rpos = 0; + q->wpos = 0; + q->size = len; + q->next = NULL; + q->buf = (char *)(q) + sizeof(*q); + + if (io->outqlast == NULL) + io->outq = q; + else + io->outqlast->next = q; + io->outqlast = q; + + return (q); +} + +size_t +iobuf_queued(struct iobuf *io) +{ + return io->queued; +} + +void * +iobuf_reserve(struct iobuf *io, size_t len) +{ + struct ioqbuf *q; + void *r; + + if (len == 0) + return (NULL); + + if (((q = io->outqlast) == NULL) || q->size - q->wpos <= len) { + if ((q = ioqbuf_alloc(io, len)) == NULL) + return (NULL); + } + + r = q->buf + q->wpos; + q->wpos += len; + io->queued += len; + + return (r); +} + +int +iobuf_queue(struct iobuf *io, const void *data, size_t len) +{ + void *buf; + + if (len == 0) + return (0); + + if ((buf = iobuf_reserve(io, len)) == NULL) + return (-1); + + memmove(buf, data, len); + + return (0); +} + +int +iobuf_queuev(struct iobuf *io, const struct iovec *iov, int iovcnt) +{ + int i; + size_t len = 0; + char *buf; + + for (i = 0; i < iovcnt; i++) + len += iov[i].iov_len; + + if ((buf = iobuf_reserve(io, len)) == NULL) + return (-1); + + for (i = 0; i < iovcnt; i++) { + if (iov[i].iov_len == 0) + continue; + memmove(buf, iov[i].iov_base, iov[i].iov_len); + buf += iov[i].iov_len; + } + + return (0); + +} + +int +iobuf_fqueue(struct iobuf *io, const char *fmt, ...) +{ + va_list ap; + int len; + + va_start(ap, fmt); + len = iobuf_vfqueue(io, fmt, ap); + va_end(ap); + + return (len); +} + +int +iobuf_vfqueue(struct iobuf *io, const char *fmt, va_list ap) +{ + char *buf; + int len; + + len = vasprintf(&buf, fmt, ap); + + if (len == -1) + return (-1); + + len = iobuf_queue(io, buf, len); + free(buf); + + return (len); +} + +ssize_t +iobuf_write(struct iobuf *io, int fd) +{ + struct iovec iov[IOV_MAX]; + struct ioqbuf *q; + int i; + ssize_t n; + + i = 0; + for (q = io->outq; q ; q = q->next) { + if (i >= IOV_MAX) + break; + iov[i].iov_base = q->buf + q->rpos; + iov[i].iov_len = q->wpos - q->rpos; + i++; + } + + n = writev(fd, iov, i); + if (n == -1) { + if (errno == EAGAIN || errno == EINTR) + return (IOBUF_WANT_WRITE); + if (errno == EPIPE) + return (IOBUF_CLOSED); + return (IOBUF_ERROR); + } + + iobuf_drain(io, n); + + return (n); +} + +int +iobuf_flush(struct iobuf *io, int fd) +{ + ssize_t s; + + while (io->queued) + if ((s = iobuf_write(io, fd)) < 0) + return (s); + + return (0); +} + +#ifdef IO_TLS + +int +iobuf_flush_ssl(struct iobuf *io, void *ssl) +{ + ssize_t s; + + while (io->queued) + if ((s = iobuf_write_ssl(io, ssl) < 0)) + return (s); + + return (0); +} + +ssize_t +iobuf_write_ssl(struct iobuf *io, void *ssl) +{ + struct ioqbuf *q; + int r; + ssize_t n; + + q = io->outq; + n = SSL_write(ssl, q->buf + q->rpos, q->wpos - q->rpos); + if (n <= 0) { + switch ((r = SSL_get_error(ssl, n))) { + case SSL_ERROR_WANT_READ: + return (IOBUF_WANT_READ); + case SSL_ERROR_WANT_WRITE: + return (IOBUF_WANT_WRITE); + case SSL_ERROR_ZERO_RETURN: /* connection closed */ + return (IOBUF_CLOSED); + case SSL_ERROR_SYSCALL: + if (ERR_peek_last_error()) + return (IOBUF_SSLERROR); + if (r == 0) + errno = EPIPE; + return (IOBUF_ERROR); + default: + return (IOBUF_SSLERROR); + } + } + iobuf_drain(io, n); + + return (n); +} + +ssize_t +iobuf_read_ssl(struct iobuf *io, void *ssl) +{ + ssize_t n; + int r; + + n = SSL_read(ssl, io->buf + io->wpos, iobuf_left(io)); + if (n < 0) { + switch ((r = SSL_get_error(ssl, n))) { + case SSL_ERROR_WANT_READ: + return (IOBUF_WANT_READ); + case SSL_ERROR_WANT_WRITE: + return (IOBUF_WANT_WRITE); + case SSL_ERROR_SYSCALL: + if (ERR_peek_last_error()) + return (IOBUF_SSLERROR); + if (r == 0) + errno = EPIPE; + return (IOBUF_ERROR); + default: + return (IOBUF_SSLERROR); + } + } else if (n == 0) + return (IOBUF_CLOSED); + + io->wpos += n; + + return (n); +} + +#endif /* IO_TLS */ diff --git a/foobar/portable/smtpscript/iobuf.h b/foobar/portable/smtpscript/iobuf.h new file mode 100644 index 00000000..ee4690c8 --- /dev/null +++ b/foobar/portable/smtpscript/iobuf.h @@ -0,0 +1,71 @@ +/* $OpenBSD$ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> + +#include <stdarg.h> + +struct ioqbuf { + struct ioqbuf *next; + char *buf; + size_t size; + size_t wpos; + size_t rpos; +}; + +struct iobuf { + char *buf; + size_t max; + size_t size; + size_t wpos; + size_t rpos; + + size_t queued; + struct ioqbuf *outq; + struct ioqbuf *outqlast; +}; + +#define IOBUF_WANT_READ -1 +#define IOBUF_WANT_WRITE -2 +#define IOBUF_CLOSED -3 +#define IOBUF_ERROR -4 +#define IOBUF_SSLERROR -5 + +int iobuf_init(struct iobuf *, size_t, size_t); +void iobuf_clear(struct iobuf *); + +int iobuf_extend(struct iobuf *, size_t); +void iobuf_normalize(struct iobuf *); +void iobuf_drop(struct iobuf *, size_t); +size_t iobuf_space(struct iobuf *); +size_t iobuf_len(struct iobuf *); +size_t iobuf_left(struct iobuf *); +char *iobuf_data(struct iobuf *); +char *iobuf_getline(struct iobuf *, size_t *); +ssize_t iobuf_read(struct iobuf *, int); +ssize_t iobuf_read_ssl(struct iobuf *, void *); + +size_t iobuf_queued(struct iobuf *); +void* iobuf_reserve(struct iobuf *, size_t); +int iobuf_queue(struct iobuf *, const void*, size_t); +int iobuf_queuev(struct iobuf *, const struct iovec *, int); +int iobuf_fqueue(struct iobuf *, const char *, ...); +int iobuf_vfqueue(struct iobuf *, const char *, va_list); +int iobuf_flush(struct iobuf *, int); +int iobuf_flush_ssl(struct iobuf *, void *); +ssize_t iobuf_write(struct iobuf *, int); +ssize_t iobuf_write_ssl(struct iobuf *, void *); diff --git a/foobar/portable/smtpscript/parse.y b/foobar/portable/smtpscript/parse.y new file mode 100644 index 00000000..47a0f35c --- /dev/null +++ b/foobar/portable/smtpscript/parse.y @@ -0,0 +1,905 @@ +/* $OpenBSD: parse.y,v 1.109 2012/10/14 11:58:23 gilles Exp $ */ + +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> + * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org> + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. + * Copyright (c) 2001 Theo de Raadt. All rights reserved. + * + * 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 <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/ioctl.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <inttypes.h> +#include <netdb.h> +#include <paths.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <util.h> + +#include "smtpscript.h" + +TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); +static struct file { + TAILQ_ENTRY(file) entry; + FILE *stream; + char *name; + int lineno; + int errors; +} *file, *topfile; +struct file *pushfile(const char *, int); +int popfile(void); +int check_file_secrecy(int, const char *); +int yyparse(void); +int yylex(void); +int kw_cmp(const void *, const void *); +int lookup(char *); +int lgetc(int); +int lungetc(int); +int findeol(void); +int yyerror(const char *, ...) + __attribute__ ((format (printf, 1, 2))); + +TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); +struct sym { + TAILQ_ENTRY(sym) entry; + int used; + int persist; + char *nam; + char *val; +}; +int symset(const char *, const char *, int); +char *symget(const char *); + +void push_op(struct op *); +struct op *peek_op(void); +struct op *pop_op(void); + +#define MAXDEPTH 50 + +static struct op * opstack[MAXDEPTH]; +static int opstackidx; + +static int errors = 0; + +static struct script *currscript; +static struct procedure *currproc; + +int delaytonum(char *); + +typedef struct { + union { + int64_t number; + char *string; + struct op *op; + } v; + int lineno; +} YYSTYPE; + +%} + +%token INCLUDE PORT REPEAT RANDOM NOOP +%token PROC TESTCASE NAME NO_AUTOCONNECT EXPECT FAIL SKIP +%token CALL CONNECT DISCONNECT STARTTLS SLEEP WRITE WRITELN +%token SMTP OK TEMPFAIL PERMFAIL HELO +%token ERROR ARROW +%token <v.string> STRING +%token <v.number> NUMBER +%type <v.number> quantifier port duration +%type <v.op> statement block +%% + +grammar : /* empty */ + | grammar '\n' + | grammar include '\n' + | grammar varset '\n' + | grammar proc '\n' + | grammar testcase '\n' + | grammar error '\n' { file->errors++; } + ; + +include : INCLUDE STRING { + struct file *nfile; + + if ((nfile = pushfile($2, 0)) == NULL) { + yyerror("failed to include file %s", $2); + free($2); + YYERROR; + } + free($2); + + file = nfile; + lungetc('\n'); + } + ; + +varset : STRING '=' STRING { + if (symset($1, $3, 0) == -1) + errx(1, "cannot store variable"); + free($1); + free($3); + } + ; + +optnl : '\n' optnl + | + ; + +nl : '\n' optnl + ; + +quantifier : /* empty */ { $$ = 1; } + | 's' { $$ = 1000; } + | 'm' { $$ = 60 * 1000; } + | 'h' { $$ = 3600 * 1000; } + ; + +duration : NUMBER quantifier { + if ($1 < 0) { + yyerror("invalid duration: %" PRId64, $1); + YYERROR; + } + $$ = $1 * $2; + } + ; + +port : PORT STRING { + struct servent *servent; + + servent = getservbyname($2, "tcp"); + if (servent == NULL) { + yyerror("port %s is invalid", $2); + free($2); + YYERROR; + } + $$ = ntohs(servent->s_port); + free($2); + } + | PORT NUMBER { + if ($2 <= 0 || $2 >= (int)USHRT_MAX) { + yyerror("invalid port: %" PRId64, $2); + YYERROR; + } + $$ = $2; + } + | /* empty */ { + $$ = 25; + } + ; + +statement : block + | REPEAT NUMBER { push_op(NULL); } statement { + pop_op(); + $$ = op_repeat(peek_op(), $2, $4); + } + | RANDOM { push_op(NULL); } block { + pop_op(); + $$ = op_random(peek_op(), $3); + } + | CALL STRING { + struct procedure *p; + p = procedure_get_by_name(currscript, $2); + if (p == NULL) { + yyerror("call to undefined proc \"%s\"", $2); + file->errors++; + } else if (p == currproc) { + yyerror("recursive call to proc \"%s\"", $2); + file->errors++; + } else { + $$ = op_call(peek_op(), p); + } + free($2); + } + | NOOP { + $$ = op_noop(peek_op()); + } + | SLEEP duration { + $$ = op_sleep(peek_op(), $2); + } + | FAIL STRING { + $$ = op_fail(peek_op(), $2); + } + | CONNECT STRING port { + $$ = op_connect(peek_op(), $2, $3); + } + | DISCONNECT { + $$ = op_disconnect(peek_op()); + } + | STARTTLS { + $$ = op_starttls(peek_op()); + } + | WRITE STRING { + $$ = op_write(peek_op(), $2, strlen($2)); + } + | WRITELN STRING { + $$ = op_printf(peek_op(), "%s\r\n", $2); + free($2); + } + | EXPECT DISCONNECT { + $$ = op_expect_disconnect(peek_op()); + } + | EXPECT SMTP { + $$ = op_expect_smtp_response(peek_op(), + RESP_SMTP_ANY | RESP_SMTP_MULTILINE); + } + | EXPECT SMTP OK { + $$ = op_expect_smtp_response(peek_op(), + RESP_SMTP_OK); + } + | EXPECT SMTP HELO { + $$ = op_expect_smtp_response(peek_op(), + RESP_SMTP_OK | RESP_SMTP_MULTILINE); + } + | EXPECT SMTP TEMPFAIL { + $$ = op_expect_smtp_response(peek_op(), + RESP_SMTP_TEMPFAIL); + } + | EXPECT SMTP PERMFAIL { + $$ = op_expect_smtp_response(peek_op(), + RESP_SMTP_PERMFAIL); + } + ; + +statement_list : statement nl statement_list + | statement + | /* EMPTY */ + ; + +block : '{' { + push_op(op_block(peek_op())); + } optnl statement_list '}' { + $$ = pop_op(); + } + ; + +procparam : '%' STRING { + if (proc_addvar(currproc, $2) == -1) { + yyerror("cannot add parameter %s", $2); + file->errors++; + } + } + ; + +procparams : procparam procparams + | /* EMPTY */ + ; + +proc : PROC STRING { + printf("# proc %s\n", $2); + currproc = procedure_create(currscript, $2); + if (currproc == NULL) + file->errors++; + } procparams block { + if (currproc) + currproc->root = $5; + } + ; + +testopt_name : NAME STRING { + if (procedure_get_by_name(currscript, $2)) { + file->errors++; + } else { + free(currproc->name); + currproc->name = ($2); + } + } + | /* EMPTY */ + ; + +testopt_cnx : NO_AUTOCONNECT { + currproc->flags |= PROC_NOCONNECT; + } + | /* EMPTY */ + ; +testopt_fail : EXPECT FAIL { + currproc->flags |= PROC_EXPECTFAIL; + } + | /* EMPTY */ + ; + +testopt_skip : SKIP { + currproc->flags |= PROC_SKIP; + } + | /* EMPTY */ + ; + +testcaseopts : testopt_name testopt_cnx testopt_fail testopt_skip; + +testcase : TESTCASE { + char buf[1024]; + snprintf(buf, sizeof buf, "<%s:%i>", + file->name, file->lineno); + currproc = procedure_create(currscript, strdup(buf)); + if (currproc) { + currproc->flags |= PROC_TESTCASE; + } else { + file->errors++; + } + } testcaseopts block { + currproc->root = $4; + } + ; +%% + +struct keywords { + const char *k_name; + int k_val; +}; + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + + file->errors++; + va_start(ap, fmt); + fprintf(stderr, "%s:%d: ", file->name, yylval.lineno); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + return (0); +} + +int +kw_cmp(const void *k, const void *e) +{ + return (strcmp(k, ((const struct keywords *)e)->k_name)); +} + +int +lookup(char *s) +{ + /* this has to be sorted always */ + static const struct keywords keywords[] = { + { "call", CALL }, + { "connect", CONNECT }, + { "disconnect", DISCONNECT }, + { "expect", EXPECT }, + { "fail", FAIL }, + { "helo", HELO }, + { "name", NAME }, + { "no-autoconnect", NO_AUTOCONNECT }, + { "noop", NOOP }, + { "ok", OK }, + { "permfail", PERMFAIL }, + { "port", PORT }, + { "proc", PROC }, + { "random", RANDOM }, + { "repeat", REPEAT }, + { "skip", SKIP }, + { "sleep", SLEEP }, + { "smtp", SMTP }, + { "starttls", STARTTLS }, + { "tempfail", TEMPFAIL }, + { "test-case", TESTCASE }, + { "write", WRITE }, + { "writeln", WRITELN }, + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + + if (p) + return (p->k_val); + else + return (STRING); +} + +#define MAXPUSHBACK 128 + +char *parsebuf; +int parseindex; +char pushback_buffer[MAXPUSHBACK]; +int pushback_index = 0; + +int +lgetc(int quotec) +{ + int c, next; + + if (parsebuf) { + /* Read character from the parsebuffer instead of input. */ + if (parseindex >= 0) { + c = parsebuf[parseindex++]; + if (c != '\0') + return (c); + parsebuf = NULL; + } else + parseindex++; + } + + if (pushback_index) + return (pushback_buffer[--pushback_index]); + + if (quotec) { + if ((c = getc(file->stream)) == EOF) { + yyerror("reached end of file while parsing " + "quoted string"); + if (file == topfile || popfile() == EOF) + return (EOF); + return (quotec); + } + return (c); + } + + while ((c = getc(file->stream)) == '\\') { + next = getc(file->stream); + if (next != '\n') { + c = next; + break; + } + yylval.lineno = file->lineno; + file->lineno++; + } + + while (c == EOF) { + if (file == topfile || popfile() == EOF) + return (EOF); + c = getc(file->stream); + } + return (c); +} + +int +lungetc(int c) +{ + if (c == EOF) + return (EOF); + if (parsebuf) { + parseindex--; + if (parseindex >= 0) + return (c); + } + if (pushback_index < MAXPUSHBACK-1) + return (pushback_buffer[pushback_index++] = c); + else + return (EOF); +} + +int +findeol(void) +{ + int c; + + parsebuf = NULL; + pushback_index = 0; + + /* skip to either EOF or the first real EOL */ + while (1) { + c = lgetc(0); + if (c == '\n') { + file->lineno++; + break; + } + if (c == EOF) + break; + } + return (ERROR); +} + +int +yylex(void) +{ + char buf[8096]; + char *p, *val; + int quotec, next, c; + int token; + +top: + p = buf; + while ((c = lgetc(0)) == ' ' || c == '\t') + ; /* nothing */ + + yylval.lineno = file->lineno; + if (c == '#') + while ((c = lgetc(0)) != '\n' && c != EOF) + ; /* nothing */ + if (c == '$' && parsebuf == NULL) { + while (1) { + if ((c = lgetc(0)) == EOF) + return (0); + + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + if (isalnum(c) || c == '_') { + *p++ = (char)c; + continue; + } + *p = '\0'; + lungetc(c); + break; + } + val = symget(buf); + if (val == NULL) { + yyerror("macro '%s' not defined", buf); + return (findeol()); + } + parsebuf = val; + parseindex = 0; + goto top; + } + + switch (c) { + case '\'': + case '"': + quotec = c; + while (1) { + if ((c = lgetc(quotec)) == EOF) + return (0); + if (c == '\n') { + file->lineno++; + continue; + } else if (c == '\\') { + if ((next = lgetc(quotec)) == EOF) + return (0); + if (next == quotec || c == ' ' || c == '\t') + c = next; + else if (next == '\n') { + file->lineno++; + continue; + } else + lungetc(next); + } else if (c == quotec) { + *p = '\0'; + break; + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + *p++ = (char)c; + } + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + err(1, "yylex: strdup"); + return (STRING); + } + +#define allowed_to_end_number(x) \ + (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') + + if (c == '-' || isdigit(c)) { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && isdigit(c)); + lungetc(c); + if (p == buf + 1 && buf[0] == '-') + goto nodigits; + if (c == EOF || allowed_to_end_number(c)) { + const char *errstr = NULL; + + *p = '\0'; + yylval.v.number = strtonum(buf, LLONG_MIN, + LLONG_MAX, &errstr); + if (errstr) { + yyerror("\"%s\" invalid number: %s", + buf, errstr); + return (findeol()); + } + return (NUMBER); + } else { +nodigits: + while (p > buf + 1) + lungetc(*--p); + c = *--p; + if (c == '-') + return (c); + } + } + + if (c == '=') { + if ((c = lgetc(0)) != EOF && c == '>') + return (ARROW); + lungetc(c); + c = '='; + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && x != '<' && x != '>' && \ + x != '!' && x != '=' && x != '#' && \ + x != ',')) + + if (isalnum(c) || c == ':' || c == '_') { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + if ((token = lookup(buf)) == STRING) + if ((yylval.v.string = strdup(buf)) == NULL) + err(1, "yylex: strdup"); + return (token); + } + if (c == '\n') { + yylval.lineno = file->lineno; + file->lineno++; + } + if (c == EOF) + return (0); + return (c); +} + +int +check_file_secrecy(int fd, const char *fname) +{ + struct stat st; + + if (fstat(fd, &st)) { + warn("cannot stat %s", fname); + return (-1); + } + if (st.st_uid != 0 && st.st_uid != getuid()) { + warnx("%s: owner not root or current user", fname); + return (-1); + } + if (st.st_mode & (S_IRWXG | S_IRWXO)) { + warnx("%s: group/world readable/writeable", fname); + return (-1); + } + return (0); +} + +struct file * +pushfile(const char *name, int secret) +{ + struct file *nfile; + + if ((nfile = calloc(1, sizeof(struct file))) == NULL) { + warn("malloc"); + return (NULL); + } + if ((nfile->name = strdup(name)) == NULL) { + warn("malloc"); + free(nfile); + return (NULL); + } + if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { + warn("%s", nfile->name); + free(nfile->name); + free(nfile); + return (NULL); + } else if (secret && + check_file_secrecy(fileno(nfile->stream), nfile->name)) { + fclose(nfile->stream); + free(nfile->name); + free(nfile); + return (NULL); + } + nfile->lineno = 1; + TAILQ_INSERT_TAIL(&files, nfile, entry); + return (nfile); +} + +int +popfile(void) +{ + struct file *prev; + + if ((prev = TAILQ_PREV(file, files, entry)) != NULL) + prev->errors += file->errors; + + TAILQ_REMOVE(&files, file, entry); + fclose(file->stream); + free(file->name); + free(file); + file = prev; + return (file ? 0 : EOF); +} + +struct script * +parse_script(const char *filename) +{ + errors = 0; + + currscript = calloc(1, sizeof *currscript); + TAILQ_INIT(&currscript->procs); + currproc = NULL; + + opstackidx = 0; + + if ((file = pushfile(filename, 0)) == NULL) + return (NULL); + + topfile = file; + + /* + * parse configuration + */ + setservent(1); + yyparse(); + errors = file->errors; + popfile(); + endservent(); + + if (errors) + return (NULL); + + return (currscript); +} + +int +symset(const char *nam, const char *val, int persist) +{ + struct sym *sym; + + for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam); + sym = TAILQ_NEXT(sym, entry)) + ; /* nothing */ + + if (sym != NULL) { + if (sym->persist == 1) + return (0); + else { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + if ((sym = calloc(1, sizeof(*sym))) == NULL) + return (-1); + + sym->nam = strdup(nam); + if (sym->nam == NULL) { + free(sym); + return (-1); + } + sym->val = strdup(val); + if (sym->val == NULL) { + free(sym->nam); + free(sym); + return (-1); + } + sym->used = 0; + sym->persist = persist; + TAILQ_INSERT_TAIL(&symhead, sym, entry); + return (0); +} + +int +cmdline_symset(char *s) +{ + char *sym, *val; + int ret; + size_t len; + + if ((val = strrchr(s, '=')) == NULL) + return (-1); + + len = strlen(s) - strlen(val) + 1; + if ((sym = malloc(len)) == NULL) + errx(1, "cmdline_symset: malloc"); + + (void)strlcpy(sym, s, len); + + ret = symset(sym, val + 1, 1); + free(sym); + + return (ret); +} + +char * +symget(const char *nam) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) + if (strcmp(nam, sym->nam) == 0) { + sym->used = 1; + return (sym->val); + } + return (NULL); +} + +int +delaytonum(char *str) +{ + unsigned int factor; + size_t len; + const char *errstr = NULL; + int delay; + + /* we need at least 1 digit and 1 unit */ + len = strlen(str); + if (len < 2) + goto bad; + + switch(str[len - 1]) { + + case 's': + factor = 1; + break; + + case 'm': + factor = 60; + break; + + case 'h': + factor = 60 * 60; + break; + + case 'd': + factor = 24 * 60 * 60; + break; + + default: + goto bad; + } + + str[len - 1] = '\0'; + delay = strtonum(str, 1, INT_MAX / factor, &errstr); + if (errstr) + goto bad; + + return (delay * factor); + +bad: + return (-1); +} + + +void +push_op(struct op *op) +{ + if (opstackidx == MAXDEPTH) { + yyerror("too deep"); + return; + } + opstack[opstackidx++] = op; +} + +struct op * +pop_op(void) +{ + if (opstackidx == 0) + return (NULL); + return (opstack[--opstackidx]); +} + +struct op * +peek_op(void) +{ + if (opstackidx == 0) + return (NULL); + return (opstack[opstackidx - 1]); +} diff --git a/foobar/portable/smtpscript/smtpscript.c b/foobar/portable/smtpscript/smtpscript.c new file mode 100644 index 00000000..a75c4249 --- /dev/null +++ b/foobar/portable/smtpscript/smtpscript.c @@ -0,0 +1,1009 @@ +/* $OpenBSD: iobuf.h,v 1.1 2012/01/29 00:32:51 eric Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/queue.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <getopt.h> +#include <netdb.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <vis.h> + +#include "iobuf.h" + +#include "smtpscript.h" + +void *ssl_connect(int); +void ssl_close(void *); + +/* XXX */ +#define SMTP_LINE_MAX 4096 + +enum { + OP_BLOCK, + OP_REPEAT, + OP_RANDOM, + + OP_NOOP, + + OP_FAIL, + OP_CALL, + OP_CONNECT, + OP_DISCONNECT, + OP_STARTTLS, + OP_SLEEP, + OP_WRITE, + + OP_EXPECT_DISCONNECT, + OP_EXPECT_SMTP_RESPONSE, +}; + +struct op { + struct op *next; + int type; + union { + struct { + int count; + struct op *start; + struct op *last; + } block; + struct { + struct op *op; + int count; + } repeat; + struct { + struct op *block; + } random; + struct { + char *reason; + } fail; + struct { + struct procedure *proc; + } call; + struct { + char *hostname; + int portno; + } connect; + struct { + unsigned int ms; + } sleep; + struct { + const void *buf; + size_t len; + } write; + struct { + int flags; + } expect_smtp; + } u; +}; + +#define RES_OK 0 +#define RES_SKIP 1 +#define RES_FAIL 2 +#define RES_ERROR 3 + +struct ctx { + int sock; + void *ssl; + struct iobuf iobuf; + int lvl; + + int result; + char *reason; +}; + +static struct op * _op_connect; + +int verbose; +int randomdelay; /* between each testcase */ +int tapout; +size_t rundelay; /* between each testcase */ + +static size_t test_pass; +static size_t test_skip; +static size_t test_fail; +static size_t test_error; +static size_t test_total = 0; + +static struct op *op_add_child(struct op *, const struct op *); +static void run_testcase(struct procedure *); +static void print_testcase(char *status, char *name, char *reason, char *directive, size_t number); +static void process_op(struct ctx *, struct op *); +static const char * parse_smtp_response(char *, size_t, char **, int *); + +struct procedure * +procedure_create(struct script *scr, char *name) +{ + struct procedure *p; + + if (procedure_get_by_name(scr, name)) { + warnx("procedure \"%s\" already exists", name); + return (NULL); + } + + p = calloc(1, sizeof *p); + TAILQ_INIT(&p->vars); + p->name = strdup(name); + + TAILQ_INSERT_TAIL(&scr->procs, p, entry); + + return (p); +} + +struct procedure * +procedure_get_by_name(struct script *scr, const char *name) +{ + struct procedure *p; + + TAILQ_FOREACH(p, &scr->procs, entry) + if (!strcmp(name, p->name)) + return (p); + + return (NULL); +} + +int +proc_getvaridx(struct procedure *proc, char *name) +{ + struct variable *v; + int n; + + n = 0; + TAILQ_FOREACH(v, &proc->vars, entry) { + if (!strcmp(name, v->name)) + return (n); + n++; + } + + return (-1); +} + +int +proc_addvar(struct procedure *proc, char *name) +{ + struct variable *v; + + printf("adding variable \"%s\"\n", name); + + if (proc_getvaridx(proc, name) != -1) + return (-1); + v = calloc(1, sizeof *v); + v->name = name; + TAILQ_INSERT_TAIL(&proc->vars, v, entry); + + return (proc->varcount++); +} + +struct op * +op_block(struct op *parent) +{ + struct op o; + + bzero(&o, sizeof o); + o.type = OP_BLOCK; + + return (op_add_child(parent, &o)); +} + +struct op * +op_repeat(struct op *parent, int count, struct op *op) +{ + struct op o; + + bzero(&o, sizeof o); + o.type = OP_REPEAT; + o.u.repeat.count = count; + o.u.repeat.op = op; + + return (op_add_child(parent, &o)); +} + +struct op * +op_random(struct op *parent, struct op *op) +{ + struct op o; + + bzero(&o, sizeof o); + o.type = OP_RANDOM; + o.u.random.block = op; + + return (op_add_child(parent, &o)); +} + +struct op * +op_noop(struct op *parent) +{ + struct op o; + + bzero(&o, sizeof o); + o.type = OP_NOOP; + + return (op_add_child(parent, &o)); +} + +struct op * +op_call(struct op *parent, struct procedure *proc) +{ + struct op o; + + bzero(&o, sizeof o); + o.type = OP_CALL; + o.u.call.proc = proc; + + return (op_add_child(parent, &o)); +} + +struct op * +op_fail(struct op *parent, char *reason) +{ + struct op o; + + bzero(&o, sizeof o); + o.type = OP_FAIL; + o.u.fail.reason = reason; + + return (op_add_child(parent, &o)); +} + +struct op * +op_connect(struct op *parent, const char *hostname, int portno) +{ + struct op o; + + bzero(&o, sizeof o); + o.type = OP_CONNECT; + o.u.connect.hostname = strdup(hostname); + o.u.connect.portno = portno; + return (op_add_child(parent, &o)); +} + +struct op * +op_disconnect(struct op *parent) +{ + struct op o; + + bzero(&o, sizeof o); + o.type = OP_DISCONNECT; + return (op_add_child(parent, &o)); +} + +struct op * +op_starttls(struct op *parent) +{ + struct op o; + + bzero(&o, sizeof o); + o.type = OP_STARTTLS; + return (op_add_child(parent, &o)); +} + +struct op * +op_sleep(struct op *parent, unsigned int ms) +{ + struct op o; + + bzero(&o, sizeof o); + o.type = OP_SLEEP; + o.u.sleep.ms = ms; + return (op_add_child(parent, &o)); +} + +struct op * +op_write(struct op *parent, const void *buf, size_t len) +{ + struct op o; + + bzero(&o, sizeof o); + o.type = OP_WRITE; + o.u.write.buf = buf; + o.u.write.len = len; + return (op_add_child(parent, &o)); +} + +struct op * +op_printf(struct op *parent, const char *fmt, ...) +{ + va_list ap; + char *buf; + int len; + + va_start(ap, fmt); + if ((len = vasprintf(&buf, fmt, ap)) == -1) + err(1, "vasprintf"); + va_end(ap); + + return op_write(parent, buf, len); +} + +struct op * +op_expect_disconnect(struct op *parent) +{ + struct op o; + + bzero(&o, sizeof o); + o.type = OP_EXPECT_DISCONNECT; + return (op_add_child(parent, &o)); +} + +struct op * +op_expect_smtp_response(struct op *parent, int flags) +{ + struct op o; + + bzero(&o, sizeof o); + o.type = OP_EXPECT_SMTP_RESPONSE; + o.u.expect_smtp.flags = flags; + return (op_add_child(parent, &o)); +} + +static void +usage(void) +{ + extern const char *__progname; + errx(1, "Usage: %s [-rvt] [-d delay] script", __progname); +} + +int +main(int argc, char **argv) +{ + struct script *s; + struct procedure *p; + int ch; + + while ((ch = getopt(argc, argv, "d:rvt")) != -1) { + switch(ch) { + case 'v': + verbose += 1; + break; + case 'd': + rundelay = atoi(optarg) * 1000; + break; + case 'r': + randomdelay = 1; + break; + case 't': + tapout = 1; + break; + default: + usage(); + /* NOTREACHED */ + } + } + argc -= optind; + argv += optind; + + if (argc != 1) + usage(); + + s = parse_script(argv[0]); + if (s == NULL) + errx(1, "error reading script file"); + + _op_connect = op_connect(NULL, "127.0.0.1", 25); + + if (tapout) { + printf("# smtpscript is an SMTP testing framework\n\n"); + printf("TAP version 13\n"); + } + + TAILQ_FOREACH(p, &s->procs, entry) + if (p->flags & PROC_TESTCASE) + run_testcase(p); + + if (tapout) + printf("1..%zu\n", test_total); + else { + printf("passed: %zu/%zu (skipped: %zu, failed: %zu, error: %zu)\n", + test_pass, + test_total, + test_skip, + test_fail, + test_error); + } + return (0); +} + +static struct op * +op_add_child(struct op *parent, const struct op *op) +{ + struct op *n; + + n = malloc(sizeof *n); + if (n == NULL) + err(1, "malloc"); + + memmove(n, op, sizeof *n); + n->next = NULL; + + /* printf("op:%p type:%i parent: %p\n", n, n->type, parent); */ + + if (parent) { + if (parent->u.block.start == NULL) + parent->u.block.start = n; + if (parent->u.block.last) + parent->u.block.last->next = n; + parent->u.block.last = n; + parent->u.block.count += 1; + } + + return (n); +} + +static void +run_testcase(struct procedure *proc) +{ + struct ctx c; + uint32_t rdelay; + + bzero(&c, sizeof c); + c.sock = -1; + c.lvl = 1; + + if (rundelay) { + if (randomdelay) + rdelay = arc4random_uniform(rundelay); + else + rdelay = rundelay; + usleep(rdelay); + } + + fflush(stdout); + + if (verbose > 1) + printf("\n"); + + if (!(proc->flags & PROC_NOCONNECT)) + process_op(&c, _op_connect); + process_op(&c, proc->root); + + if (c.sock != -1) + close(c.sock); + if (c.ssl) + ssl_close(c.ssl); + iobuf_clear(&c.iobuf); + + if (verbose > 1) { + printf("# Done with test-case \"%s\": ", proc->name); + } + + switch (c.result) { + case RES_OK: + test_total += 1; + if (proc->flags & PROC_EXPECTFAIL) { + print_testcase("not ok", proc->name, c.reason, "TODO", test_total); // XPass + test_fail += 1; + } else if (proc->flags & PROC_SKIP) { + test_skip += 1; + print_testcase("ok", proc->name, c.reason, "SKIP", test_total); + } + else { + print_testcase("ok", proc->name, c.reason, NULL, test_total); + test_pass += 1; + } + + break; + + case RES_SKIP: + test_skip += 1; + test_total += 1; + print_testcase("not ok", proc->name, c.reason, "SKIP", test_total); + break; + + case RES_FAIL: + test_total += 1; + if (proc->flags & PROC_EXPECTFAIL) { + print_testcase("not ok", proc->name, c.reason, "TODO", test_total); // XFail + test_pass += 1; + } else if (proc->flags & PROC_SKIP) { + test_skip += 1; + print_testcase("ok", proc->name, c.reason, "SKIP", test_total); + } + else { + print_testcase("not ok", proc->name, c.reason, NULL, test_total); + test_fail += 1; + } + + break; + + case RES_ERROR: + test_error += 1; + test_total += 1; + print_testcase("not ok", proc->name, c.reason, NULL, test_total); + break; + } + + if (verbose > 1) { + printf("\n"); + } + +} + +void print_testcase(char *status, char *name, char *reason, char *directive, size_t number) +{ + printf("%s %zu", status, number); + if (directive) + printf(" - %s # %s\n", name, directive); + else + if (reason) + printf(" - %s # %s\n", name, reason); + else + printf(" - %s\n", name); +} + +static size_t +strvisx2(char *dst, const char *src, size_t srclen, int flag) +{ + size_t n, r, i; + + n = strvisx(dst, src, srclen, flag); + if (n == 0) + return (0); + + r = n; + for (i = n - 1; i; i--) { + if (dst[i] == '\r') { + memmove(dst + i + 2, dst + i + 1, n + 1 - i); + dst[i+1] = 'r'; + dst[i] = '\\'; + r++; + } else if (dst[i] == '"') { + memmove(dst + i + 2, dst + i + 1, n + 1 - i); + dst[i+1] = '"'; + dst[i] = '\\'; + r++; + } + } + + return (r); +} + +static const char * +show_data(const char *src, size_t len, size_t max) +{ + static char buf[8192 + 3]; + char tmp[256]; + size_t l, n; + + l = len; + if (len > 2048) + l = 2048; + + buf[0] = '"'; + n = strvisx2(&buf[1], src, l, VIS_SAFE | VIS_NL | VIS_TAB | VIS_CSTYLE); + if (n >= max) { + snprintf(tmp, sizeof tmp, "...\" [%zu]", l); + buf[max - strlen(tmp)] = '\0'; + strlcat(buf, tmp, sizeof(buf)); + } else { + strlcat(buf, "\"", sizeof(buf)); + } + + return (buf); +} + +static void +print_op(struct op *op, int lvl) +{ + + + if (op->type == OP_BLOCK) + return; + + while (lvl--) + printf(" "); + + switch(op->type) { + + case OP_REPEAT: + printf("=> repeat: %i\n", op->u.repeat.count); + break; + + case OP_RANDOM: + printf("=> random: %i\n", op->u.random.block->u.block.count); + break; + + case OP_NOOP: + printf("=> noop\n"); + break; + + case OP_FAIL: + printf("=> fail: %s\n", op->u.fail.reason); + break; + + case OP_CALL: + printf("=> call: %s\n", op->u.call.proc->name); + break; + + case OP_CONNECT: + printf("=> connect %s:%i\n", + op->u.connect.hostname, + op->u.connect.portno); + break; + + case OP_DISCONNECT: + printf("=> disconnect\n"); + break; + + case OP_STARTTLS: + printf("=> starttls\n"); + break; + + case OP_SLEEP: + printf("=> sleep %ims\n", op->u.sleep.ms); + break; + + case OP_WRITE: + printf("=> write %s\n", + show_data(op->u.write.buf, op->u.write.len, 70)); + break; + + case OP_EXPECT_DISCONNECT: + printf("<= disconnect\n"); + break; + + case OP_EXPECT_SMTP_RESPONSE: + printf("<= smtp-response 0x%04x\n", op->u.expect_smtp.flags); + break; + + default: + printf("<> ??? %i;\n", op->type); + break; + } +} + + +static void +set_failure(struct ctx *ctx, int res, const char *fmt, ...) +{ + va_list ap; + int len; + + ctx->result = res; + va_start(ap, fmt); + if ((len = vasprintf(&ctx->reason, fmt, ap)) == -1) + err(1, "vasprintf"); + va_end(ap); +} + +static void +process_op(struct ctx *ctx, struct op *op) +{ + struct addrinfo hints, *a, *ai; + struct op *o; + struct iobuf *iobuf; + int i, r, s, save_errno, cont; + const char *cause; + char buf[16], *servname, *line; + ssize_t n; + size_t len; + const char *e; + + if (verbose > 1) + print_op(op, ctx->lvl); + + iobuf = &ctx->iobuf; + + switch(op->type) { + + case OP_BLOCK: + ctx->lvl += 1; + for (o = op->u.block.start; o; o = o->next) { + process_op(ctx, o); + if (ctx->result) + break; + } + ctx->lvl -= 1; + break; + + case OP_REPEAT: + ctx->lvl += 1; + for (i = 0; i < op->u.repeat.count; i++) { + process_op(ctx, op->u.repeat.op); + if (ctx->result) + break; + } + ctx->lvl -= 1; + break; + + case OP_RANDOM: + + if (op->u.random.block->u.block.count == 0) + return; + + ctx->lvl += 1; + + i = arc4random_uniform(op->u.random.block->u.block.count); + for (o = op->u.random.block->u.block.start; i; i--, o = o->next) + ; + process_op(ctx, o); + if (ctx->result) + break; + ctx->lvl -= 1; + break; + + case OP_NOOP: + break; + + case OP_FAIL: + set_failure(ctx, RES_FAIL, op->u.fail.reason); + break; + + case OP_CALL: + process_op(ctx, op->u.call.proc->root); + break; + + case OP_CONNECT: + if (ctx->sock != -1) + close(ctx->sock); + ctx->sock = -1; + iobuf_clear(iobuf); + + servname = NULL; + if (op->u.connect.portno) { + snprintf(buf, sizeof buf, "%i", op->u.connect.portno); + servname = buf; + } + bzero(&hints, sizeof hints); + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + r = getaddrinfo(op->u.connect.hostname, servname, &hints, &ai); + if (r) { + set_failure(ctx, RES_ERROR, + "failed to connect to %s:%s: %s", + op->u.connect.hostname, servname, gai_strerror(r)); + return; + } + + s = -1; + for(a = ai; a; a = a->ai_next) { + s = socket(a->ai_family, a->ai_socktype, a->ai_protocol); + if (s == -1) { + cause = "socket"; + continue; + } + if (connect(s, a->ai_addr, a->ai_addrlen) == -1) { + cause = "connect"; + save_errno = errno; + close(s); + errno = save_errno; + s = -1; + continue; + } + break; /* okay we got one */ + } + freeaddrinfo(ai); + if (s == -1) { + set_failure(ctx, RES_ERROR, + "failed to connect to %s:%s: %s", + op->u.connect.hostname, servname, cause); + } else { + ctx->sock = s; + iobuf_init(iobuf, 0, 0); + } + break; + + case OP_DISCONNECT: + if (ctx->sock != -1) + close(ctx->sock); + ctx->sock = -1; + iobuf_clear(iobuf); + break; + + case OP_STARTTLS: + if (ctx->ssl) + set_failure(ctx, RES_ERROR, "SSL context already here"); + else if ((ctx->ssl = ssl_connect(ctx->sock)) == NULL) + set_failure(ctx, RES_ERROR, "SSL connection failed"); + break; + + case OP_SLEEP: + usleep(op->u.sleep.ms * 1000); + break; + + case OP_WRITE: + iobuf_queue(iobuf, op->u.write.buf, op->u.write.len); + if (ctx->ssl) + r = iobuf_flush_ssl(iobuf, ctx->ssl); + else + r = iobuf_flush(iobuf, ctx->sock); + switch (r) { + case 0: + break; + case IOBUF_CLOSED: + set_failure(ctx, RES_FAIL, "connection closed"); + break; + case IOBUF_WANT_WRITE: + set_failure(ctx, RES_ERROR, "iobuf_write(): WANT_WRITE"); + break; + case IOBUF_ERROR: + set_failure(ctx, RES_ERROR, "IO error"); + break; + case IOBUF_SSLERROR: + set_failure(ctx, RES_ERROR, "SSL error"); + break; + default: + set_failure(ctx, RES_ERROR, "iobuf_write(): bad value"); + break; + } + break; + + case OP_EXPECT_DISCONNECT: + if (iobuf_len(iobuf)) { + set_failure(ctx, RES_ERROR, "%zu bytes of input left", + iobuf_len(iobuf)); + break; + } + if (ctx->ssl) + n = iobuf_read_ssl(iobuf, ctx->ssl); + else + n = iobuf_read(iobuf, ctx->sock); + switch (n) { + case IOBUF_CLOSED: + close(ctx->sock); + ctx->sock = -1; + if (ctx->ssl) + ssl_close(ctx->ssl); + break; + case IOBUF_WANT_READ: + set_failure(ctx, RES_ERROR, "iobuf_read(): WANT_READ"); + break; + case IOBUF_ERROR: + set_failure(ctx, RES_ERROR, "IO error"); + break; + case IOBUF_SSLERROR: + set_failure(ctx, RES_ERROR, "SSL error"); + break; + default: + set_failure(ctx, RES_FAIL, "data read: %s", + show_data(iobuf_data(iobuf), iobuf_len(iobuf), 70)); + break; + } + break; + + case OP_EXPECT_SMTP_RESPONSE: + line = NULL; + while (1) { + line = iobuf_getline(iobuf, &len); + if (line) { + e = parse_smtp_response(line, len, NULL, &cont); + if (e) { + set_failure(ctx, RES_FAIL, e); + return; + } + if (!cont) { + iobuf_normalize(iobuf); + break; + } + if (!(op->u.expect_smtp.flags + & RESP_SMTP_MULTILINE)) { + set_failure(ctx, RES_FAIL, + "single line response expected"); + return; + } + continue; + } + + if (iobuf_len(iobuf) >= SMTP_LINE_MAX) { + set_failure(ctx, RES_FAIL, "line too long"); + return; + } + + iobuf_normalize(iobuf); + + again: + if (ctx->ssl) + n = iobuf_read_ssl(iobuf, ctx->ssl); + else + n = iobuf_read(iobuf, ctx->sock); + switch (n) { + case IOBUF_CLOSED: + set_failure(ctx, RES_FAIL, "connection closed"); + return; + case IOBUF_WANT_READ: + goto again; + case IOBUF_ERROR: + set_failure(ctx, RES_ERROR, "io error"); + return; + case IOBUF_SSLERROR: + set_failure(ctx, RES_ERROR, "SSL error"); + return; + default: + break; + } + } + + /* got our response */ + + if (verbose > 1) { + len = ctx->lvl; + while (len--) + printf(" "); + printf(" >>> %s\n", show_data(line, strlen(line), 70)); + } + + switch (line[0]) { + case '2': + case '3': + if (!(op->u.expect_smtp.flags & RESP_SMTP_OK)) + set_failure(ctx, RES_FAIL, + "unexpected response code0: %s", line); + break; + case '4': + if (!(op->u.expect_smtp.flags & RESP_SMTP_TEMPFAIL)) + set_failure(ctx, RES_FAIL, + "unexpected response code1: %s", line); + break; + case '5': + if (!(op->u.expect_smtp.flags & RESP_SMTP_PERMFAIL)) + set_failure(ctx, RES_FAIL, + "unexpected response code2: %s", line); + break; + default: + set_failure(ctx, RES_FAIL, + "unexpected response code???: %s", line); + break; + } + break; + + default: + ctx->result = RES_ERROR; + ctx->reason = "invalid operator"; + break; + } +} + +static const char * +parse_smtp_response(char *line, size_t len, char **msg, int *cont) +{ + size_t i; + + if (len >= SMTP_LINE_MAX) + return "line too long"; + + if (len > 3) { + if (msg) + *msg = line + 4; + if (cont) + *cont = (line[3] == '-'); + } else if (len == 3) { + if (msg) + *msg = line + 3; + if (cont) + *cont = 0; + } else + return "line too short"; + + /* validate reply code */ + if (line[0] < '2' || line[0] > '5' || !isdigit(line[1]) || + !isdigit(line[2])) + return "reply code out of range"; + + /* validate reply message */ + for (i = 0; i < len; i++) + if (!isprint(line[i])) + return "non-printable character in reply"; + + return NULL; +} diff --git a/foobar/portable/smtpscript/smtpscript.h b/foobar/portable/smtpscript/smtpscript.h new file mode 100644 index 00000000..ba90b240 --- /dev/null +++ b/foobar/portable/smtpscript/smtpscript.h @@ -0,0 +1,79 @@ +/* $OpenBSD: iobuf.h,v 1.1 2012/01/29 00:32:51 eric Exp $ */ +/* + * Copyright (c) 2012 Eric Faurot <eric@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +struct op; + +#define PROC_TESTCASE 0x0001 +#define PROC_SKIP 0x0002 +#define PROC_EXPECTFAIL 0x0004 +#define PROC_NOCONNECT 0x0008 + + +struct variable { + TAILQ_ENTRY(variable) entry; + char *name; +}; + +struct procedure { + int flags; + + TAILQ_ENTRY(procedure) entry; + char *name; + + TAILQ_HEAD(, variable) vars; + int varcount; + + struct op *root; + + int skip; + int expect_fail; +}; + +#define RESP_SMTP_OK 0x0001 +#define RESP_SMTP_TEMPFAIL 0x0002 +#define RESP_SMTP_PERMFAIL 0x0004 +#define RESP_SMTP_ANY 0x0007 + +#define RESP_SMTP_MULTILINE 0x0100 + +struct script { + TAILQ_HEAD(, procedure) procs; +}; + +int proc_addvar(struct procedure *, char *name); +int proc_getvaridx(struct procedure *, char *name); + +struct op *op_block(struct op *); +struct op *op_repeat(struct op *, int, struct op *); +struct op *op_random(struct op *, struct op *); +struct op *op_noop(struct op *); +struct op *op_fail(struct op *, char *); +struct op *op_call(struct op *, struct procedure *); +struct op *op_connect(struct op *, const char *, int); +struct op *op_disconnect(struct op *); +struct op *op_starttls(struct op *); +struct op *op_sleep(struct op *, unsigned int); +struct op *op_write(struct op *, const void *, size_t); +struct op *op_printf(struct op *, const char *, ...); + +struct op *op_expect_disconnect(struct op *); +struct op *op_expect_smtp_response(struct op *, int); + +struct procedure *procedure_create(struct script *, char *); +struct procedure *procedure_get_by_name(struct script *, const char *); + +struct script * parse_script(const char *); diff --git a/foobar/portable/smtpscript/smtpscript/Makefile b/foobar/portable/smtpscript/smtpscript/Makefile new file mode 100644 index 00000000..0520606f --- /dev/null +++ b/foobar/portable/smtpscript/smtpscript/Makefile @@ -0,0 +1,12 @@ +.PATH: ${.CURDIR}/.. + +PROG= smtpscript +SRCS= smtpscript.c iobuf.c parse.y ssl.c +NOMAN= noman + +LDADD+= -lutil -lssl -lcrypto +DPADD+= ${LIBEVENT} ${LIBUTIL} ${LIBSSL} ${LIBCRYPTO} +CPPFLAGS+= -I${.CURDIR}/.. +CPPFLAGS+= -DIO_TLS + +.include <bsd.prog.mk> diff --git a/foobar/portable/smtpscript/ssl.c b/foobar/portable/smtpscript/ssl.c new file mode 100644 index 00000000..54f0993d --- /dev/null +++ b/foobar/portable/smtpscript/ssl.c @@ -0,0 +1,167 @@ +/* $OpenBSD: ssl.c,v 1.50 2012/11/12 14:58:53 eric Exp $ */ + +/* + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> + * Copyright (c) 2008 Reyk Floeter <reyk@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <err.h> +#include <event.h> +#include <fcntl.h> +#include <imsg.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <openssl/ssl.h> +#include <openssl/engine.h> +#include <openssl/err.h> + +#define SSL_CIPHERS "HIGH" + +void ssl_error(const char *); + +static void ssl_init(void); +static SSL_CTX *ssl_ctx_create(void); +static void *ssl_client_ctx(void); + +static void +ssl_init(void) +{ + static int init = 0; + + if (init) + return; + + init = 1; + + SSL_library_init(); + SSL_load_error_strings(); + + OpenSSL_add_all_algorithms(); + + /* Init hardware crypto engines. */ + ENGINE_load_builtin_engines(); + ENGINE_register_all_complete(); +} + +void +ssl_error(const char *where) +{ + unsigned long code; + char errbuf[128]; + + for (; (code = ERR_get_error()) != 0 ;) { + ERR_error_string_n(code, errbuf, sizeof(errbuf)); + fprintf(stderr, "debug: SSL library error: %s: %s", + where, errbuf); + } +} + +void * +ssl_connect(int sock) +{ + SSL *ssl; + + ssl = ssl_client_ctx(); + + if (SSL_set_fd(ssl, sock) == 0) { + ssl_error("ssl_connect:SSL_set_fd"); + SSL_free(ssl); + return (NULL); + } + + if (SSL_connect(ssl) != 1) { + ssl_error("ssl_connect:SSL_connect"); + SSL_free(ssl); + return (NULL); + } + + return ((void*)ssl); +} + +void +ssl_close(void *a) +{ + SSL *ssl = a; + + SSL_free(ssl); +} + +static SSL_CTX * +ssl_ctx_create(void) +{ + SSL_CTX *ctx; + + ssl_init(); + + ctx = SSL_CTX_new(SSLv23_method()); + if (ctx == NULL) { + ssl_error("ssl_ctx_create"); + errx(1, "ssl_ctx_create: could not create SSL context"); + } + + SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF); + SSL_CTX_set_timeout(ctx, 30); + SSL_CTX_set_options(ctx, + SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_TICKET); + SSL_CTX_set_options(ctx, + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + if (!SSL_CTX_set_cipher_list(ctx, SSL_CIPHERS)) { + ssl_error("ssl_ctx_create"); + errx(1, "ssl_ctx_create: could not set cipher list"); + } + + return (ctx); +} + +static void * +ssl_client_ctx(void) +{ + SSL_CTX *ctx; + SSL *ssl = NULL; + int rv = -1; + + ctx = ssl_ctx_create(); + + if ((ssl = SSL_new(ctx)) == NULL) + goto done; + SSL_CTX_free(ctx); + + if (!SSL_set_ssl_method(ssl, SSLv23_client_method())) + goto done; + + rv = 0; +done: + if (rv) { + if (ssl) + SSL_free(ssl); + else if (ctx) + SSL_CTX_free(ctx); + ssl = NULL; + } + return (void*)(ssl); +} |