pax_global_header00006660000000000000000000000064135261024660014517gustar00rootroot0000000000000052 comment=2a77520108f1d6e340fcf93a5573adcaae3c8186 s-nail-14.9.15/000077500000000000000000000000001352610246600130635ustar00rootroot00000000000000s-nail-14.9.15/.gitignore000066400000000000000000000000501352610246600150460ustar00rootroot00000000000000.doc/ .obj/ gen-*.* include/mx/config.h s-nail-14.9.15/.mailmap000066400000000000000000000003341352610246600145040ustar00rootroot00000000000000Gunnar Ritter Steffen Nurpmeso Steffen Nurpmeso Steffen Nurpmeso s-nail-14.9.15/COPYING000066400000000000000000000154531352610246600141260ustar00rootroot00000000000000/* * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso. * * 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. */ /* * Copyright (c) 2000 * Gunnar Ritter. 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 acknowledgements: * This product includes software developed by the University of * California, Berkeley and its contributors. * This product includes software developed by Christos Zoulas. * This product includes software developed by Gunnar Ritter * and his contributors. * 4. Neither the name of the University nor the names of its contributors * nor the name of Gunnar Ritter nor the names of his contributors * may 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 DEVELOPERS OR COPYRIGHT HOLDERS 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. */ /* * Copyright (c) 1980, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 1996 * 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. 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. */ ========================================================================== The GSSAPI code is partially derived from sample code in: /* * Partially derived from sample code in: * * GSS-API Programming Guide * Part No: 806-3814-10 * Sun Microsystems, Inc. 901 San Antonio Road, Palo Alto, CA 94303-4900 U.S.A. * (c) 2000 Sun Microsystems */ /* * Copyright 1994 by OpenVision Technologies, Inc. * * Permission to use, copy, modify, distribute, and sell this software * and its documentation for any purpose is hereby granted without fee, * provided that the above copyright notice appears in all copies and * that both that copyright notice and this permission notice appear in * supporting documentation, and that the name of OpenVision not be used * in advertising or publicity pertaining to distribution of the software * without specific, written prior permission. OpenVision makes no * representations about the suitability of this software for any * purpose. It is provided "as is" without express or implied warranty. * * OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO * EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, 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. */ ========================================================================== The MD5 code is derived from RFC 1321: Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All rights reserved. License to copy and use this software is granted provided that it is identified as the "RSA Data Security, Inc. MD5 Message-Digest Algorithm" in all material mentioning or referencing this software or this function. License is also granted to make and use derivative works provided that such works are identified as "derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm" in all material mentioning or referencing the derived work. RSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided "as is" without express or implied warranty of any kind. These notices must be retained in any copies of any part of this documentation and/or software. s-nail-14.9.15/INSTALL000066400000000000000000000341721352610246600141230ustar00rootroot00000000000000I n s t a l l i n g S - n a i l / S - m a i l x ================================================ 1.0 Compilation 1.1 Configuration failed! 1.2 Building failed! 1.3 Tests failed! 1.4 How can i enable debugging? 2.0 Notes on building the latest release 1.0 Compilation --------------- System specific notes can be found in the next section. All (optional) features are documented (and adjustable) in make.rc. Adjustments may also take place, and are usually done, from the command line, overriding those made in make.rc (if any). The set of builtin MIME types could be adjusted in ./mime.types. Experts could find it valuable to adjust C code in ./mx-config.h. Without any adjustments all non-experimental features will be enabled, except some which provide redundant functionality (e.g., OPT_SPAM_SPAMC is disabled because the generic OPT_SPAM_FILTER can do the same). None of the features are "require"d by default, so that configuration will not fail shall any of them not be available or usable. The generated configuration is tracked: changes will be recognized and cause automatic cleanups and rebuilds as necessary. $ make -j2 tangerine # equals "$ make config build test install" $ make citron # equals "$ make config build install" $ make distclean # *Completely* cleanup working directory With adjustments: $ make OPT_POP3=no OPT_SMTP=require tangerine OBJDIR=/tmpfs/mx $ make OPT_CROSS_BUILD=y VAL_PREFIX=/some/nasty/prefix citron With utility program and feature adjustments: $ make awk=/usr/bin/nawk VERBOSE=yes OPT_NET=no tangerine If OPT_DOTLOCK has been enabled then the minimal privilege-separated SETUID (to VAL_DOTLOCK_USER, default "root") helper program will be build and installed, and therefore the installation process needs to have the appropriate privileges. In this case it may be useful to separate the configuration / building and the installation tasks and give the last step higher privileges via super(1), sudo(1), su(1) or a similar mechanism, e.g.: $ make VAL_PREFIX=/usr config && make -j4 && super make DESTDIR=./xy install would create a "s-nail" binary and install a "s-nail" manual etc. under the prefix "/usr" but rooted under "[./]xy", i.e., the binary would be installed as "[./]xy/usr/bin/s-nail". Out-of-tree compilation is supported; to use it, create the target directory of desire, change into it and run the make-emerge.sh script shipped with S-nail, then proceed as with normal in-tree building. Generated configuration files, objects and test output will be located in the directory .obj/ (or what $OBJDIR defines), in- or off-tree. The following make(1) targets exist, the default being `build': - tangerine Shorthand for "$ make config build test install": create or check and update configuration, build, test and install. The variable $DESTDIR will be honoured (see make.rc), but not be tracked in the configuration. - citron Shorthand for "$ make config build install". - all Shorthand for "$ make config build". - config Only create or check and update the configuration. - build Only build (using the existing configuration). - install Only install using the built files of the existing configuration. The variable $DESTDIR will be honoured (see make.rc), but not be tracked in the configuration. S-nail will create an uninstall shell script (${VAL_SID}${VAL_MAILX}-uninstall.sh), but which will *not* be installed if $DESTDIR is set non-empty -- within $DESTDIR, that is. Copy or move it manually from the S-nail $OBJDIR into $DESTDIR as necessary in that case. - clean Remove anything which can be rebuild. - distclean Remove anything which can be rebuild or reconfigured. - test Run mx-test.sh in --check-only mode on the built binary. - testnj Likewise, but add --no-jobs to avoid spawning multiple tests in parallel. Setting the make(1) variable $VERBOSE to an arbitrary value during `config' time, as in "$ make VERBOSE=xy tangerine", will change the output of the `all', `install' etc. targets to a more verbose one. There are also some predefined configuration sets available, meant to be used instead of doing manual adjustments. - CONFIG=NULL, CONFIG=NULLI Anything that can be turned off is off. MIME cannot. The latter adds and "require"s iconv(3) and adds the user interface strings. - CONFIG=MINIMAL Possibly what people want who need nothing but a MIME-capable mailx(1) and do not regret improved usability for the rare interactive use occasions. Adds documentation strings, the built-in line editor (MLE) with history support and key bindings, error tracking, basic colour support and IDNA addresses, as well as generic spam filter support. "Require"s iconv(3), regex(3) and the dotlock helper. - CONFIG=NETSEND Sending messages directly to the mail provider via the SMTP protocol, instead of requiring a local mail-transfer-agent (MTA) who does. Adds SSL/TLS, SMTP, GSSAPI and .netrc file parsing on top of MINIMAL. "Require"s iconv(3), SSL/TLS, SMTP (sockets) and the dotlock helper. - CONFIG=MAXIMAL Like MINIMAL, but turns on all (other) options, also obsolete or redundant ones, but none of them required. If during configuration or building some libraries seem to be missing that you know are installed on your system, or if other errors occur due to missing files but which you know exist, please ensure that the environment variable $C_INCLUDE_PATH includes the necessary "include/" paths and the environment variable $LD_LIBRARY_PATH includes the necessary "lib/"rary paths. The build system will inspect these two environment variables and automatically convert them to cc(1) (c99(1)) -I and -L options (since these environment variables are, different to the command line options, not part of the POSIX standard). To set these environment variables, the following can be done in a Bourne / Korn / POSIX compatible shell: $ C_INCLUDE_PATH="${C_INCLUDE_PATH}:/usr/local/include" $ LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:/usr/local/lib" $ export C_INCLUDE_PATH LD_LIBRARY_PATH $ make tangerine CONFIG=MINIMAL Other than the standard paths /usr/{bin,include,lib} and /usr/local/[.] should possibly be placed first instead, presuming that they are meant to override things which usually exist in standard locations. If all else fails you can also forcefully pass in include directives and library paths via prefilled $INCS and $LIBS variables: $ make INCS=-I/mypath/include LIBS="-L/mypath/lib -lmylib" tangerine 1.1 Configuration failed! ------------------------- Please see the section "Notes on building the latest release" below first, maybe it has an item for the failing platform, or even gives an answer on how to proceed nonetheless. The configuration process creates a directory ./.obj (or $OBJDIR, if set) to store some files of interest (it is also here where the build and the tests take place): - mk-config.log Any output generated during the configuration process. - mk-config.h C program header representing the configuration. - mk-config.env sh(1) environment representing the configuration. - mk-config.inc Compiler directives to include necessary C headers ... - mk-config.lib ... to find and link against needed external libraries. Of special interest is mk-config.log since the error usually manifests here in textual output. Maybe that makes it obvious what can be done (header files could not be found because of missing entries in $C_INCLUDE_PATH, libraries could not be linked because of incomplete $LD_LIBRARY_PATH, etc.). If things went wrong so completely that even "$ make distclean" is not possible, simply removing .obj ($OBJDIR) will do: "$ rm -rf ./.obj". In any case it would be appreciated if you could report the problem, so that problems can be fixed! 1.2 Building failed! -------------------- Terrible! This should not happen if configuration succeeded! Again, it would be appreciated if you could report the problem, so that problems can be fixed! 1.3 Tests failed! ----------------- Please see the section "Notes on building the latest release" below first: sometimes it is known that certain tests fail on a specific target platform, and there is no applicable fix on application level. For example, it is not uncommon that Unicode tests fail because the platform uses old or incomplete character information. It could also be a miscompilation, caused by excessive optimization and/or a compiler bug. If OPT_AUTOCC had been used in conjunction with a non-debug target (the defaults), an additional cc_maxopt=1 (or an EXTRA_CFLAGS=-O1 etc.) on the command line and a successive reconfiguration and -compilation could turn the green testing light on. In order to inspect the error more thoroughly, the mx-test.sh script can be used in --run-test mode to invoke the failing tests (testing echoes the actual test names in brackets), for example $ ./mx-test.sh --run-test ./s-nail localopts mbox would run the tests "localopts" and "mbox". Test output files look like t.TESTNAME-XY and reside in the ./.obj ($OBJDIR) directory. (If the build and test is done in a repository checkout, and if the branch "test-out" exists, then in addition to the failing test output the expected as well as -- optionally -- the file difference of these two is generated automatically, too. And these outputs are also generated for normal "$ make test" runs, then.) It might make sense to send the output files of the failing tests to the mailing-list. Because of ML message size restrictions, creating a compressed tar(1) archive of the files would be appreciated: $ rm -f .obj/t.*.old .obj/t.*.diff # In repository checkout only $ tar -cf - .obj/t.* | xz > tlogs.tar.xz or $ tar -cf - .obj/t.* | gzip > tlogs.tar.gz or $ tar -cf - .obj/t.* | zstd -T0 -19 > tlogs.tar.zst etc. Thank you! 1.4 How can i enable debugging? ------------------------------- Enable OPT_DEBUG=yes during compilation $ make CONFIG=MAXIMAL OPT_DEBUG=yes to gain compiler debug flags, memory bound canaries and Not-Yet-Dead function graph listings in case of crashes etc. For sanitizers plus special options exist, please read make.rc for more. In case of errors using the -d (*debug*) and -v (*verbose*; may be set multiply for more verbosity) options (variables) is always good idea: $ s-nail -dvv or $ s-nail -Sdebug -Sverbose -Sverbose Should you really discover any problems with S-nail it would be very useful for development if you would contact the mailing-list! Thank you! 2.0 Notes on building the latest release ---------------------------------------- - All systems: * I have turned off -Wstrict-overflow warnings unless we are debug enabled (talking about OPT_AUTOCC=yes here): too noisy on some gcc. * There are warnings on unused returns from some I/O functions. These will vanish after the large v15 I/O and MIME rewrite. * Some "XYZ may be used uninitialized" warnings are logically false. * Running the tests on a NFS filesystem might cause some false negatives. (Seen on the SunOS 5.11 OpenCSW machines, in [pipe_handlers] failure due to hard link count mismatch since NFS links the file to some other name, too, and that lingers too long, reproducibly). - All 32-bit systems: * There _may_ be warnings about format strings, like, e.g., auxlily.c:1610:10: warning: format '%lu' expects type 'long unsigned int', but argument 3 has type 'size_t' The codebase is ISO C89 which has no %z printf(3) format. However we try hard to detect the real type size and define the "PRI[du]Z" macros which end up with the correct size, which is also compile-time asserted (via "MCTA(sizeof(size_t) == XZ)"). By forcing ISO C99 mode when compiling these warnings vanish, e.g., with gcc(1) and clang(1): with OPT_AUTOCC pass "EXTRA_CFLAGS=-std=c99", otherwise ensure -std=c99 in $CFLAGS. Development: . CRUX Linux (3.5/x86-64). Frequently: . ArchLinux (x86-64). . FreeBSD (11.2/x86; 11.3-RC2/x86). - On 11.2 the tests iconv_mbyte_base64-4 and iconv_mbyte_base64-8 will fail because the ISO C function iswprint(3) will falsely claim that U+FF08 and U+FF09 (FULLWIDTH LEFT and RIGHT PARENTHESIS, respectively) are not printable, causing replacements. This is a known bug, see - iconv_mainbody-5-5 is triggered because a different replacement character is used than the one we expect. . OpenBSD (6.4/x86; 6.5/x86). . Solaris @ First of all: thanks to OpenCSW.org for offering SSH access to their Solaris build cluster! (Sparc and x86 machines with SunOS 5.9, 5.10 and 5.11.) * Some notes collected on earlier trials: + We forcefully disable stack protectors on SunOS/gcc because of linking errors seen in earlier tests. + If you get the compiler / system header installation error Undefined first referenced symbol in file __builtin_stdarg_start auxlily.o you have to overwrite the symbol with __builtin_va_start, e.g., in conjunction with OPT_AUTOCC add this: EXTRA_CFLAGS='-D__builtin_stdarg_start=__builtin_va_start' - You may see failures in Unicode-related tests due to outdated or false Unicode tables on elder releases (as in NetBSD). Pre-release, and other landmarks: . DragonFly BSD (5.6.1/x86-64). See FreeBSD for the three failing test cases. . NetBSD (8.0_RC1/i386). - Still not have setup all my environment after having lost all, so not tested recently. (- Test shcodec-unicode fails, like Solaris. (Faulty Unicode ctype.)) . OSUKISS Linux (boing-boom-tschak) @ Thanks to Jean-Marc Pigeon for generously providing access to a VZGOT on the most "beefy" machine i have ever worked on! ^_^ . Void Linux (x86) - Still not have setup all my environment after having lost all, so not tested recently. # s-ts-mode s-nail-14.9.15/NEWS000066400000000000000000002413721352610246600135730ustar00rootroot00000000000000S - n a i l / S - m a i l x N e w s ==================================== mdocmx(7) anchors are denoted by a number-sign #: typing "^A ANCHOR" while reading the man(1)ual in a capable less(1) will scroll to the manual's Point-Of-Interest, and pointing a web browser to the "#ANCHOR" of the online manual works. v14.9.15 ("Tit family in the trees"), 2019-08-18 ------------------------------------------------ Plugging a bug regarding copying data out of invalid MBOX mail databases which is present in all BSD Mails and in Unix V10 mail, and bringing in some tweaks, this update hopefully really marks the end of the v14.9.* series. After more than four and a half years i again have a VM testbed, with an increasing number of VM combinations. (Yet still too few, but nonetheless, a dramatical improvement.) This includes a GSS-API testbed, with an ArchLinux server and Linux and FreeBSD clients (do not ask why no additional FreeBSD server, i want to use binary packages). This brought GSSAPI tweaks. Credits, in order of commit appearance: Ralph Corderoy, Chet Ramey, Robert Elz, Jilles Tjoelker, Steve Izma, Viktor Szépe, and Jean-Marc Pigeon. Very special thanks go to Tarqi Kazan and Ivan Vučica, who tested GSS-API in the past until it worked (again), testing against my blind flight patches! Thank you very much, guys! We welcome Chet Ramey, Jilles Tjoelker and Steve Izma in THANKS. NOTES, ChangeLog (packager-affine) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Warning: we _will_ have v15-compat=yes as a default in v14.10! - *features*#412 and *tls-features*#582 are now prefixed with a comma ",", not with the number sign "#" (which could increasingly `eval'#191uate to a comment). - We now support parallelized tests. It takes a numeric job number out of $MAKEFLAGS, or tries to fetch the number of processors otherwise (really!). To go singleprocessor "$ make testnj" has to be called explicitly. With or without, we will terminate tests which take too long. This is truly tremendous, on the unstable9s machine of the OpenCSW.org cluster for example we now need 24 seconds instead of by far more than 300. What a release! With help of (Chet Ramey, Robert Elz, Jilles Tjoelker and Steve Izma) - EXTERNAL authentication is truly a mess. It has been fixed for POP3, where it was broken on our side. But it seems the internet does not like that, or cannot (pass user credentials from a certificate gracefully to the authenticator). Anyway. I have introduced EXTERNANON in addition for all of IMAP, POP3 and SMTP. This could now result in a usable combination, regardless of what server(s) are contacted. - The hook *on-account-cleanup*#477 will now be called even upon program exit (i.e., implicitly leaving the account). ChangeLog ^^^^^^^^^ - New *followup-to-add-cc*#418 will place the user in the Cc: list if it will place her in the Mail-Followup-To:. - New hook *on-program-exit*#485. - *pop3-auth*#499=gssapi is now supported. For IMAP, SASL-IR will be used for GSSAPI if possible (saving a packet round-trip). - *expandaddr*#409 has the new keyword "namehostex". If set, plain name addressees, like "To: steffen", will be expanded to NAME@HOSTNAME (where the latter could be *hostname*#434) if NAME is a valid user on the current host. (Viktor Szépe, Jean-Marc Pigeon) git(1) shortlog (edited) ^^^^^^^^^^^^^^^^^^^^^^^^ Steffen Nurpmeso (45): 94546dc0 Stop prefixing $features and $tls-features #, use , instead.. 4089979b mx_termios_controller_setup(): fix: honour sole $COLUMNS/$LINES in batch mode.. 897229cf nail.1: is expanded unless *hostname* set to empty string f5db11fe a_cwrite_save1(): FIX: ensure pre-v15 MBOX separation "in between" messages.. d203daa2 a_cwrite_save1(): FIX2: ensure pre-v15 MBOX separation "in between" messages.. b8663873 mx_smtp_mta(): FIX: never handled SIGPIPE! 43fabd65 Add *followup-to-add-cc* 7963df6c Tweak [a99becbc] (main.c: set MAILSEND_HEADERS_PRINT..) (Ralph Corderoy).. be5859d8 mx-test.sh: support test parallelization 12798bd4 mx-test.sh: catch wild (Chet Ramey,Robert Elz,Jilles Tjoelker,Steve Izma).. 85b2437e THANKS: Chet Ramey 989b122c THANKS: Jilles Tjoelker a12f56b3 THANKS: Steve Izma e5435409 mx-test.sh: no, use my initial approach plus Robert Elz.. 70108b66 mx-test.sh: use "while :;" not "while [ 1 ];" (Robert Elz) 32673514 (Test and) FIX: EXTERNAL AUTH for POP3 and IMAP (test bed, horray!) 2d8216f6 Call *on-account-cleanup* on program exit, too! ea340781 Add *on-program-exit* hook 81634ead Add EXTERNANON auth type (the internet is BROKEN!).. 7d18860c mx_url_parse(): do not treat empty user as set db0ef042 Add GSSAPI authentication for POP3 dda830bc Switch IMAP GSSAPI implementation to new shared one f00ef7fc *expandaddr*: add "namehostex" (Viktor Szépe, Jean-Marc Pigeon) 9d82d85e mx_pop3_setfile(), imap_setfile(): oops, !v15-compat URL _may_ have no user! v14.9.14 ("Great tit passed moult"), 2019-07-27 ----------------------------------------------- This is an unwanted and unplanned but unfortunately necessary bugfix release. I hope it marks the end of the v14.9.* series. I presume you would be surprised if it would not also bring some features, this time mostly support of MTA-style aliases as inquired by Jean-Marc Pigeon, some authentication work (XOAUTH2/ OAUTHBEARER support), and as usual development to the last minute. It fixes IMAP GSSAPI authentication, thanks to Ivan Vučica for reporting and testing this issue (Debian #930691; still have no testbed, but will soon!), and imap-delim, which i broke in July 2017, thanks to Ralph Keller for repetitive reporting. For OpenBSD and SunOS 5.9 this release fixes long standing (must be) race conditions regarding child processes and their I/O setup. Never seen before, but my new box (i stepped a decade of hardware improvements, finally) rather regulary has shown them when running the test suite. (On the OpenCSW cluster my speed varies, but i had a very good day and seen them there once.) This (finally) caused the complete rewrite of the child process (and termios) handling that i (had to) mention in communication with Gavin Troy already back in, i do not know -- 2013? (Still not event loop based, but near getting good feelings there.) Funnily the problem (child descriptors were closed by the parent before the fork(2)ed childs had the opportunity to dup(2)licate their file descriptors) reminded me of a message of the german computer magazine c't, maybe around 2001/2002, when OpenBSD improved their fork(2) performance in a day or two after having appeared declassified in a comparison with other OSes. (Of course it was nothing but our own fault to not synchronize on the child, but blindly assuming that a fork(2) child gets the opportunity to run immediately.) Dear Predrag: would it now be possible for you to upgrade from v14.8.12? I really would like to know! Credits, in order of commit appearance: Martin Lucina, Viktor Szépe, Alexander Harm, Anders Magnusson, Thomas Haigh, Martin T, Ivan Vučica, Nicholas Marriott, Alexander Harm, Steven Penny, Jean-Marc Pigeon, Martin Neitzel, Paul Vojta, Russell Bell, Paride Legovini and Ralph Keller. A special credit to Coverity.com once again, it found bugs! (, project number 444.) We welcome Martin Lucina, Anders Magnusson, Thomas Haigh, Martin T, Ivan Vučica, Nicholas Marriott, Steven Penny and Ralph Keller in THANKS. NOTES, ChangeLog (packager-affine) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - The (very backward) Debian mawk is now supported directly. (Martin Lucina, Viktor Szépe) - GCC (8.3.0) -Os inlining bug (wmt) is worked around. And GNU awk 5 warnings have been fixed (before CRUX). - "|PIPE RECEIVER" errors seen on SunOS 5.9 and OpenBSD have first been fixed, and then caused a major rewrite of the child and termios handling for a rather "real" fix. The latter has the side effect that *pipe-TYPE/SUBTYPE*#491 handlers will now have their standard output go to /dev/null. - IMAP GSSAPI authentication should work again. (Ivan Vučica) - -C#60 testable can be used more than two times. - The "grappa" mode of mk/make-release.sh can now be used "everywhere". See INSTALL on interest. (It now gracefully fails if s-nail is not installed: we need that for hashing.) - New option OPT_MTA, by default enabled. Set *mta-aliases*#467 to a valid path in aliases(5) syntax, and we will expand them. All Postfix directives but :include: are supported. Only clear text files are supported, no DBs. (Jean-Marc Pigeon) - `~^'#320 will now verify *expandaddr*#409 right away in "~^ head ins to|cc|bcc", as is already done for `~t'#341, `~c'#324 and `~b'#323. - OPT_AGENT and OPT_SPAM_SPAMD are gone; they were obsoleted on 2017-07-16, and it is not expected to become noticed. - *sendwait*#531 is now initially set, and it gained an optional value, a comma-separated list of case-insensitive strings naming specific subsystems for which synchronousness shall be ensured (only). Possible values are "mta" for *mta*#466 delivery, and "pcc" for command-pipe receivers. P.S.: you can get a list of all initial values plus via $ s-nail -:/ -v -Xset -Xx - Colours may now happen even in quickrun mode (-e#65, -H#68, -L#71)! I thought it is ok nonetheless, because we i think always documented to enwrap `colour'#160 setting in an according `if'#215, as in \if terminal && [ "$features" =% +colour ] \colo iso view-header fg=red \end - OPT_SOCKETS has been renamed to OPT_NET. - -Y#87 is now well defined under all conditions, with tests: The commands will be evaluated successively in the given order, and as if given on the program's standard input -- before interactive prompting begins in interactive mode, after standard input has been consumed otherwise. - XOAUTH2 / OAUTHBEARER (OAuth 2.0 bearer token, RFC 6750) autentication is now supported for all protocols. New FAQ entry "But, how about XOAUTH2 / OAUTHBEARER?"#45 For driving the necessary external update tool a new *on-main-loop-tick*#484 hook has been introduced. (I am thinking about adding support for an optional built-in token refresh.) - Support for the EXTERNAL authentication method has been introduced. This is UNTESTED, though. (I am still in the process of re-setting up my VM test environment.) We do not verify presence of a client certificate etc., but only that a TLS secured channel is active, when using this method. (We now *verbose*#594 log the used TLS version and cipher, too.) - *imap-delim*#654 works again as advertised! This was broken in [1b9897a9] ((BWDIC!) Fix *imap-delim* behaviour.., 2017-07-01), and i think i was pretty much irritated by then. Sorry!! While here, take *imap-delim* into account for `imapcodec'#650. (Ralph Keller). ChangeLog ^^^^^^^^^ - Manual section "HISTORY"#51 improved a bit. (Thomas Haigh) - New variable *line-editor-cpl-word-breaks*#445 (yet a bit restricted). - MLE: add mle-raise-{int,quit,tstp} functions. Ie., raising those signals via ^C and ^Z is no longer hard-wired (in the MLE), but can be reassigned. (Nicholas Marriott) - The makefiles no longer contain any awk code, that all has been separated into files under mk/. (Alexander Harm) - We now have "test" *mta*#466, which dumps to standard output or optionally to a file, and honours *mbox-fcc-and-pcc*#453: $ echo text | s-nail -:/ -Smta=test -s ubject user@exam.ple $ correct? [y/n] y Switched to branch 'mybranch' $ git commit -S -n -m 'My release [.]-xy' - $MAKEJOBS vanished, just use -j or whatever your make(1) supports. Luckily the tested make(1)s can be persuaded to dig each others .WAIT / .WAIT: / .NOTPARALLEL: targets.. and do the right thing. - $OBJDIR support added, i use it for building / testing on tmpfs. It works in conjunction with make-emerge.sh, too, thus out-of-tree out-of-tree is possible (more or less; see INSTALL). $ make tangerine OBJDIR=/tmp/x/y/z - Option VAL_PRIVSEP_USER has been renamed to VAL_PS_DOTLOCK_USER (to reflect the new "deep tree" directory layout). - Option OPT_QUOTE_FOLD has been renamed to OPT_FILTER_QUOTE_FOLD (to reflect later code changes upwards compatibly). - New options OPT_CMD_VEXPR and OPT_CMD_CSOP, by default enabled. To include the commands `vexpr'#303 and the new `csop'#166, which now provides the byte string functions of the former. (Still available through `vexpr' until v15.) (I hope to be able to later provide a `usop' or `unisop' or so.) - *v15-compat*#593 can now have a value: if it is set, the `wysh'#130 command modifier which chooses shell quoting rules for some commands is implicit. - We have some (more) backward incompatible changes, though it is likely most users will not recognize the differences. o *headline*#426 format %T is obsolete, %L fits better. o `csop'#166 `hash' and `hash32' subcommands (formerly from `vexpr'#303) use a slightly changed hash algorithm. (Which results in an improved distribution for tested sets of words in power-of-two spaced dictionary.) These are affected by the change in the second next item, too. o Changed to use shell quoting rules for arguments: + `mimetype'#223 and `unmimetype'#224. This is affected by the change in the next item, too. + `shortcut'#273 and `unshortcut'#274. + `mlist'#226 and `unmlist'#227 as well as `mlsubscribe'#228 and `unmlsubscribe'#229. + `alias'#143, too. o Changed (with legacy compat) the "@[i]" modifier prefix to a question-mark ?[case|..] suffix, as is known from URLs. We head towards direction URL syntax, now here too. + `if'#215 and `elif'#186. E.g., 'wysh if "abc" ==?case "ABC"' is true, as well as is 'wysh if 0xFFFFFFFFFFFFFFFF -eq?saturated 36#1Y2P0IJ32E8E7'. "==?" and "-eq?" would have been sufficient, here. (No unsigned mode (yet) for `if'#215.) Yes, `if'#215 and `elif'#186 now support `wysh'#130, and see already expanded arguments, then. No more "triggers". This finally makes it possible to write things like ? wysh if X;A;wysh elif Y;B;else;C;end Note 'else;C' not 'else C'. New operators: '-n "$VAR"' and '-z "$VAR"' work like in the shell, '-N varname' and '-Z varname' do not test the expansion but the existence of variables instead. Two argument forms require `wysh'#130. + `mimetype'#223 markers have changed likewise; this also affects *pipe-TYPE/SUBTYPE*#491 and *pipe-EXTENSION*#498 (with legacy compatibility and -v/-d obsoletion warnings)! ? mimetype ?t text/x-awk awk ? wysh set pipe-application/pdf='?=&?\ trap "rm -f \"${MAILX_FILENAME_TEMPORARY}\"" EXIT;\ trap "trap \"\" INT QUIT TERM; exit 1" INT QUIT TERM;\ mupdf "${MAILX_FILENAME_TEMPORARY}"' + `vexpr'#303 (and `csop'#166) modifiers changed likewise. The case-insensitive subcommands "ifind" and "iregex" have been obsoleted, just use the ?[case] modifier to the regular function instead. P.S.: Thanks to Rich Felker the `regex' subcommands now works as desired even with empty intermediate submatches. o Changed address parse mode for command line arguments plus. This modifies decade old tradition, but results in a more predictable behaviour i think. Most people will possibly even be surprised to see the old behaviour: $ , pp , du ' -> To: du, de , pp new: $ , pp , du ' -> To: "du , de , pp , du " Of course anything but perfect, our address parser is very complicated yet far from being acceptable. (Dr. Werner Fink) This affects: + -b#59, -c#61 and To: receivers, as above. (We also have a new -T#81 receiver multiplexer, which is configurable in this regard, please see below for more.) + The -r#78 address. This saw more changes: the content is no longer evaluated via shell expression parser (when *v15-compat* is set).. unless explicitly requested via the *expandaddr*#409 flag "shquote". + *sender*#530 variable. + `addrcodec'#142 command, likewise; old: ? addrc e du , e , d du , e , d #?1!22/INVAL new: ? addrc e du , e , d "du , e , d" #?0!0/NONE + `digmsg'#174 and `~^'#320 now use this parse mode fix for headers which need a single receiver, which is backward compatible but now safer since it can be fooled less easily (to split into a list what should be a single address, as shown above for `addrcodec'#142). They now can also be forced to use that parse mode for To:, Cc:, Bcc: with a new question mark modifier "?single", here the word "single" is optional. ~^ header insert To?single: exa, ~^ header show to - By established rules and popular demand occurrances of '^From_' (see *mbox-rfc4155*#454) will be MBOXO quoted (prefixed with greater-than sign '>') instead of causing a non-destructive encoding like 'quoted-printable' to be chosen, unless context (e.g., message signing) requires otherwise. Only with *mime-encoding*#463=8bit. - We now support long "Options"#5 -- try --long-help. - Finally, it is possible to force sending out messages with the new *mime-force-sendout*#464 variable. If this MUA has been compiled with iconv(3) support it can happen that sending otherwise valid text messages fails because of invalid bytes sequences according to the locale; setting this new variable will avoid this; use *mime-counter-evidence*#462 to view such messages nonetheless. (Dr. Werner Fink) ChangeLog ^^^^^^^^^ - `mimeview'#225 works again with binary formats. (Russell Bell) - IMAP searches via IMAP without matches no longer report a single match. (Dirk-Wilhelm Peters) - New -Y#87 aka --cmd= option to inject commands to be executed when startup is completed (as opposed to the earlier -X#86 aka --startup-cmd=). These commands appear as if the user had typed them in. - A new *on-history-addition*#483 can be used to filter what enters the `history'#213. - New "fcc" flag for *expandaddr*#409. (Olav Mørkrid) And "domaincheck" will cause target domain comparison against entries in the new *expandaddr-domaincheck*#410. (Olav Mørkrid) - New *mbox-fcc-and-pcc*#453 will write out file and pipe addresses as a plain RFC5322 message rather than an MBOX. (Olav Mørkrid) - The `errors'#190 queue existance and size is announced via *^ERRQUEUE-EXISTS*#354 and *^ERRQUEUE-COUNT*#353. (Russell Bell, Martin Neitzel) - Our MBOX parser is now truly compliant to POSIX. (Dr. Werner Fink) - We follow symbolic links again when writing files. (Russell Bell) - *tls-rand-file*#585 is in fact now necessarily one of the optional *tls-features*#582. (Mike Sharov) - New command line option -T#81 aka --target='FIELD: BODY'. FIELD can be To:, Cc:, Bcc: or Fcc:. The BODY is parsed as a list (just as if the given FIELD would be part of a template message fed in via -t#82), but the "?single" modifier suffix can be used to avoid this. (Dr. Werner Fink) git(1) shortlog: Steffen Nurpmeso (277 + 9) v14.9.11 ("Tit family enjoying a bath"), 2018-08-08 --------------------------------------------------- A hot summer bugfix release, but it surely brings in some new features, like TLS fingerprinting and `digmsg' message access. An embarassing number of bugfixes have been seen, to fix IMAP UID handling on 32-bit hosts, UTF-8, `readall' with empty lines, rare endless iconv(3) loops, false qsort(3)ing of addressee lists, crashes due to false user shell quoting, acceptance of "0" port numbers, and more. Most of these cases now have tests. Credits, in order of commit appearance: Paride Legovini, Andrew Gee, Olav Mørkrid, Kevin McCarthy, Michael Dressel, Jürgen Bruckner, Robert Elz, Rudolf Sykora, Doug McIlroy, Gavin Troy and Jörg Schilling. A special credit to Coverity.com again, it found a bug! (, project number 444.) We welcome Andrew Gee, Kevin McCarthy, Michael Dressel, Olav Mørkrid and Jürgen Bruckner in THANKS. NOTES, ChangeLog (packager-affine) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - I have discovered that GnuPG can --export-secret-subkey so that the real/full private key is no longer needed to --sign, and the excerpts of the private one can have a different password, too. So i have created a new for-signing subkey: EEC8C2FF. Unfortunately it is not possible to verify new signatures with the old public key, an update is necessary. For example via https://ftp.sdaoden.eu/steffen.asc or just update 1883A0DD via normal gpg(1). - Maildir support is now optional but default via OPT_MAILDIR. (Paride Legovini) - I hope for the last time there has been a change to SSL configuration: i have renamed all ssl* variables to tls*. The old ssl* ones still exist until v15, though, yet obsoletion warnings will be produced. This is so because i expect that in a not too distant future only the term TLS will be around. Also the term CipherList was falsely used, it should have been CipherString. - Most (if not all) commands which take a message list and a file target now use shell-style quoting. (Before that say `copy'#165 scanned backwards over "something possibly quoted", took that off, then treated the rest as a message list. Now shell tokens are parsed starting at the front, the last is taken off, and anything before that is the message list.) (Gavin Troy. 2013.) - `~^#'? "header show" now backward-incompatibly shows the address type in field 1, but since this _only_ applies to non-network addresses i made the change. - We will find ncurses on DragonFly BSD. - On Solaris tests no longer need GNU cksum(1): the Solaris cksum is different only for whitespace separators. (Jörg Schilling) - All generated files reside in .obj/, and the tests run in there, too. A "rm -f .obj" should suffice to clean anything up. ChangeLog ^^^^^^^^^ - `~F'#327, `~f'#328, `~M'#333, `~m'#334, `~U'#342 and `~u'#343 now default to the current message (the "dot"). (Andrew Gee) - *indentprefix*#441 handling has had its pitfalls when quoting messages. (Andrew Gee) - -r#78 will again set *from*#423 even after -S#79 has been used to set *from*. (Michael Dressel) - No longer process From: (*from*#423) content via `alternates'#145 when Sender: (*sender*#530) is set. (Michael Dressel) - Because the priority class of headers was not taken into account, it could happen that addressees in Cc: would remain but the same in To: were removed. (Michael Dressel) - IMAP accounts for RFC 4551 (and 7162) and supports 64-bit UIDVALIDITY. - *spamfilter-rate-scanscore*#567 could crash if specification did not match program output. - Shims for TLSv1.3 support, e.g., for *tls-config-pairs*#579. - Obsoleted *dotlock-ignore-error*, added *dotlock-disable*#403. (Paride Legovini) - In compose-mode, removing the In-Reply-To: header breaks an old, and starts a new thread. (Doug McIlroy) - Added new *forward-inject-tail*#422, *quote-inject-head*#513 and *quote-inject-tail*#514 variables, and extended the meaning of *quote*#509. All of *{forward,quote}-inject-{head,tail}* now support a compose-mode specific set of formats (see *quote-inject-head*#513), for now a few only. (This adds meaning onto the content of *forward-inject-head*#421 as introduced in v14.9.0.) The generated output honours *quote-fold*#512, which now takes an optional third argument in order to produce better output. While here, introduce the new command escape `~Q'#336 which performs full *quote*#509 cycles on the given message list. - Fcc: headers are now understood in -t#82 templates or when placed in compose mode (`~v'#344, *editalong*#404 etc.). Since each such header only takes one addressee, no quoting issues apply, the entire header body is the value. - `~|'#319 will pass the entire message including headers when used as "~||", e.g., prepend a file-carbon-copy message header: ~|| echo Fcc: /tmp/test; cat - New `tls'#289 multiplexer command. Yet primitive and only supports a `fingerprint' subcommand. Supports `vput'#129. The new *tls-fingerprint*#583 variable chain aids in adding support for connection verification without an installed CA certificate pool in conjunction with the new *tls-fingerprint-digest*#584 chain. Consequently *smime-sign-message-digest* has been renamed to *smime-sign-digest*#550 (old version will cease in v15). The latter now defaults to SHA512 if possible. - New MLE commands mle-go-screen-bwd and mle-go-screen-fwd to go backward and forward one screenful. And a new mle-clear-screen command. (Todd C. Miller) - New *expandaddr*#409 setting "shquote" will evaluate addresses as if specified within $'' shell-quotes for -b#59, -c#61, and all direct command line receivers. This allows for, e.g., $ s-nail -Sexpandaddr=shquote '\$contact-mail' - *quote-as-attachment*#510 no longer needs to be set before compose mode is entered in order to become honoured. - Even for -H#68 or -L#71 *folder-hook*#415s will now be called. Possible sorting is also applied. - `='#137 now optionally supports message list arguments and the `vput'#129 modifier in order to store the result list. The new `digmsg'#174 multiplexer adds some message access, just like `~^'#320 does in compose mode. In fact the set of commands is shared, yet only in compose mode `digmsg' can change messages or access attachments until v15, however. For example, #?0[steffen@essex nail.git]$ cat > /tmp/z.rc <<'_EOT' define one { if [ "${#}" -gt 0 ] digmsg create $1 - # no `read'/`readall' overlay but stdout #digmsg $1 header list digmsg $1 header show subject digmsg remove $1 shift \eval xcall one "$@" \end } define all { local set all # localize ("localopts yes" would do too) vput = all *; echo all: $all; eval call one $all } _EOT #?0[steffen@essex nail.git]$ MAILRC=/tmp/z.rc \ .obj/s-nail -:u -Snoheader -Squiet -Rf /tmp/z ? call all all: 1 2 3 212 Subject Re: [S-mailx] FYI: after USB stick loss i have rotated keys, plus 212 Subject Re: Problem with page? 212 Subject Re: s-nail Source ... ? x git(1) shortlog: Steffen Nurpmeso (203) v14.9.10 ("(40th Mail anniversary) Blue tit"), 2018-03-25 --------------------------------------------------------- On this day in 1978 Kurt Shoens placed the following comment in def.h (now it is in nail.h): /* * Mail -- a mail program * * Author: Kurt Shoens (UCB) March 25, 1978 */ v14.9.10 is mostly a stability and bugfix release. It has seen a full test series including Coverity.com scans. It fixes bugs i have introduced (also a double free in IMAP cache that i introduced for v14.9.* series to address Coverity CID 1376978..). In the end i am saying thanks to Gunnar Ritter for the IMAP module, and absolutely especially his really neat idea of an IMAP cache including offline work queue. (IMAP will nonetheless temporarily go in v15, but these ideas will come back thereafter.) I have gray hairs now. Credits, in order of commit appearance: William Yodlowsky, Stuart Henderson, Jörg Schilling, Viktor SZÉPE, Rich Felker, Ralph Corderoy and Philipp Gesang. A special credit to Coverity.com again. Because: tcc is 618496 bytes, pcc is 851968+24576 bytes, but gcc is 73355264 bytes and clang is even 147406848 bytes, i wonder why the latter two never said a word that would have addressed the pretty obvious CID 1387053! [Use of initialized value, the author.] We welcome Stuart Henderson and Philipp Gesang in THANKS. NOTES, ChangeLog (packager-affine) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - The balls are now build with umask 0022 not 0027. (William Yodlowsky, Stuart Henderson) - One actual development of this version was the addition of multiple choice VAL_ues, as documented in make.rc. For now we have VAL_IDNA (for OPT_IDNA) VAL_IDNA="idnkit idn2 idn" and VAL_RANDOM (by itself) VAL_RANDOM="arc4 ssl libgetrandom sysgetrandom urandom builtin" (Stuart Henderson) In brief: The value is interpreted as a whitespace separated list of strings, like "idn2 idn idnkit", case is ignored, order is important. The special strings "all" and "any" as well as the empty value are wildcard matches; if any entry in the list is a wildcard match, the rest of the list is ignored. The special string "error" will abort configuration once its list position is reached; this is only supported if documented, and not with an accompanying OPT_ (which then offers "require", as below). Since this VAL_RANDOM approach is so much better i have dropped OPT_SSL_RANDOM and OPT_NOEXTRANDOM that were recently introduced again. They were c..p. - Support for idnkit 2.3 has been added. Support for idnkit 1 (especially as idnkitlite) has been fixed. - For the first time this codebase should be able to handle invalid MBOX mailboxes (produced by, e.g., dma(1)) gracefully. I hope i have found all places (sic) where code has to be fixed. E.g., "? copy * INVALID-MBOX" now works. (Smalltalk already knew objects which know what they are doing are for the better... This is v15, then.) - P.S.: the two FreeBSD test failures are noted in INSTALL. ChangeLog ^^^^^^^^^ - *asksend*#368 will now really allow recomposing. - `help'#212 now supports recursive `commandalias'#162es, and command self-recursion detection now works differently, it has been false for something like commandalias x q; commandalias q echo au since q became expanded to `quit'#247 (alias expansion equals new command word). New behaviour: we allow equals once: commandalias q q; commandalias x q; x -> x -> q -> q -> quit - *editalong*#404 can have a value, say "set editalong=v" and it will startup $VISUAL#626 not $EDITOR#604. - Path separators are now normalized, thus all places, including MLE tab-expansion ("On terminal control and line editor"#17), can expand something like "///t*////t*". - -E#64 flag will not be obsoleted. -D#62 flag has been reintroduced (sets *disconnected*#651 right away, was not reinstantiated with the rest of the IMAP support.) git(1) shortlog: Steffen Nurpmeso (71) v14.9.9 ("Marsh tit savours first spring sun, II") 2018-03-06 ------------------------------------------------------------- A bugfix release. I hope with this the fallout of the Christmas 2016 "address the Dr. Problem workshop" has been fully resolved and thus MIME for header address fields, even if iconv(3) is involved, been fully restored! We have even more tests for this now. The release v14.9.8 was broken on big endian machines. I will remove the v14.9.8 balls from the server by the weekend. Sorry for the inconvenience! Credits, in order of commit appearance: Slavko, Matej Mužila, Rich Felker, Simon McVittie, Paride Legovini, Cág, Predrag Punosevac. We welcome Slavko, Matej Mužila, Rich Felker and Simon McVittie in THANKS. NOTES, ChangeLog (packager-affine) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - The v14.9.* series called *pipe-TYPE/SUBTYPE*#491 handlers in display or quote mode with CR (carriage-return) bytes stripped because of a missing output file comparison check, which broke binary formats etc. (Slavko) - We now have native support for Libidn2. (Matej Mužila) - uname(1) is now hookable by setting the shell variable uname when calling make ("uname=MY-UNAME make config" etc.). (Simon McVittie) We no longer bake the kernel version into the binary, and `version'#302 includes uname(2) output. (Simon McVittie, Paride Legovini) - We now support a fallback P(seudo)R(andomNumber)G(enerator) initialization even if getrandom(2)/getrandom(3) has been found by the configuration, just like we do for "/dev/urandom" usage. This does not affect systems with arc4random(3) or OpenSSL random usage. (David Čepelík, Simon McVittie) A new OPT_SSL_RANDOM make.rc variable, by default initialized to the value of OPT_SSL. ChangeLog ^^^^^^^^^ - `~@'#318 list-edit behaviour in -##89 batch mode was broken. - Character set names will now undergo generic normalization, including stripping of iconv(3) //SUFFIXes. git(1) shortlog: Steffen Nurpmeso (33) v14.9.7 ("Marsh tit patiently scraping bark") 2018-02-16 -------------------------------------------------------- A maintenance release which fixes bugs and brings in features. Credits, in order of commit appearance: Alexander Harm, Viktor SZÉPE, Paul Eggert, Joseph Bisch, Paride Legovini, and Peter J. Holzer. A special credit to the disappearing mutt(1) bug tracker. And to Gmane.org for creating gmane.mail.s-mailx.general! Thanks Paride Legovini for becoming maintainer of the Debian port. We welcome Joseph Bisch, Paride Legovini, and Peter J. Holzer in THANKS. NOTES, ChangeLog (packager-affine) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - The USB stick loss reported for v14.9.6 was fake news, so to say, the stick exists and therefore the old key is not compromised. - We are back at Gmane.org! news.gmane.org/gmane.mail.s-mailx.general - `history'#213 has learned to be context-sensitive a bit, and has two new subcommands, `load' and `save'. This is in parts backward incompatible because it needs a new *history-file*#429 format; however, the old format can be loaded yet compose-mode commands will not appear in compose mode no more. Iirc you can start with an old format then `save' to the new, then replace the "d" in the first column with "c" for compose-mode commands which should appear correctly. - Obsoletion warnings for variables now happen at `set'#269 time instead of when used. Running once via -v#85 may be beneficial. - The saturation modifier of `vexpr'#303 is henceforth a prefix, the suffix version is obsolete (but still supported for a while). - A network address that contains no domain-, but only a valid local user in angle brackets will be automatically expanded to a valid address when *hostname*#434 is set to a non-empty value; setting it to the empty value instructs us that the used *mta*#466 (including builtin SMTP) will perform the necessary expansion. (Viktor SZÉPE) Note that *hostname*#434 as well as *smtp-hostname*#553 will now undergo IDNA expansion if IDNA is supported. And *from*#423 and *sender*#530 are now verified at `set'#269 time, not when used. (Viktor SZÉPE) - The commit message in [d503bd82] is wrong, apologies to Paride Legovini. The test(1) operator "-n" appeared in Seventh Edition UNIX, not V8 as falsely claimed. ChangeLog ^^^^^^^^^ - Our `addrcodec'#142 parser chokes on lesser constructs. - Presence of command-line MTA arguments without *expandargv*#411 are now a hard error. It was my fault that this was not the default from the very start. (Viktor SZÉPE) - Seen on the mutt bug tracker, we also still have had problems with time settings that cross 32-bit boundaries. As that is in parts induced by the C standard, now implement those parts on our own, and be super careful in general. (Joseph Bisch) - The `~@'#318 command escape did not shell-unquote the user input again and was thus a bit broken; message attachments also work again. - Support custom headers from the command line via -C#60. And *customhdr*#397 is verified upon `set'#269 time. - The simple builtin HTML viewer now supports
elements, which many web mailers, most notably gmail, use for citation. (Peter J. Holzer) git(1) shortlog: Paride Legovini (1), Steffen Nurpmeso (66) v14.9.6 ("Marsh tit abiding a snow storm"), 2017-12-05 ------------------------------------------------------ A bugfix release which fixes four serious and three other bugs. A few new features came in, too. Many thanks go to Ralph Corderoy who reported an issue that was caused by a terrible, terrible word reversal that i managed to produce in December 2016, and which caused the v14.9.x series to not MIME encode (non-address) content of address header fields! Credits, in order of commit appearance: Thomas Dickey, Andreas Baumann, Erich Eckner, Gaetan Bisson, Solar Designer, Cág, Ivan Tham, Ralph Corderoy and Doug McIlroy. We welcome Andreas Baumann, Erich Eckner, Solar Designer and Cág in THANKS. NOTES, ChangeLog (packager-affine) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - After USB stick loss the authors OpenPGP key has been switched to pub 4096R/1883A0DD 2017-11-30 [expires: 2027-11-28] Key fingerprint = EE19 E1C1 F2F7 054F 8D39 54D8 3089 64B5 1883 A0DD uid Steffen Nurpmeso - $TMPDIR#624 no longer honoured for root runs. (Solar Designer) - *mime-encoding*#463 defaults to quoted-printable again. (Cág) - We _can_ MIME encode even header fields which contain addresses. Thanks to Ralph Corderoy we now also _do_ so again! ChangeLog ^^^^^^^^^ - ***#336 now uses *ifs*#436 when splitting. - Freezing *ttycharset*#589 via -S#79 also survives using or setting any of $LC_ALL#606, $LC_CTYPE#607 and $LANG#608 during program startup. - New `local'#126 command modifier to localize changes. Yet supported only for `set'#269, i.e., we have gained macro-local variables. - `vexpr'#303 now supports a BASE#number notation for integers, like 16#AFFE as an alternative to 0xAFFE. Hint: variable settings can most often use several bases, too, e.g., i have "set mime-counter-evidence=0b1111". - Very simple form of *quote-chars*#511 to adjust our knowledge of what actually is to be treated as a quote character. - *mime-counter-evidence*#462 deep inspection (bit four) has been improved for the sole cases of quoting or displaying a message. So messages with less than 25% of control characters and such will now be displayed (made printable). This is yet not configurable nor do we have a way to easily access a message with more than that. (Doug McIlroy) git(1) shortlog: Steffen Nurpmeso (44) v14.9.5 ("Marsh tit engaged with a peanut"), 2017-10-21 ------------------------------------------------------- A bugfix release which fixes two bugs which were cast in stone. A few compatibility improvements (AlpineLinux, Solaris). And minor features. Apologies to Jörg Schilling, a git bug i think it was who caused joining of changesets, losing a credit, and it had been pushed to [master] before the problem was realized. Credits, in order of commit appearance: Jörg Schilling, Doug McIlroy, Random832, Nick Stoughton and Ivan Tham. We welcome Nick Stoughton and Ivan Tham in THANKS. NOTES, ChangeLog (packager-affine) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - New OPT_USE_PKGSYS option can be disabled to not automatically pick known package system paths (pkg(7), OpenCSW, schily). (Jörg Schilling) ChangeLog ^^^^^^^^^ - The software indeed _never_ dealt with iconv(3) output character set errors (as opposed to invalid input character set byte sequences etc.) for the main message body! And I have missed that when i tweaked our iconv layer a bit! - Fixed a race condition with sigsuspend(2) that i could only see on OpenBSD. config.h offers n_SIGSUSPEND_NOT_WAITPID, by the way, which saves some systemcalls and did not run races, but noone adjusts this file. - Message list specifications gained two new colon modifiers, one can now "search :Ll" to find "Mailing lists"#10. The *headline*#426 format %T now also uses L and l rather than S and L accordingly. New `addrcodec'#142 subcommand `skinlist' acts like `skin' but stores in *!*#348 *^ERR*#350-EXIST if the address is one of the known "Mailing lists"#10. - `echo'#181 family now supports `vput'#129 and *!*#348 error storage, offering some kind of printf(1) experience, almost. git(1) shortlog: Steffen Nurpmeso (35) v14.9.4 ("(5th anniversary) Marsh tit"), 2017-09-18 --------------------------------------------------- This is an update feature release but which also ships a furious number of bug fixes, about six of which were pretty serious. It also applies overall trimming, and improves configuration time compatibility on macOS. Thanks to Alexander Harm there is now a macOS Homebrew package. Credits, in order of commit appearance: Paul Vojta, Daniel Lublin, Alexander Harm, Norman Ramsey, Viktor Szépe, Rich Salz, David Čepelík, Ralph Corderoy, Stéphane Chazelas, Aharon Robbins, Ken Hornstein. We welcome Daniel Lublin, Alexander Harm, David Čepelík and Stéphane Chazelas in THANKS. NOTES, ChangeLog (packager-affine) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Out-of-tree builds have become possible via the new make-emerge.sh script: $ cd /tmp && mkdir build && cd build && ~/src/nail.git/make-emerge.sh && make tangerine DESTDIR=.ddir We now have a `citron' make target which is like `tangerine' but does not run the tests. - Configuration with OPT_AUTOCC honours $CC=cc. (Norman Ramsey) - SSL/TLS configuration has been revamped (again) in order to support new possibilities of OpenSSL (and LibreSSL) without ending up and introducing more and more variables. Instead we now have *ssl-config-pairs*, a comma-separated list of all options. With e.g. OpenSSL 1.1.xx this will be directly passed through to SSL_CONF_cmd(), so there _anything_ can be passed, otherwise we use a builtin parser to map. The new *ssl-features*#? states what is supported. E.g.: if [ "$ssl-features" =% +ctx-set-maxmin-proto ] wysh set ssl-config-pairs='\ CipherList=TLSv1.2:!aNULL:!eNULL:@STRENGTH,\ Curves=P-521:P-384:P-256,\ MinProtocol=TLSv1.1' else wysh set ssl-config-pairs='\ CipherList=TLSv1.2:!aNULL:!eNULL:@STRENGTH,\ Curves=P-521:P-384:P-256,\ Protocol=-ALL\,+TLSv1.1 \, +TLSv1.2' endif OpenSSL v1.1.xx also introduces an interesting and neat idea to centralize SSL/TLS configuration of (all) programs in a single file. This can be driven via *ssl-config-file* and the new *ssl-config-module* variables, several entries per program are allowed, see *ssl-config-module* for an example. New manual section "Encrypted network communication"#13. - Variables set or unset via -S#79 are now frozen until program startup is complete. ChangeLog ^^^^^^^^^ - Historical behaviour of *askcc*#366 / *askbcc*#367 has been reintroduced. (Norman Ramsey) A new *asksend*#368 variable will show a final header summary and allows reentering compose mode. Set by default. POSIX mirrors *ask* onto *asksub*#370, so dropped" the former. - `~^'#320 no longer normalizes header names to titlecase. - We no longer generate charset=binary MIME parameters. This was introduced on 2013-01-02 and was i think owed to file(1)s -i output as i failed to find any other reference. (Normal Ramsey) - *mime-alternative-favour-rich*#461 now also works for handlers installed via *pipe-TYPE/SUBTYPE*#491. (Viktor Szépe) - v14.9.* series did not generate In-Reply-To: headers! - `alias'#143 now supports high-bit bytes and semicolon. Expect that at some later time the input must be valid according to the locale, though. (Norman Ramsey) - Combinations of *record*#517 could crash because of an unterminated variable function argument list. (Norman Ramsey) - New command `readall'#249 loads an entire file into a variable. *signature*#537 has been obsoleted. - `vexpr'#303 now supports negative arguments for the substring subcommand and adds trim, trim-front and trim-end subcommands. - `!'#133 can be used in send mode. - `~A'#321, `~a'#322, `~I'#331 and `~i'#332 will henceforth expand \t and \n only if *posix*#504 is set. Please use `set'#269 instead (with `wysh'#130, until v15). - New "The mime.types files"#35 type marker: @q ("quiet"). git(1) shortlog (edited): Steffen (Daode) Nurpmeso (90) v14.9.3 ("Crested tit nibbling sunflower seeds"), 2017-08-03 ------------------------------------------------------------ This is a bugfix release but which ships some improvements, too. It silently replaces both of v14.9.1 v14.9.2 from earlier this week, which were broken or not entirely fixed. Credits, in order of commit appearance: Felix Fontein, Paul Vojta, Ralph Corderoy, Christos Zoulas, Gavin Troy, Gaetan Bisson. Thanks, Coverity.com. We welcome Christos Zoulas in THANKS. Apologies to Viktor Szépe for the false spelling of his name in the v14.9.0 announcement. And to Gaetan Bisson for not giving credit for [14fbce97]! NOTES, ChangeLog (packager-affine) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - fakeroot support was blindly taken from Debian and broken. (Gavin Troy, Gaetan Bisson) - Base64 output was broken for cases which involved iconv(3). The data is not lost, you can read it with S-nail v14.9.0 and above, and save it somewhere. I know of no other base64 decoder which reads those things correctly, though. We now have tests. Along this i fixed an iconv(3) error which likely caused stateful decoding (like, e.g., for ISO-2022-JP) to fail because of an unnecessary reset of the iconv(3) state machine. Thanks to Gaetan Bisson for mentioning this issue! ChangeLog ^^^^^^^^^ - In compose mode the MLE allows empty lines again. - We no longer require a writable $HOME#605. Due to false code flow (but but but: with correct comment) a non-writable $HOME entry in /etc/password (i.e., from getpwuid(3)) would lead to a crash. (Felix Fontain; Ralph Corderoy) - Two faulty string operations slipped into the IMAP code, resulting in a crash and a "is-same-host" test that would fail for IMAPS connections like `save'#266 or `copy'#165 because of an implicit IMAP protocol for the target of those operations (thus IMAP != IMAPS). (Paul Vojta) - The MLE tab-expansion will now automatically append a "/" if there is only one possible expansion and that is a directory, saving the user one . (Christos Zoulas) The shell expression parser had a bug regarding understood metacharacters (;|&), which in turn could cause an infinite loop in the MLE tab-expansion for, e.g., "move &9 +", because the "&" would never have been stepped over. - New `~I'#331 command escape is like `~i'#332 but does not append a newline. - `localopts'#218 gained an optional second argument. It is now possible to specify that any macro `call'#151ed will have localopts enabled, and it is possible to fixate the setting so that it cannot be reverted. - *@*#356 should now act completely compatible to the sh(1)ell, thus obsoleting my hysteric warnings in the v14.9.0 announcement. - The `Lreply'#219, `reply'#254, `Reply'#253 series as well as `mail'#221 now manage the error status *!*#348. I.e., there are now errors like *^ERR*#350-DESTADDRREQ, ^ERR-NODATA, ^ERR-PERM and similar. It is not perfect yet, because $DEAD#603 may have been written (with *save*#525) or not, for example. `Lreply' and `reply' have been rewritten rather completely indeed. They join Reply-To: and Mail-Followup-To: dependent on the context (i.e., *reply-to-honour*#523, *followup-to-honour*#419, see "Mailing lists"#10 for the picture), and if they did, use this list as the receivers exclusively. It now honours *recipients-in-cc*#516 even for such addressees. (And now i wonder whether i should have credited Paul Vojta for that.) Also `Lreply' would have crashed for mails with Reply-To: but without *reply-to-honour*#523 set. We now have a test. Note *replyto* is obsoleted in favour of *reply-to*#522. v14.9.0 ("Long-tailed tit"), 2017-07-16 --------------------------------------- This is a major feature release which took about ~22 months (24 less two) of development to complete, and which imposed massive changes under the hood, but also quite a lot of user visible changes, including some **backward incompatibilities**. As usual, "s-nail -d" will show obsoletion warnings. We gain noticeable improvements regarding scriptability and its reliability, but also for interactive use cases, especially notable to users is our completely new M(ailx)L(ine)E(ditor) that supports rather real tabulator expansion and program-mode-context- sensitive key bindings. We now support macros with arguments, which can be `shift'ed, a `return' status can be used, and a `vexpr' multiplexer offers some arithmetic and string operations. `commandalias'es are recursive, further command modifier prefixes, like `ignerr', give a hand that we otherwise could not offer. In compose-mode the new `~^' command escape allows some message and attachment access, and can be used, e.g., to implement things like custom headers, and has been especially designed for scripted access via the new *on-compose-splice* and *on-compose-splice-shell* hooks. S-nail will move (more or less) backward-incompatibly to sh(1)ell compatible argument quoting (documented in "COMMANDS"), and an increasing number of commands do support this already: new ones exclusively, some old ones have either been switched (like `localopts'), others -- noticeably `set' -- can be switched to the new syntax with a `wysh' command modifier prefix. E.g.: ? define __xv { # Be careful to choose sh(1)ell-style on _entire_ line! localopts yes; wysh set verbose; ignerr eval "${@}"; return $? } ? commandalias call echo boo-boo ? commandalias xv call __xv ? xv list ? commandalias xv '\'call __xv ? xv list Calling the latter `xv' for `list' will give more detailed command information, including which kind of argument is used. I have not managed to implement the three features i have started this development cycle for, these are thus subject to further development, just like wysh for message-list argument commands to support, e.g., negation, wysh for `if' and consorts, the -- terminator to finally overcome the ridiculous requirement to quote entire shell commands filenames for commands like `pipe. And and and. Credits, in order of commit appearance: Antonio Radici, Aharon Robbins, Mike Frysinger, Predrag Punosevac, Michael Convey, Hariskar, Rudolf Sykora, Martin Neitzel, Gavin Troy, Salvatore Bonaccorso, Todd C. Miller, Sergey Matveev, Robert Elz, Mantas Mikulėnas, Respiranto, Jens Schleusener, Walter Alejandro Iglesias, Ralph Corderoy, David Levine, Lyndon Nerenberg, Thomas Dickey, Afan, Justin Ellingwood, Ingo Schwarze, Viktor Szépe, Gaetan Bisson, Juan RP, William Yodlowsky, Hilko Bengen, Matthew Dillon, Colin Watson, Donald Mugnai, Stephen Isard, Jürgen Daubert, Sven Neuhaus, trondd, Ismael Bouya, Felipe Gasper, Paul Eggert, Dr. Werner Fink, Ken Hornstein, Noel Chiappa, Random832, Doug McIlroy, Baptiste Daroussin, Riccardo Ductor, Pietro Cerutti, Jörg Schilling, rain1, Xin LI. We welcome Antonio Radici, Mike Frysinger, Predrag Punosevac, Michael Convey, Rudolf Sykora, Todd C. Miller, Robert Elz, Jens Schleusener, Walter Alejandro Iglesias, Thomas Dickey, Afan, Justin Ellingwood, Viktor Szépe, Juan RP, Matthew Dillon, Colin Watson, Donald Mugnai, Sven Neuhaus, Ismael Bouya, Felipe Gasper, Paul Eggert, Dr. Werner Fink, Ken Hornstein, Noel Chiappa, Random832, Doug McIlroy, Baptiste Daroussin, Riccardo Ductor, Pietro Cerutti, Jörg Schilling, rain1, and Xin LI in THANKS. Apologies: Sergey Matveev. Members of the Roff community which await progress. NOTES, ChangeLog (packager-affine) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * This release brings some backward incompatibilities, outlined in the following. Most users will not be affected, and we have added a lot of compatibility cruft, but that will vanish in v15. "$ s-nail -v"! * The configuration and build system has changed. Packagers have received updated package files. o Anything which was WANT_xy before is now OPT_xy, and compiled-in paths and values, like PREFIX or PAGER, have gained a VAL_ prefix (thus VAL_PREFIX and VAL_PAGER). This is _not_ true for non-persistent or environmental values, e.g., DESTDIR, CC, etc., and also not for the overwritable program variables during configuration, e.g., $awk. o SENDMAIL -> VAL_MTA, SENDMAIL_PROGNAME -> VAL_MTA_ARGV0, MAILSPOOL -> VAL_MAIL. And NAIL -> VAL_MAILX, though this is still a lie. o The make system now needs config..build..install or all..install or tangerine (config..build..test..install). Some constants which some experts may want to fine-tune have been moved to config.h. Usual adjustments+doc via make.rc. o The `build' phase can be parallelized by setting the $MAKEJOBS environment variable, e.g., "make MAKEJOBS=-j4 build". Note this variable is not tracked in the configuration. (Gaetan Bisson) o Unless DESTDIR is set an uninstallation script will be installed along with the rest (see INSTALL file for more). o Set the new OPT_CROSS_BUILD to avoid feature runtime tests, only compile- and link-availability will be tested. (Juan RP) o VERBOSE is implemented straight, but must be given at configuration time in order to become honoured. (William Yodlowsky) o ADDCFLAGS / ADDLDFLAGS -> EXTRA_CFLAGS / EXTRA_LDFLAGS. o The LD_LIBRARY_PATH etc. building processes will skip any path which contains the string "fakeroot". (Hilko Bengen) o We honour a set $SOURCE_DATE_EPOCH#622 environment variable to an extend that allows reproducible tests, which is why the repository gained a [test-out] branch with some expected plain text outputs. (reproducible-builds.org; Colin Watson) The new *log-prefix*#448 variable aids in improving the reproducibility of error messages. o These are upward compatible changes. * "make OPENSSL_API_COMPAT=0x10100000 all" should work. * Internal and environment variables are now explicitly _defined_ and _tracked_ after variable handling has been rewritten completely. Notes: o This means that, e.g., "$ password=NOT_SECRET s-nail" will **NOT** work no more, since *password*#489 is an internal variable! o But if you do, e.g., "? set TMPDIR=~/tmp", then this will also be reflected in the program environment (it is an environment variable) and thus affect child processes. o Therefore we no longer have `setenv' and `unsetenv'. o To integrate any other environment variable transparently into our variable management, the new command `environ'#189 needs to be used, e.g., "? environ set NEWVAR=value" or "? environ link EXISTINGVAR". - -H#68 and -L#71 have been decoupled: it used to be -e#65 -L#71 instead! - *NAIL_{HEAD,TAIL}* have been obsoleted in favour of *message-inject-head*#457 and *message-inject-tail*#458. *NAIL_HIST{FILE,SIZE}* have been obsoleted in favour of *history-file*#429 and *history-size*#432. *NAIL_EXTRA_RC* has been obsoleted in favour of *mailx-extra-rc*#451. *batch-exit-on-error* has been obsoleted by *errexit*#407, which works just like the POSIX sh(1)ell "set -e" construct; the `ignerr'#125 command modifier (`-' for command escapes in compose mode, and see below) can be used to ignore command errors even then. (This will remain even if we at some later time will support at least some of the sh(1) constructs which "swallow" failures with set -e.) *bsdannounce* is obsolete, the feature is integrated in *header*#425 as this is much more useful. (This is however also dependent upon the also new but well-known $POSIXLY_CORRECT#620 <> *posix*#504, but that is just how it is; these affect more behaviour, and increasing.) - Colour support has been changed backward in- and upward (from user interface side) compatibly, see the manual section "Coloured display"#18. + New commands: `colour'#160 and `uncolour'#161. You can define context-sensitive, terminal-capability- sensitive settings, e.g.: if terminal && [ "$features" =% +colour ] colour iso view-header ft=bold,fg=magenta,bg=cyan colour 256 view-header ft=bold,fg=208,bg=230 subject,from colour mono view-header ft=bold colour mono view-header ft=bold,ft=reverse subject,from endif + The variable *colour-pager*#393 defines whether colour and font attribute sequences should be generated when viewing something in $PAGER#618. + Set the variable *colour-disable*#392 to turn colour off without affecting established settings. + It is deduced via termcap(5) (see below) whether the terminal supports colors, e.g., "$ s-nail -Stermcap=Co#256". This is also true if we don't have termcap support. + Support for 256-colour terminals. (Gavin Troy) - `source'#281 series support shell pipes if the last character of the "filename" ends with a vertical bar |, e.g., ? source 'gpg -qd ~/.s-nailrc-private.gpg |' - Shell pipes are also supported as targets for `move'#231, `copy'#165 etc., yet unfortunately not with via a sh(1)ell token parser, so that the target still has to be a single argument. ? copy . '| cat; echo huhu' - Support for custom headers via the new `~^'#320 compose-mode command escape and in addition, or alternatively, with the internal variable *customhdr*#397, which also can be covered by `localopts'#218. (Sergey Matveev) + Support of $ORGANIZATION has been dropped. + Command escape `~e'#326 supports _any_ header. + Command escape `~^'#320 supports _any_ header. - New -:#55 command line option can be used to more easily select which startup files should be loaded, e.g., -:/ loads none. (Robert Elz) - `account'#140s and *folder-hook*#415s now have `localopts'#218 enabled by default. - A first simple form of compose-mode hooks has been implemented: *on-compose-enter*#479, *on-compose-leave*#480 and *on-compose-cleanup*#478 can be set to macros which get invoked at appropriate times. For the `resend'#258 series there is *on-resend-enter*#487 and *on-resend-cleanup*#486: this is very likely to change once true message access is possible even in this mode. An even more powerful mechanism is available via the also new *on-compose-splice*#481 and *on-compose-splice-shell*#482 hooks. These are executed in child processes and communicate with the parent via their standard input and output, and therefore can do anything and act as if they were the user. `localopts'#218 are enabled and cannot be disabled (and extend until the message is sent). (Jens Schleusener, Rudolf Sykora) ? set on-compose-splice=ocs ? define ocs { read ver echo Splice protocol version is $ver echo '~^header list' read hl; vput vexpr es substring "${hl}" 0 1 if [ "$es" != 2 ] echoerr 'Failed to read header list, bailing out' echo '~x' elif [ "$hl" @i!% ' cc' ] echo '~^header insert cc Diet is your ' read es; vput vexpr es substr "${es}" 0 1 if [ "$es" != 2 ] echoerr 'Failed to insert Cc:, bailing out'; echo '~x' end end } - "The .netrc file"#37 + gained support for comments. (Walter Alejandro Iglesias, Ralph Corderoy) + `netrc'#234 now has a "load" subcommand. + the new *netrc-pipe*#473 obsoletes OPT_AGENT and *agent-shell-lookup*, and can be used to load an encrypted .netrc file, e.g.: ? set netrc-lookup netrc-pipe='gpg -qd ~/.netrc.gpg' I.e., this is in usual .netrc syntax and thus possibly much nicer than saying "? source 'gpg -qd ~/.credentials.gpg |'". - termcap(5) / terminfo(5) support has been changed backward in- and upward (from user interface side) compatibly, please read "On terminal control and line editor"#17. + OPT_TERMCAP is by default enabled. The new, by default enabled, configuration option OPT_TERMCAP_VIA_TERMINFO can be used to (try to) use terminfo(5) instead. + The variable *termcap*#570 can be used to freely define or override terminal capabilities, and *termcap-disable*#572 will disable interaction with the chosen library, leaving only *termcap* in charge. To use the so-called ca-mode on supporting terminals, effectively turning S-nail into a fullscreen application, *termcap-ca-mode*#571 must be set. + The built-in line editor has been rather completely rewritten to be the Mailx-Line-Editor (OPT_MLE, default yes), and supports wide glyphs (if possible), infinite line lengths (2 GB) and more. Tabulator expansion is no longer an option (but needs fnmatch(3)). + Optionally (OPT_KEY_BINDINGS, default yes) it has become possible to freely define key bindings for the MLE via the new `bind'#149 and `unbind'#150 commands. These key bindings can make use of termcap(5) and/or terminfo(5) names. The MLE will install a set of default bindings (unless there is a set *line-editor-no-defaults*#447), more so with OPT_TERMCAP, i.e., try "? bind*". Sufficient support provided, one can now, e.g., type "p " and then collect the message numbers to type, scrolling forward and backward via key-bindings, without losing the line content, then commit the final line. + OPT_EDITLINE and OPT_READLINE support have been dropped. The new MLE should not miss anything. Does it? Tip: in an UTF-8 locale try "? !touch /tmp/hall{,öchen}" and then autocomplete that: once, then ^Q, and again. - `source'#281 can be used in `call'#151ed macros. What sounds so innocent replaced an entire machinery and got rid of a brilliant idea of Kurt Shoens from the 70s, but which never worked with Nail/Heirloom extensions, namely macros, and in the right order. Accompanying this -X#86 can (dig multiline arguments and can) be used to define macros and run them etc. Should work: $ s-nail -X'define x {' -Xversion -Xx -X'}' -X'call x' $ s-nail -X'source \' -X'"echo version|"' -Xx Macros can be `undefine'#171d from within themselves, and re- `define'#170d. It is still not possible to define macros from within macros, and/or have inner macros, not to talk about local scoping or anything more sophisticated such. - -u#83 / $LOGNAME#611 ($USER) handling has been redefined, and "-u USER" is now exactly the same as "-f %USER", and $LOGNAME (and $USER) is actively set to the active user. (Afan) $LOGNAME#611 is POSIX standardized and henceforth used and preferred over $USER, which came from BSD. (Todd C. Miller) - In the future (at least non-message-list) argument handling will be changed backward-incompatibly to be sh(1)ell compatible (and thus POSIX standardized), see "Shell-style argument quoting"#23. New commands use it already today (`bind'#149, `colour'#160, `headerpick'#209), some others (most importantly, `set'#269) can be forced to do so via the new `wysh'#130 command prefix, as in: ? wysh set message-inject-tail=$'\n--steffen' ? bind base $'\cA,\x61' 'echo control-A and small a' - We now actively manage *umask*#591: 0077 by default, but an empty string will use the setting that is active upon startup. Just like changes to (known) environment variables, this setting will also be inherited by any child process. (Walter Alejandro Iglesias) - Anything SENDMAIL / *sendmail*-ish has been renamed to *mta*#466, *mta-arguments*#468, *mta-no-default-arguments*#469 and *mta-argv0*#471. The reason is that in v15 we won't even have *smtp*: it is just another form of MTA, and thus obsolete by itself. Note that *mta-arguments* is now parsed via the shell-token parser, so the following ends up exactly as desired. ? set mta-arguments='-t -X "/tmp/my log"' For now we support a hack that understands a file:// URL in *mta*, too, but that is also the default if there is no protocol. E.g.: "? set mta=smtp://a:b@xy.z" - The "spamd" *spam-interface*#557 is obsolete. I haven't tested it since my main machine died, it is error prone since it assumes internals of the spamassassin wire protocol, and there never was a speed improvement over "spamc". (However it could react upon the "is-spam" state of a message, which "spamc" doesn't allow.) - The new *inbox*#440 variable will henceforth be looked up when searching for a primary system mailbox (as in "? File %"), followed by the usual $MAIL#612 and compile-time defined local mailspool search. (Stephen Isard, Jürgen Daubert) - The semantic of -a#57 and `~@'#318 have been changed, and both commands now use the same syntax: -a file[=input-charset[#output-charset]] - New "failinvaddr" keyword for *expandaddr*#409. - We finally "can" the so-called (by myself) "Dr. Problem" (a bit): (Dr. Werner Fink) $ ' 2>&1 |\ grep To: s-nail: >>> To: "Dr. D. Iet" This can be done via the new `addrcodec'#142, too, note this supports multiple modes (and the `vput'#129 command modifier): $ echo 'addrcodec e Dr. Diet Curd' | s-nail -#:/ "Dr. Diet Curd" - All commands with the string "codec" in their name use different argument quoting, namely none at all, please read "Raw data arguments for codec commands"#25. This means that `urlcodec'#299 (and `imapcodec'#650) has slightly changed semantics. And, while here: there is a new `shcodec'#271, too. - We gained "Command modifiers"#21: `\'#124 (avoid expansion of `commandalias'#162es), `vput'#129 (store result in variable), `ignerr'#125 (ignore an error of the following command, even if the new *errexit*#407 is set), `wysh'#130 (use shell-style arguments). $ echo 'vput cwd resvar;echo $resvar' | s-nail -#:/ /home/steffen/src/nail.git And the usual sh(1) stuff: `return'#264, `shift'#275, `eval'#191, plus a `xcall'#307 stack-avoidance optimization (to be used in place of a `call'#151 which would be the last called command). And an "expr(1) like thing", yet simple, `vexpr'#303. $ echo 'vexpr + 1 2' | s-nail -#:/ 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000011 03 | 0x3 | 3 $ echo 'vput vexpr resvar + 1 2;echo $resvar' | s-nail -#:/ 3 We actually start walking (*?*#347, *^*#349). ? vput vexpr res regex 'bananarama' 'Bana(.+)' '\$1\$0' ? echo $?/$^ERRNAME :$res: 1/NODATA :: ? vput vexpr res iregex 'bananarama' '(.+)rama' '\$1\$0' ? echo $?/$^ERRNAME :$res: 0/NONE :bananabananarama: The command `vpospar'#304 can be used to manage the stack of positional parameters, i.e., much like "set --". It also offers the possibility to save and restore the stack to and from variables. Etc. Btw., to contact the maintainer (make.rc variables VAL_CONTACT_WEB and VAL_CONTACT_MAIL): ? echo $contact-web; eval mail $contact-mail - `if'#215 no longer performs automatic number conversion, we use the explicit -lt, -gt etc. syntax of the sh(1). Note: `if' will change to be almost identical to sh(1) if(1), so please ensure proper test bracketing, even if it is less convenient. Moreover, the default string comparison mode has changed to case-sensitive, just like in the shell. This is because in the future this crux with trigger characters will vanish and `if' etc. will simply slurp in already expanded shell tokens, it will act like the shell in that respect. We have modifiers, though, yet only "@i" for case-insensitivity, also for regex matches: LC_ALL=C i=`LC_ALL=C.utf8 s-nail -:/ -# -X ' \define cset_test { \if [ "${ttycharset}" @i=% utf ] \echo $LC_ALL \xit 0 \end \if [ "${#}" -gt 0 ] \wysh set LC_ALL=${1} \shift \eval xcall cset_test "${@}" \end \xit 1 } \call cset_test C.UTF-8 POSIX.utf8 POSIX.UTF-8 \ en_EN.utf8 en_EN.UTF-8 en_US.utf8 en_US.UTF-8 '` [ $? -eq 0 ] && UTF8_LOCALE=$i Please note the `eval' in 'eval xcall cset_test "${@}". This is a difference of S-nail/mailx and the sh(1)ell that will remain, as documented in "COMMANDS"#20: whereas the shell implements a language and performs standardized expansions on the line until finally the command is called, S-nail will decide the type of command line parsing dependent on the seen command, and will then perform a single expansion. Therefore "${@}" will expand to multiple arguments if $# is greater 0, but it will expand to the empty string otherwise, which is not furtherly expanded away since it is meaningless like it is in the shell: therefore $# will be 1 (the empty string) not 0. - Using an explicit proto:// prefix should get you the desired thing apart of *newfolders*#474, e.g.: ? File maildir:///tmp/x.mdir ? copy * file:///tmp/x.mbox - New variable *record-files*#518 can be set to extend the meaning of *record*#517. *record-resent*#519 was there already. - New variable *ifs*#436 acts a bit like the sh(1)ell's $IFS for, e.g., the new `read'#248 command. There is a `readctl'#250 command which can be used to manage the active channel used by `read'#248. - The `~' alias for `call'#151 is gone. - `mimetype'#223 only allows specification of a single type per call, on the other hand no need to quote that. - `mimeview'#225 must now be used explicitly to look at any non-text MIME part, for normal display etc. purposes we only support "copiousoutput"#637 MIME handlers. - New *socks-proxy*#556 can be used to proxy all network traffic over a SOCKS5 proxy. (Gaetan Bisson) ChangeLog ^^^^^^^^^ - The manual has seen another major overhaul, all the variables are now documented in a single, sorted list, and many clarifications should have been added. I hope it has become a better read. (Predrag Punosevac, Michael Convey, Hariskar, Rudolf Sykora, Respiranto, Thomas Dickey, Donald Mugnai) - To support RFC 1524 a.k.a. .mailcap files (see below) many "trigger"-characters have been added for *pipe-TYPE/SUBTYPE*#491, which may (rarely) affect existing values. The .mailcap support itself is not yet implemented. - *mime-counter-evidence*#462 gained bit 4 (perform proper in-depth content inspection as necessary; set to 0xE for all bits). (Aharon Robbins) - Maildir paths are now created recursively as necessary. (Justin Ellingwood) - -M#72 and -m#73 options have been added to enforce a special send mode that will flag standard input / the given file with the specified / detected MIME 'Content-Type:'. This can be used to directly send, e.g., HTML log output. (Viktor Szépe, Ralph Corderoy) - Disallow symlinks on writable files. Note this requires O_NOFOLLOW support for the operating-system-call open(2), but which has been standardized a long time ago. (Matthew Dillon) - `retain'#263, `ignore'#216 etc. now differentiate in between From (the From: header) and From_ (the MBOX ident). In fact we now have a new `headerpick'#209 command which is a multiplexer for all retain and ignore lists used, call it without arguments to see the current setting(s). In v15 only `headerpick' and the standard-imposed wrappers `retain' and `ignore' will remain, all other wrappers will vanish. Regular expressions can now be used if available: ? headerpick headerpick type retain blahblahblah cc date from \ mail-followup-to message-id openpgp reply-to subject to \ user-agent #headerpick type ignore currently covers no fields #headerpick save retain currently covers no fields headerpick save ignore '^Original-.*$' '^X-.*$' '^DKIM.*$' headerpick forward retain cc date from list-id \ mail-followup-to openpgp reply-to subject to #headerpick forward ignore currently covers no fields - `top'#291 has been rewritten completely, `Top'#290 is new. It uses a built-in set of retain/ignore headers, but it is possible to register a custom set via `headerpick'#209. Also, *toplines*#587 has been extended a bit and the new *topsqueeze*#588 variable may pimp your `top' experience. ? headerpick top retain add subject ? top [-- Message 1 -- 87 lines, 4791 bytes --]: Subject: Re: I can't dist to myself I wrote: 3.22. bounce_delivered - `features' has been dropped, `version'#302 extended. - The *prompt*#506 handling has changed: we lost the capability to expand \?, \@ and \$, instead new "private" variables *?*#347, *account*#360, *mailbox-resolved*#450 and *mailbox-display*#449 have been introduced, and the prompt is completely shell expanded (thus twice with `wysh' or in v15), as if dollar-single-quote quoted. We do support the reverse- solidus escaped bracket notation for embedding characters which should not be counted when calculating the width of the prompt. The `colour'#160 command has a slot for the prompt colour. We gained *prompt2*#507 as a second level prompt. ? var prompt wysh set \ prompt='?\${?}!\${!}[\${account}#\${mailbox-display}]? ' - The filename "-" can be used as a receiver, e.g., $ echo Hey,\ you | s-nail -:/ -Sexpandaddr -sUB - - The -s#80 command line option, the `~s'#340 command escape as well as the corresponding slots of `~^'#320 will actively strip [\r\n] from their value (Debian #419840). - New `read'#248 and `echoerr'#182 commands, mostly for *on-compose-splice*#481. But also `echon'#183 and `echoerrn'#184, which do not write a trailing newline. - New variable *r-option-implicit*#515 may be helpful to those who regulary need the functionality of the -r#78 command line option. (Felipe Gasper, Martin Neitzel) - By using new "pseudo-URLs" one can automatize the use of S/MIME keys / (certificates / intermediate include certificates) with passwords. E.g., to drive bob@exam.ple, set *smime-sign-cert-bob@exam.ple* to the private key / certificate pair as usual, the password lookup will then be performed for bob@exam.ple.smime-cert-key, bob@exam.ple.smime-cert-cert and bob@exam.ple.smime-include-certs. Like this the password can be stored in an encrypted .netrc file when *netrc-lookup*#472 and *netrc-pipe*#473 are set, or it may be stored in an encrypted resource file that has been loaded via `source'#281 as a simple *password*#489 variable. Note that the prompting that happens as a last resort of password lookup will still interfere with a possibly running $PAGER#618 instance, dependent on the setting of *crt*#396, of course. Proper job control handling and recognizing that we are running $PAGER when doing that prompt is a TODO for v15. Sorry. - Some commands, like `set'#269, `help'#212, `list'#217, `mlist'#226 etc., now react upon the setting of *verbose*#594 and(/or) *debug*#400. - `write'#306 uses iconv(3) as appropriate. - *mbox-rfc4155*#454 has first been dropped, and was then reintroduced with different semantics. Because, it can be helpful if a messed up MBOX is read, in which case we henceforth will warn you and point you to this: ? define mboxfix { \localopts yes; \wysh set mbox-rfc4155;\ \wysh File "${1}"; \eval copy * "${2}" } ? call mboxfix /tmp/bad.mbox /tmp/good.mbox P.S. Here you see how weird the current thing still is, in v15: ? define mboxfix { localopts yes; set mbox-rfc4155; File "${1}"; copy * -- "${2}" } And also in v15 we will not apply (proper) so-called MBOXO quoting, but instead (simply MIME) re-encode mail messages. - `call_if'#152 is new and, different to "? ignerr call", silent and not messing with the return status. - The new *smime-ca-flags*#541 and *ssl-ca-flags*#? can be used to fine-tune X509_STORE_set_flags(3) a.k.a the X509 CA certificate verification. ? set ssl-ca-flags=partial-chain ? wysh set smime-ca-flags="${ssl-ca-flags}" Also, *ssl-curves*#? for TLSv1.3. - Socket connections use TLS S(erver)N(ame)I(ndication) as appropriate (RFC 7817). - `alternates'#145 checks arguments and supports `vput'#129. It by default no longer replaces but appends alternates, unless *posix*#504 mode is active. There is a new `unalternates'#146 command to remove alternates. - A new `charsetalias'#155 command. (Pietro Cerutti, mutt#3925) - New commands `filetype'#196 and `unfiletype'#197: in the future we will no longer know any builtin filetypes, in fact we already simulate .gz etc. via the new mechanism as necessary: ? filetype \ bz2 'bzip2 -dc' 'bzip2 -zc' \ gpg 'gpg -d' 'gpg -e' \ gz 'gzip -dc' 'gzip -c' \ xz 'xz -dc' 'xz -zc' \ zst 'zstd -dc' 'zstd -19 -zc' \ zst.pgp 'gpg -d | zstd -dc' 'zstd -19 -zc | gpg -e' - `~<'#315 now offers a "- [HERE-delimiter]" mode for pasting etc. (Ralph Corderoy) - `exit'#192 and `quit'#247 take an optional exit status. (That is not fixated yet, though.) - We have a useful -h / --help output. (Doug McIlroy) - *encoding* obsoleted in favour of new *mime-encoding*#463, which now defaults to base64. - *allnet*#362 now works (broken since nail 10.00, 2002-09-29). Appendix ^^^^^^^^ The complete changelog of commits in between two versions OLD and NEW can be inspected by using the git(1) `log' command: $ git log --reverse --topo-order --abbrev-commit OLD..NEW # Only topic branch headers (--no-merges for content commits only): $ git log --oneline --reverse --topo-order --merges OLD..NEW # Same, but truly accessible: $ git log --oneline --reverse --topo-order --merges --parents OLD..NEW | while read c1 c2 c3 c4 c5 c6; do printf "%-24s: \$ git log --oneline --no-merges %s ^%s\n" \ "${c6}" "${c1}" "${c2}"; done Entries for releases before v14.9.0 have been cut off and can be found in the git(1) repository: v14.8.0 - v14.8.16: $ git show v14.8.16:NEWS v13 - v14.8.5 : $ git show v14.8.5:NEWS 9.0 - 12.5 : $ git show heirloom:ChangeLog Also accessible via HTTPS?, just replace X.Y.Z accordingly: \https?://git.sdaoden.eu/cgit/s-nail.git/tree/NEWS?h=vX.Y.Y For even older releases you need to look into the [timeline] branch, but no changelog has been administrated for them. # s-tm-mode s-nail-14.9.15/README000066400000000000000000000260151352610246600137470ustar00rootroot00000000000000W e l c o m e t o S - n a i l / S - m a i l x =============================================== S-nail (later S-mailx) provides a simple and friendly environment for sending and receiving mail. It is intended to provide the functionality of the POSIX mailx(1) command, but is MIME capable and optionally offers extensions for line editing, S/MIME, SMTP and POP3, among others. S-nail divides incoming mail into its constituent messages and allows the user to deal with them in any order. It offers many commands and internal variables for manipulating messages and sending mail. It provides the user simple editing capabilities to ease the composition of outgoing messages, and increasingly powerful and reliable non-interactive scripting capabilities. Please refer to the file INSTALL for build and installation remarks, and to NEWS for release update information. The file THANKS mentions people who have helped improving and deserve acknowledgement. This software originates in the codebase of Heirloom mailx, formerly known as nail, which itself is based upon Berkeley Mail that has a history back to 1978, and which has been written to replace Unix mail, a program that already shipped with First Edition Unix from 1971 -- M. Douglas McIlroy writes in his article "A Research UNIX Reader: Annotated Excerpts from the Programmer's Manual, 1971-1986": MAIL (v1 page 21, v7 page 22) Electronic mail was there from the start. Never satisfied with its exact behavior, everybody touched it at one time or another: to assure the safety of simultaneous access, to improve privacy, to survive crashes, to exploit uucp, to screen out foreign freeloaders, or whatever. Not until v7 did the interface change (Thompson). [.] 1. Where? 2. Repository access 3. Repository layout 4. Security record 5. Authors 1. Where? --------- Our latest release can be downloaded at [1], and the fully cross- referenced manual can also be viewed as HTML online[2]. There are browsable git(1) repositories at sdaoden.eu[3] (use [4] for cloning purposes), with mirrors at Sourceforge[5] and repo.or.cz[6]. [1] https?://ftp.sdaoden.eu/s-nail-latest.tar.{gz,xz}{,.asc} [2] https?://www.sdaoden.eu/code.html#s-mailx [3] https?://git.sdaoden.eu/cgit/s-nail.git [4] https?://git.sdaoden.eu/scm/s-nail.git [5] http://sourceforge.net/projects/s-nail [6] http://repo.or.cz/s-mailx.git We have a mailing list[7] with moderated unsubscribed posting possi- bilities; subscriptions can be managed via web interface[8] (it is a GNU Mailman list, so posting to LISTNAME-request@ and the subject "subscribe" will also do). We have a browser-accessible and searchable archive[9], and The Mail Archive is so kind and offers it, with all its services, too [10]! For example, i have subscribed the RSS feed that The Mail Archive produces to Gwene.org[11]. And Gmane.org was so kind and took us, we are here[12]. Thanks to all of you! Commits to the [master], [release/*] and [stable/*] branches are posted to [13], and announcements will also be posted to [14], both are receive-only mailing-lists. [7] s-mailx@lists.sdaoden.eu [8] https://lists.sdaoden.eu/mailman/listinfo.cgi/s-mailx [9] https://lists.sdaoden.eu/pipermail/s-mailx/ [10] https://www.mail-archive.com/s-mailx@lists.sdaoden.eu/ [11] news.gwene.org/gwene.mail.s-mailx [12] news.gmane.org/gmane.mail.s-mailx.general [13] https://lists.sdaoden.eu/mailman/listinfo.cgi/s-mailx-commit [14] https://lists.sdaoden.eu/mailman/listinfo.cgi/s-announce Our heraldic animal snailmail.jpg has been found at [+1]. Thank you! [+1] http://cdn.whatculture.com/wp-content/uploads/2009/06/snailmail.jpg 2. Repository access -------------------- To create a full clone of the repository, with all the data and history: $ git clone https://git.sdaoden.eu/scm/s-nail.git With a newer git(1), and only tracking the latest stable branch: $ git clone --single-branch --branch=stable/latest \ https://git.sdaoden.eu/scm/s-nail.git Or, being selective, also with older git(1)s: $ mkdir s-nail.git $ cd s-nail.git $ git init $ git remote add origin -t 'release/*' -t stable/stable -t master \ https://git.sdaoden.eu/scm/s-nail.git $ git fetch -v And then, assuming the last had been done: $ # Show all releases $ git log --no-walk --decorate --oneline --branches='release/*' -- $ # Check out the latest release, and verify the signature $ git checkout release/latest $ git log --oneline --show-signature --max-count=1 HEAD $ make all && sudo make install 3. Repository layout -------------------- - [release/*] A new branch within release/ is created for every release, e.g., [release/v14.8.10]. History will not be rewritten. These branches consist of one commit, and that commit is signed with an OpenPGP key and used for the signed release tag, vMAJOR.MINOR.UPDATE.ar (.ar for "archive"). The commit as such covers the data modifications that make up a release, i.e., release date fixation, manual preprocessing, removal of data which does not make sense in release tarballs, etc. All this is not true for older releases, the new repository layout was introduced after v14.8.10. But it used [timeline] as a source for most references, therefore the signed tag v14.8.7.ar protects all elder references within [release/]: $ git describe --contains heads/release/v1.3.0 v14.8.7.ar~113 - [release/latest] and [release/stable] "Symbolic links" to the latest and stable, respectively, release branches. - [stable/*] A new branch within stable/ will be created for each new minor version, e.g., [stable/v14.8]. History will not be rewritten. These are the de-facto [master] branches for their respective minor release, which extend for the full lifetime of the said, e.g., the branch [stable/v14.7] has been created once the v14.7.0 release was made, and it extends until the release of v14.7.11, the last v14.7 update release made. Once the time for a new release has come, the head of such a stable branch will gain a signed commit and a signed stable tag, vMAJOR.MINOR.UPDATE, and then be used as the source for a new branch in release/. Packagers who want to include all the bugfixes when they eventually iterate their package can create local "packager releases" with the "grappa" mode of the script mk/make-release.sh. With it, they can track the stable/ branch of desire, and have a [myrelease] branch where the local releases are made. In order to get the very same file modifications that are made in the official [release/] they need an installed perl(1). For example: $ git fetch $ git checkout stable/stable $ sh mk/make-release.sh grappa myrel # myrel created as necessary Preparing a release on commit [.] Grappa to be brought from stable/stable to myrel Program version is [.], packager release addition shall be: 2 Is s-nail correct? [y/n] y Switched to branch 'myrel' .. $ git commit -S -n -m 'My release [.]-2' - [stable/latest] and [stable/stable] "Symbolic links" to the latest and stable, respectively, stable branches. These are possibly what users should track which want to have the newest non-release bugfixes and stable, backward-compatible commits. See below for examples how to accomplish that. - [master] Rooted on top of [heirloom]. It gains only stable, but possibly backward-incompatible changes (usually mentioned on the ML), and will be used to create new entries in stable/. It may gain signed commits for sealing purposes from time to time. History will not be rewritten. - [next] Rooted on top of [master], this consists of a furious mixture of commits that eventually end up in [master]. Daring users may give this branch a try, but bugs and temporary nonstarters have to be dealt with, then. - [crawl] Developer chaos (distributed horror backup - do not use!). - [test-out] This branch contains the test output files. The test itself only tests checksums, the full output is for development reference purposes only. - [unix-mail,bsd-Mail,timeline] Sketchy efforts to collect the complete history of Unix mail and its successor, BSD Mail. Anything from the pre-nail era has been taken from CSRG and TUHS, for nail and Heirloom mailx i have used release balls. The [timeline] branch was the original effort, and it will be continuously extended whenever new releases will be made, but its history will not be rewritten, which is why it is a sketchy effort. The [unix-mail] and [bsd-Mail] branches have been added later, and will hopefully offer the most complete picture possible as time goes by (not taking into account the "nupas" effort of Research Unix, though) -- this means their history may change, but all commits are signed with an OpenPGP key. - [heirloom] A full git(1) cvsimport of the Heirloom mailx(1) cvs(1) repository. 4. Security record ------------------ - CVE-2004-2771, and CVE-2014-7844. http://seclists.org/oss-sec/2014/q4/1066. Fixed in: v14.7.9 (on day of announcement on oss-sec) Note: Affected all BSD Mail-based codebases. - CVE-2017-5899. Credits: wapiflapi. Fixed in: v14.8.16 (on day of disclosure) P.S.: helper program renamed to -dotlock. Desc.: > vulnerability in the setuid root helper binary > The problem is that an O_EXCL file is created with a user controlled > path because the di.di_hostname and di.di_randstr are never checked. > This means that using s-nail-privsep a normal user can create a file > anywhere on the filesystem, which is a security problem. 5. Authors ---------- Unix mail seems to have been written mostly by Ken Thompson. Berkeley Mail was (according to def.h) developed by Kurt Shoens, dated March 25, 1978. According to the CSRG commit log authors of BSD mail in the time span 1980-10-08 to 1995-05-01 were, in order of appearance (commit count): Kurt Shoens (379), Kirk McKusick (50), Carl Smith (16), Bill Bush (2), Eric Allman (6), Craig Leres (43), Sam Leffler (51), Ralph Campbell (21), Serge Granik (28), Edward Wang (253), Donn Seeley (1), Jay Lepreau (3), Jim Bloom (1), Anne Hughes (2), Kevin Dunlap (34), Keith Bostic (253), Mike Karels (1), Cael Staelin (6) and Dave Borman (17). One commit by Charlie Root, 36 by "dist". Official BSD Mail development ceased in 1995 according to the CSRG (Berkeley's Computer Systems Research Group) repository. Mail has then seen further development in open source BSD variants, noticeably by Christos Zoulas in NetBSD. Gunnar Ritter reused that codebase when he started developing nail in February 2000, and incorporated numerous patches from OpenBSD, NetBSD, RedHat and Debian. He added MIME code, network protocol support, and POSIX conformance improvements. In March 2006, he integrated that program into the Heirloom project, renaming it to Heirloom mailx, the development of which ceased in 2008. In 2012 Steffen (Daode) Nurpmeso adopted the codebase as S-nail. We try to end up as S-mailx. # s-ts-mode s-nail-14.9.15/THANKS000066400000000000000000000164501352610246600140040ustar00rootroot00000000000000S-nail is maintained and developed by Steffen Nurpmeso. This software originates in the codebase of Heirloom mailx, formerly known as nail, which itself is based upon Berkeley Mail that has a history back to 1978, and which has been written to replace Unix mail, a program that already shipped with First Edition Unix. Other people contributed by reporting problems, suggesting various improvements, whether directly or indirectly, or submitting actual code. Here is a list of those people. I hope the list is complete and free of errors. Afan snailmail at ottenheimer dot com Andreas Baumann mail at andreasbaumann dot cc Russell Bell russellbell at gmail dot com Hilko Bengen bengen at debian dot org Joseph Bisch joseph dot bisch at gmail dot com Gaetan Bisson bisson at archlinux dot org Karol Błażewicz karol dot blazewicz at gmail dot com Salvatore Bonaccorso carnil at debian dot org Ismael Bouya ismael at bouya dot org Martin Brandenburg martin at martinbrandenburg dot com Peter Bray pdb_ml at yahoo dot com dot au Jürgen Bruckner juergen at bruckner dot tk Cág ca6c at bitmessage dot ch Claudio Cappelli claudio dot cappelli dot linux at gmail dot com David Čepelík d at dcepelik dot cz Pietro Cerutti gahr at gahr dot ch Jan Chaloupka jchaloup at redhat dot com Stéphane Chazelas stephane dot chazelas at gmail dot com Noel Chiappa jnc at mercury dot lcs dot mit dot edu Michael Convey smconvey at gmail dot com Ralph Corderoy ralph at inputplus dot co dot uk Jérémie Courrèges-Anglas jca at wxcvbn dot org Baptiste Daroussin bapt at FreeBSD dot org Jürgen Daubert jue at jue dot li Solar Designer solar at openwall dot com Thomas Dickey dickey at his dot com Matthew Dillon dillon at backplane dot com John Dodson johnd at physiol dot usyd dot edu dot au Michael Dressel dressel at mail dot desy dot de Riccardo Ductor r dot ductor at gmail dot com Erich Eckner erich dot eckner at gmx dot de Paul Eggert eggert at cs dot ucla dot edu Justin Ellingwood JustinEllingwood at gmail dot com Robert Elz kre at munnari dot OZ dot AU Rich Felker dalias at libc dot org Dr. Werner Fink werner at suse dot de Felix Fontein felix at fontein dot de Mike Frysinger vapier at gentoo dot org Ezequiel Garzón garzon dot lucero at gmail dot com Felipe Gasper felipe at felipegasper dot com Andrew Gee ahg at eng dot cam dot ac dot uk Philipp Gesang phg+lists at phi-gamma dot net Philip Guenther guenther at openbsd dot org Thomas Haigh UW-Milwaukee and Universität Siegen Alexander Harm contact at aharm dot de Stuart Henderson stu at spacehopper dot org Peter Hofmann snailusers at uninformativ dot de Frantisek Holop minusf at obiit dot org Peter J. Holzer hjp-luga2 at hjp dot at Ken Hornstein kenh at pobox dot com Walter Alejandro Iglesias eloi at roquesor dot com Doug McIlroy doug at cs dot dartmouth dot edu Stephen Isard 3s9xh9m02 at sneakemail dot com Steve Izma sizma at golden dot net Joan aseques at gmail dot com Josef Jurek josef dot jurek at gmail dot com Ryan Kavanagh rak at debian dot org Tarqi Kazan tarqi at cfs dot or dot gs Ralph Keller 20 dot keller at use dot startmail dot com Matthias Kilian kili at outback dot escape dot de Vincent Lefevre vincent at vinc17 dot net Paride Legovini pl at ninthfloor dot org David Levine levinedl at acm dot org Xin LI delphij at FreeBSD dot org Johannes Löthberg johannes at kyriasis dot com Daniel Lublin daniel at lublin dot se Martin Lucina martin at lucina dot net Anders Magnusson ragge at ludd dot ltu dot se Wiesław Magusiak wiemag at poczta dot onet dot pl Nicholas Marriott nicholas dot marriott at gmail dot com Sergey Matveev stargrave at stargrave dot org Kevin McCarthy kevin at 8t8 dot us Dominic Meskys dominicmeskys at gmail dot com Dagobert Michelsen dam at opencsw dot org Mantas Mikulėnas grawity at gmail dot com Todd C. Miller Todd dot Miller at courtesan dot com Olav Mørkrid omega at bogus dot net Donald Mugnai donaldmugnai at gmail dot com Matej Mužila mmuzila at redhat dot com Martin Neitzel neitzel at escape dot de Lyndon Nerenberg lyndon at orthanc dot ca Marius Nestor www.softpedia.com Sven Neuhaus sven-snail at sven dot de Sunil Nimmagadda sunil at sunilnimmagadda dot com Björn Persson bjorn at xn--rombobjrn-67a dot se Steven Penny svnpenn at gmail dot com Dirk-Wilhelm Peters peters at schwertfisch dot de Dr. Matthias St. Pierre Matthias dot St dot Pierre at ncp-e dot com Jean-Marc Pigeon jmp at safe dot ca Predrag Punosevac punosevac72 at gmail dot com Allan McRae allan at archlinux dot org Antonio Radici antonio at debian dot org rain1 rain1 at openmailbox dot org Chet Ramey chet dot ramey at case dot edu Gianluca Ramunno ramunno dot gianluca at gmail dot com Random832 random832 at fastmail dot com Aharon Robbins arnold at skeeve dot com Kurt Roeckx kurt at roeckx dot be Juan RP xtraeme at voidlinux dot eu Rich Salz rsalz at akamai dot com Jörg Schilling Joerg dot Schilling at fokus dot fraunhofer dot de Jens Schleusener Jens dot Schleusener at t-online dot de Georg Schlisio g dot schlisio at dukun dot de Johannes Schöpfer johannes@schoepfer.info Martin Sebor msebor at redhat dot com Mike Sharov msharov at users dot sourceforge dot net Slavko linux at slavino dot sk Nick Stoughton nick at usenix dot org Andy Switala andy dot switala at gmail dot com Rudolf Sykora rudolf dot sykora at gmail dot com Viktor Szépe viktor at szepe dot net Martin T m4rtntns at gmail dot com Bob Tennent rdt at cs dot queensu dot ca Ivan Tham pickfire at riseup dot net Tim trondd at kagu-tsuchi dot com Jilles Tjoelker jilles at FreeBSD dot org Warren Toomey wkt at tuhs dot org Gavin Troy gavtroy at fastmail dot fm Paul Vojta vojta at math dot berkeley dot edu Ivan Vučica ivan at vucica dot net wapiflapi wapiflapi at yahoo dot fr Simon McVittie smcv at debian dot org Colin Watson cjwatson at debian dot org William Yodlowsky william at OpenBSD dot org Ypnose ypnx at mailoo dot org Christos Zoulas christos at zoulas dot com # s-tm-mode s-nail-14.9.15/TODO000066400000000000000000000743231352610246600135640ustar00rootroot00000000000000TODO reminder. Rename S-nail to S-mailx in v15.0, change things i've messed with a single, massively backward incompatible change. In general the code is in a pretty bad shape due to the signal handling. I should have sat back in 2012/13 and consider what i am doing. My fault. If i would, we would have a blocked signal mask anywhere in this software except in a few cases where it is necessary and/or possible to deal with signals, and possibly we would not even have to consider to switch the entire codebase to (the much superior, and the only sane approach) SysV signal handling, without SA_RESTART. But a few things are already pretty good, except for normal iterations and a review once we have a better signal handling, and can be taken with us. - We should have generic ENOMEM conditions, now that we have $!. I.e., test overflow (e.g., nam-a-grp.c, whether an alias _can_ be created / extended), like n_ENOMEM_CHECK(INTTYPE, SIZE1, SIZE2, NULL or message), which returns m_bool (now bool_t). Callers need to be aware of NULL returns and pass through errors, then. - We need a "void" box that can be jumped to, i.e., a state in which no box at all is active. -- When a MBOX mailbox is removed while it is opened then changing the folder is not possible. This is an inherent problem of the Berkeley Mail codebase, and we need to have a fully functional intermediate VOID box mechanism plus an object-based mailbox implementation to overcome it. -- Also, when the folder was modified concurrently we should bail, or, in an interactive session, prompt the user what to do. - IDNA decoding. Needs a complete design change. (Unless wants to brute force decode anything before display, of course.) - If pipes fail for part viewers then at least the usual PART X.Y should be shown, maybe even including some error message. I had 'set pipe-text/html="lynx -dump -force_html /dev/stdin"' but NetBSD does not have lynx(1), and i thought i've found a S-nail(1) bug. - Line editing should gain possibility of context sensitive tab completion. - Maybe there should be an additional ZOMBIE directive that is served in equal spirit to DEAD, but that could be a valid MBOX... ? What i want is a *real* resend, best if possible from command line. Meaning, also the possibility to postpone a message. In general. - Having a newsreader would be a really cool thing. (RFC 977 and 2980) - printhead()/hprf(): support %n newline format (%t tab?). Make it possible to use the *datefield* algorithm for plain From_ derived dates (needs a From_ parser, i.e., strptime()-alike). Once we have that, rename *datefield-markout-older* to *date-markout-older* ?? Note that NetBSD's mail(1) has some other nice things. Note also that our code is quite unflexible. - headerpick: add resend-retain/ignore! (Ralph Corderoy, Norman Shapiro) (Delivered-To thread on nmh. Will be hard to do because of codepaths!) - -r should be the Sender:, which should automatically propagate to From: if possible and/or necessary. It should be possible to suppress -r stuff from From: and Sender:, but fallback to special -r arg as appropriate. Low-Level --------- - Improve name extraction rules. And field parsing. There are structured and unstructured fields. There are quoted pairs and comments etc. Rewrite the entire parsing mechanism to comply to RFC 5322, and try to merge all those many subparsers around in the codebase, and accordingly. So much duplicated work ... Name parsing improved a bit for v13 and v14.9, but it's still broken. yankword(), *extract(), etc.: RFC 5322 says that comments in address fields SHOULD NOT be used (mutt(1) maps them to full name-addr forms if approbiate, even if that actually changes content!!?), and that full name-addr SHOULD be used. - After I/O layer rework we should optionally be able to read RSS (Atom?) feeds -- Expat should be available almost everywhere and should be able to parse that? Atom is harder because it may support html+. I mean, yeah, it's stupid, but we could fill in header fields with dummies and still use S-nail to look into the separated feeds as if they were mail messages; anyway i would like to save me from using too many tools -- three seems reasonable. - `sync'hronize commando -- robin@stjerndorff.org (Robin Stjerndorff): Wondering how to update back to my Maildir, moving new read mails in ~/Maildir from new to cur, without exiting the application. Automation available? [And simply re-`[Ff]i' involves a lot of unnecessary work] -- Provide sync'ing options -- Jacob Gelbman : If I open two instances of mailx, I then delete a message and then quit in one. Then in the other one I read a message and quit, mailx saves the status of the read message and the fact that a message was deleted, even though it was opened before the other instance deleted it. How is it doing that? [Of course he was using Maildir] - Add TODO notes for those RFCs: RFC 977 -> 3977 - Network News Transfer Protocol RFC 1036 - Standard for USENET Messages RFC 1524 - True support for mailcap files? TODO YES! We really need to replace the pipe-TYPE/SUBTYPE mechanism with something real. When we can work on the parsed MAIL DOM. RFC 1939 - Post Office Protocol v3 RFC 2017 - URL External-Body Access-Type RFC 2183 - The Content-Disposition Header RFC 2369 - The Use of URLs as Meta-Syntax for Core Mail List Commands and their Transport through Message Header Fields (RFC 6068 - The 'mailto' URL scheme) RFC 2384,1738 - I.e., Much better URL support RFC 2387 - multipart/related -- yet handled like /alternative RFC 2392 - Content-ID and Message-ID Uniform Resource Locators RFC 2405 - The format of MIME message bodies. RFC 2406 - Common multimedia types. RFC 2407 - Encoding of non-ASCII text in message headers. RFC 2449 - POP3 Extensions (including SASL) RFC 2595 - TLS for POP3 (among others) RFC 2980 - Common NNTP Extensions RFC 3156 - MIME Security with OpenPGP RFC 3207 - SMTP over TLS RFC 3461, 3464 - Simple Mail Transfer Protocol (SMTP) Service Extension for Delivery Status Notifications (DSNs), An Extensible Message Format for Delivery Status Notifications RFC 3676 - Updates to the text/plain MIME type and extensions for flowed text (format=flowed). (Martin Neitzel) rfc4315.txt Internet Message Access Protocol (IMAP) - UIDPLUS extension RFC 4422, 4505 - Simple Authentication and Security layer (SASL) (Tarqi Kazan) RFC 4551 IMAP Extension for Conditional STORE RFC 4880 - OpenPGP Message Format RFC 4954 - SMTP Authentication rfc4959.txt IMAP Extension for Simple Authentication and Security Layer (SASL) Initial Client Response rfc4978.txt The IMAP COMPRESS Extension rfc5161.txt The IMAP ENABLE Extension rfc5198.txt Unicode Format for Network Interchange RFC 5246 - Transport Layer Security (TLS) RFC 5321 - Simple Mail Transfer Protocol. RFC 5322 - The basic format of email messages. RFC 5598 - Internet Mail Architecture RFC 5751 - Secure/Multipurpose Internet Mail Extensions (S/MIME) TODO NOTE that our S/MIME support is extremely weak regarding TODO understanding, we should not rely on OpenSSL but instead TODO handle it ourselfs; the RFC says: S/MIME is used to secure MIME entities. A MIME entity can be a sub- part, sub-parts of a message, or the whole message with all its sub- parts. A MIME entity that is the whole message includes only the MIME message headers and MIME body, and does not include the RFC-822 header. Note that S/MIME can also be used to secure MIME entities used in applications other than Internet mail. If protection of the RFC-822 header is required, the use of the message/rfc822 media type is explained later in this section. RFC 6125 - Representation and Verification of Domain-Based Application Service Identity within Internet Public Key Infrastructure Using X.509 (PKIX) Certificates in the Context of Transport Layer Security (TLS) RFC 6152 - SMTP Service Extension for 8-bit MIME Transport RFC 6409 - Message Submission for Mail rfc6530.txt Overview and Framework for Internationalized Email rfc6531.txt SMTP Extension for Internationalized Email rfc6532.txt Internationalized Email Headers rfc6854.txt Update to Internet Message Format to Allow Group Syntax in the "From:" and "Sender:" Header Fields rfc6855.txt IMAP Support for UTF-8 rfc6856.txt Post Office Protocol Version 3 (POP3) Support for UTF-8 rfc6857.txt Post-Delivery Message Downgrading for Internationalized Email Messages rfc6858.txt Simplified POP and IMAP Downgrading for Internationalized Email RFC 7162 IMAP CONDSTORE & QRESYNC RFC 8058 Signaling One-Click Functionality for List Email Headers RFC 8460 on SMTP TLS Reporting RFC 8461 on SMTP MTA Strict Transport Security (MTA-STS) RFC 8474 IMAP Extension for Object Identifiers RFC 8484 on DNS Queries over HTTPS (DoH) RFC 8550 Secure/Multipurpose Internet Mail Extensions (S/MIME) Version 4.0 Certificate Handling RFC 8551 Secure/Multipurpose Internet Mail Extensions (S/MIME) Version 4.0 Message Specification RFC 8601 Message Header Field for Indicating Message Authentication Status RFC 8616 Email Authentication for Internationalized Mail RFC 8621 The JSON Meta Application Protocol (JMAP) for Mail draft-ietf-uta-email-tls-certs-01.txt SMTP security via opportunistic DANE TLS draft-ietf-dane-smtp-with-dane-15 draft-melnikov-smime-header-signing Considerations for protecting Email header with S/MIME Read https://tools.ietf.org/html/draft-ietf-uta-tls-bcp-07. Can we implement OCSP (see RFC 6066; -> RFC 6960)???? - This is how the codebase has to be reworked in respect to signals and jumping: 1. We introduce some environment/carrier structs: struct eval_ctx, struct cmd_ctx, (struct send_ctx). All of these form lists. eval_ctx gets a new instance every time evaluate() is entered; for the interactive mode, commands() instantiates an outermost eval_ctx that "cannot be left". cmd_ctx knows about the eval_ctx in which it is was created; it is created for each command that has an entry in cmd_tab and is passed as the new argument of these kind of functions. (send_ctx is the carrier for the MIME and send layer rewrite.) 2. cmd_tab handling becomes more intelligent: it should be able to perform argument checks of subcommands, e.g., should learn about subcommands, and their very own argument types / number / etc. The cmd_ctx needs to carry around the cmd_tab structure so that the commands can themselves print the synopsis in case of errors -- alternatively we need a return bit which should cause the command callee to emit the synopsis as an error message, whatever is best. Some (hopefully) duplicate occurrences of such strings can vanish. 2.1 We have some commands which offer "show" subcommands. Those should only work in interactive mode OR SO. This could be done with a flag, too. (Driven from the lexer.) X. Offer a central "`[un]onevent' EVENT MACRO [conditions]" register. Change all hooks to use that one, optimize the case where a single macro is registered for a single event but with different preconditions. E.g., "on_interactive_mode_enter" could then be hooked to call `bind' and set `colour's, for example. In conjunction with 2. above those commands could simply be (silent, successful) no-ops before we reach that state (and again after on_interactive_mode_leave is processed). 8. The line buffer used in evaluate() that is passed through to commands (thus: in cmd_ctx, then) needs to become `const'. (I tried to do so in the past, but some commands write into it, thus i stopped and iirc even added some changes on my own which take favour of reusing that buffer.) + Macro execution then no longer needs to clone the macro content lines before executing then. + The command context also takes preparsed command array if so specified in cmd_tab, and entries are cleaned up (see 2.) 9. We should unite all the un*() commands with their non-un* versions, if they have one. They may take a different argument list etc., but only one entry in the command table for such. - The POSIX standard command abbreviations must remain, so maybe outsource those in a hashtable or whatever that is checked first, but detach command table order from that anyway. - Offer a(n optional, and on/off switchable) Damerau-Levenshtein mode for command completion; 10. We MUST switch the entire codebase to use SysV signal handling, don't do the BSDish SA_RESTART, which is why we still suffer the way we do and need jumps. I can't dig BSD signal handling, and never ever did so myself until i got here. 20. The attachment charset selection loop can then be rewritten to check whether an ^C occurred and treat that as end-of-loop condition. In v14.6.3 this was introduced, but it should act differently depending on whether the interrupt occurred during character set selection or attachment filename input. Also in respect whether the interrupt is "propagated" or not. It's ugly, and documented accordingly. 30. It should be considered to drop many variables in favour of URL ?SEARCH usage for keys, or key=value pairs, e.g. smtp://exam.ple?starttls=yes;smtp-hostname=; etc. USER@HOST is neat, but that is possibly preferable? Question: what is with smtp-hostname and such?? 31. Flag updates of individual messages must find their way through to the protocol. 32. Use deque (on partial views). 34. We need a new abstraction: `vie[ws]'. I.e, viewset, viewclear, view(show|look)? We will have (possibly readonly) boxes, a summary cache file, which is created when a mailbox is read in, and all that crap that we currently have (setptr(), setmsize(), etc.!) must vanish. Instead there is another, in-memory abstraction, the view. Some views are built-in and are somehow selectable (the "all" view, for example, and the "new" view). It is possible to make a view persistent by giving it a name, e.g., 'viewset NAME MSG-SPEC' -- 'viewset allnew :n' (and 'viewset XY `' or something must be capable to tag the last a.k.a current). Switching to a named view would thus look over the entire current view (!) for all messages that comply to the message-spec of the view, then create a sorted/threaded display of that subset and create a new anonymous "result" view. It must be possible to specify that a view is to be applied to the entire mailbox instead of the current view, via a simple easy understandable syntax. Or name it "msgset". We won't extend macros that much because it would require much too much logic for no purpose, instead we'll (hopefully) add some scriptable abstraction, with an optional built-in Lua binding. 50. Support SASL, unite all GSS-API etc. under an abstraction! Maybe even drop direct GSS-API and support only through SASL. That is, we can very well provide our own little SASL-client abstraction with what we have already by simply defining some "readline" abstraction plus struct ccred for use by the authentication layer: the protocols must set it up by passing in a line of authentication mechanisms and a callback mechanism. Possibly the user should be able to permit or forbid automatic selection of GSS-API (to avoid useless round-trips) etc. etc. 80. The MIME rewrite: mime_parser <-> mime "DOM" analyzer <-> selectively create filter chains per part and do XY. This also affects sending, and it will allow us to dig MIME (multipart) mail for -t/-m _correctly_. Also in sofar as we can hook a content-decoder before diving into the MIME structure, and with a DOM, we can re-encode such things properly as we (re)send such mails. All this is wrong at the time of this writing! We still need to special treat things like, e.g., RFC 2046, 5.2.1. But on top of we-can, as opposed to the opposite. (Brezn Stangl, brezn DOT stangl AT yandex DOT com; Martin T) 99. Now i'm dreaming some more: with the new object-based approach multiple mailboxes could be in an open state. And it should be possible to do so for the user (`file' and `folder' are required to quit the current mailbox [first -- this not yet]), which is why we either need new trigger characters or new commands. The absolute sensation would be joinable operations over multiple open mailboxes, e.g., views over multiple such! 100. If i say `p 3 2 1' then i mean `3 2 1' not `1 2 3'. 200. Split program: when entering interactive mode, the main machine should fork and the UI should run in the forked one, taking the terminal (have done setsid, TIOCSTTY, tcsetpgrp, dance). - Communication via sendmsg()/recvmsg(), it was in BSD as soon as 1982 says CSRG (date and time created 82/12/04 16:22:24 by mckusick); ok, a bit different by then, but on 1990-04-04 at latest in todays form (Mike Karels: [.]define cmsghdr structure for ancillary data, with new format; move access rights into ancillary data; add MSG_WAITALL). - Maybe furtherly diversify: network (with loop), main machine (with loop), credential helper, i do not know. Provide security sandboxing if possible, i.e., capsicum, pledge/unveil, prctl/seccomp. - The thread sort doesn't get [A is deleted] B answers A C answers B D answers B E is unrelated F answers A The current sort fails to recognize that F and the thread starting at B are related, which results in a mess. Tests: 41.bad-thread, 58.bad-thread .. -- Being able to sort the outermost level of threads was a suggestion of Rudolf Sykora, especially being able to sort the outermost level according to the date of the newest message in a thread. - Drop **use-starttls* in favour of something better: support 'auto', 'no' and 'yes' and act accordingly. For the former be smart enough on the protocol side. (RFC 3207 describes man-in-the-middle attacks due to 'auto' TLS, so explicit 'yes' should be favoured). - NOTE: we do not really support IPv6 sofar in that we are not prepared to deal with IPv6 addresses (as in '[ADDR]:PORT'). Pimp url_parse(). And socket I/O. - I had a connection collapse during a POP3 download, and neither was there a chance to get access to the 22 yet downloaded mails (after five minutes of waiting followed by CNTRL-C), nor did the layer recognize this very well (got myriads of `POP3 connection already closed.' messages, btw., the thirty-something messages which were not yet downloaded caused (after CNTRL-C) this: ETC. ETC. - I got an email in base64 that obviously used CRNL line endings, and once i've replied the CR where quoted as *control* characters. Get rid of those (kwcrtest.mbox; may be hard to do everywhere for some time, due to how we deal with I/O and Send layer etc). - edit.c doesn't do NEED_BODY (but IMAP won't work anyway). - Stuff .. s-nail FILE' is not interactive, even though it accepts terminal input.) . Just like the RFC 3676 link above, it would be nice if it would be somehow possible to recognize links in a document; i don't know yet how this could be achieved without losing formatting information (i mean, we could enable this and inject terminal colour sequences, but one should be able to say 'follow link x', starting an action handler, and the 'x' must come from somwhere - simply injecting '[NUMBER]' references distorts visual). Anyway, it's just a filter that recognized the usual stuff, and of course we can simply have a buffer which records all such occurrences, so that user can say '? xy NUMBER', but without the context it soon gets hard. . TTY layer: the tc*() family may fail with EINTR, which MUST be handled; setting also generates SIGTTOU when we're not in foreground pgrp, so we better deal with all that and ENSURE WE GET THROUGH when resetting terminal attributes! .. TTY "I guess it would be much better to create our own session via setpgid(2) and then tcsetpgrp(3) any processes we run synchronously, and properly deal with SIGTTOU, but it always has been like that and i won't do that before other things have been changed. . Remove all occurrences of mbtowc() with mbrtowc(); temporarily add (some) global mbstate_t objects until the send / MIME layer rewrite is done and has the carrier. Use flip states and add aux funs with only update the state+toggle on success -- CURRENTLY MBTOWC FAILURES ARE PRACTICALLY NOT HANDLED!! P.S.: the standards do not allow that well at all. Since we work so much with *ttycharset* we would need a setlocale_from_charset(), but which does not exist (except implicitly for UTF-8 locales). But we need char classification! This task up to S-CText. . which_protocol(), *newmail* mechanism, displayname, mailname: all of this SHIT must vanish and be replaced by a URL, and a nice "VFS" mailbox object that carries all necessary state so that one can work with it. If not mentioned somewhere else: struct message should be splitted into a tree of objects, with a base class that has as few fields as possible; the global *message should be a deque, only accessible via iterator; it should store pointers to (the actually used subtype of) message structures instead; i.e., for maildir boxes the path is yet allocated separately, then it could be part of the message object, etc. It should track the number of contained parts, so that the "fits-onto-the-screen" tests are more useful than today. . Given how many temporary files we use, it would make sense to support a reusable single temporary file, as in singletmp_take() and singletmp_release(), where singletmp_release() would close and thus drop the file if it excesses a specific (configurable) size, and the mainloop tick would close it (after X (configurable) unused ticks)) otherwise. I guess this would improve performance for searching etc. etc. . _(), N_(), V_(): use GNU tools for extraction etc., and write a simple helper program which converts these files to a serialized hashmap, just like we did for the okeys (and *exactly* so); add a config check whether the ({}) extension is supported and finally use that for some ({static char const *tr_res;}) injection optimization, then. (Think SFSYS) . Searching body/text yet includes headers from attachments and attachment data. This is shit. :) . The "nifty" unregister_file()->_compress() mechanism that even shovels '-Sfolder=imaps://user1@localhost -Srecord="+Sent Items"' *records* calls clearerr() on the descriptor before performing it's action anyway. when we really make it even to the I/O rewrite, it should be possible to dis-/allow such -- it doesn't make sense to add something faulty to whatever was not faulty before! . `dp' prints EOF at the end of a thread even if unread messages follow . `resend' doesn't smime-sign. . RFC 5751 describes a message multipart layout that also includes the headers in the signature; it would be nice (for completeness sake) to be able to support that. Note shutup@ietf.org. . The capability to save a message under the name of a recipient is in the standard etc., but i've never used it. What would be cool, otoh, would be if there would be the possibility to register a regular expression, and if just *any* recipient of a message matches, store the message in the given folder instead. I.e., if i send a message to s-nail-users@ then i most likely want to get a copy to the corresponding box, regardless of whoever the message was sent To: Cc: or Bcc: else.. . mutt list handling (`~') is very powerful . We have some use of *at() functions, especially anything which temporarily switches cwd. . *newmail* is terrible. At some later time we need to do somethings with timeouts etc. (for MBOX and Maildir it's not that bad, but for anything over the network, yet the mentioned may come in over NFS). Remove it until we have something better? . The RFC 8098 *disposition-notification-send* mechanism is yet not truly conforming (and works with *from*). Also, this is only the sender side, there should be support for creating the MDN response. (Maybe ternary option: off (default), create-when-unread-flag-goes-away, ditto-but-also-strip-header) .. Also, there is DSN as a SMTP extension, see the RFCs 3461, 346 (as above) and 6522 (Wikipedia). . The var_* series should return "const char*" not "char*". This should already work today because otherwise we would get SEGV all through the way. .. While here: rename enum okeys to enum internal_variables, and the ok_*() series to iv_(). And see below for env_*() series. . fexpand() the 2nd: it should return structure because we need to check for FEDIT_SYSBOX, which currently only checks whether the first character of a file name is '%', not whether it is '%', '%:FILEPATH' or '%VALIDUSER', because that is impossible to do! . On the long run in-memory password storage should be zeroed after use, possibly even encoded *during* use. After v15. . We need a `spamcheck' command that is like `spamrate' but updates the mail in-place, i.e., with the headers that the spam engine adds. . __narrow_suffix() is wrong (for stateful encodings that we don't support yet) and should inject a reset sequence if it shortens the string. . When a user edits a specific header, it should no longer be modified. (Do not loose knowledge that collect() edited it.) . The new internal ~/$ expansion mechanism should get support for POSIX parameter expansions ${[:]-} and ${[:]+} (and ${[:]?}). There is no real way to get the functionality otherwise... . Make S/MIME an option separate of SSL/TLS, i.e., optional. . With very long input Heirloom mailx(1) / S-nail(1) can produce encoded-words (RFC 2047) with incomplete multibyte sequences (i.e., non self-contained encoded-words). . Group addresses, especially the undisclosed recipients but also "Bla": addresses; are missing. . Per-folder (S/MIME) en- and decryption key (Tarqi Kazan): if a xy variable is set (that points to a key) add a transparent en- and decryption layer on top of any per-message operation (for boxes for which the variable is set). . For v15.0: remember private thread with Tarqi Kazan (2015-05) and try to improve situation with *record*, so that only messages enter it which have really been sent. If we support postponing and have a multi-process layout and add an intermediate *record-queue* we may be able to improve the situation. . [Dd]ecrypt should transport decryption errors, not silently be like copy and copy undecrypted content, because this is what it's for? ..We need atomic operations with rollback support in order to make this happen, but i think maybe file truncation (decryption always appends?) is enough provided that files are locked? WE NEED ATOMIC OPERATION SUPPORT for quite some operations. Man, are we far from that. . `pipe' is total shit regarding MIME. We need some defined and documented method to configure which parts are displayed and/or how they are visually separated. .. In PARTICULAR we MUST NOT use stdout for log/status in batch mode: ssh X "echo 'move * |cat' | s-nail -#f %" >> download should do the right thing, but can't like that due to unwanted noise in the stdout output! Best would be if it would be possible to explicitly define a file/dev to be used, but falling back to stderr in batch mode otherwise. (think S-Web42) . Exit status handling is sick. . *mime-allow-text-controls* is a no-brainer: instead we should introduce something that allows us to switch and detect UTF-16 once we run into the problematic situation, then start all over in an Unicode mode? I.e.: continue to force the user to set such a switch, but do it in a sensible fashion, because the UTF-16 data stream may nonetheless contain control characters?? -- . smime_verify(): only dump the multipart that is signed into the file for verification purposes. DOCUMENT that only the FIRST such part is verified. Ditto, we don't decrypt but on toplevel. Sic. . convert iconv so that it always "places the reset sequence" i.e. finalizes the string properly. we don't do this at all right now! . -:, *mimetypes-load-control*, ?, should honour the given load order; as appropriate, add a "b" for built-in! It happened to me that i searched for at least 30 minutes for a bug that resulted in text/plain not text/x-diff only to find out that this was because of ArchLinux's /etc/mime.types! . getapproval() should support a TRUM1 return, meaning "cancel", to be understood as appropriate. . `mbox' _can_ be made usable anywhere with yet another PS_MBOX global bypass! ditto touch,save,Save . We should be much smarter regarding when we allow a PAGER etc. to be used, which is supposed to be a possibly useful thing in $ s-nail -Scrt=0 >LOG 2>&1 . when doing Lreply we may ask for Reply-To:, but strip out the address actively even if user said yes to the question. That should not happen? It somehow matches the documentation however. unsure. . if -t is used and the file includes Mail-Followup-To:, then we should NOT add to it, OR we need to offer a way to get there! . `mimetype': more type markers: i want to be able to send application/mbox as text if it is 7bit clean; ditto application/x-sh. Ditto xml etc. And: if highbits, try conversion, but fall back to base64 instead of failing to send the message. ?ui=t,wire=7bit,8bit-or-base64 Something like that. # s-ts-mode s-nail-14.9.15/include/000077500000000000000000000000001352610246600145065ustar00rootroot00000000000000s-nail-14.9.15/include/mx/000077500000000000000000000000001352610246600151325ustar00rootroot00000000000000s-nail-14.9.15/include/mx/child.h000066400000000000000000000116561352610246600163770ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Child process handling, direct (pipe streams are in file-streams.h). * * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef mx_CHILD_H #define mx_CHILD_H #include #define mx_HEADER #include /* */ #define mx_CHILD_MAXARGS 3 /* xxx vector */ enum mx_child_flags{ mx_CHILD_NONE = 0, /* Ensure the forked child has started running (as opposed to the default * success which only remarks "fork(2) has returned successfully") */ mx_CHILD_SPAWN_CONTROL = 1u<<0, /* On top of SPAWN_CONTROL, wait until the child called execve */ mx_CHILD_SPAWN_CONTROL_LINGER = 1u<<1, /* run(): handle all the lifetime, and wait etc., return final status */ mx_CHILD_RUN_WAIT_LIFE = 1u<<2, /* Child uses terminal and requires termios handling */ mx__CHILD_JOBCTL = 1u<<8 }; /* mx_child_ctx_setup() to zero out */ struct mx_child_ctx{ /* Output: */ s32 cc_pid; /* Filled upon successful start (when not waited..) */ s32 cc_exit_status; /* WEXITSTATUS(); -WEXITSTATUS() if !WIFEXITED */ /* err_no() on failure; with SPAWN_CONTROL it could be set to errors that * happen in the child, too; set to ERR_CHILD when there was error and there * was no error */ s32 cc_error; u32 cc_flags; /* child_flags */ /* Input: the arguments are all optional */ /* Signals must be handled by the caller. If this is set, then it is * a sigset_t*: the signals to ignore in the new process. * SIGINT is enabled unless it is in the mask. */ void *cc_mask; /* In fact a sigset_t XXX su_sigset! */ s32 cc_fds[2]; /* A real FD, or _FD_PASS or _FD_NULL XXX sz[2] */ char const *cc_cmd; /* The command to run, */ char const *cc_args[mx_CHILD_MAXARGS]; /* and its optional arguments */ /* NIL or NIL terminated array; Note: is modified if set! */ char const **cc_env_addon; /* TODO su_dict */ sz cc__cpipe[2]; }; /* Slots in .cc_fds */ #define mx_CHILD_FD_IN 0 #define mx_CHILD_FD_OUT 1 /* Special file descriptors for .cc_fds */ #define mx_CHILD_FD_PASS (-1) #define mx_CHILD_FD_NULL (-2) /* At program startup: initialize controller (panic on failure) */ EXPORT void mx_child_controller_setup(void); /* Initialize (zero out etc.). The .cc_fds are set to CHILD_FD_PASS */ EXPORT void mx_child_ctx_setup(struct mx_child_ctx *ccp); /* Start and run a command, with optional arguments and splicing of stdin and * stdout, as defined by the ctx_setup()d ccp, return whether the process has * been started successfully. * With RUN_WAIT_LIFE the return value also indicates whether the parent has * child_wait()ed successfully, i.e., whether the child has terminated * already; .cc_exit_status etc. can be examined for more. * Otherwise _signal(), _forget() and _wait() can be used on ccp */ EXPORT boole mx_child_run(struct mx_child_ctx *ccp); /* Fork a child process, "enable" the below functions upon success. * With SPAWN_CONTROL the parent will linger until the child has called * in_child_setup() or even (with SPAWN_CONTROL_LINGER) until it execve's */ EXPORT boole mx_child_fork(struct mx_child_ctx *ccp); /* Setup an image in the child; signals are still blocked before that! */ EXPORT void mx_child_in_child_setup(struct mx_child_ctx *ccp); /* This can only be used if SPAWN_CONTROL_LINGER had been used. * It will pass err up to the parent, and close the control pipe. * It does not exit the program */ EXPORT void mx_child_in_child_exec_failed(struct mx_child_ctx *ccp, s32 err); /* Send a signal to a managed process, return 0 on success, a negative value * if the process does no(t) (longer) exist, or an error constant */ EXPORT s32 mx_child_signal(struct mx_child_ctx *ccp, s32 sig); /* Loose any knowledge we might have regarding ccp. * Neither waiting nor any other status report will be available. * This must not be used in conjunction with FD_PASS. */ EXPORT void mx_child_forget(struct mx_child_ctx *ccp); /* Wait on the child and return whether ccp was a known child and has been * waited for successfully; examine ccp for error / status */ EXPORT boole mx_child_wait(struct mx_child_ctx *ccp); #include #endif /* mx_CHILD_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/cmd-charsetalias.h000066400000000000000000000026301352610246600205100ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ `charsetalias'. * * Copyright (c) 2017 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef mx_CHARSETALIAS_H #define mx_CHARSETALIAS_H #include #define mx_HEADER #include /* `(un)?charsetalias' */ EXPORT int c_charsetalias(void *vp); EXPORT int c_uncharsetalias(void *vp); /* Try to expand a charset, return mapping or itself. * If is_normalized is true iconv_normalize_name() will not be called on cp */ EXPORT char const *mx_charsetalias_expand(char const *cp, boole is_normalized); #include #endif /* mx_CHARSETALIAS_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/cmd-commandalias.h000066400000000000000000000026731352610246600205040ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ `commandalias'. * * Copyright (c) 2017 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef mx_COMMANDALIAS_H #define mx_COMMANDALIAS_H #include #define mx_HEADER #include /* `(un)?commandalias' */ EXPORT int c_commandalias(void *vp); EXPORT int c_uncommandalias(void *vp); /* Whether a `commandalias' name exists, returning name or NIL, pointing * expansion_or_nil to expansion if set: both point into internal storage */ EXPORT char const *mx_commandalias_exists(char const *name, char const **expansion_or_nil); #include #endif /* mx_COMMANDALIAS_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/cmd-csop.h000066400000000000000000000022341352610246600170110ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ `csop'. * * Copyright (c) 2017 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef mx_CSOP_H #define mx_CSOP_H #include #ifdef mx_HAVE_CMD_CSOP #define mx_HEADER #include /* `csop' */ EXPORT int c_csop(void *vp); #include #endif /* mx_HAVE_CMD_CSOP */ #endif /* mx_CSOP_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/cmd-filetype.h000066400000000000000000000037151352610246600176730ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ `filetype'. * * Copyright (c) 2017 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef mx_FILETYPE_H #define mx_FILETYPE_H #include #define mx_HEADER #include struct mx_filetype{ char const *ft_ext_dat; /* Extension this handles, without first period */ uz ft_ext_len; char const *ft_load_dat; /* And the load and save command strings */ uz ft_load_len; char const *ft_save_dat; uz ft_save_len; }; /* `(un)?filetype' */ EXPORT int c_filetype(void *vp); EXPORT int c_unfiletype(void *vp); /* Whether the non-existing file has a handable "equivalent", to be checked by * iterating over all established extensions and trying the resulting * concatenated filename; a set res_or_nil will be filled on success (data must * be copied out) */ EXPORT boole mx_filetype_trial(struct mx_filetype *res_or_nil, char const *file); /* Whether (the extension of) file is known; a set res_or_nil will be filled * on success (data must be copied out) */ EXPORT boole mx_filetype_exists(struct mx_filetype *res_or_nil, char const *file); #include #endif /* mx_FILETYPE_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/cmd-mlist.h000066400000000000000000000034751352610246600172050ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ `mlist', `mlsubscribe'. * * Copyright (c) 2014 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef mx_MLIST_H #define mx_MLIST_H #include #define mx_HEADER #include enum mx_mlist_type{ mx_MLIST_OTHER = 0, /* Normal address */ mx_MLIST_KNOWN = 1, /* A known `mlist' */ mx_MLIST_SUBSCRIBED = -1 /* A `mlsubscribe'd list */ }; /* `(un)?ml(ist|subscribe)' */ EXPORT int c_mlist(void *vp); EXPORT int c_unmlist(void *vp); EXPORT int c_mlsubscribe(void *vp); EXPORT int c_unmlsubscribe(void *vp); /* Whether a name is a (wanted) list; * give MLIST_OTHER to the latter to search for any, in which case all * receivers are searched until EOL or _SUBSCRIBED is seen. * XXX the latter possibly belongs to message or header */ EXPORT enum mx_mlist_type mx_mlist_query(char const *name, boole subscribed_only); EXPORT enum mx_mlist_type mx_mlist_query_mp(struct message *mp, enum mx_mlist_type what); #include #endif /* mx_MLIST_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/cmd-shortcut.h000066400000000000000000000024311352610246600177170ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ `shortcut'. * * Copyright (c) 2017 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef mx_SHORTCUT_H #define mx_SHORTCUT_H #include #define mx_HEADER #include /* `(un)?shortcut' */ EXPORT int c_shortcut(void *vp); EXPORT int c_unshortcut(void *vp); /* Check if str is a shortcut, return expansion or NIL */ EXPORT char const *mx_shortcut_expand(char const *str); #include #endif /* mx_SHORTCUT_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/cmd-vexpr.h000066400000000000000000000022441352610246600172120ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ `vexpr'. * * Copyright (c) 2017 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef mx_VEXPR_H #define mx_VEXPR_H #include #ifdef mx_HAVE_CMD_VEXPR #define mx_HEADER #include /* `vexpr' */ EXPORT int c_vexpr(void *vp); #include #endif /* mx_HAVE_CMD_VEXPR */ #endif /* mx_VEXPR_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/colour.h000066400000000000000000000114071352610246600166110ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ `(un)?colour' commands, and anything working with it. * * Copyright (c) 2014 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef mx_COLOUR_H #define mx_COLOUR_H #include #ifdef mx_HAVE_COLOUR #define mx_HEADER #include #define mx_COLOUR(X) X /* We do have several contexts of colour IDs; since only one of them can be * active at any given time let's share the value range */ enum mx_colour_ctx{ mx_COLOUR_CTX_SUM, mx_COLOUR_CTX_VIEW, mx_COLOUR_CTX_MLE, mx__COLOUR_CTX_MAX1 }; enum mx_colour_id{ /* Header summary */ mx_COLOUR_ID_SUM_DOTMARK = 0, mx_COLOUR_ID_SUM_HEADER, mx_COLOUR_ID_SUM_THREAD, /* Message display */ mx_COLOUR_ID_VIEW_FROM_ = 0, mx_COLOUR_ID_VIEW_HEADER, mx_COLOUR_ID_VIEW_MSGINFO, mx_COLOUR_ID_VIEW_PARTINFO, /* Mailx-Line-Editor */ mx_COLOUR_ID_MLE_POSITION = 0, mx_COLOUR_ID_MLE_PROMPT, mx_COLOUR_ID_MLE_ERROR, mx__COLOUR_IDS = mx_COLOUR_ID_VIEW_PARTINFO + 1 }; /* Colour preconditions, let's call them tags, cannot be an enum because for * message display they are the actual header name of the current header. * Thus let's use constants of pseudo pointers */ #define mx_COLOUR_TAG_SUM_DOT ((char*)-2) #define mx_COLOUR_TAG_SUM_OLDER ((char*)-3) enum mx_colour_get_flags{ mx_COLOUR_GET_FORCED = 1u<<0, /* Act even if COLOUR_IS_ACTIVE() is false */ mx_COLOUR_PAGER_USED = 1u<<1 /* Assume output goes to pager */ }; struct mx_colour_env{ struct mx_colour_env *ce_last; boole ce_enabled; /* Colour enabled on this level */ u8 ce_ctx; /* enum mx_colour_ctx */ u8 ce_ispipe; /* .ce_outfp known to be a pipe */ u8 ce__pad[5]; FILE *ce_outfp; struct a_colour_map *ce_current; /* Active colour or NIL */ }; struct mx_colour_pen; /* `(un)?colour' */ EXPORT int c_colour(void *v); EXPORT int c_uncolour(void *v); /* An execution context is teared down, and it finds to have a colour stack. * Signals are blocked */ EXPORT void mx_colour_stack_del(struct n_go_data_ctx *gdcp); /* We want coloured output (in this autorec memory cycle), pager_used is used * to test whether *colour-pager* is to be inspected, if fp is given, the reset * sequence will be written as necessary by _stack_del() * env_gut() will reset() as necessary if fp is not NIL */ EXPORT void mx_colour_env_create(enum mx_colour_ctx cctx, FILE *fp, boole pager_used); EXPORT void mx_colour_env_gut(void); /* Putting anything (for pens: including NIL) resets current state first */ EXPORT void mx_colour_put(enum mx_colour_id cid, char const *ctag); EXPORT void mx_colour_reset(void); /* Of course temporary only and may return NIL. Does not affect state! */ EXPORT struct str const *mx_colour_reset_to_str(void); /* A pen is bound to its environment and automatically reclaimed; it may be * NULL but that can be used anyway for simplicity. * This includes pen_to_str() -- which doesn't affect state! */ EXPORT struct mx_colour_pen *mx_colour_pen_create(enum mx_colour_id cid, char const *ctag); EXPORT void mx_colour_pen_put(struct mx_colour_pen *self); /* Get an escape sequence (or NIL, if self is, or no colour there is) */ EXPORT struct str const *mx_colour_pen_to_str(struct mx_colour_pen *self); /* * NEW STYLE TODO */ /* Get terminal reset control sequence (or NIL if no colour there is). * The return value is "volatile" to colour change commands */ EXPORT struct str const *mx_colour_get_reset_cseq(u32 get_flags); /* Just get the pen for the given combination, or NIL. * The return value is "volatile" to colour change commands */ EXPORT struct mx_colour_pen *mx_colour_get_pen(u32 get_flags, enum mx_colour_ctx cctx, enum mx_colour_id cid, char const *ctag); /* Get terminal control sequence (or NIL, if self is, or no colour there is) */ EXPORT struct str const *mx_colour_pen_get_cseq( struct mx_colour_pen const *self); #include #else # define mx_COLOUR(X) #endif /* mx_HAVE_COLOUR */ #endif /* mx_COLOUR_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/cred-auth.h000066400000000000000000000043421352610246600171620ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Credential and authentication (method) lookup. * * Copyright (c) 2014 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef mx_CRED_AUTH_H #define mx_CRED_AUTH_H #include #ifdef mx_HAVE_NET #include #define mx_HEADER #include enum mx_cred_authtype{ mx_CRED_AUTHTYPE_NONE = 1u<<0, mx_CRED_AUTHTYPE_PLAIN = 1u<<1, /* POP3: APOP is covered by this */ mx_CRED_AUTHTYPE_LOGIN = 1u<<2, mx_CRED_AUTHTYPE_OAUTHBEARER = 1u<<3, mx_CRED_AUTHTYPE_EXTERNAL = 1u<<4, mx_CRED_AUTHTYPE_EXTERNANON = 1u<<5, mx_CRED_AUTHTYPE_CRAM_MD5 = 1u<<6, mx_CRED_AUTHTYPE_GSSAPI = 1u<<7 }; struct mx_cred_ctx{ u32 cc_cproto; /* Used enum cproto */ u16 cc_authtype; /* Desired enum mx_cred_authtype */ boole cc_needs_tls; /* .cc_authtype requires TLS transport */ u8 cc__pad[1]; char const *cc_auth; /* Authentication type as string */ struct str cc_user; /* User (url_xdec()oded) or NIL */ struct str cc_pass; /* Password (url_xdec()oded) or NIL */ }; /* Zero ccp and lookup credentials for communicating with urlp. * Return whether credentials are available and valid (for chosen auth) */ EXPORT boole mx_cred_auth_lookup(struct mx_cred_ctx *ccp, struct mx_url *urlp); EXPORT boole mx_cred_auth_lookup_old(struct mx_cred_ctx *ccp, enum cproto cproto, char const *addr); #include #endif /* mx_HAVE_NET */ #endif /* mx_CRED_AUTH_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/cred-md5.h000066400000000000000000000075001352610246600167050ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ MD5 message digest related. * * Copyright (c) 2014 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ /* MD5.H - header file for MD5C.C from RFC 1321 is */ /* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All rights reserved. License to copy and use this software is granted provided that it is identified as the "RSA Data Security, Inc. MD5 Message-Digest Algorithm" in all material mentioning or referencing this software or this function. License is also granted to make and use derivative works provided that such works are identified as "derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm" in all material mentioning or referencing the derived work. RSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided "as is" without express or implied warranty of any kind. These notices must be retained in any copies of any part of this documentation and/or software. */ #ifndef mx_CRED_MD5_H #define mx_CRED_MD5_H #include #ifdef mx_HAVE_MD5 #ifdef mx_HAVE_XTLS_MD5 # include #endif #define mx_HEADER #include /* */ #define mx_MD5_DIGEST_SIZE 16u /* */ #define mx_MD5_TOHEX_SIZE 32u /* MD5 (RFC 1321) related facilities */ #ifdef mx_HAVE_XTLS_MD5 # define mx_md5_t MD5_CTX # define mx_md5_init MD5_Init # define mx_md5_update MD5_Update # define mx_md5_final MD5_Final #else /* RFC 1321, MD5.H: */ /* * This version of MD5 has been changed such that any unsigned type with * at least 32 bits is acceptable. This is important e.g. for Cray vector * machines which provide only 64-bit integers. */ typedef unsigned long mx_md5_type; typedef struct{ mx_md5_type state[4]; /* state (ABCD) */ mx_md5_type count[2]; /* number of bits, modulo 2^64 (lsb first) */ unsigned char buffer[64]; /* input buffer */ } mx_md5_t; EXPORT void mx_md5_init(mx_md5_t *); EXPORT void mx_md5_update(mx_md5_t *, unsigned char *, unsigned int); EXPORT void mx_md5_final(unsigned char[mx_MD5_DIGEST_SIZE], mx_md5_t *); #endif /* mx_HAVE_XTLS_MD5 */ /* Store the MD5 checksum as a hexadecimal string in *hex*, *not* terminated, * using lowercase ASCII letters as defined in RFC 2195 */ EXPORT char *mx_md5_tohex(char hex[mx_MD5_TOHEX_SIZE], void const *vp); /* CRAM-MD5 encode the *user* / *pass* / *b64* combo; NULL on overflow error */ EXPORT char *mx_md5_cram_string(struct str const *user, struct str const *pass, char const *b64); /* RFC 2104: HMAC: Keyed-Hashing for Message Authentication. * unsigned char *text: pointer to data stream * int text_len : length of data stream * unsigned char *key : pointer to authentication key * int key_len : length of authentication key * caddr_t digest : caller digest to be filled in */ EXPORT void mx_md5_hmac(unsigned char *text, int text_len, unsigned char *key, int key_len, void *digest); #include #endif /* mx_HAVE_MD5 */ #endif /* mx_CRED_MD5_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/cred-netrc.h000066400000000000000000000027621352610246600173400ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ .netrc file handling. * * Copyright (c) 2014 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef mx_CRED_NETRC_H #define mx_CRED_NETRC_H #include #ifdef mx_HAVE_NETRC #include #define mx_HEADER #include /* `netrc' */ EXPORT int c_netrc(void *vp); /* We shall lookup a machine in .netrc says ok_blook(netrc_lookup). * only_pass is true then the lookup is for the password only, otherwise we * look for a user (and add password only if we have an exact machine match) */ EXPORT boole mx_netrc_lookup(struct mx_url *urlp, boole only_pass); #include #endif /* mx_HAVE_NETRC */ #endif /* mx_CRED_NETRC_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/def.h000066400000000000000000000011371352610246600160430ustar00rootroot00000000000000/* Copyright (c) 1979 Regents of the University of California */ # /* * Mail -- a mail program * * Commands are: * t print out these messages * r reply to messages * m mail to users (analogous to send) * e edit messages * c [directory] chdir to dir or home if none * x exit quickly * w file save messages in file * q quit, save remaining stuff in mbox * d delete messages * u undelete messages * h print message headers * * Author: Kurt Shoens (UCB) March 25, 1978 */ s-nail-14.9.15/include/mx/dig-msg.h000066400000000000000000000057241352610246600166420ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ `digmsg'. * * Copyright (c) 2016 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef mx_DIG_MSG_H #define mx_DIG_MSG_H #include #include #define mx_HEADER #include enum mx_dig_msg_flags{ mx_DIG_MSG_NONE, mx_DIG_MSG_COMPOSE = 1u<<0, /* Compose mode object.. */ mx_DIG_MSG_COMPOSE_DIGGED = 1u<<1, /* ..with `digmsg' handle also! */ mx_DIG_MSG_RDONLY = 1u<<2, /* Message is read-only */ mx_DIG_MSG_OWN_MEMBAG = 1u<<3, /* .gdm_membag==&.gdm__membag_buf[0] */ mx_DIG_MSG_HAVE_FP = 1u<<4, /* Open on a fs_tmp_open() file */ mx_DIG_MSG_FCLOSE = 1u<<5 /* (mx_HAVE_FP:) needs fclose() */ }; struct mx_dig_msg_ctx{ struct mx_dig_msg_ctx *dmc_last; /* Linked only if !DIG_MSG_COMPOSE */ struct mx_dig_msg_ctx *dmc_next; struct message *dmc_mp; /* XXX Yet NULL if DIG_MSG_COMPOSE */ enum mx_dig_msg_flags dmc_flags; u32 dmc_msgno; /* XXX Only if !DIG_MSG_COMPOSE */ FILE *dmc_fp; struct header *dmc_hp; struct su_mem_bag *dmc_membag; struct su_mem_bag dmc__membag_buf[1]; }; /* This is a bit hairy */ #define mx_DIG_MSG_COMPOSE_CREATE(DMCP,HP) \ do{\ union {struct mx_dig_msg_ctx *dmc; void *v; u8 *b;} __p__;\ mx_dig_msg_compose_ctx = __p__.dmc = DMCP;\ __p__.b += sizeof *__p__.dmc;\ do *--__p__.b = 0; while(__p__.dmc != DMCP);\ (DMCP)->dmc_flags = mx_DIG_MSG_COMPOSE;\ (DMCP)->dmc_hp = HP;\ (DMCP)->dmc_membag = su_mem_bag_top(n_go_data->gdc_membag);\ }while(0) #define mx_DIG_MSG_COMPOSE_GUT(DMCP) \ do{\ ASSERT(mx_dig_msg_compose_ctx == DMCP);\ /* File cleaned up via fs_close_all_files() */\ mx_dig_msg_compose_ctx = NIL;\ }while(0) EXPORT_DATA struct mx_dig_msg_ctx *mx_dig_msg_read_overlay; /* XXX HACK */ EXPORT_DATA struct mx_dig_msg_ctx *mx_dig_msg_compose_ctx; /* Or NIL XXX HACK*/ /**/ EXPORT void mx_dig_msg_on_mailbox_close(struct mailbox *mbox); /* XXX HACK */ /* `digmsg' */ EXPORT int c_digmsg(void *vp); /* Accessibility hook for `~^' command; needs mx_DIG_MSG_COMPOSE_CREATE() */ EXPORT boole mx_dig_msg_circumflex(struct mx_dig_msg_ctx *dmcp, FILE *fp, char const *cmd); #include #endif /* mx_DIG_MSG_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/file-locks.h000066400000000000000000000071541352610246600173420ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ File locking, also via so-called dotlock files. * * Copyright (c) 2015 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef mx_FILE_LOCKS_H #define mx_FILE_LOCKS_H #include #define mx_HEADER #include enum mx_file_lock_type{ mx_FILE_LOCK_TYPE_READ, mx_FILE_LOCK_TYPE_WRITE }; enum mx_file_dotlock_state{ mx_FILE_DOTLOCK_STATE_NONE, mx_FILE_DOTLOCK_STATE_CANT_CHDIR, /* Failed to chdir(2) into desired path */ mx_FILE_DOTLOCK_STATE_NAMETOOLONG, /* Lock file name would be too long */ mx_FILE_DOTLOCK_STATE_ROFS, /* Read-only filesys (no error, mailbox RO) */ mx_FILE_DOTLOCK_STATE_NOPERM, /* No permission to creat lock file */ mx_FILE_DOTLOCK_STATE_NOEXEC, /* Privilege separated dotlocker not found */ mx_FILE_DOTLOCK_STATE_PRIVFAILED, /* Rising privileges failed in privsep */ mx_FILE_DOTLOCK_STATE_EXIST, /* Lock file already exists, stale lock? */ mx_FILE_DOTLOCK_STATE_FISHY, /* Something makes us think bad of situation */ mx_FILE_DOTLOCK_STATE_DUNNO, /* Catch-all error */ mx_FILE_DOTLOCK_STATE_PING, /* Not an error, but have to wait for lock */ /* ORd to any but _NONE: give up, do not retry */ mx_FILE_DOTLOCK_STATE_ABANDON = 1u<<7 }; #ifdef mx_HAVE_DOTLOCK struct mx_file_dotlock_info{ char const *fdi_file_name; /* Mailbox to lock */ char const *fdi_lock_name; /* .fdi_file_name + .lock */ char const *fdi_hostname; /* ..filled in parent (due resolver delays) */ char const *fdi_randstr; /* ..ditto, random string */ uz fdi_pollmsecs; /* Delay in between locking attempts */ struct stat *fdi_stb; }; #endif /* Will retry FILE_LOCK_TRIES times if pollmsecs > 0. * If pollmsecs is UZ_MAX, FILE_LOCK_MILLIS is used */ EXPORT boole mx_file_lock(int fd, enum mx_file_lock_type flt, off_t off, off_t len, uz pollmsecs); /* XXX off_t -> s64 */ /* Aquire a flt mx_file_lock(). * Will try FILE_LOCK_TRIES times if pollmsecs > 0 (once otherwise). * If pollmsecs is UZ_MAX, FILE_LOCK_MILLIS is used. * If *dotlock-disable* is set (FILE*)-1 is returned if flt could be aquired, * NIL if not, with err_no being usable. * Otherwise a dotlock file is created, and a registered control-pipe FILE* is * returned upon success which keeps the link in between us and the * lock-holding fork(2)ed subprocess (which conditionally replaced itself via * execv(2) with the privilege-separated dotlock helper program): the lock file * will be removed once the control pipe is closed via pipe_close(). * If *dotlock_ignore_error* is set (FILE*)-1 will be returned if at least the * normal file lock could be established, otherwise err_no() is usable */ EXPORT FILE *mx_file_dotlock(char const *fname, int fd, enum mx_file_lock_type flt, off_t off, off_t len, uz pollmsecs); #include #endif /* mx_FILE_LOCKS_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/file-streams.h000066400000000000000000000177721352610246600177140ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ File- and pipe streams, as well as temporary file creation. * * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef mx_FILE_STREAMS_H #define mx_FILE_STREAMS_H #include #define mx_HEADER #include enum mx_fs_oflags{ mx_FS_O_RDONLY = 1u<<0, mx_FS_O_WRONLY = 1u<<1, mx_FS_O_RDWR = 1u<<2, mx_FS_O_APPEND = 1u<<3, mx_FS_O_CREATE = 1u<<4, mx_FS_O_TRUNC = 1u<<5, mx_FS_O_EXCL = 1u<<6, mx_FS_O_UNLINK = 1u<<7, /* Only for tmp_open(): unlink(2) after creation */ /* Register file in our file table, causing its close when we jump away * and/or the mainloop ticks otherwise, shall it still exist */ mx_FS_O_REGISTER = 1u<<8, /* tmp_open(): unlink at unregistration: O_REGISTER!, !O_UNLINK */ mx_FS_O_REGISTER_UNLINK = 1u<<9, /* tmp_open(): do not release signals/unlink: !O_UNLINK! */ mx_FS_O_HOLDSIGS = 1u<<10, mx_FS_O_SUFFIX = 1u<<11 /* tmp_open() name hint is mandatory! extension! */ }; enum mx_fs_open_state{ /* TODO add mx_fs_open_mode, too */ /* Lower bits are in fact enum protocol! */ mx_FS_OPEN_STATE_NONE = 0, mx_FS_OPEN_STATE_EXISTS = 1u<<5 }; MCTA(n_PROTO_MASK < mx_FS_OPEN_STATE_EXISTS, "Bit carrier ranges overlap") /* Note: actually publically visible part of larger internal struct */ struct mx_fs_tmp_ctx{ char const *fstc_filename; }; /* */ #ifdef O_CLOEXEC # define mx_FS_FD_CLOEXEC_SET(FD) do {;} while(0) #else # define mx_FS_FD_CLOEXEC_SET(FD) mx_fs_fd_cloexec_set(FD) #endif /* oflags implied: cloexec,O_REGISTER. * {"r", O_RDONLY}, * {"w", O_WRONLY | O_CREAT | n_O_NOXY_BITS | O_TRUNC}, * {"wx", O_WRONLY | O_CREAT | O_EXCL}, * {"a", O_WRONLY | O_APPEND | O_CREAT | mx_O_NOXY_BITS}, * {"a+", O_RDWR | O_APPEND | O_CREAT | mx_O_NOXY_BITS}, * {"r+", O_RDWR}, * {"w+", O_RDWR | O_CREAT | mx_O_NOXY_BITS | O_TRUNC}, * {"W+", O_RDWR | O_CREAT | O_EXCL} * Prepend (!) an ampersand & ("background") to _not_ include O_REGISTER, * in which case the returned file must be closed with normal fclose(3). * mx_O_NOXY_BITS come from mx-config.h */ EXPORT FILE *mx_fs_open(char const *file, char const *oflags); /* TODO: Should be Mailbox::create_from_url(URL::from_string(DATA))! * Open file according to oflags (& prefix disallowed)m and register it * (leading ampersand & to suppress this is disallowed). * Handles compressed files, maildir etc. * If fs_or_nil is given it will be filled accordingly */ EXPORT FILE *mx_fs_open_any(char const *file, char const *oflags, enum mx_fs_open_state *fs_or_nil); /* Create a temporary file in $TMPDIR, use namehint for its name (prefix * unless O_SUFFIX is set in the fs_oflags oflags, in which case namehint is an * extension that MUST be part of aka fit in the resulting filename, otherwise * tmp_open() will fail), and return a stdio FILE pointer with access oflags. * *fstcp_or_nil may only be non-NIL under certain asserted conditions: * - if O_REGISTER: it is fully filled in; whether the filename is actually * useful depends on the chosen UNLINK mode * - else if O_HOLDSIGS: filename filled in, tmp_release() is callable, * - else O_UNLINK must not and O_REGISTER_UNLINK could be set (filename is * filled in, tmp_release() is not callable. * In the latter two cases autorec memory storage will be created (on success). * One of O_WRONLY and O_RDWR must be set. Implied: 0600,cloexec */ EXPORT FILE *mx_fs_tmp_open(char const *namehint, u32 oflags, struct mx_fs_tmp_ctx **fstcp_or_nil); /* If (O_REGISTER|)O_HOLDSIGS and a context pointer was set when calling * tmp_open(), then sigs_all_*() had not been released yet. * Call this to first unlink(2) the temporary file and then release signals */ EXPORT void mx_fs_tmp_release(struct mx_fs_tmp_ctx *fstcp); /* oflags implied: cloexec (unless nocloexec), O_REGISTER */ EXPORT FILE *mx_fs_fd_open(sz fd, char const *oflags, boole nocloexec); /* */ EXPORT void mx_fs_fd_cloexec_set(sz fd); /* Close and unregister a FILE* opened with any of fs_open(), fs_open_any(), * fs_tmp_open() (with O_REGISTER) or fd_open() */ EXPORT boole mx_fs_close(FILE *fp); /* Create a pair of file descriptors piped together, and ensure the CLOEXEC * bit is set in both; no registration is performed */ EXPORT boole mx_fs_pipe_cloexec(sz fd[2]); /* Create a process to be communicated with via a pipe. * mode can be r, W (newfd1 must be set, maybe to CHILD_FD_PASS or * CHILD_FD_NULL) or w (newfd1 is implicitly CHILD_FD_PASS). * In CHILD_FD_PASS cases pipe_close() must be called with waiting enabled, * whic is asserted! Note that child.h is NOT included. * env_addon may be NIL, otherwise it is expected to be a NIL terminated * array of "K=V" strings to be placed into the childs environment * TODO v15 hack: If cmd==(char*)-1 then shell is indeed expected to be a PTF * TODO v15 hack: :P that will be called from within the child process */ EXPORT FILE *mx_fs_pipe_open(char const *cmd, char const *mode, char const *shell, char const **env_addon, int newfd1); /* Takes a FILE* returned by pipe_open, and returns <0 if no process can be * found, 0 on success, and an errno on kill(2) failure */ EXPORT s32 mx_fs_pipe_signal(FILE *fp, s32 sig); /* Close fp, which has been opened by fs_pipe_open(). * With dowait returns true only upon successful program exit. * In conjunction with CHILD_FD_PASS dowait is mandatory. */ EXPORT boole mx_fs_pipe_close(FILE *fp, boole dowait); /* Close all _O_REGISTERed files and pipes */ EXPORT void mx_fs_close_all(void); /* XXX Temporary (pre v15 I/O) line buffer "pool". * (Possibly) Get a line buffer, and release one to the pool, respectively. * The latter is driven by the mainloop to perform cleanups */ EXPORT void mx_fs_linepool_aquire(char **dp, uz *dsp); EXPORT void mx_fs_linepool_release(char *dp, uz ds); EXPORT void mx_fs_linepool_cleanup(void); /* TODO The rest below is old-style (will vanish with I/O layer rewrite) */ /* fgets() replacement to handle lines of arbitrary size and with embedded \0 * characters. * line - line buffer. *line may be NULL. * linesize - allocated size of line buffer. * count - maximum characters to read. May be NULL. * llen - length_of_line(*line). * fp - input FILE. * appendnl - always terminate line with \n, append if necessary. * Manages the n_PS_READLINE_NL hack */ EXPORT char *fgetline(char **line, uz *linesize, uz *count, uz *llen, FILE *fp, int appendnl su_DBG_LOC_ARGS_DECL); #ifdef su_HAVE_DBG_LOC_ARGS # define fgetline(A,B,C,D,E,F) \ fgetline(A, B, C, D, E, F su_DBG_LOC_ARGS_INJ) #endif /* Read up a line from the specified input into the linebuffer. * Return the number of characters read. Do not include the newline at EOL. * n is the number of characters already read and present in *linebuf; it'll be * treated as _the_ line if no more (successful) reads are possible. * Manages the n_PS_READLINE_NL hack */ EXPORT int readline_restart(FILE *ibuf, char **linebuf, uz *linesize, uz n su_DBG_LOC_ARGS_DECL); #ifdef su_HAVE_DBG_LOC_ARGS # define readline_restart(A,B,C,D) \ readline_restart(A, B, C, D su_DBG_LOC_ARGS_INJ) #endif /* Determine the size of the file possessed by the passed buffer */ EXPORT off_t fsize(FILE *iob); #include #endif /* mx_FILE_STREAMS_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/filter-html.h000066400000000000000000000047041352610246600175370ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ HTML tagsoup filter. * * Copyright (c) 2015 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef mx_FILTER_HTML_H #define mx_FILTER_HTML_H #include #ifdef mx_HAVE_FILTER_HTML_TAGSOUP #define mx_HEADER #include struct mx_flthtml{ FILE *fh_os; /* Output stream */ u32 fh_flags; u32 fh_lmax; /* Maximum byte +1 in .fh_line/4 */ u32 fh_len; /* Current bytes in .fh_line */ u32 fh_last_ws; /* Last whitespace on line (fold purposes) */ u32 fh_mboff; /* Last offset for "mbtowc" */ u32 fh_mbwidth; /* We count characters not bytes if possible */ char *fh_line; /* Output line buffer - MUST be last field! */ s32 fh_href_dist; /* Count of lines since last HREF flush */ u32 fh_href_no; /* HREF sequence number */ struct mx_flthtml_href *fh_hrefs; struct mx_flthtml_tag const *fh_ign_tag; /* Tag that ends ignore mode */ char *fh_curr; /* Current cursor into .fh_bdat */ char *fh_bmax; /* Maximum byte in .fh_bdat +1 */ char *fh_bdat; /* (Temporary) Tag content data storage */ }; /* TODO Because we don't support filter chains yet this filter will be run * TODO in a dedicated subprocess, driven via a special fs_popen() mode */ EXPORT int mx_flthtml_process_main(void); EXPORT void mx_flthtml_init(struct mx_flthtml *self); EXPORT void mx_flthtml_destroy(struct mx_flthtml *self); EXPORT void mx_flthtml_reset(struct mx_flthtml *self, FILE *f); EXPORT sz mx_flthtml_push(struct mx_flthtml *self, char const *dat,uz len); EXPORT sz mx_flthtml_flush(struct mx_flthtml *self); #include #endif /* mx_HAVE_FILTER_HTML_TAGSOUP */ #endif /* mx_FILTER_HTML_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/filter-quote.h000066400000000000000000000053431352610246600177300ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ struct quoteflt: quotation (sub) filter. * * Copyright (c) 2012/3 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef mx_FILTER_QUOTE_H #define mx_FILTER_QUOTE_H #include #if defined mx_HAVE_FILTER_QUOTE_FOLD && defined mx_HAVE_C90AMEND1 # include #endif #define mx_HEADER #include struct quoteflt{ FILE *qf_os; /* Output stream */ char const *qf_pfix; u32 qf_pfix_len; /* Length of prefix: 0: bypass */ u32 qf_qfold_min; /* Simple way: wrote prefix? */ boole qf_bypass; /* Simply write to .qf_os TODO BYPASS, then! */ /* TODO quoteflt.qf_nl_last is a hack that i have introduced so that we * TODO finally can gracefully place a newline last in the visual display. * TODO I.e., for cases where quoteflt shouldn't be used at all */ boole qf_nl_last; /* Last thing written/seen was NL */ #ifndef mx_HAVE_FILTER_QUOTE_FOLD u8 qf__dummy[6]; #else u8 qf_state; /* *quote-fold* state machine */ boole qf_brk_isws; /* Breakpoint is at WS */ u32 qf_qfold_max; /* Otherwise: line lengths */ u32 qf_qfold_maxnws; u32 qf_wscnt; /* Whitespace count */ char const *qf_quote_chars; /* *quote-chars* */ u32 qf_brkl; /* Breakpoint */ u32 qf_brkw; /* Visual width, breakpoint */ u32 qf_datw; /* Current visual output line width */ u8 qf__dummy2[4]; struct str qf_dat; /* Current visual output line */ struct str qf_currq; /* Current quote, compressed */ mbstate_t qf_mbps[2]; #endif }; EXPORT struct quoteflt *quoteflt_dummy(void); /* TODO LEGACY */ EXPORT void quoteflt_init(struct quoteflt *self, char const *prefix, boole bypass); EXPORT void quoteflt_destroy(struct quoteflt *self); EXPORT void quoteflt_reset(struct quoteflt *self, FILE *f); EXPORT sz quoteflt_push(struct quoteflt *self, char const *dat, uz len); EXPORT sz quoteflt_flush(struct quoteflt *self); #include #endif /* mx_FILTER_QUOTE_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/gen-version.h000066400000000000000000000002761352610246600175440ustar00rootroot00000000000000#define n_VERSION "v14.9.15" #define n_VERSION_DATE "2019-08-17" #define n_VERSION_MAJOR "14" #define n_VERSION_MINOR "9" #define n_VERSION_UPDATE "15" #define n_VERSION_HEXNUM "0x0E00900F" s-nail-14.9.15/include/mx/iconv.h000066400000000000000000000103061352610246600164210ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ iconv(3) interface. * * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany. * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1980, 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. */ #ifndef mx_ICONV_H #define mx_ICONV_H #include #ifdef mx_HAVE_ICONV # include #endif #define mx_HEADER #include #ifdef mx_HAVE_ICONV enum n_iconv_flags{ n_ICONV_NONE, n_ICONV_IGN_ILSEQ = 1u<<0, /* Ignore input EILSEQ (replacement char) */ n_ICONV_IGN_NOREVERSE = 1u<<1, /* .. non-reversible conversions in output */ n_ICONV_UNIREPL = 1u<<2, /* Use Unicode replacement 0xFFFD=EF BF BD */ n_ICONV_DEFAULT = n_ICONV_IGN_ILSEQ | n_ICONV_IGN_NOREVERSE, n_ICONV_UNIDEFAULT = n_ICONV_DEFAULT | n_ICONV_UNIREPL }; EXPORT_DATA s32 n_iconv_err_no; /* TODO HACK: part of CTX to not get lost */ EXPORT_DATA iconv_t iconvd; #endif /* mx_HAVE_ICONV */ /* Returns a newly n_autorec_alloc()ated thing if there were adjustments. * Return value is always smaller or of equal size. * NIL will be returned if cset is an invalid character set name */ EXPORT char *n_iconv_normalize_name(char const *cset); /* Is it ASCII indeed? */ EXPORT boole n_iconv_name_is_ascii(char const *cset); #ifdef mx_HAVE_ICONV EXPORT iconv_t n_iconv_open(char const *tocode, char const *fromcode); /* If *cd* == *iconvd*, assigns -1 to the latter */ EXPORT void n_iconv_close(iconv_t cd); /* Reset encoding state */ EXPORT void n_iconv_reset(iconv_t cd); /* iconv(3), but return su_err_no() or 0 upon success. * The err_no may be ERR_NOENT unless n_ICONV_IGN_NOREVERSE is set in icf. * iconv_str() auto-grows on ERR_2BIG errors; in and in_rest_or_nil may be * the same object. * Note: ERR_INVAL (incomplete sequence at end of input) is NOT handled, so the * replacement character must be added manually if that happens at EOF! * TODO These must be contexts. For now we duplicate su_err_no() into * TODO n_iconv_err_no in order to be able to access it when stuff happens * TODO "in between"! */ EXPORT int n_iconv_buf(iconv_t cd, enum n_iconv_flags icf, char const **inb, uz *inbleft, char **outb, uz *outbleft); EXPORT int n_iconv_str(iconv_t icp, enum n_iconv_flags icf, struct str *out, struct str const *in, struct str *in_rest_or_nil); /* If tocode==NIL, uses *ttycharset*. If fromcode==NIL, uses UTF-8. * Returns a autorec_alloc()ed buffer or NIL */ EXPORT char *n_iconv_onetime_cp(enum n_iconv_flags icf, char const *tocode, char const *fromcode, char const *input); #endif /* mx_HAVE_ICONV */ #include #endif /* mx_ICONV_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/mta-aliases.h000066400000000000000000000031761352610246600175120ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ MTA alias processing. * * Copyright (c) 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef mx_MTA_ALIASES_H #define mx_MTA_ALIASES_H #include #ifdef mx_HAVE_MTA_ALIASES #include #define mx_HEADER #include /* Expand all names from *npp which are still of type mx_NAME_ADDRSPEC_ISNAME, * iff *mta-aliases* is set. * Return ERR_NONE when processing completed normally, ERR_NOENT if the file * given in *mta-aliases* does not exist, or whatever other error occurred. * ERR_DESTADDRREQ is returned instead of ERR_NONE if after expansion ISNAME * entries still remain in *npp. * The result may contain duplicates */ EXPORT s32 mx_mta_aliases_expand(struct mx_name **npp); #include #endif /* mx_HAVE_MTA_ALIASES */ #endif /* mx_MTA_ALIASES_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/nail.h000066400000000000000000002126551352610246600162410ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Header inclusion, macros, constants, types and the global var declarations. *@ TODO Should be split in myriads of FEATURE-GROUP.h headers. Sort. def.h. * * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany. * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: BSD-3-Clause TODO ISC */ /* * Copyright (c) 1980, 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. */ #ifndef n_NAIL_H # define n_NAIL_H #include #include #include #ifdef mx_HAVE_GETTIMEOFDAY # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #ifdef mx_HAVE_REGEX # include #endif /* Many things possibly of interest for adjustments have been outsourced */ #include #include #include /* TODO should not be needed */ /* TODO fake */ #include "su/code-in.h" struct mx_dig_msg_ctx; /* */ #define n_FROM_DATEBUF 64 /* Size of RFC 4155 From_ line date */ #define n_DATE_DAYSYEAR 365u #define n_DATE_NANOSSEC (n_DATE_MICROSSEC * 1000) #define n_DATE_MICROSSEC (n_DATE_MILLISSEC * 1000) #define n_DATE_MILLISSEC 1000u #define n_DATE_SECSMIN 60u #define n_DATE_MINSHOUR 60u #define n_DATE_HOURSDAY 24u #define n_DATE_SECSHOUR (n_DATE_SECSMIN * n_DATE_MINSHOUR) #define n_DATE_SECSDAY (n_DATE_SECSHOUR * n_DATE_HOURSDAY) /* Network protocol newline */ #define NETNL "\015\012" #define NETLINE(X) X NETNL /* * OS, CC support, generic macros etc. TODO remove -> SU! */ /* CC */ /* Suppress some technical warnings via #pragma's unless developing. * XXX Wild guesses: clang(1) 1.7 and (OpenBSD) gcc(1) 4.2.1 don't work */ #ifndef mx_HAVE_DEVEL # if su_CC_VCHECK_CLANG(3, 4) # pragma clang diagnostic ignored "-Wassign-enum" # pragma clang diagnostic ignored "-Wdisabled-macro-expansion" # pragma clang diagnostic ignored "-Wformat" # elif su_CC_VCHECK_GCC(4, 7) # pragma GCC diagnostic ignored "-Wunused-local-typedefs" # pragma GCC diagnostic ignored "-Wformat" # endif #endif #undef mx_HAVE_NATCH_CHAR #if defined mx_HAVE_SETLOCALE && defined mx_HAVE_C90AMEND1 && \ defined mx_HAVE_WCWIDTH # define mx_HAVE_NATCH_CHAR # define n_NATCH_CHAR(X) X #else # define n_NATCH_CHAR(X) #endif #define n_UNCONST(X) su_UNCONST(void*,X) /* TODO */ /* * Types */ typedef void (*n_sighdl_t)(int); enum n_announce_flags{ n_ANNOUNCE_NONE = 0, /* Only housekeeping */ n_ANNOUNCE_MAIN_CALL = 1u<<0, /* POSIX covered startup call */ n_ANNOUNCE_STATUS = 1u<<1, /* Only print status */ n_ANNOUNCE_CHANGE = 1u<<2, /* Folder changed */ n__ANNOUNCE_HEADER = 1u<<6, n__ANNOUNCE_ANY = 1u<<7 }; enum expand_addr_flags{ EAF_NONE = 0, /* -> EAF_NOFILE | EAF_NOPIPE */ EAF_RESTRICT = 1u<<0, /* "restrict" (do unless interaktive / -[~#]) */ EAF_FAIL = 1u<<1, /* "fail" */ EAF_FAILINVADDR = 1u<<2, /* "failinvaddr" */ EAF_DOMAINCHECK = 1u<<3, /* "domaincheck" <-> *expandaddr-domaincheck* */ EAF_NAMEHOSTEX = 1u<<4, /* "namehostex": expand local user names */ EAF_SHEXP_PARSE = 1u<<5, /* shexp_parse() the address first is allowed */ /* Bits reused by enum expand_addr_check_mode! */ EAF_FCC = 1u<<8, /* +"fcc" umbrella */ EAF_FILE = 1u<<9, /* +"file" targets */ EAF_PIPE = 1u<<10, /* +"pipe" command pipe targets */ EAF_NAME = 1u<<11, /* +"name"s (non-address) names / MTA aliases */ EAF_ADDR = 1u<<12, /* +"addr" network address (contain "@") */ EAF_TARGET_MASK = EAF_FCC | EAF_FILE | EAF_PIPE | EAF_NAME | EAF_ADDR, EAF_RESTRICT_TARGETS = EAF_NAME | EAF_ADDR /* (default set if not set) */ /* TODO HACK! In pre-v15 we have a control flow problem (it is a general * TODO design problem): if n_collect() calls makeheader(), e.g., for -t or * TODO because of ~e diting, then that will checkaddr() and that will * TODO remove invalid headers. However, this code path does not know * TODO about keeping track of senderrors unless a pointer has been passed, * TODO but which it doesn't for ~e, and shall not, too. Thus, invalid * TODO addresses may be automatically removed, silently, and noone will * TODO ever know, in particular not regarding "failinvaddr". * TODO The hacky solution is this bit -- which can ONLY be used for fields * TODO which will be subject to namelist_vaporise_head() later on!! --, * TODO if it is set (by n_header_extract()) then checkaddr() will NOT strip * TODO invalid headers off IF it deals with a NULL senderror pointer */ ,EAF_MAYKEEP = 1u<<15 }; enum expand_addr_check_mode{ EACM_NONE = 0u, /* Don't care about *expandaddr* */ EACM_NORMAL = 1u<<0, /* Use our normal *expandaddr* checking */ EACM_STRICT = 1u<<1, /* Never allow any file or pipe addresse */ EACM_MODE_MASK = 0x3u, /* _NORMAL and _STRICT are mutual! */ EACM_NOLOG = 1u<<2, /* Do not log check errors */ /* Some special overwrites of EAF_TARGETs. * May NOT clash with EAF_* bits which may be ORd to these here! */ EACM_NONAME = 1u<<16, EACM_NONAME_OR_FAIL = 1u<<17, EACM_DOMAINCHECK = 1u<<18 /* Honour it! */ }; enum n_cmd_arg_flags{ /* TODO Most of these need to change, in fact in v15 * TODO i rather see the mechanism that is used in c_bind() extended and used * TODO anywhere, i.e. n_cmd_arg_parse(). * TODO Note we may NOT support arguments with su_cs_len()>=U32_MAX (?) */ n_CMD_ARG_TYPE_MSGLIST = 0, /* Message list type */ n_CMD_ARG_TYPE_NDMLIST = 1, /* Message list, no defaults */ n_CMD_ARG_TYPE_RAWDAT = 2, /* The plain string in an argv[] */ n_CMD_ARG_TYPE_STRING = 3, /* A pure string TODO obsolete */ n_CMD_ARG_TYPE_WYSH = 4, /* getrawlist(), sh(1) compatible */ n_CMD_ARG_TYPE_RAWLIST = 5, /* getrawlist(), old style TODO obsolete */ n_CMD_ARG_TYPE_WYRA = 6, /* _RAWLIST or _WYSH (with `wysh') TODO obs. */ n_CMD_ARG_TYPE_ARG = 7, /* n_cmd_arg_desc/n_cmd_arg() new-style */ n_CMD_ARG_TYPE_MASK = 7, /* Mask of the above */ n_CMD_ARG_A = 1u<<4, /* Needs an active mailbox */ n_CMD_ARG_F = 1u<<5, /* Is a conditional command */ n_CMD_ARG_G = 1u<<6, /* Is supposed to produce "gabby" history */ n_CMD_ARG_H = 1u<<7, /* Never place in `history' */ n_CMD_ARG_I = 1u<<8, /* Interactive command bit */ n_CMD_ARG_L = 1u<<9, /* Supports `local' prefix (only WYSH/WYRA) */ n_CMD_ARG_M = 1u<<10, /* Legal from send mode bit */ n_CMD_ARG_O = 1u<<11, /* n_OBSOLETE()d command */ n_CMD_ARG_P = 1u<<12, /* Autoprint dot after command */ n_CMD_ARG_R = 1u<<13, /* Forbidden in compose mode recursion */ n_CMD_ARG_SC = 1u<<14, /* Forbidden pre-n_PSO_STARTED_CONFIG */ n_CMD_ARG_S = 1u<<15, /* Forbidden pre-n_PSO_STARTED (POSIX) */ n_CMD_ARG_T = 1u<<16, /* Is a transparent command */ n_CMD_ARG_V = 1u<<17, /* Supports `vput' prefix (only WYSH/WYRA) */ n_CMD_ARG_W = 1u<<18, /* Invalid when read only bit */ n_CMD_ARG_X = 1u<<19, /* Valid command in n_PS_COMPOSE_FORKHOOK mode */ /* XXX Note that CMD_ARG_EM implies a _real_ return value for $! */ n_CMD_ARG_EM = 1u<<30 /* If error: n_pstate_err_no (4 $! aka. ok_v___em) */ }; enum n_cmd_arg_desc_flags{ /* - A type */ n_CMD_ARG_DESC_SHEXP = 1u<<0, /* sh(1)ell-style token */ /* TODO All MSGLIST arguments can only be used last and are always greedy * TODO (but MUST be _GREEDY, and MUST NOT be _OPTION too!). * MSGLIST_AND_TARGET may create a NULL target */ n_CMD_ARG_DESC_MSGLIST = 1u<<1, /* Message specification(s) */ n_CMD_ARG_DESC_NDMSGLIST = 1u<<2, n_CMD_ARG_DESC_MSGLIST_AND_TARGET = 1u<<3, n__CMD_ARG_DESC_TYPE_MASK = n_CMD_ARG_DESC_SHEXP | n_CMD_ARG_DESC_MSGLIST | n_CMD_ARG_DESC_NDMSGLIST | n_CMD_ARG_DESC_MSGLIST_AND_TARGET, /* - Optional flags */ /* It is not an error if an optional argument is missing; once an argument * has been declared optional only optional arguments may follow */ n_CMD_ARG_DESC_OPTION = 1u<<16, /* GREEDY: parse as many of that type as possible; must be last entry */ n_CMD_ARG_DESC_GREEDY = 1u<<17, /* If greedy, join all given arguments separated by ASCII SP right away */ n_CMD_ARG_DESC_GREEDY_JOIN = 1u<<18, /* Honour an overall "stop" request in one of the arguments (\c@ or #) */ n_CMD_ARG_DESC_HONOUR_STOP = 1u<<19, /* With any MSGLIST, only one message may be give or ERR_NOTSUP (default) */ n_CMD_ARG_DESC_MSGLIST_NEEDS_SINGLE = 1u<<20, n__CMD_ARG_DESC_FLAG_MASK = n_CMD_ARG_DESC_OPTION | n_CMD_ARG_DESC_GREEDY | n_CMD_ARG_DESC_GREEDY_JOIN | n_CMD_ARG_DESC_HONOUR_STOP | n_CMD_ARG_DESC_MSGLIST_NEEDS_SINGLE, /* We may include something for n_pstate_err_no */ n_CMD_ARG_DESC_ERRNO_SHIFT = 21u, n_CMD_ARG_DESC_ERRNO_MASK = (1u<<10) - 1 }; #define n_CMD_ARG_DESC_ERRNO_TO_ORBITS(ENO) \ (((u32)(ENO)) << n_CMD_ARG_DESC_ERRNO) #define n_CMD_ARG_DESC_TO_ERRNO(FLAGCARRIER) \ (((u32)(FLAGCARRIER) >> n_CMD_ARG_DESC_ERRNO_SHIFT) &\ n_CMD_ARG_DESC_ERRNO_MASK) enum conversion{ CONV_NONE, /* no conversion */ CONV_7BIT, /* no conversion, is 7bit */ CONV_FROMQP, /* convert from quoted-printable */ CONV_TOQP, /* convert to quoted-printable */ CONV_8BIT, /* convert to 8bit (iconv) */ CONV_FROMB64, /* convert from base64 */ CONV_FROMB64_T, /* convert from base64/text */ CONV_TOB64, /* convert to base64 */ CONV_FROMHDR, /* convert from RFC1522 format */ CONV_TOHDR, /* convert to RFC1522 format */ CONV_TOHDR_A /* convert addresses for header */ }; enum cproto{ CPROTO_CERTINFO, /* Special dummy proto for TLS certificate info xxx */ CPROTO_CCRED, /* Special dummy credential proto (S/MIME etc.) */ CPROTO_SOCKS, /* Special dummy SOCKS5 proxy proto */ CPROTO_SMTP, CPROTO_POP3 ,CPROTO_IMAP }; /* enum n_err_number from gen-config.h, which is in sync with * su_err_doc(), su_err_name() and su_err_from_name() */ enum n_exit_status{ n_EXIT_OK = EXIT_SUCCESS, n_EXIT_ERR = EXIT_FAILURE, n_EXIT_USE = 64, /* sysexits.h:EX_USAGE */ n_EXIT_NOUSER = 67, /* :EX_NOUSER */ n_EXIT_COLL_ABORT = 1<<1, /* Message collection was aborted */ n_EXIT_SEND_ERROR = 1<<2 /* Unspecified send error occurred */ }; enum fedit_mode{ FEDIT_NONE = 0, FEDIT_SYSBOX = 1u<<0, /* %: prefix */ FEDIT_RDONLY = 1u<<1, /* Readonly (per-box, n_OPT_R_FLAG is global) */ FEDIT_NEWMAIL = 1u<<2, /* `newmail' operation TODO OBSOLETE THIS! */ FEDIT_ACCOUNT = 1u<<3 /* setfile() called by `account' */ }; enum fexp_mode{ FEXP_FULL, /* Full expansion */ FEXP_NOPROTO = 1u<<0, /* TODO no which_protocol() to decide expansion */ FEXP_SILENT = 1u<<1, /* Don't print but only return errors */ FEXP_MULTIOK = 1u<<2, /* Expansion to many entries is ok */ FEXP_LOCAL = 1u<<3, /* Result must be local file/maildir */ FEXP_LOCAL_FILE = 1u<<4, /* ..must be a local file: strips protocol://! */ FEXP_NSHORTCUT = 1u<<5, /* Don't expand shortcuts */ FEXP_NSPECIAL = 1u<<6, /* No %,#,& specials */ FEXP_NFOLDER = 1u<<7, /* NSPECIAL and no + folder, too */ FEXP_NSHELL = 1u<<8, /* Don't do shell word exp. (but ~/, $VAR) */ FEXP_NVAR = 1u<<9 /* ..not even $VAR expansion */ }; enum n_go_input_flags{ n_GO_INPUT_NONE, n_GO_INPUT_CTX_BASE = 0, /* Generic shared base: don't use! */ n_GO_INPUT_CTX_DEFAULT = 1, /* Default input */ n_GO_INPUT_CTX_COMPOSE = 2, /* Compose mode input */ n__GO_INPUT_CTX_MASK = 3, /* _MASK is not a valid index here, but the lower bits are not misused, * therefore -- to save space! -- indexing is performed via "& _MASK". * This is CTA()d! For actual spacing of arrays we use _MAX1 instead */ n__GO_INPUT_CTX_MAX1 = n_GO_INPUT_CTX_COMPOSE + 1, n_GO_INPUT_HOLDALLSIGS = 1u<<8, /* sigs_all_hold() active TODO */ /* `xcall' is `call' (at the level where this is set): to be set when * teardown of top level has undesired effects, e.g., for `account's and * folder hooks etc., where we do not to loose our `localopts' unroll list */ n_GO_INPUT_NO_XCALL = 1u<<9, n_GO_INPUT_FORCE_STDIN = 1u<<10, /* Even in macro, use stdin (`read')! */ n_GO_INPUT_DELAY_INJECTIONS = 1u<<11, /* Skip go_input_inject()ions */ n_GO_INPUT_NL_ESC = 1u<<12, /* Support "\\$" line continuation */ n_GO_INPUT_NL_FOLLOW = 1u<<13, /* ..on such a follow line */ n_GO_INPUT_PROMPT_NONE = 1u<<14, /* Don't print prompt */ n_GO_INPUT_PROMPT_EVAL = 1u<<15, /* Instead, evaluate *prompt* */ /* XXX The remains are mostly hacks */ n_GO_INPUT_HIST_ADD = 1u<<16, /* Add the result to history list */ n_GO_INPUT_HIST_GABBY = 1u<<17, /* Consider history entry as gabby */ n_GO_INPUT_IGNERR = 1u<<18, /* Imply `ignerr' command modifier */ n__GO_FREEBIT = 24 }; enum n_go_input_inject_flags{ n_GO_INPUT_INJECT_NONE = 0, n_GO_INPUT_INJECT_COMMIT = 1u<<0, /* Auto-commit input */ n_GO_INPUT_INJECT_HISTORY = 1u<<1 /* Allow history addition */ }; enum n_header_extract_flags{ n_HEADER_EXTRACT_NONE, n_HEADER_EXTRACT_EXTENDED = 1u<<0, n_HEADER_EXTRACT_FULL = 2u<<0, n_HEADER_EXTRACT__MODE_MASK = n_HEADER_EXTRACT_EXTENDED | n_HEADER_EXTRACT_FULL, /* Prefill the receivers with the already existing content of the given * struct header arguent */ n_HEADER_EXTRACT_PREFILL_RECEIVERS = 1u<<8, /* Understand and ignore shell-style comments */ n_HEADER_EXTRACT_IGNORE_SHELL_COMMENTS = 1u<<9, /* Ignore a MBOX From_ line _silently */ n_HEADER_EXTRACT_IGNORE_FROM_ = 1u<<10 }; /* Special ignore (where _TYPE is covered by POSIX `ignore' / `retain'). * _ALL is very special in that it doesn't have a backing object. * Go over enum to avoid cascads of (different) CC warnings for used CTA()s */ #define n_IGNORE_ALL ((struct n_ignore*)n__IGNORE_ALL) #define n_IGNORE_TYPE ((struct n_ignore*)n__IGNORE_TYPE) #define n_IGNORE_SAVE ((struct n_ignore*)n__IGNORE_SAVE) #define n_IGNORE_FWD ((struct n_ignore*)n__IGNORE_FWD) #define n_IGNORE_TOP ((struct n_ignore*)n__IGNORE_TOP) enum{ n__IGNORE_ALL = -2, n__IGNORE_TYPE = -3, n__IGNORE_SAVE = -4, n__IGNORE_FWD = -5, n__IGNORE_TOP = -6, n__IGNORE_ADJUST = 3, n__IGNORE_MAX = 6 - n__IGNORE_ADJUST }; enum n_mailsend_flags{ n_MAILSEND_NONE, n_MAILSEND_IS_FWD = 1u<<0, n_MAILSEND_HEADERS_PRINT = 1u<<2, n_MAILSEND_RECORD_RECIPIENT = 1u<<3, n_MAILSEND_ALTERNATES_NOSTRIP = 1u<<4, n_MAILSEND_ALL = n_MAILSEND_IS_FWD | n_MAILSEND_HEADERS_PRINT | n_MAILSEND_RECORD_RECIPIENT | n_MAILSEND_ALTERNATES_NOSTRIP }; enum mimecontent{ MIME_UNKNOWN, /* unknown content */ MIME_SUBHDR, /* inside a multipart subheader */ MIME_822, /* message/rfc822 content */ MIME_MESSAGE, /* other message/ content */ MIME_TEXT_PLAIN, /* text/plain content */ MIME_TEXT_HTML, /* text/html content */ MIME_TEXT, /* other text/ content */ MIME_ALTERNATIVE, /* multipart/alternative content */ MIME_RELATED, /* mime/related (RFC 2387) */ MIME_DIGEST, /* multipart/digest content */ MIME_SIGNED, /* multipart/signed */ MIME_ENCRYPTED, /* multipart/encrypted */ MIME_MULTI, /* other multipart/ content */ MIME_PKCS7, /* PKCS7 content */ MIME_DISCARD /* content is discarded */ }; enum mime_counter_evidence{ MIMECE_NONE, MIMECE_SET = 1u<<0, /* *mime-counter-evidence* was set */ MIMECE_BIN_OVWR = 1u<<1, /* appli../octet-stream: check, ovw if possible */ MIMECE_ALL_OVWR = 1u<<2, /* all: check, ovw if possible */ MIMECE_BIN_PARSE = 1u<<3 /* appli../octet-stream: classify contents last */ }; /* Content-Transfer-Encodings as defined in RFC 2045: * - Quoted-Printable, section 6.7 * - Base64, section 6.8 */ #define QP_LINESIZE (4 * 19) /* Max. compliant QP linesize */ #define B64_LINESIZE (4 * 19) /* Max. compliant Base64 linesize */ #define B64_ENCODE_INPUT_PER_LINE ((B64_LINESIZE / 4) * 3) enum mime_enc{ MIMEE_NONE, /* message is not in MIME format */ MIMEE_BIN, /* message is in binary encoding */ MIMEE_8B, /* message is in 8bit encoding */ MIMEE_7B, /* message is in 7bit encoding */ MIMEE_QP, /* message is quoted-printable */ MIMEE_B64 /* message is in base64 encoding */ }; /* xxx QP came later, maybe rewrite all to use mime_enc_flags directly? */ enum mime_enc_flags{ MIMEEF_NONE, MIMEEF_SALLOC = 1u<<0, /* Use n_autorec_alloc(), not n_realloc().. */ /* ..result .s,.l point to user buffer of *_LINESIZE+[+[+]] bytes instead */ MIMEEF_BUF = 1u<<1, MIMEEF_CRLF = 1u<<2, /* (encode) Append "\r\n" to lines */ MIMEEF_LF = 1u<<3, /* (encode) Append "\n" to lines */ /* (encode) If one of _CRLF/_LF is set, honour *_LINESIZE+[+[+]] and * inject the desired line-ending whenever a linewrap is desired */ MIMEEF_MULTILINE = 1u<<4, /* (encode) Quote with header rules, do not generate soft NL breaks? * For mustquote(), specifies whether special RFC 2047 header rules * should be used instead */ MIMEEF_ISHEAD = 1u<<5, /* (encode) Ditto; for mustquote() this furtherly fine-tunes behaviour in * that characters which would not be reported as "must-quote" when * detecting whether quoting is necessary at all will be reported as * "must-quote" if they have to be encoded in an encoded word */ MIMEEF_ISENCWORD = 1u<<6, __MIMEEF_LAST = 6u }; enum qpflags{ QP_NONE = MIMEEF_NONE, QP_SALLOC = MIMEEF_SALLOC, QP_BUF = MIMEEF_BUF, QP_ISHEAD = MIMEEF_ISHEAD, QP_ISENCWORD = MIMEEF_ISENCWORD }; enum b64flags{ B64_NONE = MIMEEF_NONE, B64_SALLOC = MIMEEF_SALLOC, B64_BUF = MIMEEF_BUF, B64_CRLF = MIMEEF_CRLF, B64_LF = MIMEEF_LF, B64_MULTILINE = MIMEEF_MULTILINE, /* Not used, but for clarity only */ B64_ISHEAD = MIMEEF_ISHEAD, B64_ISENCWORD = MIMEEF_ISENCWORD, /* Special version of Base64, "Base64URL", according to RFC 4648. * Only supported for encoding! */ B64_RFC4648URL = 1u<<(__MIMEEF_LAST+1), /* Don't use any ("=") padding; * may NOT be used with any of _CRLF, _LF or _MULTILINE */ B64_NOPAD = 1u<<(__MIMEEF_LAST+2) }; enum mime_parse_flags{ MIME_PARSE_NONE, MIME_PARSE_DECRYPT = 1u<<0, MIME_PARSE_PARTS = 1u<<1, MIME_PARSE_SHALLOW = 1u<<2, /* In effect we parse this message for user display or quoting purposes, so * relaxed rules regarding content inspection may be applicable */ MIME_PARSE_FOR_USER_CONTEXT = 1u<<3 }; enum mime_handler_flags{ MIME_HDL_NULL, /* No pipe- mimetype handler, go away */ MIME_HDL_CMD, /* Normal command */ MIME_HDL_TEXT, /* @ special cmd to force treatment as text */ MIME_HDL_PTF, /* A special pointer-to-function handler */ MIME_HDL_MSG, /* Display msg (returned as command string) */ MIME_HDL_TYPE_MASK = 7u, MIME_HDL_COPIOUSOUTPUT = 1u<<4, /* _CMD produces reintegratable text */ MIME_HDL_ISQUOTE = 1u<<5, /* Is quote action (we have info, keep it!) */ MIME_HDL_NOQUOTE = 1u<<6, /* No MIME for quoting */ MIME_HDL_ASYNC = 1u<<7, /* Should run asynchronously */ MIME_HDL_NEEDSTERM = 1u<<8, /* Takes over terminal */ MIME_HDL_TMPF = 1u<<9, /* Create temporary file (zero-sized) */ MIME_HDL_TMPF_FILL = 1u<<10, /* Fill in the msg body content */ MIME_HDL_TMPF_UNLINK = 1u<<11 /* Delete it later again */ }; enum okay{ STOP = 0, OKAY = 1 }; enum okey_xlook_mode{ OXM_PLAIN = 1u<<0, /* Plain key always tested */ OXM_H_P = 1u<<1, /* Check PLAIN-.url_h_p */ OXM_U_H_P = 1u<<2, /* Check PLAIN-.url_u_h_p */ OXM_ALL = 0x7u }; /* <0 means "stop" unless *prompt* extensions are enabled. */ enum prompt_exp{ PROMPT_STOP = -1, /* \c */ /* *prompt* extensions: \$, \@ etc. */ PROMPT_DOLLAR = -2, PROMPT_AT = -3 }; enum protocol{ n_PROTO_NONE, n_PROTO_FILE, /* refers to a local file */ PROTO_FILE = n_PROTO_FILE, n_PROTO_POP3, /* is a pop3 server string */ PROTO_POP3 = n_PROTO_POP3, n_PROTO_IMAP, PROTO_IMAP = n_PROTO_IMAP, n_PROTO_MAILDIR, /* refers to a maildir folder */ PROTO_MAILDIR = n_PROTO_MAILDIR, n_PROTO_UNKNOWN, /* unknown protocol */ PROTO_UNKNOWN = n_PROTO_UNKNOWN, n_PROTO_MASK = (1u << 5) - 1 }; enum sendaction{ SEND_MBOX, /* no conversion to perform */ SEND_RFC822, /* no conversion, no From_ line */ SEND_TODISP, /* convert to displayable form */ SEND_TODISP_ALL, /* same, include all MIME parts */ SEND_TODISP_PARTS, /* same, but only interactive, user-selected parts */ SEND_SHOW, /* convert to 'show' command form */ SEND_TOSRCH, /* convert for IMAP SEARCH */ SEND_TOFILE, /* convert for saving body to a file */ SEND_TOPIPE, /* convert for pipe-content/subc. */ SEND_QUOTE, /* convert for quoting */ SEND_QUOTE_ALL, /* same, include all MIME parts */ SEND_DECRYPT /* decrypt */ }; enum n_shexp_parse_flags{ n_SHEXP_PARSE_NONE, /* Don't perform expansions or interpret reverse solidus escape sequences. * Output may be NULL, otherwise the possibly trimmed non-expanded input is * used as output (implies _PARSE_META_KEEP) */ n_SHEXP_PARSE_DRYRUN = 1u<<0, n_SHEXP_PARSE_TRUNC = 1u<<1, /* Truncate result storage on entry */ n_SHEXP_PARSE_TRIM_SPACE = 1u<<2, /* ..surrounding tokens */ n_SHEXP_PARSE_TRIM_IFSSPACE = 1u<<3, /* " */ n_SHEXP_PARSE_LOG = 1u<<4, /* Log errors */ n_SHEXP_PARSE_LOG_D_V = 1u<<5, /* Log errors if n_PO_D_V */ n_SHEXP_PARSE_IFS_VAR = 1u<<6, /* IFS is *ifs*, not su_cs_is_blank() */ n_SHEXP_PARSE_IFS_ADD_COMMA = 1u<<7, /* Add comma , to normal "IFS" */ n_SHEXP_PARSE_IFS_IS_COMMA = 1u<<8, /* Let comma , be the sole "IFS" */ n_SHEXP_PARSE_IGNORE_EMPTY = 1u<<9, /* Ignore empty tokens, start over */ /* Implicitly open quotes, and ditto closing. _AUTO_FIXED may only be used * if an auto-quote-mode is enabled, implies _AUTO_CLOSE and causes the * quote mode to be permanently active (cannot be closed) */ n_SHEXP_PARSE_QUOTE_AUTO_FIXED = 1u<<16, n_SHEXP_PARSE_QUOTE_AUTO_SQ = 1u<<17, n_SHEXP_PARSE_QUOTE_AUTO_DQ = 1u<<18, n_SHEXP_PARSE_QUOTE_AUTO_DSQ = 1u<<19, n_SHEXP_PARSE_QUOTE_AUTO_CLOSE = 1u<<20, /* Ignore an open quote at EOS */ n__SHEXP_PARSE_QUOTE_AUTO_MASK = n_SHEXP_PARSE_QUOTE_AUTO_SQ | n_SHEXP_PARSE_QUOTE_AUTO_DQ | n_SHEXP_PARSE_QUOTE_AUTO_DSQ, /* Recognize metacharacters to separate tokens */ n_SHEXP_PARSE_META_VERTBAR = 1u<<21, n_SHEXP_PARSE_META_AMPERSAND = 1u<<22, /* Interpret ; as a sequencing operator, go_input_inject() remainder */ n_SHEXP_PARSE_META_SEMICOLON = 1u<<23, /* LPAREN, RPAREN, LESSTHAN, GREATERTHAN */ n_SHEXP_PARSE_META_MASK = n_SHEXP_PARSE_META_VERTBAR | n_SHEXP_PARSE_META_AMPERSAND | n_SHEXP_PARSE_META_SEMICOLON, /* Keep the metacharacter (or IFS character), do not skip over it */ n_SHEXP_PARSE_META_KEEP = 1u<<24, n__SHEXP_PARSE_LAST = 24 }; enum n_shexp_state{ n_SHEXP_STATE_NONE, /* We have produced some output (or would have, with _PARSE_DRYRUN). * Note that empty quotes like '' produce no output but set this bit */ n_SHEXP_STATE_OUTPUT = 1u<<0, /* Don't call the parser again (# comment seen; out of input). * Not (necessarily) mutual with _OUTPUT) */ n_SHEXP_STATE_STOP = 1u<<1, n_SHEXP_STATE_UNICODE = 1u<<2, /* \[Uu] used */ n_SHEXP_STATE_CONTROL = 1u<<3, /* Control characters seen */ n_SHEXP_STATE_QUOTE = 1u<<4, /* Any quotes seen */ n_SHEXP_STATE_WS_LEAD = 1u<<5, /* _TRIM_{IFS,}SPACE: seen.. */ n_SHEXP_STATE_WS_TRAIL = 1u<<6, /* .. leading / trailing WS */ n_SHEXP_STATE_META_VERTBAR = 1u<<7, /* Metacharacter | follows/ed */ n_SHEXP_STATE_META_AMPERSAND = 1u<<8, /* Metacharacter & follows/ed */ n_SHEXP_STATE_META_SEMICOLON = 1u<<9, /* Metacharacter ; follows/ed */ n_SHEXP_STATE_META_MASK = n_SHEXP_STATE_META_VERTBAR | n_SHEXP_STATE_META_AMPERSAND | n_SHEXP_STATE_META_SEMICOLON, n_SHEXP_STATE_ERR_CONTROL = 1u<<16, /* \c notation with invalid arg. */ n_SHEXP_STATE_ERR_UNICODE = 1u<<17, /* Valid \[Uu] and !n_PSO_UNICODE */ n_SHEXP_STATE_ERR_NUMBER = 1u<<18, /* Bad number (\[UuXx]) */ n_SHEXP_STATE_ERR_IDENTIFIER = 1u<<19, /* Invalid identifier */ n_SHEXP_STATE_ERR_BADSUB = 1u<<20, /* Empty/bad ${}/[] substitution */ n_SHEXP_STATE_ERR_GROUPOPEN = 1u<<21, /* _QUOTEOPEN + no }/]/)/ 4 ${/[/( */ n_SHEXP_STATE_ERR_QUOTEOPEN = 1u<<22, /* Quote remains open at EOS */ n_SHEXP_STATE_ERR_MASK = su_BITENUM_MASK(16, 22) }; enum n_sigman_flags{ n_SIGMAN_NONE = 0, n_SIGMAN_HUP = 1<<0, n_SIGMAN_INT = 1<<1, n_SIGMAN_QUIT = 1<<2, n_SIGMAN_TERM = 1<<3, n_SIGMAN_PIPE = 1<<4, n_SIGMAN_IGN_HUP = 1<<5, n_SIGMAN_IGN_INT = 1<<6, n_SIGMAN_IGN_QUIT = 1<<7, n_SIGMAN_IGN_TERM = 1<<8, n_SIGMAN_ALL = 0xFF, /* Mostly for _leave() reraise flags */ n_SIGMAN_VIPSIGS = n_SIGMAN_HUP | n_SIGMAN_INT | n_SIGMAN_QUIT | n_SIGMAN_TERM, n_SIGMAN_NTTYOUT_PIPE = 1<<16, n_SIGMAN_VIPSIGS_NTTYOUT = n_SIGMAN_HUP | n_SIGMAN_INT | n_SIGMAN_QUIT | n_SIGMAN_TERM | n_SIGMAN_NTTYOUT_PIPE, n__SIGMAN_PING = 1<<17 }; enum n_str_trim_flags{ n_STR_TRIM_FRONT = 1u<<0, n_STR_TRIM_END = 1u<<1, n_STR_TRIM_BOTH = n_STR_TRIM_FRONT | n_STR_TRIM_END }; #ifdef mx_HAVE_TLS enum n_tls_verify_level{ n_TLS_VERIFY_IGNORE, n_TLS_VERIFY_WARN, n_TLS_VERIFY_ASK, n_TLS_VERIFY_STRICT }; #endif enum tdflags{ TD_NONE, /* no display conversion */ TD_ISPR = 1<<0, /* use isprint() checks */ TD_ICONV = 1<<1, /* use iconv() */ TD_DELCTRL = 1<<2, /* delete control characters */ /* NOTE: _TD_EOF and _TD_BUFCOPY may be ORd with enum conversion and * enum sendaction, and may thus NOT clash with their bit range! */ _TD_EOF = 1<<14, /* EOF seen, last round! */ _TD_BUFCOPY = 1<<15 /* Buffer may be constant, copy it */ }; enum n_visual_info_flags{ n_VISUAL_INFO_NONE, n_VISUAL_INFO_ONE_CHAR = 1u<<0, /* Step only one char, then return */ n_VISUAL_INFO_SKIP_ERRORS = 1u<<1, /* Treat via replacement, step byte */ n_VISUAL_INFO_WIDTH_QUERY = 1u<<2, /* Detect visual character widths */ /* Rest only with mx_HAVE_C90AMEND1, mutual with _ONE_CHAR */ n_VISUAL_INFO_WOUT_CREATE = 1u<<8, /* Use/create .vic_woudat */ n_VISUAL_INFO_WOUT_SALLOC = 1u<<9, /* ..autorec_alloc() it first */ /* Only visuals into .vic_woudat - implies _WIDTH_QUERY */ n_VISUAL_INFO_WOUT_PRINTABLE = 1u<<10, n__VISUAL_INFO_FLAGS = n_VISUAL_INFO_WOUT_CREATE | n_VISUAL_INFO_WOUT_SALLOC | n_VISUAL_INFO_WOUT_PRINTABLE }; enum n_program_option{ n_PO_D = 1u<<0, /* -d / *debug* */ n_PO_V = 1u<<1, /* -v / *verbose* */ n_PO_VV = 1u<<2, /* .. more verbosity */ n_PO_VVV = 1u<<3, /* .. most verbosity */ n_PO_EXISTONLY = 1u<<4, /* -e */ n_PO_HEADERSONLY = 1u<<5, /* -H */ n_PO_HEADERLIST = 1u<<6, /* -L */ n_PO_QUICKRUN_MASK = n_PO_EXISTONLY | n_PO_HEADERSONLY | n_PO_HEADERLIST, n_PO_E_FLAG = 1u<<7, /* -E / *skipemptybody* */ n_PO_F_FLAG = 1u<<8, /* -F */ n_PO_Mm_FLAG = 1u<<9, /* -M or -m (plus n_poption_arg_Mm) */ n_PO_R_FLAG = 1u<<10, /* -R */ n_PO_r_FLAG = 1u<<11, /* -r (plus n_poption_arg_r) */ n_PO_S_FLAG_TEMPORARY = 1u<<12, /* -S about to set a variable */ n_PO_t_FLAG = 1u<<13, /* -t */ n_PO_TILDE_FLAG = 1u<<14, /* -~ */ n_PO_BATCH_FLAG = 1u<<15, /* -# */ /* Some easy-access shortcut; the V bits must be contiguous! */ n_PO_V_MASK = n_PO_V | n_PO_VV | n_PO_VVV, n_PO_D_V = n_PO_D | n_PO_V_MASK, n_PO_D_VV = n_PO_D | n_PO_VV | n_PO_VVV, n_PO_D_VVV = n_PO_D | n_PO_VVV }; #define n_OBSOLETE(X) \ do{\ if(n_poption & n_PO_D_V)\ n_err("%s: %s\n", _("Obsoletion warning"), X);\ }while(0) #define n_OBSOLETE2(X,Y) \ do{\ if(n_poption & n_PO_D_V)\ n_err("%s: %s: %s\n", _("Obsoletion warning"), X, Y);\ }while(0) /* Program state bits which may regulary fluctuate */ enum n_program_state{ n_PS_ROOT = 1u<<30, /* Temporary "bypass any checks" bit */ #define n_PS_ROOT_BLOCK(ACT) \ do{\ boole a___reset___ = !(n_pstate & n_PS_ROOT);\ n_pstate |= n_PS_ROOT;\ ACT;\ if(a___reset___)\ n_pstate &= ~n_PS_ROOT;\ }while(0) /* XXX These are internal to the state machine and do not belong here, * XXX yet this was the easiest (accessible) approach */ n_PS_ERR_XIT = 1u<<0, /* Unless `ignerr' seen -> n_PSO_XIT */ n_PS_ERR_QUIT = 1u<<1, /* ..ditto: -> n_PSO_QUIT */ n_PS_ERR_EXIT_MASK = n_PS_ERR_XIT | n_PS_ERR_QUIT, n_PS_SOURCING = 1u<<2, /* During load() or `source' */ n_PS_ROBOT = 1u<<3, /* .. even more robotic */ n_PS_COMPOSE_MODE = 1u<<4, /* State machine recursed */ n_PS_COMPOSE_FORKHOOK = 1u<<5, /* A hook running in a subprocess */ n_PS_HOOK_NEWMAIL = 1u<<7, n_PS_HOOK = 1u<<8, n_PS_HOOK_MASK = n_PS_HOOK_NEWMAIL | n_PS_HOOK, n_PS_EDIT = 1u<<9, /* Current mailbox no "system mailbox" */ n_PS_SETFILE_OPENED = 1u<<10, /* (hack) setfile() opened a new box */ n_PS_SAW_COMMAND = 1u<<11, /* ..after mailbox switch */ n_PS_DID_PRINT_DOT = 1u<<12, /* Current message has been printed */ n_PS_SIGWINCH_PEND = 1u<<13, /* Need update of $COLUMNS/$LINES */ n_PS_PSTATE_PENDMASK = n_PS_SIGWINCH_PEND, /* pstate housekeeping needed */ n_PS_ARGLIST_MASK = su_BITENUM_MASK(14, 16), n_PS_ARGMOD_LOCAL = 1u<<14, /* "local" modifier TODO struct CmdCtx */ n_PS_ARGMOD_VPUT = 1u<<15, /* "vput" modifier TODO struct CmdCtx */ n_PS_ARGMOD_WYSH = 1u<<16, /* "wysh" modifier TODO struct CmdCtx */ n_PS_MSGLIST_GABBY = 1u<<14, /* n_getmsglist() saw something gabby */ n_PS_MSGLIST_DIRECT = 1u<<15, /* A msg was directly chosen by number */ n_PS_EXPAND_MULTIRESULT = 1u<<17, /* Last fexpand() with MULTIOK had .. */ n_PS_ERRORS_PROMPT = 1u<<18, /* New error to be reported in prompt */ /* Bad hacks */ n_PS_HEADER_NEEDED_MIME = 1u<<24, /* mime_write_tohdr() not ASCII clean */ n_PS_READLINE_NL = 1u<<25, /* readline_input()+ saw a \n */ n_PS_BASE64_STRIP_CR = 1u<<26 /* Go for text output, strip CR's */ }; /* Various states set once, and first time messages or initializers */ enum n_program_state_once{ /* We have four program states: (0) pre getopt() done, (_GETOPT) pre rcfile * loaded etc., (_CONFIG) only -X evaluation missing still, followed by * _STARTED when we are fully setup */ n_PSO_STARTED_GETOPT = 1u<<0, n_PSO_STARTED_CONFIG = 1u<<1, n_PSO_STARTED = 1u<<2, /* Exit request pending (quick) */ n_PSO_XIT = 1u<<3, n_PSO_QUIT = 1u<<4, n_PSO_EXIT_MASK = n_PSO_XIT | n_PSO_QUIT, /* Pre _STARTED */ /* 1u<<5, */ n_PSO_UNICODE = 1u<<6, n_PSO_ENC_MBSTATE = 1u<<7, n_PSO_SENDMODE = 1u<<9, n_PSO_INTERACTIVE = 1u<<10, n_PSO_TTYIN = 1u<<11, n_PSO_TTYOUT = 1u<<12, /* TODO should be TTYERR! */ n_PSO_TTYANY = n_PSO_TTYIN | n_PSO_TTYOUT, /* mx_tty_fp = TTY */ n_PSO_TTYERR = 1u<<13, /* "Later" */ n_PSO_t_FLAG_DONE = 1u<<15, n_PSO_ATTACH_QUOTE_NOTED = 1u<<16, n_PSO_ERRORS_NOTED = 1u<<17, n_PSO_LINE_EDITOR_INIT = 1u<<18, n_PSO_RANDOM_INIT = 1u<<19, n_PSO_TERMCAP_DISABLE = 1u<<20, n_PSO_TERMCAP_CA_MODE = 1u<<21, n_PSO_TERMCAP_FULLWIDTH = 1u<<22, /* !am or am+xn (right margin wrap) */ n_PSO_PS_DOTLOCK_NOTED = 1u<<23 }; /* {{{ A large enum with all the boolean and value options a.k.a their keys. * Only the constant keys are in here, to be looked up via ok_[bv]look(), * ok_[bv]set() and ok_[bv]clear(). * Variable properties are placed in {PROP=VALUE[:,PROP=VALUE:]} comments, * a {\} comment causes the next line to be read for (overlong) properties. * Notes: * - see the introductional source comments before changing *anything* in here! * - virt= implies rdonly,nodel * - import= implies env * - num and posnum are mutual exclusive * - most default VAL_ues come from in from build system via ./make.rc * (Keep in SYNC: nail.h:okeys, nail.rc, nail.1:"Initial settings") */ enum okeys { /* This is used for all macro(-local) variables etc., i.e., * [*@#]|[1-9][0-9]*, in order to have something with correct properties. * It is also used for the ${^.+} multiplexer */ ok_v___special_param, /* {nolopts=1,rdonly=1,nodel=1} */ /*__qm/__em aka ?/! should be num=1 but that more expensive than what now */ ok_v___qm, /* {name=?,nolopts=1,rdonly=1,nodel=1} */ ok_v___em, /* {name=!,nolopts=1,rdonly=1,nodel=1} */ ok_v_account, /* {nolopts=1,rdonly=1,nodel=1} */ ok_b_add_file_recipients, ok_v_agent_shell_lookup, /* {obsolete=1} */ ok_b_allnet, ok_b_append, /* *ask* is auto-mapped to *asksub* as imposed by standard! */ ok_b_ask, /* {vip=1} */ ok_b_askatend, ok_b_askattach, ok_b_askbcc, ok_b_askcc, ok_b_asksign, ok_b_asksend, /* {i3val=TRU1} */ ok_b_asksub, /* {i3val=TRU1} */ ok_v_attrlist, ok_v_autobcc, ok_v_autocc, ok_b_autocollapse, ok_b_autoprint, ok_b_autothread, /* {obsolete=1} */ ok_v_autosort, ok_b_bang, ok_b_batch_exit_on_error, /* {obsolete=1} */ ok_v_bind_timeout, /* {notempty=1,posnum=1} */ ok_b_bsdannounce, /* {obsolete=1} */ ok_b_bsdcompat, ok_b_bsdflags, ok_b_bsdheadline, ok_b_bsdmsgs, ok_b_bsdorder, ok_v_build_cc, /* {virt=VAL_BUILD_CC_ARRAY} */ ok_v_build_ld, /* {virt=VAL_BUILD_LD_ARRAY} */ ok_v_build_os, /* {virt=VAL_BUILD_OS} */ ok_v_build_rest, /* {virt=VAL_BUILD_REST_ARRAY} */ ok_v_COLUMNS, /* {notempty=1,posnum=1,env=1} */ /* Charset lowercase conversion handled via vip= */ ok_v_charset_7bit, /* {vip=1,notempty=1,defval=CHARSET_7BIT} */ /* But unused without mx_HAVE_ICONV, we use ok_vlook(CHARSET_8BIT_OKEY)! */ ok_v_charset_8bit, /* {vip=1,notempty=1,defval=CHARSET_8BIT} */ ok_v_charset_unknown_8bit, /* {vip=1} */ ok_v_cmd, ok_b_colour_disable, ok_b_colour_pager, ok_v_contact_mail, /* {virt=VAL_CONTACT_MAIL} */ ok_v_contact_web, /* {virt=VAL_CONTACT_WEB} */ ok_v_crt, /* {posnum=1} */ ok_v_customhdr, /* {vip=1} */ ok_v_DEAD, /* {notempty=1,env=1,defval=VAL_DEAD} */ ok_v_datefield, /* {i3val="%Y-%m-%d %H:%M"} */ ok_v_datefield_markout_older, /* {i3val="%Y-%m-%d"} */ ok_b_debug, /* {vip=1} */ ok_b_disposition_notification_send, ok_b_dot, ok_b_dotlock_disable, ok_b_dotlock_ignore_error, /* {obsolete=1} */ ok_v_EDITOR, /* {env=1,notempty=1,defval=VAL_EDITOR} */ ok_v_editalong, ok_b_editheaders, ok_b_emptystart, ok_v_encoding, /* {obsolete=1} */ ok_b_errexit, ok_v_escape, /* {defval=n_ESCAPE} */ ok_v_expandaddr, ok_v_expandaddr_domaincheck, /* {notempty=1} */ ok_v_expandargv, ok_v_features, /* {virt=VAL_FEATURES} */ ok_b_flipr, ok_v_folder, /* {vip=1} */ ok_v_folder_resolved, /* {rdonly=1,nodel=1} */ ok_v_folder_hook, ok_b_followup_to, ok_b_followup_to_add_cc, ok_v_followup_to_honour, ok_b_forward_as_attachment, ok_v_forward_inject_head, ok_v_forward_inject_tail, ok_v_from, /* {vip=1} */ ok_b_fullnames, ok_v_fwdheading, /* {obsolete=1} */ ok_v_HOME, /* {vip=1,nodel=1,notempty=1,import=1} */ ok_b_header, /* {i3val=TRU1} */ ok_v_headline, ok_v_headline_bidi, ok_b_headline_plain, ok_v_history_file, ok_b_history_gabby, ok_b_history_gabby_persist, ok_v_history_size, /* {notempty=1,posnum=1} */ ok_b_hold, ok_v_hostname, /* {vip=1} */ ok_b_idna_disable, ok_v_ifs, /* {vip=1,defval=" \t\n"} */ ok_v_ifs_ws, /* {vip=1,rdonly=1,nodel=1,i3val=" \t\n"} */ ok_b_ignore, ok_b_ignoreeof, ok_v_inbox, ok_v_indentprefix, /* {defval="\t"} */ ok_b_keep, ok_b_keep_content_length, ok_b_keepsave, ok_v_LANG, /* {vip=1,env=1,notempty=1} */ ok_v_LC_ALL, /* {name=LC_ALL,vip=1,env=1,notempty=1} */ ok_v_LC_CTYPE, /* {name=LC_CTYPE,vip=1,env=1,notempty=1} */ ok_v_LINES, /* {notempty=1,posnum=1,env=1} */ ok_v_LISTER, /* {env=1,notempty=1,defval=VAL_LISTER} */ ok_v_LOGNAME, /* {rdonly=1,import=1} */ ok_v_line_editor_cpl_word_breaks, /* {\ } */ /* {defval=n_LINE_EDITOR_CPL_WORD_BREAKS} */ ok_b_line_editor_disable, ok_b_line_editor_no_defaults, ok_v_log_prefix, /* {nodel=1,i3val=VAL_UAGENT ": "} */ ok_v_MAIL, /* {env=1} */ ok_v_MAILRC, /* {import=1,notempty=1,defval=VAL_MAILRC} */ ok_b_MAILX_NO_SYSTEM_RC, /* {name=MAILX_NO_SYSTEM_RC,import=1} */ ok_v_MBOX, /* {env=1,notempty=1,defval=VAL_MBOX} */ ok_v_mailbox_resolved, /* {nolopts=1,rdonly=1,nodel=1} */ ok_v_mailbox_display, /* {nolopts=1,rdonly=1,nodel=1} */ ok_v_mailx_extra_rc, ok_b_markanswered, ok_b_mbox_fcc_and_pcc, /* {i3val=1} */ ok_b_mbox_rfc4155, ok_b_memdebug, /* {vip=1} */ ok_b_message_id_disable, ok_v_message_inject_head, ok_v_message_inject_tail, ok_b_metoo, ok_b_mime_allow_text_controls, ok_b_mime_alternative_favour_rich, ok_v_mime_counter_evidence, /* {posnum=1} */ ok_v_mime_encoding, ok_b_mime_force_sendout, ok_v_mimetypes_load_control, ok_v_mta, /* {notempty=1,defval=VAL_MTA} */ ok_v_mta_aliases, /* {notempty=1} */ ok_v_mta_arguments, ok_b_mta_no_default_arguments, ok_b_mta_no_receiver_arguments, ok_v_mta_argv0, /* {notempty=1,defval=VAL_MTA_ARGV0} */ /* TODO drop all those _v_mailx which are now accessible via `digmsg'! * TODO Documentation yet removed, n_temporary_compose_hook_varset() not */ ok_v_mailx_command, /* {rdonly=1,nodel=1} */ ok_v_mailx_subject, /* {rdonly=1,nodel=1} */ ok_v_mailx_from, /* {rdonly=1,nodel=1} */ ok_v_mailx_sender, /* {rdonly=1,nodel=1} */ ok_v_mailx_to, /* {rdonly=1,nodel=1} */ ok_v_mailx_cc, /* {rdonly=1,nodel=1} */ ok_v_mailx_bcc, /* {rdonly=1,nodel=1} */ ok_v_mailx_raw_to, /* {rdonly=1,nodel=1} */ ok_v_mailx_raw_cc, /* {rdonly=1,nodel=1} */ ok_v_mailx_raw_bcc, /* {rdonly=1,nodel=1} */ ok_v_mailx_orig_from, /* {rdonly=1,nodel=1} */ ok_v_mailx_orig_to, /* {rdonly=1,nodel=1} */ ok_v_mailx_orig_cc, /* {rdonly=1,nodel=1} */ ok_v_mailx_orig_bcc, /* {rdonly=1,nodel=1} */ ok_v_NAIL_EXTRA_RC, /* {name=NAIL_EXTRA_RC,obsolete=1} */ ok_b_NAIL_NO_SYSTEM_RC, /* {name=NAIL_NO_SYSTEM_RC,import=1,obsolete=1} */ ok_v_NAIL_HEAD, /* {name=NAIL_HEAD,obsolete=1} */ ok_v_NAIL_HISTFILE, /* {name=NAIL_HISTFILE,obsolete=1} */ ok_v_NAIL_HISTSIZE, /* {name=NAIL_HISTSIZE,notempty=1,num=1,obsolete=1} */ ok_v_NAIL_TAIL, /* {name=NAIL_TAIL,obsolete=1} */ ok_v_NETRC, /* {env=1,notempty=1,defval=VAL_NETRC} */ ok_b_netrc_lookup, /* {chain=1} */ ok_v_netrc_pipe, ok_v_newfolders, ok_v_newmail, ok_v_on_account_cleanup, /* {notempty=1} */ ok_v_on_compose_cleanup, /* {notempty=1} */ ok_v_on_compose_enter, /* {notempty=1} */ ok_v_on_compose_leave, /* {notempty=1} */ ok_v_on_compose_splice, /* {notempty=1} */ ok_v_on_compose_splice_shell, /* {notempty=1} */ ok_v_on_history_addition, /* {notempty=1} */ ok_v_on_main_loop_tick, /* {notempty=1} */ ok_v_on_program_exit, /* {notempty=1} */ ok_v_on_resend_cleanup, /* {notempty=1} */ ok_v_on_resend_enter, /* {notempty=1} */ ok_b_outfolder, ok_v_PAGER, /* {env=1,notempty=1,defval=VAL_PAGER} */ ok_v_PATH, /* {nodel=1,import=1} */ ok_b_POSIXLY_CORRECT, /* {vip=1,import=1,name=POSIXLY_CORRECT} */ ok_b_page, ok_v_password, /* {chain=1} */ ok_b_piperaw, ok_v_pop3_auth, /* {chain=1} */ ok_b_pop3_bulk_load, ok_v_pop3_keepalive, /* {notempty=1,posnum=1} */ ok_b_pop3_no_apop, /* {chain=1} */ ok_b_pop3_use_starttls, /* {chain=1} */ ok_b_posix, /* {vip=1} */ ok_b_print_alternatives, ok_v_prompt, /* {i3val="? "} */ ok_v_prompt2, /* {i3val=".. "} */ ok_b_quiet, ok_v_quote, ok_b_quote_as_attachment, ok_v_quote_chars, /* {vip=1,notempty=1,defval=">|}:"} */ ok_v_quote_fold, ok_v_quote_inject_head, ok_v_quote_inject_tail, ok_b_r_option_implicit, ok_b_recipients_in_cc, ok_v_record, ok_b_record_files, ok_b_record_resent, ok_b_reply_in_same_charset, ok_v_reply_strings, ok_v_replyto, /* {obsolete=1} */ ok_v_reply_to, /* {notempty=1} */ ok_v_reply_to_honour, ok_b_rfc822_body_from_, /* {name=rfc822-body-from_} */ ok_v_SHELL, /* {import=1,notempty=1,defval=VAL_SHELL} */ ok_b_SYSV3, /* {env=1,obsolete=1} */ ok_b_save, /* {i3val=TRU1} */ ok_v_screen, /* {notempty=1,posnum=1} */ ok_b_searchheaders, /* Charset lowercase conversion handled via vip= */ ok_v_sendcharsets, /* {vip=1} */ ok_b_sendcharsets_else_ttycharset, ok_v_sender, /* {vip=1} */ ok_v_sendmail, /* {obsolete=1} */ ok_v_sendmail_arguments, /* {obsolete=1} */ ok_b_sendmail_no_default_arguments, /* {obsolete=1} */ ok_v_sendmail_progname, /* {obsolete=1} */ ok_v_sendwait, /* {i3val=""} */ ok_b_showlast, ok_b_showname, ok_b_showto, ok_v_Sign, ok_v_sign, ok_v_signature, /* {obsolete=1} */ ok_b_skipemptybody, /* {vip=1} */ ok_v_smime_ca_dir, ok_v_smime_ca_file, ok_v_smime_ca_flags, ok_b_smime_ca_no_defaults, ok_v_smime_cipher, /* {chain=1} */ ok_v_smime_crl_dir, ok_v_smime_crl_file, ok_v_smime_encrypt, /* {chain=1} */ ok_b_smime_force_encryption, ok_b_smime_no_default_ca, /* {obsolete=1} */ ok_b_smime_sign, ok_v_smime_sign_cert, /* {chain=1} */ ok_v_smime_sign_digest, /* {chain=1} */ ok_v_smime_sign_include_certs, /* {chain=1} */ ok_v_smime_sign_message_digest, /* {chain=1,obsolete=1} */ ok_v_smtp, /* {obsolete=1} */ ok_v_smtp_auth, /* {chain=1} */ ok_v_smtp_auth_password, /* {obsolete=1} */ ok_v_smtp_auth_user, /* {obsolete=1} */ ok_v_smtp_hostname, /* {vip=1} */ ok_b_smtp_use_starttls, /* {chain=1} */ ok_v_SOURCE_DATE_EPOCH, /* {\ } */ /* {name=SOURCE_DATE_EPOCH,rdonly=1,import=1,notempty=1,posnum=1} */ ok_v_socket_connect_timeout, /* {posnum=1} */ ok_v_socks_proxy, /* {chain=1,notempty=1} */ ok_v_spam_interface, ok_v_spam_maxsize, /* {notempty=1,posnum=1} */ ok_v_spamc_command, ok_v_spamc_arguments, ok_v_spamc_user, ok_v_spamfilter_ham, ok_v_spamfilter_noham, ok_v_spamfilter_nospam, ok_v_spamfilter_rate, ok_v_spamfilter_rate_scanscore, ok_v_spamfilter_spam, ok_v_ssl_ca_dir, /* {chain=1,obsolete=1} */ ok_v_ssl_ca_file, /* {chain=1,obsolete=1} */ ok_v_ssl_ca_flags, /* {chain=1,obsolete=1} */ ok_b_ssl_ca_no_defaults, /* {chain=1,obsolete=1} */ ok_v_ssl_cert, /* {chain=1,obsolete=1} */ ok_v_ssl_cipher_list, /* {chain=1,obsolete=1} */ ok_v_ssl_config_file, /* {obsolete=1} */ ok_v_ssl_config_module, /* {chain=1,obsolete=1} */ ok_v_ssl_config_pairs, /* {chain=1,obsolete=1} */ ok_v_ssl_curves, /* {chain=1,obsolete=1} */ ok_v_ssl_crl_dir, /* {obsolete=1} */ ok_v_ssl_crl_file, /* {obsolete=1} */ ok_v_ssl_features, /* {virt=VAL_TLS_FEATURES,obsolete=1} */ ok_v_ssl_key, /* {chain=1,obsolete=1} */ ok_v_ssl_method, /* {chain=1,obsolete=1} */ ok_b_ssl_no_default_ca, /* {obsolete=1} */ ok_v_ssl_protocol, /* {chain=1,obsolete=1} */ ok_v_ssl_rand_egd, /* {obsolete=1} */ ok_v_ssl_rand_file, /* {obsolete=1}*/ ok_v_ssl_verify, /* {chain=1,obsolete=1} */ ok_v_stealthmua, ok_v_system_mailrc, /* {virt=VAL_SYSCONFDIR "/" VAL_SYSCONFRC} */ ok_v_TERM, /* {env=1} */ ok_v_TMPDIR, /* {import=1,vip=1,notempty=1,defval=VAL_TMPDIR} */ ok_v_termcap, ok_b_termcap_ca_mode, ok_b_termcap_disable, ok_v_tls_ca_dir, /* {chain=1} */ ok_v_tls_ca_file, /* {chain=1} */ ok_v_tls_ca_flags, /* {chain=1} */ ok_b_tls_ca_no_defaults, /* {chain=1} */ ok_v_tls_config_file, ok_v_tls_config_module, /* {chain=1} */ ok_v_tls_config_pairs, /* {chain=1} */ ok_v_tls_crl_dir, ok_v_tls_crl_file, ok_v_tls_features, /* {virt=VAL_TLS_FEATURES} */ ok_v_tls_fingerprint, /* {chain=1} */ ok_v_tls_fingerprint_digest, /* {chain=1} */ ok_v_tls_rand_file, ok_v_tls_verify, /* {chain=1} */ ok_v_toplines, /* {notempty=1,num=1,defval="5"} */ ok_b_topsqueeze, /* Charset lowercase conversion handled via vip= */ ok_v_ttycharset, /* {vip=1,notempty=1,defval=CHARSET_8BIT} */ ok_b_typescript_mode, /* {vip=1} */ ok_v_USER, /* {rdonly=1,import=1} */ ok_v_umask, /* {vip=1,nodel=1,posnum=1,i3val="0077"} */ ok_v_user, /* {notempty=1,chain=1} */ ok_v_VISUAL, /* {env=1,notempty=1,defval=VAL_VISUAL} */ ok_v_v15_compat, ok_b_verbose, /* {vip=1} */ ok_v_version, /* {virt=n_VERSION} */ ok_v_version_date, /* {virt=n_VERSION_DATE} */ ok_v_version_hexnum, /* {virt=n_VERSION_HEXNUM,posnum=1} */ ok_v_version_major, /* {virt=n_VERSION_MAJOR,posnum=1} */ ok_v_version_minor, /* {virt=n_VERSION_MINOR,posnum=1} */ ok_v_version_update, /* {virt=n_VERSION_UPDATE,posnum=1} */ ok_b_writebackedited , /* Obsolete IMAP related non-sorted */ ok_b_disconnected, /* {chain=1} */ ok_v_imap_auth, /* {chain=1} */ ok_v_imap_cache, ok_v_imap_delim, /* {chain=1} */ ok_v_imap_keepalive, /* {chain=1} */ ok_v_imap_list_depth, ok_b_imap_use_starttls /* {chain=1} */ }; /* }}} */ enum {n_OKEYS_MAX = ok_b_imap_use_starttls}; /* Forwards */ struct mx_name; /* -> names.h */ struct str{ char *s; /* the string's content */ uz l; /* the stings's length */ }; struct n_string{ char *s_dat; /*@ May contain NULs, not automatically terminated */ u32 s_len; /*@ gth of string */ u32 s_auto : 1; /* Stored in auto-reclaimed storage? */ u32 s_size : 31; /* of .s_dat, -1 */ }; struct n_strlist{ struct n_strlist *sl_next; uz sl_len; char sl_dat[VFIELD_SIZE(0)]; }; #define n_STRLIST_ALLOC(SZ) /* XXX -> nailfuns.h (and pimp interface) */\ n_alloc(VSTRUCT_SIZEOF(struct n_strlist, sl_dat) + (SZ) +1) #define n_STRLIST_AUTO_ALLOC(SZ) \ n_autorec_alloc(VSTRUCT_SIZEOF(struct n_strlist, sl_dat) + (SZ) +1) #define n_STRLIST_LOFI_ALLOC(SZ) \ n_lofi_alloc(VSTRUCT_SIZEOF(struct n_strlist, sl_dat) + (SZ) +1) struct n_cmd_arg_desc{ char cad_name[12]; /* Name of command */ u32 cad_no; /* Number of entries in cad_ent_flags */ /* [enum n_cmd_arg_desc_flags,arg-dep] */ u32 cad_ent_flags[VFIELD_SIZE(0)][2]; }; /* ISO C(99) doesn't allow initialization of "flex array" */ #define n_CMD_ARG_DESC_SUBCLASS_DEF(CMD,NO,VAR) \ static struct n_cmd_arg_desc_ ## CMD {\ char cad_name[12];\ u32 cad_no;\ u32 cad_ent_flags[NO][2];\ } const VAR = { #CMD "\0", NO, #define n_CMD_ARG_DESC_SUBCLASS_DEF_END } #define n_CMD_ARG_DESC_SUBCLASS_CAST(P) ((struct n_cmd_arg_desc const*)P) struct n_cmd_arg_ctx{ struct n_cmd_arg_desc const *cac_desc; /* Input: description of command */ char const *cac_indat; /* Input that shall be parsed */ uz cac_inlen; /* Input length (UZ_MAX: do a su_cs_len()) */ u32 cac_msgflag; /* Input (option): required flags of messages */ u32 cac_msgmask; /* Input (option): relevant flags of messages */ uz cac_no; /* Output: number of parsed arguments */ struct n_cmd_arg *cac_arg; /* Output: parsed arguments */ char const *cac_vput; /* "Output": vput prefix used: varname */ }; struct n_cmd_arg{ struct n_cmd_arg *ca_next; char const *ca_indat; /*[PRIV] Pointer into n_cmd_arg_ctx.cac_indat */ uz ca_inlen; /*[PRIV] of .ca_indat of this arg (no NUL) */ u32 ca_ent_flags[2]; /* Copy of n_cmd_arg_desc.cad_ent_flags[X] */ u32 ca_arg_flags; /* [Output: _WYSH: copy of parse result flags] */ u8 ca__dummy[4]; union{ struct str ca_str; /* _CMD_ARG_DESC_SHEXP */ int *ca_msglist; /* _CMD_ARG_DESC_MSGLIST+ */ } ca_arg; /* Output: parsed result */ }; struct n_cmd_desc{ char const *cd_name; /* Name of command */ int (*cd_func)(void*); /* Implementor of command */ enum n_cmd_arg_flags cd_caflags; u32 cd_msgflag; /* Required flags of msgs */ u32 cd_msgmask; /* Relevant flags of msgs */ /* XXX requires cmd-tab.h initializer changes u8 cd__pad[4];*/ struct n_cmd_arg_desc const *cd_cadp; #ifdef mx_HAVE_DOCSTRINGS char const *cd_doc; /* One line doc for command */ #endif }; /* Yechh, can't initialize unions */ #define cd_minargs cd_msgflag /* Minimum argcount for WYSH/WYRA/RAWLIST */ #define cd_maxargs cd_msgmask /* Max argcount for WYSH/WYRA/RAWLIST */ struct n_go_data_ctx{ struct su_mem_bag *gdc_membag; void *gdc_ifcond; /* Saved state of conditional stack */ #ifdef mx_HAVE_COLOUR struct mx_colour_env *gdc_colour; boole gdc_colour_active; u8 gdc__colour_pad[7]; # define mx_COLOUR_IS_ACTIVE() \ (/*n_go_data->gc_data.gdc_colour != su_NIL &&*/\ /*n_go_data->gc_data.gdc_colour->ce_enabled*/ n_go_data->gdc_colour_active) #endif struct su_mem_bag gdc__membag_buf[1]; }; struct mime_handler{ enum mime_handler_flags mh_flags; struct str mh_msg; /* Message describing this command */ /* XXX union{} the following? */ char const *mh_shell_cmd; /* For MIME_HDL_CMD */ int (*mh_ptf)(void); /* PTF main() for MIME_HDL_PTF */ }; struct search_expr{ /* XXX Type of search should not be evaluated but be enum */ boole ss_field_exists; /* Only check whether field spec. exists */ boole ss_skin; /* Shall work on (skin()ned) addresses */ u8 ss__pad[6]; char const *ss_field; /* Field spec. where to search (not always used) */ char const *ss_body; /* Field body search expression */ #ifdef mx_HAVE_REGEX regex_t *ss_fieldre; /* Could be instead of .ss_field */ regex_t *ss_bodyre; /* Ditto, .ss_body */ regex_t ss__fieldre_buf; regex_t ss__bodyre_buf; #endif }; /* This is somewhat temporary for pre v15 */ struct n_sigman{ u32 sm_flags; /* enum n_sigman_flags */ int sm_signo; struct n_sigman *sm_outer; n_sighdl_t sm_ohup; n_sighdl_t sm_oint; n_sighdl_t sm_oquit; n_sighdl_t sm_oterm; n_sighdl_t sm_opipe; sigjmp_buf sm_jump; }; struct n_timespec{ s64 ts_sec; sz ts_nsec; }; struct time_current{ /* TODO s64, etc. */ time_t tc_time; struct tm tc_gm; struct tm tc_local; char tc_ctime[32]; }; struct mailbox{ enum{ MB_NONE = 000, /* no reply expected */ MB_COMD = 001, /* command reply expected */ MB_MULT = 002, /* multiline reply expected */ MB_PREAUTH = 004, /* not in authenticated state */ MB_BYE = 010, /* may accept a BYE state */ MB_BAD_FROM_ = 1<<4 /* MBOX with invalid From_ seen & logged */ } mb_active; FILE *mb_itf; /* temp file with messages, read open */ FILE *mb_otf; /* same, write open */ char *mb_sorted; /* sort method */ enum{ MB_VOID, /* no type (e. g. connection failed) */ MB_FILE, /* local file */ MB_POP3, /* POP3 mailbox */ MB_IMAP, /* IMAP mailbox */ MB_CACHE, /* IMAP cache */ MB_MAILDIR /* maildir folder */ } mb_type; /* type of mailbox */ enum{ MB_DELE = 01, /* may delete messages in mailbox */ MB_EDIT = 02 /* may edit messages in mailbox */ } mb_perm; int mb_threaded; /* mailbox has been threaded */ #ifdef mx_HAVE_IMAP enum mbflags{ MB_NOFLAGS = 000, MB_UIDPLUS = 001, /* supports IMAP UIDPLUS */ MB_SASL_IR = 002 /* supports RFC 4959 SASL-IR */ } mb_flags; u64 mb_uidvalidity; /* IMAP unique identifier validity */ char *mb_imap_account; /* name of current IMAP account */ char *mb_imap_pass; /* xxx v15-compat URL workaround */ char *mb_imap_mailbox; /* name of current IMAP mailbox */ char *mb_cache_directory; /* name of cache directory */ char mb_imap_delim[8]; /* Directory separator(s), [0] += replacer */ #endif /* XXX mailbox.mb_accmsg is a hack in so far as the mailbox object should * XXX have an on_close event to which that machinery should connect */ struct mx_dig_msg_ctx *mb_digmsg; /* Open `digmsg' connections */ struct mx_socket *mb_sock; /* socket structure */ }; enum needspec{ NEED_UNSPEC, /* unspecified need, don't fetch */ NEED_HEADER, /* need the header of a message */ NEED_BODY /* need header and body of a message */ }; enum content_info{ CI_NOTHING, /* Nothing downloaded yet */ CI_HAVE_HEADER = 1u<<0, /* Header is downloaded */ CI_HAVE_BODY = 1u<<1, /* Entire message is downloaded */ CI_HAVE_MASK = CI_HAVE_HEADER | CI_HAVE_BODY, CI_MIME_ERRORS = 1u<<2, /* Defective MIME structure */ CI_EXPANDED = 1u<<3, /* Container part (pk7m) exploded into X */ CI_SIGNED = 1u<<4, /* Has a signature.. */ CI_SIGNED_OK = 1u<<5, /* ..verified ok.. */ CI_SIGNED_BAD = 1u<<6, /* ..verified bad (missing key).. */ CI_ENCRYPTED = 1u<<7, /* Is encrypted.. */ CI_ENCRYPTED_OK = 1u<<8, /* ..decryption possible/ok.. */ CI_ENCRYPTED_BAD = 1u<<9 /* ..not possible/ok */ }; /* Note: flags that are used in obs-imap-cache.c may not change */ enum mflag{ MUSED = 1u<<0, /* entry is used, but this bit isn't */ MDELETED = 1u<<1, /* entry has been deleted */ MSAVED = 1u<<2, /* entry has been saved */ MTOUCH = 1u<<3, /* entry has been noticed */ MPRESERVE = 1u<<4, /* keep entry in sys mailbox */ MMARK = 1u<<5, /* message is marked! */ MODIFY = 1u<<6, /* message has been modified */ MNEW = 1u<<7, /* message has never been seen */ MREAD = 1u<<8, /* message has been read sometime. */ MSTATUS = 1u<<9, /* message status has changed */ MBOX = 1u<<10, /* Send this to mbox, regardless */ MNOFROM = 1u<<11, /* no From line */ MHIDDEN = 1u<<12, /* message is hidden to user */ MFULLYCACHED = 1u<<13, /* IMAP cached */ MBOXED = 1u<<14, /* message has been sent to mbox */ MUNLINKED = 1u<<15, /* Unlinked from IMAP cache */ MNEWEST = 1u<<16, /* message is very new (newmail) */ MFLAG = 1u<<17, /* message has been flagged recently */ MUNFLAG = 1u<<18, /* message has been unflagged */ MFLAGGED = 1u<<19, /* message is `flagged' */ MANSWER = 1u<<20, /* message has been answered recently */ MUNANSWER = 1u<<21, /* message has been unanswered */ MANSWERED = 1u<<22, /* message is `answered' */ MDRAFT = 1u<<23, /* message has been drafted recently */ MUNDRAFT = 1u<<24, /* message has been undrafted */ MDRAFTED = 1u<<25, /* message is marked as `draft' */ MOLDMARK = 1u<<26, /* messages was marked previously */ MSPAM = 1u<<27, /* message is classified as spam */ MSPAMUNSURE = 1u<<28, /* message may be spam, but it is unsure */ /* The following are hacks in so far as they let imagine what the future * will bring, without doing this already today */ MBADFROM_ = 1u<<29, /* From_ line must be replaced */ MDISPLAY = 1u<<30 /* Display content of this part */ }; #define MMNORM (MDELETED | MSAVED | MHIDDEN) #define MMNDEL (MDELETED | MHIDDEN) #define visible(mp) (((mp)->m_flag & MMNDEL) == 0) struct mimepart{ enum mflag m_flag; enum content_info m_content_info; #ifdef mx_HAVE_SPAM u32 m_spamscore; /* Spam score as int, 24:8 bits */ #else u8 m__pad1[4]; #endif int m_block; /* Block number of this part */ uz m_offset; /* Offset in block of part */ uz m_size; /* Bytes in the part */ uz m_xsize; /* Bytes in the full part */ long m_lines; /* Lines in the message; wire format! */ long m_xlines; /* Lines in the full message; ditto */ time_t m_time; /* Time the message was sent */ char const *m_from; /* Message sender */ struct mimepart *m_nextpart; /* Next part at same level */ struct mimepart *m_multipart; /* Parts of multipart */ struct mimepart *m_parent; /* Enclosing multipart part */ char const *m_ct_type; /* Content-type */ char const *m_ct_type_plain; /* Content-type without specs */ char const *m_ct_type_usr_ovwr; /* Forcefully overwritten one */ char const *m_charset; char const *m_ct_enc; /* Content-Transfer-Encoding */ enum mimecontent m_mimecontent; /* ..in enum */ enum mime_enc m_mime_enc; /* ..in enum */ char *m_partstring; /* Part level string */ char *m_filename; /* ..of attachment */ char const *m_content_description; char const *m_external_body_url; /* message/external-body:access-type=URL */ struct mime_handler *m_handler; /* MIME handler if yet classified */ }; struct message{ enum mflag m_flag; /* flags */ enum content_info m_content_info; #ifdef mx_HAVE_SPAM u32 m_spamscore; /* Spam score as int, 24:8 bits */ #else u8 m__pad1[4]; #endif int m_block; /* block number of this message */ uz m_offset; /* offset in block of message */ uz m_size; /* Bytes in the message */ uz m_xsize; /* Bytes in the full message */ long m_lines; /* Lines in the message */ long m_xlines; /* Lines in the full message */ time_t m_time; /* time the message was sent */ time_t m_date; /* time in the 'Date' field */ #ifdef mx_HAVE_IMAP u64 m_uid; /* IMAP unique identifier */ #endif #ifdef mx_HAVE_MAILDIR char const *m_maildir_file; /* original maildir file of msg */ u32 m_maildir_hash; /* hash of file name in maildir sub */ #endif int m_collapsed; /* collapsed thread information */ unsigned m_idhash; /* hash on Message-ID for threads */ unsigned m_level; /* thread level of message */ long m_threadpos; /* position in threaded display */ struct message *m_child; /* first child of this message */ struct message *m_younger; /* younger brother of this message */ struct message *m_elder; /* elder brother of this message */ struct message *m_parent; /* parent of this message */ }; /* Given a file address, determine the block number it represents */ #define mailx_blockof(off) S(int,(off) / 4096) #define mailx_offsetof(off) S(int,(off) % 4096) #define mailx_positionof(block, offset) (S(off_t,block) * 4096 + (offset)) enum gfield{ /* TODO -> enum m_grab_head, m_GH_xy */ GNONE, GTO = 1u<<0, /* Grab To: line */ GSUBJECT = 1u<<1, /* Likewise, Subject: line */ GCC = 1u<<2, /* And the Cc: line */ GBCC = 1u<<3, /* And also the Bcc: line */ GNL = 1u<<4, /* Print blank line after */ GDEL = 1u<<5, /* Entity removed from list */ GCOMMA = 1u<<6, /* detract() puts in commas */ GUA = 1u<<7, /* User-Agent field */ GMIME = 1u<<8, /* MIME 1.0 fields */ GMSGID = 1u<<9, /* a Message-ID */ GNAMEONLY = 1u<<10, /* detract() does NOT use fullnames */ GIDENT = 1u<<11, /* From:, Reply-To:, MFT: (user headers) */ GREF = 1u<<12, /* References:, In-Reply-To:, (Message-ID:) */ GREF_IRT = 1u<<30, /* XXX Hack; only In-Reply-To: -> n_run_editor() */ GDATE = 1u<<13, /* Date: field */ GFULL = 1u<<14, /* Include full names, comments etc. */ GSKIN = 1u<<15, /* Skin names */ GEXTRA = 1u<<16, /* Extra fields (mostly like GIDENT XXX) */ GFILES = 1u<<17, /* Include filename and pipe addresses */ GFULLEXTRA = 1u<<18, /* Only with GFULL: GFULL less address */ GBCC_IS_FCC = 1u<<19, /* This GBCC is (or was) indeed a Fcc: */ GSHEXP_PARSE_HACK = 1u<<20, /* lextract()+: *expandaddr*=shquote */ /* All given input (nalloc() etc.) to be interpreted as a single address */ GNOT_A_LIST = 1u<<21, GNULL_OK = 1u<<22, /* NULL return OK for nalloc()+ */ GMAILTO_URI = 1u<<23, /* RFC 6068-style */ /* HACK: support "|bla", i.e., anything enclosed in quotes; e.g., used for * MTA alias parsing */ GQUOTE_ENCLOSED_OK = 1u<<24 }; #define GMASK (GTO | GSUBJECT | GCC | GBCC) enum header_flags{ HF_NONE = 0, HF_LIST_REPLY = 1u<<0, HF_MFT_SENDER = 1u<<1, /* Add ourselves to Mail-Followup-To: */ HF_RECIPIENT_RECORD = 1u<<10, /* Save message in file named after rec. */ HF__NEXT_SHIFT = 11u }; /* Structure used to pass about the current state of a message (header) */ struct n_header_field{ struct n_header_field *hf_next; u32 hf_nl; /* Field-name length */ u32 hf_bl; /* Field-body length*/ char hf_dat[VFIELD_SIZE(0)]; }; struct header{ u32 h_flags; /* enum header_flags bits */ u32 h_dummy; char *h_subject; /* Subject string */ char const *h_charset; /* preferred charset */ struct mx_name *h_from; /* overridden "From:" field */ struct mx_name *h_sender; /* overridden "Sender:" field */ struct mx_name *h_to; /* Dynamic "To:" string */ struct mx_name *h_cc; /* Carbon copies string */ struct mx_name *h_bcc; /* Blind carbon copies */ struct mx_name *h_fcc; /* Fcc: file carbon copies to */ struct mx_name *h_ref; /* References (possibly overridden) */ struct attachment *h_attach; /* MIME attachments */ struct mx_name *h_reply_to; /* overridden "Reply-To:" field */ struct mx_name *h_message_id; /* overridden "Message-ID:" field */ struct mx_name *h_in_reply_to;/* overridden "In-Reply-To:" field */ struct mx_name *h_mft; /* Mail-Followup-To */ char const *h_list_post; /* Address from List-Post:, for `Lreply' */ struct n_header_field *h_user_headers; struct n_header_field *h_custom_headers; /* (Cached result) */ /* Raw/original versions of the header(s). If any */ char const *h_mailx_command; struct mx_name *h_mailx_raw_to; struct mx_name *h_mailx_raw_cc; struct mx_name *h_mailx_raw_bcc; struct mx_name *h_mailx_orig_from; struct mx_name *h_mailx_orig_to; struct mx_name *h_mailx_orig_cc; struct mx_name *h_mailx_orig_bcc; }; struct n_addrguts{ /* Input string as given (maybe replaced with a fixed one!) */ char const *ag_input; uz ag_ilen; /* su_cs_len() of input */ uz ag_iaddr_start; /* Start of *addr-spec* in .ag_input */ uz ag_iaddr_aend; /* ..and one past its end */ char *ag_skinned; /* Output (alloced if !=.ag_input) */ uz ag_slen; /* su_cs_len() of .ag_skinned */ uz ag_sdom_start; /* Start of domain in .ag_skinned, */ u32 ag_n_flags; /* enum mx_name_flags of .ag_skinned */ }; /* MIME attachments */ enum attach_conv{ AC_DEFAULT, /* _get_lc() -> _iter_*() */ AC_FIX_INCS, /* "charset=".a_input_charset (nocnv) */ AC_TMPFILE /* attachment.a_tmpf is converted */ }; enum n_attach_error{ n_ATTACH_ERR_NONE, n_ATTACH_ERR_FILE_OPEN, n_ATTACH_ERR_ICONV_FAILED, n_ATTACH_ERR_ICONV_NAVAIL, n_ATTACH_ERR_OTHER }; struct attachment{ struct attachment *a_flink; /* Forward link in list. */ struct attachment *a_blink; /* Backward list link */ char const *a_path_user; /* Path as given (maybe including iconv spec) */ char const *a_path; /* Path as opened */ char const *a_path_bname; /* Basename of path as opened */ char const *a_name; /* File name to be stored (EQ a_path_bname) */ char const *a_content_type; /* content type */ char const *a_content_disposition; /* content disposition */ struct mx_name *a_content_id; /* content id */ char const *a_content_description; /* content description */ char const *a_input_charset; /* Interpretation depends on .a_conv */ char const *a_charset; /* ... */ FILE *a_tmpf; /* If AC_TMPFILE */ enum attach_conv a_conv; /* User chosen conversion */ int a_msgno; /* message number */ }; struct sendbundle{ struct header *sb_hp; struct mx_name *sb_to; FILE *sb_input; struct mx_url *sb_urlp; /* Or NIL for file-based MTA */ struct mx_cred_ctx *sb_credp; /* cred-auth.h not included */ struct str sb_signer; /* USER@HOST for signing+ */ }; /* For saving the current directory and later returning */ struct cw{ #ifdef mx_HAVE_FCHDIR int cw_fd; #else char cw_wd[PATH_MAX]; #endif }; /* * Global variable declarations * * These become instantiated in main.c. */ #undef VL #ifdef mx_SOURCE_MASTER # ifndef mx_HAVE_AMALGAMATION # define VL # else # define VL static # endif #else # define VL extern #endif #define n_empty su_empty #ifndef mx_HAVE_AMALGAMATION VL char const n_month_names[12 + 1][4]; VL char const n_weekday_names[7 + 1][4]; VL char const n_uagent[sizeof VAL_UAGENT]; # ifdef mx_HAVE_UISTRINGS VL char const n_error[sizeof n_ERROR]; # endif VL char const n_path_devnull[sizeof n_PATH_DEVNULL]; VL char const n_0[2]; VL char const n_1[2]; VL char const n_m1[3]; /* -1 */ VL char const n_em[2]; /* Exclamation-mark ! */ VL char const n_ns[2]; /* Number sign # */ VL char const n_star[2]; /* Asterisk * */ VL char const n_hy[2]; /* Hyphen-Minus - */ VL char const n_qm[2]; /* Question-mark ? */ VL char const n_at[2]; /* Commercial at @ */ #endif /* mx_HAVE_AMALGAMATION */ VL FILE *n_stdin; VL FILE *n_stdout; VL FILE *n_stderr; /* XXX Plus mx_tty_fp in tty.h */ /* XXX *_read_overlay and dig_msg_compose_ctx are hacks caused by missing * XXX event driven nature of individual program parts */ VL void *n_readctl_read_overlay; /* `readctl' XXX HACK */ VL u32 n_mb_cur_max; /* Value of MB_CUR_MAX */ VL gid_t n_group_id; /* getgid() and getuid() */ VL uid_t n_user_id; VL pid_t n_pid; /* getpid() (lazy initialized) */ VL int n_exit_status; /* Program exit status TODO long term: ex_no */ VL u32 n_poption; /* Bits of enum n_program_option */ VL struct n_header_field *n_poption_arg_C; /* -C custom header list */ VL char const *n_poption_arg_Mm; /* Argument for -[Mm] aka n_PO_[Mm]_FLAG */ VL struct mx_name *n_poption_arg_r; /* Argument to -r option */ VL char const **n_smopts; /* MTA options from command line */ VL uz n_smopts_cnt; /* Entries in n_smopts */ /* The current execution data context */ VL struct n_go_data_ctx *n_go_data; VL u32 n_psonce; /* Bits of enum n_program_state_once */ VL u32 n_pstate; /* Bits of enum n_program_state */ /* TODO "cmd_tab.h ARG_EM set"-storage (n_[01..]) as long as we don't have a * TODO struct CmdCtx where each command has its own ARGC/ARGV, errno and exit * TODO status and may-place-in-history bit, need to manage a global bypass.. */ #ifdef mx_HAVE_ERRORS VL s32 n_pstate_err_cnt; /* What backs $^ERRQUEUE-xy */ #endif /* TODO n_pstate_err_no: this should contain the error number in the lower * TODO bits, and a suberror in the high bits: offer accessor/setter macros. * TODO Like this we could use $^ERR-SUBNO or so to access these from outer * TODO space, and could perform much better testing; e.g., too many failures * TODO simply result in _INVAL, but what has it been exactly? * TODO This will furthermore allow better testing, in that even without * TODO uistrings we can test error conditions _exactly_! * TODO And change the tests accordingly, even support a mode where our * TODO error output is entirely suppressed, so that we _really_ can test * TODO and only based upon the subnumber!! */ VL s32 n_pstate_err_no; /* What backs $! su_ERR_* TODO ..HACK */ VL s32 n_pstate_ex_no; /* What backs $? n_EX_* TODO ..HACK ->64-bit */ /* XXX stylish sorting */ VL int msgCount; /* Count of messages read in */ VL struct mailbox mb; /* Current mailbox */ VL char mailname[PATH_MAX]; /* Name of current file TODO URL/object*/ VL char displayname[80 - 16]; /* Prettyfied for display TODO URL/obj*/ VL char prevfile[PATH_MAX]; /* Name of previous file TODO URL/obj */ VL off_t mailsize; /* Size of system mailbox */ VL struct message *dot; /* Pointer to current message */ VL struct message *prevdot; /* Previous current message */ VL struct message *message; /* The actual message structure */ VL struct message *threadroot; /* first threaded message */ VL int *n_msgvec; /* Folder setmsize(), list.c res. store*/ #ifdef mx_HAVE_IMAP VL int imap_created_mailbox; /* hack to get feedback from imap */ #endif VL struct n_header_field *n_customhdr_list; /* *customhdr* list */ VL struct time_current time_current; /* time(3); send: mail1() XXXcarrier */ #ifdef mx_HAVE_TLS VL enum n_tls_verify_level n_tls_verify_level; /* TODO local per-context! */ #endif VL volatile int interrupts; /* TODO rid! */ VL n_sighdl_t dflpipe; /* * Finally, let's include the function prototypes XXX embed */ #ifndef mx_SOURCE_PS_DOTLOCK_MAIN # include "mx/nailfuns.h" #endif #include "su/code-ou.h" #endif /* n_NAIL_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/nailfuns.h000066400000000000000000002140641352610246600171310ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Function prototypes and function-alike macros. *@ TODO Should be split in myriads of FEATURE-GROUP.h headers. * * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany. * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: BSD-3-Clause TODO ISC */ /* * Copyright (c) 1980, 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. */ struct su_cs_dict; struct quoteflt; /* * TODO Convert optional utility+ functions to n_*(); ditto * TODO else use generic module-specific prefixes: str_(), am[em]_, sm[em]_, .. */ /* TODO s-it-mode: not really (docu, funnames, funargs, etc) */ #undef FL #ifndef mx_HAVE_AMALGAMATION # define FL extern #else # define FL static #endif /* * Macro-based generics */ /* RFC 822, 3.2. */ #define fieldnamechar(c) \ (su_cs_is_ascii(c) && (c) > 040 && (c) != 0177 && (c) != ':') /* Could the string contain a regular expression? * NOTE: on change: manual contains several occurrences of this string! */ #define n_is_maybe_regex(S) n_is_maybe_regex_buf(S, su_UZ_MAX) #define n_is_maybe_regex_buf(D,L) \ (su_cs_first_of_cbuf_cbuf(D, L, "^[]*+?|$", su_UZ_MAX) != su_UZ_MAX) /* Single-threaded, use unlocked I/O */ #ifdef mx_HAVE_PUTC_UNLOCKED # undef getc # define getc(c) getc_unlocked(c) # undef putc # define putc(c, f) putc_unlocked(c, f) #endif /* There are problems with dup()ing of file-descriptors for child processes. * We have to somehow accomplish that the FILE* fp makes itself comfortable * with the *real* offset of the underlaying file descriptor. * POSIX Issue 7 overloaded fflush(3): if used on a readable stream, then * * if the file is not already at EOF, and the file is one capable of * seeking, the file offset of the underlying open file description shall * be set to the file position of the stream */ #if defined _POSIX_VERSION && _POSIX_VERSION + 0 >= 200809L # define n_real_seek(FP,OFF,WH) (fseek(FP, OFF, WH) != -1 && fflush(FP) != EOF) # define really_rewind(stream) \ do{\ rewind(stream);\ fflush(stream);\ }while(0) #else # define n_real_seek(FP,OFF,WH) \ (fseek(FP, OFF, WH) != -1 && fflush(FP) != EOF &&\ lseek(fileno(FP), OFF, WH) != -1) # define really_rewind(stream) \ do{\ rewind(stream);\ fflush(stream);\ lseek(fileno(stream), 0, SEEK_SET);\ }while(0) #endif /* fflush() and rewind() */ #define fflush_rewind(stream) \ do{\ fflush(stream);\ rewind(stream);\ }while(0) /* Truncate a file to the last character written. This is useful just before * closing an old file that was opened for read/write */ #define ftrunc(stream) \ do{\ off_t off;\ fflush(stream);\ off = ftell(stream);\ if(off >= 0)\ ftruncate(fileno(stream), off);\ }while(0) /* * accmacvar.c */ /* Macros: `define', `undefine', `call', `call_if' */ FL int c_define(void *v); FL int c_undefine(void *v); FL int c_call(void *v); FL int c_call_if(void *v); /* Accounts: `account', `unaccount' */ FL void mx_account_leave(void); FL int c_account(void *v); FL int c_unaccount(void *v); /* `localopts', `shift', `return' */ FL int c_localopts(void *vp); FL int c_shift(void *vp); FL int c_return(void *vp); /* TODO Main loop on tick event; mx_sigs_all_holdx() is active */ FL void temporary_on_xy_hook_caller(char const *hname, char const *mac, boole sigs_held); /* TODO Check whether a *folder-hook* exists for currently active mailbox */ FL boole temporary_folder_hook_check(boole nmail); FL void temporary_folder_hook_unroll(void); /* XXX im. hack */ /* TODO v15 drop Invoke compose hook macname */ FL void temporary_compose_mode_hook_call(char const *macname, void (*hook_pre)(void *), void *hook_arg); FL void temporary_compose_mode_hook_unroll(void); #ifdef mx_HAVE_HISTORY /* TODO *on-history-addition* */ FL boole temporary_addhist_hook(char const *ctx, boole gabby, char const *histent); #endif /* TODO v15 drop: let shexp_parse_token take a carrier with positional * TODO params, then let callers use that as such!! * Call hook in a recursed environment named name where positional params are * setup according to argv/argc. NOTE: all signals blocked! */ #ifdef mx_HAVE_REGEX FL char *temporary_pospar_access_hook(char const *name, char const **argv, u32 argc, char *(*hook)(void *uservp), void *uservp); #endif /* Setting up batch mode, variable-handling side */ FL void n_var_setup_batch_mode(void); /* Can name freely be used as a variable by users? */ FL boole n_var_is_user_writable(char const *name); /* Don't use n_var_* unless you *really* have to! */ /* Constant option key look/(un)set/clear */ FL char *n_var_oklook(enum okeys okey); #define ok_blook(C) (n_var_oklook(su_CONCAT(ok_b_, C)) != NULL) #define ok_vlook(C) n_var_oklook(su_CONCAT(ok_v_, C)) FL boole n_var_okset(enum okeys okey, up val); #define ok_bset(C) \ n_var_okset(su_CONCAT(ok_b_, C), (up)TRU1) #define ok_vset(C,V) \ n_var_okset(su_CONCAT(ok_v_, C), (up)(V)) FL boole n_var_okclear(enum okeys okey); #define ok_bclear(C) n_var_okclear(su_CONCAT(ok_b_, C)) #define ok_vclear(C) n_var_okclear(su_CONCAT(ok_v_, C)) /* Variable option key lookup/(un)set/clear. * If try_getenv is true we'll getenv(3) _if_ vokey is not a known enum okey. * _vexplode() is to be used by the shell expansion stuff when encountering * ${@} in double-quotes, in order to provide sh(1)ell compatible behaviour; * it returns whether there are any elements in argv (*cookie) */ FL char const *n_var_vlook(char const *vokey, boole try_getenv); FL boole n_var_vexplode(void const **cookie); FL boole n_var_vset(char const *vokey, up val); FL boole n_var_vclear(char const *vokey); /* Special case to handle the typical [xy-USER@HOST,] xy-HOST and plain xy * variable chains; oxm is a bitmix which tells which combinations to test */ #ifdef mx_HAVE_NET FL char *n_var_xoklook(enum okeys okey, struct mx_url const *urlp, enum okey_xlook_mode oxm); # define xok_BLOOK(C,URL,M) (n_var_xoklook(C, URL, M) != NULL) # define xok_VLOOK(C,URL,M) n_var_xoklook(C, URL, M) # define xok_blook(C,URL,M) xok_BLOOK(su_CONCAT(ok_b_, C), URL, M) # define xok_vlook(C,URL,M) xok_VLOOK(su_CONCAT(ok_v_, C), URL, M) #endif /* User variable access: `set', `local' and `unset' */ FL int c_set(void *vp); FL int c_unset(void *vp); /* `varshow' */ FL int c_varshow(void *v); /* Ditto: `varedit' */ FL int c_varedit(void *v); /* `environ' */ FL int c_environ(void *v); /* `vpospar' */ FL int c_vpospar(void *v); /* * attachment.c * xxx Interface quite sick */ /* Try to add an attachment for file, fexpand(_LOCAL|_NOPROTO)ed. * Return the new aplist aphead. * The newly created attachment may be stored in *newap, or NULL on error */ FL struct attachment *n_attachment_append(struct attachment *aplist, char const *file, enum n_attach_error *aerr_or_null, struct attachment **newap_or_null); /* Shell-token parse names, and append resulting file names to aplist, return * (new) aplist head */ FL struct attachment *n_attachment_append_list(struct attachment *aplist, char const *names); /* Remove ap from aplist, and return the new aplist head */ FL struct attachment *n_attachment_remove(struct attachment *aplist, struct attachment *ap); /* Find by file-name. If any path component exists in name then an exact match * of the creation-path is used directly; if instead the basename of that path * matches all attachments are traversed to find an exact match first, the * first of all basename matches is returned as a last resort; * If no path component exists the filename= parameter is searched (and also * returned) in preference over the basename, otherwise likewise. * If name is in fact a message number the first match is taken. * If stat_or_null is given: FAL0 on NULL return, TRU1 for exact/single match, * TRUM1 for ambiguous matches */ FL struct attachment *n_attachment_find(struct attachment *aplist, char const *name, boole *stat_or_null); /* Interactively edit the attachment list, return updated list */ FL struct attachment *n_attachment_list_edit(struct attachment *aplist, enum n_go_input_flags gif); /* Print all attachments to fp, return number of lines written, -1 on error */ FL sz n_attachment_list_print(struct attachment const *aplist, FILE *fp); /* * auxlily.c */ /* Compute *screen* size */ FL uz n_screensize(void); /* In n_PSO_INTERACTIVE, we want to go over $PAGER. * These are specialized version of fs_pipe_open()/fs_pipe_close() which also * encapsulate error message printing, terminal handling etc. additionally */ FL FILE *mx_pager_open(void); FL boole mx_pager_close(FILE *fp); /* Use a pager or STDOUT to print *fp*; if *lines* is 0, they'll be counted */ FL void page_or_print(FILE *fp, uz lines); /* Parse name and guess at the required protocol. * If check_stat is true then stat(2) will be consulted - a TODO c..p hack * TODO that together with *newfolders*=maildir adds Maildir support; sigh! * If try_hooks is set check_stat is implied and if the stat(2) fails all * file-hook will be tried in order to find a supported version of name. * If adjusted_or_null is not NULL it will be set to the final version of name * this function knew about: a %: FEDIT_SYSBOX prefix is forgotten, in case * a hook is needed the "real" filename will be placed. * TODO This c..p should be URL::from_string()->protocol() or something! */ FL enum protocol which_protocol(char const *name, boole check_stat, boole try_hooks, char const **adjusted_or_null); /* Hexadecimal itoa (NUL terminates) / atoi (-1 on error) */ FL char * n_c_to_hex_base16(char store[3], char c); FL s32 n_c_from_hex_base16(char const hex[2]); /* Return the name of the dead.letter file */ FL char const * n_getdeadletter(void); /* Detect and query the hostname to use */ FL char *n_nodename(boole mayoverride); /* Convert from / to *ttycharset* */ #ifdef mx_HAVE_IDNA FL boole n_idna_to_ascii(struct n_string *out, char const *ibuf, uz ilen); /*TODO FL boole n_idna_from_ascii(struct n_string *out, char const *ibuf, uz ilen);*/ #endif /* Check whether the argument string is a TRU1 or FAL0 boolean, or an invalid * string, in which case TRUM1 is returned. * If the input buffer is empty emptyrv is used as the return: if it is GE * FAL0 it will be made a binary boolean, otherwise TRU2 is returned. * inlen may be UZ_MAX to force su_cs_len() detection */ FL boole n_boolify(char const *inbuf, uz inlen, boole emptyrv); /* Dig a "quadoption" in inbuf, possibly going through getapproval() in * interactive mode, in which case prompt is printed first if set. . Just like n_boolify() otherwise */ FL boole n_quadify(char const *inbuf, uz inlen, char const *prompt, boole emptyrv); /* Is the argument "all" (case-insensitive) or "*" */ FL boole n_is_all_or_aster(char const *name); /* Get seconds since epoch, return pointer to static struct. * Unless force_update is true we may use the event-loop tick time */ FL struct n_timespec const *n_time_now(boole force_update); #define n_time_epoch() ((time_t)n_time_now(FAL0)->ts_sec) /* Update *tc* to now; only .tc_time updated unless *full_update* is true */ FL void time_current_update(struct time_current *tc, boole full_update); /* ctime(3), but do ensure 26 byte limit, do not crash XXX static buffer. * NOTE: no trailing newline */ FL char *n_time_ctime(s64 secsepoch, struct tm const *localtime_or_nil); /* Returns 0 if fully slept, number of millis left if ignint is true and we * were interrupted. Actual resolution may be second or less. * Note in case of mx_HAVE_SLEEP this may be SIGALARM based. */ FL uz n_msleep(uz millis, boole ignint); /* Our error print series.. Note: these reverse scan format in order to know * whether a newline was included or not -- this affects the output! * xxx Prototype changes to be reflected in src/su/core-code. (for now) */ FL void n_err(char const *format, ...); FL void n_errx(boole allow_multiple, char const *format, ...); FL void n_verr(char const *format, va_list ap); FL void n_verrx(boole allow_multiple, char const *format, va_list ap); /* ..(for use in a signal handler; to be obsoleted..).. */ FL void n_err_sighdl(char const *format, ...); /* Our perror(3); if errval is 0 su_err_no() is used; newline appended */ FL void n_perr(char const *msg, int errval); /* Announce a fatal error (and die); newline appended */ FL void n_alert(char const *format, ...); FL void n_panic(char const *format, ...); /* `errors' */ #ifdef mx_HAVE_ERRORS FL int c_errors(void *vp); #endif /* */ #ifdef mx_HAVE_REGEX FL char const *n_regex_err_to_doc(const regex_t *rep, int e); #endif /* Shared code for c_unxy() which base upon su_cs_dict, e.g., `shortcut' */ FL su_boole mx_unxy_dict(char const *cmdname, struct su_cs_dict *dp, void *vp); /* Sort all keys of dp, iterate over them, call the given hook ptf for each * key/data pair, place any non-NIL returned in the *result list. * A non-NIL *result will not be updated, but be appended to. * tailpp_or_nil can be set to speed up follow runs. * The boole return states error, *result may be NIL even upon success, * e.g., if dp is NIL or empty */ FL boole mx_xy_dump_dict(char const *cmdname, struct su_cs_dict *dp, struct n_strlist **result, struct n_strlist **tailpp_or_nil, struct n_strlist *(*ptf)(char const *cmdname, char const *key, void const *dat)); /* Default callback which can be used when dat is in fact a char const* */ FL struct n_strlist *mx_xy_dump_dict_gen_ptf(char const *cmdname, char const *key, void const *dat); /* page_or_print() all members of slp, one line per node. * If slp is NIL print a line that no cmdname are registered */ FL boole mx_page_or_print_strlist(char const *cmdname, struct n_strlist *slp); /* * cmd-cnd.c */ /* if.elif.else.endif conditional execution */ FL int c_if(void *v); FL int c_elif(void *v); FL int c_else(void *v); FL int c_endif(void *v); /* Whether an `if' block is in a whiteout condition */ FL boole n_cnd_if_is_skip(void); /* An execution context is teared down, and it finds to have an if stack */ FL void n_cnd_if_stack_del(struct n_go_data_ctx *gdcp); /* * cmd-folder.c */ /* `file' (`folder') and `File' (`Folder') */ FL int c_file(void *v); FL int c_File(void *v); /* 'newmail' command: Check for new mail without writing old mail back */ FL int c_newmail(void *v); /* noop */ FL int c_noop(void *v); /* Remove mailbox */ FL int c_remove(void *v); /* Rename mailbox */ FL int c_rename(void *v); /* List the folders the user currently has */ FL int c_folders(void *v); /* * cmd-head.c */ /* `headers' (show header group, possibly after setting dot) */ FL int c_headers(void *v); /* Like c_headers(), but pre-prepared message vector */ FL int print_header_group(int *vector); /* Scroll to the next/previous screen */ FL int c_scroll(void *v); FL int c_Scroll(void *v); /* Move the dot up or down by one message */ FL int c_dotmove(void *v); /* Print out the headlines for each message in the passed message list */ FL int c_from(void *v); /* Print all messages in msgvec visible and either only_marked is false or they * are MMARKed. * TODO If subject_thread_compress is true then a subject will not be printed * TODO if it equals the subject of the message "above"; as this only looks * TODO in the thread neighbour and NOT in the "visible" neighbour, the caller * TODO has to ensure the result will look sane; DROP + make it work (tm) */ FL void print_headers(int const *msgvec, boole only_marked, boole subject_thread_compress); /* * cmd-msg.c */ /* Paginate messages, honour/don't honour ignored fields, respectively */ FL int c_more(void *v); FL int c_More(void *v); /* Type out messages, honour/don't honour ignored fields, respectively */ FL int c_type(void *v); FL int c_Type(void *v); /* Show raw message content */ FL int c_show(void *v); /* `mimeview' */ FL int c_mimeview(void *vp); /* Pipe messages, honour/don't honour ignored fields, respectively */ FL int c_pipe(void *vp); FL int c_Pipe(void *vp); /* Print the first *toplines* of each desired message */ FL int c_top(void *v); FL int c_Top(void *v); /* If any arguments were given, go to the next applicable argument following * dot, otherwise, go to the next applicable message. If given as first * command with no arguments, print first message */ FL int c_next(void *v); /* `=': print out the value(s) of (or dot) */ FL int c_pdot(void *vp); /* Print the size of each message */ FL int c_messize(void *v); /* Delete messages */ FL int c_delete(void *v); /* Delete messages, then type the new dot */ FL int c_deltype(void *v); /* Undelete the indicated messages */ FL int c_undelete(void *v); /* Touch all the given messages so that they will get mboxed */ FL int c_stouch(void *v); /* Make sure all passed messages get mboxed */ FL int c_mboxit(void *v); /* Preserve messages, so that they will be sent back to the system mailbox */ FL int c_preserve(void *v); /* Mark all given messages as unread */ FL int c_unread(void *v); /* Mark all given messages as read */ FL int c_seen(void *v); /* Message flag manipulation */ FL int c_flag(void *v); FL int c_unflag(void *v); FL int c_answered(void *v); FL int c_unanswered(void *v); FL int c_draft(void *v); FL int c_undraft(void *v); /* * cmd-misc.c */ /* `!': process a shell escape by saving signals, ignoring signals and sh -c */ FL int c_shell(void *v); /* `shell': fork an interactive shell */ FL int c_dosh(void *v); /* `cwd': print user's working directory */ FL int c_cwd(void *v); /* `chdir': change user's working directory */ FL int c_chdir(void *v); /* `echo' series: expand file names like echo (to stdout/stderr, with/out * trailing newline) */ FL int c_echo(void *v); FL int c_echoerr(void *v); FL int c_echon(void *v); FL int c_echoerrn(void *v); /* `read' */ FL int c_read(void *vp); /* `readall' */ FL int c_readall(void *vp); /* `version', and generic support for the shared initial version line, which * appends to sp the UA name, version etc., and a \n LF */ FL struct n_string *n_version(struct n_string *sp); FL int c_version(void *vp); /* * cmd-resend.c */ /* All thinkable sorts of `reply' / `respond' and `followup'.. */ FL int c_reply(void *vp); FL int c_replyall(void *vp); FL int c_replysender(void *vp); FL int c_Reply(void *vp); FL int c_followup(void *vp); FL int c_followupall(void *vp); FL int c_followupsender(void *vp); FL int c_Followup(void *vp); /* ..and a mailing-list reply */ FL int c_Lreply(void *vp); /* 'forward' / `Forward' */ FL int c_forward(void *vp); FL int c_Forward(void *vp); /* Resend a message list to a third person. * The latter does not add the Resent-* header series */ FL int c_resend(void *vp); FL int c_Resend(void *vp); /* * cmd-tab.c * Actual command table, `help', `list', etc., and the n_cmd_arg() parser. */ /* Isolate the command from the arguments, return pointer to end of cmd name */ FL char const *n_cmd_isolate_name(char const *cmd); /* Whether cmd is a valid command name (and not a modifier, for example) */ FL boole n_cmd_is_valid_name(char const *cmd); /* First command which fits for cmd, or NULL */ FL struct n_cmd_desc const *n_cmd_firstfit(char const *cmd); /* Get the default command for the empty line */ FL struct n_cmd_desc const *n_cmd_default(void); /* Scan an entire command argument line, return whether result can be used, * otherwise no resources are allocated (in ->cac_arg). * For _WYSH arguments the flags _TRIM_SPACE (v15 _not_ _TRIM_IFSSPACE) and * _LOG are implicit, _META_SEMICOLON is starting with the last (non-optional) * argument, and then a trailing empty argument is ignored, too */ FL boole n_cmd_arg_parse(struct n_cmd_arg_ctx *cacp); /* Save away the data from autorec memory, and restore it to that. * The heap storage is a single pointer to be n_free() by users */ FL void *n_cmd_arg_save_to_heap(struct n_cmd_arg_ctx const *cacp); FL struct n_cmd_arg_ctx *n_cmd_arg_restore_from_heap(void *vp); /* Scan out the list of string arguments according to rm, return -1 on error; * res_dat is NULL terminated unless res_size is 0 or error occurred */ FL int /* TODO v15*/ getrawlist(boole wysh, char **res_dat, uz res_size, char const *line, uz linesize); /* * cmd-write.c */ /* Save a message in a file. Mark the message as saved so we can discard when * the user quits */ FL int c_save(void *vp); FL int c_Save(void *vp); /* Copy a message to a file without affected its saved-ness */ FL int c_copy(void *vp); FL int c_Copy(void *vp); /* Move a message to a file */ FL int c_move(void *vp); FL int c_Move(void *vp); /* Decrypt and copy a message to a file. Like plain `copy' at times */ FL int c_decrypt(void *vp); FL int c_Decrypt(void *vp); /* Write the indicated messages at the end of the passed file name, minus * header and trailing blank line. This is the MIME save function */ FL int c_write(void *vp); /* * collect.c */ /* temporary_compose_mode_hook_call() etc. setter hook */ FL void n_temporary_compose_hook_varset(void *arg); /* If quotefile is (char*)-1, stdin will be used, caller has to verify that * we're not running in interactive mode */ FL FILE *n_collect(enum n_mailsend_flags msf, struct header *hp, struct message *mp, char const *quotefile, s8 *checkaddr_err); /* * edit.c */ /* Edit a message list */ FL int c_editor(void *v); /* Invoke the visual editor on a message list */ FL int c_visual(void *v); /* Run an editor on either size bytes of the file fp (or until EOF if size is * negative) or on the message mp, and return a new file or NULL on error of if * the user didn't perform any edits (not possible in pipe mode). * For now we ASSERT that mp==NULL if hp!=NULL, treating this as a special call * from within compose mode, and giving TRUM1 to n_puthead(). * Signals must be handled by the caller. * viored is 'e' for $EDITOR, 'v' for $VISUAL, or '|' for child_run(), in * which case pipecmd must have been given */ FL FILE *n_run_editor(FILE *fp, off_t size, int viored, boole readonly, struct header *hp, struct message *mp, enum sendaction action, n_sighdl_t oldint, char const *pipecmd); /* * folder.c */ /* Set up editing on the given file name. * If the first character of name is %, we are considered to be editing the * file, otherwise we are reading our mail which has signficance for mbox and * so forth */ FL int setfile(char const *name, enum fedit_mode fm); FL int newmailinfo(int omsgCount); /* Set the size of the message vector used to construct argument lists to * message list functions */ FL void setmsize(int sz); /* Logic behind -H / -L invocations */ FL void print_header_summary(char const *Larg); /* Announces the current folder as indicated. * Is responsible for updating "dot" (after a folder change). */ FL void n_folder_announce(enum n_announce_flags af); FL int getmdot(int nmail); FL void initbox(char const *name); /* Determine and expand the current *folder* name, return it (with trailing * solidus) or the empty string also in case of errors: since POSIX mandates * a default of CWD if not set etc., that seems to be a valid fallback, then */ FL char const *n_folder_query(void); /* Prepare the seekable O_APPEND MBOX fout for appending of another message. * If st_or_null is not NULL it is assumed to point to an up-to-date status of * fout, otherwise an internal fstat(2) is performed as necessary. * Returns su_err_no() of error */ FL int n_folder_mbox_prepare_append(FILE *fout, struct stat *st_or_null); /* * go.c * Program input of all sorts, input lexing, event loops, command evaluation. * Also alias handling. */ /* Setup the run environment; this i *only* for main() */ FL void n_go_init(void); /* Interpret user commands. If stdin is not a tty, print no prompt; return * whether last processed command returned error; this is *only* for main()! */ FL boole n_go_main_loop(void); /* Actual cmd input */ /* */ FL void n_go_input_clearerr(void); /* Force n_go_input() to read EOF next */ FL void n_go_input_force_eof(void); /* Returns true if force_eof() has been set -- it is set automatically if * an input context enters EOF state (rather than error, as in ferror(3)) */ FL boole n_go_input_is_eof(void); /* Are there any go_input_inject()ions pending? */ FL boole n_go_input_have_injections(void); /* Force n_go_input() to read that buffer next. * If n_GO_INPUT_INJECT_COMMIT is not set the line editor is reentered with buf * as the default/initial line content */ FL void n_go_input_inject(enum n_go_input_inject_flags giif, char const *buf, uz len); /* Read a complete line of input, with editing if interactive and possible. * If string is set it is used as the initial line content if in interactive * mode, otherwise this argument is ignored for reproducibility. * If histok_or_nil is set it will be updated to FAL0 if input shall not be * placed in history. * Return number of octets or a value <0 on error. * Note: may use the currently `source'd file stream instead of stdin! * Manages the n_PS_READLINE_NL hack * TODO We need an OnReadLineCompletedEvent and drop this function */ FL int n_go_input(enum n_go_input_flags gif, char const *prompt, char **linebuf, uz *linesize, char const *string, boole *histok_or_nil su_DBG_LOC_ARGS_DECL); #ifdef su_HAVE_DBG_LOC_ARGS # define n_go_input(A,B,C,D,E,F) n_go_input(A,B,C,D,E,F su_DBG_LOC_ARGS_INJ) #endif /* Read a line of input, with editing if interactive and possible, return it * savestr()d or NULL in case of errors or if an empty line would be returned. * This may only be called from toplevel (not during n_PS_ROBOT). * If string is set it is used as the initial line content if in interactive * mode, otherwise this argument is ignored for reproducibility */ FL char *n_go_input_cp(enum n_go_input_flags gif, char const *prompt, char const *string); /* Deal with loading of resource files and dealing with a stack of files for * the source command */ /* Load a file of user system startup resources. * *Only* for main(), returns whether program shall continue */ FL boole n_go_load(char const *name); /* "Load" or go_inject() command line option "cmd" arguments in order. * *Only* for main(), returns whether program shall continue unless injectit is * set, in which case this function does not fail */ FL boole n_go_XYargs(boole injectit, char const **lines, uz cnt); /* Pushdown current input file and switch to a new one. */ FL int c_source(void *v); FL int c_source_if(void *v); /* Evaluate a complete macro / a single command. For the former lines will * always be free()d, for the latter cmd will always be duplicated internally */ FL boole n_go_macro(enum n_go_input_flags gif, char const *name, char **lines, void (*on_finalize)(void*), void *finalize_arg); FL boole n_go_command(enum n_go_input_flags gif, char const *cmd); /* XXX See a_GO_SPLICE in source */ FL void n_go_splice_hack(char const *cmd, FILE *new_stdin, FILE *new_stdout, u32 new_psonce, void (*on_finalize)(void*), void *finalize_arg); FL void n_go_splice_hack_remove_after_jump(void); /* XXX Hack: may we release our (interactive) (terminal) control to a different * XXX program, e.g., a $PAGER? */ FL boole n_go_may_yield_control(void); /* `eval' */ FL int c_eval(void *vp); /* `xcall' */ FL int c_xcall(void *vp); /* `exit' and `quit' commands */ FL int c_exit(void *vp); FL int c_quit(void *vp); /* `readctl' */ FL int c_readctl(void *vp); /* * header.c */ /* Return the user's From: address(es) */ FL char const * myaddrs(struct header *hp); /* Boil the user's From: addresses down to a single one, or use *sender* */ FL char const * myorigin(struct header *hp); /* See if the passed line buffer, which may include trailing newline (sequence) * is a mail From_ header line according to POSIX ("From "). * If check_rfc4155 is true we'll return TRUM1 instead if the From_ line * matches POSIX but is _not_ compatible to RFC 4155 */ FL boole is_head(char const *linebuf, uz linelen, boole check_rfc4155); /* Print hp "to user interface" fp for composing purposes xxx what a sigh */ FL boole n_header_put4compose(FILE *fp, struct header *hp); /* Extract some header fields (see e.g. -t documentation) from a message. * This calls expandaddr() on some headers and sets checkaddr_err_or_null if * that is set -- note it explicitly allows EAF_NAME because aliases are not * expanded when this is called! */ FL void n_header_extract(enum n_header_extract_flags hef, FILE *fp, struct header *hp, s8 *checkaddr_err_or_null); /* Return the desired header line from the passed message * pointer (or NULL if the desired header field is not available). * If mult is zero, return the content of the first matching header * field only, the content of all matching header fields else */ FL char * hfield_mult(char const *field, struct message *mp, int mult); #define hfieldX(a, b) hfield_mult(a, b, 1) #define hfield1(a, b) hfield_mult(a, b, 0) /* Check whether the passed line is a header line of the desired breed. * If qm_suffix_or_nil is set then the field?[MOD]: syntax is supported, the * suffix substring range of linebuf will be stored in there, then, or NIL; * this logically casts away the const. * Return the field body, or NULL */ FL char const *n_header_get_field(char const *linebuf, char const *field, struct str *qm_suffix_or_nil); /* Start of a "comment". Ignore it */ FL char const * skip_comment(char const *cp); /* Return the start of a route-addr (address in angle brackets), if present */ FL char const * routeaddr(char const *name); /* Query *expandaddr*, parse it and return flags. * The flags are already adjusted for n_PSO_INTERACTIVE, n_PO_TILDE_FLAG etc. */ FL enum expand_addr_flags expandaddr_to_eaf(void); /* Check if an address is invalid, either because it is malformed or, if not, * according to eacm. Return FAL0 when it looks good, TRU1 if it is invalid * but the error condition wasn't covered by a 'hard "fail"ure', -1 otherwise */ FL s8 is_addr_invalid(struct mx_name *np, enum expand_addr_check_mode eacm); /* Does *NP* point to a file or pipe addressee? */ #define is_fileorpipe_addr(NP) \ (((NP)->n_flags & mx_NAME_ADDRSPEC_ISFILEORPIPE) != 0) /* Skin an address according to the RFC 822 interpretation of "host-phrase" */ FL char * skin(char const *name); /* Skin *name* and extract *addr-spec* according to RFC 5322 and enum gfield. * Store the result in .ag_skinned and also fill in those .ag_ fields that have * actually been seen. * Return NULL on error, or name again, but which may have been replaced by * a version with fixed quotation etc.! */ FL char const *n_addrspec_with_guts(struct n_addrguts *agp, char const *name, u32 gfield); /* `addrcodec' */ FL int c_addrcodec(void *vp); /* Fetch the real name from an internet mail address field */ FL char * realname(char const *name); /* Get the list of senders (From: or Sender: or From_ line) from this message. * The return value may be empty and needs lextract()ion */ FL char *n_header_senderfield_of(struct message *mp); /* Trim away all leading Re: etc., return pointer to plain subject. * Note it doesn't perform any MIME decoding by itself */ FL char const *subject_re_trim(char const *cp); FL int msgidcmp(char const *s1, char const *s2); /* Fake Sender for From_ lines if missing, e. g. with POP3 */ FL char const * fakefrom(struct message *mp); /* From username Fri Jan 2 20:13:51 2004 * | | | | | * 0 5 10 15 20 */ #if defined mx_HAVE_IMAP_SEARCH || defined mx_HAVE_IMAP FL time_t unixtime(char const *from); #endif FL time_t rfctime(char const *date); FL time_t combinetime(int year, int month, int day, int hour, int minute, int second); /* Determine the date to print in faked 'From ' lines */ FL void substdate(struct message *m); /* Create ready-to-go environment taking into account *datefield* etc., * and return a result in auto-reclaimed storage. * TODO hack *color_tag_or_null could be set to n_COLOUR_TAG_SUM_OLDER. * time_current is used for comparison and must thus be up-to-date */ FL char *n_header_textual_date_info(struct message *mp, char const **color_tag_or_null); /* Create ready-to-go sender name of a message in *cumulation_or_null, the * addresses only in *addr_or_null, the real names only in *name_real_or_null, * and the full names in *name_full_or_null, taking acount for *showname*. * If *is_to_or_null is set, *showto* and n_is_myname() are taken into account * when choosing which names to use. * The list as such is returned, or NULL if there is really none (empty strings * will be stored, then). * All results are in auto-reclaimed storage, but may point to the same string. * TODO *is_to_or_null could be set to whether we actually used To:, or not. * TODO n_header_textual_sender_info(): should only create a list of matching * TODO name objects, which the user can iterate over and o->to_str().. */ FL struct mx_name *n_header_textual_sender_info(struct message *mp, char **cumulation_or_null, char **addr_or_null, char **name_real_or_null, char **name_full_or_null, boole *is_to_or_null); /* TODO Weird thing that tries to fill in From: and Sender: */ FL void setup_from_and_sender(struct header *hp); /* Note: returns 0x1 if both args were NULL */ FL struct mx_name const *check_from_and_sender(struct mx_name const *fromfield, struct mx_name const *senderfield); #ifdef mx_HAVE_XTLS FL char * getsender(struct message *m); #endif /* This returns NULL if hp is NULL or when no information is available. * hp remains unchanged (->h_in_reply_to is not set!) */ FL struct mx_name *n_header_setup_in_reply_to(struct header *hp); /* Fill in / reedit the desired header fields */ FL int grab_headers(enum n_go_input_flags gif, struct header *hp, enum gfield gflags, int subjfirst); /* Check whether sep->ss_sexpr (or ->ss_sregex) matches any header of mp. * If sep->s_where (or >s_where_wregex) is set, restrict to given headers */ FL boole n_header_match(struct message *mp, struct search_expr const *sep); /* Verify whether len (UZ_MAX=su_cs_len) bytes of name form a standard or * otherwise known header name (that must not be used as a custom header). * Return the (standard) header name, or NULL */ FL char const *n_header_is_known(char const *name, uz len); /* Add a custom header to the given list, in auto-reclaimed or heap memory */ FL boole n_header_add_custom(struct n_header_field **hflp, char const *dat, boole heap); /* * ignore.c */ /* `(un)?headerpick' */ FL int c_headerpick(void *vp); FL int c_unheaderpick(void *vp); /* TODO Compat variants of the c_(un)?h*() series, * except for `retain' and `ignore', which are standardized */ FL int c_retain(void *vp); FL int c_ignore(void *vp); FL int c_unretain(void *vp); FL int c_unignore(void *vp); FL int c_saveretain(void *v); FL int c_saveignore(void *v); FL int c_unsaveretain(void *v); FL int c_unsaveignore(void *v); FL int c_fwdretain(void *v); FL int c_fwdignore(void *v); FL int c_unfwdretain(void *v); FL int c_unfwdignore(void *v); /* Ignore object lifecycle. (Most of the time this interface deals with * special n_IGNORE_* objects, e.g., n_IGNORE_TYPE, though.) * isauto: whether auto-reclaimed storage is to be used for allocations; * if so, _del() needn't be called */ FL struct n_ignore *n_ignore_new(boole isauto); FL void n_ignore_del(struct n_ignore *self); /* Are there just _any_ user settings covered by self? */ FL boole n_ignore_is_any(struct n_ignore const *self); /* Set an entry to retain (or ignore). * Returns FAL0 if dat is not a valid header field name or an invalid regular * expression, TRU1 if insertion took place, and TRUM1 if already set */ FL boole n_ignore_insert(struct n_ignore *self, boole retain, char const *dat, uz len); #define n_ignore_insert_cp(SELF,RT,CP) n_ignore_insert(SELF, RT, CP, UZ_MAX) /* Returns TRU1 if retained, TRUM1 if ignored, FAL0 if not covered */ FL boole n_ignore_lookup(struct n_ignore const *self, char const *dat, uz len); #define n_ignore_lookup_cp(SELF,CP) n_ignore_lookup(SELF, CP, UZ_MAX) #define n_ignore_is_ign(SELF,FDAT,FLEN) \ (n_ignore_lookup(SELF, FDAT, FLEN) == TRUM1) /* * imap-search.c */ /* Return -1 on invalid spec etc., the number of matches otherwise */ #ifdef mx_HAVE_IMAP_SEARCH FL sz imap_search(char const *spec, int f); #endif /* * maildir.c */ #ifdef mx_HAVE_MAILDIR FL int maildir_setfile(char const *who, char const *name, enum fedit_mode fm); FL boole maildir_quit(boole hold_sigs_on); FL enum okay maildir_append(char const *name, FILE *fp, long offset); FL enum okay maildir_remove(char const *name); #endif /* mx_HAVE_MAILDIR */ /* * (Former memory.c, now SU TODO get rid of compat macros) * Heap memory and automatically reclaimed storage, plus pseudo "alloca" * */ /* Generic heap memory */ #define n_alloc su_MEM_ALLOC #define n_realloc su_MEM_REALLOC #define n_calloc(NO,SZ) su_MEM_CALLOC_N(SZ, NO) #define n_free su_MEM_FREE /* Auto-reclaimed storage */ #define n_autorec_relax_create() \ su_mem_bag_auto_relax_create(n_go_data->gdc_membag) #define n_autorec_relax_gut() \ su_mem_bag_auto_relax_gut(n_go_data->gdc_membag) #define n_autorec_relax_unroll() \ su_mem_bag_auto_relax_unroll(n_go_data->gdc_membag) /* (Even older obsolete names!) */ #define srelax_hold n_autorec_relax_create #define srelax_rele n_autorec_relax_gut #define srelax n_autorec_relax_unroll #define n_autorec_alloc su_MEM_BAG_SELF_AUTO_ALLOC #define n_autorec_calloc(NO,SZ) su_MEM_BAG_SELF_AUTO_CALLOC_N(SZ, NO) /* Pseudo alloca (and also auto-reclaimed) */ #define n_lofi_alloc su_MEM_BAG_SELF_LOFI_ALLOC #define n_lofi_calloc su_MEM_BAG_SELF_LOFI_CALLOC #define n_lofi_free su_MEM_BAG_SELF_LOFI_FREE #define n_lofi_snap_create() su_mem_bag_lofi_snap_create(n_go_data->gdc_membag) #define n_lofi_snap_unroll(COOKIE) \ su_mem_bag_lofi_snap_unroll(n_go_data->gdc_membag, COOKIE) /* * message.c */ /* Return a file buffer all ready to read up the passed message pointer */ FL FILE * setinput(struct mailbox *mp, struct message *m, enum needspec need); /* */ FL enum okay get_body(struct message *mp); /* Reset (free) the global message array */ FL void message_reset(void); /* Append the passed message descriptor onto the message array; if mp is NULL, * NULLify the entry at &[msgCount-1] */ FL void message_append(struct message *mp); /* Append a NULL message */ FL void message_append_null(void); /* Check whether sep->ss_sexpr (or ->ss_sregex) matches mp. If with_headers is * true then the headers will also be searched (as plain text) */ FL boole message_match(struct message *mp, struct search_expr const *sep, boole with_headers); /* */ FL struct message * setdot(struct message *mp); /* Touch the named message by setting its MTOUCH flag. Touched messages have * the effect of not being sent back to the system mailbox on exit */ FL void touch(struct message *mp); /* Convert user message spec. to message numbers and store them in vector, * which should be capable to hold msgCount+1 entries (n_msgvec ASSERTs this). * flags is n_cmd_arg_ctx.cac_msgflag == n_cmd_desc.cd_msgflag == enum mflag. * If capp_or_null is not NULL then the last (string) token is stored in here * and not interpreted as a message specification; in addition, if only one * argument remains and this is the empty string, 0 is returned (*vector=0; * this is used to implement n_CMD_ARG_DESC_MSGLIST_AND_TARGET). * A NUL *buf input results in a 0 return, *vector=0, [*capp_or_null=NULL]. * Returns the count of messages picked up or -1 on error */ FL int n_getmsglist(char const *buf, int *vector, int flags, struct n_cmd_arg **capp_or_null); /* Find the first message whose flags&m==f and return its message number */ FL int first(int f, int m); /* Mark the named message by setting its mark bit */ FL void mark(int mesg, int f); /* * mime.c */ /* *sendcharsets* .. *charset-8bit* iterator; *a_charset_to_try_first* may be * used to prepend a charset to this list (e.g., for *reply-in-same-charset*). * The returned boolean indicates charset_iter_is_valid(). * Without mx_HAVE_ICONV, this "iterates" over *ttycharset* only */ FL boole charset_iter_reset(char const *a_charset_to_try_first); FL boole charset_iter_next(void); FL boole charset_iter_is_valid(void); FL char const * charset_iter(void); /* And this is (xxx temporary?) which returns the iterator if that is valid and * otherwise either *charset-8bit* or *ttycharset*, dep. on mx_HAVE_ICONV */ FL char const * charset_iter_or_fallback(void); FL void charset_iter_recurse(char *outer_storage[2]); /* TODO LEGACY */ FL void charset_iter_restore(char *outer_storage[2]); /* TODO LEGACY */ /* Check whether our headers will need MIME conversion */ #ifdef mx_HAVE_ICONV FL char const * need_hdrconv(struct header *hp); #endif /* Convert header fields from RFC 1522 format */ FL void mime_fromhdr(struct str const *in, struct str *out, enum tdflags flags); /* Interpret MIME strings in parts of an address field */ FL char * mime_fromaddr(char const *name); /* fwrite(3) performing the given MIME conversion */ FL sz mime_write(char const *ptr, uz size, FILE *f, enum conversion convert, enum tdflags dflags, struct quoteflt *qf, struct str *outrest, struct str *inrest); FL sz xmime_write(char const *ptr, uz size, /* TODO LEGACY */ FILE *f, enum conversion convert, enum tdflags dflags, struct str *outrest, struct str *inrest); /* * mime-enc.c * Content-Transfer-Encodings as defined in RFC 2045 (and RFC 2047): * - Quoted-Printable, section 6.7 * - Base64, section 6.8 * TODO For now this is pretty mixed up regarding this external interface * TODO (and due to that the code is, too). * TODO In v15.0 CTE will be filter based, and explicit conversion will * TODO gain clear error codes */ /* Default MIME Content-Transfer-Encoding: as via *mime-encoding*. * Cannot be MIMEE_BIN nor MIMEE_7B (i.e., only B64, QP, 8B) */ FL enum mime_enc mime_enc_target(void); /* Map from a Content-Transfer-Encoding: header body (which may be NULL) */ FL enum mime_enc mime_enc_from_ctehead(char const *hbody); /* XXX Try to get rid of that */ FL char const * mime_enc_from_conversion(enum conversion const convert); /* How many characters of (the complete body) ln need to be quoted. * Only MIMEEF_ISHEAD and MIMEEF_ISENCWORD are understood */ FL uz mime_enc_mustquote(char const *ln, uz lnlen, enum mime_enc_flags flags); /* How much space is necessary to encode len bytes in QP, worst case. * Includes room for terminator, UZ_MAX on overflow */ FL uz qp_encode_calc_size(uz len); /* If flags includes QP_ISHEAD these assume "word" input and use special * quoting rules in addition; soft line breaks are not generated. * Otherwise complete input lines are assumed and soft line breaks are * generated as necessary. Return NULL on error (overflow) */ FL struct str * qp_encode(struct str *out, struct str const *in, enum qpflags flags); #ifdef notyet FL struct str * qp_encode_cp(struct str *out, char const *cp, enum qpflags flags); FL struct str * qp_encode_buf(struct str *out, void const *vp, uz vp_len, enum qpflags flags); #endif /* The buffers of out and *rest* will be managed via n_realloc(). * If inrest_or_null is needed but NULL an error occurs, otherwise tolerant. * Return FAL0 on error; caller is responsible to free buffers */ FL boole qp_decode_header(struct str *out, struct str const *in); FL boole qp_decode_part(struct str *out, struct str const *in, struct str *outrest, struct str *inrest_or_null); /* How much space is necessary to encode len bytes in Base64, worst case. * Includes room for (CR/LF/CRLF and) terminator, UZ_MAX on overflow */ FL uz b64_encode_calc_size(uz len); /* Note these simply convert all the input (if possible), including the * insertion of NL sequences if B64_CRLF or B64_LF is set (and multiple thereof * if B64_MULTILINE is set). * Thus, in the B64_BUF case, better call b64_encode_calc_size() first. * Return NULL on error (overflow; cannot happen for B64_BUF) */ FL struct str * b64_encode(struct str *out, struct str const *in, enum b64flags flags); FL struct str * b64_encode_buf(struct str *out, void const *vp, uz vp_len, enum b64flags flags); #ifdef notyet FL struct str * b64_encode_cp(struct str *out, char const *cp, enum b64flags flags); #endif /* The _{header,part}() variants are failure tolerant, the latter requires * outrest to be set; due to the odd 4:3 relation inrest_or_null should be * given, _then_, it is an error if it is needed but not set. * TODO pre v15 callers should ensure that no endless loop is entered because * TODO the inrest cannot be converted and ends up as inrest over and over: * TODO give NULL to stop such loops. * The buffers of out and possibly *rest* will be managed via n_realloc(). * Returns FAL0 on error; caller is responsible to free buffers. * XXX FAL0 is effectively not returned for _part*() variants, * XXX (instead replacement characters are produced for invalid data. * XXX _Unless_ operation could EOVERFLOW.) * XXX I.e. this is bad and is tolerant for text and otherwise not */ FL boole b64_decode(struct str *out, struct str const *in); FL boole b64_decode_header(struct str *out, struct str const *in); FL boole b64_decode_part(struct str *out, struct str const *in, struct str *outrest, struct str *inrest_or_null); /* * mime-param.c */ /* Get a mime style parameter from a header body */ FL char * mime_param_get(char const *param, char const *headerbody); /* Format parameter name to have value, autorec_alloc() it or NULL in result. * 0 on error, 1 or -1 on success: the latter if result contains \n newlines, * which it will if the created param requires more than MIME_LINELEN bytes; * there is never a trailing newline character */ /* TODO mime_param_create() should return a StrList<> or something. * TODO in fact it should take a HeaderField* and append a HeaderFieldParam*! */ FL s8 mime_param_create(struct str *result, char const *name, char const *value); /* Get the boundary out of a Content-Type: multipart/xyz header field, return * autorec_alloc()ed copy of it; store su_cs_len() in *len if set */ FL char * mime_param_boundary_get(char const *headerbody, uz *len); /* Create a autorec_alloc()ed MIME boundary */ FL char * mime_param_boundary_create(void); /* * mime-parse.c */ /* Create MIME part object tree for and of mp */ FL struct mimepart * mime_parse_msg(struct message *mp, enum mime_parse_flags mpf); /* * mime-types.c */ /* `(un)?mimetype' commands */ FL int c_mimetype(void *v); FL int c_unmimetype(void *v); /* Check whether the Content-Type name is internally known */ FL boole n_mimetype_check_mtname(char const *name); /* Return a Content-Type matching the name, or NULL if none could be found */ FL char *n_mimetype_classify_filename(char const *name); /* Classify content of *fp* as necessary and fill in arguments; **charset* is * set to *charset-7bit* or charset_iter_or_fallback() if NULL. * no_mboxo states whether 7BIT/8BIT is acceptible if only existence of * a ^From_ would otherwise enforce QP/BASE64 * TODO this should take a carrier and only fill that in with what has been * TODO detected/classified, and suggest hints; rest up to caller! * TODO This is not only more correct (no_mboxo crux++), it simplifies a lot */ FL enum conversion n_mimetype_classify_file(FILE *fp, char const **contenttype, char const **charset, int *do_iconv, boole no_mboxo); /* Dependend on *mime-counter-evidence* mpp->m_ct_type_usr_ovwr will be set, * but otherwise mpp is const. for_user_context rather maps 1:1 to * MIME_PARSE_FOR_USER_CONTEXT */ FL enum mimecontent n_mimetype_classify_part(struct mimepart *mpp, boole for_user_context); /* Query handler for a part, return the plain type (& MIME_HDL_TYPE_MASK). * mhp is anyway initialized (mh_flags, mh_msg) */ FL enum mime_handler_flags n_mimetype_handler(struct mime_handler *mhp, struct mimepart const *mpp, enum sendaction action); /* * path.c */ /* Test to see if the passed file name is a directory, return true if it is. * If check_access is set, we also access(2): if it is TRUM1 only X_OK|R_OK is * tested, otherwise X_OK|R_OK|W_OK. */ FL boole n_is_dir(char const *name, boole check_access); /* Recursively create a directory */ FL boole n_path_mkdir(char const *name); /* Delete a file, but only if the file is a plain file; return FAL0 on system * error and TRUM1 if name is not a plain file, return TRU1 on success */ FL boole n_path_rm(char const *name); /* A get-wd..restore-wd approach */ FL enum okay cwget(struct cw *cw); FL enum okay cwret(struct cw *cw); FL void cwrelse(struct cw *cw); /* * quit.c */ /* Save all of the undetermined messages at the top of "mbox". Save all * untouched messages back in the system mailbox. Remove the system mailbox, * if none saved there. * TODO v15 Note: assumes hold_sigs() has been called _and_ can be temporarily * TODO dropped via a single rele_sigs() if hold_sigs_on */ FL boole quit(boole hold_sigs_on); /* Adjust the message flags in each message */ FL int holdbits(void); /* Create another temporary file and copy user's mbox file darin. If there is * no mbox, copy nothing. If he has specified "append" don't copy his mailbox, * just copy saveable entries at the end */ FL enum okay makembox(void); FL void save_mbox_for_possible_quitstuff(void); /* TODO DROP IF U CAN */ FL int savequitflags(void); FL void restorequitflags(int); /* * send.c */ /* Send message described by the passed pointer to the passed output buffer. * Return -1 on error. Adjust the status: field if need be. If doitp is * given, suppress ignored header fields. prefix is a string to prepend to * each output line. action = data destination * (SEND_MBOX,_TOFILE,_TODISP,_QUOTE,_DECRYPT). stats[0] is line count, * stats[1] is character count. stats may be NULL. Note that stats[0] is * valid for SEND_MBOX only */ FL int sendmp(struct message *mp, FILE *obuf, struct n_ignore const *doitp, char const *prefix, enum sendaction action, u64 *stats); /* * sendout.c */ /* Check whether outgoing transport is via SMTP/SUBMISSION etc. * Returns TRU1 if yes and URL parsing succeeded, TRUM1 if *mta* is file based * (or bad), and FAL0 if URL parsing failed */ FL boole mx_sendout_mta_url(struct mx_url *urlp); /* Interface between the argument list and the mail1 routine which does all the * dirty work */ FL int n_mail(enum n_mailsend_flags msf, struct mx_name *to, struct mx_name *cc, struct mx_name *bcc, char const *subject, struct attachment *attach, char const *quotefile); /* `mail' and `Mail' commands, respectively */ FL int c_sendmail(void *v); FL int c_Sendmail(void *v); /* Mail a message on standard input to the people indicated in the passed * header, applying all the address massages first. (Internal interface) */ FL enum okay n_mail1(enum n_mailsend_flags flags, struct header *hp, struct message *quote, char const *quotefile); /* Create a Date: header field. * We compare the localtime() and gmtime() results to get the timezone, because * numeric timezones are easier to read and because $TZ isn't always set */ FL int mkdate(FILE *fo, char const *field); /* Dump the to, subject, cc header on the passed file buffer. * nosend_msg tells us not to dig to deep but to instead go for compose mode or * editing a message (yet we're stupid and cannot do it any better) - if it is * TRUM1 then we're really in compose mode and will produce some fields for * easier filling in (see n_run_editor() proto for this hack) */ FL boole n_puthead(boole nosend_msg, struct header *hp, FILE *fo, enum gfield w, enum sendaction action, enum conversion convert, char const *contenttype, char const *charset); /* Note: hp->h_to must already have undergone address massage(s), it is taken * as-is; h_cc and h_bcc are asserted to be NIL. urlp should be NIL if * sendout_mta_url() says we are file based, otherwise... */ FL enum okay n_resend_msg(struct message *mp, struct mx_url *urlp, struct header *hp, boole add_resent); /* *save* / $DEAD */ FL void savedeadletter(FILE *fp, boole fflush_rewind_first); /* * shexp.c */ /* Evaluate the string given as a new mailbox name. Supported meta characters: * . % for my system mail box * . %user for user's system mail box * . # for previous file * . & invoker's mbox file * . +file file in folder directory * . any shell meta character (except for FEXP_NSHELL). * a poor man's vis(3), on name before calling this (and showing the user). * If FEXP_MULTIOK is set we return an array of terminated strings, the (last) * result string is terminated via \0\0 and n_PS_EXPAND_MULTIRESULT is set. * Returns the file name as an auto-reclaimed string */ FL char *fexpand(char const *name, enum fexp_mode fexpm); /* Parse the next shell token from input (->s and ->l are adjusted to the * remains, data is constant beside that; ->s may be NULL if ->l is 0, if ->l * EQ UZ_MAX su_cs_len(->s) is used) and append the resulting output to store. * If cookie is not NULL and we're in double-quotes then ${@} will be exploded * just as known from the sh(1)ell in that case */ FL enum n_shexp_state n_shexp_parse_token(enum n_shexp_parse_flags flags, struct n_string *store, struct str *input, void const **cookie); /* Quick+dirty simplified : if an error occurs, returns a copy of *cp and set * *cp to NULL, otherwise advances *cp to over the parsed token */ FL char *n_shexp_parse_token_cp(enum n_shexp_parse_flags flags, char const **cp); /* Quote input in a way that can, in theory, be fed into parse_token() again. * ->s may be NULL if ->l is 0, if ->l EQ UZ_MAX su_cs_len(->s) is used. * If rndtrip is true we try to make the resulting string "portable" (by * converting Unicode to \u etc.), otherwise we produce something to be * consumed "now", i.e., to display for the user. * Resulting output is _appended_ to store. * TODO Note: last resort, since \u and $ expansions etc. are necessarily * TODO already expanded and can thus not be reverted, but ALL we have */ FL struct n_string *n_shexp_quote(struct n_string *store, struct str const *input, boole rndtrip); FL char *n_shexp_quote_cp(char const *cp, boole rndtrip); /* Can name be used as a variable name? I.e., this returns false for special * parameter names like $# etc. */ FL boole n_shexp_is_valid_varname(char const *name); /* `shcodec' */ FL int c_shcodec(void *vp); /* * spam.c */ #ifdef mx_HAVE_SPAM /* Direct mappings of the various spam* commands */ FL int c_spam_clear(void *v); FL int c_spam_set(void *v); FL int c_spam_forget(void *v); FL int c_spam_ham(void *v); FL int c_spam_rate(void *v); FL int c_spam_spam(void *v); #endif /* * strings.c */ /* Return a pointer to a dynamic copy of the argument */ FL char *savestr(char const *str su_DBG_LOC_ARGS_DECL); FL char *savestrbuf(char const *sbuf, uz slen su_DBG_LOC_ARGS_DECL); #ifdef su_HAVE_DBG_LOC_ARGS # define savestr(CP) savestr(CP su_DBG_LOC_ARGS_INJ) # define savestrbuf(CBP,CBL) savestrbuf(CBP, CBL su_DBG_LOC_ARGS_INJ) #endif /* Concatenate cp2 onto cp1 (if not NULL), separated by sep (if not '\0') */ FL char *savecatsep(char const *cp1, char sep, char const *cp2 su_DBG_LOC_ARGS_DECL); #ifdef su_HAVE_DBG_LOC_ARGS # define savecatsep(S1,SEP,S2) savecatsep(S1, SEP, S2 su_DBG_LOC_ARGS_INJ) #endif /* Make copy of argument incorporating old one, if set, separated by space */ #define save2str(S,O) savecatsep(O, ' ', S) /* strcat */ #define savecat(S1,S2) savecatsep(S1, '\0', S2) /* */ FL struct str * str_concat_csvl(struct str *self, ...); /* */ FL struct str *str_concat_cpa(struct str *self, char const * const *cpa, char const *sep_o_null su_DBG_LOC_ARGS_DECL); #ifdef su_HAVE_DBG_LOC_ARGS # define str_concat_cpa(S,A,N) str_concat_cpa(S, A, N su_DBG_LOC_ARGS_INJ) #endif /* Plain char* support, not auto-reclaimed (unless noted) */ /* Reverse solidus quote (" and \) v'alue, and return autorec_alloc()ed */ FL char * string_quote(char const *v); /* Convert a string to lowercase, in-place and with multibyte-aware */ FL void makelow(char *cp); /* Is *sub* a substring of *str*, case-insensitive and multibyte-aware? */ FL boole substr(char const *str, char const *sub); /* struct str related support funs TODO _cp->_cs! */ /* *self->s* is n_realloc()ed */ #define n_str_dup(S, T) n_str_assign_buf((S), (T)->s, (T)->l) /* *self->s* is n_realloc()ed; if buflen==UZ_MAX su_cs_len() is called unless * buf is NULL; buf may be NULL if buflen is 0 */ FL struct str *n_str_assign_buf(struct str *self, char const *buf, uz buflen su_DBG_LOC_ARGS_DECL); #define n_str_assign(S, T) n_str_assign_buf(S, (T)->s, (T)->l) #define n_str_assign_cp(S, CP) n_str_assign_buf(S, CP, UZ_MAX) /* *self->s* is n_realloc()ed, *self->l* incremented; if buflen==UZ_MAX * su_cs_len() is called unless buf is NULL; buf may be NULL if buflen is 0 */ FL struct str *n_str_add_buf(struct str *self, char const *buf, uz buflen su_DBG_LOC_ARGS_DECL); #define n_str_add(S, T) n_str_add_buf(S, (T)->s, (T)->l) #define n_str_add_cp(S, CP) n_str_add_buf(S, CP, UZ_MAX) #ifdef su_HAVE_DBG_LOC_ARGS # define n_str_assign_buf(S,B,BL) \ n_str_assign_buf(S, B, BL su_DBG_LOC_ARGS_INJ) # define n_str_add_buf(S,B,BL) n_str_add_buf(S, B, BL su_DBG_LOC_ARGS_INJ) #endif /* Remove leading and trailing su_cs_is_space()s and *ifs-ws*, respectively. * The ->s and ->l of the string will be adjusted, but no NUL termination will * be applied to a possibly adjusted buffer! * If dofaults is set, " \t\n" is always trimmed (in addition) */ FL struct str *n_str_trim(struct str *self, enum n_str_trim_flags stf); FL struct str *n_str_trim_ifs(struct str *self, boole dodefaults); /* struct n_string * May have NIL buffer, may contain embedded NULs */ FL struct n_string *n__string_clear(struct n_string *self); /* Lifetime. n_string_gut() is optional for _creat_auto() strings */ INLINE struct n_string * n_string_creat(struct n_string *self){ self->s_dat = NIL; self->s_len = self->s_auto = self->s_size = 0; return self; } INLINE struct n_string * n_string_creat_auto(struct n_string *self){ self->s_dat = NIL; self->s_len = self->s_auto = self->s_size = 0; self->s_auto = TRU1; return self; } INLINE void n_string_gut(struct n_string *self){ if(self->s_dat != NIL) n__string_clear(self); } INLINE struct n_string * n_string_trunc(struct n_string *self, uz len){ ASSERT(UCMP(z, len, <=, self->s_len)); self->s_len = S(u32,len); return self; } INLINE struct n_string * n_string_take_ownership(struct n_string *self, char *buf, u32 size, u32 len){ ASSERT(self->s_dat == NIL); ASSERT(size == 0 || buf != NIL); ASSERT(len == 0 || len < size); self->s_dat = buf; self->s_size = size; self->s_len = len; return self; } INLINE struct n_string * n_string_drop_ownership(struct n_string *self){ self->s_dat = NIL; self->s_len = self->s_size = 0; return self; } INLINE struct n_string * n_string_clear(struct n_string *self){ if(self->s_size > 0) self = n__string_clear(self); return self; } /* Check whether a buffer of Len bytes can be inserted into S(elf) */ INLINE boole n_string_get_can_book(uz len){ return (S(uz,S32_MAX) - Z_ALIGN(1) > len); } INLINE boole n_string_can_book(struct n_string *self, uz len){ return (n_string_get_can_book(len) && S(uz,S32_MAX) - Z_ALIGN(1) - len > self->s_len); } /* Reserve room for noof additional bytes, but don't adjust length (yet) */ FL struct n_string *n_string_reserve(struct n_string *self, uz noof su_DBG_LOC_ARGS_DECL); #define n_string_book n_string_reserve /* Resize to exactly nlen bytes; any new storage isn't initialized */ FL struct n_string *n_string_resize(struct n_string *self, uz nlen su_DBG_LOC_ARGS_DECL); #ifdef su_HAVE_DBG_LOC_ARGS # define n_string_reserve(S,N) (n_string_reserve)(S, N su_DBG_LOC_ARGS_INJ) # define n_string_resize(S,N) (n_string_resize)(S, N su_DBG_LOC_ARGS_INJ) #endif /* */ FL struct n_string *n_string_push_buf(struct n_string *self, char const *buf, uz buflen su_DBG_LOC_ARGS_DECL); #define n_string_push(S, T) n_string_push_buf(S, (T)->s_len, (T)->s_dat) #define n_string_push_cp(S,CP) n_string_push_buf(S, CP, UZ_MAX) FL struct n_string *n_string_push_c(struct n_string *self, char c su_DBG_LOC_ARGS_DECL); #define n_string_assign(S,T) ((S)->s_len = 0, n_string_push(S, T)) #define n_string_assign_c(S,C) ((S)->s_len = 0, n_string_push_c(S, C)) #define n_string_assign_cp(S,CP) ((S)->s_len = 0, n_string_push_cp(S, CP)) #define n_string_assign_buf(S,B,BL) \ ((S)->s_len = 0, n_string_push_buf(S, B, BL)) #ifdef su_HAVE_DBG_LOC_ARGS # define n_string_push_buf(S,B,BL) \ (n_string_push_buf)(S, B, BL su_DBG_LOC_ARGS_INJ) # define n_string_push_c(S,C) (n_string_push_c)(S, C su_DBG_LOC_ARGS_INJ) #endif /* */ FL struct n_string *n_string_unshift_buf(struct n_string *self, char const *buf, uz buflen su_DBG_LOC_ARGS_DECL); #define n_string_unshift(S,T) \ n_string_unshift_buf(S, (T)->s_len, (T)->s_dat) #define n_string_unshift_cp(S,CP) \ n_string_unshift_buf(S, CP, UZ_MAX) FL struct n_string *n_string_unshift_c(struct n_string *self, char c su_DBG_LOC_ARGS_DECL); #ifdef su_HAVE_DBG_LOC_ARGS # define n_string_unshift_buf(S,B,BL) \ (n_string_unshift_buf)(S, B, BL su_DBG_LOC_ARGS_INJ) # define n_string_unshift_c(S,C) \ (n_string_unshift_c)(S, C su_DBG_LOC_ARGS_INJ) #endif /* */ FL struct n_string *n_string_insert_buf(struct n_string *self, uz idx, char const *buf, uz buflen su_DBG_LOC_ARGS_DECL); #define n_string_insert(S,I,T) \ n_string_insert_buf(S, I, (T)->s_len, (T)->s_dat) #define n_string_insert_cp(S,I,CP) \ n_string_insert_buf(S, I, CP, UZ_MAX) FL struct n_string *n_string_insert_c(struct n_string *self, uz idx, char c su_DBG_LOC_ARGS_DECL); #ifdef su_HAVE_DBG_LOC_ARGS # define n_string_insert_buf(S,I,B,BL) \ (n_string_insert_buf)(S, I, B, BL su_DBG_LOC_ARGS_INJ) # define n_string_insert_c(S,I,C) \ (n_string_insert_c)(S, I, C su_DBG_LOC_ARGS_INJ) #endif /* */ FL struct n_string *n_string_cut(struct n_string *self, uz idx, uz len); /* Ensure self has a - NUL terminated - buffer, and return that. * The latter may return the pointer to an internal empty RODATA instead */ FL char *n_string_cp(struct n_string *self su_DBG_LOC_ARGS_DECL); FL char const *n_string_cp_const(struct n_string const *self); #ifdef su_HAVE_DBG_LOC_ARGS # define n_string_cp(S) (n_string_cp)(S su_DBG_LOC_ARGS_INJ) #endif /* * thread.c */ /* */ FL int c_thread(void *vp); /* */ FL int c_unthread(void *vp); /* */ FL struct message * next_in_thread(struct message *mp); FL struct message * prev_in_thread(struct message *mp); FL struct message * this_in_thread(struct message *mp, long n); /* Sorted mode is internally just a variant of threaded mode with all m_parent * and m_child links being NULL */ FL int c_sort(void *vp); /* */ FL int c_collapse(void *v); FL int c_uncollapse(void *v); /* */ FL void uncollapse1(struct message *mp, int always); /* * tls.c */ #ifdef mx_HAVE_TLS /* */ FL void n_tls_set_verify_level(struct mx_url const *urlp); /* */ FL boole n_tls_verify_decide(void); /* */ FL enum okay smime_split(FILE *ip, FILE **hp, FILE **bp, long xcount, int keep); /* */ FL FILE * smime_sign_assemble(FILE *hp, FILE *bp, FILE *sp, char const *message_digest); /* */ FL FILE * smime_encrypt_assemble(FILE *hp, FILE *yp); /* */ FL struct message * smime_decrypt_assemble(struct message *m, FILE *hp, FILE *bp); /* `certsave' */ FL int c_certsave(void *vp); /* */ FL boole n_tls_rfc2595_hostname_match(char const *host, char const *pattern); /* `tls' */ FL int c_tls(void *vp); #endif /* mx_HAVE_TLS */ /* * xtls.c */ #ifdef mx_HAVE_XTLS /* Our wrapper for RAND_bytes(3); the implementation exists only when * HAVE_RANDOM is RANDOM_IMPL_TLS, though */ FL void mx_tls_rand_bytes(void *buf, uz blen); /* Will fill in a non-NULL *urlp->url_cert_fprint with auto-reclaimed * buffer on success, otherwise urlp is constant */ FL boole n_tls_open(struct mx_url *urlp, struct mx_socket *sp); /* */ FL void ssl_gen_err(char const *fmt, ...); /* */ FL int c_verify(void *vp); /* */ FL FILE * smime_sign(FILE *ip, char const *addr); /* */ FL FILE * smime_encrypt(FILE *ip, char const *certfile, char const *to); FL struct message * smime_decrypt(struct message *m, char const *to, char const *cc, boole is_a_verify_call); /* */ FL enum okay smime_certsave(struct message *m, int n, FILE *op); #endif /* mx_HAVE_XTLS */ /* * obs-imap.c */ #ifdef mx_HAVE_IMAP FL void n_go_onintr_for_imap(void); /* The former returns the input again if no conversion is necessary */ FL char const *imap_path_encode(char const *path, boole *err_or_null); FL char *imap_path_decode(char const *path, boole *err_or_null); FL char const * imap_fileof(char const *xcp); FL enum okay imap_noop(void); FL enum okay imap_select(struct mailbox *mp, off_t *size, int *count, const char *mbx, enum fedit_mode fm); FL int imap_setfile(char const *who, const char *xserver, enum fedit_mode fm); FL enum okay imap_header(struct message *m); FL enum okay imap_body(struct message *m); FL void imap_getheaders(int bot, int top); FL boole imap_quit(boole hold_sigs_on); FL enum okay imap_undelete(struct message *m, int n); FL enum okay imap_unread(struct message *m, int n); FL int c_imapcodec(void *vp); FL int c_imap_imap(void *vp); FL int imap_newmail(int nmail); FL enum okay imap_append(const char *xserver, FILE *fp, long offset); FL int imap_folders(const char *name, int strip); FL enum okay imap_copy(struct message *m, int n, const char *name); # ifdef mx_HAVE_IMAP_SEARCH FL sz imap_search1(const char *spec, int f); # endif FL int imap_thisaccount(const char *cp); FL enum okay imap_remove(const char *name); FL enum okay imap_rename(const char *old, const char *new); FL enum okay imap_dequeue(struct mailbox *mp, FILE *fp); FL int c_connect(void *vp); FL int c_disconnect(void *vp); FL int c_cache(void *vp); FL int disconnected(const char *file); FL void transflags(struct message *omessage, long omsgCount, int transparent); FL time_t imap_read_date_time(const char *cp); FL const char * imap_make_date_time(time_t t); /* Extract the protocol base and return a duplicate */ FL char *protbase(char const *cp su_DBG_LOC_ARGS_DECL); # ifdef su_HAVE_DBG_LOC_ARGS # define protbase(CP) (protbase)(CP su_DBG_LOC_ARGS_INJ) # endif #endif /* mx_HAVE_IMAP */ /* * obs-imap-cache.c */ #ifdef mx_HAVE_IMAP FL enum okay getcache1(struct mailbox *mp, struct message *m, enum needspec need, int setflags); FL enum okay getcache(struct mailbox *mp, struct message *m, enum needspec need); FL void putcache(struct mailbox *mp, struct message *m); FL void initcache(struct mailbox *mp); FL void purgecache(struct mailbox *mp, struct message *m, long mc); FL void delcache(struct mailbox *mp, struct message *m); FL enum okay cache_setptr(enum fedit_mode fm, int transparent); FL enum okay cache_list(struct mailbox *mp, char const *base, int strip, FILE *fp); FL enum okay cache_remove(char const *name); FL enum okay cache_rename(char const *old, char const *new); FL u64 cached_uidvalidity(struct mailbox *mp); FL FILE * cache_queue(struct mailbox *mp); FL enum okay cache_dequeue(struct mailbox *mp); #endif /* mx_HAVE_IMAP */ /* * obs-lzw.c */ #ifdef mx_HAVE_IMAP FL int zwrite(void *cookie, const char *wbp, int num); FL int zfree(void *cookie); FL int zread(void *cookie, char *rbp, int num); FL void * zalloc(FILE *fp); #endif /* mx_HAVE_IMAP */ #ifndef mx_HAVE_AMALGAMATION # undef FL # define FL #endif /* s-it-mode */ s-nail-14.9.15/include/mx/names.h000066400000000000000000000162461352610246600164170ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Names (read: addresses), including `alternates' and `alias' lists. *@ TODO It should be solely that, parsing etc. should be in header.c, *@ TODO or rfc5322.c or something like this. * * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef mx_NAMES_H #define mx_NAMES_H /* XXX a lie - it is rather n_ yet */ #include #define mx_HEADER #include enum mx_name_flags{ mx_NAME_SKINNED = 1u<<0, /* Has been skin()ned */ mx_NAME_IDNA = 1u<<1, /* IDNA has been applied */ mx_NAME_NAME_SALLOC = 1u<<2, /* .n_name in detached memory */ mx_NAME_ADDRSPEC_ISFILE = 1u<<3, /* ..is a file path */ mx_NAME_ADDRSPEC_ISPIPE = 1u<<4, /* ..is a command for piping */ mx_NAME_ADDRSPEC_ISFILEORPIPE = mx_NAME_ADDRSPEC_ISFILE | mx_NAME_ADDRSPEC_ISPIPE, mx_NAME_ADDRSPEC_ISNAME = 1u<<5, /* ..is an alias name */ mx_NAME_ADDRSPEC_ISADDR = 1u<<6, /* ..is a mail network address.. */ mx_NAME_ADDRSPEC_WITHOUT_DOMAIN = 1u<<7, /* ..but without domain name */ mx_NAME_ADDRSPEC_ISMASK = su_BITENUM_MASK(3,6), /* Bits not values for easy & testing */ mx_NAME_ADDRSPEC_ERR_EMPTY = 1u<<9, /* An empty string (or NIL) */ mx_NAME_ADDRSPEC_ERR_ATSEQ = 1u<<10, /* Weird @ sequence */ mx_NAME_ADDRSPEC_ERR_CHAR = 1u<<11, /* Invalid character */ mx_NAME_ADDRSPEC_ERR_IDNA = 1u<<12, /* IDNA convertion failed */ mx_NAME_ADDRSPEC_ERR_NAME = 1u<<13, /* Alias with invalid content */ mx_NAME_ADDRSPEC_INVALID = mx_NAME_ADDRSPEC_ERR_EMPTY | mx_NAME_ADDRSPEC_ERR_ATSEQ | mx_NAME_ADDRSPEC_ERR_CHAR | mx_NAME_ADDRSPEC_ERR_IDNA | mx_NAME_ADDRSPEC_ERR_NAME, /* Error storage (we must fit in 31-bit!) */ mx__NAME_SHIFTWC = 14, mx__NAME_MAXWC = 0x1FFFF, mx__NAME_MASKWC = mx__NAME_MAXWC << mx__NAME_SHIFTWC /* Bit 31 (32) == S32_MIN temporarily used */ }; struct mx_name{ struct mx_name *n_flink; struct mx_name *n_blink; enum gfield n_type; /* Header field this comes from */ u32 n_flags; /* enum mx_name_flags */ char *n_name; char *n_fullname; /* .n_name, unless GFULL: +comments, etc */ char *n_fullextra; /* GFULL, without address */ }; /* In the !_ERR_EMPTY case, the failing character can be queried */ INLINE s32 mx_name_flags_get_err_wc(u32 flags){ return (((flags & mx__NAME_MASKWC) >> mx__NAME_SHIFTWC) & mx__NAME_MAXWC); } /* ..where err is mx_name_flags (mix) */ INLINE u32 mx_name_flags_set_err(u32 flags, u32 err, s32 e_wc){ return ((flags & ~(mx_NAME_ADDRSPEC_INVALID | mx__NAME_MASKWC)) | S(u32,err) | ((S(u32,e_wc) & mx__NAME_MAXWC) << mx__NAME_SHIFTWC)); } /* Allocate a single element of a name list, initialize its name field to the * passed name and return it. * May return NULL with GNULL_OK (only, unfortunately) */ EXPORT struct mx_name *nalloc(char const *str, enum gfield ntype); /* Alloc an Fcc: entry TODO temporary only i hope */ EXPORT struct mx_name *nalloc_fcc(char const *file); /* Like nalloc(), but initialize from content of np */ EXPORT struct mx_name *ndup(struct mx_name *np, enum gfield ntype); /* Concatenate the two passed name lists, return the result */ EXPORT struct mx_name *cat(struct mx_name *n1, struct mx_name *n2); /* Duplicate np */ EXPORT struct mx_name *n_namelist_dup(struct mx_name const *np, enum gfield ntype); /* Determine the number of undeleted elements in a name list and return it; * the latter also doesn't count file and pipe addressees in addition */ EXPORT u32 count(struct mx_name const *np); EXPORT u32 count_nonlocal(struct mx_name const *np); /* Extract a list of names from a line, and make a list of names from it. * Return the list or NULL if none found */ EXPORT struct mx_name *extract(char const *line, enum gfield ntype); /* Like extract() unless line contains anyof ",\"\\(<|", in which case * comma-separated list extraction is used instead */ EXPORT struct mx_name *lextract(char const *line, enum gfield ntype); /* Interprets the entire line as one address: identical to extract() and * lextract() but only returns one (or none) name. * GSKIN will be added to ntype as well as GNULL_OK: may return NULL! */ EXPORT struct mx_name *n_extract_single(char const *line, enum gfield ntype); /* Turn a list of names into a string of the same names */ EXPORT char *detract(struct mx_name *np, enum gfield ntype); /* Get a lextract() list via n_go_input_cp(), reassigning to *np* */ EXPORT struct mx_name *grab_names(enum n_go_input_flags gif, char const *field, struct mx_name *np, int comma, enum gfield gflags); /* Check whether n1 n2 share the domain name */ EXPORT boole name_is_same_domain(struct mx_name const *n1, struct mx_name const *n2); /* Check all addresses in np and delete invalid ones; if set_on_error is not * NULL it'll be set to TRU1 for error or -1 for "hard fail" error */ EXPORT struct mx_name *checkaddrs(struct mx_name *np, enum expand_addr_check_mode eacm, s8 *set_on_error); /* Vaporise all duplicate addresses in hp (.h_(to|cc|bcc)) so that an address * that "first" occurs in To: is solely in there, ditto Cc:, after expanding * aliases etc. eacm and set_on_error are passed to checkaddrs(). * After updating hp to the new state this returns a flat list of all * addressees, which may be NIL */ EXPORT struct mx_name *n_namelist_vaporise_head(struct header *hp, boole metoo, boole strip_alternates, enum expand_addr_check_mode eacm, s8 *set_on_error); /* Map all of the aliased users in the invoker's mailrc file and insert them * into the list */ EXPORT struct mx_name *usermap(struct mx_name *names, boole force_metoo); /* Remove all of the duplicates from the passed name list by insertion sorting * them, then checking for dups. Return the head of the new list */ EXPORT struct mx_name *elide(struct mx_name *names); /* `(un)?alias' */ EXPORT int c_alias(void *vp); EXPORT int c_unalias(void *vp); /* Is name a valid alias name (as opposed to: "is an alias") */ EXPORT boole mx_alias_is_valid_name(char const *name); /* `(un)?alternates' deal with the list of alternate names */ EXPORT int c_alternates(void *vp); EXPORT int c_unalternates(void *vp); /* If keep_single is set one alternates member will be allowed in np */ EXPORT struct mx_name *mx_alternates_remove(struct mx_name *np, boole keep_single); /* Likewise, is name an alternate in broadest sense? */ EXPORT boole mx_name_is_mine(char const *name); #include #endif /* mx_NAMES_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/net-pop3.h000066400000000000000000000050501352610246600167500ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ POP3 (RFCs 1939, 2595) client. * * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany. * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: BSD-4-Clause */ /* * Copyright (c) 2002 * Gunnar Ritter. 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 Gunnar Ritter * and his contributors. * 4. Neither the name of Gunnar Ritter nor the names of his contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER 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 GUNNAR RITTER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef mx_NET_POP3_H #define mx_NET_POP3_H #include #ifdef mx_HAVE_POP3 #define mx_HEADER #include /* */ EXPORT enum okay mx_pop3_noop(void); /* */ EXPORT int mx_pop3_setfile(char const *who, char const *server, enum fedit_mode fm); /* */ EXPORT enum okay mx_pop3_header(struct message *m); /* */ EXPORT enum okay mx_pop3_body(struct message *m); /* */ EXPORT boole mx_pop3_quit(boole hold_sigs_on); #include #endif /* mx_HAVE_POP3 */ #endif /* mx_NET_POP3_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/net-smtp.h000066400000000000000000000023411352610246600170520ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ SMTP (simple mail transfer protocol) MTA. * * Copyright (c) 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef mx_NET_SMTP_H #define mx_NET_SMTP_H #include #ifdef mx_HAVE_SMTP #define mx_HEADER #include /* Send a message via SMTP */ EXPORT boole mx_smtp_mta(struct sendbundle *sbp); #include #endif /* mx_HAVE_SMTP */ #endif /* mx_NET_SMTP_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/net-socket.h000066400000000000000000000064621352610246600173670ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Socket operations. TODO enum okay -> boole * * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany. * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: BSD-3-Clause */ /* * 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. */ #ifndef mx_NET_SOCKET_H #define mx_NET_SOCKET_H #include #ifdef mx_HAVE_NET #include #define mx_HEADER #include struct mx_socket{ /* data associated with a socket */ int s_fd; /* file descriptor */ #ifdef mx_HAVE_TLS int s_use_tls; /* TLS is used */ # ifdef mx_HAVE_XTLS void *s_tls; /* TLS object */ # endif char *s_tls_finger; /* Set to autorec! store for CPROTO_CERTINFO */ #endif char *s_wbuf; /* for buffered writes */ int s_wbufsize; /* allocated size of s_buf */ int s_wbufpos; /* position of first empty data byte */ char *s_rbufptr; /* read pointer to s_rbuf */ int s_rsz; /* size of last read in s_rbuf */ char const *s_desc; /* description of error messages */ void (*s_onclose)(void); /* execute on close */ char s_rbuf[LINESIZE + 1]; /* for buffered reads */ }; /* Note: immediately closes the socket again for CPROTO_CERTINFO */ EXPORT boole mx_socket_open(struct mx_socket *sp, struct mx_url *urlp); /* */ EXPORT int mx_socket_close(struct mx_socket *sp); /* */ EXPORT enum okay mx_socket_write(struct mx_socket *sp, char const *data); EXPORT enum okay mx_socket_write1(struct mx_socket *sp, char const *data, int sz, int use_buffer); /* */ EXPORT int mx_socket_getline(char **line, uz *linesize, uz *linelen, struct mx_socket *sp su_DBG_LOC_ARGS_DECL); #ifdef su_HAVE_DBG_LOC_ARGS # define mx_socket_getline(A,B,C,D) \ mx_socket_getline(A, B, C, D su_DBG_LOC_ARGS_INJ) #endif #include #endif /* mx_HAVE_NET */ #endif /* mx_NET_SOCKET_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/random.h000066400000000000000000000033651352610246600165720ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Random string creation. * * Copyright (c) 2015 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef mx_RANDOM_H #define mx_RANDOM_H #include #define mx_HEADER #include /* mx_HAVE_RANDOM: supported (external) PRG implementations */ #define mx_RANDOM_IMPL_BUILTIN 0 #define mx_RANDOM_IMPL_ARC4 1 #define mx_RANDOM_IMPL_TLS 2 #define mx_RANDOM_IMPL_GETRANDOM 3 /* (both, syscall + library) */ #define mx_RANDOM_IMPL_URANDOM 4 /* Get a (pseudo) random string of *len* bytes, _not_ counting the NUL * terminator, the second returns an n_autorec_alloc()ed buffer. * If su_STATE_REPRODUCIBLE and reprocnt_or_nil not NIL then we produce * a reproducable string by using and managing that counter instead */ EXPORT char *mx_random_create_buf(char *dat, uz len, u32 *reprocnt_or_nil); EXPORT char *mx_random_create_cp(uz len, u32 *reprocnt_or_nil); #include #endif /* mx_RANDOM_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/sigs.h000066400000000000000000000102011352610246600162420ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Signal handling and commands heavily related with signals. * * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany. * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: BSD-3-Clause TODO ISC */ /* * Copyright (c) 1980, 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. */ #ifndef mx_SIGS_H #define mx_SIGS_H #include /* TODO FAKE */ #define mx_HEADER #include /* `sleep' */ FL int c_sleep(void *v); /* */ FL void n_raise(int signo); /* Provide BSD-like signal() on all systems TODO v15 -> SysV -> n_signal() */ FL n_sighdl_t safe_signal(int signum, n_sighdl_t handler); /* Provide reproducable non-restartable signal handler installation */ FL n_sighdl_t n_signal(int signo, n_sighdl_t hdl); /* Block all signals except some fatal trap ones and SIGCHLD. * sigadjust starts an optional 0 terminated list of signal adjustments: * a positive one will be sigdelset()ted, a negative one will be added. * Adjusts the list if already active */ FL void mx_sigs_all_hold(s32 sigadjust, ...); #define mx_sigs_all_holdx() mx_sigs_all_hold(0) FL void mx_sigs_all_rele(void); /* Hold HUP/QUIT/INT */ FL void hold_sigs(void); FL void rele_sigs(void); /* Call _ENTER_SWITCH() with the according flags, it'll take care for the rest * and also set the jump buffer - it returns 0 if anything went fine and * a signal number if a jump occurred, in which case all handlers requested in * flags are temporarily SIG_IGN. * _cleanup_ping() informs the condome that no jumps etc. shall be performed * until _leave() is called in the following -- to be (optionally) called right * before the local jump label is reached which is jumped to after a long jump * occurred, straight code flow provided, e.g., to avoid destructors to be * called twice. _leave() must always be called last, reraise_flags will be * used to decide how signal handling has to continue */ #define n_SIGMAN_ENTER_SWITCH(S,F) do{\ int __x__;\ hold_sigs();\ if(sigsetjmp((S)->sm_jump, 1))\ __x__ = -1;\ else\ __x__ = F;\ n__sigman_enter(S, __x__);\ }while(0); switch((S)->sm_signo) FL int n__sigman_enter(struct n_sigman *self, int flags); FL void n_sigman_cleanup_ping(struct n_sigman *self); FL void n_sigman_leave(struct n_sigman *self, enum n_sigman_flags flags); /* Pending signal or 0? */ FL int n_sigman_peek(void); FL void n_sigman_consume(void); /* Not-Yet-Dead debug information (handler installation in main.c) */ #if su_DVLOR(1, 0) FL void mx__nyd_oncrash(int signo); #endif #include #endif /* mx_SIGS_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/termcap.h000066400000000000000000000236371352610246600167510ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Terminal capability interaction. * * Copyright (c) 2016 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef mx_TERMCAP_H #define mx_TERMCAP_H #include /* Switch indicating necessity of terminal access interface. * (As of the time of this writing TERMCAP only with available MLE, but..) */ #if defined mx_HAVE_TERMCAP || defined mx_HAVE_COLOUR || defined mx_HAVE_MLE # define mx_HAVE_TCAP #endif #ifdef mx_HAVE_TCAP #define mx_HEADER #include enum mx_termcap_captype{ mx_TERMCAP_CAPTYPE_NONE, /* Internally we share the bitspace, so ensure no value ends up as 0 */ mx_TERMCAP_CAPTYPE_BOOL, mx_TERMCAP_CAPTYPE_NUMERIC, mx_TERMCAP_CAPTYPE_STRING, mx__TERMCAP_CAPTYPE_MAX1 }; /* Termcap commands; different to queries commands perform actions. * Commands are resolved upon init time, and are all termcap(5)-compatible, * therefore we use the short termcap(5) names. * * Note this is parsed by make-tcap-map.pl, which expects the syntax * "CONSTANT, COMMENT" where COMMENT is "Capname/TCap-Code, TYPE[, FLAGS]", * and one of Capname and TCap-Code may be the string "-" meaning ENOENT; * a | vertical bar or end-of-comment ends processing; see termcap.c. * We may use the free-form part after | for the "Variable String" and notes on * necessary termcap_cmd() arguments; if those are in [] brackets they are not * regular but are only used when the command, i.e., its effect, is somehow * simulated / faked by a built-in fallback implementation. * Availability of built-in fallback indicated by leading @ (at-sign) */ enum mx_termcap_cmd{ # ifdef mx_HAVE_TERMCAP mx_TERMCAP_CMD_te, /* rmcup/te, STRING | exit_ca_mode: -,- */ mx_TERMCAP_CMD_ti, /* smcup/ti, STRING | enter_ca_mode: -,- */ mx_TERMCAP_CMD_ks, /* smkx/ks, STRING | keypad_xmit: -,- */ mx_TERMCAP_CMD_ke, /* rmkx/ke, STRING | keypad_local: -,- */ # endif # ifdef mx_HAVE_MLE mx_TERMCAP_CMD_ce, /* el/ce, STRING | @ clr_eol: [start-column],- */ mx_TERMCAP_CMD_ch, /* hpa/ch, STRING, IDX1 | column_address: column,- */ mx_TERMCAP_CMD_cr, /* cr/cr, STRING | @ carriage_return: -,- */ mx_TERMCAP_CMD_le, /* cub1/le, STRING, CNT | @ cursor_left: count,- */ mx_TERMCAP_CMD_nd, /* cuf1/nd, STRING, CNT | @ cursor_right: count,- */ # ifdef mx_HAVE_TERMCAP mx_TERMCAP_CMD_cd, /* ed/cd, STRING | clr_eos: -,- */ mx_TERMCAP_CMD_ho, /* home/ho, STRING | cursor_home: -,- */ # endif mx_TERMCAP_CMD_cl, /* clear/cl, STRING | clear_screen(+home): -,- */ # endif mx__TERMCAP_CMD_MAX1, mx__TERMCAP_CMD_MASK = (1u<<24) - 1, /* Only perform command if ca-mode is used */ mx_TERMCAP_CMD_FLAG_CA_MODE = 1u<<29, /* I/O should be flushed after command completed */ mx_TERMCAP_CMD_FLAG_FLUSH = 1u<<30 }; /* Termcap queries; a query is a command that returns a struct n_termcap_value. * Queries are resolved once they are used first, and may not be termcap(5)- * compatible, therefore we use terminfo(5) names. * * Note this is parsed by make-tcap-map.pl, which expects the syntax * "CONSTANT, COMMENT" where COMMENT is "Capname/TCap-Code, TYPE[, FLAGS]", * and one of Capname and TCap-Code may be the string "-" meaning ENOENT; * a | vertical bar or end-of-comment ends processing; see termcap.c. * We may use the free-form part after | for the "Variable String" and notes. * The "xkey | X:" keys are Dickey's xterm extensions, see (our) manual */ enum mx_termcap_query{ mx_TERMCAP_QUERY_am, /* am/am, BOOL | auto_right_margin */ mx_TERMCAP_QUERY_sam, /* sam/YE, BOOL | semi_auto_right_margin */ mx_TERMCAP_QUERY_xenl, /* xenl/xn, BOOL | eat_newline_glitch */ # ifdef mx_HAVE_COLOUR mx_TERMCAP_QUERY_colors, /* colors/Co, NUMERIC | max_colors */ # endif /* --make-tcap-map--: only KEY_BINDINGS follow. DON'T CHANGE THIS LINE! */ /* Update the `bind' manual on change! */ # ifdef mx_HAVE_KEY_BINDINGS mx_TERMCAP_QUERY_key_backspace, /* kbs/kb, STRING */ mx_TERMCAP_QUERY_key_dc, /* kdch1/kD, STRING | delete-character */ mx_TERMCAP_QUERY_key_sdc, /* kDC / *4, STRING | ..shifted */ mx_TERMCAP_QUERY_key_eol, /* kel/kE, STRING | clear-to-end-of-line */ mx_TERMCAP_QUERY_key_exit, /* kext/@9, STRING */ mx_TERMCAP_QUERY_key_ic, /* kich1/kI, STRING | insert character */ mx_TERMCAP_QUERY_key_sic, /* kIC/#3, STRING | ..shifted */ mx_TERMCAP_QUERY_key_home, /* khome/kh, STRING */ mx_TERMCAP_QUERY_key_shome, /* kHOM/#2, STRING | ..shifted */ mx_TERMCAP_QUERY_key_end, /* kend/@7, STRING */ mx_TERMCAP_QUERY_key_send, /* kEND / *7, STRING | ..shifted */ mx_TERMCAP_QUERY_key_npage, /* knp/kN, STRING */ mx_TERMCAP_QUERY_key_ppage, /* kpp/kP, STRING */ mx_TERMCAP_QUERY_key_left, /* kcub1/kl, STRING */ mx_TERMCAP_QUERY_key_sleft, /* kLFT/#4, STRING | ..shifted */ mx_TERMCAP_QUERY_xkey_aleft, /* kLFT3/-, STRING | X: Alt+left */ mx_TERMCAP_QUERY_xkey_cleft, /* kLFT5/-, STRING | X: Control+left */ mx_TERMCAP_QUERY_key_right, /* kcuf1/kr, STRING */ mx_TERMCAP_QUERY_key_sright, /* kRIT/%i, STRING | ..shifted */ mx_TERMCAP_QUERY_xkey_aright, /* kRIT3/-, STRING | X: Alt+right */ mx_TERMCAP_QUERY_xkey_cright, /* kRIT5/-, STRING | X: Control+right */ mx_TERMCAP_QUERY_key_down, /* kcud1/kd, STRING */ mx_TERMCAP_QUERY_xkey_sdown, /* kDN/-, STRING | ..shifted */ mx_TERMCAP_QUERY_xkey_adown, /* kDN3/-, STRING | X: Alt+down */ mx_TERMCAP_QUERY_xkey_cdown, /* kDN5/-, STRING | X: Control+down */ mx_TERMCAP_QUERY_key_up, /* kcuu1/ku, STRING */ mx_TERMCAP_QUERY_xkey_sup, /* kUP/-, STRING | ..shifted */ mx_TERMCAP_QUERY_xkey_aup, /* kUP3/-, STRING | X: Alt+up */ mx_TERMCAP_QUERY_xkey_cup, /* kUP5/-, STRING | X: Control+up */ mx_TERMCAP_QUERY_kf0, /* kf0/k0, STRING */ mx_TERMCAP_QUERY_kf1, /* kf1/k1, STRING */ mx_TERMCAP_QUERY_kf2, /* kf2/k2, STRING */ mx_TERMCAP_QUERY_kf3, /* kf3/k3, STRING */ mx_TERMCAP_QUERY_kf4, /* kf4/k4, STRING */ mx_TERMCAP_QUERY_kf5, /* kf5/k5, STRING */ mx_TERMCAP_QUERY_kf6, /* kf6/k6, STRING */ mx_TERMCAP_QUERY_kf7, /* kf7/k7, STRING */ mx_TERMCAP_QUERY_kf8, /* kf8/k8, STRING */ mx_TERMCAP_QUERY_kf9, /* kf9/k9, STRING */ mx_TERMCAP_QUERY_kf10, /* kf10/k;, STRING */ mx_TERMCAP_QUERY_kf11, /* kf11/F1, STRING */ mx_TERMCAP_QUERY_kf12, /* kf12/F2, STRING */ mx_TERMCAP_QUERY_kf13, /* kf13/F3, STRING */ mx_TERMCAP_QUERY_kf14, /* kf14/F4, STRING */ mx_TERMCAP_QUERY_kf15, /* kf15/F5, STRING */ mx_TERMCAP_QUERY_kf16, /* kf16/F6, STRING */ mx_TERMCAP_QUERY_kf17, /* kf17/F7, STRING */ mx_TERMCAP_QUERY_kf18, /* kf18/F8, STRING */ mx_TERMCAP_QUERY_kf19, /* kf19/F9, STRING */ # endif /* mx_HAVE_KEY_BINDINGS */ mx__TERMCAP_QUERY_MAX1 }; struct mx_termcap_value{ enum mx_termcap_captype tv_captype; u8 tv__dummy[4]; union mx_termcap_value_data{ boole tvd_bool; u32 tvd_numeric; char const *tvd_string; } tv_data; }; /* termcap(3) / xy lifetime handling -- only called if we're n_PSO_INTERACTIVE * but not doing something in n_PO_QUICKRUN_MASK */ EXPORT void mx_termcap_init(void); EXPORT void mx_termcap_destroy(void); /* enter_ca_mode / enable keypad (both if possible). * TODO When complete is not set we won't enter_ca_mode, for example: we don't * TODO want a complete screen clearance after $PAGER returned after displaying * TODO a mail, because otherwise the screen would look differently for normal * TODO stdout display messages. Etc. */ # ifdef mx_HAVE_TERMCAP EXPORT void mx_termcap_resume(boole complete); EXPORT void mx_termcap_suspend(boole complete); # define mx_TERMCAP_RESUME(CPL) do{ mx_termcap_resume(CPL); }while(0) # define mx_TERMCAP_SUSPEND(CPL) do{ mx_termcap_suspend(CPL); }while(0) # endif /* Command multiplexer, returns FAL0 on I/O error, TRU1 on success and TRUM1 * for commands which are not available and have no built-in fallback. * For query options the return represents a true value and -1 error. * Will return FAL0 directly unless we've been initialized. * By convention unused argument slots are given as -1 */ EXPORT sz mx_termcap_cmd(enum mx_termcap_cmd cmd, sz a1, sz a2); # define mx_termcap_cmdx(CMD) mx_termcap_cmd(CMD, -1, -1) /* Query multiplexer. If query is mx__TERMCAP_QUERY_MAX1 then * tvp->tv_data.tvd_string must contain the name of the query to look up; this * is used to lookup just about *any* (string) capability. * Returns TRU1 on success and TRUM1 for queries for which a built-in default * is returned; FAL0 is returned on non-availability; for boolean the return * value equals the result as such (still tvp is mandatory argument) */ EXPORT boole mx_termcap_query(enum mx_termcap_query query, struct mx_termcap_value *tvp); /* Get a mx_termcap_query for name or -1 if it is not known, and -2 if * type wasn't _NONE and the type doesn't match. */ # ifdef mx_HAVE_KEY_BINDINGS EXPORT s32 mx_termcap_query_for_name(char const *name, enum mx_termcap_captype type); EXPORT char const *mx_termcap_name_of_query(enum mx_termcap_query query); # endif #endif /* mx_HAVE_TCAP */ #ifndef mx_TERMCAP_RESUME # define mx_TERMCAP_RESUME(CPL) do{;}while(0) # define mx_TERMCAP_SUSPEND(CPL) do{;}while(0); #endif #include #endif /* mx_TERMCAP_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/termios.h000066400000000000000000000130221352610246600167630ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Terminal attributes and state. * * Copyright (c) 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef mx_TERMIOS_H #define mx_TERMIOS_H #include #define mx_HEADER #include enum mx_termios_cmd{ /* Throw away the entire stack, and restore normal terminal state. * The outermost level will be regulary shutdown, as via POP. * The state_change handlers of all stack entries will be called. * Further bits may not be set (this is 0) */ mx_TERMIOS_CMD_RESET, /* Only when HANDS_OFF is active, only by itself! */ /* mx_TERMIOS_CMD_SET_PGRP = 1u,*/ /* Create a (POPable) environment, as necessary change to the given mode. * An environment carries the terminal mode as well as a possibly installed * on_state_change hook; if such environment is reentered, the state change * hook gets called to resume after the mode has been reestablished. * Likewise, if it is left, it gets called to suspend first. * XXX Any state change requires this, only RAW and RAW_TIMEOUT may be * XXX switched back and forth on the same level (otherwise state_change * XXX needs cmd argument, plus plus plus) */ mx_TERMIOS_CMD_PUSH = 1u<<1, /* Pop stack and restore the terminal setting active before. * If a hook is installed, it will be called first. * If a mode is given, debug version will assert the stack top matches. * Further bits are ignored */ mx_TERMIOS_CMD_POP = 1u<<2, mx__TERMIOS_CMD_CTL_MASK = mx_TERMIOS_CMD_PUSH | mx_TERMIOS_CMD_POP, mx_TERMIOS_CMD_NORMAL = 1u<<3, /* Normal canonical mode */ mx_TERMIOS_CMD_PASSWORD = 2u<<3, /* Password input mode */ mx_TERMIOS_CMD_RAW = 3u<<3, /* Raw mode, use by-(the given-)byte(s) input */ mx_TERMIOS_CMD_RAW_TIMEOUT = 4u<<3, /* Raw mode, use (the given) timeout */ mx_TERMIOS_CMD_HANDS_OFF = 5u<<3, /* We do not own the terminal */ mx__TERMIOS_CMD_ACT_MASK = 7u<<3 }; enum mx_termios_setup{ mx_TERMIOS_SETUP_STARTUP, mx_TERMIOS_SETUP_TERMSIZE }; enum mx_termios_state_change{ mx_TERMIOS_STATE_SUSPEND = 1u<<0, /* Need to suspend terminal state */ mx_TERMIOS_STATE_RESUME = 1u<<1, /* Need to resume terminal state */ mx_TERMIOS_STATE_SIGNAL = 1u<<2, /* Call was caused by a signal */ mx_TERMIOS_STATE_JOB_SIGNAL = 1u<<3, /* It was a job signal indeed */ /* The environment is being popped. * If it is still active, _STATE_SUSPEND will be set in addition. * For HANDS_OFF handlers this will be called in a RESET even in already * suspended state, so no _STATE_SUSPEND is set, then! */ mx_TERMIOS_STATE_POP = 1u<<4 }; /* tiossc is termios_state_change bitmix, cookie is user argument. * signal is only meaningful when _STATE_SIGNAL is set. * Return value indicates whether level shall be CMD_POPped: it is only * honoured if TERMIOS_STATE_SUSPEND and TERMIOS_STATE_SIGNAL are both set. * TODO This "to-pop" return will vanish in v15, we only need it due to longjmp * TODO and of course it sucks since how many levels does the jump cross? * TODO We do not know except when installing setjmps on each and every level, * TODO but we do not; it is ok for this MUA today, but a real generic solution * TODO in v15 will simply not care for signal jumps at all, they suck more */ typedef boole (*mx_termios_on_state_change)(up cookie, u32 tiossc, s32 signal); struct mx_termios_dimension{ u32 tiosd_height; /* .tiosd_height might be deduced via terminal speed, in which case this * still is set to the real terminal height */ u32 tiosd_real_height; u32 tiosd_width; /* .tiosd_width might be reduces deduced by one if we have no termcap * support or if the terminal cannot write in the last column (without * wrapping), in which case this still is set to the real terminal width */ u32 tiosd_real_width; }; /* */ EXPORT_DATA struct mx_termios_dimension mx_termios_dimen; /* For long iterative output, like `list', tabulator-completion, etc., * determine the screen width that should be used */ #define mx_TERMIOS_WIDTH_OF_LISTS() \ (mx_termios_dimen.tiosd_width - (mx_termios_dimen.tiosd_width >> 3)) /* Installs signal handlers etc. Early! */ EXPORT void mx_termios_controller_setup(enum mx_termios_setup what); /* Install a state change hook for the current environment, * which will receive cookie as its user argument. * May not be used in and for the top level */ EXPORT void mx_termios_on_state_change_set(mx_termios_on_state_change hdl, up cookie); /* tiosc is a bitmix of mx_termios_cmd values. * For _RAW and _RAW_TIMEOUT a1 describes VMIN and VTIME, respectively, * for SET_PGRP it is the PID */ EXPORT boole mx_termios_cmd(u32 tiosc, uz a1); #define mx_termios_cmdx(CMD) mx_termios_cmd(CMD, 0) #include #endif /* mx_TERMIOS_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/tty.h000066400000000000000000000066651352610246600161400ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ MLE (Mailx Line Editor) and some more TTY stuff. * * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef mx_TTY_H #define mx_TTY_H #include #define mx_HEADER #include EXPORT_DATA FILE *mx_tty_fp; /* Our terminal output TODO input channel */ /* Return whether user says yes, on STDIN if interactive. * Uses noninteract_default, the return value for non-interactive use cases, * as a hint for n_boolify() and chooses the yes/no string to append to prompt * accordingly. If prompt is NIL "Continue" is used instead. * Handles+reraises SIGINT */ EXPORT boole mx_tty_yesorno(char const *prompt, boole noninteract_default); #ifdef mx_HAVE_NET /* Get a password the expected way, return autorec string on success or NIL */ EXPORT char *mx_tty_getuser(char const *query); /* Get a password the expected way, return autorec string on success or NIL. * SIGINT is temporarily blocked, *not* reraised */ EXPORT char *mx_tty_getpass(char const *query); #endif /* Create the prompt and return its visual width in columns, which may be 0 * if evaluation is disabled etc. The data is placed in store. * xprompt is inspected only if prompt is enabled and no *prompt* evaluation * takes place */ EXPORT u32 mx_tty_create_prompt(struct n_string *store, char const *xprompt, enum n_go_input_flags gif); /* MLE */ /* Overall interactive terminal life cycle for the MLE */ #ifdef mx_HAVE_MLE EXPORT void mx_tty_init(void); EXPORT void mx_tty_destroy(boole xit_fastpath); #else # define mx_tty_init() do{;}while(0) # define mx_tty_destroy(B) do{;}while(0) #endif /* Read a line after printing prompt (if set and non-empty). * If n>0 assumes that *linebuf has n bytes of default content. * histok_or_nil like for go_input(). * Only the _CTX_ bits in lif are used */ EXPORT int mx_tty_readline(enum n_go_input_flags gif, char const *prompt, char **linebuf, uz *linesize, uz n, boole *histok_or_nil su_DBG_LOC_ARGS_DECL); #ifdef su_HAVE_DBG_LOC_ARGS # define mx_tty_readline(A,B,C,D,E,F) \ (mx_tty_readline)(A, B, C, D, E, F su_DBG_LOC_ARGS_INJ) #endif /* Add a line (most likely as returned by tty_readline()) to the history. * Whether and how an entry is added for real depends on gif, e.g., * n_GO_INPUT_HIST_GABBY / *history-gabby* relation. * Empty strings are never stored */ EXPORT void mx_tty_addhist(char const *s, enum n_go_input_flags gif); #ifdef mx_HAVE_HISTORY EXPORT int c_history(void *v); #endif /* `bind' and `unbind' */ #ifdef mx_HAVE_KEY_BINDINGS EXPORT int c_bind(void *v); EXPORT int c_unbind(void *v); #endif #include #endif /* mx_TTY_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/ui-str.h000066400000000000000000000076421352610246600165370ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Visual strings, string classification/preparation for the user interface. * * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany. * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: BSD-3-Clause TODO ISC */ /* * 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. */ #ifndef mx_UI_STR_H #define mx_UI_STR_H #include #ifdef mx_HAVE_C90AMEND1 # include # include #endif #define mx_HEADER #include #ifdef mx_HAVE_C90AMEND1 typedef wchar_t wc_t; # define n_WC_C(X) L ## X #else typedef char wc_t; /* Yep: really 8-bit char */ # define n_WC_C(X) X #endif struct n_visual_info_ctx{ char const *vic_indat; /*I Input data */ uz vic_inlen; /*I If UZ_MAX, su_cs_len(.vic_indat) */ char const *vic_oudat; /*O remains */ uz vic_oulen; uz vic_chars_seen; /*O number of characters processed */ uz vic_bytes_seen; /*O number of bytes passed */ uz vic_vi_width; /*[O] visual width of the entire range */ wc_t *vic_woudat; /*[O] if so requested */ uz vic_woulen; /*[O] entries in .vic_woudat, if used */ wc_t vic_waccu; /*O The last wchar_t/char processed (if any) */ enum n_visual_info_flags vic_flags; /*O Copy of parse flags */ /* TODO bidi */ #ifdef mx_HAVE_C90AMEND1 mbstate_t *vic_mbstate; /*IO .vic_mbs_def used if NULL */ mbstate_t vic_mbs_def; #endif }; /* setlocale(3), *ttycharset* etc. */ EXPORT void n_locale_init(void); /* Parse (onechar of) a given buffer, and generate infos along the way. * If _WOUT_CREATE is set in vif, .vic_woudat will be NUL terminated! * vicp must be zeroed before first use */ EXPORT boole n_visual_info(struct n_visual_info_ctx *vicp, enum n_visual_info_flags vif); /* Check (multibyte-safe) how many bytes of buf (which is blen byts) can be * safely placed in a buffer (field width) of maxlen bytes */ EXPORT uz field_detect_clip(uz maxlen, char const *buf, uz blen); /* Place cp in a autorec_alloc()ed buffer, column-aligned. * For header display only */ EXPORT char *colalign(char const *cp, int col, int fill, int *cols_decr_used_or_nil); /* Convert a string to a displayable one; * prstr() returns the result savestr()d, prout() writes it */ EXPORT void makeprint(struct str const *in, struct str *out); EXPORT uz delctrl(char *cp, uz len); EXPORT char *prstr(char const *s); EXPORT int prout(char const *s, uz sz, FILE *fp); #include #endif /* mx_UI_STR_H */ /* s-it-mode */ s-nail-14.9.15/include/mx/url.h000066400000000000000000000107501352610246600161100ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ UniformResourceLocator. * * Copyright (c) 2014 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef mx_URL_H #define mx_URL_H #include #define mx_HEADER #include #ifdef mx_HAVE_NET /* XXX only for now */ enum mx_url_flags{ mx_URL_TLS_REQUIRED = 1u<<0, /* Whether protocol always uses SSL/TLS.. */ mx_URL_TLS_OPTIONAL = 1u<<1, /* ..may later upgrade to SSL/TLS */ mx_URL_TLS_MASK = mx_URL_TLS_REQUIRED | mx_URL_TLS_OPTIONAL, mx_URL_HAD_USER = 1u<<2, /* Whether .url_user was part of the URL */ mx_URL_HOST_IS_NAME = 1u<<3 /* .url_host not numeric address */ }; struct mx_url{ /* XXX not _ctx: later object */ char const *url_input; /* Input as given (really) */ u32 url_flags; u16 url_portno; /* atoi .url_port or default, host endian */ u8 url_cproto; /* enum cproto as given */ u8 url_proto_len; /* Length of .url_proto (to first '\0') */ char url_proto[16]; /* Communication protocol as 'xy\0://\0' */ char const *url_port; /* Port (if given) or NIL */ struct str url_user; /* User, exactly as given / looked up */ struct str url_user_enc; /* User, url_xenc()oded */ struct str url_pass; /* Pass (url_xdec()oded) or NULL */ /* TODO we don't know whether .url_host is a name or an address. Us * TODO Net::IPAddress::fromString() to check that, then set * TODO URL_HOST_IS_NAME solely based on THAT! Until then, * TODO URL_HOST_IS_NAME ONLY set if n_URL_TLS_MASK+mx_HAVE_GETADDRINFO */ struct str url_host; /* Service hostname TODO we don't know */ struct str url_path; /* Path suffix or NULL */ /* TODO: url_get_component(url *, enum COMPONENT, str *store) */ struct str url_h_p; /* .url_host[:.url_port] */ /* .url_user@.url_host * Note: for CPROTO_SMTP this may resolve HOST via *smtp-hostname* (-> * *hostname*)! (And may later be overwritten according to *from*!) */ struct str url_u_h; struct str url_u_h_p; /* .url_user@.url_host[:.url_port] */ struct str url_eu_h_p; /* .url_user_enc@.url_host[:.url_port] */ char const *url_p_u_h_p; /* .url_proto://.url_u_h_p */ char const *url_p_eu_h_p; /* .url_proto://.url_eu_h_p */ char const *url_p_eu_h_p_p; /* .url_proto://.url_eu_h_p[/.url_path] */ }; #endif /* mx_HAVE_NET */ /* URL en- and decoding according to (enough of) RFC 3986 (RFC 1738). * These return a newly autorec_alloc()ated result, or NIL on length excess */ EXPORT char *mx_url_xenc(char const *cp, boole ispath su_DBG_LOC_ARGS_DECL); EXPORT char *mx_url_xdec(char const *cp su_DBG_LOC_ARGS_DECL); #ifdef su_HAVE_DBG_LOC_ARGS # define mx_url_xenc(CP,P) mx_url_xenc(CP, P su_DBG_LOC_ARGS_INJ) # define mx_url_xdec(CP) mx_url_xdec(CP su_DBG_LOC_ARGS_INJ) #endif /* `urlcodec' */ EXPORT int c_urlcodec(void *vp); /* Parse a RFC 6058 'mailto' URI to a single to: (TODO yes, for now hacky). * Return NIL or something that can be converted to a struct mx_name */ EXPORT char *mx_url_mailto_to_address(char const *mailtop); /* Return port for proto, or NIL if unknown. * Upon sucess *port_or_nil and *issnd_or_nil will be updated, if set; the * latter states whether protocol is of a sending type (SMTP, file etc.). * For file:// and test[://] this returns su_empty, in the former case * *port_or_nil is 0 and in the latter U16_MAX */ EXPORT char const *mx_url_servbyname(char const *proto, u16 *port_or_nil, boole *issnd_or_nil); /* Parse data, which must meet the criteria of the protocol cproto, and fill * in the URL structure urlp (URL rather according to RFC 3986) */ #ifdef mx_HAVE_NET EXPORT boole mx_url_parse(struct mx_url *urlp, enum cproto cproto, char const *data); #endif /* mx_HAVE_NET */ #include #endif /* mx_URL_H */ /* s-it-mode */ s-nail-14.9.15/include/su/000077500000000000000000000000001352610246600151355ustar00rootroot00000000000000s-nail-14.9.15/include/su/a-t-t.h000066400000000000000000000121031352610246600162250ustar00rootroot00000000000000/*@ C++ auto_type_toolbox. For the lazy sort. *@ If su_A_T_T_DECL_ONLY is defined before inclusion, just enough for *@ prototyping a deriviation is provided. * * Copyright (c) 2003 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef su_A_T_T_H #ifndef su_A_T_T_DECL_ONLY # define su_A_T_T_H #endif #ifdef CXX_DOXYGEN /*! * \file * \ingroup COLL * \brief Automatic \r{type_toolbox} toolboxes * * This file has the special property that if the preprocessor variable * \c{su_A_T_T_DECL_ONLY} is defined before it is included, then only minimal * declarations are provided, just enough to prototype an actual usage * \c{typedef}. * A later inclusion (without that variable) will then provide the definitions. */ #endif #include su_USECASE_MX_DISABLED #if !su_C_LANG || defined CXX_DOXYGEN #ifndef su_A_T_T_DECL_ONLY # include #endif #define su_CXX_HEADER #include NSPC_BEGIN(su) template class auto_type_toolbox; #ifndef su_A_T_T_DECL_OK # define su_A_T_T_DECL_OK /*! * \ingroup COLL * \brief Automatic \r{type_toolbox} toolboxes (\r{su/a-t-t.h}) * * Supposed that a (newly created) C++ type provides a basic set of * functionality, easy creation of a toolbox instance becomes possible: * * \cb{ * auto_type_toolbox const att; * * type_toolbox const *ttp = att.get_instance(); * } * * For this to work, we need: * * \list{\li{ * A default constructor and an assignment method, the latter of which with the * \r{su_state_err_type} plus \r{su_state_err_flags} status argument documented * for \r{su_clone_fun}. * }\li{ * An unequality operator \fn{!=}. * }\li{ * A function \fn{uz hash(void) const}. * }} * * \remarks{If \a{T} is a pointer type, the a-t-t will still create heap * clones, so \c{T*} and \c{T} are in fact treated alike!} * * \remarks{Many \SU object types and functionality groups offer * specializations, for example \r{CS}.} */ template class auto_type_toolbox{ public: /*! \_ */ typedef NSPC(su)type_traits type_traits; /*! Accessing this field should be avoided because there may be * specializations which do not offer it -- \r{get_instance()} is inline. */ static type_toolbox const instance; /*! \_ */ static type_toolbox const *get_instance(void) {return &instance;} private: static typename type_traits::tp s_clone(typename type_traits::tp_const t, u32 estate); static void s_delete(typename type_traits::tp self); static typename type_traits::tp s_assign(typename type_traits::tp self, typename type_traits::tp_const t, u32 estate); static sz s_compare(typename type_traits::tp_const self, typename type_traits::tp_const t); static uz s_hash(typename type_traits::tp_const self); }; #endif /* su_A_T_T_DECL_OK */ #ifdef su_A_T_T_DECL_ONLY # undef su_A_T_T_DECL_ONLY #else template PRI STA typename type_traits::tp auto_type_toolbox::s_clone(typename type_traits::tp_const t, u32 estate){ ASSERT_RET(t != NIL, NIL); type_traits::tp self = su_NEW(typename type_traits::type); if(self->assign(t, estate) != 0){ su_DEL(self); self = NIL; } return self; } template PRI STA void auto_type_toolbox::s_delete(typename type_traits::tp self){ ASSERT_RET_VOID(self != NIL); su_DEL(self); } template PRI STA typename type_traits::tp auto_type_toolbox::s_assign(typename type_traits::tp self, typename type_traits::tp_const t, u32 estate){ ASSERT_RET(self != NIL, NIL); ASSER_RET(t != NIL, self); if(self != t){ if(self->assign(t, estate) != 0) self = NIL; } return self; } template PRI STA sz auto_type_toolbox::s_compare(typename type_traits::tp_const self, typename type_traits::tp_const t){ ASSERT_RET(self != NIL, (t != NIL) ? -1 : 0); ASSERT_RET(t != NIL, 1); return self->compare(*t); } template PRI STA uz auto_type_toolbox::s_hash(typename type_traits::tp_const self){ ASSERT_RET(self != NIL, 0); return self->hash(); } template STA type_toolbox const auto_type_toolbox::instance = su_TYPE_TOOLBOX_I9R(&s_clone, &s_delete, &s_assign, &s_compare, &s_hash); #endif // !su_A_T_T_DECL_ONLY NSPC_END(su) #include #endif /* !su_C_LANG || defined CXX_DOXYGEN */ #endif /* su_A_T_T_H */ /* s-it-mode */ s-nail-14.9.15/include/su/avopt.h000066400000000000000000000175311352610246600164460ustar00rootroot00000000000000/*@ Command line option parser. * * Copyright (c) 2001 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef su_AVOPT_H #define su_AVOPT_H /*! * \file * \ingroup AVOPT * \brief \r{AVOPT} */ #include #define su_HEADER #include C_DECL_BEGIN struct su_avopt; /*! * \defgroup AVOPT Command line argument parser * \ingroup MISC * \brief Command line argument option parser (\r{su/avopt.h}) * * Differences to the POSIX \c{getopt()} standard: * * \list{\li{ * Long options are supported. * They can be mapped to a short option by adding a \c{;CHAR} suffix to * the long option (after the possible \c{:} which indicates an argument). * If so, calling \r{su_avopt_parse()} will return the short option equivalence * instead of only setting \r{su_avopt::avo_current_long_idx} and returning * \r{su_AVOPT_STATE_LONG}. * }\li{ * This implementation always differentiates in between * \r{su_AVOPT_STATE_ERR_OPT} and \r{su_AVOPT_STATE_ERR_ARG} errors. * A leading colon \c{:} in the short option string is thus treated as a normal * argument. * }\li{ * Any printable ASCII character except hyphen-minus \c{-} may be used as an * option. * * Long options can neither contain colon \c{:} (indicating the option has an * argument), semicolon \c{;} (indicating a short option letter equivalent * follows), nor the equal-sign \c{=} (used to separate an option and argument * within the same argument vector slot), however. * * It is best to place the short option colon \c{:} as the first entry in the * short option string, otherwise the colon will be mistreated as an argument * requirement for the preceeding option. * }\li{ * Error messages are not logged. * Instead, the format strings \r{su_avopt_fmt_err_arg} and * \r{su_avopt_fmt_err_opt} can be used with an argument of * \r{su_avopt::avo_current_err_opt}. * }} * * \remarks{Since the return value of \r{su_avopt_parse()} and * \r{su_avopt::avo_current_opt} are both \r{su_s8}, the entire range of bytes * with the high bit set is available to provide short option equivalences for * long options.} * @{ */ /*! \remarks{The values of these constants are ASCII control characters.} */ enum su_avopt_state{ su_AVOPT_STATE_DONE = '\0', /*!< \_ */ su_AVOPT_STATE_LONG = '\001', /*!< \_ */ su_AVOPT_STATE_ERR_ARG = '\002', /*!< \_ */ su_AVOPT_STATE_ERR_OPT = '\003' /*!< \_ */ }; /*! \remarks{Most fields make sense only after \r{su_avopt_parse()} has been * called (at least once).} */ struct su_avopt{ char const *avo_current_arg; /*!< Current argument or \NIL. */ s8 avo_current_opt; /*!< Or a \r{su_avopt_state} constant. */ u8 avo_flags; /*! Only useful if \r{su_AVOPT_STATE_LONG} has been returned (and can be * found in \r{su_avopt::avo_current_opt}). */ u16 avo_current_long_idx; u32 avo_argc; /*!< Remaining count. */ char const * const *avo_argv; /*!< Remaining entries. */ char const *avo_curr; char const *avo_opts_short; /*!< Short options as given. */ char const * const *avo_opts_long; /*!< Long options as given. */ /*! The current option that lead to an error as a NUL terminated string. * In case of long options only this may include the argument, too, i.e., * the entire argument vector entry which caused failure. * Only useful if any of \r{su_AVOPT_STATE_ERR_ARG} and * \r{su_AVOPT_STATE_ERR_OPT} has occurred. */ char const *avo_current_err_opt; char avo__buf[Z_ALIGN_PZ(2)]; }; /*! \_ */ EXPORT_DATA char const su_avopt_fmt_err_arg[]; /*! \_ */ EXPORT_DATA char const su_avopt_fmt_err_opt[]; /*! One of \a{opts_short_or_nil} and \a{opts_long_or_nil} must be given. * The latter must be a \NIL terminated array. */ EXPORT struct su_avopt *su_avopt_setup(struct su_avopt *self, u32 argc, char const * const *argv, char const *opts_short_or_nil, char const * const *opts_long_or_nil); /*! Returns either a member of \r{su_avopt_state} to indicate errors and * other states, or a short option character. * In case of \r{su_AVOPT_STATE_LONG}, \r{su_avopt::avo_current_long_idx} * points to the entry in \r{su_avopt::avo_opts_long} that has been * detected. */ EXPORT s8 su_avopt_parse(struct su_avopt *self); /*! If there are long options, query them all in order and dump them via the * given pointer to function \a{ptf}. * If there is a short option equivalence, \a{sopt} is not the empty string. * Options will be preceeded with \c{-} and \c{--}, respectively. * Stops when \a{ptf} returns \FAL0, otherwise returns \TRU1. * * \remarks{The long option string is copied over to a stack buffer of 128 * bytes (\a{lopt}), any excess is cut off.} * * \remarks{The documentation string \a{doc} always points to a byte in the * corresponding long option string passed in by the user.} */ EXPORT boole su_avopt_dump_doc(struct su_avopt const *self, boole (*ptf)(up cookie, boole has_arg, char const *sopt, char const *lopt, char const *doc), up cookie); /*! @} */ C_DECL_END #include #if !su_C_LANG || defined CXX_DOXYGEN # define su_CXX_HEADER # include NSPC_BEGIN(su) class avopt; /*! * \ingroup AVOPT * C++ variant of \r{AVOPT} (\r{su/avopt.h}) */ class EXPORT avopt : private su_avopt{ public: /*! \copydoc{su_avopt_state} */ enum state{ /*! \copydoc{su_AVOPT_STATE_DONE} */ state_done = su_AVOPT_STATE_DONE, /*! \copydoc{su_AVOPT_STATE_LONG} */ state_long = su_AVOPT_STATE_LONG, /*! \copydoc{su_AVOPT_STATE_ERR_ARG} */ state_err_arg = su_AVOPT_STATE_ERR_ARG, /*! \copydoc{su_AVOPT_STATE_ERR_OPT} */ state_err_opt = su_AVOPT_STATE_ERR_OPT }; /*! \copydoc{su_avopt_setup()} */ avopt(u32 argc, char const * const *argv, char const *opts_short, char const * const *opts_long=NIL){ su_avopt_setup(this, argc, argv, opts_short, opts_long); } /*! \_ */ ~avopt(void){} /*! \copydoc{su_avopt_parse()} */ s8 parse(void) {return su_avopt_parse(this);} /*! \copydoc{su_avopt::avo_current_arg} */ char const *current_arg(void) const {return avo_current_arg;} /*! \copydoc{su_avopt::avo_current_opt} */ s8 current_opt(void) const {return avo_current_opt;} /*! \copydoc{su_avopt::avo_current_long_idx} */ u16 current_long_idx(void) const {return avo_current_long_idx;} /*! \copydoc{su_avopt::avo_current_err_opt} */ char const *current_err_opt(void) const {return avo_current_err_opt;} /*! \copydoc{su_avopt::avo_argc} */ s32 argc(void) const {return avo_argc;} /*! \copydoc{su_avopt::avo_argv} */ char const * const *argv(void) const {return avo_argv;} /*! \copydoc{su_avopt::avo_opts_short} */ char const *opts_short(void) const {return avo_opts_short;} /*! \copydoc{su_avopt::avo_opts_long} */ char const * const *opts_long(void) const {return avo_opts_long;} /*! \copydoc{su_avopt_dump_doc()} */ boole dump_doc(boole (*ptf)(up cookie, boole has_arg, char const *sopt, char const *lopt, char const *doc), up cookie=NIL) const{ return su_avopt_dump_doc(this, ptf, cookie); } }; NSPC_END(su) # include #endif /* !C_LANG || CXX_DOXYGEN */ #endif /* su_AVOPT_H */ /* s-it-mode */ s-nail-14.9.15/include/su/bits.h000066400000000000000000000215141352610246600162520ustar00rootroot00000000000000/*@ Bit operations. TODO asm optimizations * * Copyright (c) 2001 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef su_BITS_H #define su_BITS_H /*! * \file * \ingroup BITS * \brief \r{BITS} */ #include #define su_HEADER #include C_DECL_BEGIN /*! * \defgroup BITS Bit operations * \ingroup MISC * \brief Bit operations (\r{su/bits.h}) * @{ */ /*! Round up \a{BITS} to the next \r{su_UZ_BITS} multiple. * \remarks{\a{BITS} may not be 0.} */ #define su_BITS_ROUNDUP(BITS) (((BITS) + (su_UZ_BITS - 1)) & ~(su_UZ_BITS - 1)) /*! Calculate needed number of \r{su_uz} (array entries) to store \a{BITS}. * \remarks{\a{BITS} may not be 0.} */ #define su_BITS_TO_UZ(BITS) (su_BITS_ROUNDUP(BITS) / su_UZ_BITS) /*! Array offset for \a{BIT}. */ #define su_BITS_WHICH_OFF(BIT) ((BIT) / su_UZ_BITS) /*! Bit to test in slot indexed via \r{su_BITS_WHICH_OFF()}. */ #define su_BITS_WHICH_BIT(BIT) ((BIT) & (su_UZ_BITS - 1)) /*! Maximum useful offset in a \r{su_uz} array for \a{BITS}. * \remarks{\a{BITS} may not be 0.} */ #define su_BITS_TOPOFF(BITS) (su_BITS_TO_UZ(BITS) - 1) /*! Number of useful bits in the topmost offset of a \r{su_uz} array used to * store \a{BITS}. * \remarks{\a{BITS} may not be 0.} */ #define su_BITS_TOPBITNO(BITS) (su_UZ_BITS - (su_BITS_ROUNDUP(BITS) - (BITS))) /*! The mask for the topmost offset of a \r{su_uz} array used to store * store \a{BITS}. * \remarks{\a{BITS} may not be 0.} */ #define su_BITS_TOPMASK(BITS) (su_UZ_MAX >> (su_BITS_ROUNDUP(BITS) - (BITS))) /*! \_ */ INLINE boole su_bits_test(uz x, uz bit){ ASSERT_RET(bit < UZ_BITS, FAL0); return ((x & (1lu << bit)) != 0); } /*! \_ */ INLINE uz su_bits_set(uz x, uz bit){ ASSERT_RET(bit < UZ_BITS, x); return (x | (1lu << bit)); } /*! \_ */ INLINE uz su_bits_flip(uz x, uz bit){ ASSERT_RET(bit < UZ_BITS, x); return (x ^ (1lu << bit)); } /*! \_ */ INLINE uz su_bits_clear(uz x, uz bit){ ASSERT_RET(bit < UZ_BITS, x); return (x & ~(1lu << bit)); } /*! \_ */ INLINE boole su_bits_test_and_set(uz *xp, uz bit){ boole rv; ASSERT_RET(xp != NIL, FAL0); ASSERT_RET(bit < UZ_BITS, FAL0); bit = 1lu << bit; rv = ((*xp & bit) != 0); *xp |= bit; return rv; } /*! \_ */ INLINE boole su_bits_test_and_flip(uz *xp, uz bit){ boole rv; ASSERT_RET(xp != NIL, FAL0); ASSERT_RET(bit < UZ_BITS, FAL0); bit = 1lu << bit; rv = ((*xp & bit) != 0); *xp ^= bit; return rv; } /*! \_ */ INLINE boole su_bits_test_and_clear(uz *xp, uz bit){ boole rv; ASSERT_RET(xp != NIL, FAL0); ASSERT_RET(bit < UZ_BITS, FAL0); bit = 1lu << bit; rv = ((*xp & bit) != 0); *xp &= ~bit; return rv; } /*! \r{su_UZ_MAX} if none found. */ INLINE uz su_bits_find_first_set(uz x){ uz i = 0; if(x != 0) do if(x & 1) return i; while((++i, x >>= 1)); return UZ_MAX; } /*! \r{su_UZ_MAX} if none found. */ INLINE uz su_bits_find_last_set(uz x){ if(x != 0){ uz i = UZ_BITS - 1; do if(x & (1lu << i)) return i; while(i--); } return UZ_MAX; } /*! \_ */ INLINE uz su_bits_rotate_left(uz x, uz bits){ ASSERT_RET(bits < UZ_BITS, x); return ((x << bits) | (x >> (UZ_BITS - bits))); } /*! \_ */ INLINE uz su_bits_rotate_right(uz x, uz bits){ ASSERT_RET(bits < UZ_BITS, x); return ((x >> bits) | (x << (UZ_BITS - bits))); } /*! \_ */ INLINE boole su_bits_array_test(uz const *xap, uz bit){ ASSERT_RET(xap != NIL, FAL0); return su_bits_test(xap[su_BITS_WHICH_OFF(bit)], su_BITS_WHICH_BIT(bit)); } /*! \_ */ INLINE void su_bits_array_set(uz *xap, uz bit){ ASSERT_RET_VOID(xap != NIL); xap += su_BITS_WHICH_OFF(bit); *xap = su_bits_set(*xap, su_BITS_WHICH_BIT(bit)); } /*! \_ */ INLINE void su_bits_array_flip(uz *xap, uz bit){ ASSERT_RET_VOID(xap != NIL); xap += su_BITS_WHICH_OFF(bit); *xap = su_bits_flip(*xap, su_BITS_WHICH_BIT(bit)); } /*! \_ */ INLINE void su_bits_array_clear(uz *xap, uz bit){ ASSERT_RET_VOID(xap != NIL); xap += su_BITS_WHICH_OFF(bit); *xap = su_bits_clear(*xap, su_BITS_WHICH_BIT(bit)); } /*! \_ */ INLINE boole su_bits_array_test_and_set(uz *xap, uz bit){ ASSERT_RET(xap != NIL, FAL0); xap += su_BITS_WHICH_OFF(bit); return su_bits_test_and_set(xap, su_BITS_WHICH_BIT(bit)); } /*! \_ */ INLINE boole su_bits_array_test_and_flip(uz *xap, uz bit){ ASSERT_RET(xap != NIL, FAL0); xap += su_BITS_WHICH_OFF(bit); return su_bits_test_and_flip(xap, su_BITS_WHICH_BIT(bit)); } /*! \_ */ INLINE boole su_bits_array_test_and_clear(uz *xap, uz bit){ ASSERT_RET(xap != NIL, FAL0); xap += su_BITS_WHICH_OFF(bit); return su_bits_test_and_clear(xap, su_BITS_WHICH_BIT(bit)); } #if 0 /* TODO port array_find_first() */ /*! \_ */ EXTERN uz su_bits_array_find_first_set(uz const *xap, uz xaplen); /*! \_ */ EXTERN uz su_bits_array_find_last_set(uz const *xap, uz xaplen); /*! \_ */ EXTERN uz su_bits_array_find_first_set_after(uz const *xap, uz xaplen, uz startbit); #endif /*! @} */ C_DECL_END #include #if !su_C_LANG || defined CXX_DOXYGEN # define su_CXX_HEADER # include NSPC_BEGIN(su) class bits; /*! * \ingroup BITS * C++ variant of \r{BITS} (\r{su/bits.h}) */ class bits{ public: /*! \copydoc{su_bits_test()} */ static boole test(uz x, uz bit) {return su_bits_test(x, bit);} /*! \copydoc{su_bits_set()} */ static uz set(uz x, uz bit) {return su_bits_set(x, bit);} /*! \copydoc{su_bits_flip()} */ static uz flip(uz x, uz bit) {return su_bits_flip(x, bit);} /*! \copydoc{su_bits_clear()} */ static uz clear(uz x, uz bit) {return su_bits_clear(x, bit);} /*! \copydoc{su_bits_test_and_set()} */ static boole test_and_set(uz *xp, uz bit){ return su_bits_test_and_set(xp, bit); } /*! \copydoc{su_bits_test_and_flip()} */ static boole test_and_flip(uz *xp, uz bit){ return su_bits_test_and_flip(xp, bit); } /*! \copydoc{su_bits_test_and_clear()} */ static boole test_and_clear(uz *xp, uz bit){ return su_bits_test_and_clear(xp, bit); } /*! \copydoc{su_bits_find_first_set()} */ static uz find_first_set(uz x) {return su_bits_find_first_set(x);} /*! \copydoc{su_bits_find_last_set()} */ static uz find_last_set(uz x) {return su_bits_find_last_set(x);} /*! \copydoc{su_bits_rotate_left()} */ static uz rotate_left(uz x, uz bits) {return su_bits_rotate_left(x, bits);} /*! \copydoc{su_bits_rotate_right()} */ static uz rotate_right(uz x, uz bits){ return su_bits_rotate_right(x, bits); } /*! \copydoc{su_bits_array_test()} */ static boole array_test(uz const *xap, uz bit){ return su_bits_array_test(xap, bit); } /*! \copydoc{su_bits_array_set()} */ static void array_set(uz *xap, uz bit) {su_bits_array_set(xap, bit);} /*! \copydoc{su_bits_array_flip()} */ static void array_flip(uz *xap, uz bit) {su_bits_array_flip(xap, bit);} /*! \copydoc{su_bits_array_clear()} */ static void array_clear(uz *xap, uz bit) {su_bits_array_clear(xap, bit);} /*! \copydoc{su_bits_array_test_and_set()} */ static boole array_test_and_set(uz *xap, uz bit){ return su_bits_array_test_and_set(xap, bit); } /*! \copydoc{su_bits_array_test_and_flip()} */ static boole array_test_and_flip(uz *xap, uz bit){ return su_bits_array_test_and_flip(xap, bit); } /*! \copydoc{su_bits_array_test_and_clear()} */ static boole array_test_and_clear(uz *xap, uz bit){ return su_bits_array_test_and_clear(xap, bit); } #if 0 /* TODO port array_find_first() */ /*! \copydoc{su_bits_array_find_first_set()} */ static uz array_find_first_set(uz const *xap, uz xaplen){ return su_bits_array_find_first_set(xap, xaplen); } /*! \copydoc{su_bits_array_find_last_set()} */ static uz array_find_last_set(uz const *xap, uz xaplen){ return su_bits_array_find_last_set(xap, xaplen); } /*! \copydoc{su_bits_array_find_first_set_after()} */ static uz array_find_first_set_after(uz const *xap, uz xaplen, uz startbit){ return su_bits_array_find_first_set_after(xap, xaplen, startbit); } #endif }; NSPC_END(su) # include #endif /* !C_LANG || CXX_DOXYGEN */ #endif /* su_BITS_H */ /* s-it-mode */ s-nail-14.9.15/include/su/code-in.h000066400000000000000000000306421352610246600166310ustar00rootroot00000000000000/*@ Internal: opposite of code-ou.h. * * Copyright (c) 2003 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef su_CODE_H # error su/code-in.h must be included after su/code.h #endif #ifdef su_CODE_IN_H # error su/code-ou.h must be included before including su/code-in.h again #endif #define su_CODE_IN_H /* LANG */ #undef C_LANG #undef C_DECL_BEGIN #undef C_DECL_END #define C_LANG su_C_LANG #define C_DECL_BEGIN su_C_DECL_BEGIN #define C_DECL_END su_C_DECL_END #define NSPC_BEGIN su_NSPC_BEGIN #define NSPC_END su_NSPC_END #define NSPC_USE su_NSPC_USE #define NSPC su_NSPC #if defined su_CXX_HEADER || (defined su_SOURCE && !su_C_LANG) # define CLASS_NO_COPY su_CLASS_NO_COPY # define SELFTHIS_RET su_SELFTHIS_RET # define PUB su_PUB # define PRO su_PRO # define PRI su_PRI # define STA su_STA # define VIR su_VIR # define OVR su_OVR # define OVRX su_OVRX #endif #define S su_S #define R su_R #define C su_C #define NIL su_NIL #define SHADOW su_SHADOW #if defined su_HEADER || defined su_CXX_HEADER # ifdef su_SOURCE # define EXPORT su_EXPORT # define EXPORT_DATA su_EXPORT_DATA # define IMPORT su_IMPORT # define IMPORT_DATA su_IMPORT_DATA # endif #elif defined mx_HEADER # ifdef mx_SOURCE # define EXPORT su_EXPORT # define EXPORT_DATA su_EXPORT_DATA # define IMPORT su_IMPORT # define IMPORT_DATA su_IMPORT_DATA # endif #elif defined rf_HEADER # ifdef rf_SOURCE # define EXPORT su_EXPORT # define EXPORT_DATA su_EXPORT_DATA # define IMPORT su_IMPORT # define IMPORT_DATA su_IMPORT_DATA # endif #endif #ifndef EXPORT # define EXPORT su_IMPORT # define EXPORT_DATA su_IMPORT_DATA # define IMPORT su_IMPORT # define IMPORT_DATA su_IMPORT_DATA #endif #define CTA su_CTA #define LCTA su_LCTA #define CTAV su_CTAV #define LCTAV su_LCTAV #define MCTA su_MCTA /* CC */ #define INLINE su_INLINE #define SINLINE su_SINLINE #define LIKELY su_LIKELY #define UNLIKELY su_UNLIKELY /* SUPPORT MACROS+ */ #undef ABS #undef CLIP #undef MAX #undef MIN #undef IS_POW2 #define ABS su_ABS #define CLIP su_CLIP #define MAX su_MAX #define MIN su_MIN #define IS_POW2 su_IS_POW2 #define ALIGNOF su_ALIGNOF #define Z_ALIGN su_Z_ALIGN #define Z_ALIGN_SMALL su_Z_ALIGN_SMALL #define Z_ALIGN_PZ su_Z_ALIGN_PZ /* ASSERT series */ #define ASSERT_INJ su_ASSERT_INJ #define ASSERT_NB su_ASSERT_NB #define ASSERT su_ASSERT #define ASSERT_LOC su_ASSERT_LOC #define ASSERT_EXEC su_ASSERT_EXEC #define ASSERT_EXEC_LOC su_ASSERT_EXEC_LOC #define ASSERT_JUMP su_ASSERT_JUMP #define ASSERT_JUMP_LOC su_ASSERT_JUMP_LOC #define ASSERT_RET su_ASSERT_RET #define ASSERT_RET_LOC su_ASSERT_RET_LOC #define ASSERT_RET_VOID su_ASSERT_RET_VOID #define ASSERT_RET_VOID_LOC su_ASSERT_RET_VOID_LOC #define ASSERT_NYD_EXEC su_ASSERT_NYD_EXEC #define ASSERT_NYD_EXEC_LOC su_ASSERT_NYD_EXEC_LOC #define ASSERT_NYD su_ASSERT_NYD #define ASSERT_NYD_LOC su_ASSERT_NYD_LOC #define DBG su_DBG #define NDGB su_NDBG #define DBGOR su_DBGOR #define DVL su_DVL #define NDVL su_NDVL #define DVLOR su_DVLOR #define FIELD_INITN su_FIELD_INITN #define FIELD_INITI su_FIELD_INITI #define FIELD_OFFSETOF su_FIELD_OFFSETOF #define FIELD_RANGEOF su_FIELD_RANGEOF #define FIELD_SIZEOF su_FIELD_SIZEOF #define MT su_MT #define NELEM su_NELEM /* Not-Yet-Dead macros TODO stubs */ #undef NYD_IN #undef NYD_OU #undef NYD #undef NYD2_IN #undef NYD2_OU #undef NYD2 #define NYD_OU_LABEL su_NYD_OU_LABEL #if defined NDEBUG || (!defined su_HAVE_DEBUG && !defined su_HAVE_DEVEL) /**/ #elif defined su_HAVE_DEVEL # define NYD_IN if(1){su_nyd_chirp(1, __FILE__, __LINE__, su_FUN); # define NYD_OU \ goto NYD_OU_LABEL;NYD_OU_LABEL:\ su_nyd_chirp(2, __FILE__, __LINE__, su_FUN);}else{} # define NYD if(0){}else{su_nyd_chirp(0, __FILE__, __LINE__, su_FUN);} # ifdef NYD2 # undef NYD2 # define NYD2_IN if(1){su_nyd_chirp(1, __FILE__, __LINE__, su_FUN); # define NYD2_OU \ goto NYD_OU_LABEL;NYD_OU_LABEL:\ su_nyd_chirp(2, __FILE__, __LINE__, su_FUN);}else{} # define NYD2 if(0){}else{su_nyd_chirp(0, __FILE__, __LINE__, su_FUN);} # endif #else # define NYD_IN do{su_nyd_chirp(1, __FILE__, __LINE__, su_FUN); # define NYD_OU \ goto NYD_OU_LABEL;NYD_OU_LABEL:\ su_nyd_chirp(2, __FILE__, __LINE__, su_FUN);}while(0) # define NYD do{su_nyd_chirp(0, __FILE__, __LINE__, su_FUN);}while(0) # ifdef NYD2 # undef NYD2 # define NYD2_IN do{su_nyd_chirp(1, __FILE__, __LINE__, su_FUN); # define NYD2_OU \ goto NYD_OU_LABEL;NYD_OU_LABEL:\ su_nyd_chirp(2, __FILE__, __LINE__, su_FUN);}while(0) # define NYD2 do{su_nyd_chirp(0, __FILE__, __LINE__, su_FUN);}while(0) # endif #endif /**/ #ifndef NYD # define NYD_IN do{ # define NYD_OU goto NYD_OU_LABEL;NYD_OU_LABEL:;}while(0) # define NYD do{}while(0) #endif #ifndef NYD2 # define NYD2_IN do{ # define NYD2_OU goto NYD_OU_LABEL;NYD_OU_LABEL:;}while(0) # define NYD2 do{}while(0) #endif #define P2UZ su_P2UZ #define PCMP su_PCMP /* Translation: may NOT set errno! */ #undef _ #undef N_ #undef V_ #ifdef mx_SOURCE # undef A_ # define A_(S) S # ifdef mx_HAVE_UISTRINGS # define _(S) S # define N_(S) S # define V_(S) S # else # define _(S) su_empty # define N_(S) "" # define V_(S) su_empty # endif #elif defined su_SOURCE # define _(S) S # define N_(S) S # define V_(S) S #endif #define SMP su_SMP #define UCMP su_UCMP #define UNCONST su_UNCONST #define UNVOLATILE su_UNVOLATILE #define UNALIGN su_UNALIGN #define UNINIT su_UNINIT #define UNINIT_DECL su_UNINIT_DECL #define UNUSED su_UNUSED #define VFIELD_SIZE su_VFIELD_SIZE #define VSTRUCT_SIZEOF su_VSTRUCT_SIZEOF /* POD TYPE SUPPORT (only if !C++) */ #if defined su_HEADER ||\ ((defined su_SOURCE || defined mx_SOURCE) && su_C_LANG) # define ul su_ul # define ui su_ui # define us su_us # define uc su_uc # define sl su_sl # define si su_si # define ss su_ss # define sc su_sc # define u8 su_u8 # define s8 su_s8 # define u16 su_u16 # define s16 su_s16 # define u32 su_u32 # define s32 su_s32 # define u64 su_u64 # define s64 su_s64 # define uz su_uz # define sz su_sz # define up su_up # define sp su_sp # define FAL0 su_FAL0 # define TRU1 su_TRU1 # define TRU2 su_TRU2 # define TRUM1 su_TRUM1 # define boole su_boole #endif /* su_HEADER || ((su_SOURCE || mx_SOURCE) && su_C_LANG) */ #define U8_MAX su_U8_MAX #define S8_MIN su_S8_MIN #define S8_MAX su_S8_MAX #define U16_MAX su_U16_MAX #define S16_MIN su_S16_MIN #define S16_MAX su_S16_MAX #define U32_MAX su_U32_MAX #define S32_MIN su_S32_MIN #define S32_MAX su_S32_MAX #define U64_MAX su_U64_MAX #define S64_MIN su_S64_MIN #define S64_MAX su_S64_MAX #define U64_C su_U64_C #define S64_C su_S64_C #define UZ_MAX su_UZ_MAX #define SZ_MIN su_SZ_MIN #define SZ_MAX su_SZ_MAX #define UZ_BITS su_UZ_BITS /* MEMORY */ #define su_ALLOCATE su_MEM_ALLOCATE #define su_ALLOCATE_LOC su_MEM_ALLOCATE_LOC #define su_REALLOCATE su_MEM_REALLOCATE #define su_REALLOCATE_LOC su_MEM_REALLOCATE_LOC #define su_ALLOC su_MEM_ALLOC #define su_ALLOC_LOC su_MEM_ALLOC_LOC #define su_ALLOC_LOCOR su_MEM_ALLOC_LOCOR #define su_ALLOC_N su_MEM_ALLOC_N #define su_ALLOC_N_LOC su_MEM_ALLOC_N_LOC #define su_ALLOC_N_LOCOR su_MEM_ALLOC_N_LOCOR #define su_CALLOC su_MEM_CALLOC #define su_CALLOC_LOC su_MEM_CALLOC_LOC #define su_CALLOC_LOCOR su_MEM_CALLOC_LOCOR #define su_CALLOC_N su_MEM_CALLOC_N #define su_CALLOC_N_LOC su_MEM_CALLOC_N_LOC #define su_CALLOC_N_LOCOR su_MEM_CALLOC_N_LOCOR #define su_REALLOC su_MEM_REALLOC #define su_REALLOC_LOC su_MEM_REALLOC_LOC #define su_REALLOC_LOCOR su_MEM_REALLOC_LOCOR #define su_REALLOC_N su_MEM_REALLOC_N #define su_REALLOC_N_LOC su_MEM_REALLOC_N_LOC #define su_REALLOC_N_LOCOR su_MEM_REALLOC_N_LOCOR #define su_TALLOC su_MEM_TALLOC #define su_TALLOC_LOC su_MEM_TALLOC_LOC #define su_TALLOC_LOCOR su_MEM_TALLOC_LOCOR #define su_TCALLOC su_MEM_TCALLOC #define su_TCALLOC_LOC su_MEM_TCALLOC_LOC #define su_TCALLOC_LOCOR su_MEM_TCALLOC_LOCOR #define su_TREALLOC su_MEM_TREALLOC #define su_TREALLOC_LOC su_MEM_TREALLOC_LOC #define su_TREALLOC_LOCOR su_MEM_TREALLOC_LOCOR #define su_TALLOCF su_MEM_TALLOCF #define su_TALLOCF_LOC su_MEM_TALLOCF_LOC #define su_TALLOCF_LOCOR su_MEM_TALLOCF_LOCOR #define su_TCALLOCF su_MEM_TCALLOCF #define su_TCALLOCF_LOC su_MEM_TCALLOCF_LOC #define su_TCALLOCF_LOCOR su_MEM_TCALLOCF_LOCOR #define su_TREALLOCF su_MEM_TREALLOCF #define su_TREALLOCF_LOC su_MEM_TREALLOCF_LOC #define su_TREALLOCF_LOCOR su_MEM_TREALLOCF_LOCOR #define su_FREE su_MEM_FREE #define su_FREE_LOC su_MEM_FREE_LOC #define su_FREE_LOCOR su_MEM_FREE_LOCOR #if !su_C_LANG # define su_NEW su_MEM_NEW # define su_NEW_LOC su_MEM_NEW_LOC # define su_NEW_LOCOR su_MEM_NEW_LOCOR # define su_CNEW su_MEM_CNEW # define su_CNEW_LOC su_MEM_CNEW_LOC # define su_CNEW_LOCOR su_MEM_CNEW_LOCOR # define su_NEW_HEAP su_MEM_NEW_HEAP # define su_NEW_HEAP_LOC su_MEM_NEW_HEAP_LOC # define su_NEW_HEAP_LOCOR su_MEM_NEW_HEAP_LOCOR # define su_DEL su_MEM_DEL # define su_DEL_LOC su_MEM_DEL_LOC # define su_DEL_LOCOR su_MEM_DEL_LOCOR # define su_DEL_HEAP su_MEM_DEL_HEAP # define su_DEL_HEAP_LOC su_MEM_DEL_HEAP_LOC # define su_DEL_HEAP_LOCOR su_MEM_DEL_HEAP_LOCOR # define su_DEL_PRIVATE su_MEM_DEL_PRIVATE # define su_DEL_PRIVATE_LOC su_MEM_DEL_PRIVATE_LOC # define su_DEL_PRIVATE_LOCOR su_MEM_DEL_PRIVATE_LOCOR # define su_DEL_HEAP_PRIVATE su_MEM_DEL_HEAP_PRIVATE # define su_DEL_HEAP_PRIVATE_LOC su_MEM_DEL_HEAP_PRIVATE_LOC # define su_DEL_HEAP_PRIVATE_LOCOR su_MEM_DEL_HEAP_PRIVATE_LOCOR #endif /* !C_LANG */ #ifdef su_MEM_BAG_SELF # ifdef su_HAVE_MEM_BAG_AUTO # define su_AUTO_ALLOC su_MEM_BAG_SELF_AUTO_ALLOC # define su_AUTO_ALLOC_LOC su_MEM_BAG_SELF_AUTO_ALLOC_LOC # define su_AUTO_ALLOC_LOCOR su_MEM_BAG_SELF_AUTO_ALLOC_LOCOR # define su_AUTO_ALLOC_N su_MEM_BAG_SELF_AUTO_ALLOC_N # define su_AUTO_ALLOC_N_LOC su_MEM_BAG_SELF_AUTO_ALLOC_N_LOC # define su_AUTO_ALLOC_N_LOCOR su_MEM_BAG_SELF_AUTO_ALLOC_N_LOCOR # define su_AUTO_CALLOC su_MEM_BAG_SELF_AUTO_CALLOC # define su_AUTO_CALLOC_LOC su_MEM_BAG_SELF_AUTO_CALLOC_LOC # define su_AUTO_CALLOC_LOCOR su_MEM_BAG_SELF_AUTO_CALLOC_LOCOR # define su_AUTO_CALLOC_N su_MEM_BAG_SELF_AUTO_CALLOC_N # define su_AUTO_CALLOC_N_LOC su_MEM_BAG_SELF_AUTO_CALLOC_N_LOC # define su_AUTO_CALLOC_N_LOCOR su_MEM_BAG_SELF_AUTO_CALLOC_N_LOCOR # define su_AUTO_TALLOC su_MEM_BAG_SELF_AUTO_TALLOC # define su_AUTO_TALLOC_LOC su_MEM_BAG_SELF_AUTO_TALLOC_LOC # define su_AUTO_TALLOC_LOCOR su_MEM_BAG_SELF_AUTO_TALLOC_LOCOR # define su_AUTO_TCALLOC su_MEM_BAG_SELF_AUTO_TCALLOC # define su_AUTO_TCALLOC_LOC su_MEM_BAG_SELF_AUTO_TCALLOC_LOC # define su_AUTO_TCALLOC_LOCOR su_MEM_BAG_SELF_AUTO_TCALLOC_LOCOR # endif /* su_HAVE_MEM_BAG_AUTO */ # ifdef su_HAVE_MEM_BAG_LOFI # define su_LOFI_ALLOC su_MEM_BAG_SELF_LOFI_ALLOC # define su_LOFI_ALLOC_LOC su_MEM_BAG_SELF_LOFI_ALLOC_LOC # define su_LOFI_ALLOC_LOCOR su_MEM_BAG_SELF_LOFI_ALLOC_LOCOR # define su_LOFI_ALLOC_N su_MEM_BAG_SELF_LOFI_ALLOC_N # define su_LOFI_ALLOC_N_LOC su_MEM_BAG_SELF_LOFI_ALLOC_N_LOC # define su_LOFI_ALLOC_N_LOCOR su_MEM_BAG_SELF_LOFI_ALLOC_N_LOCOR # define su_LOFI_CALLOC su_MEM_BAG_SELF_LOFI_CALLOC # define su_LOFI_CALLOC_LOC su_MEM_BAG_SELF_LOFI_CALLOC_LOC # define su_LOFI_CALLOC_LOCOR su_MEM_BAG_SELF_LOFI_CALLOC_LOCOR # define su_LOFI_CALLOC_N su_MEM_BAG_SELF_LOFI_CALLOC_N # define su_LOFI_CALLOC_N_LOC su_MEM_BAG_SELF_LOFI_CALLOC_N_LOC # define su_LOFI_CALLOC_N_LOCOR su_MEM_BAG_SELF_LOFI_CALLOC_N_LOCOR # define su_LOFI_TALLOC su_MEM_BAG_SELF_LOFI_TALLOC # define su_LOFI_TALLOC_LOC su_MEM_BAG_SELF_LOFI_TALLOC_LOC # define su_LOFI_TALLOC_LOCOR su_MEM_BAG_SELF_LOFI_TALLOC_LOCOR # define su_LOFI_TCALLOC su_MEM_BAG_SELF_LOFI_TCALLOC # define su_LOFI_TCALLOC_LOC su_MEM_BAG_SELF_LOFI_TCALLOC_LOC # define su_LOFI_TCALLOC_LOCOR su_MEM_BAG_SELF_LOFI_TCALLOC_LOCOR # define su_LOFI_FREE su_MEM_BAG_SELF_LOFI_FREE # define su_LOFI_FREE_LOC su_MEM_BAG_SELF_LOFI_FREE_LOC # define su_LOFI_FREE_LOCOR su_MEM_BAG_SELF_LOFI_FREE_LOCOR # endif /* su_HAVE_MEM_BAG_LOFI */ #endif /* su_MEM_BAG_SELF */ /* s-it-mode */ s-nail-14.9.15/include/su/code-ou.h000066400000000000000000000141571352610246600166510ustar00rootroot00000000000000/*@ Internal: opposite of code-in.h. * * Copyright (c) 2003 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef su_CODE_IN_H # error su/code-ou.h is useless if su/code-in.h has not been included #endif #undef su_CODE_IN_H /* LANG */ #undef C_LANG #undef C_DECL_BEGIN #undef C_DECL_END #undef NSPC_BEGIN #undef NSPC_END #undef NSPC_USE #undef NSPC #if defined su_CXX_HEADER || (defined su_SOURCE && !su_C_LANG) # undef CLASS_NO_COPY # undef SELFTHIS_RET # undef PUB # undef PRO # undef PRI # undef STA # undef VIR # undef OVR # undef OVRX #endif #undef S #undef R #undef C #undef NIL #undef SHADOW #undef EXPORT #undef EXPORT_DATA #undef IMPORT #undef IMPORT_DATA #undef CTA #undef LCTA #undef CTAV #undef LCTAV #undef MCTA /* CC */ #undef PACKED #undef INLINE #undef SINLINE #undef LIKELY #undef UNLIKELY /* SUPPORT MACROS+ */ #undef ABS #undef CLIP #undef MAX #undef MIN #undef IS_POW2 #undef ALIGNOF #undef Z_ALIGN #undef Z_ALIGN_SMALL #undef Z_ALIGN_PZ #undef ASSERT_INJ #undef ASSERT_NB #undef ASSERT #undef ASSERT_LOC #undef ASSERT_EXEC #undef ASSERT_EXEC_LOC #undef ASSERT_JUMP #undef ASSERT_JUMP_LOC #undef ASSERT_RET #undef ASSERT_RET_LOC #undef ASSERT_RET_VOID #undef ASSERT_RET_VOID_LOC #undef ASSERT_NYD_EXEC #undef ASSERT_NYD_EXEC_LOC #undef ASSERT_NYD #undef ASSERT_NYD_LOC #undef DBG #undef NDGB #undef DBGOR #undef DVL #undef NDVL #undef DVLOR #undef FIELD_INITN #undef FIELD_INITI #undef FIELD_OFFSETOF #undef FIELD_RANGEOF #undef FIELD_SIZEOF #undef MT #undef NELEM #undef NYD_OU_LABEL #undef NYD_IN #undef NYD_OU #undef NYD #undef NYD2_IN #undef NYD2_OU #undef NYD2 #undef P2UZ #undef PCMP #ifdef mx_SOURCE # undef A_ #endif #if defined su_SOURCE || defined mx_SOURCE # undef _ # undef N_ # undef V_ #endif #undef SMP #undef UCMP #undef UNCONST #undef UNVOLATILE #undef UNALIGN #undef UNINIT #undef UNINIT_DECL #undef UNUSED #undef VFIELD_SIZE #undef VSTRUCT_SIZEOF /* POD TYPE SUPPORT (only if !C++) */ #if defined su_HEADER ||\ ((defined su_SOURCE || defined mx_SOURCE) && su_C_LANG) # undef ul # undef ui # undef us # undef uc # undef sl # undef si # undef ss # undef sc # undef u8 # undef s8 # undef u16 # undef s16 # undef u32 # undef s32 # undef u64 # undef s64 # undef uz # undef sz # undef up # undef sp # undef FAL0 # undef TRU1 # undef TRU2 # undef TRUM1 # undef boole #endif /* su_HEADER || ((su_SOURCE || mx_SOURCE) && su_C_LANG) */ #undef U8_MAX #undef S8_MIN #undef S8_MAX #undef U16_MAX #undef S16_MIN #undef S16_MAX #undef U32_MAX #undef S32_MIN #undef S32_MAX #undef U64_MAX #undef S64_MIN #undef S64_MAX #undef U64_C #undef S64_C #undef UZ_MAX #undef SZ_MIN #undef SZ_MAX #undef UZ_BITS /* MEMORY */ #undef su_ALLOCATE #undef su_ALLOCATE_LOC #undef su_REALLOCATE #undef su_REALLOCATE_LOC #undef su_ALLOC #undef su_ALLOC_LOC #undef su_ALLOC_LOCOR #undef su_ALLOC_N #undef su_ALLOC_N_LOC #undef su_ALLOC_N_LOCOR #undef su_CALLOC #undef su_CALLOC_LOC #undef su_CALLOC_LOCOR #undef su_CALLOC_N #undef su_CALLOC_N_LOC #undef su_CALLOC_N_LOCOR #undef su_REALLOC #undef su_REALLOC_LOC #undef su_REALLOC_LOCOR #undef su_REALLOC_N #undef su_REALLOC_N_LOC #undef su_REALLOC_N_LOCOR #undef su_TALLOC #undef su_TALLOC_LOC #undef su_TALLOC_LOCOR #undef su_TCALLOC #undef su_TCALLOC_LOC #undef su_TCALLOC_LOCOR #undef su_TREALLOC #undef su_TREALLOC_LOC #undef su_TREALLOC_LOCOR #undef su_TALLOCF #undef su_TALLOCF_LOC #undef su_TALLOCF_LOCOR #undef su_TCALLOCF #undef su_TCALLOCF_LOC #undef su_TCALLOCF_LOCOR #undef su_TREALLOCF #undef su_TREALLOCF_LOC #undef su_TREALLOCF_LOCOR #undef su_FREE #undef su_FREE_LOC #undef su_FREE_LOCOR #if !su_C_LANG # undef su_NEW # undef su_NEW_LOC # undef su_NEW_LOCOR # undef su_CNEW # undef su_CNEW_LOC # undef su_CNEW_LOCOR # undef su_NEW_HEAP # undef su_NEW_HEAP_LOC # undef su_NEW_HEAP_LOCOR # undef su_DEL # undef su_DEL_LOC # undef su_DEL_LOCOR # undef su_DEL_HEAP # undef su_DEL_HEAP_LOC # undef su_DEL_HEAP_LOCOR # undef su_DEL_PRIVATE # undef su_DEL_PRIVATE_LOC # undef su_DEL_PRIVATE_LOCOR # undef su_DEL_HEAP_PRIVATE # undef su_DEL_HEAP_PRIVATE_LOC # undef su_DEL_HEAP_PRIVATE_LOCOR #endif /* !C_LANG */ #ifdef su_MEM_BAG_SELF # ifdef su_HAVE_MEM_BAG_AUTO # undef su_AUTO_ALLOC # undef su_AUTO_ALLOC_LOC # undef su_AUTO_ALLOC_LOCOR # undef su_AUTO_ALLOC_N # undef su_AUTO_ALLOC_N_LOC # undef su_AUTO_ALLOC_N_LOCOR # undef su_AUTO_CALLOC # undef su_AUTO_CALLOC_LOC # undef su_AUTO_CALLOC_LOCOR # undef su_AUTO_CALLOC_N # undef su_AUTO_CALLOC_N_LOC # undef su_AUTO_CALLOC_N_LOCOR # undef su_AUTO_TALLOC # undef su_AUTO_TALLOC_LOC # undef su_AUTO_TALLOC_LOCOR # undef su_AUTO_TCALLOC # undef su_AUTO_TCALLOC_LOC # undef su_AUTO_TCALLOC_LOCOR # endif /* su_HAVE_MEM_BAG_AUTO */ # ifdef su_HAVE_MEM_BAG_LOFI # undef su_LOFI_ALLOC # undef su_LOFI_ALLOC_LOC # undef su_LOFI_ALLOC_LOCOR # undef su_LOFI_ALLOC_N # undef su_LOFI_ALLOC_N_LOC # undef su_LOFI_ALLOC_N_LOCOR # undef su_LOFI_CALLOC # undef su_LOFI_CALLOC_LOC # undef su_LOFI_CALLOC_LOCOR # undef su_LOFI_CALLOC_N # undef su_LOFI_CALLOC_N_LOC # undef su_LOFI_CALLOC_N_LOCOR # undef su_LOFI_TALLOC # undef su_LOFI_TALLOC_LOC # undef su_LOFI_TALLOC_LOCOR # undef su_LOFI_TCALLOC # undef su_LOFI_TCALLOC_LOC # undef su_LOFI_TCALLOC_LOCOR # undef su_LOFI_FREE # undef su_LOFI_FREE_LOC # undef su_LOFI_FREE_LOCOR # endif /* su_HAVE_MEM_BAG_LOFI */ #endif /* su_MEM_BAG_SELF */ #undef su_HEADER #undef su_CXX_HEADER #undef mx_HEADER #undef rf_HEADER /* s-it-mode */ s-nail-14.9.15/include/su/code.h000066400000000000000000001746041352610246600162340ustar00rootroot00000000000000/*@ Code of the basic infrastructure (POD types, macros etc.) and functions. *@ And main documentation entry point, as below. *@ - Reacts upon su_HAVE_DEBUG, su_HAVE_DEVEL, and NDEBUG. *@ The latter is a precondition for su_HAVE_INLINE; dependent upon compiler *@ __OPTIMIZE__ (and __OPTIMIZE_SIZE__) may be looked at in addition, then. *@ su_HAVE_DEVEL is meant as a possibility to enable test paths with *@ debugging disabled. *@ - Some macros require su_FILE to be defined to a literal. *@ - Define su_MASTER to inject what is to be injected once; for example, *@ it enables su_M*CTA() compile time assertions. * * Copyright (c) 2001 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef su_CODE_H #define su_CODE_H #include /*! * \mainpage SU --- Steffen's Utilities * * Afters years of finding myself too busy to port my old C++ library of which * i am so prowd to the C language, and because of the ever increasing * necessity to have a foundation of things i like using nonetheless, * i finally have started creating a minimal set of tools instead. * * Some introductional notes: * * \list{\li{ * The basic infrastructure of \SU is provided by the file \r{su/code.h}. * Because all other \SU headers include it (thus), having it available is * almost always implicit. * It should be noted, however, that the \r{CORE} reacts upon a few * preprocessor switches, as documented there. * }\li{ * Datatype overflow errors and out-of-memory situations are usually detected * and result in abortions (via \r{su_LOG_EMERG} logs). * Alternatively all or individual \r{su_state_err_type}s will not cause * hard-failures. * * The actual global mode of operation can be queried via \r{su_state_get()} * (presence checks with \r{su_state_has()}, * is configurable via \r{su_state_set()} and \r{su_state_clear()}, and often * the default can also be changed on a by-call or by-object basis, see * \r{su_state_err_flags} and \r{su_clone_fun} for more on this. * * \remarks{C++ object creation failures via \c{su_MEM_NEW()} etc. will however * always cause program abortion due to standard imposed execution flow. * This can be worked around by using \c{su_MEM_NEW_HEAP()} as appropriate.} * }\li{ * Most collection and string object types work on 32-bit (or even 31-bit) * lengths a.k.a. counts a.k.a. sizes. * For simplicity of use, and because datatype overflow is a handled case, the * user interface very often uses \r{su_uz} (i.e., \c{size_t}). * Other behaviour is explicitly declared with a "big" prefix, as in * "biglist", but none such object does exist at the time of this writing. * }} */ /*! * \file * \ingroup CORE * \brief \r{CORE} */ /* CONFIG {{{ *//*! * \defgroup CONFIG SU configuration * \ingroup CORE * \brief Overall \SU configuration (\r{su/code.h}) * * It reflects the chosen configuration and the build time environment. * @{ */ #ifdef DOXYGEN /* Features */ /*! Whether the \SU namespace exists. * If not, facilities exist in the global namespace. */ # define su_HAVE_NSPC # define su_HAVE_DEBUG /*!< Debug variant, including assertions etc. */ /*! Test paths available in non-debug code. * Also, compiler pragmas which suppress some warnings are not set, etc.*/ # define su_HAVE_DEVEL # define su_HAVE_DOCSTRINGS /*!< Some more helpful strings. */ # define su_HAVE_MEM_BAG_AUTO /*!< \_ */ # define su_HAVE_MEM_BAG_LOFI /*!< \_ */ /*! Normally the debug library provides memory write boundary excess via * canaries (see \r{MEM_CACHE_ALLOC} and \r{su_MEM_ALLOC_DEBUG}). * Since this counteracts external memory checkers like \c{valgrind(1)} or * the ASAN (address sanitizer) compiler extensions, the \SU checkers can be * disabled explicitly. */ # define su_HAVE_MEM_CANARIES_DISABLE # define su_HAVE_SMP /*!< \r{SMP} support available? */ /*!< Multithreading support available? * This is a subfeature of \r{SMP}. */ # define su_HAVE_MT /* Values */ # define su_PAGE_SIZE /*!< \_ */ #endif /*! @} *//* CONFIG }}} */ /*! * \defgroup CORE Basic infrastructure * \brief Macros, POD types, and basic interfaces (\r{su/code.h}) * * The basic infrastructure: * * \list{\li{ * Reacts upon \vr{su_HAVE_DEBUG}, \vr{su_HAVE_DEVEL}, and \vr{NDEBUG}. * Whereas the former two are configuration-time constants which will create * additional API and cause a different ABI, the latter will only cause * preprocessor changes, for example for \r{su_ASSERT()}. * }\li{ * The latter is a precondition for \vr{su_HAVE_INLINE}. * }\li{ * Some macros require \vr{su_FILE} to be defined to a literal. * }\li{ * Define \vr{su_MASTER} to inject what is to be injected once; for example, * it enables \c{su_M*CTA()} compile time assertions. * }} * @{ */ /* OS {{{ */ #define su_OS_CYGWIN 0 /*!< \_ */ #define su_OS_DARWIN 0 /*!< \_ */ #define su_OS_DRAGONFLY 0 /*!< \_ */ #define su_OS_EMX 0 /*!< \_ */ #define su_OS_FREEBSD 0 /*!< \_ */ #define su_OS_LINUX 0 /*!< \_ */ #define su_OS_MINIX 0 /*!< \_ */ #define su_OS_MSDOS 0 /*!< \_ */ #define su_OS_NETBSD 0 /*!< \_ */ #define su_OS_OPENBSD 0 /*!< \_ */ #define su_OS_SOLARIS 0 /*!< \_ */ #define su_OS_SUNOS 0 /*!< \_ */ #define su_OS_WIN32 0 /*!< \_ */ #define su_OS_WIN64 0 /*!< \_ */ #if 0 #elif defined __CYGWIN__ # undef su_OS_CYGWIN # define su_OS_CYGWIN 1 #elif defined DARWIN || defined _DARWIN # undef su_OS_DARWIN # define su_OS_DARWIN 1 #elif defined __DragonFly__ # undef su_OS_DRAGONFLY # define su_OS_DRAGONFLY 1 #elif defined __EMX__ # undef su_OS_EMX # define su_OS_EMX 1 #elif defined __FreeBSD__ # undef su_OS_FREEBSD # define su_OS_FREEBSD 1 #elif defined __linux__ || defined __linux # undef su_OS_LINUX # define su_OS_LINUX 1 #elif defined __minix # undef su_OS_MINIX # define su_OS_MINIX 1 #elif defined __MSDOS__ # undef su_OS_MSDOS # define su_OS_MSDOS 1 #elif defined __NetBSD__ # undef su_OS_NETBSD # define su_OS_NETBSD 1 #elif defined __OpenBSD__ # undef su_OS_OPENBSD # define su_OS_OPENBSD 1 #elif defined __solaris__ || defined __sun # if defined __SVR4 || defined __svr4__ # undef su_OS_SOLARIS # define su_OS_SOLARIS 1 # else # undef su_OS_SUNOS # define su_OS_SUNOS 1 # endif #endif /* OS }}} */ /* LANG {{{ */ #ifndef __cplusplus # define su_C_LANG 1 /*!< \_ */ # define su_C_DECL_BEGIN /*!< \_ */ # define su_C_DECL_END /*!< \_ */ /* Casts */ # define su_S(T,I) ((T)(I)) /*!< \_ */ # define su_R(T,I) ((T)(I)) /*!< \_ */ # define su_C(T,I) ((T)(I)) /*!< \_ */ # define su_NIL ((void*)0) /*!< \_ */ #else # define su_C_LANG 0 # define su_C_DECL_BEGIN extern "C" { # define su_C_DECL_END } # ifdef su_HAVE_NSPC # define su_NSPC_BEGIN(X) namespace X { # define su_NSPC_END(X) } # define su_NSPC_USE(X) using namespace X; # define su_NSPC(X) X:: # else # define su_NSPC_BEGIN(X) /**/ # define su_NSPC_END(X) /**/ # define su_NSPC_USE(X) /**/ # define su_NSPC(X) /**/:: # endif /* Disable copy-construction and assigment of class */ # define su_CLASS_NO_COPY(C) private:C(C const &);C &operator=(C const &); /* If C++ class inherits from a C class, and the C class "return self", we * have to waste a return register even if self==this */ # define su_SELFTHIS_RET(X) /* return *(X); */ X; return *this /* C++ only allows those at the declaration, not the definition */ # define su_PUB # define su_PRO # define su_PRI # define su_STA # define su_VIR # define su_OVR /* This is for the declarator only */ # if __cplusplus +0 < 201103L # define su_OVRX # else # define su_OVRX override # endif /* Casts */ # define su_S(T,I) static_cast(I) # define su_R(T,I) reinterpret_cast(I) # define su_C(T,I) const_cast(I) # define su_NIL (0L) #endif /* __cplusplus */ /*! The \r{su_state_err()} mechanism can be configured to not cause * abortion in case of datatype overflow and out-of-memory situations. * Most functions return error conditions to pass them to their caller, * but this is impossible for, e.g., C++ copy-constructors and assignment * operators. * And \SU does not use exceptions. * So if those errors could occur and thus be hidden, the prototype is marked * with this "keyword" so that callers can decide whether they want to take * alternative routes to come to the desired result or not. */ #define su_SHADOW /* "su_EXPORT myfun()", "class su_EXPORT myclass" */ #if su_OS_WIN32 || su_OS_WIN64 # define su_EXPORT __declspec((dllexport)) # define su_EXPORT_DATA __declspec((dllexport)) # define su_IMPORT __declspec((dllimport)) # define su_IMPORT_DATA __declspec((dllimport)) #else # define su_EXPORT /*extern*/ /*!< \_ */ # define su_EXPORT_DATA extern /*!< \_ */ # define su_IMPORT /*extern*/ /*!< \_ */ # define su_IMPORT_DATA extern /*!< \_ */ #endif /* Compile-Time-Assert * Problem is that some compilers warn on unused local typedefs, so add * a special local CTA to overcome this */ #if (!su_C_LANG && __cplusplus +0 >= 201103L) || defined DOXYGEN # define su_CTA(T,M) static_assert(T, M) /*!< \_ */ # define su_LCTA(T,M) static_assert(T, M) /*!< \_ */ #elif 0 /* unusable! */ && \ defined __STDC_VERSION__ && __STDC_VERSION__ +0 >= 201112L # define su_CTA(T,M) _Static_assert(T, M) # define su_LCTA(T,M) _Static_assert(T, M) #else # define su_CTA(T,M) su__CTA_1(T, su_FILE, __LINE__) # define su_LCTA(T,M) su__LCTA_1(T, su_FILE, __LINE__) # define su__CTA_1(T,F,L) su__CTA_2(T, F, L) # define su__CTA_2(T,F,L) \ typedef char ASSERTION_failed_file_ ## F ## _line_ ## L[(T) ? 1 : -1] # define su__LCTA_1(T,F,L) su__LCTA_2(T, F, L) # define su__LCTA_2(T,F,L) \ do{\ typedef char ASSERT_failed_file_ ## F ## _line_ ## L[(T) ? 1 : -1];\ ASSERT_failed_file_ ## F ## _line_ ## L __i_am_unused__;\ su_UNUSED(__i_am_unused__);\ }while(0) #endif #define su_CTAV(T) su_CTA(T, "Unexpected value of constant") /*!< \_ */ #define su_LCTAV(T) su_LCTA(T, "Unexpected value of constant") /*!< \_ */ #ifdef su_MASTER # define su_MCTA(T,M) su_CTA(T, M); #else # define su_MCTA(T,M) #endif /* LANG }}} */ /* CC {{{ */ #define su_CC_CLANG 0 /*!< \_ */ #define su_CC_VCHECK_CLANG(X,Y) 0 /*!< \_ */ #define su_CC_GCC 0 /*!< \_ */ #define su_CC_VCHECK_GCC(X,Y) 0 /*!< \_ */ #define su_CC_PCC 0 /*!< \_ */ #define su_CC_VCHECK_PCC(X,Y) 0 /*!< \_ */ #define su_CC_SUNPROC 0 /*!< \_ */ #define su_CC_VCHECK_SUNPROC(X,Y) 0 /*!< \_ */ #define su_CC_TINYC 0 /*!< \_ */ #define su_CC_VCHECK_TINYC(X,Y) 0 /*!< \_ */ #ifdef __clang__ # undef su_CC_CLANG # undef su_CC_VCHECK_CLANG # define su_CC_CLANG 1 # define su_CC_VCHECK_CLANG(X,Y) \ (__clang_major__ +0 > (X) || \ (__clang_major__ +0 == (X) && __clang_minor__ +0 >= (Y))) # define su_CC_EXTEN __extension__ # define su_CC_PACKED __attribute__((packed)) # if !defined su_CC_BOM &&\ defined __BYTE_ORDER__ && defined __ORDER_LITTLE_ENDIAN__ &&\ defined __ORDER_BIG_ENDIAN # if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ # define su_CC_BOM su_CC_BOM_LITTLE # elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ # define su_CC_BOM su_CC_BOM_BIG # else # error Unsupported __BYTE_ORDER__ # endif # endif #elif defined __GNUC__ /* __clang__ */ # undef su_CC_GCC # undef su_CC_VCHECK_GCC # define su_CC_GCC 1 # define su_CC_VCHECK_GCC(X,Y) \ (__GNUC__ +0 > (X) || (__GNUC__ +0 == (X) && __GNUC_MINOR__ +0 >= (Y))) # define su_CC_EXTEN __extension__ # define su_CC_PACKED __attribute__((packed)) # if !defined su_CC_BOM &&\ defined __BYTE_ORDER__ && defined __ORDER_LITTLE_ENDIAN__ &&\ defined __ORDER_BIG_ENDIAN # if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ # define su_CC_BOM su_CC_BOM_LITTLE # elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ # define su_CC_BOM su_CC_BOM_BIG # else # error Unsupported __BYTE_ORDER__ # endif # endif #elif defined __PCC__ /* __GNUC__ */ # undef su_CC_PCC # undef su_CC_VCHECK_PCC # define su_CC_PCC 1 # define su_CC_VCHECK_PCC(X,Y) \ (__PCC__ +0 > (X) || (__PCC__ +0 == (X) && __PCC_MINOR__ +0 >= (Y))) # define su_CC_EXTEN __extension__ # define su_CC_PACKED __attribute__((packed)) # if !defined su_CC_BOM &&\ defined __BYTE_ORDER__ && defined __ORDER_LITTLE_ENDIAN__ &&\ defined __ORDER_BIG_ENDIAN # if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ # define su_CC_BOM su_CC_BOM_LITTLE # elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ # define su_CC_BOM su_CC_BOM_BIG # else # error Unsupported __BYTE_ORDER__ # endif # endif #elif defined __SUNPRO_C /* __PCC__ */ # undef su_CC_SUNPROC # define su_CC_SUNPROC 1 # define su_CC_PACKED TODO: PACKED attribute not supported for SunPro C #elif defined __TINYC__ /* __SUNPRO_C */ # undef su_CC_TINYC # define su_CC_TINYC 1 # define su_CC_EXTEN /* __extension__ (ignored) */ # define su_CC_PACKED __attribute__((packed)) #elif !defined su_CC_IGNORE_UNKNOWN # error SU: This compiler is not yet supported. # error SU: To continue with your CFLAGS etc., define su_CC_IGNORE_UNKNOWN. # error SU: It may be necessary to define su_CC_PACKED to a statement that # error SU: enables structure packing; it may not be a #pragma, but a _Pragma #endif #ifndef su_CC_EXTEN # define su_CC_EXTEN /*!< \_ */ #endif #ifndef su_CC_PACKED /*! \_ */ # define su_CC_PACKED TODO: PACKED attribute not supported for this compiler #endif #if defined su_CC_BOM || defined DOXYGEN # ifdef DOXYGEN /*! If the CC offers \r{su_BOM} classification macros, defined to either * \r{su_CC_BOM_LITTLE} or \r{su_CC_BOM_BIG}, otherwise not defined. */ # define su_CC_BOM # endif # define su_CC_BOM_LITTLE 1234 /*!< Only if there is \r{su_CC_BOM}. */ # define su_CC_BOM_BIG 4321 /*!< Only if there is \r{su_CC_BOM}. */ #endif #if !defined su_CC_UZ_TYPE && defined __SIZE_TYPE__ # define su_CC_UZ_TYPE __SIZE_TYPE__ #endif /* Function name */ #if defined __STDC_VERSION__ && __STDC_VERSION__ +0 >= 199901L # define su_FUN __func__ /*!< "Not a literal". */ #elif su_CC_CLANG || su_CC_VCHECK_GCC(3, 4) || su_CC_PCC || su_CC_TINYC # define su_FUN __extension__ __FUNCTION__ #else # define su_FUN su_empty /* Something that is not a literal */ #endif /* inline keyword */ #define su_HAVE_INLINE #if su_C_LANG # ifdef DOXYGEN # define su_INLINE inline /*!< \_ */ # define su_SINLINE inline /*!< \_ */ # elif su_CC_CLANG || su_CC_GCC || su_CC_PCC # if defined __STDC_VERSION__ && __STDC_VERSION__ +0 >= 199901L /* That gcc is totally weird */ # if su_OS_OPENBSD && su_CC_GCC # define su_INLINE extern __inline __attribute__((gnu_inline)) # define su_SINLINE static __inline __attribute__((gnu_inline)) /* All CCs coming here know __OPTIMIZE__ */ # elif !defined NDEBUG || !defined __OPTIMIZE__ # define su_INLINE static inline # define su_SINLINE static inline # else /* xxx gcc 8.3.0 bug: does not truly inline with -Os */ # if su_CC_GCC && defined __OPTIMIZE_SIZE__ # define su_INLINE inline __attribute__((always_inline)) # else # define su_INLINE inline # endif # define su_SINLINE static inline # endif # else # define su_INLINE static __inline # define su_SINLINE static __inline # endif # else # define su_INLINE static /* TODO __attribute__((unused)) alikes? */ # define su_SINLINE static /* TODO __attribute__((unused)) alikes? */ # undef su_HAVE_INLINE # endif #else # define su_INLINE inline # define su_SINLINE static inline #endif #ifndef NDEBUG # undef su_HAVE_INLINE #endif #if defined __predict_true && defined __predict_false # define su_LIKELY(X) __predict_true((X) != 0) # define su_UNLIKELY(X) __predict_false((X) != 0) #elif su_CC_CLANG || su_CC_VCHECK_GCC(2, 96) || su_CC_PCC || su_CC_TINYC # define su_LIKELY(X) __builtin_expect((X) != 0, 1) # define su_UNLIKELY(X) __builtin_expect((X) != 0, 0) #else # define su_LIKELY(X) ((X) != 0) /*!< \_ */ # define su_UNLIKELY(X) ((X) != 0) /*!< \_ */ #endif /* CC }}} */ /* SUPPORT MACROS+ {{{ */ /* USECASE_XY_DISABLED for tagging unused files: * git rm `git grep ^su_USECASE_MX_DISABLED` */ #ifdef su_USECASE_MX # define su_USECASE_MX_DISABLED This file is not a (valid) compilation unit #endif #ifndef su_USECASE_MX_DISABLED # define su_USECASE_MX_DISABLED #endif /* Basic support macros, with side effects */ /*! \_ */ #define su_ABS(A) ((A) < 0 ? -(A) : (A)) /*! \_ */ #define su_CLIP(X,A,B) (((X) <= (A)) ? (A) : (((X) >= (B)) ? (B) : (X))) /*! \_ */ #define su_MAX(A,B) ((A) < (B) ? (B) : (A)) /*! \_ */ #define su_MIN(A,B) ((A) < (B) ? (A) : (B)) /*! \_ */ #define su_IS_POW2(X) ((((X) - 1) & (X)) == 0) /* Alignment. Note: su_uz POW2 asserted in POD section below! */ #if defined __STDC_VERSION__ && __STDC_VERSION__ +0 >= 201112L # include # define su_ALIGNOF(X) _Alignof(X) #else /*! \c{_Alignof()} if available, something hacky otherwise */ # define su_ALIGNOF(X) ((sizeof(X) + sizeof(su_uz)) & ~(sizeof(su_uz) - 1)) #endif /* Roundup/align an integer; Note: POW2 asserted in POD section below! */ /*! Overalign an integer value to a size that cannot cause just any problem * for anything which does not use special alignment directives. */ #define su_Z_ALIGN(X) ((su_S(su_uz,X) + 2*su__ZAL_L) & ~((2*su__ZAL_L) - 1)) /*! Smaller than \r{su_Z_ALIGN()}, but sufficient for basic plain-old-data. */ #define su_Z_ALIGN_SMALL(X) ((su_S(su_uz,X) + su__ZAL_L) & ~(su__ZAL_L - 1)) /*! \r{su_Z_ALIGN()}, but only for pointers and \r{su_uz}. */ #define su_Z_ALIGN_PZ(X) ((su_S(su_uz,X) + su__ZAL_S) & ~(su__ZAL_S - 1)) # define su__ZAL_S su_MAX(su_ALIGNOF(su_uz), su_ALIGNOF(void*)) # define su__ZAL_L su_MAX(su__ZAL_S, su_ALIGNOF(su_u64))/* XXX FP,128bit */ /* Variants of ASSERT */ #if defined NDEBUG || defined DOXYGEN # define su_ASSERT_INJ(X) /*!< Injection! */ # define su_ASSERT_NB(X) ((void)0) /*!< No block. */ # define su_ASSERT(X) do{}while(0) /*!< \_ */ # define su_ASSERT_LOC(X,FNAME,LNNO) do{}while(0) /*!< \_ */ # define su_ASSERT_EXEC(X,S) do{}while(0) /*!< \_ */ # define su_ASSERT_EXEC_LOC(X,S,FNAME,LNNO) do{}while(0) /*!< \_ */ # define su_ASSERT_JUMP(X,L) do{}while(0) /*!< \_ */ # define su_ASSERT_JUMP_LOC(X,L,FNAME,LNNO) do{}while(0) /*!< \_ */ # define su_ASSERT_RET(X,Y) do{}while(0) /*!< \_ */ # define su_ASSERT_RET_LOC(X,Y,FNAME,LNNO) do{}while(0) /*!< \_ */ # define su_ASSERT_RET_VOID(X) do{}while(0) /*!< \_ */ # define su_ASSERT_RET_VOID_LOC(X,Y,FNAME,LNNO) do{}while(0) /*!< \_ */ # define su_ASSERT_NYD_EXEC(X,Y) do{}while(0) /*!< \_ */ # define su_ASSERT_NYD_EXEC_LOC(X,FNAME,LNNO) do{}while(0) /*!< \_ */ # define su_ASSERT_NYD(X) do{}while(0) /*!< \_ */ # define su_ASSERT_NYD_LOC(X,FNAME,LNNO) do{}while(0) /*!< \_ */ #else # define su_ASSERT_INJ(X) X # define su_ASSERT_NB(X) \ su_R(void,((X) ? su_TRU1 \ : su_assert(su_STRING(X), __FILE__, __LINE__, su_FUN, su_TRU1), su_FAL0)) # define su_ASSERT(X) su_ASSERT_LOC(X, __FILE__, __LINE__) # define su_ASSERT_LOC(X,FNAME,LNNO) \ do if(!(X))\ su_assert(su_STRING(X), FNAME, LNNO, su_FUN, su_TRU1);\ while(0) # define su_ASSERT_EXEC(X,S) su_ASSERT_EXEC_LOC(X, S, __FILE__, __LINE__) # define su_ASSERT_EXEC_LOC(X,S,FNAME,LNNO) \ do if(!(X)){\ su_assert(su_STRING(X), FNAME, LNNO, su_FUN, su_FAL0);\ S;\ }while(0) # define su_ASSERT_JUMP(X,L) su_ASSERT_JUMP_LOC(X, L, __FILE__, __LINE__) # define su_ASSERT_JUMP_LOC(X,L,FNAME,LNNO) \ do if(!(X)){\ su_assert(su_STRING(X), FNAME, LNNO, su_FUN, su_FAL0);\ goto L;\ }while(0) # define su_ASSERT_RET(X,Y) su_ASSERT_RET_LOC(X, Y, __FILE__, __LINE__) # define su_ASSERT_RET_LOC(X,Y,FNAME,LNNO) \ do if(!(X)){\ su_assert(su_STRING(X), FNAME, LNNO, su_FUN, su_FAL0);\ return Y;\ }while(0) # define su_ASSERT_RET_VOID(X) su_ASSERT_RET_VOID_LOC(X, __FILE__, __LINE__) # define su_ASSERT_RET_VOID_LOC(X,FNAME,LNNO) \ do if(!(X)){\ su_assert(su_STRING(X), FNAME, LNNO, su_FUN, su_FAL0);\ return;\ }while(0) # define su_ASSERT_NYD_EXEC(X,Y) \ su_ASSERT_NYD_EXEC_LOC(X, Y, __FILE__, __LINE__) # define su_ASSERT_NYD_EXEC_LOC(X,Y,FNAME,LNNO) \ do if(!(X)){\ su_assert(su_STRING(X), FNAME, LNNO, su_FUN, su_FAL0);\ Y; goto su_NYD_OU_LABEL;\ }while(0) # define su_ASSERT_NYD(X) su_ASSERT_NYD_LOC(X, __FILE__, __LINE__) # define su_ASSERT_NYD_LOC(X,FNAME,LNNO) \ do if(!(X)){\ su_assert(su_STRING(X), FNAME, LNNO, su_FUN, su_FAL0);\ goto su_NYD_OU_LABEL;\ }while(0) #endif /* defined NDEBUG || defined DOXYGEN */ /*! Create a bit mask for the bit range LO..HI -- HI cannot use highest bit! */ #define su_BITENUM_MASK(LO,HI) (((1u << ((HI) + 1)) - 1) & ~((1u << (LO)) - 1)) /* For injection macros like su_DBG(), NDBG, DBGOR, 64, 32, 6432 */ #define su_COMMA , /* Debug injections */ #if defined su_HAVE_DEBUG && !defined NDEBUG # define su_DBG(X) X # define su_NDBG(X) # define su_DBGOR(X,Y) X #else # define su_DBG(X) # define su_NDBG(X) X # define su_DBGOR(X,Y) Y #endif /* Debug file location arguments. (For an usage example see su/mem.h.) */ #if defined su_HAVE_DEVEL || defined su_HAVE_DEBUG # define su_HAVE_DBG_LOC_ARGS # define su_DBG_LOC_ARGS_FILE su__dbg_loc_args_file # define su_DBG_LOC_ARGS_LINE su__dbg_loc_args_line # define su_DBG_LOC_ARGS_DECL_SOLE \ char const *su_DBG_LOC_ARGS_FILE, su_u32 su_DBG_LOC_ARGS_LINE # define su_DBG_LOC_ARGS_DECL , su_DBG_LOC_ARGS_DECL_SOLE # define su_DBG_LOC_ARGS_INJ_SOLE __FILE__, __LINE__ # define su_DBG_LOC_ARGS_INJ , su_DBG_LOC_ARGS_INJ_SOLE # define su_DBG_LOC_ARGS_USE_SOLE su_DBG_LOC_ARGS_FILE, su_DBG_LOC_ARGS_LINE # define su_DBG_LOC_ARGS_USE , su_DBG_LOC_ARGS_USE_SOLE # define su_DBG_LOC_ARGS_ORUSE su_DBG_LOC_ARGS_FILE, su_DBG_LOC_ARGS_LINE # define su_DBG_LOC_ARGS_UNUSED() \ do{\ su_UNUSED(su_DBG_LOC_ARGS_FILE);\ su_UNUSED(su_DBG_LOC_ARGS_LINE);\ }while(0) #else # define su_DBG_LOC_ARGS_FILE "unused" # define su_DBG_LOC_ARGS_LINE 0 # # define su_DBG_LOC_ARGS_DECL_SOLE # define su_DBG_LOC_ARGS_DECL # define su_DBG_LOC_ARGS_INJ_SOLE # define su_DBG_LOC_ARGS_INJ # define su_DBG_LOC_ARGS_USE_SOLE # define su_DBG_LOC_ARGS_USE # define su_DBG_LOC_ARGS_ORUSE su_DBG_LOC_ARGS_FILE, su_DBG_LOC_ARGS_LINE # define su_DBG_LOC_ARGS_UNUSED() do{}while(0) #endif /* su_HAVE_DEVEL || su_HAVE_DEBUG */ /* Development injections */ #if defined su_HAVE_DEVEL || defined su_HAVE_DEBUG /* Not: !defined NDEBUG) */ # define su_DVL(X) X # define su_NDVL(X) # define su_DVLOR(X,Y) X #else # define su_DVL(X) # define su_NDVL(X) X # define su_DVLOR(X,Y) Y #endif /* To avoid files that are overall empty */ #define su_EMPTY_FILE() typedef int su_CONCAT(su_notempty_shall_b_, su_FILE); /* C field init */ #if su_C_LANG && defined __STDC_VERSION__ && __STDC_VERSION__ +0 >= 199901L # define su_FIELD_INITN(N) .N = # define su_FIELD_INITI(I) [I] = #else # define su_FIELD_INITN(N) # define su_FIELD_INITI(I) #endif /* XXX offsetof+: clang,pcc check faked! */ #if su_CC_VCHECK_CLANG(5, 0) || su_CC_VCHECK_GCC(4, 1) ||\ su_CC_VCHECK_PCC(1, 2) || defined DOXYGEN /*! The offset of field \a{F} in the type \a{T}. */ # define su_FIELD_OFFSETOF(T,F) __builtin_offsetof(T, F) #else # define su_FIELD_OFFSETOF(T,F) su_S(su_uz,su_S(su_up,&(su_S(T *,su_NIL)->F))) #endif /*! Distance in between the fields \a{S}tart and \a{E}end in type \a{T}. */ #define su_FIELD_RANGEOF(T,S,E) \ (su_FIELD_OFFSETOF(T, E) - su_FIELD_OFFSETOF(T, S)) /*! sizeof() for member fields */ #define su_FIELD_SIZEOF(T,F) sizeof(su_S(T *,su_NIL)->F) /* Multithread injections */ #ifdef su_HAVE_MT # define su_MT(X) X #else # define su_MT(X) #endif /*! Members in constant array */ #define su_NELEM(A) (sizeof(A) / sizeof((A)[0])) /* NYD comes from code-{in,ou}.h (support function below). * Instrumented functions will always have one label for goto: purposes */ #define su_NYD_OU_LABEL su__nydou /*! Pointer to size_t */ #define su_P2UZ(X) su_S(su_uz,(su_up)(X)) /*! Pointer comparison */ #define su_PCMP(A,C,B) (su_R(su_up,A) C su_R(su_up,B)) /* SMP injections */ #ifdef su_HAVE_SMP # define su_SMP(X) X #else # define su_SMP(X) #endif /* String stuff. * __STDC_VERSION__ is ISO C99, so also use __STDC__, which should work */ #if defined __STDC__ || defined __STDC_VERSION__ || su_C_LANG # define su_STRING(X) #X # define su_XSTRING(X) su_STRING(X) # define su_CONCAT(S1,S2) su__CONCAT_1(S1, S2) # define su__CONCAT_1(S1,S2) S1 ## S2 #else # define su_STRING(X) "X" # define su_XSTRING STRING # define su_CONCAT(S1,S2) S1/* will no work out though */S2 #endif /* Compare (maybe mixed-signed) integers cases to T bits, unsigned, * T is one of our homebrew integers, e.g., UCMP(32, su_ABS(n), >, wleft). * Note: does not sign-extend correctly, that is still up to the caller */ #if su_C_LANG # define su_UCMP(T,A,C,B) (su_S(su_ ## u ## T,A) C su_S(su_ ## u ## T,B)) #else # define su_UCMP(T,A,C,B) \ (su_S(su_NSPC(su) u ## T,A) C su_S(su_NSPC(su) u ## T,B)) #endif /* Casts-away (*NOT* cast-away) */ #define su_UNCONST(T,P) su_R(T,su_R(su_up,su_S(void const*,P))) #define su_UNVOLATILE(T,P) su_R(T,su_R(su_up,su_S(void volatile*,P))) /* To avoid warnings with modern compilers for "char*i; *(s32_t*)i=;" */ #define su_UNALIGN(T,P) su_R(T,su_R(su_up,P)) #define su_UNXXX(T,C,P) su_R(T,su_R(su_up,su_S(C,P))) /* Avoid "may be used uninitialized" warnings */ #if defined NDEBUG && !(defined su_HAVE_DEBUG || defined su_HAVE_DEVEL) # define su_UNINIT(N,V) su_S(void,0) # define su_UNINIT_DECL(V) #else # define su_UNINIT(N,V) N = V # define su_UNINIT_DECL(V) = V #endif /*! Avoid "unused" warnings */ #define su_UNUSED(X) ((void)(X)) /* Variable-type size (with byte array at end) */ #if su_C_LANG && defined __STDC_VERSION__ && __STDC_VERSION__ +0 >= 199901L # define su_VFIELD_SIZE(X) # define su_VSTRUCT_SIZEOF(T,F) sizeof(T) #else # define su_VFIELD_SIZE(X) \ ((X) == 0 ? sizeof(su_uz) \ : (su_S(su_sz,X) < 0 ? sizeof(su_uz) - su_ABS(X) : su_S(su_uz,X))) # define su_VSTRUCT_SIZEOF(T,F) (sizeof(T) - su_FIELD_SIZEOF(T, F)) #endif /* SUPPORT MACROS+ }}} */ /* We are ready to start using our own style */ #ifndef su_CC_SIZE_TYPE # include /* TODO create config time script, */ #endif #include /* TODO query infos and drop */ #include /* TODO those includes! */ #define su_HEADER #include C_DECL_BEGIN /* POD TYPE SUPPORT TODO maybe configure-time, from a su/config.h?! {{{ */ /* TODO Note: the PRI* series will go away once we have FormatCtx! */ /* First some shorter aliases for "normal" integers */ typedef unsigned long su_ul; /*!< \_ */ typedef unsigned int su_ui; /*!< \_ */ typedef unsigned short su_us; /*!< \_ */ typedef unsigned char su_uc; /*!< \_ */ typedef signed long su_sl; /*!< \_ */ typedef signed int su_si; /*!< \_ */ typedef signed short su_ss; /*!< \_ */ typedef signed char su_sc; /*!< \_ */ #if defined UINT8_MAX || defined DOXYGEN # define su_U8_MAX UINT8_MAX /*!< \_ */ # define su_S8_MIN INT8_MIN /*!< \_ */ # define su_S8_MAX INT8_MAX /*!< \_ */ typedef uint8_t su_u8; /*!< \_ */ typedef int8_t su_s8; /*!< \_ */ #elif UCHAR_MAX != 255 # error UCHAR_MAX must be 255 #else # define su_U8_MAX UCHAR_MAX # define su_S8_MIN CHAR_MIN # define su_S8_MAX CHAR_MAX typedef unsigned char su_u8; typedef signed char su_s8; #endif #if !defined PRIu8 || !defined PRId8 # undef PRIu8 # undef PRId8 # define PRIu8 "hhu" # define PRId8 "hhd" #endif #if defined UINT16_MAX || defined DOXYGEN # define su_U16_MAX UINT16_MAX /*!< \_ */ # define su_S16_MIN INT16_MIN /*!< \_ */ # define su_S16_MAX INT16_MAX /*!< \_ */ typedef uint16_t su_u16; /*!< \_ */ typedef int16_t su_s16; /*!< \_ */ #elif USHRT_MAX != 0xFFFFu # error USHRT_MAX must be 0xFFFF #else # define su_U16_MAX USHRT_MAX # define su_S16_MIN SHRT_MIN # define su_S16_MAX SHRT_MAX typedef unsigned short su_u16; typedef signed short su_s16; #endif #if !defined PRIu16 || !defined PRId16 # undef PRIu16 # undef PRId16 # if su_U16_MAX == UINT_MAX # define PRIu16 "u" # define PRId16 "d" # else # define PRIu16 "hu" # define PRId16 "hd" # endif #endif #if defined UINT32_MAX || defined DOXYGEN # define su_U32_MAX UINT32_MAX /*!< \_ */ # define su_S32_MIN INT32_MIN /*!< \_ */ # define su_S32_MAX INT32_MAX /*!< \_ */ typedef uint32_t su_u32; /*!< \_ */ typedef int32_t su_s32; /*!< \_ */ #elif ULONG_MAX == 0xFFFFFFFFu # define su_U32_MAX ULONG_MAX # define su_S32_MIN LONG_MIN # define su_S32_MAX LONG_MAX typedef unsigned long int su_u32; typedef signed long int su_s32; #elif UINT_MAX != 0xFFFFFFFFu # error UINT_MAX must be 0xFFFFFFFF #else # define su_U32_MAX UINT_MAX # define su_S32_MIN INT_MIN # define su_S32_MAX INT_MAX typedef unsigned int su_u32; typedef signed int su_s32; #endif #if !defined PRIu32 || !defined PRId32 # undef PRIu32 # undef PRId32 # if su_U32_MAX == ULONG_MAX # define PRIu32 "lu" # define PRId32 "ld" # else # define PRIu32 "u" # define PRId32 "d" # endif #endif #if defined UINT64_MAX || defined DOXYGEN # define su_U64_MAX UINT64_MAX /*!< \_ */ # define su_S64_MIN INT64_MIN /*!< \_ */ # define su_S64_MAX INT64_MAX /*!< \_ */ # define su_S64_C(C) INT64_C(C) /*!< \_ */ # define su_U64_C(C) UINT64_C(C) /*!< \_ */ typedef uint64_t su_u64; /*!< \_ */ typedef int64_t su_s64; /*!< \_ */ #elif ULONG_MAX <= 0xFFFFFFFFu # if !defined ULLONG_MAX # error We need a 64 bit integer # else # define su_U64_MAX ULLONG_MAX # define su_S64_MIN LLONG_MIN # define su_S64_MAX LLONG_MAX # define su_S64_C(C) su_CONCAT(C, ll) # define su_U64_C(C) su_CONCAT(C, ull) su_CC_EXTEN typedef unsigned long long su_u64; su_CC_EXTEN typedef signed long long su_s64; # endif #else # define su_U64_MAX ULONG_MAX # define su_S64_MIN LONG_MIN # define su_S64_MAX LONG_MAX # define su_S64_C(C) su_CONCAT(C, l) # define su_U64_C(C) su_CONCAT(C, ul) typedef unsigned long su_u64; typedef signed long su_s64; #endif #if !defined PRIu64 || !defined PRId64 || !defined PRIX64 || !defined PRIo64 # undef PRIu64 # undef PRId64 # undef PRIX64 # undef PRIo64 # if defined ULLONG_MAX && su_U64_MAX == ULLONG_MAX # define PRIu64 "llu" # define PRId64 "lld" # define PRIX64 "llX" # define PRIo64 "llo" # else # define PRIu64 "lu" # define PRId64 "ld" # define PRIX64 "lX" # define PRIo64 "lo" # endif #endif /* (So that we can use UCMP() for size_t comparison, too) */ #ifdef su_CC_SIZE_TYPE typedef su_CC_SIZE_TYPE su_uz; #else typedef size_t su_uz; /*!< \_ */ #endif #undef PRIuZ #undef PRIdZ #if (defined __STDC_VERSION__ && __STDC_VERSION__ +0 >= 199901L) ||\ defined DOXYGEN # define PRIuZ "zu" # define PRIdZ "zd" # define su_UZ_MAX SIZE_MAX /*!< \_ */ #elif defined SIZE_MAX /* UnixWare has size_t as unsigned as required but uses a signed limit * constant (which is thus false!) */ # if SIZE_MAX == su_U64_MAX || SIZE_MAX == su_S64_MAX # define PRIuZ PRIu64 # define PRIdZ PRId64 MCTA(sizeof(size_t) == sizeof(u64), "Format string mismatch, compile with ISO C99 compiler (-std=c99)!") # elif SIZE_MAX == su_U32_MAX || SIZE_MAX == su_S32_MAX # define PRIuZ PRIu32 # define PRIdZ PRId32 MCTA(sizeof(size_t) == sizeof(u32), "Format string mismatch, compile with ISO C99 compiler (-std=c99)!") # else # error SIZE_MAX is neither su_U64_MAX nor su_U32_MAX (please report this) # endif # define su_UZ_MAX SIZE_MAX #endif #if !defined PRIuZ && !defined DOXYGEN # define PRIuZ "lu" # define PRIdZ "ld" MCTA(sizeof(size_t) == sizeof(unsigned long), "Format string mismatch, compile with ISO C99 compiler (-std=c99)!") #endif /* The signed equivalence is not really compliant to the standard */ #if su_UZ_MAX == su_U32_MAX || su_UZ_MAX == su_S32_MAX || defined DOXYGEN # define su_SZ_MIN su_S32_MIN /*!< \_ */ # define su_SZ_MAX su_S32_MAX /*!< \_ */ # define su_UZ_BITS 32u /*!< \_ */ # define su_64(X) /*!< \_ */ # define su_32(X) X /*!< \_ */ # define su_6432(X,Y) Y /*!< \_ */ typedef su_s32 su_sz; /*!< \_ */ #elif su_UZ_MAX == su_U64_MAX # define su_SZ_MIN su_S64_MIN # define su_SZ_MAX su_S64_MAX # define su_UZ_BITS 64u # define su_64(X) X # define su_32(X) # define su_6432(X,Y) X typedef su_s64 su_sz; #else # error I cannot handle this maximum value of size_t #endif MCTA(sizeof(su_uz) == sizeof(void*), "SU cannot handle sizeof(su_uz) != sizeof(void*)") /* Regardless of P2UZ provide this one; only use it rarely */ #if defined UINTPTR_MAX || defined DOXYGEN typedef uintptr_t su_up; /*!< \_ */ typedef intptr_t su_sp; /*!< \_ */ #else # ifdef SIZE_MAX typedef su_uz su_up; typedef su_sz su_sp; # else typedef su_ul su_up; typedef su_sl su_sp; # endif #endif /*! Values for #su_boole (normally only \c{FAL0} and \c{TRU1}). */ enum{ su_FAL0, /*!< 0 (no bits set). */ su_TRU1, /*!< The value 1. */ su_TRU2, /*!< The value 2. */ su_TRUM1 = -1 /*!< All bits set. */ }; typedef su_s8 su_boole; /*!< The \SU boolean type (see \FAL0 etc.). */ /* POD TYPE SUPPORT }}} */ /* BASIC TYPE TRAITS {{{ */ struct su_toolbox; /* plus PTF typedefs */ /*! Create a new default instance of an object type, return it or \NIL. * See \r{su_clone_fun} for the meaning of \a{estate}. */ typedef void *(*su_new_fun)(u32 estate); /*! Create a clone of \a{t}, and return it. * \a{estate} might be set to some \r{su_state_err_type}s to be turned to * non-fatal errors, and contain \r{su_state_err_flags} with additional * control requests. * Otherwise (\a{estate} is 0) \NIL can still be returned for * \r{su_STATE_ERR_NOMEM} or \r{su_STATE_ERR_OVERFLOW}, dependent on the * global \r{su_state_get()} / \r{su_state_has()} setting, * as well as for other errors and with other \r{su_err_number}s, of course. * Also see \r{su_STATE_ERR_NIL_IS_VALID_OBJECT}. */ typedef void *(*su_clone_fun)(void const *t, u32 estate); /*! Delete an instance returned by \r{su_new_fun} or \r{su_clone_fun} (or * \r{su_assign_fun}). */ typedef void (*su_delete_fun)(void *self); /*! Assign \a{t}; see \r{su_clone_fun} for the meaning of \a{estate}. * In-place update of \SELF is (and should) not (be) assumed, but instead the * return value has to be used, with the exception as follows. * First all resources of \a{self} should be released (an operation which is * not supposed to fail), then the assignment be performed. * If this fails, \a{self} should be turned to cleared state again, * and \NIL should be returned. * * \remarks{This function is not used by (object owning) \r{COLL} unless * \r{su_STATE_ERR_NIL_IS_VALID_OBJECT} is set. Regardless, if \NIL is * returned to indicate error then the caller which passed a non-\NIL object * is responsible for deletion or taking other appropriate steps.} * * \remarks{If \a{self} and \a{t} are \r{COLL}, then if assignment fails then * whereas \a{self} will not manage any elements, it has been assigned \a{t}'s * possible existent \r{su_toolbox} as well as other attributes. * Some \r{COLL} will provide an additional \c{assign_elems()} function.} */ typedef void *(*su_assign_fun)(void *self, void const *t, u32 estate); /*! Compare \a{a} and \a{b}, and return a value less than 0 if \a{a} is "less * than \a{b}", 0 on equality, and a value greater than 0 if \a{a} is * "greate than \a{b}". */ typedef su_sz (*su_compare_fun)(void const *a, void const *b); /*! Create a hash that reproducibly represents \SELF. */ typedef su_uz (*su_hash_fun)(void const *self); /* Needs to be binary compatible with \c{su::{toolbox,type_toolbox}}! */ /*! A toolbox provides object handling knowledge to \r{COLL}. * Also see \r{su_TOOLBOX_I9R()}. */ struct su_toolbox{ su_clone_fun tb_clone; /*!< \copydoc{su_clone_fun}. */ su_delete_fun tb_delete; /*!< \copydoc{su_delete_fun}. */ su_assign_fun tb_assign; /*!< \copydoc{su_assign_fun}. */ su_compare_fun tb_compare; /*!< \copydoc{su_compare_fun}. */ su_hash_fun tb_hash; /*!< \copydoc{su_hash_fun}. */ }; /* Use C-style casts, not and ever su_R()! */ /*! Initialize a \r{su_toolbox}. */ #define su_TOOLBOX_I9R(CLONE,DELETE,ASSIGN,COMPARE,HASH) \ {\ su_FIELD_INITN(tb_clone) (su_clone_fun)(CLONE),\ su_FIELD_INITN(tb_delete) (su_delete_fun)(DELETE),\ su_FIELD_INITN(tb_assign) (su_assign_fun)(ASSIGN),\ su_FIELD_INITN(tb_compare) (su_compare_fun)(COMPARE),\ su_FIELD_INITN(tb_hash) (su_hash_fun)(HASH)\ } /* BASIC TYPE TRAITS }}} */ /* BASIC C INTERFACE (SYMBOLS) {{{ */ /*! Byte order mark macro; there are also \r{su_bom}, \r{su_BOM_IS_BIG()} and * \r{su_BOM_IS_LITTLE()}. */ #define su_BOM 0xFEFFu /* su_state.. machinery: first byte: global log instance.. */ /*! Log priorities, for simplicity of use without _LEVEL or _LVL prefix, * for \r{su_log_set_level()}. */ enum su_log_level{ su_LOG_EMERG, /*!< System is unusable (abort()s the program) */ su_LOG_ALERT, /*!< Action must be taken immediately */ su_LOG_CRIT, /*!< Critical conditions */ su_LOG_ERR, /*!< Error conditions */ su_LOG_WARN, /*!< Warning conditions */ su_LOG_NOTICE, /*!< Normal but significant condition */ su_LOG_INFO, /*!< Informational */ su_LOG_DEBUG /*!< Debug-level message */ }; /*! Adjustment possibilities for the global log domain (e.g, * \r{su_log_write()}), to be set via \r{su_state_set()}, to be queried via * \r{su_state_has()}. */ enum su_state_log_flags{ /*! Prepend a messages \r{su_log_level}. */ su_STATE_LOG_SHOW_LEVEL = 1u<<4, /*! Show the PID (Process IDentification number). * This flag is only honoured if \r{su_program} set to non-\NIL. */ su_STATE_LOG_SHOW_PID = 1u<<5 }; /* ..second byte: hardening errors.. */ /*! Global hardening for out-of-memory and integer etc. overflow: types. * By default out-of-memory situations, or container and string etc. * insertions etc. which cause count/offset datatype overflow result in * \r{su_LOG_EMERG}s, and thus program abortion. * * This global default can be changed by including the corresponding * \c{su_state_err_type} (\r{su_STATE_ERR_NOMEM} and * \r{su_STATE_ERR_OVERFLOW}, respectively), in the global \SU state machine * via \r{su_state_set()}, in which case logging uses \r{su_LOG_ALERT} level, * a corresponding \r{su_err_number} will be assigned for \r{su_err_no()}, and * the failed function will return error. * * Often functions and object allow additional control over the global on * a by-call or by-object basis, taking a state argument which consists of * \c{su_state_err_type} and \r{su_state_err_flags} bits. * In order to support this these values do not form an enumeration, but * rather are combinable bits. */ enum su_state_err_type{ su_STATE_ERR_NOMEM = 1u<<8, /*!< Out-of-memory. */ su_STATE_ERR_OVERFLOW = 1u<<9 /*!< Integer/xy domain overflow. */ }; /*! Hardening for out-of-memory and integer etc. overflow: adjustment flags. * Many functions offer the possibility to adjust the global \r{su_state_get()} * (\r{su_state_has()}) \r{su_state_err_type} default on a per-call level, and * object types (can) do so on a per-object basis. * * If so, the global state can (selectively) be bypassed by adding in * \r{su_state_err_type}s to be ignored to an (optional) function argument, * or object control function or field. * It is also possible to instead enforce program abortion regardless of * a global ignorance policy, and pass other control flags. */ enum su_state_err_flags{ /*! A mask containing all \r{su_state_err_type} bits. */ su_STATE_ERR_TYPE_MASK = su_STATE_ERR_NOMEM | su_STATE_ERR_OVERFLOW, /*! Allow passing of all errors. * This is just a better name alias for \r{su_STATE_ERR_TYPE_MASK}. */ su_STATE_ERR_PASS = su_STATE_ERR_TYPE_MASK, /*! Regardless of global (and additional local) policy, if this flag is * set, an actual error causes a hard program abortion. */ su_STATE_ERR_NOPASS = 1u<<12, /*! If this flag is set and no abortion is about to happen, a corresponding * \r{su_err_number} will not be assigned to \r{su_err_no()}. */ su_STATE_ERR_NOERRNO = 1u<<13, /*! This is special in that it plays no role in the global state machine. * However, many types or functions which provide \a{estate} arguments and * use (NOT) \r{su_STATE_ERR_MASK} to overload that with meaning, adding * support for owning \r{COLL} (for \r{su_toolbox} users, to be exact) * actually made sense: if this bit is set it indicates that \NIL values * returned by \r{su_toolbox} members are acceptible values (and thus do not * cause actions like insertion, replacement etc. to fail). */ su_STATE_ERR_NIL_IS_VALID_OBJECT = 1u<<14, /*! Alias for \r{su_STATE_ERR_NIL_IS_VALID_OBJECT}. */ su_STATE_ERR_NILISVALO = su_STATE_ERR_NIL_IS_VALID_OBJECT, /*! Handy mask for the entire family of error \SU error bits, * \r{su_state_err_type} and \r{su_state_err_flags}. * It can be used by functions or methods which allow fine-tuning of error * behaviour to strip down an user argument. * * \remarks{This mask itself is covered by the mask \c{0xFF00}. * This condition is compile-time asserted.} */ su_STATE_ERR_MASK = su_STATE_ERR_TYPE_MASK | su_STATE_ERR_PASS | su_STATE_ERR_NOPASS | su_STATE_ERR_NOERRNO | su_STATE_ERR_NIL_IS_VALID_OBJECT }; /* ..third byte: misc flags */ /*! \_ */ enum su_state_flags{ su_STATE_NONE, /*!< No flag: this is 0. */ su_STATE_DEBUG = 1u<<16, /*!< \_ */ su_STATE_VERBOSE = 1u<<17, /*!< \_ */ /*! Reproducible behaviour switch. * See \r{su_reproducible_build}, * and \xln{https://reproducible-builds.org}. */ su_STATE_REPRODUCIBLE = 1u<<18 }; enum su__state_flags{ /* enum su_log_level is first "member" */ su__STATE_LOG_MASK = 0x0Fu, su__STATE_D_V = su_STATE_DEBUG | su_STATE_VERBOSE, /* What is not allowed in the global state machine */ su__STATE_GLOBAL_MASK = 0x00FFFFFFu & ~(su__STATE_LOG_MASK | (su_STATE_ERR_MASK & ~su_STATE_ERR_TYPE_MASK)) }; MCTA((uz)su_LOG_DEBUG <= (uz)su__STATE_LOG_MASK, "Bit ranges may not overlap") MCTA(((uz)su_STATE_ERR_MASK & ~0xFF00) == 0, "Bits excess documented bounds") #ifdef su_HAVE_MT enum su__glock_type{ su__GLOCK_STATE, su__GLOCK_LOG, su__GLOCK_MAX = su__GLOCK_LOG }; #endif /*! The \SU error number constants. * In order to achieve a 1:1 mapping of the \SU and the host value, e.g., * of \ERR{INTR} and \c{EINTR}, the actual values will be detected at * compilation time. * Non resolvable (native) mappings will map to \ERR{NOTOBACCO}, * \SU mappings with no (native) mapping will have high unsigned numbers. */ enum su_err_number{ #ifdef DOXYGEN su_ERR_NONE, /*!< No error. */ su_ERR_NOTOBACCO /*!< No such errno, fallback: no mapping exists. */ #else su__ERR_NUMBER_ENUM_C # undef su__ERR_NUMBER_ENUM_C #endif }; union su__bom_union{ char bu_buf[2]; u16 bu_val; }; /* Known endianess bom versions, see su_bom_little, su_bom_big */ EXPORT_DATA union su__bom_union const su__bom_little; EXPORT_DATA union su__bom_union const su__bom_big; /* (Not yet) Internal enum su_state_* bit carrier */ EXPORT_DATA uz su__state; /*! The byte order mark \r{su_BOM} in host, \r{su_bom_little} and * \r{su_bom_big} byte order. * The latter two are macros which access constant union data. * We also define two helpers \r{su_BOM_IS_BIG()} and \r{su_BOM_IS_LITTLE()}, * which will expand to preprocessor statements if possible (by using * \r{su_CC_BOM}, \r{su_CC_BOM_LITTLE} and \r{su_CC_BOM_BIG}), but otherwise * to comparisons of the external constants. */ EXPORT_DATA u16 const su_bom; #define su_bom_little su__bom_little.bu_val /*!< \_ */ #define su_bom_big su__bom_big.bu_val /*!< \_ */ #if defined su_CC_BOM || defined DOXYGEN # define su_BOM_IS_BIG() (su_CC_BOM == su_CC_BOM_BIG) /*!< \r{su_bom}. */ # define su_BOM_IS_LITTLE() (su_CC_BOM == su_CC_BOM_LITTLE) /*!< \r{su_bom}. */ #else # define su_BOM_IS_BIG() (su_bom == su_bom_big) # define su_BOM_IS_LITTLE() (su_bom == su_bom_little) #endif /*! The empty string. */ EXPORT_DATA char const su_empty[1]; /*! The string \c{reproducible_build}, see \r{su_STATE_REPRODUCIBLE}. */ EXPORT_DATA char const su_reproducible_build[]; /*! Can be set to the name of the program to, e.g., create a common log * message prefix. * Also see \r{su_STATE_LOG_SHOW_PID}, \r{su_STATE_LOG_SHOW_LEVEL}. */ EXPORT_DATA char const *su_program; /**/ #ifdef su_HAVE_MT EXPORT void su__glock(enum su__glock_type gt); EXPORT void su__gunlock(enum su__glock_type gt); #endif /*! Interaction with the SU library (global) state machine. * This covers \r{su_state_log_flags}, \r{su_state_err_type}, * and \r{su_state_flags} flags and values. */ INLINE u32 su_state_get(void){ return (su__state & su__STATE_GLOBAL_MASK); } /*! Interaction with the SU library (global) state machine: * test whether all (not any) of \a{flags} are set in \r{su_state_get()}. */ INLINE boole su_state_has(uz flags){ uz f = flags & su__STATE_GLOBAL_MASK; return ((su__state & f) == f); } /*! \_ */ INLINE void su_state_set(uz flags){ MT( su__glock(su__GLOCK_STATE); ) su__state |= flags & su__STATE_GLOBAL_MASK; MT( su__gunlock(su__GLOCK_STATE); ) } /*! \_ */ INLINE void su_state_clear(uz flags){ MT( su__glock(su__GLOCK_STATE); ) su__state &= ~(flags & su__STATE_GLOBAL_MASK); MT( su__gunlock(su__GLOCK_STATE); ) } /*! Notify an error to the \SU (global) state machine. * If the function is allowd to return a corresponding \r{su_err_number} will * be returned. */ EXPORT s32 su_state_err(enum su_state_err_type err, uz state, char const *msg_or_nil); /*! \_ */ EXPORT s32 su_err_no(void); /*! \_ */ EXPORT s32 su_err_set_no(s32 eno); /*! Return string(s) describing C error number eno. * This is effectively identical to \r{su_err_name()} if the compile-time * option \r{su_HAVE_DOCSTRINGS} is missing. */ EXPORT char const *su_err_doc(s32 eno); /*! \_ */ EXPORT char const *su_err_name(s32 eno); /*! Try to map an error name to an error number. * Returns the fallback error as a negative value if none found */ EXPORT s32 su_err_from_name(char const *name); /*! \_ */ EXPORT s32 su_err_no_via_errno(void); /*! \_ */ INLINE enum su_log_level su_log_get_level(void){ return S(enum su_log_level,su__state & su__STATE_LOG_MASK); } /*! \_ */ INLINE void su_log_set_level(enum su_log_level nlvl){ MT( su__glock(su__GLOCK_STATE); ) su__state = (su__state & su__STATE_GLOBAL_MASK) | (S(uz,nlvl) & su__STATE_LOG_MASK); MT( su__gunlock(su__GLOCK_STATE); ) } /*! SMP lock the global log domain. */ INLINE void su_log_lock(void){ MT( su__glock(su__GLOCK_LOG); ) } /*! SMP unlock the global log domain. */ INLINE void su_log_unlock(void){ MT( su__gunlock(su__GLOCK_LOG); ) } /*! Log functions of various sort. * Regardless of the level these also log if \c{STATE_DEBUG|STATE_VERBOSE}. * If \r{su_program} is set, it will be prepended to messages.TODO 1perLn */ EXPORT void su_log_write(enum su_log_level lvl, char const *fmt, ...); /*! See \r{su_log_write()}. The \a{vp} is a \c{&va_list}. */ EXPORT void su_log_vwrite(enum su_log_level lvl, char const *fmt, void *vp); /*! Like perror(3). */ EXPORT void su_perr(char const *msg, s32 eno_or_0); #if !defined su_ASSERT_EXPAND_NOTHING || defined DOXYGEN /*! With a \FAL0 crash this only logs. * In order to get rid of linkage define \c{su_ASSERT_EXPAND_NOTHING}. */ EXPORT void su_assert(char const *expr, char const *file, u32 line, char const *fun, boole crash); #else # define su_assert(EXPR,FILE,LINE,FUN,CRASH) #endif #if DVLOR(1, 0) EXPORT void su_nyd_chirp(u8 act, char const *file, u32 line, char const *fun); EXPORT void su_nyd_stop(void); EXPORT void su_nyd_dump(void (*ptf)(up cookie, char const *buf, uz blen), up cookie); #endif /* BASIC C INTERFACE (SYMBOLS) }}} */ C_DECL_END #include #if !su_C_LANG || defined CXX_DOXYGEN # define su_CXX_HEADER # include NSPC_BEGIN(su) /* POD TYPE SUPPORT {{{ */ // All instanceless static encapsulators. class min; class max; // Define in-namespace wrappers for C types. code-in/ou do not define short // names for POD when used from within C++ typedef su_ul ul; /*!< \_ */ typedef su_ui ui; /*!< \_ */ typedef su_us us; /*!< \_ */ typedef su_uc uc; /*!< \_ */ typedef su_sl sl; /*!< \_ */ typedef su_si si; /*!< \_ */ typedef su_ss ss; /*!< \_ */ typedef su_sc sc; /*!< \_ */ typedef su_u8 u8; /*!< \_ */ typedef su_s8 s8; /*!< \_ */ typedef su_u16 u16; /*!< \_ */ typedef su_s16 s16; /*!< \_ */ typedef su_u32 u32; /*!< \_ */ typedef su_s32 s32; /*!< \_ */ typedef su_u64 u64; /*!< \_ */ typedef su_s64 s64; /*!< \_ */ typedef su_uz uz; /*!< \_ */ typedef su_sz sz; /*!< \_ */ typedef su_up up; /*!< \_ */ typedef su_sp sp; /*!< \_ */ typedef su_boole boole; /*!< \_ */ /*! Values for \r{su_boole}. */ enum{ FAL0 = su_FAL0, /*!< \_ */ TRU1 = su_TRU1, /*!< \_ */ TRU2 = su_TRU2, /*!< \_ */ TRUM1 = su_TRUM1 /*!< All bits set. */ }; /* Place the mentioned alignment CTAs */ MCTA(IS_POW2(sizeof(uz)), "Must be power of two") MCTA(IS_POW2(su__ZAL_S), "Must be power of two") MCTA(IS_POW2(su__ZAL_L), "Must be power of two") /*! \_ */ class min{ public: static NSPC(su)s8 const s8 = su_S8_MIN; /*!< \copydoc{su_S8_MIN} */ static NSPC(su)s16 const s16 = su_S16_MIN; /*!< \copydoc{su_S16_MIN} */ static NSPC(su)s32 const s32 = su_S32_MIN; /*!< \copydoc{su_S32_MIN} */ static NSPC(su)s64 const s64 = su_S64_MIN; /*!< \copydoc{su_S64_MIN} */ static NSPC(su)sz const sz = su_SZ_MIN; /*!< \copydoc{su_SZ_MIN} */ }; /*! \_ */ class max{ public: static NSPC(su)s8 const s8 = su_S8_MAX; /*!< \copydoc{su_S8_MAX} */ static NSPC(su)s16 const s16 = su_S16_MAX; /*!< \copydoc{su_S16_MAX} */ static NSPC(su)s32 const s32 = su_S32_MAX; /*!< \copydoc{su_S32_MAX} */ static NSPC(su)s64 const s64 = su_S64_MAX; /*!< \copydoc{su_S64_MAX} */ static NSPC(su)sz const sz = su_SZ_MAX; /*!< \copydoc{su_SZ_MAX} */ static NSPC(su)u8 const u8 = su_U8_MAX; /*!< \copydoc{su_U8_MAX} */ static NSPC(su)u16 const u16 = su_U16_MAX; /*!< \copydoc{su_U16_MAX} */ static NSPC(su)u32 const u32 = su_U32_MAX; /*!< \copydoc{su_U32_MAX} */ static NSPC(su)u64 const u64 = su_U64_MAX; /*!< \copydoc{su_U64_MAX} */ static NSPC(su)uz const uz = su_UZ_MAX; /*!< \copydoc{su_UZ_MAX} */ }; /* POD TYPE SUPPORT }}} */ /* BASIC TYPE TRAITS {{{ */ template class type_traits; template struct type_toolbox; // Plus C wrapper typedef // External forward, defined in a-t-t.h. template class auto_type_toolbox; typedef su_toolbox toolbox; /*!< See \r{type_toolbox}, \r{COLL}. */ /*! See \r{type_toolbox}, \r{COLL}. */ template class type_traits{ public: typedef T type; /*!< \_ */ typedef T *tp; /*!< \_ */ typedef T const type_const; /*!< \_ */ typedef T const *tp_const; /*!< \_ */ typedef NSPC(su)type_toolbox type_toolbox; /*!< \_ */ typedef NSPC(su)auto_type_toolbox auto_type_toolbox; /*!< \_ */ /*! Non-pointer types are by default own-guessed, pointer based ones not. */ static boole const ownguess = TRU1; /*! Ditto, associative collections, keys. */ static boole const ownguess_key = TRU1; /*! \_ */ static void *to_vp(tp_const t) {return C(void*,S(void const*,t));} /*! \_ */ static void const *to_const_vp(tp_const t) {return t;} /*! \_ */ static tp to_tp(void const *t) {return C(tp,S(tp_const,t));} /*! \_ */ static tp_const to_const_tp(void const *t) {return S(tp_const,t);} }; // Some specializations template class type_traits{ // (ugly, but required for some node based colls..) public: typedef T type; typedef T *tp; typedef T const type_const; typedef T const *tp_const; typedef NSPC(su)type_toolbox type_toolbox; typedef NSPC(su)auto_type_toolbox auto_type_toolbox; static boole const ownguess = FAL0; static boole const ownguess_key = TRU1; static void *to_vp(tp_const t) {return C(tp,t);} static void const *to_const_vp(tp_const t) {return t;} static tp to_tp(void const *t) {return C(tp,S(tp_const,t));} static tp_const to_const_tp(void const *t) {return S(tp_const,t);} }; template class type_traits{ public: typedef T type; typedef T *tp; typedef T const type_const; typedef T const *tp_const; typedef NSPC(su)type_toolbox type_toolbox; typedef NSPC(su)auto_type_toolbox auto_type_toolbox; static boole const ownguess = FAL0; static boole const ownguess_key = TRU1; static void *to_vp(tp_const t) {return C(tp,t);} static void const *to_const_vp(tp_const t) {return t;} static tp to_tp(void const *t) {return C(tp,S(tp_const,t));} static tp_const to_const_tp(void const *t) {return S(tp_const,t);} }; template<> class type_traits{ public: typedef void *type; typedef void *tp; typedef void const *type_const; typedef void const *tp_const; typedef NSPC(su)toolbox type_toolbox; typedef NSPC(su)auto_type_toolbox auto_type_toolbox; static boole const ownguess = FAL0; static boole const ownguess_key = FAL0; static void *to_vp(tp_const t) {return C(tp,t);} static void const *to_const_vp(tp_const t) {return t;} static tp to_tp(void const *t) {return C(tp,S(tp_const,t));} static tp_const to_const_tp(void const *t) {return S(tp_const,t);} }; template<> class type_traits{ public: typedef void const *type; typedef void const *tp; typedef void const *type_const; typedef void const *tp_const; typedef NSPC(su)toolbox type_toolbox; typedef NSPC(su)auto_type_toolbox auto_type_toolbox; static boole const ownguess = FAL0; static boole const ownguess_key = FAL0; static void *to_vp(tp_const t) {return C(void*,t);} static void const *to_const_vp(tp_const t) {return t;} static tp to_tp(void const *t) {return C(void*,t);} static tp_const to_const_tp(void const *t) {return t;} }; template<> class type_traits{ public: typedef char *type; typedef char *tp; typedef char const *type_const; typedef char const *tp_const; typedef NSPC(su)type_toolbox type_toolbox; typedef NSPC(su)auto_type_toolbox auto_type_toolbox; static boole const ownguess = FAL0; static boole const ownguess_key = TRU1; static void *to_vp(tp_const t) {return C(tp,t);} static void const *to_const_vp(tp_const t) {return t;} static tp to_tp(void const *t) {return C(tp,S(tp_const,t));} static tp_const to_const_tp(void const *t) {return S(tp_const,t);} }; template<> class type_traits{ public: typedef char const *type; typedef char const *tp; typedef char const *type_const; typedef char const *tp_const; typedef NSPC(su)type_toolbox type_toolbox; typedef NSPC(su)auto_type_toolbox auto_type_toolbox; static boole const ownguess = FAL0; static boole const ownguess_key = TRU1; static void *to_vp(tp_const t) {return C(char*,t);} static void const *to_const_vp(tp_const t) {return t;} static tp to_tp(void const *t) {return C(char*,S(tp_const,t));} static tp_const to_const_tp(void const *t) {return S(tp_const,t);} }; /*! This is binary compatible with \r{toolbox} (and \r{su_toolbox})! * Also see \r{COLL}. */ template struct type_toolbox{ /*! \_ */ typedef NSPC(su)type_traits type_traits; /*! \copydoc{su_clone_fun} */ typedef typename type_traits::tp (*clone_fun)( typename type_traits::tp_const t, u32 estate); /*! \copydoc{su_delete_fun} */ typedef void (*delete_fun)(typename type_traits::tp self); /*! \copydoc{su_assign_fun} */ typedef typename type_traits::tp (*assign_fun)( typename type_traits::tp self, typename type_traits::tp_const t, u32 estate); /*! \copydoc{su_compare_fun} */ typedef sz (*compare_fun)(typename type_traits::tp_const self, typename type_traits::tp_const t); /*! \copydoc{su_hash_fun} */ typedef uz (*hash_fun)(typename type_traits::tp_const self); /*! \r{#clone_fun} */ clone_fun ttb_clone; /*! \r{#delete_fun} */ delete_fun ttb_delete; /*! \r{#assign_fun} */ assign_fun ttb_assign; /*! \r{#compare_fun} */ compare_fun ttb_compare; /*! \r{#hash_fun} */ hash_fun ttb_hash; }; /*! Initialize a \r{type_toolbox}. */ #define su_TYPE_TOOLBOX_I9R(CLONE,DELETE,ASSIGN,COMPARE,HASH) \ { CLONE, DELETE, ASSIGN, COMPARE, HASH } // abc,clip,max,min,pow2 -- the C macros are in SUPPORT MACROS+ /*! \_ */ template inline T get_abs(T const &a) {return su_ABS(a);} /*! \copydoc{su_CLIP()} */ template inline T const &get_clip(T const &a, T const &min, T const &max){ return su_CLIP(a, min, max); } /*! \copydoc{su_MAX()} */ template inline T const &get_max(T const &a, T const &b) {return su_MAX(a, b);} /*! \copydoc{su_MIN()} */ template inline T const &get_min(T const &a, T const &b) {return su_MIN(a, b);} /*! \copydoc{su_IS_POW2()} */ template inline int is_pow2(T const &a) {return su_IS_POW2(a);} /* BASIC TYPE TRAITS }}} */ /* BASIC C++ INTERFACE (SYMBOLS) {{{ */ // FIXME C++ does not yet expose the public C EXPORT_DATA symbols // All instanceless static encapsulators. class bom; class err; class log; class state; /*! \copydoc{su_bom} */ class bom{ public: /*! \copydoc{su_BOM} */ static u16 host(void) {return su_BOM;} /*! \copydoc{su_bom_little} */ static u16 little(void) {return su_bom_little;} /*! \copydoc{su_bom_big} */ static u16 big(void) {return su_bom_big;} }; /*! \_ */ class err{ public: /*! \copydoc{su_err_number} */ enum err_number{ #ifdef DOXYGEN err_none, /*!< No error. */ err_notobacco /*!< No such errno, fallback: no mapping exists. */ #else su__CXX_ERR_NUMBER_ENUM # undef su__CXX_ERR_NUMBER_ENUM #endif }; /*! \copydoc{su_err_no()} */ static s32 no(void) {return su_err_no();} /*! \copydoc{su_err_set_no()} */ static void set_no(s32 eno) {su_err_set_no(eno);} /*! \copydoc{su_err_doc()} */ static char const *doc(s32 eno) {return su_err_doc(eno);} /*! \copydoc{su_err_name()} */ static char const *name(s32 eno) {return su_err_name(eno);} /*! \copydoc{su_err_from_name()} */ static s32 from_name(char const *name) {return su_err_from_name(name);} /*! \copydoc{su_err_no_via_errno()} */ static s32 no_via_errno(void) {return su_err_no_via_errno();} }; /*! \_ */ class log{ public: /*! \copydoc{su_log_level} */ enum level{ emerg = su_LOG_EMERG, /*!< \copydoc{su_LOG_EMERG} */ alert = su_LOG_ALERT, /*!< \copydoc{su_LOG_ALERT} */ crit = su_LOG_CRIT, /*!< \copydoc{su_LOG_CRIT} */ err = su_LOG_ERR, /*!< \copydoc{su_LOG_ERR} */ warn = su_LOG_WARN, /*!< \copydoc{su_LOG_WARN} */ notice = su_LOG_NOTICE, /*!< \copydoc{su_LOG_NOTICE} */ info = su_LOG_INFO, /*!< \copydoc{su_LOG_INFO} */ debug = su_LOG_DEBUG /*!< \copydoc{su_LOG_DEBUG} */ }; // Log functions of various sort. // Regardless of the level these also log if state_debug|state_verbose. // The vp is a &va_list /*! \copydoc{su_log_get_level()} */ static level get_level(void) {return S(level,su_log_get_level());} /*! \copydoc{su_log_set_level()} */ static void set_level(level lvl) {su_log_set_level(S(su_log_level,lvl));} /*! \copydoc{su_STATE_LOG_SHOW_LEVEL} */ static boole get_show_level(void){ return su_state_has(su_STATE_LOG_SHOW_LEVEL); } /*! \copydoc{su_STATE_LOG_SHOW_LEVEL} */ static void set_show_level(boole on){ if(on) su_state_set(su_STATE_LOG_SHOW_LEVEL); else su_state_clear(su_STATE_LOG_SHOW_LEVEL); } /*! \copydoc{su_STATE_LOG_SHOW_PID} */ static boole get_show_pid(void){ return su_state_has(su_STATE_LOG_SHOW_PID); } /*! \copydoc{su_STATE_LOG_SHOW_PID} */ static void set_show_pid(boole on){ if(on) su_state_set(su_STATE_LOG_SHOW_PID); else su_state_clear(su_STATE_LOG_SHOW_PID); } /*! \copydoc{su_log_lock()} */ static void lock(void) {su_log_lock();} /*! \copydoc{su_log_unlock()} */ static void unlock(void) {su_log_unlock();} /*! \copydoc{su_log_write()} */ static void write(level lvl, char const *fmt, ...); /*! \copydoc{su_log_vwrite()} */ static void vwrite(level lvl, char const *fmt, void *vp){ su_log_vwrite(S(enum su_log_level,lvl), fmt, vp); } /*! \copydoc{su_perr()} */ static void perr(char const *msg, s32 eno_or_0) {su_perr(msg, eno_or_0);} }; /*! \_ */ class state{ public: /*! \copydoc{su_state_err_type} */ enum err_type{ /*! \copydoc{su_STATE_ERR_NOMEM} */ err_nomem = su_STATE_ERR_NOMEM, /*! \copydoc{su_STATE_ERR_OVERFLOW} */ err_overflow = su_STATE_ERR_OVERFLOW }; /*! \copydoc{su_state_err_flags} */ enum err_flags{ /*! \copydoc{su_STATE_ERR_TYPE_MASK} */ err_type_mask = su_STATE_ERR_TYPE_MASK, /*! \copydoc{su_STATE_ERR_PASS} */ err_pass = su_STATE_ERR_PASS, /*! \copydoc{su_STATE_ERR_NOPASS} */ err_nopass = su_STATE_ERR_NOPASS, /*! \copydoc{su_STATE_ERR_NOERRNO} */ err_noerrno = su_STATE_ERR_NOERRNO, /*! \copydoc{su_STATE_ERR_MASK} */ err_mask = su_STATE_ERR_MASK }; /*! \copydoc{su_state_flags} */ enum flags{ /*! \copydoc{su_STATE_NONE} */ none = su_STATE_NONE, /*! \copydoc{su_STATE_DEBUG} */ debug = su_STATE_DEBUG, /*! \copydoc{su_STATE_VERBOSE} */ verbose = su_STATE_VERBOSE, /*! \copydoc{su_STATE_REPRODUCIBLE} */ reproducible = su_STATE_REPRODUCIBLE }; /*! \copydoc{su_program} */ static char const *get_program(void) {return su_program;} /*! \copydoc{su_program} */ static void set_program(char const *name) {su_program = name;} /*! \copydoc{su_state_get()} */ static boole get(void) {return su_state_get();} /*! \copydoc{su_state_has()} */ static boole has(uz state) {return su_state_has(state);} /*! \copydoc{su_state_set()} */ static void set(uz state) {su_state_set(state);} /*! \copydoc{su_state_clear()} */ static void clear(uz state) {su_state_clear(state);} /*! \copydoc{su_state_err()} */ static s32 err(err_type err, uz state, char const *msg_or_nil=NIL){ return su_state_err(S(su_state_err_type,err), state, msg_or_nil); } }; /* BASIC C++ INTERFACE (SYMBOLS) }}} */ NSPC_END(su) #include #endif /* !C_LANG || CXX_DOXYGEN */ /* MORE DOXYGEN TOP GROUPS {{{ */ /*! * \defgroup COLL Collections * \brief Collections * * In \SU, and by default, collections learn how to deal with managed objects * through \r{su_toolbox} objects. * The C++ variants deduce many more things, and automatically, through * (specializations of) \r{type_traits}, \r{type_toolbox}, and * \r{auto_type_toolbox}. */ /*! * \defgroup IO Input/Output * \brief Input and Output */ /*! * \defgroup NET Network * \brief Network */ /*! * \defgroup MEM Memory * \brief Memory */ /*! * \defgroup MISC Miscellaneous * \brief Miscellaneous */ /*! * \defgroup SMP SMP * \brief Simultaneous Multi Processing * * This covers general \r{su_HAVE_SMP}, as well as its multi-threading subset * \r{su_HAVE_MT}. */ /*! * \defgroup TEXT Text * \brief Text */ /* MORE DOXYGEN TOP GROUPS }}} */ /*! @} */ #endif /* !su_CODE_H */ /* s-it-mode */ s-nail-14.9.15/include/su/config.h000066400000000000000000000042211352610246600165520ustar00rootroot00000000000000/*@ (Yet) Manual config.h. *@ XXX Should be splitted into gen-config.h and config.h. * * Copyright (c) 2001 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef su_CODE_H # error Please include su/code.h not su/config.h. #endif #ifndef su_CONFIG_H #define su_CONFIG_H /*#define su_HAVE_NSPC*/ /* For now thought of _MX, _ROFF; _SU: standalone library */ #ifndef su_USECASE_SU # define su_USECASE_MX #endif #ifdef su_USECASE_MX /* In this case we get our config, error maps etc., all from here. * We must take care not to break OPT_AMALGAMATION though */ # ifndef mx_HAVE_AMALGAMATION # include # endif #else # include #endif /* Internal configurables: values */ /* Number of Not-Yet-Dead calls that are remembered */ #define su_NYD_ENTRIES (25 * 84) /* Global configurables (code.h:CONFIG): features */ #ifdef mx_HAVE_DEBUG # define su_HAVE_DEBUG #endif #ifdef mx_HAVE_DEVEL # define su_HAVE_DEVEL #endif #ifdef mx_HAVE_DOCSTRINGS # define su_HAVE_DOCSTRINGS #endif #define su_HAVE_MEM_BAG_AUTO #define su_HAVE_MEM_BAG_LOFI #ifdef mx_HAVE_NOMEMDBG # define su_HAVE_MEM_CANARIES_DISABLE #endif #undef su_HAVE_MT #undef su_HAVE_SMP /* Global configurables (code.h:CONFIG): values */ /* Hardware page size (xxx additional dynamic lookup support) */ #ifndef su_PAGE_SIZE # error Need su_PAGE_SIZE configuration #endif #endif /* !su_CONFIG_H */ /* s-it-mode */ s-nail-14.9.15/include/su/cs-dict.h000066400000000000000000000623571352610246600166510ustar00rootroot00000000000000/*@ Dictionary with char* keys. * * Copyright (c) 2001 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef su_CS_DICT_H #define su_CS_DICT_H /*! * \file * \ingroup CS_DICT * \brief \r{CS_DICT} */ #include #define su_HEADER #include C_DECL_BEGIN struct su_cs_dict; struct su_cs_dict_view; /*! * \defgroup CS_DICT Dictionary with C-style string keys * \ingroup COLL * \brief Dictionary with C-style string keys (\r{su/cs-dict.h}) * * A dictionary for unique non-\NIL \c{char*} a.k.a. byte character data keys * with a maximum length of \r{su_S32_MAX} bytes. * The view type is the associative unidirectional \r{su_cs_dict_view}: * \r{CS_DICT_VIEW}. * * \list{\li{ * Keys will be stored in full in the list nodes which make up the dictionary. * They will be hashed and compared by means of \ref{CS}. * }\li{ * Values are optionally owned (\r{su_CS_DICT_OWNS}), in which case the given, * then mandatory \r{su_toolbox} is used to manage value objects. * Dependent upon \r{su_CS_DICT_NIL_IS_VALID_OBJECT} \NIL values will still be * supported. * }\li{ * \r{su_STATE_ERR_PASS} may be enforced on a per-object level by setting * \r{su_CS_DICT_ERR_PASS}. * This interacts with \r{su_CS_DICT_NIL_IS_VALID_OBJECT}. * }\li{ * In-array-index list node head resorting can be controlled via * \r{su_CS_DICT_HEAD_RESORT}. * }\li{ * The array size / node count spacing relation is controllable via * \r{su_cs_dict_set_treshold_shift()}. * Automatic shrinks are unaffected by that and happen only if * \r{su_CS_DICT_AUTO_SHRINK} is enabled. * }\li{ * It is possible to turn instances into \r{su_CS_DICT_FROZEN} state. * \r{su_cs_dict_balance()} can be used at a later time to thaw the object * and make it reflect its new situation, if so desired. * }} * * \remarks{This collection does not offer \fn{hash()} and \fn{compare()} * functions, because these operations would be too expensive: for reproducable * and thus correct results a temporary sorted copy would be necessary. * * Due to lack of this functionality it also does not offer a \r{su_toolbox} * instance to operate on object-instances of itself.} * @{ */ /*! Flags for \r{su_cs_dict_create()}, to be queried via * \r{su_cs_dict_flags()}, and to be adjusted via \r{su_cs_dict_add_flags()} * and \r{su_cs_dict_clear_flags()}. */ enum su_cs_dict_flags{ /*! Whether power-of-two spacing and mask indexing is used. * If this is not set, prime spacing and modulo indexing will be used. * \remarks{Changing this setting later on is possible, but testifying the * implied consequences work out is up to the caller.} */ su_CS_DICT_POW2_SPACED = 1u<<0, /*! Values are owned. * \remarks{Changing this setting later on is possible, but testifying the * implied consequences work out is up to the caller.} */ su_CS_DICT_OWNS = 1u<<1, /*! Keys shall be hashed, compared and stored case-insensitively. */ su_CS_DICT_CASE = 1u<<2, /*! Enable array-index list head rotation? * This dictionary uses an array of nodes which form singly-linked lists. * With this bit set, whenever a key is found in such a list, its list node * will become the new head of the list, which could over time improve * lookup speed due to lists becoming sorted by "hotness" over time. */ su_CS_DICT_HEAD_RESORT = 1u<<3, /*! Enable automatic shrinking of the management array. * This is not enabled by default (the array only grows). * See \r{su_CS_DICT_FROZEN} (and \r{su_cs_dict_set_treshold_shift()}). */ su_CS_DICT_AUTO_SHRINK = 1u<<4, /*! Freeze the dictionary. * A frozen dictionary will neither grow nor shrink the management array of * nodes automatically. * When inserting/removing many key/value tuples it increases efficiency to * first freeze, perform the operations, and then perform finalization * by calling \r{su_cs_dict_balance()}. */ su_CS_DICT_FROZEN = 1u<<5, /*! Mapped to \r{su_STATE_ERR_PASS}. */ su_CS_DICT_ERR_PASS = su_STATE_ERR_PASS, /*! Mapped to \r{su_STATE_ERR_NIL_IS_VALID_OBJECT}, but only honoured for * dictionaries which \r{su_CS_DICT_OWNS} its values. */ su_CS_DICT_NIL_IS_VALID_OBJECT = su_STATE_ERR_NIL_IS_VALID_OBJECT, /*! Alias for \r{su_CS_DICT_NIL_IS_VALID_OBJECT}. */ su_CS_DICT_NILISVALO = su_CS_DICT_NIL_IS_VALID_OBJECT, su__CS_DICT_CREATE_MASK = su_CS_DICT_POW2_SPACED | su_CS_DICT_OWNS | su_CS_DICT_CASE | su_CS_DICT_HEAD_RESORT | su_CS_DICT_AUTO_SHRINK | su_CS_DICT_FROZEN | su_CS_DICT_ERR_PASS | su_CS_DICT_NIL_IS_VALID_OBJECT }; #ifdef su_SOURCE_CS_DICT CTA((su_STATE_ERR_MASK & ~0xFF00u) == 0, "Reuse of low order bits impossible, or mask excesses storage"); #endif /*! Opaque. */ struct su_cs_dict{ struct su__cs_dict_node **csd_array; u16 csd_flags; u8 csd_tshift; u8 csd__pad[su_6432(5,1)]; u32 csd_count; u32 csd_size; struct su_toolbox const *csd_tbox; }; struct su__cs_dict_node{ struct su__cs_dict_node *csdn_next; void *csdn_data; uz csdn_khash; u32 csdn_klen; char csdn_key[su_VFIELD_SIZE(4)]; }; /* "The const is preserved logically" */ EXPORT struct su__cs_dict_node *su__cs_dict_lookup( struct su_cs_dict const *self, char const *key, void *lookarg_or_nil); EXPORT s32 su__cs_dict_insrep(struct su_cs_dict *self, char const *key, void *value, boole replace); #if DVLOR(1, 0) EXPORT void su__cs_dict_stats(struct su_cs_dict const *self); #endif /*! Easy iteration support. * \a{VIEWNAME} must be a(n) (non-pointer) instance of \r{su_cs_dict_view}. */ #define su_CS_DICT_FOREACH(SELF,VIEWNAME) \ for(su_cs_dict_view_begin(su_cs_dict_view_setup(VIEWNAME, SELF));\ su_cs_dict_view_is_valid(VIEWNAME);\ su_cs_dict_view_next(VIEWNAME)) /*! Create an instance. * \a{flags} is a mix of \r{su_cs_dict_flags}. * If \r{su_CS_DICT_OWNS} is specified, \a{tbox_or_nil} is a mandatory, * asserted argument. */ EXPORT struct su_cs_dict *su_cs_dict_create(struct su_cs_dict *self, u16 flags, struct su_toolbox const *tbox_or_nil); /*! Initialization plus \r{su_cs_dict_assign()}. * \remarks{In difference to assignment this inherits all flags and * properties from \a{t}.} */ EXPORT SHADOW struct su_cs_dict *su_cs_dict_create_copy( struct su_cs_dict *self, struct su_cs_dict const *t); /*! Destructor. */ EXPORT void su_cs_dict_gut(struct su_cs_dict *self); /*! Assign \a{t}, and return 0 on success or, depending on the * \r{su_CS_DICT_ERR_PASS} setting, the corresponding \r{su_state_err()}. * \remarks{The element order of \SELF and \a{t} may not be identical.} * \copydoc{su_assign_fun} */ EXPORT s32 su_cs_dict_assign(struct su_cs_dict *self, struct su_cs_dict const *t); /*! Like \r{su_cs_dict_assign()}, but it only assigns the elements of \a{t}, * it does neither assign the toolbox nor any configuration flags. */ EXPORT s32 su_cs_dict_assign_elems(struct su_cs_dict *self, struct su_cs_dict const *t); /*! Remove all elements, and release all memory. */ EXPORT struct su_cs_dict *su_cs_dict_clear(struct su_cs_dict *self); /*! Remove only the elements, keep other allocations. */ EXPORT struct su_cs_dict *su_cs_dict_clear_elems(struct su_cs_dict *self); /*! Swap the fields of \a{self} and \a{t}. */ EXPORT struct su_cs_dict *su_cs_dict_swap(struct su_cs_dict *self, struct su_cs_dict *t); /*! Get the used \r{su_cs_dict_flags}. */ INLINE u16 su_cs_dict_flags(struct su_cs_dict const *self){ ASSERT(self); return self->csd_flags; } /*! Set some \r{su_cs_dict_flags}. */ INLINE struct su_cs_dict *su_cs_dict_add_flags(struct su_cs_dict *self, u16 flags){ ASSERT(self); flags &= su__CS_DICT_CREATE_MASK; self->csd_flags |= flags; return self; } /*! Clear some \r{su_cs_dict_flags}. */ INLINE struct su_cs_dict *su_cs_dict_clear_flags(struct su_cs_dict *self, u16 flags){ ASSERT(self); flags &= su__CS_DICT_CREATE_MASK; self->csd_flags &= ~flags; return self; } /*! Get the used treshold shift. * See \r{su_cs_dict_set_treshold_shift()}. */ INLINE u8 su_cs_dict_treshold_shift(struct su_cs_dict const *self){ ASSERT(self); return self->csd_tshift; } /*! Set the treshold shift. * The treshold shift is used to decide when the internal array is to be grown * according to the algorithm * \cb{count-of-buckets >= array-capacity << treshold-shift} * The value will be \r{su_CLIP()}ped in between 1 and 8; the default is 2. * It does not affect shrinking (controlled via \r{su_CS_DICT_AUTO_SHRINK}). */ INLINE struct su_cs_dict *su_cs_dict_set_treshold_shift( struct su_cs_dict *self, u8 ntshift){ ASSERT(self); self->csd_tshift = CLIP(ntshift, 1, 8); return self; } /*! Get the used \r{su_toolbox}, or \NIL. */ INLINE struct su_toolbox const *su_cs_dict_toolbox( struct su_cs_dict const *self){ ASSERT(self); return self->csd_tbox; } /*! Set the (possibly) used \r{su_toolbox}. * The toolbox is asserted if \r{su_CS_DICT_OWNS} is set. */ INLINE struct su_cs_dict *su_cs_dict_set_toolbox(struct su_cs_dict *self, struct su_toolbox const *tbox_or_nil){ ASSERT(self); ASSERT(!(su_cs_dict_flags(self) & su_CS_DICT_OWNS) || (tbox_or_nil != NIL && tbox_or_nil->tb_clone != NIL && tbox_or_nil->tb_delete != NIL && tbox_or_nil->tb_assign != NIL)); self->csd_tbox = tbox_or_nil; return self; } /*! Current number of key/value element pairs. */ INLINE u32 su_cs_dict_count(struct su_cs_dict const *self){ ASSERT(self); return self->csd_count; } /*! Thaw and balance \a{self}. * Thaw \SELF from (a possible) \r{su_CS_DICT_FROZEN} state, recalculate the * perfect size of the management table for the current number of managed * elements, and rebalance \SELF as necessary. * \remarks{If memory failures occur the balancing is simply not performed.} */ EXPORT struct su_cs_dict *su_cs_dict_balance(struct su_cs_dict *self); /*! Test whether \a{key} is present in \SELF. */ INLINE boole su_cs_dict_has_key(struct su_cs_dict const *self, char const *key){ ASSERT(self); ASSERT_RET(key != NIL, FAL0); return (su__cs_dict_lookup(self, key, NIL) != NIL); } /*! Lookup a value, return it (possibly \NIL) or \NIL. */ INLINE void *su_cs_dict_lookup(struct su_cs_dict *self, char const *key){ struct su__cs_dict_node *csdnp; ASSERT(self != NIL); ASSERT_RET(key != NIL, NIL); csdnp = su__cs_dict_lookup(self, key, NIL); return (csdnp != NIL) ? csdnp->csdn_data : NIL; } /*! Insert a new \a{key} mapping to \a{value}. * Returns 0 upon successful insertion, -1 if \a{key} already exists (use * \r{su_cs_dict_replace()} if you want to insert or update a value), * or a \r{su_err_number} (including, also depending on the setting of * \r{su_CS_DICT_ERR_PASS}, a corresponding \r{su_state_err()}). * If \a{value} is \NIL (after cloning) and \r{su_CS_DICT_OWNS} is set and * \r{su_CS_DICT_NIL_IS_VALID_OBJECT} is not, \ERR{INVAL} is returned. */ INLINE s32 su_cs_dict_insert(struct su_cs_dict *self, char const *key, void *value){ ASSERT(self); ASSERT_RET(key != NIL, 0); return su__cs_dict_insrep(self, key, value, FAL0); } /*! Insert a new, or update an existing \a{key} mapping to \a{value}. * Returns 0 upon successful insertion of a new \a{key}, -1 upon update * of an existing \a{key}, or a \r{su_err_number} (including, also depending on * the setting of \r{su_CS_DICT_ERR_PASS}, a corresponding \r{su_state_err()}). * If \a{value} is \NIL and \r{su_CS_DICT_OWNS} is set and * \r{su_CS_DICT_NIL_IS_VALID_OBJECT} is not, \ERR{INVAL} is returned. * * \remarks{When \SELF owns its values and \r{su_CS_DICT_NILISVALO} is set, * then if updating a non-\NIL value via the \r{su_assign_fun} of the used * \r{su_toolbox} fails, the old object will be deleted, \NIL will be inserted, * and this function fails.} * * Likewise, if \r{su_CS_DICT_NILISVALO} is not set, then if the * \r{su_clone_fun} of the toolbox fails to create a duplicate of \a{value}, * then the old value will remain unchanged and this function fails.} */ INLINE s32 su_cs_dict_replace(struct su_cs_dict *self, char const *key, void *value){ ASSERT(self); ASSERT_RET(key != NIL, 0); return su__cs_dict_insrep(self, key, value, TRU1); } /*! Returns the false boolean if \a{key} cannot be found. */ EXPORT boole su_cs_dict_remove(struct su_cs_dict *self, char const *key); /*! With \r{su_HAVE_DEBUG} and/or \r{su_HAVE_DEVEL} this will * \r{su_log_write()} statistics about \SELF. */ INLINE void su_cs_dict_statistics(struct su_cs_dict const *self){ UNUSED(self); #if DVLOR(1, 0) su__cs_dict_stats(self); #endif } /*! * \defgroup CS_DICT_VIEW View of and for su_cs_dict * \ingroup CS_DICT * \brief View of and for \r{su_cs_dict} (\r{su/cs-dict.h}) * * This implements an associative unidirectional view type. * Whereas it documents C++ interfaces, \r{su/view.h} also applies to C views. */ enum su__cs_dict_view_move_types{ su__CS_DICT_VIEW_MOVE_BEGIN, su__CS_DICT_VIEW_MOVE_HAS_NEXT, su__CS_DICT_VIEW_MOVE_NEXT }; /*! \_ */ struct su_cs_dict_view{ struct su_cs_dict *csdv_parent; /*!< We are \fn{is_setup()} with it. */ struct su__cs_dict_node *csdv_node; u32 csdv_index; /* Those only valid after _move(..HAS_NEXT) */ u32 csdv_next_index; struct su__cs_dict_node *csdv_next_node; }; /* "The const is preserved logically" */ EXPORT struct su_cs_dict_view *su__cs_dict_view_move( struct su_cs_dict_view *self, u8 type); /*! Easy iteration support; \a{SELF} must be \r{su_cs_dict_view_setup()}. */ #define su_CS_DICT_VIEW_FOREACH(SELF) \ for(su_cs_dict_view_begin(SELF); su_cs_dict_view_is_valid(SELF);\ su_cs_dict_view_next(SELF)) /*! Create a tie in between \SELF and its parent collection object. */ INLINE struct su_cs_dict_view *su_cs_dict_view_setup( struct su_cs_dict_view *self, struct su_cs_dict *parent){ ASSERT(self); self->csdv_parent = parent; self->csdv_node = NIL; ASSERT_RET(parent != NIL, self); return self; } /*! \r{su_cs_dict_view_setup()} must have been called before. */ INLINE struct su_cs_dict *su_cs_dict_view_parent( struct su_cs_dict_view const *self){ ASSERT(self); return self->csdv_parent; } /*! \_ */ INLINE boole su_cs_dict_view_is_valid(struct su_cs_dict_view const *self){ ASSERT(self); return (self->csdv_node != NIL); } /*! \_ */ INLINE struct su_cs_dict_view *su_cs_dict_view_invalidate( struct su_cs_dict_view *self){ ASSERT(self); self->csdv_node = NIL; return self; } /*! \_ */ INLINE char const *su_cs_dict_view_key(struct su_cs_dict_view const *self){ ASSERT(self); ASSERT_RET(su_cs_dict_view_is_valid(self), NIL); return self->csdv_node->csdn_key; } /*! \_ */ INLINE u32 su_cs_dict_view_key_len(struct su_cs_dict_view const *self){ ASSERT(self); ASSERT_RET(su_cs_dict_view_is_valid(self), 0); return self->csdv_node->csdn_klen; } /*! \_ */ INLINE uz su_cs_dict_view_key_hash(struct su_cs_dict_view const *self){ ASSERT(self); ASSERT_RET(su_cs_dict_view_is_valid(self), 0); return self->csdv_node->csdn_khash; } /*! \_ */ INLINE void *su_cs_dict_view_data(struct su_cs_dict_view const *self){ ASSERT(self); ASSERT_RET(su_cs_dict_view_is_valid(self), NIL); return self->csdv_node->csdn_data; } /*! Replace the data of a \r{su_cs_dict_view_is_valid()} view. * Behaves like \r{su_cs_dict_replace()}. */ EXPORT s32 su_cs_dict_view_set_data(struct su_cs_dict_view *self, void *value); /*! Move a setup view to the first position, if there is one. * \r{su_cs_dict_view_is_valid()} must be tested thereafter. */ INLINE struct su_cs_dict_view *su_cs_dict_view_begin( struct su_cs_dict_view *self){ ASSERT(self); return su__cs_dict_view_move(self, su__CS_DICT_VIEW_MOVE_BEGIN); } /*! Whether another position follows a \r{su_cs_dict_view_is_valid()} one. * A following \r{su_cs_dict_view_next()} will use informations collected by * this function. */ INLINE boole su_cs_dict_view_has_next(struct su_cs_dict_view const *self){ ASSERT(self); ASSERT_RET(su_cs_dict_view_is_valid(self), FAL0); return (su__cs_dict_view_move(C(struct su_cs_dict_view*,self), su__CS_DICT_VIEW_MOVE_HAS_NEXT)->csdv_next_node != NIL); } /*! Step forward a \r{su_cs_dict_view_is_valid()} view. */ INLINE struct su_cs_dict_view *su_cs_dict_view_next( struct su_cs_dict_view *self){ ASSERT(self); ASSERT_RET(su_cs_dict_view_is_valid(self), self); return su__cs_dict_view_move(self, su__CS_DICT_VIEW_MOVE_NEXT); } /*! Search for \a{key} and return the new \r{su_cs_dict_view_is_valid()}. */ EXPORT boole su_cs_dict_view_find(struct su_cs_dict_view *self, char const *key); /*! Remove the key/value tuple of a \r{su_cs_dict_view_is_valid()} view, * then move to the next valid position, if any. */ EXPORT struct su_cs_dict_view *su_cs_dict_view_remove( struct su_cs_dict_view *self); /*! Test two views for equality. */ INLINE sz su_cs_dict_view_cmp(struct su_cs_dict_view const *self, struct su_cs_dict_view const *t){ ASSERT_RET(self, -(t != NIL)); ASSERT_RET(t, 1); return (self->csdv_node == t->csdv_node); } /*! @} */ /*! @} */ C_DECL_END #include #if !su_C_LANG || defined CXX_DOXYGEN # include # define su_CXX_HEADER # include NSPC_BEGIN(su) template class cs_dict; /*! * \ingroup CS_DICT * C++ variant of \r{CS_DICT} (\r{su/cs-dict.h}) */ template::ownguess> class cs_dict : private su_cs_dict{ class gview; class gview : private su_cs_dict_view{ public: // xxx clang 5.0.1 BUG: needed this-> to find superclass field gview(void) {this->csdv_parent = NIL; this->csdv_node = NIL;} gview(gview const &t) {*this = t;} ~gview(void) {} gview &operator=(gview const &t){ SELFTHIS_RET(*S(su_cs_dict_view*,this) = *S(su_cs_dict_view const*,&t)); } gview &setup(su_cs_dict &parent){ SELFTHIS_RET(su_cs_dict_view_setup(this, &parent)); } boole is_setup(void) const {return su_cs_dict_view_parent(this) != NIL;} boole is_same_parent(gview const &t) const{ return su_cs_dict_view_parent(this) == su_cs_dict_view_parent(&t); } boole is_valid(void) const {return su_cs_dict_view_is_valid(this);} gview &invalidate(void){ SELFTHIS_RET(su_cs_dict_view_invalidate(this)); } char const *key(void) const {return su_cs_dict_view_key(this);} void *data(void) {return su_cs_dict_view_data(this);} void const *data(void) const {return su_cs_dict_view_data(this);} s32 set_data(void *value) {return su_cs_dict_view_set_data(this, value);} gview &begin(void) {SELFTHIS_RET(su_cs_dict_view_begin(this));} boole has_next(void) const {return su_cs_dict_view_has_next(this);} gview &next(void) {SELFTHIS_RET(su_cs_dict_view_next(this));} boole find(void const *key){ return su_cs_dict_view_find(this, S(char const*,key)); } gview &remove(void) {SELFTHIS_RET(su_cs_dict_view_remove(this));} sz cmp(gview const &t) const {return su_cs_dict_view_cmp(this, &t);} }; public: /*! \copydoc{su_cs_dict_flags} */ enum flags{ f_none, /*!< This is 0. */ /*! \copydoc{su_CS_DICT_POW2_SPACED} */ f_pow2_spaced = su_CS_DICT_POW2_SPACED, /*! \copydoc{su_CS_DICT_CASE} */ f_case = su_CS_DICT_CASE, /*! \copydoc{su_CS_DICT_HEAD_RESORT} */ f_head_resort = su_CS_DICT_HEAD_RESORT, /*! \copydoc{su_CS_DICT_AUTO_SHRINK} */ f_auto_shrink = su_CS_DICT_AUTO_SHRINK, /*! \copydoc{su_CS_DICT_FROZEN} */ f_frozen = su_CS_DICT_FROZEN, /*! \copydoc{su_CS_DICT_ERR_PASS} */ f_err_pass = su_CS_DICT_ERR_PASS, /*! \copydoc{su_CS_DICT_NIL_IS_VALID_OBJECT} */ f_nil_is_valid_object = su_CS_DICT_NIL_IS_VALID_OBJECT, /*! Alias for \r{f_nil_is_valid_object}. */ f_nilisvalo = su_CS_DICT_NILISVALO }; /*! \_ */ typedef NSPC(su)type_traits type_traits; /*! \_ */ typedef typename type_traits::type_toolbox type_toolbox; /*! \_ */ typedef typename type_traits::auto_type_toolbox auto_type_toolbox; /*! \_ */ typedef typename type_traits::tp tp; /*! \_ */ typedef typename type_traits::tp_const tp_const; /*! \_ */ typedef NSPC(su)view_traits view_traits; friend class NSPC(su)view_assoc_unidir; friend class NSPC(su)view_assoc_unidir_const; /*! \r{CS_DICT_VIEW} (\r{su/cs-dict.h}) */ typedef NSPC(su)view_assoc_unidir view; /*! \r{CS_DICT_VIEW} (\r{su/cs-dict.h}) */ typedef NSPC(su)view_assoc_unidir_const view_const; /*! \copydoc{su_cs_dict_create()} */ cs_dict(type_toolbox const *ttbox=NIL, u16 flags=f_none){ ASSERT(!OWNS || (ttbox != NIL && ttbox->ttb_clone != NIL && ttbox->ttb_delete != NIL && ttbox->ttb_assign != NIL)); flags &= su__CS_DICT_CREATE_MASK & ~su_CS_DICT_OWNS; if(OWNS) flags |= su_CS_DICT_OWNS; su_cs_dict_create(this, flags, R(su_toolbox const*,ttbox)); } /*! \copydoc{su_cs_dict_create_copy()} */ SHADOW cs_dict(cs_dict const &t) {su_cs_dict_create_copy(this, &t);} /*! \copydoc{su_cs_dict_gut()} */ ~cs_dict(void) {su_cs_dict_gut(this);} /*! \copydoc{su_cs_dict_assign()} */ s32 assign(cs_dict const &t) {return su_cs_dict_assign(this, &t);} /*! \r{assign()} */ SHADOW cs_dict &operator=(cs_dict const &t) {SELFTHIS_RET(assign(t));} /*! \copydoc{su_cs_dict_assign_elems()} */ s32 assign_elems(cs_dict const &t){ return su_cs_dict_assign_elems(this, &t); } /*! \copydoc{su_cs_dict_clear()} */ cs_dict &clear(void) {SELFTHIS_RET(su_cs_dict_clear(this));} /*! \copydoc{su_cs_dict_clear_elems()} */ cs_dict &clear_elems(void) {SELFTHIS_RET(su_cs_dict_clear_elems(this));} /*! \copydoc{su_cs_dict_swap()} */ cs_dict &swap(cs_dict &t) {SELFTHIS_RET(su_cs_dict_swap(this, &t));} /*! \copydoc{su_cs_dict_flags()} */ u16 flags(void) const {return (su_cs_dict_flags(this) & ~su_CS_DICT_OWNS);} /*! \copydoc{su_cs_dict_add_flags()} */ cs_dict &add_flags(u16 flags){ SELFTHIS_RET(su_cs_dict_add_flags(this, flags & ~su_CS_DICT_OWNS)); } /*! \copydoc{su_cs_dict_clear_flags()} */ cs_dict &clear_flags(u16 flags){ SELFTHIS_RET(su_cs_dict_clear_flags(this, flags & ~su_CS_DICT_OWNS)); } /*! \copydoc{su_cs_dict_treshold_shift()} */ u8 treshold_shift(void) const {return su_cs_dict_treshold_shift(this);} /*! \copydoc{su_cs_dict_set_treshold_shift()} */ cs_dict &set_treshold_shift(u8 tshift){ SELFTHIS_RET(su_cs_dict_set_treshold_shift(this, tshift)); } /*! \copydoc{su_cs_dict_toolbox()} */ type_toolbox const *toolbox(void) const{ return R(type_toolbox const*,su_cs_dict_toolbox(this)); } /*! \copydoc{su_cs_dict_set_toolbox()} */ cs_dict &set_toolbox(type_toolbox const *tbox_or_nil){ SELFTHIS_RET(su_cs_dict_set_toolbox(this, R(su_toolbox const*,tbox_or_nil))); } /*! \copydoc{su_cs_dict_count()} */ u32 count(void) const {return csd_count;} /*! Whether \r{count()} is 0. */ boole is_empty(void) const {return (count() == 0);} /*! \copydoc{su_cs_dict_balance()} */ cs_dict &balance(void) {SELFTHIS_RET(su_cs_dict_balance(this));} /*! \copydoc{su_cs_dict_has_key()} */ boole has_key(char const *key) const{ ASSERT_RET(key != NIL, FAL0); return su_cs_dict_has_key(this, key); } /*! \copydoc{su_cs_dict_lookup()} */ tp lookup(char const *key){ ASSERT_RET(key != NIL, NIL); return type_traits::to_tp(su_cs_dict_lookup(this, key)); } /*! \r{lookup()} */ tp operator[](char const *key) {return lookup(key);} /*! \r{lookup()} */ tp_const lookup(char const *key) const{ ASSERT_RET(key != NIL, NIL); return type_traits::to_const_tp(su_cs_dict_lookup(C(su_cs_dict*,this), key)); } /*! \r{lookup()} */ tp_const operator[](char const *key) const {return lookup(key);} /*! \copydoc{su_cs_dict_insert()} */ s32 insert(char const *key, tp_const value){ ASSERT_RET(key != NIL, 0); return su_cs_dict_insert(this, key, type_traits::to_vp(value)); } /*! \copydoc{su_cs_dict_replace()} */ s32 replace(char const *key, tp_const value){ ASSERT_RET(key != NIL, 0); return su_cs_dict_replace(this, key, type_traits::to_vp(value)); } /*! \copydoc{su_cs_dict_remove()} */ boole remove(char const *key){ ASSERT_RET(key != NIL, FAL0); return su_cs_dict_remove(this, key); } /*! \copydoc{su_cs_dict_statistics()} */ void statistics(void) const {su_cs_dict_statistics(this);} }; NSPC_END(su) # include #endif /* !C_LANG || CXX_DOXYGEN */ #endif /* su_CS_DICT_H */ /* s-it-mode */ s-nail-14.9.15/include/su/cs.h000066400000000000000000000367251352610246600157300ustar00rootroot00000000000000/*@ Anything (locale agnostic: ASCII only) around char and char*. * * Copyright (c) 2001 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef su_CS_H #define su_CS_H /*! * \file * \ingroup CS * \brief \r{CS} tools and heap */ #include #define su_HEADER #include C_DECL_BEGIN /*! * \defgroup CS Byte character data * \ingroup TEXT * \brief Byte character data, locale agnostic: ASCII only (\r{su/cs.h}) * * Oh, the vivid part this is! * @{ */ /*! \_ */ enum su_cs_ctype{ su_CS_CTYPE_NONE, /*!< \_ */ su_CS_CTYPE_ALNUM = 1u<<0, /*!< \_ */ su_CS_CTYPE_ALPHA = 1u<<1, /*!< \_ */ su_CS_CTYPE_BLANK = 1u<<2, /*!< \_ */ su_CS_CTYPE_CNTRL = 1u<<3, /*!< \_ */ su_CS_CTYPE_DIGIT = 1u<<4, /*!< \_ */ su_CS_CTYPE_GRAPH = 1u<<5, /*!< \_ */ su_CS_CTYPE_LOWER = 1u<<6, /*!< \_ */ su_CS_CTYPE_PRINT = 1u<<7, /*!< \_ */ su_CS_CTYPE_PUNCT = 1u<<8, /*!< \_ */ su_CS_CTYPE_SPACE = 1u<<9, /*!< \_ */ su_CS_CTYPE_UPPER = 1u<<10, /*!< \_ */ su_CS_CTYPE_WHITE = 1u<<11, /*!< SPACE, HT or LF */ su_CS_CTYPE_XDIGIT = 1u<<12, /*!< \_ */ su__CS_CTYPE_MAXSHIFT = 13u, su__CS_CTYPE_MASK = (1u< #if !su_C_LANG || defined CXX_DOXYGEN # define su_A_T_T_DECL_ONLY # include # define su_CXX_HEADER # include NSPC_BEGIN(su) class cs; /*! * \ingroup CS * C++ variant of \r{CS} (\r{su/cs.h}) */ class EXPORT cs{ public: /*! \copydoc{su_cs_ctype} */ enum ctype{ /*! \copydoc{su_CS_CTYPE_NONE} */ ctype_none = su_CS_CTYPE_NONE, /*! \copydoc{su_CS_CTYPE_ALNUM} */ ctype_alnum = su_CS_CTYPE_ALNUM, /*! \copydoc{su_CS_CTYPE_ALPHA} */ ctype_alpha = su_CS_CTYPE_ALPHA, /*! \copydoc{su_CS_CTYPE_BLANK} */ ctype_blank = su_CS_CTYPE_BLANK, /*! \copydoc{su_CS_CTYPE_CNTRL} */ ctype_cntrl = su_CS_CTYPE_CNTRL, /*! \copydoc{su_CS_CTYPE_DIGIT} */ ctype_digit = su_CS_CTYPE_DIGIT, /*! \copydoc{su_CS_CTYPE_GRAPH} */ ctype_graph = su_CS_CTYPE_GRAPH, /*! \copydoc{su_CS_CTYPE_LOWER} */ ctype_lower = su_CS_CTYPE_LOWER, /*! \copydoc{su_CS_CTYPE_PRINT} */ ctype_print = su_CS_CTYPE_PRINT, /*! \copydoc{su_CS_CTYPE_PUNCT} */ ctype_punct = su_CS_CTYPE_PUNCT, /*! \copydoc{su_CS_CTYPE_SPACE} */ ctype_space = su_CS_CTYPE_SPACE, /*! \copydoc{su_CS_CTYPE_UPPER} */ ctype_upper = su_CS_CTYPE_UPPER, /*! \copydoc{su_CS_CTYPE_WHITE} */ ctype_white = su_CS_CTYPE_WHITE, /*! \copydoc{su_CS_CTYPE_XDIGIT} */ ctype_xdigit = su_CS_CTYPE_XDIGIT }; /*! \copydoc{su_cs_toolbox} */ static NSPC(su)type_toolbox const * const type_toolbox; /*! \copydoc{su_cs_toolbox} */ static NSPC(su)type_toolbox const * const const_type_toolbox; /*! \copydoc{su_cs_toolbox_case} */ static NSPC(su)type_toolbox const * const type_toolbox_case; /*! \copydoc{su_cs_toolbox_case} */ static NSPC(su)type_toolbox const * const const_type_toolbox_case; /*! \copydoc{su_cs_is_ascii()} */ static boole is_ascii(s32 x) {return su_cs_is_ascii(x);} /*! \copydoc{su_cs_is_alnum()} */ static boole is_alnum(s32 x) {return su_cs_is_alnum(x);} /*! \copydoc{su_cs_is_alpha()} */ static boole is_alpha(s32 x) {return su_cs_is_alpha(x);} /*! \copydoc{su_cs_is_blank()} */ static boole is_blank(s32 x) {return su_cs_is_blank(x);} /*! \copydoc{su_cs_is_cntrl()} */ static boole is_cntrl(s32 x) {return su_cs_is_cntrl(x);} /*! \copydoc{su_cs_is_digit()} */ static boole is_digit(s32 x) {return su_cs_is_digit(x);} /*! \copydoc{su_cs_is_graph()} */ static boole is_graph(s32 x) {return su_cs_is_graph(x);} /*! \copydoc{su_cs_is_lower()} */ static boole is_lower(s32 x) {return su_cs_is_lower(x);} /*! \copydoc{su_cs_is_print()} */ static boole is_print(s32 x) {return su_cs_is_print(x);} /*! \copydoc{su_cs_is_punct()} */ static boole is_punct(s32 x) {return su_cs_is_punct(x);} /*! \copydoc{su_cs_is_space()} */ static boole is_space(s32 x) {return su_cs_is_space(x);} /*! \copydoc{su_cs_is_upper()} */ static boole is_upper(s32 x) {return su_cs_is_upper(x);} /*! \copydoc{su_cs_is_white()} */ static boole is_white(s32 x) {return su_cs_is_white(x);} /*! \copydoc{su_cs_is_xdigit()} */ static boole is_xdigit(s32 x) {return su_cs_is_xdigit(x);} /*! \copydoc{su_cs_is_ctype()} */ static boole is_ctype(s32 x, u32 ct) {return su_cs_is_ctype(x, ct);} /*! \copydoc{su_cs_cmp()} */ static sz cmp(char const *cp1, char const *cp2){ return su_cs_cmp(cp1, cp2); } /*! \copydoc{su_cs_cmp_n()} */ static sz cmp(char const *cp1, char const *cp2, uz n){ return su_cs_cmp_n(cp1, cp2, n); } /*! \copydoc{su_cs_cmp_case()} */ static sz cmp_case(char const *cp1, char const *cp2){ return su_cs_cmp_case(cp1, cp2); } /*! \copydoc{su_cs_cmp_case_n()} */ static sz cmp_case(char const *cp1, char const *cp2, uz n){ return su_cs_cmp_case_n(cp1, cp2, n); } /*! \copydoc{su_cs_copy_n()} */ static char *copy(char *dst, char const *src, uz n){ return su_cs_copy_n(dst, src, n); } /*! \copydoc{su_cs_dup_cbuf()} */ static char *dup(char const *buf, uz len, u32 estate=state::none){ return su_cs_dup_cbuf(buf, len, estate); } /*! \copydoc{su_cs_dup()} */ static char *dup(char const *cp, u32 estate=state::none){ return su_cs_dup(cp, estate); } /*! \copydoc{su_cs_find()} */ static char *find(char const *cp, char const *x) {return su_cs_find(cp, x);} /*! \copydoc{su_cs_find_c()} */ static char *find(char const *cp, char x) {return su_cs_find_c(cp, x);} /*! \copydoc{su_cs_hash_cbuf()} */ static uz hash(char const *buf, uz len) {return su_cs_hash_cbuf(buf, len);} /*! \copydoc{su_cs_hash()} */ static uz hash(char const *cp) {return su_cs_hash(cp);} /*! \copydoc{su_cs_hash_case_cbuf()} */ static uz hash_case(char const *buf, uz len){ return su_cs_hash_case_cbuf(buf, len); } /*! \copydoc{su_cs_hash_case()} */ static uz hash_case(char const *cp) {return su_cs_hash_case(cp);} /*! \copydoc{su_cs_len()} */ static uz len(char const *cp) {return su_cs_len(cp);} /*! \copydoc{su_cs_pcopy()} */ static char *pcopy(char *dst, char const *src){ return su_cs_pcopy(dst, src); } /*! \copydoc{su_cs_pcopy_n()} */ static char *pcopy(char *dst, char const *src, uz n){ return su_cs_pcopy_n(dst, src, n); } /*! \copydoc{su_cs_rfind_c()} */ static char *rfind(char const *cp, char x) {return su_cs_rfind_c(cp, x);} /*! \copydoc{su_cs_sep_c()} */ static char *sep(char **iolist, char sep, boole ignore_empty){ return su_cs_sep_c(iolist, sep, ignore_empty); } /*! \copydoc{su_cs_sep_escable_c()} */ static char *sep_escable(char **iolist, char sep, boole ignore_empty){ return su_cs_sep_escable_c(iolist, sep, ignore_empty); } /*! \copydoc{su_cs_starts_with()} */ static boole starts_with(char const *cp, char const *x){ return su_cs_starts_with(cp, x); } /*! \copydoc{su_cs_to_lower()} */ static s32 to_lower(s32 c) {return su_cs_to_lower(c);} /*! \copydoc{su_cs_to_upper()} */ static s32 to_upper(s32 c) {return su_cs_to_upper(c);} }; /*! * \ingroup CS * \r{auto_type_toolbox} specialization (also \r{cs::toolbox}; \r{su/cs.h}) */ template<> class auto_type_toolbox{ public: /*! \_ */ static type_toolbox const *get_instance(void){ return cs::type_toolbox; } }; /*! * \ingroup CS * \r{auto_type_toolbox} specialization (also \r{cs::toolbox}; \r{su/cs.h}) */ template<> class auto_type_toolbox{ public: /*! \_ */ static type_toolbox const *get_instance(void){ return cs::const_type_toolbox; } }; NSPC_END(su) # include #endif /* !C_LANG || CXX_DOXYGEN */ #endif /* su_CS_H */ /* s-it-mode */ s-nail-14.9.15/include/su/icodec.h000066400000000000000000000426171352610246600165460ustar00rootroot00000000000000/*@ ATOI and ITOA: simple non-restartable integer conversion. * * Copyright (c) 2017 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef su_ICODEC_H #define su_ICODEC_H /*! * \file * \ingroup ICODEC * \brief \r{ICODEC} */ #include #define su_HEADER #include C_DECL_BEGIN /*! * \defgroup ICODEC Integer/String conversion * \ingroup TEXT * \brief Convert integers from and to strings (\r{su/icodec.h}) * @{ */ /*! * \defgroup IDEC Integers from Strings * \ingroup ICODEC * \brief Parsing integers out of string buffers (\r{su/icodec.h}) * @{ */ /*! \_ */ enum su_idec_mode{ su_IDEC_MODE_NONE, /*!< \_ */ /*! Will be used to choose limits, error constants, etc. */ su_IDEC_MODE_SIGNED_TYPE = 1u<<0, /*! If a power-of-two is used explicitly, or if a \a{base} of 0 is used * and a known standard prefix is seen, enforce interpretation as unsigned. * This only makes a difference in conjunction with * \r{su_IDEC_MODE_SIGNED_TYPE}. */ su_IDEC_MODE_POW2BASE_UNSIGNED = 1u<<1, /*! Relaxed \a{base} 0 convenience: if the input used \c{BASE#number} number * sign syntax, then the scan will be restarted anew with the base given. * Like this an UI can be permissive and support \c{s=' -008'; eval 10#$s} * out of the box (it would require a lot of logic otherwise). */ su_IDEC_MODE_BASE0_NUMBER_SIGN_RESCAN = 1u<<2, #if 0 su_IDEC_MODE_SIGN_FORCE_SIGNED_TYPE = 1u<<2, #endif /*! Assume input is an 8-bit integer (limits, saturation, etc.). */ su_IDEC_MODE_LIMIT_8BIT = 1u<<3, /*! Assume input is an 16-bit integer (limits, saturation, etc.). */ su_IDEC_MODE_LIMIT_16BIT = 2u<<3, /*! Assume input is an 32-bit integer (limits, saturation, etc.). */ su_IDEC_MODE_LIMIT_32BIT = 3u<<3, su__IDEC_MODE_LIMIT_MASK = 3u<<3, /*! Do not treat it as an error if the limit is excessed! * Like this saturated (and bit-limited) results can be created * (in that \r{su_IDEC_STATE_EOVERFLOW is suppressed). */ su_IDEC_MODE_LIMIT_NOERROR = 1u<<5, /* These bits are duplicated in the _state result bits! */ su__IDEC_MODE_MASK = (1u<<6) - 1 }; /*! \_ */ enum su_idec_state{ su_IDEC_STATE_NONE, /*!< \_*/ /*! Malformed input, no usable result has been stored. */ su_IDEC_STATE_EINVAL = 1u<<8, /*! Bad character according to base, but we have seen some good ones * before, otherwise \r{su_IDEC_STATE_EINVAL} would have been used. */ su_IDEC_STATE_EBASE = 2u<<8, su_IDEC_STATE_EOVERFLOW = 3u<<8, /*!< Result too large. */ su_IDEC_STATE_EMASK = 3u<<8, /*!< Any errors, that is. */ su_IDEC_STATE_SEEN_MINUS = 1u<<16, /*!< Seen hyphen-minus in the input? */ su_IDEC_STATE_CONSUMED = 1u<<17, /*!< All the input has been consumed. */ su__IDEC_PRIVATE_SHIFT1 = 24u }; MCTA(su__IDEC_MODE_MASK <= (1u<<8) - 1, "Shared bit range overlaps") /*! Decode \a{clen} (or \r{su_cs_len()} if \r{su_UZ_MAX}) bytes of \a{cbuf} * into an integer according to the \r{su_idec_mode} \a{idec_mode}, * store a/the result in \a{*resp} (in the \r{su_IDEC_STATE_EINVAL} case an * overflow constant is used, for signed types it depends on parse state * whether MIN/MAX are used), which must point to storage of the correct type, * return the resulting \r{su_idec_state} (which includes \a{idec_mode}). * If \a{endptr_or_nil} is will be pointed to the last parsed byte. * Base auto-detection can be enfored by setting \a{base} to 0. */ EXPORT u32 su_idec(void *resp, char const *cbuf, uz clen, u8 base, u32 idec_mode, char const **endptr_or_nil); /*! \_ */ INLINE u32 su_idec_cp(void *resp, char const *cp, u8 base, u32 idec_mode, char const **endptr_or_nil){ uz len = UZ_MAX; ASSERT_EXEC(cp != NIL, len = 0); return su_idec(resp, cp, len, base, idec_mode, endptr_or_nil); } /*! \_ */ #define su_idec_u8(RP,CBP,CL,B,CLP) \ su_idec(RP, CBP, CL, B, (su_IDEC_MODE_LIMIT_8BIT), CLP) /*! \_ */ #define su_idec_u8_cp(RP,CBP,B,CLP) su_idec_u8(RP,CBP,su_UZ_MAX,B,CLP) /*! \_ */ #define su_idec_s8(RP,CBP,CL,B,CLP) \ su_idec(RP, CBP, CL, B,\ (su_IDEC_MODE_SIGNED_TYPE | su_IDEC_MODE_LIMIT_8BIT), CLP) /*! \_ */ #define su_idec_s8_cp(RP,CBP,B,CLP) su_idec_s8(RP,CBP,su_UZ_MAX,B,CLP) /*! \_ */ #define su_idec_u16(RP,CBP,CL,B,CLP) \ su_idec(RP, CBP, CL, B, (su_IDEC_MODE_LIMIT_16BIT), CLP) /*! \_ */ #define su_idec_u16_cp(RP,CBP,B,CLP) su_idec_u16(RP,CBP,su_UZ_MAX,B,CLP) /*! \_ */ #define su_idec_s16(RP,CBP,CL,B,CLP) \ su_idec(RP, CBP, CL, B,\ (su_IDEC_MODE_SIGNED_TYPE | su_IDEC_MODE_LIMIT_16BIT), CLP) /*! \_ */ #define su_idec_s16_cp(RP,CBP,B,CLP) su_idec_s16(RP,CBP,su_UZ_MAX,B,CLP) /*! \_ */ #define su_idec_u32(RP,CBP,CL,B,CLP) \ su_idec(RP, CBP, CL, B, (su_IDEC_MODE_LIMIT_32BIT), CLP) /*! \_ */ #define su_idec_u32_cp(RP,CBP,B,CLP) su_idec_u32(RP,CBP,su_UZ_MAX,B,CLP) /*! \_ */ #define su_idec_s32(RP,CBP,CL,B,CLP) \ su_idec(RP, CBP, CL, B,\ (su_IDEC_MODE_SIGNED_TYPE | su_IDEC_MODE_LIMIT_32BIT), CLP) /*! \_ */ #define su_idec_s32_cp(RP,CBP,B,CLP) su_idec_s32(RP,CBP,su_UZ_MAX,B,CLP) /*! \_ */ #define su_idec_u64(RP,CBP,CL,B,CLP) su_idec(RP, CBP, CL, B, 0, CLP) /*! \_ */ #define su_idec_u64_cp(RP,CBP,B,CLP) su_idec_u64(RP,CBP,su_UZ_MAX,B,CLP) /*! \_ */ #define su_idec_s64(RP,CBP,CL,B,CLP) \ su_idec(RP, CBP, CL, B, (su_IDEC_MODE_SIGNED_TYPE), CLP) /*! \_ */ #define su_idec_s64_cp(RP,CBP,B,CLP) su_idec_s64(RP,CBP,su_UZ_MAX,B,CLP) #if UZ_BITS == 32 /*! \_ */ # define su_idec_uz(RP,CBP,CL,B,CLP) \ su_idec(RP, CBP, CL, B, (su_IDEC_MODE_LIMIT_32BIT), CLP) /*! \_ */ # define su_idec_sz(RP,CBP,CL,B,CLP) \ su_idec(RP, CBP, CL, B,\ (su_IDEC_MODE_SIGNED_TYPE | su_IDEC_MODE_LIMIT_32BIT), CLP) #else # define su_idec_uz(RP,CBP,CL,B,CLP) su_idec(RP, CBP, CL, B, 0, CLP) # define su_idec_sz(RP,CBP,CL,B,CLP) \ su_idec(RP, CBP, CL, B, (su_IDEC_MODE_SIGNED_TYPE), CLP) #endif /*! \_ */ #define su_idec_uz_cp(RP,CBP,B,CLP) su_idec_uz(RP,CBP,su_UZ_MAX,B,CLP) /*! \_ */ #define su_idec_sz_cp(RP,CBP,B,CLP) su_idec_sz(RP,CBP,su_UZ_MAX,B,CLP) #if UZ_BITS == 32 /*! \_ */ # define su_idec_up(RP,CBP,CL,B,CLP) \ su_idec(RP, CBP, CL, B, (su_IDEC_MODE_LIMIT_32BIT), CLP) /*! \_ */ # define su_idec_sp(RP,CBP,CL,B,CLP) \ su_idec(RP, CBP, CL, B,\ (su_IDEC_MODE_SIGNED_TYPE | su_IDEC_MODE_LIMIT_32BIT), CLP) #else # define su_idec_up(RP,CBP,CL,B,CLP) su_idec(RP, CBP, CL, B, 0, CLP) # define su_idec_sp(RP,CBP,CL,B,CLP) \ su_idec(RP, CBP, CL, B, (su_IDEC_MODE_SIGNED_TYPE), CLP) #endif /*! \_ */ #define su_idec_up_cp(RP,CBP,B,CLP) su_idec_up(RP,CBP,su_UZ_MAX,B,CLP) /*! \_ */ #define su_idec_sp_cp(RP,CBP,B,CLP) su_idec_sp(RP,CBP,su_UZ_MAX,B,CLP) /*! @} */ /*! * \defgroup IENC Integers to Strings * \ingroup ICODEC * \brief Creating textual integer representations (\r{su/icodec.h}) * * \remarks{The support macros simply cast the type to the given type, * therefore care for correct signedness extension etc. has to be taken.} * @{ */ enum{ /*! Maximum buffer size needed by \r{su_ienc()}, * including \c{NUL} and base prefixes. */ su_IENC_BUFFER_SIZE = 80u }; /*! \_ */ enum su_ienc_mode{ su_IENC_MODE_NONE, /*!< \_ */ /*! Whether signedness correction shall be applied. */ su_IENC_MODE_SIGNED_TYPE = 1u<<1, /*! Positive nubers shall have a plus-sign \c{+} prefix. */ su_IENC_MODE_SIGNED_PLUS = 1u<<2, /*! Positive nubers shall have a space prefix. * Has a lower priority than \r{su_IENC_MODE_SIGNED_PLUS}. */ su_IENC_MODE_SIGNED_SPACE = 1u<<3, /*! No base prefixes shall prepend the number, even if the conversion base * would normally place one. */ su_IENC_MODE_NO_PREFIX = 1u<<4, /*! For bases greater ten (10), use lowercase letters instead of the default * uppercase. * This does not cover the base. */ su_IENC_MODE_LOWERCASE = 1u<<5, su__IENC_MODE_SHIFT = 6u, su__IENC_MODE_MASK = (1u< #if !su_C_LANG || defined CXX_DOXYGEN # define su_CXX_HEADER # include NSPC_BEGIN(su) class idec; class ienc; /*! * \ingroup IDEC * C++ variant of \r{IDEC} (\r{su/icodec.h}) */ class idec{ public: /*! \copydoc{su_idec_mode} */ enum mode{ /*! \copydoc{su_IDEC_MODE_NONE} */ mode_none = su_IDEC_MODE_NONE, /*! \copydoc{su_IDEC_MODE_SIGNED_TYPE} */ mode_signed_type = su_IDEC_MODE_SIGNED_TYPE, /*! \copydoc{su_IDEC_MODE_POW2BASE_UNSIGNED} */ mode_pow2base_unsigned = su_IDEC_MODE_POW2BASE_UNSIGNED, /*! \copydoc{su_IDEC_MODE_BASE0_NUMBER_SIGN_RESCAN} */ mode_base0_number_sign_rescan = su_IDEC_MODE_BASE0_NUMBER_SIGN_RESCAN, /*! \copydoc{su_IDEC_MODE_LIMIT_8BIT} */ mode_limit_8bit = su_IDEC_MODE_LIMIT_8BIT, /*! \copydoc{su_IDEC_MODE_LIMIT_16BIT} */ mode_limit_16bit = su_IDEC_MODE_LIMIT_16BIT, /*! \copydoc{su_IDEC_MODE_LIMIT_32BIT} */ mode_limit_32bit = su_IDEC_MODE_LIMIT_32BIT, /*! \copydoc{su_IDEC_MODE_LIMIT_NOERROR} */ mode_limit_noerror = su_IDEC_MODE_LIMIT_NOERROR }; /*! \copydoc{su_idec_state} */ enum state{ /*! \copydoc{su_IDEC_STATE_NONE} */ state_none = su_IDEC_STATE_NONE, /*! \copydoc{su_IDEC_STATE_EINVAL} */ state_einval = su_IDEC_STATE_EINVAL, /*! \copydoc{su_IDEC_STATE_EBASE} */ state_ebase = su_IDEC_STATE_EBASE, /*! \copydoc{su_IDEC_STATE_EOVERFLOW} */ state_eoverflow = su_IDEC_STATE_EOVERFLOW, /*! \copydoc{su_IDEC_STATE_EMASK} */ state_emask = su_IDEC_STATE_EMASK, /*! \copydoc{su_IDEC_STATE_SEEN_MINUS} */ state_seen_minus = su_IDEC_STATE_SEEN_MINUS, /*! \copydoc{su_IDEC_STATE_CONSUMED} */ state_consumed = su_IDEC_STATE_CONSUMED }; /*! \copydoc{su_idec()} */ static u32 convert(void *resp, char const *cbuf, uz clen, u8 base, u32 mode, char const **endptr_or_nil=NIL){ return su_idec(resp, cbuf, clen, base, mode, endptr_or_nil); } /*! \r{su_idec()} */ static u32 convert(void *resp, char const *cbuf, u8 base, u32 mode, char const **endptr_or_nil=NIL){ return su_idec_cp(resp, cbuf, base, mode, endptr_or_nil); } /*! \r{su_idec()} */ static u32 convert(u8 &resr, char const *cbuf, uz clen, u8 base, char const **endptr_or_nil=NIL){ return su_idec_u8(&resr, cbuf, clen, base, endptr_or_nil); } /*! \r{su_idec()} */ static u32 convert(u8 &resr, char const *cbuf, u8 base, char const **endptr_or_nil=NIL){ return su_idec_u8_cp(&resr, cbuf, base, endptr_or_nil); } /*! \r{su_idec()} */ static u32 convert(s8 &resr, char const *cbuf, uz clen, u8 base, char const **endptr_or_nil=NIL){ return su_idec_s8(&resr, cbuf, clen, base, endptr_or_nil); } /*! \r{su_idec()} */ static u32 convert(s8 &resr, char const *cbuf, u8 base, char const **endptr_or_nil=NIL){ return su_idec_s8_cp(&resr, cbuf, base, endptr_or_nil); } /*! \r{su_idec()} */ static u32 convert(u16 &resr, char const *cbuf, uz clen, u8 base, char const **endptr_or_nil=NIL){ return su_idec_u16(&resr, cbuf, clen, base, endptr_or_nil); } /*! \r{su_idec()} */ static u32 convert(u16 &resr, char const *cbuf, u8 base, char const **endptr_or_nil=NIL){ return su_idec_u16_cp(&resr, cbuf, base, endptr_or_nil); } /*! \r{su_idec()} */ static u32 convert(s16 &resr, char const *cbuf, uz clen, u8 base, char const **endptr_or_nil=NIL){ return su_idec_s16(&resr, cbuf, clen, base, endptr_or_nil); } /*! \r{su_idec()} */ static u32 convert(s16 &resr, char const *cbuf, u8 base, char const **endptr_or_nil=NIL){ return su_idec_s16_cp(&resr, cbuf, base, endptr_or_nil); } /*! \r{su_idec()} */ static u32 convert(u32 &resr, char const *cbuf, uz clen, u8 base, char const **endptr_or_nil=NIL){ return su_idec_u32(&resr, cbuf, clen, base, endptr_or_nil); } /*! \r{su_idec()} */ static u32 convert(u32 &resr, char const *cbuf, u8 base, char const **endptr_or_nil=NIL){ return su_idec_u32_cp(&resr, cbuf, base, endptr_or_nil); } /*! \r{su_idec()} */ static u32 convert(s32 &resr, char const *cbuf, uz clen, u8 base, char const **endptr_or_nil=NIL){ return su_idec_s32(&resr, cbuf, clen, base, endptr_or_nil); } /*! \r{su_idec()} */ static u32 convert(s32 &resr, char const *cbuf, u8 base, char const **endptr_or_nil=NIL){ return su_idec_s32_cp(&resr, cbuf, base, endptr_or_nil); } /*! \r{su_idec()} */ static u32 convert(u64 &resr, char const *cbuf, uz clen, u8 base, char const **endptr_or_nil=NIL){ return su_idec_u64(&resr, cbuf, clen, base, endptr_or_nil); } /*! \r{su_idec()} */ static u32 convert(u64 &resr, char const *cbuf, u8 base, char const **endptr_or_nil=NIL){ return su_idec_u64_cp(&resr, cbuf, base, endptr_or_nil); } /*! \r{su_idec()} */ static u32 convert(s64 &resr, char const *cbuf, uz clen, u8 base, char const **endptr_or_nil=NIL){ return su_idec_s64(&resr, cbuf, clen, base, endptr_or_nil); } /*! \r{su_idec()} */ static u32 convert(s64 &resr, char const *cbuf, u8 base, char const **endptr_or_nil=NIL){ return su_idec_s64_cp(&resr, cbuf, base, endptr_or_nil); } }; /*! * \ingroup IENC * C++ variant of \r{IENC} (\r{su/icodec.h}) */ class ienc{ public: enum{ /*! \copydoc{su_IENC_BUFFER_SIZE} */ buffer_size = su_IENC_BUFFER_SIZE }; /*! \copydoc{su_ienc_mode} */ enum mode{ /*! \copydoc{su_IENC_MODE_NONE} */ mode_none = su_IENC_MODE_NONE, /*! \copydoc{su_IENC_MODE_SIGNED_TYPE} */ mode_signed_type = su_IENC_MODE_SIGNED_TYPE, /*! \copydoc{su_IENC_MODE_SIGNED_PLUS} */ mode_signed_plus = su_IENC_MODE_SIGNED_PLUS, /*! \copydoc{su_IENC_MODE_SIGNED_SPACE} */ mode_signed_space = su_IENC_MODE_SIGNED_SPACE, /*! \copydoc{su_IENC_MODE_NO_PREFIX} */ mode_no_prefix = su_IENC_MODE_NO_PREFIX, /*! \copydoc{su_IENC_MODE_LOWERCASE} */ mode_lowercase = su_IENC_MODE_LOWERCASE }; /*! \copydoc{su_ienc()} */ static char *convert(char *cbuf, u64 value, u8 base=10, u32 mode=mode_none){ return su_ienc(cbuf, value, base, mode); } /*! \r{su_ienc()} */ static char *convert(char *cbuf, s64 value, u8 base=10, u32 mode=mode_none){ return su_ienc(cbuf, value, base, mode); } /*! \r{su_ienc()} */ static char *convert(char *cbuf, u32 value, u8 base=10, u32 mode=mode_none){ return su_ienc(cbuf, value, base, mode); } /*! \r{su_ienc()} */ static char *convert(char *cbuf, s32 value, u8 base=10, u32 mode=mode_none){ return su_ienc(cbuf, value, base, mode); } /*! \r{su_ienc()} */ static char *convert(char *cbuf, u16 value, u8 base=10, u32 mode=mode_none){ return su_ienc(cbuf, value, base, mode); } /*! \r{su_ienc()} */ static char *convert(char *cbuf, s16 value, u8 base=10, u32 mode=mode_none){ return su_ienc(cbuf, value, base, mode); } /*! \r{su_ienc()} */ static char *convert(char *cbuf, u8 value, u8 base=10, u32 mode=mode_none){ return su_ienc(cbuf, value, base, mode); } /*! \r{su_ienc()} */ static char *convert(char *cbuf, s8 value, u8 base=10, u32 mode=mode_none){ return su_ienc(cbuf, value, base, mode); } }; NSPC_END(su) # include #endif /* !C_LANG || CXX_DOXYGEN */ #endif /* su_ICODEC_H */ /* s-it-mode */ s-nail-14.9.15/include/su/mem-bag.h000066400000000000000000000535021352610246600166200ustar00rootroot00000000000000/*@ Mem bag objects to throw in and possibly forget about allocations. *@ Depends on su_HAVE_MEM_BAG_{AUTO,LOFI}. TODO FLUX *@ The allocation interface is macro-based for the sake of debugging. * * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef su_MEM_BAG_H #define su_MEM_BAG_H /*! * \file * \ingroup MEM * \brief \r{MEM_BAG}: objects to pool allocations */ #include #if defined su_HAVE_MEM_BAG_AUTO || defined su_HAVE_MEM_BAG_LOFI /*! * \ingroup MEM_BAG * Defined if memory bags are available. * They are if just any memory allocation type is supported. */ # define su_HAVE_MEM_BAG #endif #ifdef su_HAVE_MEM_BAG #define su_HEADER #include C_DECL_BEGIN struct su_mem_bag; /*! * \defgroup MEM_BAG Memory bags * \ingroup MEM * \brief \r{MEM} objects to pool allocations (\r{su/mem-bag.h}) * * Memory bags introduce possibilities to bundle heap memory with a job, * and to throw away the bag(s) as such after the job is done. * They are a more generalized successor of the string allocator that * Kurt Shoens developed for BSD Mail in the 70s. * As such, neither constructors nor destructors are supported. * * Dependent upon compile-time options bags offer the auto-reclaimed string * memory allocator (with \r{su_HAVE_MEM_BAG_AUTO}), * and/or a replacement for \c{alloca(3)} (\r{su_HAVE_MEM_BAG_LOFI}: a stack of * last-out, first-in memory storage). * In general the memory served is \r{su_Z_ALIGN_PZ()} aligned. * Allocation requests larger than about \r{su_S32_MAX} result in * \r{su_STATE_ERR_OVERFLOW} errors. * * The bag(s) form(s) a stack which can be \r{su_mem_bag_push()}ed and * \r{su_mem_bag_pop()}ped. * One only works with the outermost object, it will internally choose the * \r{su_mem_bag_top()} automatically. * * By defining \c{su_MEM_BAG_SELF} a more convenient preprocessor based * interface is available, just as for the \r{MEM_CACHE_ALLOC} interface. * This is not furtherly documented, though. * * If any of \r{su_HAVE_DEBUG} or \r{su_HAVE_MEM_CANARIES_DISABLE} is defined * then these objects only manage the chunks, the user chunk memory itself * will be served by the normal \r{MEM_CACHE_ALLOC} allocator instead, to * allow neatless integration within address sanitizers etc. * @{ */ #ifdef su_USECASE_MX # define su_MEM_BAG_SELF (n_go_data->gdc_membag) #endif /*! Mirrors a subset of the \r{su_mem_alloc_flags}. *//* Equality CTAsserted */ enum su_mem_bag_alloc_flags{ su_MEM_BAG_ALLOC_NONE, /*!< \_ */ su_MEM_BAG_ALLOC_CLEAR = 1u<<1, /*!< Zero memory. */ /*! An alias (i.e., same value) for \r{su_STATE_ERR_OVERFLOW}. */ su_MEM_BAG_ALLOC_OVERFLOW_OK = su_STATE_ERR_OVERFLOW, /*! An alias (i.e., same value) for \r{su_STATE_ERR_NOMEM}. */ su_MEM_BAG_ALLOC_NOMEM_OK = su_STATE_ERR_NOMEM, /*! An alias (i.e., same value) for \r{su_STATE_ERR_PASS}. */ su_MEM_BAG_ALLOC_MAYFAIL = su_STATE_ERR_PASS, /*! An alias (i.e., same value) for \r{su_STATE_ERR_NOPASS}. */ su_MEM_BAG_ALLOC_MUSTFAIL = su_STATE_ERR_NOPASS, su__MEM_BAG_ALLOC_USER_MASK = 0xFF | su_STATE_ERR_MASK }; /*! \_ */ struct su_mem_bag{ struct su_mem_bag *mb_top; /* Stacktop (outermost object only) */ struct su_mem_bag *mb_outer; /* Outer object in stack */ struct su_mem_bag *mb_outer_save; u32 mb_bsz; /* Pool size available to users.. */ u32 mb_bsz_wo_gap; /* ..less some GAP */ #ifdef su_HAVE_MEM_BAG_AUTO sz mb_auto_relax_recur; struct su__mem_bag_auto_buf *mb_auto_top; struct su__mem_bag_auto_buf *mb_auto_full; struct su__mem_bag_auto_huge *mb_auto_huge; #endif #ifdef su_HAVE_MEM_BAG_LOFI struct su__mem_bag_lofi_pool *mb_lofi_pool; struct su__mem_bag_lofi_chunk *mb_lofi_top; #endif }; /*! \a{bsz} is a buffer size hint used to space memory chunk pool buffers, * which thus also defines the maximum size of chunks which are served (less * some internal management overhead). * If \a{bsz} is 0 then two pages (\r{su_PAGE_SIZE}) are used (to accommodate * the use case of page allocations), otherwise it is cramped to some internal * limits (currently 1 KB / 10 MB). * * Memory bags can serve chunks larger than what pools can handle, but these * need special treatment, and thus counteract the idea of a pool: their * occurrence is logged with \r{su_HAVE_DEBUG}. */ EXPORT struct su_mem_bag *su_mem_bag_create(struct su_mem_bag *self, uz bsz); /*! If \SELF owns a stack of bags as created via \r{su_mem_bag_push()}, all * entries of the stack will be forcefully popped and destructed; * presence of a stack is a \r{su_HAVE_DEBUG} log condition. */ EXPORT void su_mem_bag_gut(struct su_mem_bag *self); /*! Fixate the current snapshot of auto-reclaimed and flux storage of \SELF: * earlier allocations will be persistent, only later ones will be covered by * \r{su_mem_bag_reset()}. * \remarks{Only to be called once per object.} * \remarks{Only applies to \SELF or its current \r{su_mem_bag_top()}, if there * is one, does not propagate through the stack.} */ EXPORT struct su_mem_bag *su_mem_bag_fixate(struct su_mem_bag *self); /*! To be called from the (main)loops upon tick and break-off time to perform * debug checking and memory cleanup. * If \SELF owns a stack of \r{su_mem_bag_push()}ed objects, these will be * forcefully destructed. * The cleanup will release all LOFI memory, drop all the relaxation created by * \r{su_mem_bag_auto_relax_create()} and all auto-reclaimed and flux storage * that is not covered by r{su_mem_bag_fixate()}. * \remarks{Possible \r{su_HAVE_DEBUG} logs via \r{su_LOG_DEBUG}.} */ EXPORT struct su_mem_bag *su_mem_bag_reset(struct su_mem_bag *self); /*! Push the initialized bag \a{that_one} onto the bag stack layer of \SELF. * \a{that_one} will be used to serve memory until \r{su_mem_bag_pop()} is * called, which implicitly happens during \r{su_mem_bag_reset()}. * It is possible to push and thus pop a bag twice: this is sometimes * handy to store memory persistantly in some outer stack level. */ EXPORT struct su_mem_bag *su_mem_bag_push(struct su_mem_bag *self, struct su_mem_bag *that_one); /*! Pop the \r{su_mem_bag_push()}ed bag \a{that_one} off the stack. * If \a{that_one} is not top of the stack, all bags from top down to * \a{that_one} will be popped in one go. * Popping a stack does not reset its allocations. */ EXPORT struct su_mem_bag *su_mem_bag_pop(struct su_mem_bag *self, struct su_mem_bag *that_one); /*! Get the bag that currently serves on the stack top. * Returns \SELF if there is no stack. */ INLINE struct su_mem_bag * su_mem_bag_top(struct su_mem_bag *self){ ASSERT_RET(self != NIL, NIL); ASSERT_RET(self->mb_outer == NIL, su_mem_bag_top(self->mb_outer)); return (self->mb_top != NIL) ? self->mb_top : self; } /* * Allocation interface: auto */ #ifdef su_HAVE_MEM_BAG_AUTO /*! Lower memory pressure on auto-reclaimed storage for code which has * a sinus-curve looking style of memory usage, i.e., peek followed by * release, like, e.g., doing a task on all messages of a mailbox in order. * Such code should call \c{relax_create()}, successively call * \c{relax_unroll()} after a single job has been handled, concluded with * a final \c{relax_gut()}. * \remarks{Only applies to \SELF or its current \r{su_mem_bag_top()}, if there * is one, does not propagate through the stack.} */ EXPORT struct su_mem_bag *su_mem_bag_auto_relax_create( struct su_mem_bag *self); /*! See \r{su_mem_bag_auto_relax_create()}. */ EXPORT struct su_mem_bag *su_mem_bag_auto_relax_gut(struct su_mem_bag *self); /*! See \r{su_mem_bag_auto_relax_create()}. */ EXPORT struct su_mem_bag *su_mem_bag_auto_relax_unroll( struct su_mem_bag *self); /*! This is rather internal, but due to the \r{su_mem_bag_alloc_flags} * \a{mbaf} maybe handy sometimes. * Normally to be used through the macro interface. * Attempts to allocate \r{su_S32_MAX} or more bytes result in overflow errors, * see \r{su_MEM_BAG_ALLOC_OVERFLOW_OK} and \r{su_MEM_BAG_ALLOC_NOMEM_OK}. */ EXPORT void *su_mem_bag_auto_allocate(struct su_mem_bag *self, uz size, uz no, u32 mbaf su_DBG_LOC_ARGS_DECL); /*! \_ */ # define su_MEM_BAG_AUTO_ALLOCATE(BAGP,SZ,NO,F) \ su_mem_bag_auto_allocate(BAGP, SZ, NO, F su_DBG_LOC_ARGS_INJ) # ifdef su_HAVE_DBG_LOC_ARGS # define su_MEM_BAG_AUTO_ALLOCATE_LOC(BAGP,SZ,NO,F,FNAME,LNNO) \ su_mem_bag_auto_allocate(BAGP, SZ, NO, F, FNAME, LNNO) # else /*! \_ */ # define su_MEM_BAG_AUTO_ALLOCATE_LOC(BAGP,SZ,NO,F,FNAME,LNNO) \ su_mem_bag_auto_allocate(BAGP, SZ, NO, F) # endif /* The "normal" interface, slim, but su_USECASE_ specific: use _ALLOCATE_ for * other use cases. These set MUSTFAIL and always return a valid pointer. */ # ifdef su_MEM_BAG_SELF # define su_MEM_BAG_SELF_AUTO_ALLOC(SZ) \ su_MEM_BAG_AUTO_ALLOCATE(su_MEM_BAG_SELF, SZ, 1,\ su_MEM_BAG_ALLOC_MUSTFAIL) # define su_MEM_BAG_SELF_AUTO_ALLOC_LOC(SZ,FNAME,LNNO) \ su_MEM_BAG_AUTO_ALLOCATE_LOC(su_MEM_BAG_SELF, SZ, 1,\ su_MEM_BAG_ALLOC_MUSTFAIL, FNAME, LNNO) # define su_MEM_BAG_SELF_AUTO_ALLOC_N(SZ,NO) \ su_MEM_BAG_AUTO_ALLOCATE(su_MEM_BAG_SELF, SZ, NO,\ su_MEM_BAG_ALLOC_MUSTFAIL) # define su_MEM_BAG_SELF_AUTO_ALLOC_N_LOC(SZ,NO,FNAME,LNNO) \ su_MEM_BAG_AUTO_ALLOCATE_LOC(su_MEM_BAG_SELF, SZ, NO,\ su_MEM_BAG_ALLOC_MUSTFAIL, FNAME, LNNO) # define su_MEM_BAG_SELF_AUTO_CALLOC(SZ) \ su_MEM_BAG_AUTO_ALLOCATE(su_MEM_BAG_SELF, SZ, 1,\ su_MEM_BAG_ALLOC_CLEAR | su_MEM_BAG_ALLOC_MUSTFAIL) # define su_MEM_BAG_SELF_AUTO_CALLOC_LOC(SZ,FNAME,LNNO) \ su_MEM_BAG_AUTO_ALLOCATE_LOC(su_MEM_BAG_SELF, SZ, 1,\ su_MEM_BAG_ALLOC_CLEAR | su_MEM_BAG_ALLOC_MUSTFAIL, FNAME, LNNO) # define su_MEM_BAG_SELF_AUTO_CALLOC_N(SZ,NO) \ su_MEM_BAG_AUTO_ALLOCATE(su_MEM_BAG_SELF, SZ, NO,\ su_MEM_BAG_ALLOC_CLEAR | su_MEM_BAG_ALLOC_MUSTFAIL) # define su_MEM_BAG_SELF_AUTO_CALLOC_N_LOC(SZ,NO,FNAME,LNNO) \ su_MEM_BAG_AUTO_ALLOCATE_LOC(su_MEM_BAG_SELF, SZ, NO,\ su_MEM_BAG_ALLOC_CLEAR | su_MEM_BAG_ALLOC_MUSTFAIL, FNAME, LNNO) # define su_MEM_BAG_SELF_AUTO_TALLOC(T,NO) \ su_S(T *,su_MEM_BAG_SELF_AUTO_ALLOC_N(sizeof(T), su_S(su_uz,NO))) # define su_MEM_BAG_SELF_AUTO_TALLOC_LOC(T,NO,FNAME,LNNO) \ su_S(T *,su_MEM_BAG_SELF_AUTO_ALLOC_N_LOC(sizeof(T), su_S(su_uz,NO),\ FNAME, LNNO)) # define su_MEM_BAG_SELF_AUTO_TCALLOC(T,NO) \ su_S(T *,su_MEM_BAG_SELF_AUTO_CALLOC_N(sizeof(T), su_S(su_uz,NO)) # define su_MEM_BAG_SELF_AUTO_TCALLOC_LOC(T,NO,FNAME,LNNO) \ su_S(T *,su_MEM_BAG_SELF_AUTO_CALLOC_N_LOC(sizeof(T), su_S(su_uz,NO),\ FNAME, LNNO)) /* (The painful _LOCOR series) */ # ifdef su_HAVE_DBG_LOC_ARGS # define su_MEM_BAG_SELF_AUTO_ALLOC_LOCOR(SZ,ORARGS) \ su_MEM_BAG_SELF_AUTO_ALLOC_LOC(SZ, ORARGS) # define su_MEM_BAG_SELF_AUTO_ALLOC_N_LOCOR(SZ,NO,ORARGS) \ su_MEM_BAG_SELF_AUTO_ALLOC_N_LOC(SZ, NO, ORARGS) # define su_MEM_BAG_SELF_AUTO_CALLOC_LOCOR(SZ,ORARGS) \ su_MEM_BAG_SELF_AUTO_CALLOC_LOC(SZ, ORGARGS) # define su_MEM_BAG_SELF_AUTO_CALLOC_N_LOCOR(SZ,NO,ORARGS) \ su_MEM_BAG_SELF_AUTO_CALLOC_N_LOC(SZ, NO, ORARGS) # define su_MEM_BAG_SELF_AUTO_TALLOC_LOCOR(T,NO,ORARGS) \ su_MEM_BAG_SELF_AUTO_TALLOC_LOC(T, NO, ORARGS) # define su_MEM_BAG_SELF_AUTO_TCALLOC_LOCOR(T,NO,ORARGS) \ su_MEM_BAG_SELF_AUTO_TCALLOC_LOC(T, NO, ORARGS) # else # define su_MEM_BAG_SELF_AUTO_ALLOC_LOCOR(SZ,ORARGS) \ su_MEM_BAG_SELF_AUTO_ALLOC(SZ) # define su_MEM_BAG_SELF_AUTO_ALLOC_N_LOCOR(SZ,NO,ORARGS) \ su_MEM_BAG_SELF_AUTO_ALLOC_N(SZ, NO) # define su_MEM_BAG_SELF_AUTO_CALLOC_LOCOR(SZ,ORARGS) \ su_MEM_BAG_SELF_AUTO_CALLOC(SZ) # define su_MEM_BAG_SELF_AUTO_CALLOC_N_LOCOR(SZ,NO,ORARGS) \ su_MEM_BAG_SELF_AUTO_CALLOC_N(SZ, NO) # define su_MEM_BAG_SELF_AUTO_TALLOC_LOCOR(T,NO,ORARGS) \ su_MEM_BAG_SELF_AUTO_TALLOC(T, NO) # define su_MEM_BAG_SELF_AUTO_TCALLOC_LOCOR(T,NO,ORARGS) \ su_MEM_BAG_SELF_AUTO_TCALLOC(T, NO) # endif /* !su_HAVE_DBG_LOC_ARGS */ # endif /* su_MEM_BAG_SELF */ #endif /* su_HAVE_MEM_BAG_AUTO */ /* * Allocation interface: lofi */ #ifdef su_HAVE_MEM_BAG_LOFI /*! The snapshot can be used in a local context: if taken, many allocations * can be freed in one go by calling \c{lofi_snap_unroll()}. * \remarks{Only applies to \SELF or its current \r{su_mem_bag_top()}, if there * is one, does not propagate through the stack.} */ EXPORT void *su_mem_bag_lofi_snap_create(struct su_mem_bag *self); /*! Unroll a taken LOFI snapshot by freeing all its allocations. * The \a{cookie} is no longer valid after this operation. * This can only be called on the stack level where the snap has been taken. */ EXPORT struct su_mem_bag *su_mem_bag_lofi_snap_unroll(struct su_mem_bag *self, void *cookie); /*! This is rather internal, but due to the \r{su_mem_bag_alloc_flags} * \a{mbaf} maybe handy sometimes. * Normally to be used through the macro interface. * Attempts to allocate \r{su_S32_MAX} or more bytes result in overflow errors, * see \r{su_MEM_BAG_ALLOC_OVERFLOW_OK} and \r{su_MEM_BAG_ALLOC_NOMEM_OK}. */ EXPORT void *su_mem_bag_lofi_allocate(struct su_mem_bag *self, uz size, uz no, u32 mbaf su_DBG_LOC_ARGS_DECL); /*! Free \a{ovp}; \r{su_HAVE_DEBUG} will log if it is not stack top. */ EXPORT struct su_mem_bag *su_mem_bag_lofi_free(struct su_mem_bag *self, void *ovp su_DBG_LOC_ARGS_DECL); /*! \_ */ # define su_MEM_BAG_LOFI_ALLOCATE(BAGP,SZ,NO,F) \ su_mem_bag_lofi_allocate(BAGP, SZ, NO, F su_DBG_LOC_ARGS_INJ) # ifdef su_HAVE_DBG_LOC_ARGS # define su_MEM_BAG_LOFI_ALLOCATE_LOC(BAGP,SZ,NO,F,FNAME,LNNO) \ su_mem_bag_lofi_allocate(BAGP, SZ, NO, F, FNAME, LNNO) # else /*! \_ */ # define su_MEM_BAG_LOFI_ALLOCATE_LOC(BAGP,SZ,NO,F,FNAME,LNNO) \ su_mem_bag_lofi_allocate(BAGP, SZ, NO, F) # endif /*! \_ */ # define su_MEM_BAG_LOFI_FREE(BAGP,OVP) \ su_mem_bag_lofi_free(BAGP, OVP su_DBG_LOC_ARGS_INJ) # ifdef su_HAVE_DBG_LOC_ARGS # define su_MEM_BAG_LOFI_FREE_LOC(BAGP,OVP,FNAME,LNNO) \ su_mem_bag_lofi_free(BAGP, OVP, FNAME, LNNO) # else /*! \_ */ # define su_MEM_BAG_LOFI_FREE_LOC(BAGP,OVP,FNAME,LNNO) \ su_mem_bag_lofi_free(BAGP, OVP) # endif /* The "normal" interface, slim, but su_USECASE_ specific: use _ALLOCATE_ for * other use cases. These set MUSTFAIL and always return a valid pointer. */ # ifdef su_MEM_BAG_SELF # define su_MEM_BAG_SELF_LOFI_ALLOC(SZ) \ su_MEM_BAG_LOFI_ALLOCATE(su_MEM_BAG_SELF, SZ, 1,\ su_MEM_BAG_ALLOC_MUSTFAIL) # define su_MEM_BAG_SELF_LOFI_ALLOC_LOC(SZ,FNAME,LNNO) \ su_MEM_BAG_LOFI_ALLOCATE_LOC(su_MEM_BAG_SELF, SZ, 1,\ su_MEM_BAG_ALLOC_MUSTFAIL, FNAME, LNNO) # define su_MEM_BAG_SELF_LOFI_ALLOC_N(SZ,NO) \ su_MEM_BAG_LOFI_ALLOCATE(su_MEM_BAG_SELF, SZ, NO,\ su_MEM_BAG_ALLOC_MUSTFAIL) # define su_MEM_BAG_SELF_LOFI_ALLOC_N_LOC(SZ,NO,FNAME,LNNO) \ su_MEM_BAG_LOFI_ALLOCATE_LOC(su_MEM_BAG_SELF, SZ, NO,\ su_MEM_BAG_ALLOC_MUSTFAIL, FNAME, LNNO) # define su_MEM_BAG_SELF_LOFI_CALLOC(SZ) \ su_MEM_BAG_LOFI_ALLOCATE(su_MEM_BAG_SELF, SZ, 1,\ su_MEM_BAG_ALLOC_CLEAR | su_MEM_BAG_ALLOC_MUSTFAIL) # define su_MEM_BAG_SELF_LOFI_CALLOC_LOC(SZ,FNAME,LNNO) \ su_MEM_BAG_LOFI_SELF_ALLOCATE_LOC(su_MEM_BAG_SELF, SZ, 1,\ su_MEM_BAG_ALLOC_CLEAR | su_MEM_BAG_ALLOC_MUSTFAIL, FNAME, LNNO) # define su_MEM_BAG_SELF_LOFI_CALLOC_N(SZ,NO) \ su_MEM_BAG_LOFI_ALLOCATE(su_MEM_BAG_SELF, SZ, NO,\ su_MEM_BAG_ALLOC_CLEAR | su_MEM_BAG_ALLOC_MUSTFAIL) # define su_MEM_BAG_SELF_LOFI_CALLOC_N_LOC(SZ,NO,FNAME,LNNO) \ su_MEM_BAG_LOFI_ALLOCATE_LOC(su_MEM_BAG_SELF, SZ, NO,\ su_MEM_BAG_ALLOC_CLEAR | su_MEM_BAG_ALLOC_MUSTFAIL, FNAME, LNNO) # define su_MEM_BAG_SELF_LOFI_TALLOC(T,NO) \ su_S(T *,su_MEM_BAG_SELF_LOFI_ALLOC_N(sizeof(T), su_S(su_uz,NO))) # define su_MEM_BAG_SELF_LOFI_TALLOC_LOC(T,NO,FNAME,LNNO) \ su_S(T *,su_MEM_BAG_SELF_LOFI_ALLOC_N_LOC(sizeof(T), su_S(su_uz,NO),\ FNAME, LNNO)) # define su_MEM_BAG_SELF_LOFI_TCALLOC(T,NO) \ su_S(T *,su_MEM_BAG_SELF_LOFI_CALLOC_N(sizeof(T), su_S(su_uz,NO)) # define su_MEM_BAG_SELF_LOFI_TCALLOC_LOC(T,NO,FNAME,LNNO) \ su_S(T *,su_MEM_BAG_SELF_LOFI_CALLOC_N_LOC(sizeof(T), su_S(su_uz,NO),\ FNAME, LNNO)) # define su_MEM_BAG_SELF_LOFI_FREE(OVP) \ su_MEM_BAG_LOFI_FREE(su_MEM_BAG_SELF, OVP) # define su_MEM_BAG_SELF_LOFI_FREE_LOC(OVP,FNAME,LNNO) \ su_MEM_BAG_LOFI_FREE_LOC(su_MEM_BAG_SELF, OVP, FNAME, LNNO) /* (The painful _LOCOR series) */ # ifdef su_HAVE_DBG_LOC_ARGS # define su_MEM_BAG_SELF_LOFI_ALLOC_LOCOR(SZ,ORARGS) \ su_MEM_BAG_SELF_LOFI_ALLOC_LOC(SZ, ORARGS) # define su_MEM_BAG_SELF_LOFI_ALLOC_N_LOCOR(SZ,NO,ORARGS) \ su_MEM_BAG_SELF_LOFI_ALLOC_N_LOC(SZ, NO, ORARGS) # define su_MEM_BAG_SELF_LOFI_CALLOC_LOCOR(SZ,ORARGS) \ su_MEM_BAG_SELF_LOFI_CALLOC_LOC(SZ, ORGARGS) # define su_MEM_BAG_SELF_LOFI_CALLOC_N_LOCOR(SZ,NO,ORARGS) \ su_MEM_BAG_SELF_LOFI_CALLOC_N_LOC(SZ, NO, ORARGS) # define su_MEM_BAG_SELF_LOFI_TALLOC_LOCOR(T,NO,ORARGS) \ su_MEM_BAG_SELF_LOFI_TALLOC_LOC(T, NO, ORARGS) # define su_MEM_BAG_SELF_LOFI_TCALLOC_LOCOR(T,NO,ORARGS) \ su_MEM_BAG_SELF_LOFI_TCALLOC_LOC(T, NO, ORARGS) # define su_MEM_BAG_SELF_LOFI_FREE_LOCOR(OVP,ORARGS) \ su_MEM_BAG_SELF_LOFI_FREE_LOC(OVP, ORARGS) # else # define su_MEM_BAG_SELF_LOFI_ALLOC_LOCOR(SZ,ORARGS) \ su_MEM_BAG_SELF_LOFI_ALLOC(SZ) # define su_MEM_BAG_SELF_LOFI_ALLOC_N_LOCOR(SZ,NO,ORARGS) \ su_MEM_BAG_SELF_LOFI_ALLOC_N(SZ, NO) # define su_MEM_BAG_SELF_LOFI_CALLOC_LOCOR(SZ,ORARGS) \ su_MEM_BAG_SELF_LOFI_CALLOC(SZ) # define su_MEM_BAG_SELF_LOFI_CALLOC_N_LOCOR(SZ,NO,ORARGS) \ su_MEM_BAG_SELF_LOFI_CALLOC_N(SZ, NO) # define su_MEM_BAG_SELF_LOFI_TALLOC_LOCOR(T,NO,ORARGS) \ su_MEM_BAG_SELF_LOFI_TALLOC(T, NO) # define su_MEM_BAG_SELF_LOFI_TCALLOC_LOCOR(T,NO,ORARGS) \ su_MEM_BAG_SELF_LOFI_TCALLOC(T, NO) # define su_MEM_BAG_SELF_LOFI_FREE_LOCOR(OVP,ORARGS) \ su_MEM_BAG_SELF_LOFI_FREE_LOC(OVP, ORARGS) # endif /* !su_HAVE_DBG_LOC_ARGS */ # endif /* su_MEM_BAG_SELF */ #endif /* su_HAVE_MEM_BAG_LOFI */ /*! @} */ C_DECL_END #include #if !su_C_LANG || defined CXX_DOXYGEN # define su_CXX_HEADER # include NSPC_BEGIN(su) class mem_bag; /*! * \ingroup MEM_BAG * C++ variant of \r{MEM_BAG} (\r{su/mem-bag.h}) */ class EXPORT mem_bag : private su_mem_bag{ su_CLASS_NO_COPY(mem_bag) public: /*! \copydoc{su_mem_bag_alloc_flags} */ enum alloc_flags{ /*! \copydoc{su_MEM_BAG_ALLOC_NONE} */ alloc_none = su_MEM_BAG_ALLOC_NONE, /*! \copydoc{su_MEM_BAG_ALLOC_CLEAR} */ alloc_clear = su_MEM_BAG_ALLOC_CLEAR, /*! \copydoc{su_MEM_BAG_ALLOC_OVERFLOW_OK} */ alloc_overflow_ok = su_MEM_BAG_ALLOC_OVERFLOW_OK, /*! \copydoc{su_MEM_BAG_ALLOC_NOMEM_OK} */ alloc_nomem_ok = su_MEM_BAG_ALLOC_NOMEM_OK, /*! \copydoc{su_MEM_BAG_ALLOC_MAYFAIL} */ alloc_mayfail = su_MEM_BAG_ALLOC_MAYFAIL, /*! \copydoc{su_MEM_BAG_ALLOC_MUSTFAIL} */ alloc_mustfail = su_MEM_BAG_ALLOC_MUSTFAIL }; /*! \copydoc{su_mem_bag_create()} */ mem_bag(uz bsz=0) {su_mem_bag_create(this, bsz);} /*! \copydoc{su_mem_bag_gut()} */ ~mem_bag(void) {su_mem_bag_gut(this);} /*! \copydoc{su_mem_bag_fixate()} */ mem_bag &fixate(void) {SELFTHIS_RET(su_mem_bag_fixate(this));} /*! \copydoc{su_mem_bag_reset()} */ mem_bag &reset(void) {SELFTHIS_RET(su_mem_bag_reset(this));} /*! \copydoc{su_mem_bag_push()} */ mem_bag &push(mem_bag &that_one){ SELFTHIS_RET(su_mem_bag_push(this, &that_one)); } /*! \copydoc{su_mem_bag_pop()} */ mem_bag &pop(mem_bag &that_one){ SELFTHIS_RET(su_mem_bag_pop(this, &that_one)); } /*! \copydoc{su_mem_bag_top()} */ mem_bag &top(void){ return (mb_top != NIL) ? *S(mem_bag*,mb_top) : *this; } #ifdef su_HAVE_MEM_BAG_AUTO /*! \copydoc{su_mem_bag_auto_relax_create()} */ mem_bag &auto_relax_create(void){ SELFTHIS_RET(su_mem_bag_auto_relax_create(this)); } /*! \copydoc{su_mem_bag_auto_relax_gut()} */ mem_bag &auto_relax_gut(void){ SELFTHIS_RET(su_mem_bag_auto_relax_gut(this)); } /*! \copydoc{su_mem_bag_auto_relax_unroll()} */ mem_bag &auto_relax_unroll(void){ SELFTHIS_RET(su_mem_bag_auto_relax_unroll(this)); } /*! \copydoc{su_mem_bag_auto_allocate()} */ void *auto_allocate(uz size, uz no=1, u32 af=alloc_none){ return su_mem_bag_auto_allocate(this, size, no, af su_DBG_LOC_ARGS_INJ); } #endif /* su_HAVE_MEM_BAG_AUTO */ #ifdef su_HAVE_MEM_BAG_LOFI /*! \copydoc{su_mem_bag_lofi_snap_create()} */ void *lofi_snap_create(void) {return su_mem_bag_lofi_snap_create(this);} /*! \copydoc{su_mem_bag_lofi_snap_unroll()} */ mem_bag &lofi_snap_unroll(void *cookie){ SELFTHIS_RET(su_mem_bag_lofi_snap_unroll(this, cookie)); } /*! \copydoc{su_mem_bag_lofi_allocate()} */ void *lofi_allocate(uz size, uz no=1, u32 af=alloc_none){ return su_mem_bag_lofi_allocate(this, size, no, af su_DBG_LOC_ARGS_INJ); } /*! \copydoc{su_mem_bag_lofi_free()} */ mem_bag &lofi_free(void *ovp){ SELFTHIS_RET(su_mem_bag_lofi_free(this, ovp su_DBG_LOC_ARGS_INJ)); } #endif /* su_HAVE_MEM_BAG_LOFI */ }; NSPC_END(su) # include #endif /* !C_LANG || CXX_DOXYGEN */ #endif /* su_HAVE_MEM_BAG */ #endif /* !su_MEM_BAG_H */ /* s-it-mode */ s-nail-14.9.15/include/su/mem.h000066400000000000000000000526711352610246600160770ustar00rootroot00000000000000/*@ Memory: tools like copy, move etc., and a heap allocation interface. * * Copyright (c) 2001 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef su_MEM_H #define su_MEM_H /*! * \file * \ingroup MEM * \brief \r{MEM} tools and heap */ #include #define su_HEADER #include C_DECL_BEGIN /*! * \defgroup MEM_TOOLS Memory tools * \ingroup MEM * \brief \r{MEM} tools like copy (\r{su/mem.h}) * * In general argument pointers may be given as \NIL if a length argument * which covers them is given as 0. * @{ */ /* A memset that is not optimized away */ EXPORT_DATA void * (* volatile su_mem_set_volatile)(void*, int, uz); /*! \_ */ EXPORT sz su_mem_cmp(void const *vpa, void const *vpb, uz len); /*! \_ */ EXPORT void *su_mem_copy(void *vp, void const *src, uz len); /*! \_ */ EXPORT void *su_mem_find(void const *vp, s32 what, uz len); /*! \_ */ EXPORT void *su_mem_rfind(void const *vp, s32 what, uz len); /*! \_ */ EXPORT void *su_mem_move(void *vp, void const *src, uz len); /*! \_ */ EXPORT void *su_mem_set(void *vp, s32 what, uz len); /*! @} */ /*! * \defgroup MEM_CACHE_ALLOC Heap memory * \ingroup MEM * \brief Allocating heap memory (\r{su/mem.h}) * * It interacts with \r{su_STATE_ERR_NOMEM} and \r{su_STATE_ERR_OVERFLOW}, * but also allows per-call failure ignorance. * * The \r{su_MEM_ALLOC_DEBUG}-enabled cache surrounds served chunks with * canaries which will detect write bound violations. * Also, the enum \r{su_mem_alloc_flags} allows flagging allocation requests * with three (or four when including "0") different flag marks; * these will show up in error messages, like allocation check and cache dump * output, which are also available, then. * Finally served memory chunks will be initialized with a (non-0) pattern * when debugging is enabled. * @{ */ #if (defined su_HAVE_DEBUG && !defined su_HAVE_MEM_CANARIES_DISABLE) ||\ defined DOXYGEN /*! Whether the cache is debug-enabled. * It is if \r{su_HAVE_DEBUG} is, and \r{su_HAVE_MEM_CANARIES_DISABLE} * is not defined. */ # define su_MEM_ALLOC_DEBUG #endif /*! \_ */ enum su_mem_alloc_flags{ su_MEM_ALLOC_NONE, /*!< \_ */ /*! Zero memory. * \remarks{TODO: until the C++ memory cache is ported this flag will not be * honoured by reallocation requests.} */ su_MEM_ALLOC_CLEAR = 1u<<1, /*! Perform overflow checks against 32-bit, not \r{su_UZ_MAX}. */ su_MEM_ALLOC_32BIT_OVERFLOW = 1u<<2, /*! Perform overflow checks against 31-bit, not \r{su_UZ_MAX}. */ su_MEM_ALLOC_31BIT_OVERFLOW = 1u<<3, /*! An alias (i.e., same value) for \r{su_STATE_ERR_OVERFLOW}. */ su_MEM_ALLOC_OVERFLOW_OK = su_STATE_ERR_OVERFLOW, /*! An alias (i.e., same value) for \r{su_STATE_ERR_NOMEM}. */ su_MEM_ALLOC_NOMEM_OK = su_STATE_ERR_NOMEM, /*! An alias (i.e., same value) for \r{su_STATE_ERR_PASS}. */ su_MEM_ALLOC_MAYFAIL = su_STATE_ERR_PASS, /*! An alias (i.e., same value) for \r{su_STATE_ERR_NOPASS}. */ su_MEM_ALLOC_MUSTFAIL = su_STATE_ERR_NOPASS, su__MEM_ALLOC_MARK_SHIFT = 16u, /*! Debug (log etc.) flag mark "no mark", */ su_MEM_ALLOC_MARK_0 = 0u< #if !su_C_LANG || defined CXX_DOXYGEN # define su_CXX_HEADER # include NSPC_BEGIN(su) class mem; /*! \_ */ class mem{ public: /*! * \defgroup CXX_MEM_TOOLS C++ memory tools * \ingroup MEM_TOOLS * \brief C++ variant of \r{MEM_TOOLS} (\r{su/mem.h}) * * In general argument pointers may be given as \NIL if a length argument * which covers them is given as 0. * @{ */ /*! \copydoc{su_mem_cmp()} */ static sz cmp(void const *vpa, void const *vpb, uz len){ return su_mem_cmp(vpa, vpb, len); } /*! \copydoc{su_mem_copy()} */ static void *copy(void *vp, void const *src, uz len){ return su_mem_copy(vp, src, len); } /*! \copydoc{su_mem_find()} */ static void *find(void const *vp, s32 what, uz len){ return su_mem_find(vp, what, len); } /*! \copydoc{su_mem_rfind()} */ static void *rfind(void const *vp, s32 what, uz len){ return su_mem_rfind(vp, what, len); } /*! \copydoc{su_mem_move()} */ static void *move(void *vp, void const *src, uz len){ return su_mem_move(vp, src, len); } /*! \copydoc{su_mem_set()} */ static void *set(void *vp, s32 what, uz len){ return su_mem_set(vp, what, len); } /*! \copydoc{su_mem_set_volatile()} */ static void *set_volatile(void *vp, s32 what, uz len){ return (*su_mem_set_volatile)(vp, what, len); } public: /*! @} */ /*! * \defgroup CXX_MEM_CACHE_ALLOC C++ heap memory * \ingroup MEM_CACHE_ALLOC * \brief C++ variant of \r{MEM_CACHE_ALLOC} (\r{su/mem.h}) * * It interacts with \r{state::err_nomem} and \r{state::err_overflow}, * but also allows per-call failure ignorance. * \remarks{No mirrors of the C interface macros are available.} * @{ */ struct johnny; struct mary; /*! \copydoc{su_mem_alloc_flags} */ enum alloc_flags{ alloc_none = su_MEM_ALLOC_NONE, /*!< \copydoc{su_MEM_ALLOC_NONE} */ alloc_clear = su_MEM_ALLOC_CLEAR, /*!< \copydoc{su_MEM_ALLOC_CLEAR} */ /*! \copydoc{su_MEM_ALLOC_32BIT_OVERFLOW} */ alloc_32bit_overflow = su_MEM_ALLOC_32BIT_OVERFLOW, /*! \copydoc{su_MEM_ALLOC_31BIT_OVERFLOW} */ alloc_31bit_overflow = su_MEM_ALLOC_31BIT_OVERFLOW, /*! \copydoc{su_MEM_ALLOC_OVERFLOW_OK} */ alloc_overflow_ok = su_MEM_ALLOC_OVERFLOW_OK, /*! \copydoc{su_MEM_ALLOC_NOMEM_OK} */ alloc_nomem_ok = su_MEM_ALLOC_NOMEM_OK, /*! \copydoc{su_MEM_ALLOC_MAYFAIL} */ alloc_mayfail = su_MEM_ALLOC_MAYFAIL, /*! \copydoc{su_MEM_ALLOC_MUSTFAIL} */ alloc_mustfail = su_MEM_ALLOC_MUSTFAIL, alloc_mark_0 = su_MEM_ALLOC_MARK_0, /*!< \copydoc{su_MEM_ALLOC_MARK_0} */ alloc_mark_1 = su_MEM_ALLOC_MARK_1, /*!< \copydoc{su_MEM_ALLOC_MARK_1} */ alloc_mark_2 = su_MEM_ALLOC_MARK_2, /*!< \copydoc{su_MEM_ALLOC_MARK_2} */ alloc_mark_3 = su_MEM_ALLOC_MARK_3 /*!< \copydoc{su_MEM_ALLOC_MARK_3} */ }; enum{ alloc_min = su_MEM_ALLOC_MIN /*!< \copydoc{su_MEM_ALLOC_MIN} */ }; /*! \copydoc{su_mem_conf_option} */ enum conf_option{ conf_debug = su_MEM_CONF_DEBUG, /*!< \copydoc{su_MEM_CONF_DEBUG} */ /*! \copydoc{su_MEM_CONF_ON_ERROR_EMERG} */ conf_on_error_emerg = su_MEM_CONF_ON_ERROR_EMERG, /*! \copydoc{su_MEM_CONF_LINGER_FREE} */ conf_linger_free = su_MEM_CONF_LINGER_FREE, /*! \copydoc{su_MEM_CONF_LINGER_FREE_RELEASE} */ conf_linger_free_release = su_MEM_CONF_LINGER_FREE_RELEASE }; /*! The base of \r{su_MEM_NEW()} etc. * Be aware it does not even set \r{su_MEM_ALLOC_MUSTFAIL} automatically. */ #define su_MEM_ALLOC_NEW(T,F) \ new(su_MEM_ALLOCATE(sizeof(T), 1, F),\ su_S(su_NSPC(su)mem::johnny const*,su_NIL)) T /*! \r{su_MEM_ALLOC_NEW()} */ #define su_MEM_ALLOC_NEW_LOC(T,F,FNAME,LNNO) \ new(su_MEM_ALLOCATE_LOC(sizeof(T), 1, F, FNAME, LNNO),\ su_S(su_NSPC(su)mem::johnny const*,su_NIL)) T /*! \_ */ #define su_MEM_NEW(T) su_MEM_ALLOC_NEW(T, su_MEM_ALLOC_MUSTFAIL) /*! \_ */ #define su_MEM_NEW_LOC(T,FNAME,LNNO) \ su_MEM_ALLOC_NEW_LOC(T, su_MEM_ALLOC_MUSTFAIL, FNAME, LNNO) /*! \_ */ #define su_MEM_CNEW(T) \ su_MEM_ALLOC_NEW(T, su_MEM_ALLOC_CLEAR | su_MEM_ALLOC_MUSTFAIL) /*! \_ */ #define su_MEM_CNEW_LOC(T,FNAME,LNNO) \ su_MEM_ALLOC_NEW_LOC(T, su_MEM_ALLOC_CLEAR | su_MEM_ALLOC_MUSTFAIL,\ FNAME, LNNO) /*! \_ */ #define su_MEM_NEW_HEAP(T,VP) new(VP, su_S(su_NSPC(su)mem::johnny*,su_NIL)) T /*! \_ */ #define su_MEM_DEL(TP) (su_NSPC(su)mem::del__heap(TP), su_MEM_FREE(TP)) /*! \_ */ #define su_MEM_DEL_LOC(TP,FNAME,LNNO) \ (su_NSPC(su)mem::del__heap(TP), su_MEM_FREE_LOC(TP, FNAME, LNNO)) /*! \_ */ #define su_MEM_DEL_HEAP(TP) su_NSPC(su)mem::del__heap(TP) /*! \_ */ #define su_MEM_DEL_HEAP_LOC(TP,FNAME,LNNO) su_NSPC(su)mem::del__heap(TP) /*! \_ */ #define su_MEM_DEL_PRIVATE(T,TP) \ (su_ASSERT((TP) != su_NIL), (TP)->~T(), su_MEM_FREE(TP)) /*! \_ */ #define su_MEM_DEL_PRIVATE_LOC(T,TP,FNAME,LNNO) \ (su_ASSERT_LOC((TP) != su_NIL, FNAME, LNNO),\ (TP)->~T(), su_MEM_FREE_LOC(TP, FNAME, LNNO)) /*! \_ */ #define su_MEM_DEL_HEAP_PRIVATE(T,TP) (su_ASSERT((TP) != su_NIL), (TP)->~T()) /*! \_ */ #define su_MEM_DEL_HEAP_PRIVATE_LOC(T,TP,FNAME,LNNO) \ (su_ASSERT((TP) != su_NIL), (TP)->~T()) /* (The painful _LOCOR series) */ #ifdef su_HAVE_DBG_LOC_ARGS # define su_MEM_NEW_LOCOR(T,ORARGS) su_MEM_NEW_LOC(T, ORARGS) # define su_MEM_CNEW_LOCOR(T,ORARGS) su_MEM_CNEW_LOC(T, ORARGS) # define su_MEM_NEW_HEAP_LOCOR(T,VP,ORARGS) su_MEM_NEW_HEAP_LOC(T, VP, ORARGS) # define su_MEM_DEL_LOCOR(TP,ORARGS) su_MEM_DEL_LOC(TP, ORARGS) # define su_MEM_DEL_HEAP_LOCOR(TP,ORARGS) su_MEM_DEL_HEAP_LOC(TP, ORARGS) # define su_MEM_DEL_PRIVATE_LOCOR(T,TP,ORARGS) \ su_MEM_DEL_PRIVATE_LOC(T, TP, ORARGS) # define su_MEM_DEL_HEAP_PRIVATE_LOCOR(T,TP,ORARGS) \ su_MEM_DEL_HEAP_PRIVATE_LOC(T, TP, ORARGS) #else /*! \_ */ # define su_MEM_NEW_LOCOR(T,ORARGS) su_MEM_NEW(T) /*! \_ */ # define su_MEM_CNEW_LOCOR(T,ORARGS) su_MEM_CNEW(T) /*! \_ */ # define su_MEM_NEW_HEAP_LOCOR(T,VP,ORARGS) su_MEM_NEW_HEAP(T, VP) /*! \_ */ # define su_MEM_DEL_LOCOR(TP,ORARGS) su_MEM_DEL(TP) /*! \_ */ # define su_MEM_DEL_HEAP_LOCOR(TP,ORARGS) su_MEM_DEL_HEAP(TP) /*! \_ */ # define su_MEM_DEL_PRIVATE_LOCOR(T,TP,ORARGS) su_MEM_DEL_PRIVATE(T, TP) /*! \_ */ # define su_MEM_DEL_HEAP_PRIVATE_LOCOR(T,TP,ORARGS) \ su_MEM_DEL_HEAP_PRIVATE(T, TP) #endif /* !su_HAVE_DBG_LOC_ARGS */ /*! @} */ /*! * \defgroup CXX_MEM_CACHE_SUP C++ heap "support" * \ingroup MEM_CACHE_SUP * \brief \r{CXX_MEM_CACHE_ALLOC} support (\r{su/mem.h}) * @{ */ /*! \copydoc{su_mem_get_usable_size()} */ static uz get_usable_size(uz size) {return su_mem_get_usable_size(size);} /*! \copydoc{su_mem_get_usable_size_32()} */ static u32 get_usable_size_32(uz size){ return su_mem_get_usable_size_32(size); } /*! \copydoc{su_mem_conf_option()} */ static void set_conf(u32 co, uz val) {su_mem_set_conf(co, val);} /*! \r{su_mem_check()} */ static void check(void) {su_mem_check();} /*! \r{su_mem_trace()} */ static void trace(void) {su_mem_trace();} template static void del__heap(T *tptr){ ASSERT_RET_VOID(tptr != NIL); tptr->~T(); } /*! @} */ }; /*! * \ingroup CXX_MEM_CACHE_ALLOC * In order to be able, a global overwrite of \c{new()} is necessary. */ inline void *operator new(size_t sz, void *vp, NSPC(su)mem::johnny const *j){ UNUSED(sz); UNUSED(j); return vp; } NSPC_END(su) # include #endif /* !C_LANG || CXX_DOXYGEN */ #endif /* !su_MEM_H */ /* s-it-mode */ s-nail-14.9.15/include/su/prime.h000066400000000000000000000057631352610246600164350ustar00rootroot00000000000000/*@ Prime numbers. * * Copyright (c) 2001 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef su_PRIME_H #define su_PRIME_H /*! * \file * \ingroup PRIME * \brief \r{PRIME} */ #include #define su_HEADER #include C_DECL_BEGIN /*! * \defgroup PRIME Prime numbers * \ingroup MISC * \brief Prime numbers (\r{su/prime.h}) * @{ */ /*! Minimum pre-calculated lookup prime (a \r{su_u32}). */ #define su_PRIME_LOOKUP_MIN 0x2u /*! Maximum pre-calculated lookup prime (a \r{su_u32}). */ #define su_PRIME_LOOKUP_MAX 0x18000005u /*! \_ * \remarks{Very unacademical (brute force).} */ EXPORT boole su_prime_is_prime(u64 no, boole allowpseudo); /*! \_ */ EXPORT u64 su_prime_get_former(u64 no, boole allowpseudo); /*! \_ */ EXPORT u64 su_prime_get_next(u64 no, boole allowpseudo); /*! Former precalculated, minimum is \r{su_PRIME_LOOKUP_MIN}. */ EXPORT u32 su_prime_lookup_former(u32 no); /*! Next precalculated, maximum is \r{su_PRIME_LOOKUP_MAX}. */ EXPORT u32 su_prime_lookup_next(u32 no); /*! @} */ C_DECL_END #include #if !su_C_LANG || defined CXX_DOXYGEN # define su_CXX_HEADER # include NSPC_BEGIN(su) class prime; /*! * \ingroup PRIME * C++ variant of \r{PRIME} (\r{su/prime.h}) */ class prime{ public: /*! \copydoc{su_PRIME_LOOKUP_MIN} */ static u32 const lookup_min = su_PRIME_LOOKUP_MIN; /*! \copydoc{su_PRIME_LOOKUP_MAX} */ static u32 const lookup_max = su_PRIME_LOOKUP_MAX; /*! \copydoc{su_prime_is_prime()} */ static boole is_prime(u64 no, boole allowpseudo=TRU1){ return su_prime_is_prime(no, allowpseudo); } /*! \copydoc{su_prime_get_former()} */ static u64 get_former(u64 no, boole allowpseudo=TRU1){ return su_prime_get_former(no, allowpseudo); } /*! \copydoc{su_prime_get_next()} */ static u64 get_next(u64 no, boole allowpseudo=TRU1){ return su_prime_get_next(no, allowpseudo); } /*! \copydoc{su_prime_lookup_former()} */ static u32 lookup_former(u32 no) {return su_prime_lookup_former(no);} /*! \copydoc{su_prime_lookup_next()} */ static u32 lookup_next(u32 no) {return su_prime_lookup_next(no);} }; NSPC_END(su) # include #endif /* !C_LANG || CXX_DOXYGEN */ #endif /* su_PRIME_H */ /* s-it-mode */ s-nail-14.9.15/include/su/sort.h000066400000000000000000000043101352610246600162730ustar00rootroot00000000000000/*@ Sorting (of arrays). * * Copyright (c) 2001 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef su_SORT_H #define su_SORT_H /*! * \file * \ingroup SORT * \brief \r{SORT} */ #include #define su_HEADER #include C_DECL_BEGIN /*! * \defgroup SORT Sorting of arrays * \ingroup MISC * \brief Sorting of arrays (\r{su/sort.h}) * @{ */ /*! Sort an array of pointers with Knuth's shell sort algorithm * (Volume 3, page 84). * \a{arr} may be \NIL if \a{entries} is 0. * If \a{cmp_or_nil} is \NIL by-pointer comparison is performed. * Otherwise, \a{cmp_or_nil} will not be called for \NIL array entries, * which thus can result in false sorting. */ EXPORT void su_sort_shell_vpp(void const **arr, uz entries, su_compare_fun cmp_or_nil); /*! @} */ C_DECL_END #include #if !su_C_LANG || defined CXX_DOXYGEN # define su_CXX_HEADER # include NSPC_BEGIN(su) class sort; /*! * \ingroup SORT * C++ variant of \r{SORT} (\r{su/sort.h}) */ class sort{ public: /*! \copydoc{su_sort_shell_vpp()} */ template static void shell(T const **arr, uz entries, typename type_toolbox::compare_fun cmp_or_nil){ ASSERT_RET_VOID(entries == 0 || arr != NIL); su_sort_shell_vpp(R(void const**,arr), entries, R(su_compare_fun,cmp_or_nil)); } }; NSPC_END(su) # include #endif /* !C_LANG || CXX_DOXYGEN */ #endif /* su_PRIME_H */ /* s-it-mode */ s-nail-14.9.15/include/su/utf.h000066400000000000000000000063671352610246600161200ustar00rootroot00000000000000/*@ Convert in between UnicodeTranformationFormats. * * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef su_UTF_H #define su_UTF_H /*! * \file * \ingroup UTF * \brief \r{UTF} */ #include #define su_HEADER #include C_DECL_BEGIN /*! * \defgroup UTF Unicode Transformation Formats * \ingroup TEXT * \brief Convert in between UnicodeTranformationFormats (\r{su/utf.h}) * @{ */ /*! * \defgroup UTF8 UTF-8 * \ingroup UTF * \brief UTF-8 (\r{su/utf.h}) * @{ */ enum{ /*! Maximum buffer size of an UTF-8 sequence including terminating NUL. */ su_UTF8_BUFFER_SIZE = 5u }; /*! The Unicode replacement character \c{0xFFFD} as an UTF-8 literal. */ #define su_UTF8_REPLACER "\xEF\xBF\xBD" /*! Compiled in version of \r{su_UTF8_REPLACER}. */ EXPORT_DATA char const su_utf8_replacer[sizeof su_UTF8_REPLACER]; /*! Convert, and update arguments to point after range. * Returns \r{su_U32_MAX} on error, in which case the arguments will have been * stepped one byte. */ EXPORT u32 su_utf8_to_32(char const **bdat, uz *blen); /*! @} */ /*! * \defgroup UTF32 UTF-32 * \ingroup UTF * \brief UTF-32 (\r{su/utf.h}) * @{ */ /*! The Unicode replacement character \c{0xFFFD} as an UTF-32 codepoint. */ #define su_UTF32_REPLACER 0xFFFDu /*! Convert an UTF-32 character to an UTF-8 sequence. * \a{bp} must be large enough also for the terminating NUL (see * \r{su_UTF8_BUFFER_SIZE}), its length will * be returned. */ EXPORT uz su_utf32_to_8(u32 c, char *bp); /*! @} */ /*! @} */ C_DECL_END #include #if !su_C_LANG || defined CXX_DOXYGEN # define su_CXX_HEADER # include NSPC_BEGIN(su) class utf8; class utf32; /*! * \ingroup UTF8 * C++ variant of \r{UTF8} (\r{su/utf.h}) */ class EXPORT utf8{ public: enum{ /*! \copydoc{su_UTF8_BUFFER_SIZE} */ buffer_size = su_UTF8_BUFFER_SIZE }; /*! \copydoc{su_UTF8_REPLACER} */ static char const replacer[sizeof su_UTF8_REPLACER]; /*! \copydoc{su_utf8_to_32()} */ static u32 convert_to_32(char const **bdat, uz *blen){ return su_utf8_to_32(bdat, blen); } }; /*! * \ingroup UTF32 * C++ variant of \r{UTF32} (\r{su/utf.h}) */ class utf32{ public: /*! \copydoc{su_UTF32_REPLACER} */ static u32 const replacer = su_UTF32_REPLACER; /*! \copydoc{su_utf32_to_8()} */ static uz convert_to_8(u32 c, char *bp) {return su_utf32_to_8(c, bp);} }; NSPC_END(su) # include #endif /* !C_LANG || CXX_DOXYGEN */ #endif /* su_UTF_H */ /* s-it-mode */ s-nail-14.9.15/include/su/view.h000066400000000000000000000617571352610246600163000ustar00rootroot00000000000000/*@ Generic C++ View (-of-a-collection, for iterating plus purposes) templates. *@ (Merely of interest when creating a new C++ collection type.) * * Copyright (c) 2001 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef su_VIEW_H # define su_VIEW_H #ifdef CXX_DOXYGEN /*! * \file * \ingroup VIEW * \brief Generic view template(s) (super classes) * * Merely of interest when creating new C++ collection types. */ #endif #include su_USECASE_MX_DISABLED #if !su_C_LANG || defined CXX_DOXYGEN #define su_CXX_HEADER #include NSPC_BEGIN(su) template class view_traits; template class view__base; template class view_unidir; template class view_unidir_const; template class view_bidir; template class view_bidir_const; template class view_random; template class view_random_const; template class view_assoc_unidir; template class view_assoc_unidir_const; template class view_assoc_bidir; template class view_assoc_bidir_const; /*! * \defgroup VIEW C++ View superclasses * \ingroup COLL * \brief Generic view template(s) (super classes) (\r{su/view.h}) * * Merely of interest when creating new C++ collection types. * * \head1{Superclass requirements} * * The minimum required interface that the typesafe C++ templates require, * and its expected behaviour. * In general there is a basic interface and several type-specific additions * which stack upon each other. * * \head2{Base} * * \list{\li{ * Copy constructor * }\li{ * Assignment operator (C: \fn{_assign()}) * }\li{ * \fn{self_class &setup(super_collection &tc)} * Create a tie in between this view and its parental collection instance. * This is superficial: the programmer is responsible to ensure that the parent * remains accessible, remains unmodified, etc. * }\li{ * \fn{boole is_setup(void) const} * }\li{ * \fn{boole is_same_parent(self_class const &t) const}: * Whether two views are tied to the same collection object. * }\li{ * \fn{boole is_valid(void) const}: * A valid view points to an accessible slot of the collection instance. * }\li{ * \fn{self_class &invalidate(void)} * Invalidate the position, but keep the parent collection tied. * }\li{ * \fn{void const *data(void) const}: * \NIL is returned if \fn{is_valid()} assertion triggers. * Also avaiable via \fn{operator*(void) const} * and \fn{operator->(void) const}. * }\li{ * \fn{self_class &begin(void)}: * Move the \fn{is_setup()} view to the first iteratable position, if any. * }\li{ * \fn{boole has_next(void) const}, \fn{self_class &next(void)}: * Step the \c{is_valid()} view to the next position, if any. * }\li{ * \fn{sz cmp(self_class const &t) const}: * This need not compare all possible cases, only those which make sense; * Equality and inequality must always be provided. * Neither \fn{is_setup()} nor \fn{is_valid()} are preconditions for * comparison; a non-\fn{setup()}d view shall compare less-than an * \fn{is_setup()} one, ditto \fn{is_valid()}. * }} * * \head2{Additions for views with non-constant parent} * * \list{\li{ * \fn{void *data(void)}: * \NIL is returned if \fn{is_valid()} assertion triggers. * Also avaiable via \fn{operator*(void)} and \fn{operator->(void)}. * }\li{ * \fn{s32 set_data(void *dat)}: * Replace the value of an \fn{is_valid()} position. * Returns \err{none} upon success, and \err{einval} if the \fn{is_setup()} * assertion fails. * }\li{ * \fn{self_class &remove(void)}: * Remove the current \c{is_valid()} entry, and move to the next position, * if there is any. * }} * * \head2{Additions for non-associative views, non-constant parent} * * \list{\li{ * \fn{s32 insert(void *dat)}: * Insert new element after current position if that \fn{is_valid()}, * otherwise creates a new \fn{begin()}. * Returns \err{none} and is positioned at the inserted element upon * success, and \err{einval} if the \fn{is_setup()} assertion fails. * }\li{ * \fn{s32 insert_range(self_class &startpos, self_class &endpos)}: * Insert new element(s) after current position if that \fn{is_valid()}, * otherwise creates a new \fn{begin()} first. * \a{startpos} must be \fn{is_valid()} and must not be \fn{is_same_parent()} * as \THIS; if it is, it is asserted that ranges do not overlap. * * All positions accessible by iterating \a{startpos} will be inserted. * If \a{endpos} is \fn{is_valid()} \c{startpos.is_same_parent(endpos)} must * assert and the iteration does not include \a{endpos}. * Returns \err{none} and is positioned at the last inserted element * upon success, and \err{einval} if the \fn{is_setup()} or any of the * argument assertions fail, among others. * }\li{ * \fn{self_class &remove_range(self_class &endpos)}: * Remove all elements starting at the current \fn{is_valid()} entry unless * \THIS becomes invalid, or a given \fn{is_valid()} \a{endpos}ition is * reached, which is not removed. * * If \a{endpos} \fn{is_valid()} then it must be \fn{is_same_parent()} * as this view; it may also become updated in this case in order to * stay at the same effective position after the operation completed: * for example, removing a range from an array requires \a{endpos} to be * moved further to "the front". * }} * * \head2{Additions for associative views} * * \list{\li{ * \fn{void const *key(void) const}: * \NIL is returned if the \fn{is_valid()} assertion triggers. * }} * * \head2{Additions for non-associative unidirectional views} * * \list{\li{ * \fn{self_class &go_to(uz off)}: * Move the \fn{is_setup()} view to the absolute position \a{off}. * }\li{ * \fn{boole find(void const *dat, boole byptr)}: * Search for \a{dat} in the \fn{is_setup()} view, either starting at the * current position if \fn{is_valid()}, at \fn{begin()} otherwise. * \a{byptr} states whether data shall be found by simple pointer comparison; * if not, the \c{toolbox} of the \fn{is_setup()}d parent should be * asserted (if used by the collection in question). * }} * * \head2{Additions for associative unidirectional views} * * \list{\li{ * \fn{boole find(void const *key)}: * Search for \a{key} in the \fn{is_setup()} view. * }} * * \head2{Additions for bidirectional views} * * \list{\li{ * \fn{self_class &end(void)}: * Move the \fn{is_setup()} view to the last position, if any. * }\li{ * \fn{boole has_last(void) const} * }\li{ * \fn{self_class &last(void)}: * Move the \fn{is_valid()} view to the position before the current one, * if any. * }} * * \head2{Additions for non-associative bidirectional views} * * \list{\li{ * \fn{boole rfind(void const *dat, boole byptr)}: * Search for \a{dat} in the \fn{is_setup()} view, either starting at the * current position if \fn{is_valid()}, at \fn{end()} otherwise. * \a{byptr} states whether data shall be found by simple pointer comparison; * if not, the \c{toolbox} of the \fn{setup()}d parent should be asserted. * }} * * \head2{Additions for non-associative random-access views} * * This type extends the requirement of the \fn{cmp()} function in the * base set, and requires the additional results less-than, * less-than-or-equal, greater-than and greater-than-or-equal. * * \list{\li{ * \fn{self_class &go_around(sz reloff)}: * Move the \fn{is_valid()} view relative by \a{reloff} positions. * }} * @{ */ /*! \_ */ enum view_category{ view_category_non_assoc, /*!< \_ */ view_category_assoc /*!< \_ */ }; /*! \_ */ enum view_type{ view_type_unidir, /*!< \_ */ view_type_bidir, /*!< \_ */ view_type_random, /*!< \_ */ view_type_assoc_unidir, /*!< \_ */ view_type_assoc_bidir /*!< \_ */ }; /*! \_ */ template class view_traits{ public: typedef BASECOLLT base_coll_type; /*!< \_ */ // Identical to normal traits except for view_category_assoc views typedef NSPC(su)type_traits key_type_traits; /*!< \_ */ typedef NSPC(su)type_traits type_traits; /*!< \_ */ }; /* @} */ // class view__base{{{ template class view__base{ protected: GBASEVIEWT m_view; view__base(void) : m_view() {} view__base(view__base const &t) : m_view(t.m_view) {} public: ~view__base(void) {} protected: view__base &assign(view__base const &t){ m_view = t.m_view; return *this; } view__base &operator=(view__base const &t) {return assign(t);} public: boole is_setup(void) const {return m_view.is_setup();} boole is_same_parent(view__base const &t) const{ return m_view.is_same_parent(t.m_view); } boole is_valid(void) const {return m_view.is_valid();} operator boole(void) const {return is_valid();} protected: view__base &invalidate(void){ (void)m_view.invalidate(); return *this; } sz cmp(view__base const &t) const {return m_view.cmp(t.m_view);} public: boole is_equal(view__base const &t) const {return (cmp(t) == 0);} boole operator==(view__base const &t) const {return (cmp(t) == 0);} boole operator!=(view__base const &t) const {return (cmp(t) != 0);} }; // }}} // Because of the various sorts of views we define helper macros. // Unfortunately GCC (up to and including 3.4.2) cannot access // base& _x ... _x.m_view // ("is protected within this context"), but can only access // base& _x ... MYSELF& y = _x ... y.m_view // (and only so if we put a "using" directive, see su__VIEW_IMPL_START__BASE). // To be (hopefully..) absolutely safe use a C-style cast #define su__VIEW_DOWNCAST(X) ((su__VIEW_NAME&)X) #define su__VIEW_IMPL_START /*{{{*/\ template\ class su__VIEW_NAME : public view__base{\ typedef su__VIEW_NAME myself;\ typedef view__base base;\ \ /* XXX All these typedefs could be moved to class view__base!? */\ typedef typename VIEWTRAITS::base_coll_type base_coll_type;\ typedef typename VIEWTRAITS::key_type_traits key_type_traits;\ typedef typename VIEWTRAITS::type_traits type_traits;\ \ /* (Simply add _key_ - for non-associative this is eq tp_const) */\ typedef typename key_type_traits::tp_const key_tp_const;\ typedef typename type_traits::type type;\ typedef typename type_traits::tp tp;\ typedef typename type_traits::tp_const tp_const;\ \ protected:\ /* (GCC (up to and incl. 3.4.2) does not find it otherwise) */\ using base::m_view;\ \ public:\ static NSPC(su)view_category const view_category = su__VIEW_CATEGORY;\ static NSPC(su)view_type const view_type = su__VIEW_TYPE;\ \ su__VIEW_NAME(void) : base() {}\ template explicit su__VIEW_NAME(TCOLL &tc) : base(){\ (void)m_view.setup(S(base_coll_type&,tc)).begin();\ }\ /* (Need to offer all forms to allow additional TCOLL template(s)..) */\ su__VIEW_NAME(su__VIEW_NAME &t) : base(t) {}\ su__VIEW_NAME(su__VIEW_NAME const &t) : base(t) {}\ ~su__VIEW_NAME(void) {}\ \ su__VIEW_NAME &assign(su__VIEW_NAME const &t){\ return S(myself&,base::assign(t));\ }\ su__VIEW_NAME &operator=(su__VIEW_NAME const &t) {return assign(t);}\ \ using base::is_setup;\ template su__VIEW_NAME &setup(TCOLL &tc){\ (void)m_view.setup(S(base_coll_type&,tc));\ return *this;\ }\ \ using base::is_same_parent;\ \ using base::is_valid;\ using base::operator boole;\ \ su__VIEW_NAME &invalidate(void) {return S(myself&,base::invalidate());}\ \ tp_const data(void) const{\ ASSERT_RET(is_valid(), NIL);\ return type_traits::to_const_tp(m_view.data());\ }\ tp_const operator*(void) const {return data();}\ tp_const operator->(void) const {return data();}\ \ su__VIEW_NAME &begin(void){\ ASSERT_RET(is_setup(), *this);\ (void)m_view.begin();\ return *this;\ }\ template su__VIEW_NAME &begin(TCOLL &tc){\ (void)m_view.setup(S(base_coll_type&,tc)).begin();\ return *this;\ }\ \ boole has_next(void) const{\ ASSERT_RET(is_valid(), FAL0);\ return m_view.has_next();\ }\ su__VIEW_NAME &next(void){\ ASSERT_RET(is_valid(), *this);\ (void)m_view.next();\ return *this;\ }\ su__VIEW_NAME &operator++(void) {return next();}\ \ using base::is_equal;\ using base::operator==;\ using base::operator!=;\ /*}}} }*/ #define su__VIEW_IMPL_NONCONST /*{{{*/\ tp data(void){\ ASSERT_RET(is_valid(), NIL);\ return type_traits::to_tp(m_view.data());\ }\ s32 set_data(tp dat){\ ASSERT_RET(is_valid(), err::einval);\ return m_view.set_data(type_traits::to_vp(dat));\ }\ tp operator*(void) {return data();}\ tp operator->(void) {return data();}\ \ su__VIEW_NAME &remove(void){\ ASSERT_RET(is_valid(), *this);\ (void)m_view.remove();\ return *this;\ }\ /*}}}*/ #define su__VIEW_IMPL_CONST /*{{{*/\ /* (We need to cast away the 'const', but it is preserved logically..) */\ template explicit su__VIEW_NAME(TCOLL const &tc){\ (void)m_view.setup(S(base_coll_type&,C(TCOLL&,tc))).begin();\ }\ /* (Need to offer all copy-forms to allow TCOLL template..) */\ explicit su__VIEW_NAME(su__VIEW_NAME_NONCONST &t)\ : base(t){\ }\ explicit su__VIEW_NAME(\ su__VIEW_NAME_NONCONST const &t) : base(t) {}\ \ su__VIEW_NAME &assign(\ su__VIEW_NAME_NONCONST const &t){\ return S(myself&,base::assign(t));\ }\ su__VIEW_NAME &operator=(\ su__VIEW_NAME_NONCONST const &t){\ return assign(t);\ }\ \ /* (We need to cast away the 'const', but it is preserved logically..) */\ template su__VIEW_NAME &setup(TCOLL const &tc){\ (void)m_view.setup(S(base_coll_type&,C(TCOLL&,tc)));\ return *this;\ }\ /*}}}*/ #define su__VIEW_IMPL_NONASSOC #define su__VIEW_IMPL_NONASSOC_NONCONST /*{{{*/\ /* err::enone or error */\ s32 insert(tp dat){\ ASSERT_RET(is_setup(), err::einval);\ return m_view.insert(type_traits::to_vp(dat));\ }\ s32 insert(base &startpos, base const &endpos){\ ASSERT_RET(is_setup(), err::einval);\ ASSERT_RET(startpos.is_valid(), err::einval);\ ASSERT_RET(!endpos.is_valid() || startpos.is_same_parent(endpos),\ err::einval);\ if(DBGOR(1, 0)){\ if(is_same_parent(startpos)){\ myself v(startpos);\ if(endpos.is_valid()){\ for(;; ++v)\ if(v == endpos)\ break;\ else if(!v || v == *this)\ return err::einval;\ }else{\ for(; v; ++v)\ if(v == *this)\ return err::einval;\ }\ }\ }\ return m_view.insert_range(su__VIEW_DOWNCAST(startpos).m_view,\ su__VIEW_DOWNCAST(endpos).m_view);\ }\ \ su__VIEW_NAME &remove(base &endpos){\ ASSERT_RET(is_valid(), *this);\ ASSERT_RET(!endpos.is_valid() || is_same_parent(endpos), *this);\ (void)m_view.remove_range(su__VIEW_DOWNCAST(endpos).m_view);\ return *this;\ }\ /*}}}*/ #define su__VIEW_IMPL_NONASSOC_CONST #define su__VIEW_IMPL_ASSOC /*{{{*/\ key_tp_const key(void) const{\ ASSERT_RET(is_valid(), NIL);\ return key_type_traits::to_const_tp(m_view.key());\ }\ /*}}}*/ #define su__VIEW_IMPL_ASSOC_NONCONST #define su__VIEW_IMPL_ASSOC_CONST #define su__VIEW_IMPL_UNIDIR #define su__VIEW_IMPL_UNIDIR_NONCONST #define su__VIEW_IMPL_UNIDIR_CONST /*{{{*/\ /* (We need to cast away the 'const', but it is preserved logically..) */\ template su__VIEW_NAME &begin(TCOLL const &tc){\ (void)m_view.setup(S(base_coll_type&,C(TCOLL&,tc))).begin();\ return *this;\ }\ /*}}}*/ #define su__VIEW_IMPL_UNIDIR_NONASSOC /*{{{*/\ /* is_valid() must be tested thereafter */\ su__VIEW_NAME &go_to(uz off){\ ASSERT_RET(is_setup(), *this);\ (void)m_view.go_to(off);\ return *this;\ }\ \ boole find(tp_const dat, boole byptr=FAL0){\ ASSERT_RET(is_setup(), (invalidate(), FAL0));\ /* FIXME toolbox assert if !byptr */\ return m_view.find(type_traits::to_const_vp(dat), byptr);\ }\ /*}}}*/ #define su__VIEW_IMPL_UNIDIR_ASSOC /*{{{*/\ boole find(key_tp_const key){\ ASSERT_RET(is_setup(), (invalidate(), FAL0));\ return m_view.find(key_type_traits::to_const_vp(key));\ }\ /*}}}*/ #define su__VIEW_IMPL_BIDIR /*{{{*/\ su__VIEW_NAME &end(void){\ ASSERT_RET(is_setup(), *this);\ (void)m_view.end();\ return *this;\ }\ \ boole has_last(void) const{\ ASSERT_RET(is_valid(), FAL0);\ return m_view.has_last();\ }\ su__VIEW_NAME &last(void){\ ASSERT_RET(is_valid(), *this);\ (void)m_view.last();\ return *this;\ }\ su__VIEW_NAME &operator--(void) {return last();}\ /*}}}*/ #define su__VIEW_IMPL_BIDIR_NONCONST /*{{{*/\ template su__VIEW_NAME &end(TCOLL &tc){\ (void)m_view.setup(S(base_coll_type&,tc)).end();\ return *this;\ }\ /*}}}*/ #define su__VIEW_IMPL_BIDIR_CONST /*{{{*/\ /* (We need to cast away the 'const', but it is preserved logically..) */\ template su__VIEW_NAME &end(TCOLL const &tc){\ (void)m_view.setup(S(base_coll_type&,C(TCOLL&,tc))).end();\ return *this;\ }\ /*}}}*/ #define su__VIEW_IMPL_BIDIR_NONASSOC /*{{{*/\ boole rfind(tp_const dat, boole byptr=FAL0){\ ASSERT_RET(is_setup(), (invalidate(), FAL0));\ /* FIXME toolbox assert if !byptr */\ return m_view.rfind(type_traits::to_const_vp(dat), byptr);\ }\ /*}}}*/ #define su__VIEW_IMPL_BIDIR_ASSOC #define su__VIEW_IMPL_RANDOM #define su__VIEW_IMPL_RANDOM_NONCONST #define su__VIEW_IMPL_RANDOM_CONST #define su__VIEW_IMPL_RANDOM_NONASSOC /*{{{*/\ /* is_valid() must be tested thereafter */\ su__VIEW_NAME &go_around(sz reloff){\ ASSERT_RET(is_valid(), *this);\ return m_view.go_around(reloff);\ }\ su__VIEW_NAME &operator+=(sz reloff) {return go_around(reloff);}\ su__VIEW_NAME &operator-=(sz reloff) {return go_around(-reloff);}\ su__VIEW_NAME operator+(sz reloff) const{\ su__VIEW_NAME rv(*this);\ ASSERT_RET(is_valid(), rv);\ return (rv += reloff);\ }\ su__VIEW_NAME operator-(sz reloff) const{\ su__VIEW_NAME rv(*this);\ ASSERT_RET(is_valid(), rv);\ return (rv -= reloff);\ }\ \ boole operator<(base const &t) const {return (base::cmp(t) < 0);}\ boole operator>(base const &t) const {return (base::cmp(t) > 0);}\ boole operator<=(base const &t) const {return (base::cmp(t) <= 0);}\ boole operator>=(base const &t) const {return (base::cmp(t) >= 0);}\ /*}}}*/ #define su__VIEW_IMPL_RANDOM_ASSOC #define su__VIEW_IMPL_END }; // Puuuh. Let us go! {{{ #undef su__VIEW_CATEGORY #define su__VIEW_CATEGORY view_category_non_assoc #undef su__VIEW_TYPE #define su__VIEW_TYPE view_type_unidir #undef su__VIEW_NAME #undef su__VIEW_NAME_NONCONST #define su__VIEW_NAME view_unidir #define su__VIEW_NAME_NONCONST view_unidir su__VIEW_IMPL_START su__VIEW_IMPL_NONCONST su__VIEW_IMPL_NONASSOC su__VIEW_IMPL_NONASSOC_NONCONST su__VIEW_IMPL_UNIDIR su__VIEW_IMPL_UNIDIR_NONCONST su__VIEW_IMPL_UNIDIR_NONASSOC su__VIEW_IMPL_END #undef su__VIEW_NAME #define su__VIEW_NAME view_unidir_const su__VIEW_IMPL_START su__VIEW_IMPL_CONST su__VIEW_IMPL_NONASSOC su__VIEW_IMPL_NONASSOC_CONST su__VIEW_IMPL_UNIDIR su__VIEW_IMPL_UNIDIR_CONST su__VIEW_IMPL_UNIDIR_NONASSOC su__VIEW_IMPL_END #undef su__VIEW_TYPE #define su__VIEW_TYPE view_type_bidir #undef su__VIEW_NAME #undef su__VIEW_NAME_NONCONST #define su__VIEW_NAME view_bidir #define su__VIEW_NAME_NONCONST view_bidir su__VIEW_IMPL_START su__VIEW_IMPL_NONCONST su__VIEW_IMPL_NONASSOC su__VIEW_IMPL_NONASSOC_NONCONST su__VIEW_IMPL_UNIDIR su__VIEW_IMPL_UNIDIR_NONCONST su__VIEW_IMPL_UNIDIR_NONASSOC su__VIEW_IMPL_BIDIR su__VIEW_IMPL_BIDIR_NONCONST su__VIEW_IMPL_BIDIR_NONASSOC su__VIEW_IMPL_END #undef su__VIEW_NAME #define su__VIEW_NAME view_bidir_const su__VIEW_IMPL_START su__VIEW_IMPL_CONST su__VIEW_IMPL_NONASSOC su__VIEW_IMPL_NONASSOC_CONST su__VIEW_IMPL_UNIDIR su__VIEW_IMPL_UNIDIR_CONST su__VIEW_IMPL_UNIDIR_NONASSOC su__VIEW_IMPL_BIDIR su__VIEW_IMPL_BIDIR_CONST su__VIEW_IMPL_BIDIR_NONASSOC su__VIEW_IMPL_END #undef su__VIEW_TYPE #define su__VIEW_TYPE view_type_random #undef su__VIEW_NAME #undef su__VIEW_NAME_NONCONST #define su__VIEW_NAME view_random #define su__VIEW_NAME_NONCONST view_random su__VIEW_IMPL_START su__VIEW_IMPL_NONCONST su__VIEW_IMPL_NONASSOC su__VIEW_IMPL_NONASSOC_NONCONST su__VIEW_IMPL_UNIDIR su__VIEW_IMPL_UNIDIR_NONCONST su__VIEW_IMPL_UNIDIR_NONASSOC su__VIEW_IMPL_BIDIR su__VIEW_IMPL_BIDIR_NONCONST su__VIEW_IMPL_BIDIR_NONASSOC su__VIEW_IMPL_RANDOM su__VIEW_IMPL_RANDOM_NONCONST su__VIEW_IMPL_RANDOM_NONASSOC su__VIEW_IMPL_END #undef su__VIEW_NAME #define su__VIEW_NAME view_random_const su__VIEW_IMPL_START su__VIEW_IMPL_CONST su__VIEW_IMPL_NONASSOC su__VIEW_IMPL_NONASSOC_CONST su__VIEW_IMPL_UNIDIR su__VIEW_IMPL_UNIDIR_CONST su__VIEW_IMPL_UNIDIR_NONASSOC su__VIEW_IMPL_BIDIR su__VIEW_IMPL_BIDIR_CONST su__VIEW_IMPL_BIDIR_NONASSOC su__VIEW_IMPL_RANDOM su__VIEW_IMPL_RANDOM_CONST su__VIEW_IMPL_RANDOM_NONASSOC su__VIEW_IMPL_END #undef su__VIEW_CATEGORY #define su__VIEW_CATEGORY view_category_assoc #undef su__VIEW_TYPE #define su__VIEW_TYPE view_type_assoc_unidir #undef su__VIEW_NAME #undef su__VIEW_NAME_NONCONST #define su__VIEW_NAME view_assoc_unidir #define su__VIEW_NAME_NONCONST view_assoc_unidir su__VIEW_IMPL_START su__VIEW_IMPL_NONCONST su__VIEW_IMPL_ASSOC su__VIEW_IMPL_ASSOC_NONCONST su__VIEW_IMPL_UNIDIR su__VIEW_IMPL_UNIDIR_NONCONST su__VIEW_IMPL_UNIDIR_ASSOC su__VIEW_IMPL_END #undef su__VIEW_NAME #define su__VIEW_NAME view_assoc_unidir_const su__VIEW_IMPL_START su__VIEW_IMPL_CONST su__VIEW_IMPL_ASSOC su__VIEW_IMPL_ASSOC_CONST su__VIEW_IMPL_UNIDIR su__VIEW_IMPL_UNIDIR_CONST su__VIEW_IMPL_UNIDIR_ASSOC su__VIEW_IMPL_END #undef su__VIEW_TYPE #define su__VIEW_TYPE view_type_assoc_bidir #undef su__VIEW_NAME #undef su__VIEW_NAME_NONCONST #define su__VIEW_NAME view_assoc_bidir #define su__VIEW_NAME_NONCONST view_assoc_bidir su__VIEW_IMPL_START su__VIEW_IMPL_NONCONST su__VIEW_IMPL_ASSOC su__VIEW_IMPL_ASSOC_NONCONST su__VIEW_IMPL_UNIDIR su__VIEW_IMPL_UNIDIR_NONCONST su__VIEW_IMPL_UNIDIR_ASSOC su__VIEW_IMPL_BIDIR su__VIEW_IMPL_BIDIR_NONCONST su__VIEW_IMPL_BIDIR_ASSOC su__VIEW_IMPL_END #undef su__VIEW_NAME #define su__VIEW_NAME view_assoc_bidir_const su__VIEW_IMPL_START su__VIEW_IMPL_CONST su__VIEW_IMPL_ASSOC su__VIEW_IMPL_ASSOC_CONST su__VIEW_IMPL_UNIDIR su__VIEW_IMPL_UNIDIR_CONST su__VIEW_IMPL_UNIDIR_ASSOC su__VIEW_IMPL_BIDIR su__VIEW_IMPL_BIDIR_CONST su__VIEW_IMPL_BIDIR_ASSOC su__VIEW_IMPL_RANDOM su__VIEW_IMPL_RANDOM_CONST su__VIEW_IMPL_RANDOM_ASSOC su__VIEW_IMPL_END // }}} // Cleanup {{{ #undef su__VIEW_DOWNCAST #undef su__VIEW_IMPL_START #undef su__VIEW_IMPL_NONCONST #undef su__VIEW_IMPL_CONST #undef su__VIEW_IMPL_NONASSOC #undef su__VIEW_IMPL_NONASSOC_NONCONST #undef su__VIEW_IMPL_NONASSOC_CONST #undef su__VIEW_IMPL_ASSOC #undef su__VIEW_IMPL_ASSOC_NONCONST #undef su__VIEW_IMPL_ASSOC_CONST #undef su__VIEW_IMPL_UNIDIR #undef su__VIEW_IMPL_UNIDIR_NONCONST #undef su__VIEW_IMPL_UNIDIR_CONST #undef su__VIEW_IMPL_UNIDIR_NONASSOC #undef su__VIEW_IMPL_UNIDIR_ASSOC #undef su__VIEW_IMPL_BIDIR #undef su__VIEW_IMPL_BIDIR_NONCONST #undef su__VIEW_IMPL_BIDIR_CONST #undef su__VIEW_IMPL_BIDIR_NONASSOC #undef su__VIEW_IMPL_BIDIR_ASSOC #undef su__VIEW_IMPL_RANDOM #undef su__VIEW_IMPL_RANDOM_NONCONST #undef su__VIEW_IMPL_RANDOM_CONST #undef su__VIEW_IMPL_RANDOM_NONASSOC #undef su__VIEW_IMPL_RANDOM_ASSOC #undef su__VIEW_IMPL_END #undef su__VIEW_CATEGORY #undef su__VIEW_TYPE #undef su__VIEW_NAME #undef su__VIEW_NAME_NONCONST // }}} NSPC_END(su) #include #endif /* !su_C_LANG || defined CXX_DOXYGEN */ #endif /* su_VIEW_H */ /* s-it-mode */ s-nail-14.9.15/make-emerge.sh000077500000000000000000000042021352610246600155770ustar00rootroot00000000000000#!/bin/sh - #@ Out-of-tree compilation support, à la #@ $ cd /tmp && mkdir build && cd build && #@ ~/src/nail.git/make-emerge.sh && make tangerine DESTDIR=.ddir # Public Domain ## Upon interest see mk/make-config.sh, the source of all this! # For heaven's sake auto-redirect on SunOS/Solaris if [ "x${SHELL}" = x ] || [ "${SHELL}" = /bin/sh ] && \ [ -f /usr/xpg4/bin/sh ] && [ -x /usr/xpg4/bin/sh ]; then SHELL=/usr/xpg4/bin/sh export SHELL exec /usr/xpg4/bin/sh "${0}" "${@}" fi [ -n "${SHELL}" ] || SHELL=/bin/sh export SHELL config_exit() { exit ${1} } syno() { if [ ${#} -gt 0 ]; then echo >&2 "ERROR: ${*}" echo >&2 fi echo >&2 'Synopsis: SOURCEDIR/make-emerge.sh [from within target directory]' exit 1 } oneslash() { ./makefile ${cp} "${topdir}"make.rc ./ ${cp} "${topdir}"mime.types ./ ${cp} "${topdir}"include/mx/gen-version.h include/mx/ set +e echo 'You should now be able to proceed as normal (e.g., "$ make all")' # s-sh-mode s-nail-14.9.15/make.rc000066400000000000000000000474301352610246600143360ustar00rootroot00000000000000#@ make.rc defines the set of features and values used. #@ Reading INSTALL first is beneficial. #@ #@ - Choosing a predefined CONFIG= disallows further option fine-tuning. #@ (With some exceptions, e.g., OPT_DEBUG.) #@ #@ - Specifying settings on the command line will take precedence over #@ the variables in here (correctly triggering build updates as #@ necessary, too). #@ #@ - Features / options have an OPT_ prefix and usually need to be #@ assigned a boolean value, as in OPT_IDNA=yes. Booleans are 1/0, #@ y/n, true/false, yes/no and on/off (case does not matter). #@ #@ The value "require" is also a true boolean, but will in addition #@ cause configuration to fail if the requested condition cannot be #@ satisfied. This functionality is available where documented. #@ #@ - Values have a VAL_ prefix, and are assigned strings, for example #@ VAL_PREFIX="/usr/local" #@ #@ Values which are only used during configuration, building, and / or #@ installation have no prefix, e.g., DESTDIR, VERBOSE, awk, sed etc. #@ Strings which contain whitespace need to be quoted. #@ #@ Some values offer "multiple choice" values, e.g., VAL_RANDOM #@ (without accompanying feature option) and VAL_IDNA (with OPT_IDNA). #@ They address the case of different implementations, and can then be #@ used to choose the desired one(s). #@ The value must be a comma-separated list of strings, for example #@ "idn2,idn,idnkit", the case is ignored, but the order is important. #@ The special strings "all" and "any" as well as the empty value #@ are wildcard matches; if any entry in the list is a wildcard match, #@ the entire list is ignored. #@ The special string "error" will abort configuration once its list #@ position is reached; this is only supported if documented, and not #@ with an accompanying OPT_ (which then offers "require", as above). #@ #@ - This file is parsed by the shell: it is in sh(1), not in make(1) #@ syntax. Evaluation occurs *after* it has been read, so command #@ line overwrites take effect. To use multiline values, escape the #@ newlines on all lines but the last with a reverse solidus (back- #@ slash), as in "LINE \". #@ To embed a shell variable unexpanded, use two: "XY=\\${HOME}". #@ The parsing is sequential top-to-bottom (nonetheless), so that #@ shell snippets in a value can refer only to stuff yet defined. #@ #@ - You may NOT comment out anything in here -- if you want to disable #@ a feature, set it to a false boolean. ## IDENTITIES, PATHS AND PROGRAMS ## # Contact info (end up as the INTERNAL VARIABLES *contact-mail* and # *contact-web*, respectively). VAL_CONTACT_MAIL=s-mailx@lists.sdaoden.eu VAL_CONTACT_WEB=https://www.sdaoden.eu/code.html # The user ID our small privilege-separated dotlock helper program will # be SETUID to, shall it be included ($OPT_DOTLOCK). # Installation will then require the chown(1) program (as below) and # sufficient privileges to perform a SETUID to this user ID. VAL_PS_DOTLOCK_USER=root # General prefix where S-nail should be installed. VAL_PREFIX=/usr/local # Fine tune individual locations, normally under $VAL_PREFIX. # . the place of the S-nail program. VAL_BINDIR=${VAL_PREFIX}/bin # . the place of privilege-separated helpers, names of which are # ${VAL_SID}${VAL_MAILX}-foo ($VAL_SID and $VAL_MAILX are below). # (Only with: $OPT_DOTLOCK.) VAL_LIBEXECDIR=${VAL_PREFIX}/libexec # . of the manual. VAL_MANDIR=${VAL_PREFIX}/share/man # . of the exemplary resource file. VAL_SYSCONFDIR=${VAL_PREFIX}/etc # The variable $DESTDIR is prepended to all the paths from above at # installation time; this feature can be used for, e.g., package # building: if $VAL_PREFIX is "/usr/local", but $DESTDIR is set to "here", # then S-nail will still think its $VAL_PREFIX is "/usr/local" whereas the # build system will instead use "here/usr/local". # NOTE: it cannot be set in here, but must be given on the command line # when invoking the "install" make(1) rule (directly or indirectly). # (That is, if you uncomment it, it will be update-tracked.) #DESTDIR= # Where the local mail system stores user $MAIL files. VAL_MAIL=`\ if [ -d /var/spool/mail ]; then \ echo /var/spool/mail;\ else \ echo /var/mail;\ fi` # Path to the local MTA (Mail-Transfer-Agent). # MTA aliases (aliases(5)) are optionally supported via OPT_MTA_ALIASES. VAL_MTA=`\ if [ -x /usr/bin/sendmail ]; then \ echo /usr/bin/sendmail;\ elif [ -x /usr/lib/sendmail ]; then \ echo /usr/lib/sendmail;\ else \ echo /usr/sbin/sendmail;\ fi` # Today a lot of systems no longer use sendmail(1), but a different MTA. # To ensure compatibility with sendmail(1), a system called # mailwrapper(8) is often used, which selects the required service by # looking at the name by which the program actually has been invoked. # This variable can be used to adjust this name as necessary. VAL_MTA_ARGV0=sendmail # Default $SHELL (sh(1) path). # Sometimes we simply invoke a command directly via execlp(3) instead of # indirectly through *SHELL* -- in these cases execlp(3) may fallback to # it's own built-in sh(1) path. VAL_SHELL=/bin/sh # Some more default fallback values, some of which are standardized # and (thus)/or documented in the manual (changes not reflected there!). # Note that default paths are often not (shell) quoted when displayed. VAL_DEAD_BASENAME=dead.letter VAL_DEAD=~/${VAL_DEAD_BASENAME} VAL_EDITOR=ed VAL_LISTER=ls VAL_MAILRC=~/.mailrc VAL_MBOX=~/mbox VAL_NETRC=~/.netrc VAL_PAGER=more VAL_TMPDIR=/tmp VAL_VISUAL=vi # Default locations of mime.types(5). VAL_MIME_TYPES_USR=~/.mime.types VAL_MIME_TYPES_SYS=/etc/mime.types # Default screen width. The width is reduced by 1 for compatibility # with some old terminals; if termcap/terminfo support is available then # we will use the full width if possible. # Values must be decimal numbers, SHOULD > 10, MUST < 1000. VAL_HEIGHT=24 VAL_WIDTH=80 # The following tools may be provided a.k.a. overwritten, # `command -v NAME` is used to query the utility otherwise: # STRIP=, awk=, basename=, cat=, chmod=, cp=, cmp=, cksum=, getconf= # grep=, ln=, mkdir=, mv=, pwd=, rm=, sed=, sort=, tee=, tr=, uname= # Usually in administrator paths: # chown= [$OPT_DOTLOCK] # Note that awk(1), rm(1), tr(1) and uname(1) are needed before this # file is read, all other utilities will be checked afterwards only. # uname(1) is in fact needed before the initial OS setup and thus no OS # specific adjustments (e.g., $PATH) have been performed yet, but its # use can be circumvented by setting $OS (uname -s) and $OSFULLSPEC # (uname -a: this is not baked into the binary, it is only used to # recognize build environment changes). # Due to the evaluation order of the build system all those programs are # usually needed, but by setting any of the variables to true(1), as in # chown=/usr/bin/true, availability of unneeded programs can be faked. ## FEATURE SET ## # Some operating systems only support the C/POSIX (7-bit, but eight bit # bytes are passed through unchanged) and UTF-8 based locales, e.g., # Plan9, Musl based Linux variants and newer OpenBSD. For such # environments we can avoid a lot of tests and may enable support for # features which would otherwise not be available. # Note: $OS is available as normalized all-lowercase upon evaluation. OPT_ALWAYS_UNICODE_LOCALE=`\ if [ "${OS}" = openbsd ] || [ -f /lib/ld-musl-x86_64.so.1 ]; then \ echo yes;\ else \ echo no;\ fi` # It is possible to compile S-nail as a "single-source", meaning that # all source files are injected into a single compilation unit, which is # then compiled. This allows the compiler to perform more # optimizations, and also reduces the management overhead that is used # for / needed by the linker. In theory. # (Note this meant as a pure optimization, the make(1)file system will # offer no source dependeny tracking in this mode.) OPT_AMALGAMATION=no # Shall S-nail try to automatically detect a compiler and detect and # provide a set of known-good compiler flags? It will use $CC if this # variable is set, otherwise a compiler is actively searched for. # If this option is chosen additions to flags may still be provided # by setting $EXTRA_CFLAGS and $EXTRA_LDFLAGS to whatever is desired. # Thus: set this to false and use your normal $CC / $CFLAGS / $LDFLAGS, # otherwise pass additional flags via $EXTRA_CFLAGS / $EXTRA_LDFLAGS: # $ make EXTRA_CFLAGS=-std=c99 tangerine # Whatever you do, the configuration is fixated and updates will force # rebuilds. And far below in this file there is $OPT_FORCED_STACKPROT, # too, which can be used to cause injection of stack protectors, but # which normally happens for development and debug builds only. # (Remember, file is parsed from top to bottom, sorry.) OPT_AUTOCC=yes # Whether the commands `csop', `vexpr' .. shall be included. # v15compat: until v15 VEXPR needs CSOP. OPT_CMD_CSOP=yes OPT_CMD_VEXPR=yes # A simple form of coloured output can optionally be produced. OPT_COLOUR=yes # For cross-compilation purposes it may be useful to not actually run # systemcall etc. tests (link and run the executable) but only to # perform the link tests necessary to detect host environment. OPT_CROSS_BUILD=no # We may include `help' etc. strings for commands, increasing size a bit. OPT_DOCSTRINGS=yes # File dotlocking is performed for "system mailbox" (%[USER] and # %:ANYFILE) MBOX files: when synchronizing any such FILE a FILE.lock # file will be created in the directory of FILE, for the duration of the # synchronization: set $OPT_DOTLOCK to support this traditional mail # spool file locking. # $VAL_MAIL(s) where normal system mailboxes reside are usually not # writable by normal users, except that a user may read and write his # own mailbox. But this means that a program run by the user cannot # create a .lock file! The solution is to install a privilege-separated # mini-program that has the sole purpose and functionality of managing # the dotlock file in such situations -- and only then, as a last # ressort. With it dotlock files can be created for any mailbox for # which the invoking user has read (or read-write) permissions, and # under the UID and GID of the mailbox itself! We call it -dotlock. # It will be SETUID to VAL_PS_DOTLOCK_USER, as above. # $OPT_DOTLOCK can be "require"d. OPT_DOTLOCK=yes # Enable the `errors' command; S-nail is a console-based application and # thus errors may fly by pretty fast as other operations are in # progress; or $PAGERs are started and clear errors off the screen. # If enabled errors will be duplicated to an error queue as they happen # and the `errors' command will show them when asked to. OPT_ERRORS=yes # We do have a very primitive HTML tagsoup filter which can be used to # convert HTML to plain text for display purposes. If enabled it will # be used for all MIME types which have the corresponding type marker # (more on this in the manual section "The mime.types files"). And # which do not have any user defined MIME type handler, of course. OPT_FILTER_HTML_TAGSOUP=yes # A simple line-based quoting mechanism can be made available via the # *quote-fold* mechanism. This will be turned off automatically if the # required character classification is not available on the host. # Can be "require"d. # TODO should not wrap lines when only WS or a NL-escaping \ follows OPT_FILTER_QUOTE_FOLD=yes # Character set conversion enables reading and sending of mails in # multiple character sets through usage of the iconv(3) library. Please # read the manual section "Character sets" for the complete picture. # This should usually be enabled; it can be "require"d. OPT_ICONV=yes # IDNA (internationalized domain names for applications) offers users # the possibility to use domain names in their native language, i.e., to # use non-US-ASCII content, as in, e.g., , # which the IDNA algorithm would convert to # . :) # Multiple implementations are supported: # . idnkit - idnkit, https://www.nic.ad.jp/ja/idn/idnkit/download, # either of version 1 for IDNA 2003 or version 2 for IDNA 2008. # . idn2 - GNU Libidn2 for IDNA 2008, https://www.gnu.org/software/libidn/, # . idn - GNU Libidn for IDNA 2003, same, # OPT_IDNA can be "require"d. OPT_IDNA=yes VAL_IDNA=idnkit,idn2,idn # IMAP-style SEARCH expressions can be supported. This addressing mode # is available with all types of folders; for folders not located on # IMAP servers, or for servers unable to execute the SEARCH command, the # search is performed locally. OPT_IMAP_SEARCH=yes # Whether support for Maildir E-mail directories shall be enabled. OPT_MAILDIR=yes # Line editing and -history (manual "On terminal and line editor"). # If ISO C (ISO/IEC 9899:1990/Amendment 1:1995) is supported on the # system then our built-in MLE (Mailx-Line-Editor) version can be used. # An enabled & available OPT_TERMCAP may affect and improve the MLE. # Can be "require"d. OPT_MLE=yes # Add support for `history' management. OPT_HISTORY=yes # Add support for `(un)?bind'ing of key sequences. OPT_KEY_BINDINGS=yes # Use termcap(5) for MLE terminal control; can be "require"d. # Today most environments ship a termcap(5) that in fact is part of # terminfo(5), and acts as a converting wrapper for this library. # To avoid this redundancy we also support terminfo(5), and use it # instead if we find it (assuming that termcap(5) is a stub, then). # Note that terminfo(5) offers access to more key sequences, e.g., # kLFT5, for which no termcap(5) entry exists. # terminfo(5) support can (thus) be "require"d. OPT_TERMCAP=yes OPT_TERMCAP_VIA_TERMINFO=yes # Whether support for MTA aliases(5) shall be included. # With it, and if *mta-aliases* is set to a file, MTA aliases will be # expanded as a last step before messages are sent. # Note this only supports text-, no database files; expansion only # supports (names and) address values, /file/name and |command values # are ignored (*verbose* logs). TODO :include:/file/name. OPT_MTA_ALIASES=yes # Several different P(seudo) R(andom number) G(enerator) possibilities. # No need for somewhat strong random is necessary. # The following will be used as sole PRG implementations. # . arc4 - we search for arc4random(3). # . tls - if OPT_TLS is available the PRG of the TLS library can be used. # The following will only be used to seed our builtin ARC4 PRG. # . libgetrandom - getrandom(3) via C library. # . sysgetrandom - getrandom(2) via SYSCALL. # . urandom - reading from /dev/urandom. # . builtin - unscientific seeding for our builtin ARC4 implementation. # . (error - bail out) VAL_RANDOM=arc4,tls,libgetrandom,sysgetrandom,urandom,builtin # Regular expression (re_format(7)) support for searches, conditional # expressions etc., we use the extended ones, then; can be "require"d. OPT_REGEX=yes # Major switch to toggle *all* network related protocols # (POP3,SMTP,IMAP) and related/dependent stuff (GSS-API,TLS); # can be "require"d. OPT_NET=yes # Detect support for GSS-API (Generic Security Services Application # Programming Interface) based authentication, e.g., Kerberos v5? # Available for IMAP and SMTP; can be "require"d. OPT_GSSAPI=yes # Add support for IMAP protocol. # Reading of mails directly on the server. # Requires $OPT_ICONV unless $OPT_ALWAYS_UNICODE_LOCALE, in which # case it only warns. Can be "require"d. OPT_IMAP=yes # The MD5 message digest (RFC 1321) enables several authentication # possibilities for POP3 (APOP), IMAP and SMTP (CRAM-MD5). OPT_MD5=yes # Support for parsing of user and password credentials from the # ~/.netrc file ($NETRC; see *netrc-lookup* manual entry). OPT_NETRC=yes # Add support for POP3 protocol. # Download of mails via POP protocol. Can be "require"d. OPT_POP3=yes # Add support for SMTP (and SUBMISSION) protocol. # Sending mails directly over the network. Can be "require"d. OPT_SMTP=yes # Detect support for Secure Socket Layer (Transport Layer Security, # TLS), i.e., encrypted socket connections; can be "require"d. # It also automatically enables support for S/MIME message signing, # verification, en- and decryption. # Supported are the OpenSSL (https://www.openssl.org) and LibreSSL # (http://www.libressl.org) libraries. OPT_TLS=yes # If $OPT_TLS: shall mechanisms to support more digest and cipher # algorithms than the few that are documented be used? # For S/MIME *smime-cipher* for example this will cause # EVP_get_cipherbyname(3) to be tried shall the built-in knowledge # not suffice to understand the user request. # Will create a large statically linked binary; dynamically linked # the costs only arise once the extended lookup is actually needed # (the first time). Some TLS libraries will always support all # algorithms. This can be "require"d. OPT_TLS_ALL_ALGORITHMS=yes # Interaction with a spam email filter is possible. # Refer to all commands and variables with a "spam" prefix, and # see the manual example section "Handling spam". # . OPT_SPAM_FILTER: # Generic filter hook which can be used with e.g. bogofilter(1) # and sylfilter(1): see documentation for the *spam-filter-** # variables for expected application behaviour. # . OPT_SPAM_SPAMC: # Support for interaction with spamassassin(1)s spamc(1). OPT_SPAM_FILTER=yes OPT_SPAM_SPAMC=no # It is possible to strip out (almost) all user interface and error messages. # This might be interesting for automated use cases (only). OPT_UISTRINGS=yes # Whether package system, for example pkgsrc(7) on NetBSD and more, # OpenCSW on SunOS/Solaris, etc., specific paths shall be automatically # included in $C_INCLUDE_PATH and $LD_LIBRARY_PATH when seen? OPT_USE_PKGSYS=yes ## -- >8 -- 8< -- ## ## Normal users should not need to read any further ## PATHS AND PROGRAMS, DEVELOPMENT ## # To ease the life of forkers and packagers "our" name can be changed. # The name is build by concatenating $VAL_SID and $VAL_MAILX, i.e., # $(VAL_SID)$(VAL_MAILX). Note that the final string must be longer # than two characters and may not contain any whitespace. VAL_SID=s- VAL_MAILX=nail # The name of the exemplary resource template. # Note 1: not overwritten by "make install" if yet exists! VAL_SYSCONFRC=${VAL_SID}${VAL_MAILX}.rc ## FEATURE SET, DEVELOPMENT ## # With $OPT_AUTOCC we can make use of the ASan AddressSanitizer and ASan # MemorySanitizer of Google # (https://github.com/google/sanitizers/wiki/AddressSanitizer). # Also USAN (https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html). # These are definetely only useful for debugging. # Also, external libraries are often problematic (e.g., ncursesw), and # ASAN_MEMORY of the tried clang 4.0.0 (4.0.0-2 of ArchLinux) was faulty. # Can be "require"d. OPT_ASAN_ADDRESS=no OPT_ASAN_MEMORY=no OPT_USAN=no # Use debug compiler flags, enable some additional commands and code # assertions. Note that setting this also enables our own memory # canaries, which require a rather large amount of runtime memory. OPT_DEBUG=no # Development only. OPT_DEVEL=no # With $OPT_AUTOCC we will use stack protector guards shall the # detected compiler support them; this goes in line with our own (heap) # memory canaries and will detect buffer overflows. It is usually only # useful during development, i.e., in a debug environment that tests all # aspects of a program. But today it is often used even in shipout code. OPT_FORCED_STACKPROT=`\ if feat_yes DEVEL || feat_yes DEBUG; then \ echo yes;\ else \ echo no;\ fi` # We use the crypto libraries' MD5 implementation if possible, unless.. OPT_NOEXTMD5=no # If $OPT_DEBUG is true we'll use a simple memory wrapper with # canaries. This interferes with memory debuggers like valgrind(1) or # the LLVM -fsanitize stuff. Enable this to not use our wrapper. OPT_NOMEMDBG=`\ if feat_yes ASAN_MEMORY || feat_yes ASAN_ADDRESS; then \ echo yes;\ else \ echo no;\ fi` # vim:set tw=72: s-it-mode s-nail-14.9.15/makefile000066400000000000000000000055371352610246600145750ustar00rootroot00000000000000#@ Makefile for S-nail. #@ Adjustments have to be made in make.rc -- or on the command line. #@ See the file INSTALL if you need help. .PHONY: ohno tangerine citron \ all config build install uninstall clean distclean test \ devel odevel .NOTPARALLEL: .WAIT: # Luckily BSD make supports specifying this as target, too # These are targets of make-emerge.sh CWDDIR= TOPDIR= OBJDIR=.obj ohno: build tangerine: config .WAIT build .WAIT test .WAIT install citron: config .WAIT build .WAIT install all: config .WAIT build config: @$(_prego) build: @$(_prestop); LC_ALL=C $${MAKE} -f mk-config.mk all install packager-install: build @$(_prestop);\ LC_ALL=C $${MAKE} -f mk-config.mk DESTDIR="$(DESTDIR)" install uninstall: @$(_prestop); LC_ALL=C $${MAKE} -f mk-config.mk uninstall clean: @$(_prestop); LC_ALL=C $${MAKE} -f mk-config.mk clean distclean: @$(_prestop); LC_ALL=C $${MAKE} -f mk-config.mk distclean devel: @CONFIG=DEVEL; export CONFIG; $(_prego); $(_prestop);\ $${SHELL} "$${TOPDIR}"mk/make-version.sh create &&\ LC_ALL=C $${MAKE} -f mk-config.mk all odevel: @CONFIG=ODEVEL; export CONFIG; $(_prego); $(_prestop);\ $${SHELL} "$${TOPDIR}"mk/make-version.sh create &&\ LC_ALL=C $${MAKE} -f mk-config.mk all d-b: @$(_prestop);\ $${SHELL} "$${TOPDIR}"mk/make-version.sh create &&\ LC_ALL=C $${MAKE} -f mk-config.mk all d-v: @$(_prestop);\ $${SHELL} "$${TOPDIR}"mk/make-version.sh create # The test should inherit the user runtime environment! test: @$(__prestop); cd "$(OBJDIR)" && LC_ALL=C $(MAKE) -f mk-config.mk test testnj: @$(__prestop); cd "$(OBJDIR)" &&\ LC_ALL=C $(MAKE) -f mk-config.mk testnj d-okeys: perl mk/make-okey-map.pl d-okeys-nv: perl mk/make-okey-map.pl noverbose d-tcaps: perl mk/make-tcap-map.pl d-tcaps-nv: perl mk/make-tcap-map.pl noverbose d-errors: sh mk/su-make-errors.sh d-errors-nv: sh mk/su-make-errors.sh noverbose d-cs-ctype: sh mk/su-make-cs-ctype.sh d-cs-ctype-nv: sh mk/su-make-cs-ctype.sh noverbose d-dox: doxygen mk/su-doxygen.rc d-gettext: (cd src/mx &&\ LC_ALL=C xgettext --sort-by-file --strict --add-location \ --from-code=UTF-8 --keyword --keyword=_ --keyword=N_ \ --add-comments=I18N --foreign-user \ -o messages.pot *.c *.h) &&\ (cd src/mx &&\ LC_ALL=C xgettext --sort-by-file --strict --add-location \ --from-code=UTF-8 --keyword --keyword=_ --keyword=N_ \ --add-comments=I18N --foreign-user \ -o messages.pot *.h) _prego = if CWDDIR="$(CWDDIR)" TOPDIR="$(TOPDIR)" \ SHELL="$(SHELL)" MAKE="$(MAKE)" CC="$(CC)" \ CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" \ $(SHELL) "$(TOPDIR)"mk/make-config.sh "$(MAKEFLAGS)"; then :;\ else exit 1; fi __prestop = if [ -f "$(OBJDIR)"/mk-config.mk ]; then :; else \ echo 'Program not configured, nothing to do';\ echo 'Use one of the targets: config, all, tangerine, citron';\ exit 0;\ fi _prestop = $(__prestop); cd "$(OBJDIR)" && . ./mk-config.env # s-mk-mode s-nail-14.9.15/mime.types000066400000000000000000000106621352610246600151050ustar00rootroot00000000000000#@ mime.types(5) #@ This is the set of MIME types that will become compiled in. #@ Official MIME types are registered at and managed by IANA. #@ A more complete list of actually used MIME types can be found at #@ . #@ This adds some more, search EXTENSION comments below. #@ It also changes some TIKA things, search CHANGE comments below. # # Lines starting with a number sign (#) (after possible whitespace) are # comments and discarded, empty lines are ignored, remaining ones are # interpreted according to the scheme # [TYPEMARKER ]MIMETYPE whitespace EXTENSION(S, whitespace separated) # MIMETYPE may occur several times, values are joined. # # Syntax extensions: # - Follow lines may be used: instead of repeating MIMETYPE, start the next # line with whitespace. (Intervening comment lines may be used.) # - TYPEMARKER: a question mark (?) will tag the MIME type as plain text # (unless the user installs a specific handler for the type in question). # The meaning is identical to what is documented for command `mimetype'. # # Syntax restrictions: # - Comments must be lines of their own. # - Only MIME types for which we have a type constant allocated in # mx/mime-types.c:enum mime_type are allowed in this file here. # - MIME types without any file extensions are not handled. # - Note that the order of types and extensions is preserved ... # - Note that at least one built-in type is required (empty C array) # Expected frequent use # text/plain CHANGE: m4->text/x-m4, pod->text/x-pod; rc<- ? text/plain txt text conf cfg def list log in properties rc ? text/x-diff diff patch ? text/troff t tr roff man me ms tmac 1 2 3 4 5 6 7 8 9 application/pdf pdf ?h application/xhtml+xml xhtml xht ?h text/html html htm application/xml xml xsl xsd ? application/x-sh sh bash ? text/x-awk awk ? text/x-chdr h ? text/x-csrc c ? text/x-c++hdr hpp hxx hh h++ hp ? text/x-c++src cpp cxx cc c++ ? text/x-ini ini ? text/x-perl pl pm al perl # CHANGE (tika: text/plain) ? text/x-pod pod # EXTENSION .txz application/x-xz xz txz # EXTENSION application/x-lzma lzma tlz application/zstd zst tzst application/gzip tgz gz emz # EXTENSION application/x-lz4 lz4 application/x-tar tar application/x-bzip2 bz2 tbz2 boz # EXTENSIONs application/x-lzip lz application/zip zip application/pgp-encrypted pgp ?q application/pgp-signature sig asc application/pkcs7-mime p7m p7c ?q application/pkcs7-signature p7s application/postscript ps eps epsf epsi image/gif gif image/jp2 jp2 image/jpeg jpg jpeg jpe jif jfif jfi image/png png application/cbor cbor # Remains alphabetically ? application/javascript js ? application/json json ? application/mbox mbox application/rdf+xml rdf owl application/rss+xml rss ? application/sql sql application/x-bcpio bcpio application/x-bittorrent torrent application/x-bzip bz tbz application/x-compress z application/x-cpio cpio ? application/x-csh csh tcsh application/x-dvi dvi ? application/x-latex latex application/x-shar shar ? application/x-tex tex ? application/x-texinfo texinfo texi application/x-x509-ca-cert der crt application/x-xfig fig application/xml-dtd dtd application/xquery xq xquery application/xslt+xml xslt audio/basic au snd audio/midi mid midi kar rmi audio/mp4 mp4a m4a m4b audio/mpeg mpga mp2 mp2a mp3 m2a m3a audio/ogg oga ogg audio/speex spx audio/x-flac flac audio/x-mpegurl m3u audio/x-ms-wma wma audio/x-pn-realaudio ram ra audio/x-wav wav image/svg+xml svg svgz image/tiff tiff tif image/x-ms-bmp bmp dib image/x-rgb rgb image/x-xbitmap xbm image/x-xpixmap xpm image/x-xwindowdump xwd message/rfc822 eml mime mht mhtml ? text/calendar ics ifb ? text/css css ? text/csv csv ? text/sgml sgml sgm ? text/x-assembly s S asm # (Note that the tuple extensions will never match since text/plain wins!) ? text/x-asciidoc asciidoc adoc ad ad.txt adoc.txt ? text/x-csharp cs # (I have assembler .cgi's written myself, don't "? " mark that one) text/x-cgi cgi ? text/x-d d ? text/x-go go ? text/x-java java text/x-jsp jsp ? text/x-lex l # EXTENSION nim ? text/x-nim nim # CHANGE (tika: text/plain) ? text/x-m4 m4 ? text/x-objcsrc m ? text/x-php php php3 php4 ? text/x-python py ? text/x-rst rst rest restx ? text/x-ruby rb text/x-uuencode uu ? text/x-vcalendar vcs ? text/vcard vcf ? text/markdown md markdown ? text/x-yacc y ? text/x-yaml yaml # s-it-mode s-nail-14.9.15/mk/000077500000000000000000000000001352610246600134725ustar00rootroot00000000000000s-nail-14.9.15/mk/make-config.in000066400000000000000000000027011352610246600162020ustar00rootroot00000000000000#@ make-config.in -- mk-config.mk template, completed by make-config.sh. .PHONY: all install uninstall clean distclean SIZE_CHECK = @if ( command -v size ) > /dev/null 2>&1; then size $(@); fi all: $(VAL_UAGENT) $(OPTIONAL_PS_DOTLOCK) $(VAL_UAGENT): $(OBJ) uman.1 urc.rc $(ECHO_LINK)$(CC) $(LDFLAGS) -o $(@) $(OBJ) $(LIBS) $(SIZE_CHECK) $(VAL_PS_DOTLOCK): $(PS_DOTLOCK_C_OBJ) $(ECHO_LINK)$(CC) $(PS_DOTLOCK_LDFLAGS) -o $(@) $(PS_DOTLOCK_C_OBJ) \ $(PS_DOTLOCK_LIBS) $(SIZE_CHECK) gen-mime-types.h: $(TOPDIR)mime.types $(ECHO_GEN) . ./mk-config.env; \ $(SHELL) "$(TOPDIR)"mk/make-mime-types.sh "$(TOPDIR)"mime.types "$(@)" uman.1: $(TOPDIR)nail.1 $(ECHO_GEN) . ./mk-config.env; \ $(SHELL) "$(TOPDIR)"mk/make-man.sh "$(TOPDIR)"nail.1 "$(@)" urc.rc: $(TOPDIR)nail.rc $(ECHO_GEN) . ./mk-config.env; \ $(SHELL) "$(TOPDIR)"mk/make-rc.sh "$(TOPDIR)"nail.rc "$(@)" install: $(ECHO_CMD) . ./mk-config.env; \ $(SHELL) "$(TOPDIR)"mk/make-install.sh uninstall: @echo 'Invoke $(VAL_UAGENT)-uninstall.sh to uninstall $(VAL_UAGENT)' clean: $(ECHO_CMD)\ $(rm) -f $(OBJ) "$(VAL_UAGENT)" ./gen-mime-types.h ./uman.1 ./urc.rc;\ \ if [ -n "$(OPTIONAL_PS_DOTLOCK)" ]; then \ $(rm) -f $(PS_DOTLOCK_C_OBJ) "$(VAL_PS_DOTLOCK)";\ fi distclean: clean $(ECHO_CMD)\ $(rm) -f mk-*;\ cd .. && $(rm) -r "$(OBJDIR)";\ if [ -f "$(CWDDIR)include/mx/config.h" ]; then \ $(rm) -f "$(CWDDIR)include/mx/config.h" \ "$(CWDDIR)include/mx/gen-config.h";\ fi # s-mk-mode s-nail-14.9.15/mk/make-config.sh000066400000000000000000002726371352610246600162270ustar00rootroot00000000000000#!/bin/sh - #@ Please see INSTALL and make.rc instead. LC_ALL=C export LC_ALL # For heaven's sake auto-redirect on SunOS/Solaris if [ "x${SHELL}" = x ] || [ "${SHELL}" = /bin/sh ] && \ [ -f /usr/xpg4/bin/sh ] && [ -x /usr/xpg4/bin/sh ]; then SHELL=/usr/xpg4/bin/sh export SHELL exec /usr/xpg4/bin/sh "${0}" "${@}" fi [ -n "${SHELL}" ] || SHELL=/bin/sh export SHELL # The feature set, to be kept in sync with make.rc # If no documentation given, the option is used as such; if doc is a hyphen, # entry is suppressed when configuration overview is printed, and also in the # *features* string: most likely for obsolete features etc. XOPTIONS="\ CMD_CSOP='csop command: C-style string operations' \ CMD_VEXPR='vexpr command: evaluate arguments as expressions' \ COLOUR='Coloured message display' \ DOCSTRINGS='Command documentation help strings' \ DOTLOCK='Dotlock files and privilege-separated dotlock program' \ ERRORS='Log message ring' \ FILTER_HTML_TAGSOUP='Simple built-in HTML-to-text display filter' \ FILTER_QUOTE_FOLD='Extended *quote-fold*ing filter' \ ICONV='Character set conversion using iconv(3)' \ IDNA='Internationalized Domain Names for Applications (encode only)' \ IMAP_SEARCH='IMAP-style search expressions' \ MAILDIR='Maildir E-mail directories' \ MLE='Mailx Line Editor' \ HISTORY='Line editor history management' \ KEY_BINDINGS='Configurable key bindings' \ TERMCAP='Terminal capability queries (termcap(5))' \ TERMCAP_VIA_TERMINFO='Terminal capability queries use terminfo(5)' \ MTA_ALIASES='MTA aliases(5) (text file) support' \ REGEX='Regular expressions' \ NET='Network support' \ GSSAPI='Generic Security Service authentication' \ IMAP='IMAP v4r1 client' \ MD5='MD5 message digest (APOP, CRAM-MD5)' \ NETRC='.netrc file support' \ POP3='Post Office Protocol Version 3 client' \ SMTP='Simple Mail Transfer Protocol client' \ TLS='Transport Layer Security (OpenSSL / LibreSSL)' \ TLS_ALL_ALGORITHMS='Support of all digest and cipher algorithms' \ SPAM_FILTER='Freely configurable *spam-filter-..*s' \ SPAM_SPAMC='Spam management via spamc(1) of spamassassin(1)' \ UISTRINGS='User interface and error message strings' \ " # Options which are automatically deduced from host environment, i.e., these # need special treatment all around here to warp from/to OPT_ stuff # setlocale, C90AMEND1, NL_LANGINFO, wcwidth XOPTIONS_DETECT="\ LOCALES='Locale support - printable characters etc. depend on environment' \ MULTIBYTE_CHARSETS='Multibyte character sets' \ TERMINAL_CHARSET='Automatic detection of terminal character set' \ WIDE_GLYPHS='Wide glyph support' \ " # Rather special options, for custom building, or which always exist. # Mostly for generating the visual overview and the *features* string XOPTIONS_XTRA="\ CROSS_BUILD='Cross-compilation: trust any detected environment' \ DEBUG='Debug enabled binary, not for end-users: THANKS!' \ DEVEL='Computers do not blunder' \ MIME='Multipurpose Internet Mail Extensions' \ SMIME='S/MIME message signing, verification, en- and decryption' \ " # To avoid too many recompilations we use a two-stage "configuration changed" # detection, the first uses mk-config.env, which only goes for actual user # config settings etc. the second uses mk-config.h, which thus includes the # things we have truly detected. This does not work well for multiple choice # values of which only one will be really used, so those user wishes may not be # placed in the header, only the really detected one (but that has to!). # Used for grep(1), for portability assume fixed matching only. H_BLACKLIST='-e VAL_RANDOM -e VAL_IDNA' # The problem is that we don't have any tools we can use right now, so # encapsulate stuff in functions which get called in right order later on option_reset() { set -- ${OPTIONS} for i do eval OPT_${i}=0 done } option_maximal() { set -- ${OPTIONS} for i do eval OPT_${i}=1 done OPT_DOTLOCK=require OPT_ICONV=require OPT_REGEX=require } option_setup() { option_parse OPTIONS_DETECT "${XOPTIONS_DETECT}" option_parse OPTIONS "${XOPTIONS}" option_parse OPTIONS_XTRA "${XOPTIONS_XTRA}" OPT_MIME=1 # Predefined CONFIG= urations take precedence over anything else if [ -n "${CONFIG}" ]; then option_reset case "${CONFIG}" in [nN][uU][lL][lL]) ;; [nN][uU][lL][lL][iI]) OPT_ICONV=require OPT_UISTRINGS=1 ;; [mM][iI][nN][iI][mM][aA][lL]) OPT_CMD_CSOP=1 OPT_CMD_VEXPR=1 OPT_COLOUR=1 OPT_DOCSTRINGS=1 OPT_DOTLOCK=require OPT_ICONV=require OPT_REGEX=require OPT_ERRORS=1 OPT_IDNA=1 OPT_MAILDIR=1 OPT_MLE=1 OPT_HISTORY=1 OPT_KEY_BINDINGS=1 OPT_SPAM_FILTER=1 OPT_UISTRINGS=1 ;; [nN][eE][tT][sS][eE][nN][dD]) OPT_CMD_CSOP=1 OPT_CMD_VEXPR=1 OPT_COLOUR=1 OPT_DOCSTRINGS=1 OPT_DOTLOCK=require OPT_ICONV=require OPT_REGEX=require OPT_ERRORS=1 OPT_IDNA=1 OPT_MAILDIR=1 OPT_MLE=1 OPT_HISTORY=1 OPT_KEY_BINDINGS=1 OPT_MTA_ALIASES=1 OPT_NET=require OPT_GSSAPI=1 OPT_NETRC=1 OPT_SMTP=require OPT_TLS=require OPT_SPAM_FILTER=1 OPT_UISTRINGS=1 ;; [mM][aA][xX][iI][mM][aA][lL]) option_maximal ;; [dD][eE][vV][eE][lL]) option_maximal OPT_DEVEL=1 OPT_DEBUG=1 ;; [oO][dD][eE][vV][eE][lL]) option_maximal OPT_DEVEL=1 ;; *) echo >&2 "Unknown CONFIG= setting: ${CONFIG}" echo >&2 ' NULL, NULLI, MINIMAL, NETSEND, MAXIMAL' exit 1 ;; esac msg_nonl "CONFIG=${CONFIG} ... " fi } # Inter-relationships XXX sort this! option_update() { if feat_no TLS; then OPT_TLS_ALL_ALGORITHMS=0 fi if feat_no SMTP && feat_no POP3 && feat_no IMAP; then OPT_NET=0 fi if feat_no NET; then if feat_require IMAP; then msg 'ERROR: need NETwork for required feature IMAP' config_exit 13 fi if feat_require POP3; then msg 'ERROR: need NETwork for required feature POP3' config_exit 13 fi if feat_require SMTP; then msg 'ERROR: need NETwork for required feature SMTP' config_exit 13 fi OPT_GSSAPI=0 OPT_IMAP=0 OPT_MD5=0 OPT_NETRC=0 OPT_POP3=0 OPT_SMTP=0 OPT_TLS=0 OPT_TLS_ALL_ALGORITHMS=0 fi if feat_no SMTP && feat_no IMAP; then OPT_GSSAPI=0 fi if feat_no ICONV; then if feat_yes IMAP; then if feat_yes ALWAYS_UNICODE_LOCALE; then msg 'WARN: no ICONV, keeping IMAP due to ALWAYS_UNICODE_LOCALE!' elif feat_require IMAP; then msg 'ERROR: need ICONV for required feature IMAP' config_exit 13 else msg 'ERROR: disabling IMAP due to missing ICONV' OPT_IMAP=0 fi fi if feat_yes IDNA; then if feat_require IDNA; then msg 'ERROR: need ICONV for required feature IDNA' config_exit 13 fi msg 'ERROR: disabling IDNA due to missing ICONV' OPT_IDNA=0 fi fi if feat_no MLE; then OPT_HISTORY=0 OPT_KEY_BINDINGS=0 OPT_TERMCAP=0 OPT_TERMCAP_VIA_TERMINFO=0 elif feat_no TERMCAP; then OPT_TERMCAP_VIA_TERMINFO=0 fi } ## -- >8 - << OPTIONS | EARLY >> - 8< -- ## # Note that potential duplicates in PATH, C_INCLUDE_PATH etc. will be cleaned # via path_check() later on once possible COMMLINE="${*}" # which(1) not standardized, command(1) -v may return non-executable: unroll! SU_FIND_COMMAND_INCLUSION=1 . "${TOPDIR}"mk/su-find-command.sh # Also not standardized: a way to round-trip quote . "${TOPDIR}"mk/su-quote-rndtrip.sh ## -- >8 - << EARLY | OS/CC >> - 8< -- ## # TODO cc_maxopt is brute simple, we should compile test program and dig real # compiler versions for known compilers, then be more specific [ -n "${cc_maxopt}" ] || cc_maxopt=100 #cc_force_no_stackprot= #ld_need_R_flags= #ld_no_bind_now= #ld_rpath_not_runpath= _CFLAGS= _LDFLAGS= os_early_setup() { # We don't "have any utility" (see make.rc) [ -n "${OS}" ] && [ -n "${OSFULLSPEC}" ] || thecmd_testandset_fail uname uname [ -n "${OS}" ] || OS=`${uname} -s` export OS msg 'Operating system is %s' "${OS}" if [ ${OS} = SunOS ]; then # According to standards(5), this is what we need to do if [ -d /usr/xpg4 ]; then :; else msg 'ERROR: On SunOS / Solaris we need /usr/xpg4 environment! Sorry.' config_exit 1 fi PATH="/usr/xpg4/bin:/usr/ccs/bin:/usr/bin:${PATH}" [ -d /usr/xpg6 ] && PATH="/usr/xpg6/bin:${PATH}" export PATH fi } os_setup() { # OSFULLSPEC is used to recognize changes (i.e., machine type, updates # etc.), it is not baked into the binary [ -n "${OSFULLSPEC}" ] || OSFULLSPEC=`${uname} -a` if [ ${OS} = darwin ]; then msg ' . have special Darwin environmental addons...' LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${DYLD_LIBRARY_PATH} elif [ ${OS} = sunos ]; then msg ' . have special SunOS / Solaris "setup" rules ...' _os_setup_sunos elif [ ${OS} = unixware ]; then if feat_yes AUTOCC && acmd_set CC cc; then msg ' . have special UnixWare environmental rules ...' feat_yes DEBUG && _CFLAGS='-v -Xa -g' || _CFLAGS='-Xa -O' CFLAGS="${_CFLAGS} ${EXTRA_CFLAGS}" LDFLAGS="${_LDFLAGS} ${EXTRA_LDFLAGS}" export CC CFLAGS LDFLAGS OPT_AUTOCC=0 ld_need_R_flags=-R fi elif [ -n "${VERBOSE}" ]; then msg ' . no special treatment for this system necessary or known' fi # Sledgehammer: better set _GNU_SOURCE # And in general: oh, boy! OS_DEFINES="${OS_DEFINES}#define _GNU_SOURCE\n" #OS_DEFINES="${OS_DEFINES}#define _POSIX_C_SOURCE 200809L\n" #OS_DEFINES="${OS_DEFINES}#define _XOPEN_SOURCE 700\n" #[ ${OS} = darwin ] && OS_DEFINES="${OS_DEFINES}#define _DARWIN_C_SOURCE\n" # On pkgsrc(7) systems automatically add /usr/pkg/* if feat_yes USE_PKGSYS; then if [ -d /usr/pkg ]; then msg ' . found pkgsrc(7), merging C_INCLUDE_PATH and LD_LIBRARY_PATH' C_INCLUDE_PATH=/usr/pkg/include:${C_INCLUDE_PATH} LD_LIBRARY_PATH=/usr/pkg/lib:${LD_LIBRARY_PATH} ld_rpath_not_runpath=1 fi fi } _os_setup_sunos() { C_INCLUDE_PATH=/usr/xpg4/include:${C_INCLUDE_PATH} LD_LIBRARY_PATH=/usr/xpg4/lib:${LD_LIBRARY_PATH} # Include packages if feat_yes USE_PKGSYS; then if [ -d /opt/csw ]; then msg ' . found OpenCSW PKGSYS' C_INCLUDE_PATH=/opt/csw/include:${C_INCLUDE_PATH} LD_LIBRARY_PATH=/opt/csw/lib:${LD_LIBRARY_PATH} ld_no_bind_now=1 ld_rpath_not_runpath=1 fi if [ -d /opt/schily ]; then msg ' . found Schily PKGSYS' C_INCLUDE_PATH=/opt/schily/include:${C_INCLUDE_PATH} LD_LIBRARY_PATH=/opt/schily/lib:${LD_LIBRARY_PATH} ld_no_bind_now=1 ld_rpath_not_runpath=1 fi fi OS_DEFINES="${OS_DEFINES}#define __EXTENSIONS__\n" #OS_DEFINES="${OS_DEFINES}#define _POSIX_C_SOURCE 200112L\n" if feat_yes AUTOCC; then if acmd_set CC cc; then feat_yes DEBUG && _CFLAGS="-v -Xa -g" || _CFLAGS="-Xa -O" CFLAGS="${_CFLAGS} ${EXTRA_CFLAGS}" LDFLAGS="${_LDFLAGS} ${EXTRA_LDFLAGS}" export CC CFLAGS LDFLAGS OPT_AUTOCC=0 ld_need_R_flags=-R else cc_maxopt=2 cc_force_no_stackprot=1 fi fi } # Check out compiler ($CC) and -flags ($CFLAGS) cc_setup() { # Even though it belongs into cc_flags we will try to compile and link # something, so ensure we have a clean state regarding CFLAGS/LDFLAGS or # EXTRA_CFLAGS/EXTRA_LDFLAGS if feat_no AUTOCC; then _cc_default # Ensure those don't do any harm EXTRA_CFLAGS= EXTRA_LDFLAGS= export EXTRA_CFLAGS EXTRA_LDFLAGS return else CFLAGS= LDFLAGS= export CFLAGS LDFLAGS fi [ -n "${CC}" ] && { _cc_default; return; } msg_nonl 'Searching for a usable C compiler .. $CC=' if acmd_set CC clang || acmd_set CC gcc || acmd_set CC tcc || acmd_set CC pcc || acmd_set CC c89 || acmd_set CC c99; then : else msg 'boing booom tschak' msg 'ERROR: I cannot find a compiler!' msg ' Neither of clang(1), gcc(1), tcc(1), pcc(1), c89(1) and c99(1).' msg ' Please set ${CC} environment variable, maybe ${CFLAGS}, rerun.' config_exit 1 fi msg '%s' "${CC}" export CC } _cc_default() { if [ -z "${CC}" ]; then msg 'To go on like you have chosen, please set $CC, rerun.' config_exit 1 fi if [ -z "${VERBOSE}" ] && [ -f ${env} ] && feat_no DEBUG; then : else msg 'Using C compiler ${CC}=%s' "${CC}" fi } cc_create_testfile() { ${cat} > ${tmp}.c <<-\! #include #include static void doit(char const *s); int main(int argc, char **argv){ (void)argc; (void)argv; doit("Hello world"); return 0; } static void doit(char const *s){ char buf[12]; memcpy(buf, s, strlen(s) +1); puts(s); } ! } cc_hello() { [ -n "${cc_check_silent}" ] || msg_nonl ' . CC compiles "Hello world" .. ' if ${CC} ${INCS} ${CFLAGS} ${EXTRA_CFLAGS} ${LDFLAGS} ${EXTRA_LDFLAGS} \ -o ${tmp2} ${tmp}.c ${LIBS}; then [ -n "${cc_check_silent}" ] || msg 'yes' feat_yes CROSS_BUILD && return 0 [ -n "${cc_check_silent}" ] || msg_nonl ' . Compiled program works .. ' if ( [ "`\"${tmp2}\"`" = 'Hello world' ] ) >/dev/null 2>&1; then [ -n "${cc_check_silent}" ] || msg 'yes' return 0 fi fi [ -n "${cc_check_silent}" ] || msg 'no' msg 'ERROR: i cannot compile or run a "Hello world" via' msg ' %s' \ "${CC} ${INCS} ${CFLAGS} ${EXTRA_CFLAGS} ${LDFLAGS} ${EXTRA_LDFLAGS} ${LIBS}" msg 'ERROR: Please read INSTALL, rerun' config_exit 1 } cc_flags() { if feat_yes AUTOCC; then if [ -f ${env} ] && feat_no DEBUG && [ -z "${VERBOSE}" ]; then cc_check_silent=1 msg 'Detecting ${CFLAGS}/${LDFLAGS} for ${CC}=%s, just a second..' \ "${CC}" else cc_check_silent= msg 'Testing usable ${CFLAGS}/${LDFLAGS} for ${CC}=%s' "${CC}" fi i=`echo "${CC}" | ${awk} 'BEGIN{FS="/"}{print $NF}'` if { echo "${i}" | ${grep} tcc; } >/dev/null 2>&1; then msg ' . have special tcc(1) environmental rules ...' _cc_flags_tcc else # As of pcc CVS 2016-04-02, stack protection support is announced but # will break if used on Linux #if { echo "${i}" | ${grep} pcc; } >/dev/null 2>&1; then # cc_force_no_stackprot=1 #fi _cc_flags_generic fi feat_no DEBUG && _CFLAGS="-DNDEBUG ${_CFLAGS}" CFLAGS="${_CFLAGS} ${EXTRA_CFLAGS}" LDFLAGS="${_LDFLAGS} ${EXTRA_LDFLAGS}" else if feat_no DEBUG; then CFLAGS="-DNDEBUG ${CFLAGS}" fi fi export CFLAGS LDFLAGS } _cc_flags_tcc() { __cflags=${_CFLAGS} __ldflags=${_LDFLAGS} _CFLAGS= _LDFLAGS= cc_check -W cc_check -Wall cc_check -Wextra cc_check -pedantic if feat_yes DEBUG; then # May have problems to find libtcc cc_check -b cc_check -g fi if ld_check -Wl,-rpath =./ no; then ld_need_R_flags=-Wl,-rpath= if [ -z "${ld_rpath_not_runpath}" ]; then ld_check -Wl,--enable-new-dtags else msg ' ! $LD_LIBRARY_PATH adjusted, not trying --enable-new-dtags' fi ld_runtime_flags # update! fi _CFLAGS="${_CFLAGS} ${__cflags}" _LDFLAGS="${_LDFLAGS} ${__ldflags}" unset __cflags __ldflags } _cc_flags_generic() { __cflags=${_CFLAGS} __ldflags=${_LDFLAGS} _CFLAGS= _LDFLAGS= feat_yes DEVEL && cc_check -std=c89 || cc_check -std=c99 # E.g., valgrind does not work well with high optimization if [ ${cc_maxopt} -gt 1 ] && feat_yes NOMEMDBG && feat_no ASAN_ADDRESS && feat_no ASAN_MEMORY; then msg ' ! OPT_NOMEMDBG, setting cc_maxopt=1 (-O1)' cc_maxopt=1 fi # Check -g first since some others may rely upon -g / optim. level if feat_yes DEBUG; then cc_check -O cc_check -g elif [ ${cc_maxopt} -gt 2 ] && cc_check -O3; then : elif [ ${cc_maxopt} -gt 1 ] && cc_check -O2; then : elif [ ${cc_maxopt} -gt 0 ] && cc_check -O1; then : else cc_check -O fi if feat_yes AMALGAMATION; then cc_check -pipe fi #if feat_yes DEVEL && cc_check -Weverything; then # : #else cc_check -W cc_check -Wall cc_check -Wextra if feat_yes DEVEL; then cc_check -Wbad-function-cast cc_check -Wcast-align cc_check -Wcast-qual cc_check -Winit-self cc_check -Wmissing-prototypes cc_check -Wshadow cc_check -Wunused cc_check -Wwrite-strings cc_check -Wno-long-long fi #fi cc_check -pedantic if feat_no DEVEL; then if feat_yes AMALGAMATION; then cc_check -Wno-unused-function fi if cc_check -Wno-uninitialized; then :; else cc_check -Wno-maybe-uninitialized fi cc_check -Wno-unused-result cc_check -Wno-unused-value fi cc_check -fno-unwind-tables cc_check -fno-asynchronous-unwind-tables cc_check -fstrict-aliasing if cc_check -fstrict-overflow && feat_yes DEVEL; then cc_check -Wstrict-overflow=5 fi if feat_yes DEBUG || feat_yes FORCED_STACKPROT; then if [ -z "${cc_force_no_stackprot}" ]; then if cc_check -fstack-protector-strong || cc_check -fstack-protector-all; then cc_check -D_FORTIFY_SOURCE=2 fi else msg ' ! Not checking for -fstack-protector compiler option,' msg ' ! since that caused errors in a "similar" configuration.' msg ' ! You may turn off OPT_AUTOCC and use your own settings, rerun' fi fi # LD (+ dependend CC) if feat_yes ASAN_ADDRESS; then _ccfg=${_CFLAGS} if cc_check -fsanitize=address && ld_check -fsanitize=address; then : else feat_bail_required ASAN_ADDRESS _CFLAGS=${_ccfg} fi fi if feat_yes ASAN_MEMORY; then _ccfg=${_CFLAGS} if cc_check -fsanitize=memory && ld_check -fsanitize=memory && cc_check -fsanitize-memory-track-origins=2 && ld_check -fsanitize-memory-track-origins=2; then : else feat_bail_required ASAN_MEMORY _CFLAGS=${_ccfg} fi fi if feat_yes USAN; then _ccfg=${_CFLAGS} if cc_check -fsanitize=undefined && ld_check -fsanitize=undefined; then : else feat_bail_required USAN _CFLAGS=${_ccfg} fi fi ld_check -Wl,-z,relro if [ -z "${ld_no_bind_now}" ]; then ld_check -Wl,-z,now else msg ' ! $LD_LIBRARY_PATH adjusted, not trying -Wl,-z,now' fi ld_check -Wl,-z,noexecstack ld_check -Wl,--as-needed if ld_check -Wl,-rpath =./ no; then ld_need_R_flags=-Wl,-rpath= # Choose DT_RUNPATH (after $LD_LIBRARY_PATH) over DT_RPATH (before) if [ -z "${ld_rpath_not_runpath}" ]; then ld_check -Wl,--enable-new-dtags else msg ' ! $LD_LIBRARY_PATH adjusted, not trying --enable-new-dtags' fi ld_runtime_flags # update! elif ld_check -Wl,-R ./ no; then ld_need_R_flags=-Wl,-R if [ -z "${ld_rpath_not_runpath}" ]; then ld_check -Wl,--enable-new-dtags else msg ' ! $LD_LIBRARY_PATH adjusted, not trying --enable-new-dtags' fi ld_runtime_flags # update! fi # Address randomization _ccfg=${_CFLAGS} if cc_check -fPIE || cc_check -fpie; then ld_check -pie || _CFLAGS=${_ccfg} fi unset _ccfg # Retpoline (xxx maybe later?) # _ccfg=${_CFLAGS} _i= # if cc_check -mfunction-return=thunk; then # if cc_check -mindirect-branch=thunk; then # _i=1 # fi # elif cc_check -mretpoline; then # _i=1 # fi # if [ -n "${_i}" ]; then # ld_check -Wl,-z,retpolineplt || _i= # fi # [ -n "${_i}" ] || _CFLAGS=${_ccfg} # unset _ccfg _CFLAGS="${_CFLAGS} ${__cflags}" _LDFLAGS="${_LDFLAGS} ${__ldflags}" unset __cflags __ldflags } ## -- >8 - <> - 8< -- ## ## Notes: ## - Heirloom sh(1) (and same origin) have _sometimes_ problems with ': >' ## redirection, so use "printf '' >" instead ## Very first: we undergo several states regarding I/O redirection etc., ## but need to deal with option updates from within all. Since all the ## option stuff should be above the scissor line, define utility functions ## and redefine them as necessary. ## And, since we have those functions, simply use them for whatever t1=ten10one1ten10one1 if ( [ ${t1##*ten10} = one1 ] && [ ${t1#*ten10} = one1ten10one1 ] && [ ${t1%%one1*} = ten10 ] && [ ${t1%one1*} = ten10one1ten10 ] ) > /dev/null 2>&1; then good_shell=1 else unset good_shell fi unset t1 ( set -o noglob ) >/dev/null 2>&1 && noglob_shell=1 || unset noglob_shell config_exit() { exit ${1} } msg() { fmt=${1} shift printf >&2 -- "${fmt}\n" "${@}" } msg_nonl() { fmt=${1} shift printf >&2 -- "${fmt}" "${@}" } # Our feature check environment _feats_eval_done=0 _feat_val_no() { [ "x${1}" = x0 ] || [ "x${1}" = xn ] || [ "x${1}" = xfalse ] || [ "x${1}" = xno ] || [ "x${1}" = xoff ] } _feat_val_yes() { [ "x${1}" = x1 ] || [ "x${1}" = xy ] || [ "x${1}" = xtrue ] || [ "x${1}" = xyes ] || [ "x${1}" = xon ] || [ "x${1}" = xrequire ] } _feat_val_require() { [ "x${1}" = xrequire ] } _feat_check() { eval _fc_i=\$OPT_${1} if [ "$_feats_eval_done" = 1 ]; then [ "x${_fc_i}" = x0 ] && return 1 return 0 fi _fc_i="`echo ${_fc_i} | ${tr} '[A-Z]' '[a-z]'`" if _feat_val_no "${_fc_i}"; then return 1 elif _feat_val_yes "${_fc_i}"; then return 0 else msg "ERROR: %s: 0/n/false/no/off or 1/y/true/yes/on/require, got: %s" \ "${1}" "${_fc_i}" config_exit 11 fi } feat_yes() { _feat_check ${1} } feat_no() { _feat_check ${1} && return 1 return 0 } feat_require() { eval _fr_i=\$OPT_${1} _fr_i="`echo ${_fr_i} | ${tr} '[A-Z]' '[a-z]'`" [ "x${_fr_i}" = xrequire ] || [ "x${_fr_i}" = xrequired ] } feat_bail_required() { if feat_require ${1}; then msg 'ERROR: feature OPT_%s is required but not available' "${1}" config_exit 13 fi feat_is_unsupported "${1}" } feat_is_disabled() { [ ${#} -eq 1 ] && msg ' . (disabled: OPT_%s)' "${1}" echo "/* OPT_${1} -> mx_HAVE_${1} */" >> ${h} } feat_is_unsupported() { msg ' ! NOTICE: unsupported: OPT_%s' "${1}" echo "/* OPT_${1} -> mx_HAVE_${1} */" >> ${h} eval OPT_${1}=0 option_update # XXX this is rather useless here (dependency chain..) } feat_def() { if feat_yes ${1}; then [ -n "${VERBOSE}" ] && msg ' . %s ... yes' "${1}" echo '#define mx_HAVE_'${1}'' >> ${h} return 0 else feat_is_disabled "${@}" return 1 fi } option_parse() { # Parse one of our XOPTIONS* in $2 and assign the sh(1) compatible list of # options, without documentation, to $1 j=\' i="`${awk} -v input=\"${2}\" ' BEGIN{ for(i = 0;;){ voff = match(input, /[0-9a-zA-Z_]+(='${j}'[^'${j}']+)?/) if(voff == 0) break v = substr(input, voff, RLENGTH) input = substr(input, voff + RLENGTH) doff = index(v, "=") if(doff > 0){ d = substr(v, doff + 2, length(v) - doff - 1) v = substr(v, 1, doff - 1) } print v } } '`" eval ${1}=\"${i}\" } option_doc_of() { # Return the "documentation string" for option $1, itself if none such j=\' ${awk} -v want="${1}" \ -v input="${XOPTIONS_DETECT}${XOPTIONS}${XOPTIONS_XTRA}" ' BEGIN{ for(;;){ voff = match(input, /[0-9a-zA-Z_]+(='${j}'[^'${j}']+)?/) if(voff == 0) break v = substr(input, voff, RLENGTH) input = substr(input, voff + RLENGTH) doff = index(v, "=") if(doff > 0){ d = substr(v, doff + 2, length(v) - doff - 1) v = substr(v, 1, doff - 1) }else d = v if(v == want){ if(d != "-") print d exit } } } ' } option_join_rc() { # Join the values from make.rc into what currently is defined, not # overwriting yet existing settings ${rm} -f ${tmp} # We want read(1) to perform reverse solidus escaping in order to be able to # use multiline values in make.rc; the resulting sh(1)/sed(1) code was very # slow in VMs (see [fa2e248]), Aharon Robbins suggested the following < ${rc} ${awk} 'BEGIN{line = ""}{ sub(/^[ ]+/, "", $0) sub(/[ ]+$/, "", $0) if(sub(/\\$/, "", $0)){ line = line $0 next }else line = line $0 if(index(line, "#") == 1){ line = "" }else if(length(line)){ print line line = "" } }' | while read line; do if [ -n "${good_shell}" ]; then i=${line%%=*} else i=`${awk} -v LINE="${line}" 'BEGIN{ sub(/=.*$/, "", LINE) print LINE }'` fi if [ "${i}" = "${line}" ]; then msg 'ERROR: invalid syntax in: %s' "${line}" continue fi eval j="\$${i}" jx="\${${i}+x}" if [ -n "${j}" ] || [ "${jx}" = x ]; then : # Yet present else j=`${awk} -v LINE="${line}" 'BEGIN{ sub(/^[^=]*=/, "", LINE) sub(/^"*/, "", LINE) sub(/"*$/, "", LINE) gsub(/"/, "\\\\\"", LINE) print LINE }'` fi [ "${i}" = "DESTDIR" ] && continue [ "${i}" = "OBJDIR" ] && continue echo "${i}=\"${j}\"" done > ${tmp} # Reread the mixed version right now . ${tmp} } option_evaluate() { # Expand the option values, which may contain shell snippets # Set booleans to 0 or 1, or require, set _feats_eval_done=1 ${rm} -f ${newenv} ${newmk} exec 5<&0 6>&1 <${tmp} >${newenv} while read line; do z= if [ -n "${good_shell}" ]; then i=${line%%=*} [ "${i}" != "${i#OPT_}" ] && z=1 else i=`${awk} -v LINE="${line}" 'BEGIN{ gsub(/=.*$/, "", LINE);\ print LINE }'` if echo "${i}" | ${grep} '^OPT_' >/dev/null 2>&1; then z=1 fi fi eval j=\$${i} if [ -n "${z}" ]; then j="`echo ${j} | ${tr} '[A-Z]' '[a-z]'`" if [ -z "${j}" ] || _feat_val_no "${j}"; then j=0 printf " /* #undef ${i} */\n" >> ${newh} elif _feat_val_yes "${j}"; then if _feat_val_require "${j}"; then j=require else j=1 fi printf " /* #define ${i} */\n" >> ${newh} else msg 'ERROR: cannot parse <%s>' "${line}" config_exit 1 fi elif { echo ${i} | ${grep} ${H_BLACKLIST} >/dev/null 2>&1; }; then : else printf "#define ${i} \"${j}\"\n" >> ${newh} fi printf -- "${i} = ${j}\n" >> ${newmk} printf -- "${i}=%s;export ${i}; " "`quote_string ${j}`" eval "${i}=\"${j}\"" done exec 0<&5 1>&6 5<&- 6<&- _feats_eval_done=1 } val_allof() { eval __expo__=\$${1} ${awk} -v HEAP="${2}" -v USER="${__expo__}" ' BEGIN{ i = split(HEAP, ha, /[, ]/) if((j = split(USER, ua, /[, ]/)) == 0) exit for(; j != 0; --j){ us = tolower(ua[j]) if(us == "all" || us == "any") continue ok = 0 for(ii = i; ii != 0; --ii) if(tolower(ha[ii]) == us){ ok = 1 break } if(!ok) exit 1 } } ' __rv__=${?} [ ${__rv__} -ne 0 ] && return ${__rv__} if ${awk} -v USER="${__expo__}" ' BEGIN{ if((j = split(USER, ua, /[, ]/)) == 0) exit for(; j != 0; --j){ us = tolower(ua[j]) if(us == "all" || us == "any") exit 0 } exit 1 } '; then eval "${1}"=\"${2}\" else # Enfore lowercase also in otherwise unchanged user value.. eval "${1}"=\""`echo ${__expo__} | ${tr} '[A-Z]_' '[a-z]-'`"\" fi return 0 } oneslash() { /dev/null 2>&1 return $? } path_check() { # "path_check VARNAME" or "path_check VARNAME FLAG VARNAME" varname=${1} addflag=${2} flagvarname=${3} j=${IFS} IFS=: [ -n "${noglob_shell}" ] && set -o noglob eval "set -- \$${1}" [ -n "${noglob_shell}" ] && set +o noglob IFS=${j} j= k= y= z= for i do [ -z "${i}" ] && continue [ -d "${i}" ] || continue if [ -n "${j}" ]; then if { z=${y}; echo "${z}"; } | ${grep} ":${i}:" >/dev/null 2>&1; then : else y="${y} :${i}:" j="${j}:${i}" # But do not link any fakeroot path into our binaries! if [ -n "${addflag}" ]; then case "${i}" in *fakeroot*) continue;; esac k="${k} ${addflag}${i}" fi fi else y=" :${i}:" j="${i}" # But do not link any fakeroot injected path into our binaries! if [ -n "${addflag}" ]; then case "${i}" in *fakeroot*) continue;; esac k="${k} ${addflag}${i}" fi fi done eval "${varname}=\"${j}\"" [ -n "${addflag}" ] && eval "${flagvarname}=\"${k}\"" unset varname } ld_runtime_flags() { if [ -n "${ld_need_R_flags}" ]; then i=${IFS} IFS=: set -- ${LD_LIBRARY_PATH} IFS=${i} for i do # But do not link any fakeroot injected path into our binaries! case "${i}" in *fakeroot*) continue;; esac LDFLAGS="${LDFLAGS} ${ld_need_R_flags}${i}" _LDFLAGS="${_LDFLAGS} ${ld_need_R_flags}${i}" done export LDFLAGS fi # Disable it for a possible second run. ld_need_R_flags= } cc_check() { [ -n "${cc_check_silent}" ] || msg_nonl ' . CC %s .. ' "${1}" ( trap "exit 11" ABRT BUS ILL SEGV # avoid error messages (really) ${CC} ${INCS} \ ${_CFLAGS} ${1} ${EXTRA_CFLAGS} ${_LDFLAGS} ${EXTRA_LDFLAGS} \ -o ${tmp2} ${tmp}.c ${LIBS} || exit 1 feat_no CROSS_BUILD || exit 0 ${tmp2} ) >/dev/null 2>&1 if [ $? -eq 0 ]; then _CFLAGS="${_CFLAGS} ${1}" [ -n "${cc_check_silent}" ] || msg 'yes' return 0 fi [ -n "${cc_check_silent}" ] || msg 'no' return 1 } ld_check() { # $1=option [$2=option argument] [$3=if set, shall NOT be added to _LDFLAGS] [ -n "${cc_check_silent}" ] || msg_nonl ' . LD %s .. ' "${1}" ( trap "exit 11" ABRT BUS ILL SEGV # avoid error messages (really) ${CC} ${INCS} ${_CFLAGS} ${_LDFLAGS} ${1}${2} ${EXTRA_LDFLAGS} \ -o ${tmp2} ${tmp}.c ${LIBS} || exit 1 feat_no CROSS_BUILD || exit 0 ${tmp2} ) >/dev/null 2>&1 if [ $? -eq 0 ]; then [ -n "${3}" ] || _LDFLAGS="${_LDFLAGS} ${1}" [ -n "${cc_check_silent}" ] || msg 'yes' return 0 fi [ -n "${cc_check_silent}" ] || msg 'no' return 1 } dump_test_program=1 _check_preface() { variable=$1 topic=$2 define=$3 echo '@@@' msg_nonl ' . %s ... ' "${topic}" #echo "/* checked ${topic} */" >> ${h} ${rm} -f ${tmp} ${tmp}.o if [ "${dump_test_program}" = 1 ]; then { echo '#include <'"${h_name}"'>'; cat; } | ${tee} ${tmp}.c else { echo '#include <'"${h_name}"'>'; cat; } > ${tmp}.c fi #echo '@P' #MAKEFLAGS= ${make} -f ${makefile} ${tmp}.x #${cat} ${tmp}.x echo '@R' } without_check() { oneorzero=$1 variable=$2 topic=$3 define=$4 libs=$5 incs=$6 echo '@@@' msg_nonl ' . %s ... ' "${topic}" if [ "${oneorzero}" = 1 ]; then if [ -n "${incs}" ] || [ -n "${libs}" ]; then echo "@ INCS<${incs}> LIBS<${libs}>" LIBS="${LIBS} ${libs}" echo "${libs}" >> ${lib} INCS="${INCS} ${incs}" echo "${incs}" >> ${inc} fi msg 'yes (deduced)' echo "${define}" >> ${h} eval have_${variable}=yes return 0 else #echo "/* ${define} */" >> ${h} msg 'no (deduced)' eval unset have_${variable} return 1 fi } compile_check() { variable=$1 topic=$2 define=$3 _check_preface "${variable}" "${topic}" "${define}" if MAKEFLAGS= ${make} -f ${makefile} XINCS="${INCS}" \ CFLAGS="${CFLAGS}" LDFLAGS="${LDFLAGS}" ${tmp}.o && [ -f ${tmp}.o ]; then msg 'yes' echo "${define}" >> ${h} eval have_${variable}=yes return 0 else #echo "/* ${define} */" >> ${h} msg 'no' eval unset have_${variable} return 1 fi } _link_mayrun() { run=$1 variable=$2 topic=$3 define=$4 libs=$5 incs=$6 _check_preface "${variable}" "${topic}" "${define}" if feat_yes CROSS_BUILD; then if [ ${run} = 1 ]; then run=0 fi fi if MAKEFLAGS= ${make} -f ${makefile} XINCS="${INCS} ${incs}" \ CFLAGS="${CFLAGS}" LDFLAGS="${LDFLAGS}" \ XLIBS="${LIBS} ${libs}" ${tmp} && [ -f ${tmp} ] && { [ ${run} -eq 0 ] || ${tmp}; }; then echo "@ INCS<${incs}> LIBS<${libs}>; executed: ${run}" msg 'yes' echo "${define}" >> ${h} LIBS="${LIBS} ${libs}" echo "${libs}" >> ${lib} INCS="${INCS} ${incs}" echo "${incs}" >> ${inc} eval have_${variable}=yes return 0 else msg 'no' #echo "/* ${define} */" >> ${h} eval unset have_${variable} return 1 fi } link_check() { _link_mayrun 0 "${1}" "${2}" "${3}" "${4}" "${5}" } run_check() { _link_mayrun 1 "${1}" "${2}" "${3}" "${4}" "${5}" } xrun_check() { _link_mayrun 2 "${1}" "${2}" "${3}" "${4}" "${5}" } string_to_char_array() { ${awk} -v xy="${@}" 'BEGIN{ # POSIX: unspecified behaviour. # Does not work for SunOS /usr/xpg4/bin/awk! if(split("abc", xya, "") == 3) i = split(xy, xya, "") else{ j = length(xy) for(i = 0; j > 0; --j){ xya[++i] = substr(xy, 1, 1) xy = substr(xy, 2) } } xya[++i] = "\\0" for(j = 1; j <= i; ++j){ if(j != 1) printf ", " y = xya[j] if(y == "\012") y = "\\n" printf "'"'"'%s'"'"'", y } }' } squeeze_em() { < "${1}" > "${2}" ${awk} \ 'BEGIN {ORS = " "} /^[^#]/ {print} {next} END {ORS = ""; print "\n"}' } ## -- >8 - <> - 8< -- ## # Very easy checks for the operating system in order to be able to adjust paths # or similar very basic things which we need to be able to go at all os_early_setup # Check those tools right now that we need before including $rc msg 'Checking for basic utility set' thecmd_testandset_fail awk awk thecmd_testandset_fail rm rm thecmd_testandset_fail tr tr # Lowercase this now in order to isolate all the remains from case matters OS_ORIG_CASE=${OS} OS=`echo ${OS} | ${tr} '[A-Z]' '[a-z]'` export OS # But first of all, create new configuration and check whether it changed if [ -z "${OBJDIR}" ]; then OBJDIR=.obj else OBJDIR=`${awk} -v input="${OBJDIR}" 'BEGIN{ if(index(input, "/")) sub("/+$", "", input) print input }'` fi rc=./make.rc env="${OBJDIR}"/mk-config.env h="${OBJDIR}"/mk-config.h h_name=mk-config.h mk="${OBJDIR}"/mk-config.mk newmk="${OBJDIR}"/mk-nconfig.mk oldmk="${OBJDIR}"/mk-oconfig.mk newenv="${OBJDIR}"/mk-nconfig.env newh="${OBJDIR}"/mk-nconfig.h oldh="${OBJDIR}"/mk-oconfig.h tmp0="${OBJDIR}"/___tmp tmp=${tmp0}1$$ tmp2=${tmp0}2$$ if [ -d "${OBJDIR}" ] || mkdir -p "${OBJDIR}"; then :; else msg 'ERROR: cannot create '"${OBJDIR}"' build directory' exit 1 fi # Initialize the option set msg_nonl 'Setting up configuration options ... ' option_setup msg 'done' # Include $rc, but only take from it what wasn't overwritten by the user from # within the command line or from a chosen fixed CONFIG= # Note we leave alone the values trap "exit 1" HUP INT TERM trap "${rm} -f ${tmp}" EXIT msg_nonl 'Joining in %s ... ' ${rc} option_join_rc msg 'done' # We need to know about that now, in order to provide utility overwrites etc. os_setup msg 'Checking for remaining set of utilities' thecmd_testandset_fail getconf getconf thecmd_testandset_fail grep grep # Before we step ahead with the other utilities perform a path cleanup first. path_check PATH # awk(1) above thecmd_testandset_fail basename basename thecmd_testandset_fail cat cat thecmd_testandset_fail chmod chmod thecmd_testandset_fail cp cp thecmd_testandset_fail cmp cmp # grep(1) above thecmd_testandset ln ln # only for tests thecmd_testandset_fail mkdir mkdir thecmd_testandset_fail mv mv # pwd(1) is needed - either for make-emerge.sh, or for ourselves #[ -n "${CWDDIR}" ] || thecmd_testandset_fail pwd pwd # rm(1) above thecmd_testandset_fail sed sed thecmd_testandset_fail sort sort thecmd_testandset_fail tee tee __PATH=${PATH} thecmd_testandset chown chown || PATH="/sbin:${PATH}" thecmd_set chown chown || PATH="/usr/sbin:${PATH}" thecmd_set_fail chown chown PATH=${__PATH} thecmd_testandset_fail MAKE make make=${MAKE} export MAKE thecmd_testandset strip strip && HAVE_STRIP=1 || HAVE_STRIP=0 # For ./mx-test.sh only thecmd_testandset_fail cksum cksum # Update OPT_ options now, in order to get possible inter-dependencies right option_update # (No functions since some shells loose non-exported variables in traps) trap "trap \"\" HUP INT TERM; exit 1" HUP INT TERM trap "trap \"\" HUP INT TERM EXIT;\ ${rm} -rf ${tmp0}.* ${tmp0}* \ ${newmk} ${oldmk} ${newenv} ${newh} ${oldh}" EXIT printf '#ifdef mx_SOURCE\n' > ${newh} # Now that we have pwd(1) and options at least permit some more actions, set # our build paths unless make-emerge.sh has been used; it would have created # a makefile with the full paths otherwise if [ -z "${CWDDIR}" ]; then CWDDIR=`${pwd}` CWDDIR=`oneslash "${CWDDIR}"` fi if [ -z "${TOPDIR}" ]; then TOPDIR=${CWDDIR} fi INCDIR="${TOPDIR}"include/ SRCDIR="${TOPDIR}"src/ MX_CWDDIR=${CWDDIR} MX_INCDIR=${INCDIR} MX_SRCDIR=${SRCDIR} PS_DOTLOCK_CWDDIR=${CWDDIR} PS_DOTLOCK_INCDIR=${INCDIR} PS_DOTLOCK_SRCDIR=${SRCDIR} SU_CWDDIR=${CWDDIR} SU_INCDIR=${INCDIR} SU_SRCDIR=${SRCDIR} # Our configuration options may at this point still contain shell snippets, # we need to evaluate them in order to get them expanded, and we need those # evaluated values not only in our new configuration file, but also at hand.. msg_nonl 'Evaluating all configuration items ... ' option_evaluate msg 'done' # printf "#define VAL_UAGENT \"${VAL_SID}${VAL_MAILX}\"\n" >> ${newh} printf "VAL_UAGENT = ${VAL_SID}${VAL_MAILX}\n" >> ${newmk} printf "VAL_UAGENT=${VAL_SID}${VAL_MAILX};export VAL_UAGENT; " >> ${newenv} # The problem now is that the test should be able to run in the users linker # and path environment, so we need to place the test: rule first, before # injecting the relevant make variables. Set up necessary environment if [ -z "${VERBOSE}" ]; then printf -- "ECHO_CC = @echo ' 'CC \$(@);\n" >> ${newmk} printf -- "ECHO_LINK = @echo ' 'LINK \$(@);\n" >> ${newmk} printf -- "ECHO_GEN = @echo ' 'GEN \$(@);\n" >> ${newmk} printf -- "ECHO_TEST = @\n" >> ${newmk} printf -- "ECHO_CMD = @echo ' CMD';\n" >> ${newmk} fi printf 'test: all\n\t$(ECHO_TEST)%s %smx-test.sh --check-only %s\n' \ "${SHELL}" "${TOPDIR}" "./${VAL_SID}${VAL_MAILX}" >> ${newmk} printf \ 'testnj: all\n\t$(ECHO_TEST)%s %smx-test.sh --no-jobs --check-only %s\n' \ "${SHELL}" "${TOPDIR}" "./${VAL_SID}${VAL_MAILX}" >> ${newmk} # Add the known utility and some other variables printf "#define VAL_PS_DOTLOCK \"${VAL_SID}${VAL_MAILX}-dotlock\"\n" >> ${newh} printf "VAL_PS_DOTLOCK = \$(VAL_UAGENT)-dotlock\n" >> ${newmk} printf 'VAL_PS_DOTLOCK=%s;export VAL_PS_DOTLOCK; ' \ "${VAL_SID}${VAL_MAILX}-dotlock" >> ${newenv} if feat_yes DOTLOCK; then printf "OPTIONAL_PS_DOTLOCK = \$(VAL_PS_DOTLOCK)\n" >> ${newmk} else printf "OPTIONAL_PS_DOTLOCK =\n" >> ${newmk} fi for i in \ CWDDIR TOPDIR OBJDIR INCDIR SRCDIR \ MX_CWDDIR MX_INCDIR MX_SRCDIR \ PS_DOTLOCK_CWDDIR PS_DOTLOCK_INCDIR PS_DOTLOCK_SRCDIR \ SU_CWDDIR SU_INCDIR SU_SRCDIR \ awk basename cat chmod chown cp cmp grep getconf \ ln mkdir mv pwd rm sed sort tee tr \ MAKE MAKEFLAGS make SHELL strip \ cksum; do eval j=\$${i} printf -- "${i} = ${j}\n" >> ${newmk} printf -- "${i}=%s;export ${i}; " "`quote_string ${j}`" >> ${newenv} done # Build a basic set of INCS and LIBS according to user environment. C_INCLUDE_PATH="${INCDIR}:${SRCDIR}:${C_INCLUDE_PATH}" if path_is_absolute "${OBJDIR}"; then C_INCLUDE_PATH="${OBJDIR}:${C_INCLUDE_PATH}" else C_INCLUDE_PATH="${CWDDIR}${OBJDIR}:${C_INCLUDE_PATH}" fi C_INCLUDE_PATH="${CWDDIR}include:${C_INCLUDE_PATH}" path_check C_INCLUDE_PATH -I _INCS INCS="${INCS} ${_INCS}" path_check LD_LIBRARY_PATH -L _LIBS LIBS="${LIBS} ${_LIBS}" unset _INCS _LIBS export C_INCLUDE_PATH LD_LIBRARY_PATH # Some environments need runtime path flags to be able to go at all ld_runtime_flags ## Detect CC, whether we can use it, and possibly which CFLAGS we can use cc_setup cc_create_testfile cc_hello # This may also update ld_runtime_flags() (again) cc_flags for i in \ COMMLINE \ INCS LIBS \ ; do eval j="\$${i}" printf -- "${i}=%s;export ${i}; " "`quote_string ${j}`" >> ${newenv} done MX_CFLAGS=${CFLAGS} PS_DOTLOCK_CFLAGS=${CFLAGS} PS_DOTLOCK_INCS=${INCS} PS_DOTLOCK_LDFLAGS=${LDFLAGS} SU_CFLAGS=${CFLAGS} SU_CXXFLAGS= SU_INCS=${INCS} for i in \ CC CFLAGS LDFLAGS \ PATH C_INCLUDE_PATH LD_LIBRARY_PATH \ OSFULLSPEC \ MX_CFLAGS \ PS_DOTLOCK_CFLAGS PS_DOTLOCK_INCS PS_DOTLOCK_LDFLAGS \ SU_CFLAGS SU_CXXFLAGS SU_INCS \ ; do eval j=\$${i} if [ -n "${j}" ]; then printf -- "${i} = ${j}\n" >> ${newmk} printf -- "${i}=%s;export ${i}; " "`quote_string ${j}`" >> ${newenv} fi done # Note that makefile reads and eval'uates one line of this file, whereas other # consumers source it via .(1) printf "\n" >> ${newenv} # Now finally check whether we already have a configuration and if so, whether # all those parameters are still the same.. or something has actually changed config_updated= if [ -f ${env} ] && ${cmp} ${newenv} ${env} >/dev/null 2>&1; then echo 'Configuration is up-to-date' exit 0 elif [ -f ${env} ]; then config_updated=1 echo 'Configuration has been updated..' else echo 'Shiny configuration..' fi # Time to redefine helper 1 config_exit() { ${rm} -f ${h} ${mk} exit ${1} } ${mv} -f ${newenv} ${env} [ -f ${h} ] && ${mv} -f ${h} ${oldh} ${mv} -f ${newh} ${h} # Note this has still #ifdef mx_SOURCE open [ -f ${mk} ] && ${mv} -f ${mk} ${oldmk} ${mv} -f ${newmk} ${mk} ## Compile and link checking tmp3=${tmp0}3$$ log="${OBJDIR}"/mk-config.log lib="${OBJDIR}"/mk-config.lib inc="${OBJDIR}"/mk-config.inc makefile=${tmp0}.mk # (No function since some shells loose non-exported variables in traps) trap "trap \"\" HUP INT TERM;\ ${rm} -f ${oldh} ${h} ${oldmk} ${mk} ${lib} ${inc}; exit 1" \ HUP INT TERM trap "trap \"\" HUP INT TERM EXIT;\ ${rm} -rf ${oldh} ${oldmk} ${tmp0}.* ${tmp0}*" EXIT # Time to redefine helper 2 msg() { fmt=${1} shift printf "@ ${fmt}\n" "${@}" printf -- "${fmt}\n" "${@}" >&5 } msg_nonl() { fmt=${1} shift printf "@ ${fmt}\n" "${@}" printf -- "${fmt}" "${@}" >&5 } # !! exec 5>&2 > ${log} 2>&1 echo "${LIBS}" > ${lib} echo "${INCS}" > ${inc} ${cat} > ${makefile} << \! .SUFFIXES: .o .c .x .y .c.o: $(CC) -Dmx_SOURCE -I./ $(XINCS) $(CFLAGS) -o $(@) -c $(<) .c.x: $(CC) -Dmx_SOURCE -I./ $(XINCS) -E $(<) > $(@) .c: $(CC) -Dmx_SOURCE -I./ $(XINCS) $(CFLAGS) $(LDFLAGS) -o $(@) $(<) $(XLIBS) ! ## Generics echo '#define VAL_BUILD_OS "'"${OS_ORIG_CASE}"'"' >> ${h} [ -n "${OS_DEFINES}" ] && printf -- "${OS_DEFINES}" >> ${h} printf '#endif /* mx_SOURCE */\n\n' >> ${h} # Opened when it was $newh if [ -n "${OS_DEFINES}" ]; then printf '#ifdef su_SOURCE\n'"${OS_DEFINES}"'#endif /* su_SOURCE */\n\n' \ >> ${h} fi ## SU i=`${getconf} PAGESIZE 2>/dev/null` [ $? -eq 0 ] || i=`${getconf} PAGE_SIZE 2>/dev/null` if [ $? -ne 0 ]; then msg 'Cannot query PAGESIZE via getconf(1), assuming 4096' i=0x1000 fi printf '#define su_PAGE_SIZE %su\n' "${i}" >> ${h} # Generate SU <> OS error number mappings dump_test_program=0 ( feat_yes DEVEL && NV= || NV=noverbose SRCDIR="${SRCDIR}" TARGET="${h}" awk="${awk}" \ ${SHELL} "${TOPDIR}"mk/su-make-errors.sh ${NV} config ) | xrun_check oserrno 'OS error mapping table generated' || config_exit 1 dump_test_program=1 ## /SU ## Test for "basic" system-calls / functionality that is used by all parts ## of our program. Once this is done fork away BASE_LIBS and other BASE_* ## macros to be used by only the subprograms (potentially). if run_check clock_gettime 'clock_gettime(2)' \ '#define mx_HAVE_CLOCK_GETTIME' << \! #include # include int main(void){ struct timespec ts; if(!clock_gettime(CLOCK_REALTIME, &ts) || errno != ENOSYS) return 0; return 1; } ! then : elif run_check clock_gettime 'clock_gettime(2) (via -lrt)' \ '#define mx_HAVE_CLOCK_GETTIME' '-lrt' << \! #include # include int main(void){ struct timespec ts; if(!clock_gettime(CLOCK_REALTIME, &ts) || errno != ENOSYS) return 0; return 1; } ! then : elif run_check gettimeofday 'gettimeofday(2)' \ '#define mx_HAVE_GETTIMEOFDAY' << \! #include /* For C89 NULL */ #include # include int main(void){ struct timeval tv; if(!gettimeofday(&tv, NULL) || errno != ENOSYS) return 0; return 1; } ! then : else have_no_subsecond_time=1 fi if run_check nanosleep 'nanosleep(2)' \ '#define mx_HAVE_NANOSLEEP' << \! #include # include int main(void){ struct timespec ts; ts.tv_sec = 1; ts.tv_nsec = 100000; if(!nanosleep(&ts, NULL) || errno != ENOSYS) return 0; return 1; } ! then : elif run_check nanosleep 'nanosleep(2) (via -lrt)' \ '#define mx_HAVE_NANOSLEEP' '-lrt' << \! #include # include int main(void){ struct timespec ts; ts.tv_sec = 1; ts.tv_nsec = 100000; if(!nanosleep(&ts, NULL) || errno != ENOSYS) return 0; return 1; } ! then : # link_check is enough for this, that function is so old, trust the proto elif link_check sleep 'sleep(3)' \ '#define mx_HAVE_SLEEP' << \! #include # include int main(void){ if(!sleep(1) || errno != ENOSYS) return 0; return 1; } ! then : else msg 'ERROR: we require one of nanosleep(2) and sleep(3).' config_exit 1 fi if run_check userdb 'gete?[gu]id(2), getpwuid(3), getpwnam(3)' << \! #include #include # include int main(void){ struct passwd *pw; gid_t gid; uid_t uid; if((gid = getgid()) != 0) gid = getegid(); if((uid = getuid()) != 0) uid = geteuid(); if((pw = getpwuid(uid)) == NULL && errno == ENOSYS) return 1; if((pw = getpwnam("root")) == NULL && errno == ENOSYS) return 1; return 0; } ! then : else msg 'ERROR: we require user and group info / database searches.' msg 'That much Unix we indulge ourselfs.' config_exit 1 fi if link_check ftruncate 'ftruncate(2)' \ '#define mx_HAVE_FTRUNCATE' << \! #include #include int main(void){ return (ftruncate(0, 0) != 0); } ! then : else # TODO support mx_HAVE_FTRUNCATE *everywhere*, do not require this syscall! msg 'ERROR: we require the ftruncate(2) system call.' config_exit 1 fi if run_check sa_restart 'SA_RESTART (for sigaction(2))' << \! #include # include int main(void){ struct sigaction nact, oact; nact.sa_handler = SIG_DFL; sigemptyset(&nact.sa_mask); nact.sa_flags = SA_RESTART; return !(!sigaction(SIGCHLD, &nact, &oact) || errno != ENOSYS); } ! then : else msg 'ERROR: we (yet) require the SA_RESTART flag for sigaction(2).' config_exit 1 fi if link_check snprintf 'snprintf(3)' << \! #include int main(void){ char b[20]; snprintf(b, sizeof b, "%s", "string"); return 0; } ! then : else msg 'ERROR: we require the snprintf(3) function.' config_exit 1 fi if link_check environ 'environ(3)' << \! #include /* For C89 NULL */ int main(void){ extern char **environ; return environ[0] == NULL; } ! then : else msg 'ERROR: we require the environ(3) array for subprocess control.' config_exit 1 fi if link_check setenv '(un)?setenv(3)' '#define mx_HAVE_SETENV' << \! #include int main(void){ setenv("s-mailx", "i want to see it cute!", 1); unsetenv("s-mailx"); return 0; } ! then : elif link_check setenv 'putenv(3)' '#define mx_HAVE_PUTENV' << \! #include int main(void){ putenv("s-mailx=i want to see it cute!"); return 0; } ! then : else msg 'ERROR: we require either the setenv(3) or putenv(3) functions.' config_exit 1 fi if link_check termios 'termios.h and tc*(3) family' << \! #include int main(void){ struct termios tios; speed_t ospeed; tcgetattr(0, &tios); tcsetattr(0, TCSANOW | TCSADRAIN | TCSAFLUSH, &tios); ospeed = ((tcgetattr(0, &tios) == -1) ? B9600 : cfgetospeed(&tios)); return 0; } ! then : else msg 'ERROR: we require termios.h and the tc[gs]etattr() family of functions.' msg 'That much Unix we indulge ourselfs.' config_exit 1 fi ## optional stuff if link_check vsnprintf 'vsnprintf(3)' << \! #include #include static void dome(char *buf, size_t blen, ...){ va_list ap; va_start(ap, blen); vsnprintf(buf, blen, "%s", ap); va_end(ap); } int main(void){ char b[20]; dome(b, sizeof b, "string"); return 0; } ! then : else feat_bail_required ERRORS fi if [ "${have_vsnprintf}" = yes ]; then __va_copy() { link_check va_copy "va_copy(3) (as ${2})" \ "#define mx_HAVE_N_VA_COPY #define n_va_copy ${2}" <<_EOT #include #include #if ${1} # if defined __va_copy && !defined va_copy # define va_copy __va_copy # endif #endif static void dome2(char *buf, size_t blen, va_list src){ va_list ap; va_copy(ap, src); vsnprintf(buf, blen, "%s", ap); va_end(ap); } static void dome(char *buf, size_t blen, ...){ va_list ap; va_start(ap, blen); dome2(buf, blen, ap); va_end(ap); } int main(void){ char b[20]; dome(b, sizeof b, "string"); return 0; } _EOT } __va_copy 0 va_copy || __va_copy 1 __va_copy fi run_check pathconf 'f?pathconf(2)' '#define mx_HAVE_PATHCONF' << \! #include #include int main(void){ int rv = 0; errno = 0; rv |= !(pathconf(".", _PC_NAME_MAX) >= 0 || errno == 0 || errno != ENOSYS); errno = 0; rv |= !(pathconf(".", _PC_PATH_MAX) >= 0 || errno == 0 || errno != ENOSYS); /* Only link check */ fpathconf(0, _PC_NAME_MAX); return rv; } ! run_check pipe2 'pipe2(2)' '#define mx_HAVE_PIPE2' << \! #include #include # include int main(void){ int fds[2]; if(!pipe2(fds, O_CLOEXEC) || errno != ENOSYS) return 0; return 1; } ! link_check tcgetwinsize 'tcgetwinsize(3)' '#define mx_HAVE_TCGETWINSIZE' << \! #include int main(void){ struct winsize ws; tcgetwinsize(0, &ws); return 0; } ! # We use this only then for now (need NOW+1) run_check utimensat 'utimensat(2)' '#define mx_HAVE_UTIMENSAT' << \! #include /* For AT_* */ #include # include int main(void){ struct timespec ts[2]; ts[0].tv_nsec = UTIME_NOW; ts[1].tv_nsec = UTIME_OMIT; if(!utimensat(AT_FDCWD, "", ts, 0) || errno != ENOSYS) return 0; return 1; } ! ## # The random check has been moved to below TLS detection due to multiple choice # selection for PRG sources link_check putc_unlocked 'putc_unlocked(3)' '#define mx_HAVE_PUTC_UNLOCKED' <<\! #include int main(void){ putc_unlocked('@', stdout); return 0; } ! link_check fchdir 'fchdir(3)' '#define mx_HAVE_FCHDIR' << \! #include int main(void){ fchdir(0); return 0; } ! if link_check realpath 'realpath(3)' '#define mx_HAVE_REALPATH' << \! #include int main(void){ char x_buf[4096], *x = realpath(".", x_buf); return (x != NULL) ? 0 : 1; } ! then if run_check realpath_malloc 'realpath(3) takes NULL' \ '#define mx_HAVE_REALPATH_NULL' << \! #include int main(void){ char *x = realpath(".", NULL); if(x != NULL) free(x); return (x != NULL) ? 0 : 1; } ! then : fi fi ## ## optional and selectable ## if feat_yes DOTLOCK; then if run_check readlink 'readlink(2)' << \! #include # include int main(void){ char buf[128]; if(!readlink("here", buf, sizeof buf) || errno != ENOSYS) return 0; return 1; } ! then : else feat_bail_required DOTLOCK fi fi if feat_yes DOTLOCK; then if run_check fchown 'fchown(2)' << \! #include # include int main(void){ if(!fchown(0, 0, 0) || errno != ENOSYS) return 0; return 1; } ! then : else feat_bail_required DOTLOCK fi fi if feat_yes DOTLOCK; then if run_check prctl_dumpable 'prctl(2) + PR_SET_DUMPABLE' \ '#define mx_HAVE_PRCTL_DUMPABLE' << \! #include # include int main(void){ if(!prctl(PR_SET_DUMPABLE, 0) || errno != ENOSYS) return 0; return 1; } ! then : elif run_check prtrace_deny 'ptrace(2) + PT_DENY_ATTACH' \ '#define mx_HAVE_PTRACE_DENY' << \! #include # include int main(void){ if(ptrace(PT_DENY_ATTACH, 0, 0, 0) != -1 || errno != ENOSYS) return 0; return 1; } ! then : elif run_check setpflags_protect 'setpflags(2) + __PROC_PROTECT' \ '#define mx_HAVE_SETPFLAGS_PROTECT' << \! #include # include int main(void){ if(!setpflags(__PROC_PROTECT, 1) || errno != ENOSYS) return 0; return 1; } ! then : fi fi ## Now it is the time to fork away the BASE_ series ${rm} -f ${tmp} squeeze_em ${inc} ${tmp} ${mv} ${tmp} ${inc} squeeze_em ${lib} ${tmp} ${mv} ${tmp} ${lib} echo "BASE_LIBS = `${cat} ${lib}`" >> ${mk} echo 'PS_DOTLOCK_LIBS = $(BASE_LIBS)' >> ${mk} echo 'SU_LIBS = $(BASE_LIBS)' >> ${mk} echo "BASE_INCS = `${cat} ${inc}`" >> ${mk} echo 'PS_DOTLOCK_INCS = $(BASE_INCS)' >> ${mk} echo 'SU_INCS = $(BASE_INCS)' >> ${mk} ## The remains are expected to be used only by the main MUA binary! OPT_LOCALES=0 link_check setlocale 'setlocale(3)' '#define mx_HAVE_SETLOCALE' << \! #include int main(void){ setlocale(LC_ALL, ""); return 0; } ! [ -n "${have_setlocale}" ] && OPT_LOCALES=1 OPT_MULTIBYTE_CHARSETS=0 OPT_WIDE_GLYPHS=0 OPT_TERMINAL_CHARSET=0 if [ -n "${have_setlocale}" ]; then link_check c90amend1 'ISO/IEC 9899:1990/Amendment 1:1995' \ '#define mx_HAVE_C90AMEND1' << \! #include #include #include #include int main(void){ char mbb[MB_LEN_MAX + 1]; wchar_t wc; iswprint(L'c'); towupper(L'c'); mbtowc(&wc, "x", 1); mbrtowc(&wc, "x", 1, NULL); wctomb(mbb, wc); return (mblen("\0", 1) == 0); } ! [ -n "${have_c90amend1}" ] && OPT_MULTIBYTE_CHARSETS=1 if [ -n "${have_c90amend1}" ]; then link_check wcwidth 'wcwidth(3)' '#define mx_HAVE_WCWIDTH' << \! #include int main(void){ wcwidth(L'c'); return 0; } ! [ -n "${have_wcwidth}" ] && OPT_WIDE_GLYPHS=1 fi link_check nl_langinfo 'nl_langinfo(3)' '#define mx_HAVE_NL_LANGINFO' << \! #include #include int main(void){ nl_langinfo(DAY_1); return (nl_langinfo(CODESET) == NULL); } ! [ -n "${have_nl_langinfo}" ] && OPT_TERMINAL_CHARSET=1 fi # have_setlocale link_check fnmatch 'fnmatch(3)' '#define mx_HAVE_FNMATCH' << \! #include int main(void){ return (fnmatch("*", ".", FNM_PATHNAME | FNM_PERIOD) == FNM_NOMATCH); } ! link_check dirent_d_type 'struct dirent.d_type' '#define mx_HAVE_DIRENT_TYPE' << \! #include int main(void){ struct dirent de; return !(de.d_type == DT_UNKNOWN || de.d_type == DT_DIR || de.d_type == DT_LNK); } ! ## optional and selectable if feat_yes ICONV; then # To be able to create tests we need to figure out which replacement # sequence the iconv(3) implementation creates ${cat} > ${tmp2}.c << \! #include /* For C89 NULL */ #include #include int main(void){ char inb[16], oub[16], *inbp, *oubp; iconv_t id; size_t inl, oul; /* U+2013 */ memcpy(inbp = inb, "\342\200\223", sizeof("\342\200\223")); inl = sizeof("\342\200\223") -1; oul = sizeof oub; oubp = oub; if((id = iconv_open("ascii", "utf-8")) == (iconv_t)-1) return 1; if(iconv(id, &inbp, &inl, &oubp, &oul) == (size_t)-1) return 1; iconv_close(id); *oubp = '\0'; oul = (size_t)(oubp - oub); if(oul == 0) return 1; /* Character-wise replacement? */ if(oul == 1){ if(oub[0] == '?') return 2; if(oub[0] == '*') return 3; return 1; } /* Byte-wise replacement? */ if(oul == sizeof("\342\200\223") -1){ if(!memcmp(oub, "???????", sizeof("\342\200\223") -1)) return 12; if(!memcmp(oub, "*******", sizeof("\342\200\223") -1)) return 13; return 1; } return 0; } ! < ${tmp2}.c link_check iconv 'iconv(3) functionality' \ '#define mx_HAVE_ICONV' || < ${tmp2}.c link_check iconv 'iconv(3) functionality (via -liconv)' \ '#define mx_HAVE_ICONV' '-liconv' || feat_bail_required ICONV if feat_yes ICONV && feat_no CROSS_BUILD; then { ${tmp}; } >/dev/null 2>&1 case ${?} in 2) echo 'MAILX_ICONV_MODE=2;export MAILX_ICONV_MODE;' >> ${env};; 3) echo 'MAILX_ICONV_MODE=3;export MAILX_ICONV_MODE;' >> ${env};; 12) echo 'MAILX_ICONV_MODE=12;export MAILX_ICONV_MODE;' >> ${env};; 13) echo 'MAILX_ICONV_MODE=13;export MAILX_ICONV_MODE;' >> ${env};; *) msg 'WARN: will restrict iconv(3) tests due to unknown replacement';; esac fi else feat_is_disabled ICONV fi # feat_yes ICONV if feat_yes NET; then ${cat} > ${tmp2}.c << \! #include #include #include # include int main(void){ struct sockaddr_un soun; if(socket(AF_UNIX, SOCK_STREAM, 0) == -1 && errno == ENOSYS) return 1; if(connect(0, (struct sockaddr*)&soun, 0) == -1 && errno == ENOSYS) return 1; if(shutdown(0, SHUT_RD | SHUT_WR | SHUT_RDWR) == -1 && errno == ENOSYS) return 1; return 0; } ! < ${tmp2}.c run_check af_unix 'AF_UNIX sockets' \ '#define mx_HAVE_UNIX_SOCKETS' || < ${tmp2}.c run_check af_unix 'AF_UNIX sockets (via -lnsl)' \ '#define mx_HAVE_UNIX_SOCKETS' '-lnsl' || < ${tmp2}.c run_check af_unix 'AF_UNIX sockets (via -lsocket -lnsl)' \ '#define mx_HAVE_UNIX_SOCKETS' '-lsocket -lnsl' fi if feat_yes NET; then ${cat} > ${tmp2}.c << \! #include #include #include # include int main(void){ struct sockaddr s; if(socket(AF_INET, SOCK_STREAM, 0) == -1 && errno == ENOSYS) return 1; if(connect(0, &s, 0) == -1 && errno == ENOSYS) return 1; return 0; } ! < ${tmp2}.c run_check sockets 'sockets' \ '#define mx_HAVE_NET' || < ${tmp2}.c run_check sockets 'sockets (via -lnsl)' \ '#define mx_HAVE_NET' '-lnsl' || < ${tmp2}.c run_check sockets 'sockets (via -lsocket -lnsl)' \ '#define mx_HAVE_NET' '-lsocket -lnsl' || feat_bail_required NET else feat_is_disabled NET fi # feat_yes NET feat_yes NET && link_check sockopt '[gs]etsockopt(2)' '#define mx_HAVE_SOCKOPT' << \! #include #include # include int main(void){ socklen_t sol; int sockfd = 3, soe; sol = sizeof soe; if(getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &soe, &sol) == -1 && errno == ENOSYS) return 1; if(setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, NULL, 0) == -1 && errno == ENOSYS) return 1; return 0; } ! feat_yes NET && link_check nonblocksock 'non-blocking sockets' \ '#define mx_HAVE_NONBLOCKSOCK' << \! #include #include #include #include #include #include #include #include #include # include int main(void){ fd_set fdset; struct timeval tv; struct sockaddr_in sin; socklen_t sol; int sofd, soe; if((sofd = socket(AF_INET, SOCK_STREAM, 0)) == -1 && errno == ENOSYS) return 1; if(fcntl(sofd, F_SETFL, O_NONBLOCK) != 0) return 1; sin.sin_family = AF_INET; sin.sin_addr.s_addr = inet_addr("127.0.0.1"); sin.sin_port = htons(80); if(connect(sofd, &sin, sizeof sin) == -1 && errno == ENOSYS) return 1; FD_ZERO(&fdset); FD_SET(sofd, &fdset); tv.tv_sec = 10; tv.tv_usec = 0; if((soe = select(sofd + 1, NULL, &fdset, NULL, &tv)) == 1){ sol = sizeof soe; getsockopt(sofd, SOL_SOCKET, SO_ERROR, &soe, &sol); if(soe == 0) return 0; }else if(soe == -1 && errno == ENOSYS) return 1; close(sofd); return 0; } ! if feat_yes NET; then link_check getaddrinfo 'getaddrinfo(3)' \ '#define mx_HAVE_GETADDRINFO' << \! #include #include #include #include int main(void){ struct addrinfo a, *ap; int lrv; switch((lrv = getaddrinfo("foo", "0", &a, &ap))){ case EAI_NONAME: case EAI_SERVICE: default: fprintf(stderr, "%s\n", gai_strerror(lrv)); case 0: break; } return 0; } ! fi if feat_yes NET && [ -z "${have_getaddrinfo}" ]; then compile_check arpa_inet_h '' \ '#define mx_HAVE_ARPA_INET_H' << \! #include #include #include #include #include ! ${cat} > ${tmp2}.c << \! #include #include #include #include #include #include #ifdef mx_HAVE_ARPA_INET_H #include #endif int main(void){ struct sockaddr_in servaddr; unsigned short portno; struct servent *ep; struct hostent *hp; struct in_addr **pptr; portno = 0; if((ep = getservbyname("POPPY-PORT", "tcp")) != NULL) portno = (unsigned short)ep->s_port; if((hp = gethostbyname("POPPY-HOST")) != NULL){ pptr = (struct in_addr**)hp->h_addr_list; if(hp->h_addrtype != AF_INET) fprintf(stderr, "au\n"); }else{ switch(h_errno){ case HOST_NOT_FOUND: case TRY_AGAIN: case NO_RECOVERY: case NO_DATA: break; default: fprintf(stderr, "au\n"); break; } } memset(&servaddr, 0, sizeof servaddr); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(portno); memcpy(&servaddr.sin_addr, *pptr, sizeof(struct in_addr)); fprintf(stderr, "Would connect to %s:%d ...\n", inet_ntoa(**pptr), (int)portno); return 0; } ! < ${tmp2}.c link_check gethostbyname 'get(serv|host)byname(3)' || < ${tmp2}.c link_check gethostbyname \ 'get(serv|host)byname(3) (via -nsl)' '' '-lnsl' || < ${tmp2}.c link_check gethostbyname \ 'get(serv|host)byname(3) (via -lsocket -nsl)' \ '' '-lsocket -lnsl' || feat_bail_required NET fi feat_yes NET && [ -n "${have_sockopt}" ] && link_check so_xtimeo 'SO_{RCV,SND}TIMEO' '#define mx_HAVE_SO_XTIMEO' << \! #include #include int main(void){ struct timeval tv; int sockfd = 3; tv.tv_sec = 42; tv.tv_usec = 21; setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv); setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); return 0; } ! feat_yes NET && [ -n "${have_sockopt}" ] && link_check so_linger 'SO_LINGER' '#define mx_HAVE_SO_LINGER' << \! #include #include int main(void){ struct linger li; int sockfd = 3; li.l_onoff = 1; li.l_linger = 42; setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &li, sizeof li); return 0; } ! VAL_TLS_FEATURES= if feat_yes TLS; then # {{{ # {{{ LibreSSL decided to define OPENSSL_VERSION_NUMBER with a useless value # instead of keeping it at the one that corresponds to the OpenSSL at fork # time: we need to test it first in order to get things right if compile_check _xtls 'TLS (LibreSSL)' \ '#define mx_HAVE_TLS #define mx_HAVE_XTLS #define mx_HAVE_XTLS_RESSL #define mx_HAVE_XTLS_OPENSSL 0' << \! #include #ifdef LIBRESSL_VERSION_NUMBER #else # error nope #endif ! then ossl_v1_1= VAL_TLS_FEATURES=libressl,-tls-rand-file # TODO OPENSSL_IS_BORINGSSL, but never tried that one! elif compile_check _xtls 'TLS (OpenSSL >= v1.1.1)' \ '#define mx_HAVE_TLS #define mx_HAVE_XTLS #define mx_HAVE_XTLS_OPENSSL 0x10101' << \! #include #if OPENSSL_VERSION_NUMBER + 0 >= 0x1010100fL #else # error nope #endif ! then ossl_v1_1=1 VAL_TLS_FEATURES=libssl-0x10100,-tls-rand-file elif compile_check _xtls 'TLS (OpenSSL >= v1.1.0)' \ '#define mx_HAVE_TLS #define mx_HAVE_XTLS #define mx_HAVE_XTLS_OPENSSL 0x10100 #define mx_HAVE_TLS_RAND_FILE' << \! #include #if OPENSSL_VERSION_NUMBER + 0 >= 0x10100000L #else # error nope #endif ! then ossl_v1_1=1 VAL_TLS_FEATURES=libssl-0x10100,+tls-rand-file elif compile_check _xtls 'TLS (OpenSSL)' \ '#define mx_HAVE_TLS #define mx_HAVE_XTLS #define mx_HAVE_XTLS_OPENSSL 0x10000 #define mx_HAVE_TLS_RAND_FILE' << \! #include #ifdef OPENSSL_VERSION_NUMBER #else # error nope #endif ! then ossl_v1_1= VAL_TLS_FEATURES=libssl-0x10000,+tls-rand-file else feat_bail_required TLS fi # }}} if feat_yes TLS; then # {{{ if [ -n "${ossl_v1_1}" ]; then without_check 1 xtls 'TLS new style TLS_client_method(3ssl)' \ '#define n_XTLS_CLIENT_METHOD TLS_client_method' \ '-lssl -lcrypto' elif link_check xtls 'TLS new style TLS_client_method(3ssl)' \ '#define n_XTLS_CLIENT_METHOD TLS_client_method' \ '-lssl -lcrypto' << \! #include #include #include #include #include #ifdef OPENSSL_NO_TLS1 /* TODO only deduced from OPENSSL_NO_SSL[23]! */ # error We need TLSv1. #endif int main(void){ SSL_CTX *ctx = SSL_CTX_new(TLS_client_method()); SSL_CTX_free(ctx); PEM_read_PrivateKey(0, 0, 0, 0); return 0; } ! then : elif link_check xtls 'TLS old style SSLv23_client_method(3ssl)' \ '#define n_XTLS_CLIENT_METHOD SSLv23_client_method' \ '-lssl -lcrypto' << \! #include #include #include #include #include #if defined OPENSSL_NO_SSL3 &&\ defined OPENSSL_NO_TLS1 /* TODO only deduced from OPENSSL_NO_SSL[23]! */ # error We need one of SSLv3 and TLSv1. #endif int main(void){ SSL_CTX *ctx = SSL_CTX_new(SSLv23_client_method()); SSL_CTX_free(ctx); PEM_read_PrivateKey(0, 0, 0, 0); return 0; } ! then : else feat_bail_required TLS fi fi # }}} if feat_yes TLS; then # {{{ if [ -n "${ossl_v1_1}" ]; then without_check 1 xtls_stack_of 'TLS STACK_OF()' \ '#define mx_HAVE_XTLS_STACK_OF' elif compile_check xtls_stack_of 'TLS STACK_OF()' \ '#define mx_HAVE_XTLS_STACK_OF' << \! #include /* For C89 NULL */ #include #include #include #include #include int main(void){ STACK_OF(GENERAL_NAME) *gens = NULL; printf("%p", gens); /* to use it */ return 0; } ! then : fi if [ -n "${ossl_v1_1}" ]; then without_check 1 xtls_conf 'TLS OpenSSL_modules_load_file(3ssl)' \ '#define mx_HAVE_XTLS_CONFIG' VAL_TLS_FEATURES="${VAL_TLS_FEATURES},+modules-load-file" elif link_check xtls_conf \ 'TLS OpenSSL_modules_load_file(3ssl) support' \ '#define mx_HAVE_XTLS_CONFIG' << \! #include /* For C89 NULL */ #include int main(void){ CONF_modules_load_file(NULL, NULL, CONF_MFLAGS_IGNORE_MISSING_FILE); CONF_modules_free(); return 0; } ! then VAL_TLS_FEATURES="${VAL_TLS_FEATURES},+modules-load-file" else VAL_TLS_FEATURES="${VAL_TLS_FEATURES},-modules-load-file" fi if [ -n "${ossl_v1_1}" ]; then without_check 1 xtls_conf_ctx 'TLS SSL_CONF_CTX support' \ '#define mx_HAVE_XTLS_CONF_CTX' VAL_TLS_FEATURES="${VAL_TLS_FEATURES},+conf-ctx" elif link_check xtls_conf_ctx 'TLS SSL_CONF_CTX support' \ '#define mx_HAVE_XTLS_CONF_CTX' << \! #include #include int main(void){ SSL_CTX *ctx = SSL_CTX_new(n_XSSL_CLIENT_METHOD()); SSL_CONF_CTX *cctx = SSL_CONF_CTX_new(); SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_FILE | SSL_CONF_FLAG_CLIENT | SSL_CONF_FLAG_CERTIFICATE | SSL_CONF_FLAG_SHOW_ERRORS); SSL_CONF_CTX_set_ssl_ctx(cctx, ctx); SSL_CONF_cmd(cctx, "Protocol", "ALL"); SSL_CONF_CTX_finish(cctx); SSL_CONF_CTX_free(cctx); SSL_CTX_free(ctx); return 0; } ! then VAL_TLS_FEATURES="${VAL_TLS_FEATURES},+conf-ctx" else VAL_TLS_FEATURES="${VAL_TLS_FEATURES},-conf-ctx" fi if [ -n "${ossl_v1_1}" ]; then without_check 1 xtls_ctx_config 'TLS SSL_CTX_config(3ssl)' \ '#define mx_HAVE_XTLS_CTX_CONFIG' VAL_TLS_FEATURES="${VAL_TLS_FEATURES},+ctx-config" elif [ -n "${have_xtls_conf}" ] && [ -n "${have_xtls_conf_ctx}" ] && link_check xtls_ctx_config 'TLS SSL_CTX_config(3ssl)' \ '#define mx_HAVE_XTLS_CTX_CONFIG' << \! #include /* For C89 NULL */ #include int main(void){ SSL_CTX_config(NULL, "SOMEVAL"); return 0; } ! then VAL_TLS_FEATURES="${VAL_TLS_FEATURES},+ctx-config" else VAL_TLS_FEATURES="${VAL_TLS_FEATURES},-ctx-config" fi if [ -n "${ossl_v1_1}" ] && [ -n "${have_xtls_conf_ctx}" ]; then without_check 1 xtls_set_maxmin_proto \ 'TLS SSL_CTX_set_min_proto_version(3ssl)' \ '#define mx_HAVE_XTLS_SET_MIN_PROTO_VERSION' VAL_TLS_FEATURES="${VAL_TLS_FEATURES},+ctx-set-maxmin-proto" elif link_check xtls_set_maxmin_proto \ 'TLS SSL_CTX_set_min_proto_version(3ssl)' \ '#define mx_HAVE_XTLS_SET_MIN_PROTO_VERSION' << \! #include /* For C89 NULL */ #include int main(void){ SSL_CTX_set_min_proto_version(NULL, 0); SSL_CTX_set_max_proto_version(NULL, 10); return 0; } ! then VAL_TLS_FEATURES="${VAL_TLS_FEATURES},+ctx-set-maxmin-proto" else VAL_TLS_FEATURES="${VAL_TLS_FEATURES},-ctx-set-maxmin-proto" fi if [ -n "${ossl_v1_1}" ] && [ -n "${have_xtls_conf_ctx}" ]; then without_check 1 xtls_set_ciphersuites \ 'TLSv1.3 SSL_CTX_set_ciphersuites(3ssl)' \ '#define mx_HAVE_XTLS_SET_CIPHERSUITES' VAL_TLS_FEATURES="${VAL_TLS_FEATURES},+ctx-set-ciphersuites" elif link_check xtls_set_ciphersuites \ 'TLSv1.3 SSL_CTX_set_ciphersuites(3ssl)' \ '#define mx_HAVE_XTLS_SET_CIPHERSUITES' << \! #include /* For C89 NULL */ #include int main(void){ SSL_CTX_set_ciphersuites(NULL, NULL); return 0; } ! then VAL_TLS_FEATURES="${VAL_TLS_FEATURES},+ctx-set-ciphersuites" else VAL_TLS_FEATURES="${VAL_TLS_FEATURES},-ctx-set-ciphersuites" fi fi # feat_yes SSL }}} if feat_yes TLS; then # digest etc algorithms {{{ if feat_yes TLS_ALL_ALGORITHMS; then if [ -n "${ossl_v1_1}" ]; then without_check 1 tls_all_algo 'TLS_ALL_ALGORITHMS support' \ '#define mx_HAVE_TLS_ALL_ALGORITHMS' elif link_check tls_all_algo 'TLS all-algorithms support' \ '#define mx_HAVE_TLS_ALL_ALGORITHMS' << \! #include int main(void){ OpenSSL_add_all_algorithms(); EVP_get_cipherbyname("two cents i never exist"); EVP_cleanup(); return 0; } ! then : else feat_bail_required TLS_ALL_ALGORITHMS fi elif [ -n "${ossl_v1_1}" ]; then without_check 1 tls_all_algo \ 'TLS all-algorithms (always available in v1.1.0+)' \ '#define mx_HAVE_TLS_ALL_ALGORITHMS' fi # Blake link_check tls_blake2 'TLS: BLAKE2 digests' \ '#define mx_HAVE_XTLS_BLAKE2' << \! #include int main(void){ EVP_blake2b512(); EVP_blake2s256(); return 0; } ! # SHA-3 link_check tls_sha3 'TLS: SHA-3 digests' '#define mx_HAVE_XTLS_SHA3' << \! #include int main(void){ EVP_sha3_512(); EVP_sha3_384(); EVP_sha3_256(); EVP_sha3_224(); return 0; } ! if feat_yes MD5 && feat_no NOEXTMD5; then run_check tls_md5 'TLS: MD5 digest' '#define mx_HAVE_XTLS_MD5' << \! #include #include #include int main(void){ char const dat[] = "abrakadabrafidibus"; char dig[16], hex[16 * 2]; MD5_CTX ctx; size_t i, j; memset(dig, 0, sizeof(dig)); memset(hex, 0, sizeof(hex)); MD5_Init(&ctx); MD5_Update(&ctx, dat, sizeof(dat) - 1); MD5_Final(dig, &ctx); #define su_cs_is_xdigit(n) ((n) > 9 ? (n) - 10 + 'a' : (n) + '0') for(i = 0; i < sizeof(hex) / 2; i++){ j = i << 1; hex[j] = su_cs_is_xdigit((dig[i] & 0xf0) >> 4); hex[++j] = su_cs_is_xdigit(dig[i] & 0x0f); } return !!memcmp("6d7d0a3d949da2e96f2aa010f65d8326", hex, sizeof(hex)); } ! fi else feat_bail_required TLS_ALL_ALGORITHMS # feat_is_disabled? fi # }}} else feat_is_disabled TLS feat_is_disabled TLS_ALL_ALGORITHMS fi # }}} feat_yes TLS printf '#ifdef mx_SOURCE\n' >> ${h} printf '#define VAL_TLS_FEATURES ",'"${VAL_TLS_FEATURES}"'"\n' >> ${h} printf '#endif /* mx_SOURCE */\n' >> ${h} if [ "${have_xtls}" = yes ]; then OPT_SMIME=1 else OPT_SMIME=0 fi # VAL_RANDOM {{{ if val_allof VAL_RANDOM \ "arc4,tls,libgetrandom,sysgetrandom,urandom,builtin,error"; then : else msg 'ERROR: VAL_RANDOM with invalid entries: %s' "${VAL_RANDOM}" config_exit 1 fi # Random implementations which completely replace our builtin machine val_random_arc4() { link_check arc4random 'VAL_RANDOM: arc4random(3)' \ '#define mx_HAVE_RANDOM mx_RANDOM_IMPL_ARC4' << \! #include int main(void){ arc4random(); return 0; } ! } val_random_tls() { if feat_yes TLS; then msg ' . VAL_RANDOM: tls ... yes' echo '#define mx_HAVE_RANDOM mx_RANDOM_IMPL_TLS' >> ${h} # Avoid reseeding, all we need is a streamy random producer link_check xtls_rand_drbg_set_reseed_defaults \ 'RAND_DRBG_set_reseed_defaults(3ssl)' \ '#define mx_HAVE_XTLS_SET_RESEED_DEFAULTS' << \! #include int main(void){ return (RAND_DRBG_set_reseed_defaults(0, 0, 0, 0) != 0); } ! return 0 else msg ' . VAL_RANDOM: tls ... no' return 1 fi } # The remaining random implementation are only used to seed our builtin # machine; we are prepared to handle failures of those, meaning that we have # a homebrew seeder; that tries to yield the time slice once, via # sched_yield(2) if available, nanosleep({0,0},) otherwise val__random_yield_ok= val__random_check_yield() { [ -n "${val__random_yield_ok}" ] && return val__random_yield_ok=1 link_check sched_yield 'sched_yield(2)' '#define mx_HAVE_SCHED_YIELD' << \! #include int main(void){ sched_yield(); return 0; } ! } val_random_libgetrandom() { val__random_check_yield link_check getrandom 'VAL_RANDOM: getrandom(3) (in sys/random.h)' \ '#define mx_HAVE_RANDOM mx_RANDOM_IMPL_GETRANDOM #define mx_RANDOM_GETRANDOM_FUN(B,S) getrandom(B, S, 0) #define mx_RANDOM_GETRANDOM_H ' <<\! #include int main(void){ char buf[256]; getrandom(buf, sizeof buf, 0); return 0; } ! } val_random_sysgetrandom() { val__random_check_yield link_check getrandom 'VAL_RANDOM: getrandom(2) (via syscall(2))' \ '#define mx_HAVE_RANDOM mx_RANDOM_IMPL_GETRANDOM #define mx_RANDOM_GETRANDOM_FUN(B,S) syscall(SYS_getrandom, B, S, 0) #define mx_RANDOM_GETRANDOM_H ' <<\! #include int main(void){ char buf[256]; syscall(SYS_getrandom, buf, sizeof buf, 0); return 0; } ! } val_random_urandom() { val__random_check_yield msg_nonl ' . VAL_RANDOM: /dev/urandom ... ' if feat_yes CROSS_BUILD; then msg 'yes (unchecked)' echo '#define mx_HAVE_RANDOM mx_RANDOM_IMPL_URANDOM' >> ${h} elif [ -f /dev/urandom ]; then msg yes echo '#define mx_HAVE_RANDOM mx_RANDOM_IMPL_URANDOM' >> ${h} else msg no return 1 fi return 0 } val_random_builtin() { val__random_check_yield msg_nonl ' . VAL_RANDOM: builtin ... ' if [ -n "${have_no_subsecond_time}" ]; then msg 'no\nERROR: %s %s' 'without a specialized PRG ' \ 'one of clock_gettime(2) and gettimeofday(2) is required.' config_exit 1 else msg yes echo '#define mx_HAVE_RANDOM mx_RANDOM_IMPL_BUILTIN' >> ${h} fi } val_random_error() { msg 'ERROR: VAL_RANDOM search reached "error" entry' config_exit 42 } oifs=${IFS} IFS=", " VAL_RANDOM="${VAL_RANDOM},error" set -- ${VAL_RANDOM} IFS=${oifs} for randfun do eval val_random_$randfun && break done # }}} VAL_RANDOM if feat_yes GSSAPI; then # {{{ ${cat} > ${tmp2}.c << \! #include int main(void){ gss_import_name(0, 0, GSS_C_NT_HOSTBASED_SERVICE, 0); gss_init_sec_context(0,0,0,0,0,0,0,0,0,0,0,0,0); return 0; } ! ${sed} -e '1s/gssapi\///' < ${tmp2}.c > ${tmp3}.c if acmd_set i krb5-config; then GSS_LIBS="`CFLAGS= ${i} --libs gssapi`" GSS_INCS="`CFLAGS= ${i} --cflags`" i='GSS-API via krb5-config(1)' else GSS_LIBS='-lgssapi' GSS_INCS= i='GSS-API in gssapi/gssapi.h, libgssapi' fi if < ${tmp2}.c link_check gss \ "${i}" '#define mx_HAVE_GSSAPI' "${GSS_LIBS}" "${GSS_INCS}" ||\ < ${tmp3}.c link_check gss \ 'GSS-API in gssapi.h, libgssapi' \ '#define mx_HAVE_GSSAPI #define GSSAPI_REG_INCLUDE' \ '-lgssapi' ||\ < ${tmp2}.c link_check gss 'GSS-API in libgssapi_krb5' \ '#define mx_HAVE_GSSAPI' \ '-lgssapi_krb5' ||\ < ${tmp3}.c link_check gss \ 'GSS-API in libgssapi, OpenBSD-style (pre 5.3)' \ '#define mx_HAVE_GSSAPI #define GSS_REG_INCLUDE' \ '-lgssapi -lkrb5 -lcrypto' \ '-I/usr/include/kerberosV' ||\ < ${tmp2}.c link_check gss 'GSS-API in libgss' \ '#define mx_HAVE_GSSAPI' \ '-lgss' ||\ link_check gss 'GSS-API in libgssapi_krb5, old-style' \ '#define mx_HAVE_GSSAPI #define GSSAPI_OLD_STYLE' \ '-lgssapi_krb5' << \! #include #include int main(void){ gss_import_name(0, 0, gss_nt_service_name, 0); gss_init_sec_context(0,0,0,0,0,0,0,0,0,0,0,0,0); return 0; } ! then : else feat_bail_required GSSAPI fi else feat_is_disabled GSSAPI fi # feat_yes GSSAPI }}} if feat_yes IDNA; then # {{{ if val_allof VAL_IDNA "idnkit,idn2,idn"; then : else msg 'ERROR: VAL_IDNA with invalid entries: %s' "${VAL_IDNA}" config_exit 1 fi val_idna_idn2() { link_check idna 'OPT_IDNA->VAL_IDNA: GNU Libidn2' \ '#define mx_HAVE_IDNA n_IDNA_IMPL_LIBIDN2' '-lidn2' << \! #include int main(void){ char *idna_utf8, *idna_lc; if(idn2_to_ascii_8z("does.this.work", &idna_utf8, IDN2_NONTRANSITIONAL | IDN2_TRANSITIONAL) != IDN2_OK) return 1; if(idn2_to_unicode_8zlz(idna_utf8, &idna_lc, 0) != IDN2_OK) return 1; idn2_free(idna_lc); idn2_free(idna_utf8); return 0; } ! } val_idna_idn() { link_check idna 'OPT_IDNA->VAL_IDNA: GNU Libidn' \ '#define mx_HAVE_IDNA n_IDNA_IMPL_LIBIDN' '-lidn' << \! #include #include #include /* XXX we actually use our own iconv instead */ int main(void){ char *utf8, *idna_ascii, *idna_utf8; utf8 = stringprep_locale_to_utf8("does.this.work"); if (idna_to_ascii_8z(utf8, &idna_ascii, IDNA_USE_STD3_ASCII_RULES) != IDNA_SUCCESS) return 1; idn_free(idna_ascii); /* (Rather link check only here) */ idna_utf8 = stringprep_convert(idna_ascii, "UTF-8", "de_DE"); return 0; } ! } val_idna_idnkit() { link_check idna 'OPT_IDNA->VAL_IDNA: idnkit' \ '#define mx_HAVE_IDNA n_IDNA_IMPL_IDNKIT' '-lidnkit' << \! #include #include #include int main(void){ idn_result_t r; char ace_name[256]; char local_name[256]; r = idn_encodename(IDN_ENCODE_APP, "does.this.work", ace_name, sizeof(ace_name)); if (r != idn_success) { fprintf(stderr, "idn_encodename failed: %s\n", idn_result_tostring(r)); return 1; } r = idn_decodename(IDN_DECODE_APP, ace_name, local_name, sizeof(local_name)); if (r != idn_success) { fprintf(stderr, "idn_decodename failed: %s\n", idn_result_tostring(r)); return 1; } return 0; } ! } val_idna_bye() { feat_bail_required IDNA } oifs=${IFS} IFS=", " VAL_IDNA="${VAL_IDNA},bye" set -- ${VAL_IDNA} IFS=${oifs} for randfun do eval val_idna_$randfun && break done else feat_is_disabled IDNA fi # }}} IDNA if feat_yes REGEX; then if link_check regex 'regular expressions' '#define mx_HAVE_REGEX' << \! #include #include int main(void){ size_t xret; int status; regex_t re; status = regcomp(&re, ".*bsd", REG_EXTENDED | REG_ICASE | REG_NOSUB); xret = regerror(status, &re, NULL, 0); status = regexec(&re, "plan9", 0,NULL, 0); regfree(&re); return !(status == REG_NOMATCH); } ! then : else feat_bail_required REGEX fi else feat_is_disabled REGEX fi if feat_yes MLE; then if [ -n "${have_c90amend1}" ]; then have_mle=1 echo '#define mx_HAVE_MLE' >> ${h} else feat_bail_required MLE fi else feat_is_disabled MLE fi if feat_yes HISTORY; then if [ -n "${have_mle}" ]; then echo '#define mx_HAVE_HISTORY' >> ${h} else feat_is_unsupported HISTORY fi else feat_is_disabled HISTORY fi if feat_yes KEY_BINDINGS; then if [ -n "${have_mle}" ]; then echo '#define mx_HAVE_KEY_BINDINGS' >> ${h} else feat_is_unsupported KEY_BINDINGS fi else feat_is_disabled KEY_BINDINGS fi if feat_yes TERMCAP; then # {{{ ADDINC= __termcaplib() { link_check termcap "termcap(5) (via ${4}${ADDINC})" \ "#define mx_HAVE_TERMCAP${3}" "${1}" "${ADDINC}" << _EOT #include #include ${2} #include #define UNCONST(P) ((void*)(unsigned long)(void const*)(P)) static int my_putc(int c){return putchar(c);} int main(void){ char buf[1024+512], cmdbuf[2048], *cpb, *r1; int r2 = OK, r3 = ERR; tgetent(buf, getenv("TERM")); cpb = cmdbuf; r1 = tgetstr(UNCONST("cm"), &cpb); tgoto(r1, 1, 1); r2 = tgetnum(UNCONST("Co")); r3 = tgetflag(UNCONST("ut")); tputs("cr", 1, &my_putc); return (r1 == NULL || r2 == -1 || r3 == 0); } _EOT } __terminfolib() { link_check terminfo "terminfo(5) (via ${2}${ADDINC})" \ '#define mx_HAVE_TERMCAP #define mx_HAVE_TERMCAP_CURSES #define mx_HAVE_TERMINFO' "${1}" "${ADDINC}" << _EOT #include #include #include #define UNCONST(P) ((void*)(unsigned long)(void const*)(P)) static int my_putc(int c){return putchar(c);} int main(void){ int er, r0, r1, r2; char *r3, *tp; er = OK; r0 = setupterm(NULL, 1, &er); r1 = tigetflag(UNCONST("bce")); r2 = tigetnum(UNCONST("colors")); r3 = tigetstr(UNCONST("cr")); tp = tparm(r3, NULL, NULL, 0,0,0,0,0,0,0); tputs(tp, 1, &my_putc); return (r0 == ERR || r1 == -1 || r2 == -2 || r2 == -1 || r3 == (char*)-1 || r3 == NULL); } _EOT } if feat_yes TERMCAP_VIA_TERMINFO; then ADDINC= do_me() { xbail= __terminfolib -ltinfo -ltinfo || __terminfolib -lcurses -lcurses || __terminfolib -lcursesw -lcursesw || xbail=y } do_me if [ -n "${xbail}" ] && [ -d /usr/local/include/ncurses ]; then ADDINC=' -I/usr/local/include/ncurses' do_me fi if [ -n "${xbail}" ] && [ -d /usr/include/ncurses ]; then ADDINC=' -I/usr/include/ncurses' do_me fi [ -n "${xbail}" ] && feat_bail_required TERMCAP_VIA_TERMINFO fi if [ -z "${have_terminfo}" ]; then ADDINC= do_me() { xbail= __termcaplib -ltermcap '' '' '-ltermcap' || __termcaplib -ltermcap '#include ' ' #define mx_HAVE_TERMCAP_CURSES' \ 'curses.h / -ltermcap' || __termcaplib -lcurses '#include ' ' #define mx_HAVE_TERMCAP_CURSES' \ 'curses.h / -lcurses' || __termcaplib -lcursesw '#include ' ' #define mx_HAVE_TERMCAP_CURSES' \ 'curses.h / -lcursesw' || xbail=y } do_me if [ -n "${xbail}" ] && [ -d /usr/local/include/ncurses ]; then ADDINC=' -I/usr/local/include/ncurses' do_me fi if [ -n "${xbail}" ] && [ -d /usr/include/ncurses ]; then ADDINC=' -I/usr/include/ncurses' do_me fi [ -n "${xbail}" ] && feat_bail_required TERMCAP if [ -n "${have_termcap}" ]; then run_check tgetent_null \ "tgetent(3) of termcap(5) takes NULL buffer" \ "#define mx_HAVE_TGETENT_NULL_BUF" << _EOT #include /* For C89 NULL */ #include #ifdef mx_HAVE_TERMCAP_CURSES # include #endif #include int main(void){ tgetent(NULL, getenv("TERM")); return 0; } _EOT fi fi unset ADDINC else # }}} feat_is_disabled TERMCAP feat_is_disabled TERMCAP_VIA_TERMINFO fi ## Final feat_def's XXX should be loop over OPTIONs feat_def ALWAYS_UNICODE_LOCALE feat_def AMALGAMATION 0 if feat_def CMD_CSOP; then feat_def CMD_VEXPR # v15compat: VEXPR needs CSOP for byte string ops YET else feat_bail_required CMD_VEXPR fi feat_def COLOUR feat_def CROSS_BUILD feat_def DOTLOCK feat_def FILTER_HTML_TAGSOUP if feat_yes FILTER_QUOTE_FOLD; then if [ -n "${have_c90amend1}" ] && [ -n "${have_wcwidth}" ]; then echo '#define mx_HAVE_FILTER_QUOTE_FOLD' >> ${h} else feat_bail_required FILTER_QUOTE_FOLD fi else feat_is_disabled FILTER_QUOTE_FOLD fi feat_def DOCSTRINGS feat_def ERRORS feat_def IMAP feat_def IMAP_SEARCH feat_def MAILDIR feat_def MD5 # XXX only sockets feat_def MTA_ALIASES feat_def NETRC feat_def POP3 feat_def SMIME feat_def SMTP feat_def SPAM_FILTER if feat_def SPAM_SPAMC; then if acmd_set i spamc; then echo "#define SPAM_SPAMC_PATH \"${i}\"" >> ${h} fi fi if feat_yes SPAM_SPAMC || feat_yes SPAM_FILTER; then echo '#define mx_HAVE_SPAM' >> ${h} else echo '/* mx_HAVE_SPAM */' >> ${h} fi feat_def UISTRINGS feat_def USE_PKGSYS feat_def ASAN_ADDRESS 0 feat_def ASAN_MEMORY 0 feat_def USAN 0 feat_def DEBUG 0 feat_def DEVEL 0 feat_def NOMEMDBG 0 ## Summarizing ${rm} -f ${tmp} squeeze_em ${inc} ${tmp} ${mv} ${tmp} ${inc} squeeze_em ${lib} ${tmp} ${mv} ${tmp} ${lib} echo "LIBS = `${cat} ${lib}`" >> ${mk} echo 'MX_LIBS = $(LIBS)' >> ${mk} echo "INCS = `${cat} ${inc}`" >> ${mk} echo 'MX_INCS = $(INCS)' >> ${mk} echo >> ${mk} # mk-config.h (which becomes mx/gen-config.h) ${mv} ${h} ${tmp} printf '#ifndef mx_GEN_CONFIG_H\n# define mx_GEN_CONFIG_H 1\n' > ${h} ${cat} ${tmp} >> ${h} printf '\n#ifdef mx_SOURCE\n' >> ${h} # Also need these for correct "second stage configuration changed" detection */ i= if (${CC} --version) >/dev/null 2>&1; then i=`${CC} --version 2>&1 | ${awk} ' BEGIN{l=""} {if(length($0)) {if(l) l = l "\\\\n"; l = l "@" $0}} END{gsub(/"/, "", l); print "\\\\n" l} '` elif (${CC} -v) >/dev/null 2>&1; then i=`${CC} -v 2>&1 | ${awk} ' BEGIN{l=""} {if(length($0)) {if(l) l = l "\\\\n"; l = l "@" $0}} END{gsub(/"/, "", l); print "\\\\n" l} '` fi i=`printf '%s %s %s\n' "${CC}" "${CFLAGS}" "${i}"` printf '#define VAL_BUILD_CC "%s"\n' "$i" >> ${h} i=`string_to_char_array "${i}"` printf '#define VAL_BUILD_CC_ARRAY %s\n' "$i" >> ${h} i=`printf '%s %s %s\n' "${CC}" "${LDFLAGS}" "\`${cat} ${lib}\`"` printf '#define VAL_BUILD_LD "%s"\n' "$i" >> ${h} i=`string_to_char_array "${i}"` printf '#define VAL_BUILD_LD_ARRAY %s\n' "$i" >> ${h} i=${COMMLINE} printf '#define VAL_BUILD_REST "%s"\n' "$i" >> ${h} i=`string_to_char_array "${i}"` printf '#define VAL_BUILD_REST_ARRAY %s\n' "$i" >> ${h} # Throw away all temporaries ${rm} -rf ${tmp0}.* ${tmp0}* # Create the string that is used by *features* and `version'. # Take this nice opportunity and generate a visual listing of included and # non-included features for the person who runs the configuration echo 'The following features are included (+) or not (-):' > ${tmp} set -- ${OPTIONS_DETECT} ${OPTIONS} ${OPTIONS_XTRA} printf '/* The "feature string" */\n' >> ${h} # Prefix sth. to avoid that + is expanded by *folder* (echo $features) printf '#define VAL_FEATURES_CNT '${#}'\n#define VAL_FEATURES ",' >> ${h} sep= for opt do sdoc=`option_doc_of ${opt}` [ -z "${sdoc}" ] && continue sopt="`echo ${opt} | ${tr} '[A-Z]_' '[a-z]-'`" feat_yes ${opt} && sign=+ || sign=- printf -- "${sep}${sign}${sopt}" >> ${h} sep=',' printf ' %s %s: %s\n' ${sign} ${sopt} "${sdoc}" >> ${tmp} done # TODO instead of using sh+tr+awk+printf, use awk, drop option_doc_of, inc here #exec 5>&1 >>${h} #${awk} -v opts="${OPTIONS_DETECT} ${OPTIONS} ${OPTIONS_XTRA}" \ # -v xopts="${XOPTIONS_DETECT} ${XOPTIONS} ${XOPTIONS_XTRA}" \ printf '"\n' >> ${h} # Create the real mk-config.mk # Note we cannot use explicit ./ filename prefix for source and object # pathnames because of a bug in bmake(1) msg 'Creating object make rules' (cd "${SRCDIR}"; ${SHELL} ../mk/make-rules.sh ps-dotlock/*.c) >> ${mk} mx_obj= su_obj= if feat_no AMALGAMATION; then (cd "${SRCDIR}"; ${SHELL} ../mk/make-rules.sh su/*.c) >> ${mk} (cd "${SRCDIR}"; ${SHELL} ../mk/make-rules.sh mx/*.c) >> ${mk} mx_obj='$(MX_C_OBJ)' su_obj='$(SU_C_OBJ)' else (cd "${SRCDIR}"; COUNT_MODE=0 ${SHELL} ../mk/make-rules.sh mx/*.c) >> ${mk} mx_obj=mx-main.o printf 'mx-main.o: gen-mime-types.h' >> ${mk} printf '\n#endif /* mx_SOURCE */\n' >> ${h} printf '/* mx_HAVE_AMALGAMATION: include sources */\n' >> ${h} printf '#elif mx_GEN_CONFIG_H + 0 == 1\n' >> ${h} printf '# undef mx_GEN_CONFIG_H\n' >> ${h} printf '# define mx_GEN_CONFIG_H 2\n#ifdef mx_SOURCE\n' >> ${h} for i in `printf '%s\n' "${SRCDIR}"su/*.c | ${sort}`; do i=`basename "${i}"` printf '# include "%s%s"\n' "${SRCDIR}su/" "${i}" >> ${h} done echo >> ${mk} for i in `printf '%s\n' "${SRCDIR}"mx/*.c | ${sort}`; do i=`basename "${i}"` if [ "${i}" = main.c ]; then continue fi printf '# include "%s%s"\n' "${SRCDIR}mx/" "${i}" >> ${h} done echo >> ${mk} fi printf 'OBJ = %s\n' "${mx_obj} ${su_obj}" >> "${mk}" printf '#endif /* mx_SOURCE */\n#endif /* mx_GEN_CONFIG_H */\n' >> ${h} echo >> ${mk} ${cat} "${TOPDIR}"mk/make-config.in >> ${mk} ## Finished! # We have completed the new configuration header. Check whether *really* # Do the "second stage configuration changed" detection, exit if nothing to do if [ -f ${oldh} ]; then if ${cmp} ${h} ${oldh} >/dev/null 2>&1; then ${mv} -f ${oldh} ${h} msg 'Effective configuration is up-to-date' exit 0 fi config_updated=1 ${rm} -f ${oldh} msg 'Effective configuration has been updated..' fi if [ -n "${config_updated}" ]; then msg 'Wiping away old objects and such..' ( cd "${OBJDIR}"; oldmk=`${basename} ${oldmk}`; ${MAKE} -f ${oldmk} clean ) fi # Ensure user edits in mx-config.h are incorporated, and that our generated # mk-config.h becomes the new public mx/gen-config.h. ${cp} -f "${CWDDIR}"mx-config.h "${CWDDIR}"include/mx/config.h ${cp} -f ${h} "${CWDDIR}"include/mx/gen-config.h msg '' while read l; do msg "${l}"; done < ${tmp} msg 'Setup:' msg ' . System-wide resource file: %s/%s' "${VAL_SYSCONFDIR}" "${VAL_SYSCONFRC}" msg ' . bindir: %s' "${VAL_BINDIR}" if feat_yes DOTLOCK; then msg ' . libexecdir: %s' "${VAL_LIBEXECDIR}" fi msg ' . mandir: %s' "${VAL_MANDIR}" msg ' . M(ail)T(ransfer)A(gent): %s (argv0: %s)' "${VAL_MTA}" "${VAL_MTA_ARGV0}" msg ' . $MAIL spool directory: %s' "${VAL_MAIL}" msg '' if [ -n "${have_fnmatch}" ] && [ -n "${have_fchdir}" ]; then exit 0 fi msg 'Remarks:' if [ -z "${have_fnmatch}" ]; then msg ' . The function fnmatch(3) could not be found.' msg ' Filename patterns like wildcard are not supported on your system' fi if [ -z "${have_fchdir}" ]; then msg ' . The function fchdir(2) could not be found.' msg ' We will use chdir(2) instead.' msg ' This is a problem only if the current working directory is changed' msg ' while this program is inside of it' fi msg '' # s-it-mode s-nail-14.9.15/mk/make-install.sh000066400000000000000000000047201352610246600164120ustar00rootroot00000000000000#!/bin/sh - #@ Install ourselves, and generate uninstall script. # # Public Domain LC_ALL=C __mkdir() { _dir="${DESTDIR}${1}" if [ -d "${_dir}" ]; then :; else ${mkdir} -m 0755 -p "${_dir}" fi } __copyfile() { _mode=${1} _src=${2} _xdst=${3} _dst="${DESTDIR}${3}" echo "rm -f \"\${DESTDIR}${_xdst}\"" >> \ "${OBJDIR}/${VAL_UAGENT}-uninstall.sh" ${cp} -f "${_src}" "${_dst}" ${chmod} ${_mode} "${_dst}" } __copychownfile() { _mode=${1} _ident=${2} _src=${3} _xdst=${4} _dst="${DESTDIR}${4}" echo "rm -f \"\${DESTDIR}${_xdst}\"" >> \ "${OBJDIR}/${VAL_UAGENT}-uninstall.sh" ${cp} -f "${_src}" "${_dst}" ${chown} ${_ident} "${_dst}" || true ${chmod} ${_mode} "${_dst}" } __stripfile() { _file=${1} if [ "${OPT_DEBUG}" != 0 ]; then :; elif [ -n "${HAVE_STRIP}" ]; then ${strip} "${_file}" fi } cd "${CWDDIR}" || exit 11 echo '#!/bin/sh -' > "${OBJDIR}/${VAL_UAGENT}-uninstall.sh" echo '#@ Uninstall script for '"${VAL_UAGENT}" >> \ "${OBJDIR}/${VAL_UAGENT}-uninstall.sh" echo >> "${OBJDIR}/${VAL_UAGENT}-uninstall.sh" echo 'DESTDIR="${DESTDIR}"' >> "${OBJDIR}/${VAL_UAGENT}-uninstall.sh" echo 'DESTDIR=' >> "${OBJDIR}/${VAL_UAGENT}-uninstall.sh" echo >> "${OBJDIR}/${VAL_UAGENT}-uninstall.sh" [ -n "${DESTDIR}" ] && __mkdir '' __mkdir "${VAL_BINDIR}" __mkdir "${VAL_MANDIR}/man1" __mkdir "${VAL_SYSCONFDIR}" __stripfile "${OBJDIR}"/"${VAL_UAGENT}" __copyfile 0555 "${OBJDIR}"/"${VAL_UAGENT}" "${VAL_BINDIR}"/"${VAL_UAGENT}" __copyfile 0444 "${OBJDIR}"/uman.1 "${VAL_MANDIR}"/man1/"${VAL_UAGENT}".1 if [ -f "${DESTDIR}${VAL_SYSCONFDIR}/${VAL_SYSCONFRC}" ]; then :; else __copyfile 0444 "${OBJDIR}"/urc.rc "${VAL_SYSCONFDIR}/${VAL_SYSCONFRC}" fi if [ "${OPT_DOTLOCK}" != 0 ]; then __mkdir "${VAL_LIBEXECDIR}" __stripfile "${OBJDIR}"/"${VAL_PS_DOTLOCK}" m='o=rx' o= #if [ -n "${_____PRIVSEP_GROUP}" ]; then # m="g=rxs,${m}" o=":${VAL_PRIVSEP_GROUP}" #else m="g=rx,${m}" #fi if [ -n "${VAL_PS_DOTLOCK_USER}" ]; then m="u=rxs,${m}" o="${VAL_PS_DOTLOCK_USER}${o}" else m="u=rx,${m}" fi; __copychownfile "${m}" "${o}" \ "${OBJDIR}"/"${VAL_PS_DOTLOCK}" "${VAL_LIBEXECDIR}/${VAL_PS_DOTLOCK}" fi; if [ -z "${DESTDIR}" ]; then __copyfile 0555 "${OBJDIR}/${VAL_UAGENT}-uninstall.sh" \ "${VAL_BINDIR}/${VAL_UAGENT}-uninstall.sh" else echo "rm -f \"\${DESTDIR}${VAL_BINDIR}/${VAL_UAGENT}\"-uninstall.sh" >> \ "${OBJDIR}/${VAL_UAGENT}-uninstall.sh" fi # s-sh-mode s-nail-14.9.15/mk/make-man.sh000066400000000000000000000027641352610246600155250ustar00rootroot00000000000000#!/bin/sh - #@ Adjust manual according to configuration settings (sourced in environment). # # Public Domain LC_ALL=C VERSION=`${SHELL} "${TOPDIR}"mk/make-version.sh query` export VERSION < "$1" > "$2" exec ${awk} ' BEGIN {written = 0} /\.\\"--MKMAN-START--/,/\.\\"--MKMAN-END--/{ if(written++ != 0) next OFS = "" print ".ds VV \\\\%v", ENVIRON["VERSION"] un = toupper(ENVIRON["VAL_UAGENT"]) ln = tolower(un) cn = toupper(substr(ln, 1, 1)) substr(ln, 2) print ".ds UU \\\\%", un print ".ds UA \\\\%", cn print ".ds uA \\\\%", ln path = ENVIRON["VAL_SYSCONFRC"] gsub("/", "/\\:", path) print ".ds UR \\\\%", path path = ENVIRON["VAL_MAILRC"] gsub("/", "/\\:", path) print ".ds ur \\\\%", path path = ENVIRON["VAL_DEAD"] gsub("/", "/\\:", path) print ".ds VD \\\\%", path path = ENVIRON["VAL_MBOX"] gsub("/", "/\\:", path) print ".ds VM \\\\%", path path = ENVIRON["VAL_NETRC"] gsub("/", "/\\:", path) print ".ds VN \\\\%", path path = ENVIRON["VAL_TMPDIR"] gsub("/", "/\\:", path) print ".ds VT \\\\%", path path = ENVIRON["VAL_MIME_TYPES_SYS"] gsub("/", "/\\:", path) print ".ds vS \\\\%", path path = ENVIRON["VAL_MIME_TYPES_USR"] gsub("/", "/\\:", path) print ".ds vU \\\\%", path OFS = " " next } {print} ' # s-sh-mode s-nail-14.9.15/mk/make-mime-types.sh000066400000000000000000000033241352610246600170340ustar00rootroot00000000000000#!/bin/sh - #@ Generate builtin mime.types (configuration sourced in environment). # # Public Domain LC_ALL=C < "$1" > "$2" exec ${awk} ' function add(mt, ln){ gsub(/[ ]]+/, " ", ln); i = split(ln, i_a); e = ""; for(j = 1; j <= i; ++j){ k = i_a[j]; e = (e ? e " " : "") k } if(e){ if(e_a[mt]) e_a[mt] = e_a[mt] " "; e_a[mt] = e_a[mt] e } } /^[ ]*#/{next} /^[ ]*$/{ltype = ""; next} /^[ ]/{ if(!ltype) print "FAULTY CONTINUATION: " $0 >> "/dev/stderr"; add(ltype, $0); next } /^(\?([thHq])? )?[a-zA-Z]/{ if($1 ~ /^\?([thHq])?$/){ pa = $1; $1 = $2; $2 = "" }else pa = ""; if($1 !~ /^([0-9a-zA-Z]+)\/([0-9a-zA-Z_+-]+)$/) print "FAULTY MIME TYPE: <" $1 ">" >> "/dev/stderr"; ltype = $1; $1 = ""; if(pa) p_a[ltype] = pa; if(!nt_a[ltype]) no_a[++no_ai] = nt_a[ltype] = ltype; add(ltype, $0) } END{ for(z = 1; z <= no_ai; ++z){ t = no_a[z]; j = index(t, "/"); mt = toupper(substr(t, 1, j - 1)); j = substr(t, j + 1); l = length(j); if(!p_a[t]) mt = "_MT_" mt; else{ tm = p_a[t]; if(tm ~ /^\?t?$/) mt = "_MT_" mt " | a_MT_TM_PLAIN"; else if(tm ~ /^\?h$/) mt = "_MT_" mt " | a_MT_TM_SOUP_h"; else if(tm ~ /^\?H$/) mt = "_MT_" mt " | a_MT_TM_SOUP_H"; else if(tm ~ /^\?q$/) mt = "_MT_" mt " | a_MT_TM_QUIET"; } print " {" mt ", " l ", \"" j e_a[t] "\"}," } } ' # s-sh-mode s-nail-14.9.15/mk/make-news-anchors.sh000066400000000000000000000065461352610246600173630ustar00rootroot00000000000000#!/bin/sh - #@ make-news-anchors.sh #@ Expand *XY*# / $XY# / -XY# / `XY'# / `~XY'# / "XY"# style anchors #@ so that the anchor matches the number given in ANCHORFILE. #@ The number sign may be followed by space, question-mark ? or a number. #@ We always expand STDIN to STDOUT, but only in the range #@ ChangeLog .. ^(Appendix|git\(1\) shortlog) (or EOF, of course) #@ The ANCHORFILE can be produced by #@ $ < manual.mdoc mdocmx.sh | #@ MDOCMX_ENABLE=1 s-roff -U -mdoc -dmx-anchor-dump=/tmp/anchors \ #@ -dmx-toc-force=tree >/dev/null # Public Domain : ${awk:=awk} syno() { if [ ${#} -gt 0 ]; then echo >&2 "ERROR: ${*}" echo >&2 fi echo >&2 'Synopsis: make-news-anchors.sh ANCHORFILE' exit 1 } [ ${#} -eq 1 ] || syno [ -f "${1}" ] || syno 'the given anchorfile does not exist' APO=\' ${awk} -v anchorfile="${1}" ' BEGIN{hot = 0} /^(NOTES|ChangeLog)/{ hot = 1 print next } /^(Appendix|git\(1\) shortlog)/{ hot = -1 print next } { if(hot <= 0){ print next } any = 0 res = "" s = $0 while(match(s, /(^|\(|[[:space:]]+)("[^"]+"|\*[^\*]+\*|`[^'${APO}']+'${APO}'|[-~][-#\/:_.[:alnum:]]+|\$[_[:alnum:]]+)#(\?|[0-9]+)?/)) { any = 1 pre = (RSTART > 1) ? substr(s, 1, RSTART - 1) : "" mat = substr(s, RSTART, RLENGTH) s = substr(s, RSTART + RLENGTH) # Unfortunately groups are not supported if(match(mat, /^(\(|[[:space:]]+)/) != 0 && RLENGTH > 0){ pre = pre substr(mat, 1, RLENGTH) mat = substr(mat, RSTART + RLENGTH) } match(mat, /#(\?|[0-9]+)?$/) mat = substr(mat, 1, RSTART - 1) res = res pre mat "#" if(mat ~ /^`/){ # Cm, Ic mat = substr(mat, 2, length(mat) - 2) t = 1 }else if(mat ~ /^\*/){ # Va mat = substr(mat, 2, length(mat) - 2) t = 2 }else if(mat ~ /^\$/){ # Ev, Dv mat = substr(mat, 2, length(mat) - 1) t = 3 }else if(mat ~ /^-/){ # Fl mat = substr(mat, 2, length(mat) - 1) t = 4 }else if(mat ~ /^"/){ # Sh, Ss. But: "catch-all" mat = substr(mat, 2, length(mat) - 2) t = 5 }else t = 0 # Insufficient, of course gsub("\\\\", "\\e", mat) ano = got = 0 while(getline < anchorfile){ if(t == 1){ if($2 != "Cm" && $2 != "Ic") continue }else if(t == 2){ if($2 != "Va") continue }else if(t == 3){ if($2 != "Ev" && $2 != "Dv") continue }else if(t == 4){ if($2 != "Fl") continue }else if(t == 0){ if($2 == "Cm" || $2 == "Ic" || $2 == "Va" || $2 == "Ev" || $2 == "Dv" || $2 == "Fl") continue } if(!got) ano = $1 $1 = $2 = "" match($0, /^[[:space:]]*/) $0 = substr($0, RLENGTH + 1) if($0 == mat){ if(got) print "WARN: ambiguous: \"" mat "\"" > "/dev/stderr" got = 1 } } close(anchorfile) if(!got){ print "ERROR: no anchor for \"" mat "\"" > "/dev/stderr" res = res "?" }else res = res ano } if(any && length(s)) res = res s print any ? res : s } ' # s-sh-mode s-nail-14.9.15/mk/make-okey-map.pl000066400000000000000000000351441352610246600164730ustar00rootroot00000000000000#!/usr/bin/env perl require 5.008_001; use utf8; #@ Parse 'enum okeys' from nail.h and create gen-okeys.h. # Public Domain my $IN = 'include/mx/nail.h'; my $OUT = 'src/mx/gen-okeys.h'; # We use `vexpr' for hashing my $MAILX = 'LC_ALL=C s-nail -#:/'; # Acceptable "longest distance" from hash-modulo-index to key my $MAXDISTANCE_PENALTY = 6; # Generate a more verbose output. Not for shipout versions. my $VERB = 1; ## -- >8 -- 8< -- ## use diagnostics -verbose; use strict; use warnings; use FileHandle; use IPC::Open2; use sigtrap qw(handler cleanup normal-signals); my ($S, @ENTS, $CTOOL, $CTOOL_EXE) = ($VERB ? ' ' : ''); sub main_fun{ if(@ARGV) {$VERB = 0; $S = ''} parse_in_h(); create_c_tool(); hash_em(); dump_map(); reverser(); cleanup(undef); exit 0 } sub cleanup{ die "$CTOOL_EXE: couldn't unlink: $^E" if $CTOOL_EXE && -f $CTOOL_EXE && 1 != unlink $CTOOL_EXE; die "$CTOOL: couldn't unlink: $^E" if $CTOOL && -f $CTOOL && 1 != unlink $CTOOL; die "Terminating due to signal $_[0]" if $_[0] }; sub basen{ my $n = $_[0]; $n =~ s/^(.*\/)?([^\/]+)$/$2/; $n } sub parse_in_h{ die "$IN: open: $^E" unless open F, '<', $IN; my ($init) = (0); while(){ # Only want the enum okeys content if(/^enum okeys/) {$init = 1; next} if(/^};/) {if($init) {$init = 2; last}; next} $init || next; # Ignore empty and comment lines /^$/ && next; /^\s*\/\*/ && next; # An entry may have a comment with special directives /^\s*(\w+),?\s*(?:\/\*\s*(?:{(.*)})\s*\*\/\s*)?$/; next unless $1; my ($k, $x) = ($1, $2); my %vals; $vals{enum} = $k; $vals{bool} = ($k =~ /^ok_b/ ? 1 : 0); $k = $1 if $k =~ /^ok_[bv]_(.+)$/; $k =~ s/_/-/g; $vals{name} = $k; if($x){ # {\}: overlong entry, placed on follow line if($x =~ /\s*\\\s*$/){ $_ = ; die "$IN: missing continuation line" unless $_; /^\s*\/\*\s*{(.*)}\s*\*\/\s*$/; $x = $1; die "$IN: invalid continuation line" unless $x } while($x && $x =~ /^([^,]+?)(?:,(.*))?$/){ $x = $2; $1 =~ /([^=]+)=(.+)/; die "Unsupported special directive: $1" if($1 ne 'name' && $1 ne 'virt' && $1 ne 'vip' && $1 ne 'rdonly' && $1 ne 'nodel' && $1 ne 'i3val' && $1 ne 'defval' && $1 ne 'import' && $1 ne 'env' && $1 ne 'nolopts' && $1 ne 'notempty' && $1 ne 'nocntrls' && $1 ne 'num' && $1 ne 'posnum' && $1 ne 'lower' && $1 ne 'chain' && $1 ne 'obsolete'); $vals{$1} = $2 } } push @ENTS, \%vals } die 'I do not see the expected content' unless $init == 2; close F } sub create_c_tool{ $CTOOL = './tmp-okey-tool-' . $$ . '.c'; $CTOOL_EXE = $CTOOL . '.exe'; die "$CTOOL: open: $^E" unless open F, '>', $CTOOL; print F '#define MAX_DISTANCE_PENALTY ', $MAXDISTANCE_PENALTY, "\n"; # >>>>>>>>>>>>>>>>>>> print F <<'_EOT'; #define a__CREATE_OKEY_MAP_PL #include #include #include #include #define NELEM(A) (sizeof(A) / sizeof(A[0])) #define u32 uint32_t #define u16 uint16_t #define u8 uint8_t enum a_amv_var_flags{ a_AMV_VF_NONE = 0, /* The basic set of flags, also present in struct a_amv_var_map.avm_flags */ a_AMV_VF_BOOL = 1u<<0, /* ok_b_* */ a_AMV_VF_CHAIN = 1u<<1, /* Is variable chain (-USER{,@HOST} variants) */ a_AMV_VF_VIRT = 1u<<2, /* "Stateless" automatic variable */ a_AMV_VF_VIP = 1u<<3, /* Wants _var_check_vips() evaluation */ a_AMV_VF_RDONLY = 1u<<4, /* May not be set by user */ a_AMV_VF_NODEL = 1u<<5, /* May not be deleted */ a_AMV_VF_I3VAL = 1u<<6, /* Has an initial value */ a_AMV_VF_DEFVAL = 1u<<7, /* Has a default value */ a_AMV_VF_IMPORT = 1u<<8, /* Import ONLY from env (pre n_PSO_STARTED) */ a_AMV_VF_ENV = 1u<<9, /* Update environment on change */ a_AMV_VF_NOLOPTS = 1u<<10, /* May not be tracked by `localopts' */ a_AMV_VF_NOTEMPTY = 1u<<11, /* May not be assigned an empty value */ a_AMV_VF_NUM = 1u<<12, /* Value must be a 32-bit number */ a_AMV_VF_POSNUM = 1u<<13, /* Value must be positive 32-bit number */ a_AMV_VF_LOWER = 1u<<14, /* Values will be stored in lowercase version */ a_AMV_VF_OBSOLETE = 1u<<15, /* Is obsolete? */ a_AMV_VF__MASK = (1u<<(15+1)) - 1, /* Extended flags, not part of struct a_amv_var_map.avm_flags */ a_AMV_VF_EXT_LOCAL = 1u<<23, /* `local' */ a_AMV_VF_EXT_LINKED = 1u<<24, /* `environ' link'ed */ a_AMV_VF_EXT_FROZEN = 1u<<25, /* Has been set by -S,.. */ a_AMV_VF_EXT_FROZEN_UNSET = 1u<<26, /* ..and was used to unset a variable */ a_AMV_VF_EXT__FROZEN_MASK = a_AMV_VF_EXT_FROZEN | a_AMV_VF_EXT_FROZEN_UNSET, a_AMV_VF_EXT__MASK = (1u<<(26+1)) - 1 }; struct a_amv_var_map{ u32 avm_hash; u16 avm_keyoff; u16 avm_flags; /* enum a_amv_var_flags */ }; struct a_amv_var_chain_map_bsrch{ char avcmb_prefix[4]; u16 avcmb_chain_map_off; u16 avcmb_chain_map_eokey; /* This is an enum okey */ }; struct a_amv_var_chain_map{ u16 avcm_keyoff; u16 avcm_okey; }; #define CTA(A,S) _EOT print F '#include "', $OUT, "\"\n\n"; print F <<'_EOT'; static u8 seen_wraparound; static size_t longest_distance; static size_t next_prime(size_t no){ /* blush (brute force) */ jredo: ++no; for(size_t i = 3; i < no; i += 2) if(no % i == 0) goto jredo; return no; } static size_t * reversy(size_t size){ struct a_amv_var_map const *vmp = a_amv_var_map, *vmaxp = vmp + NELEM(a_amv_var_map); size_t ldist = 0, *arr; arr = malloc(sizeof *arr * size); for(size_t i = 0; i < size; ++i) arr[i] = NELEM(a_amv_var_map); seen_wraparound = 0; longest_distance = 0; while(vmp < vmaxp){ u32 hash = vmp->avm_hash, i = hash % size, l; for(l = 0; arr[i] != NELEM(a_amv_var_map); ++l) if(++i == size){ seen_wraparound = 1; i = 0; } if(l > longest_distance) longest_distance = l; arr[i] = (size_t)(vmp++ - a_amv_var_map); } return arr; } int main(int argc, char **argv){ size_t *arr, size = NELEM(a_amv_var_map); fprintf(stderr, "Starting reversy, okeys=%zu\n", size); for(;;){ arr = reversy(size = next_prime(size)); fprintf(stderr, " - size=%zu longest_distance=%zu seen_wraparound=%d\n", size, longest_distance, seen_wraparound); if(longest_distance <= MAX_DISTANCE_PENALTY) break; free(arr); } printf( "#define a_AMV_VAR_REV_ILL %zuu\n" "#define a_AMV_VAR_REV_PRIME %zuu\n" "#define a_AMV_VAR_REV_LONGEST %zuu\n" "#define a_AMV_VAR_REV_WRAPAROUND %d\n" "static %s const a_amv_var_revmap[a_AMV_VAR_REV_PRIME] = {\n%s", NELEM(a_amv_var_map), size, longest_distance, seen_wraparound, argv[1], (argc > 2 ? " " : "")); for(size_t i = 0; i < size; ++i) printf("%s%zuu", (i == 0 ? "" : (i % 10 == 0 ? (argc > 2 ? ",\n " : ",\n") : (argc > 2 ? ", " : ","))), arr[i]); printf("\n};\n"); return 0; } _EOT # <<<<<<<<<<<<<<<<<<< close F } sub hash_em{ die "hash_em: open: $^E" unless my $pid = open2 *RFD, *WFD, $MAILX; foreach my $e (@ENTS){ print WFD "vexpr hash32 $e->{name}\n"; my $h = ; chomp $h; $e->{hash} = $h } print WFD "x\n"; waitpid $pid, 0; } sub dump_map{ die "$OUT: open: $^E" unless open F, '>', $OUT; print F '/*@ ', scalar basen($OUT), ', generated by ', scalar basen($0), ".\n *@ See accmacvar.c for more */\n\n"; # Dump the names sequentially (in input order), create our map entry along print F 'static char const a_amv_var_names[] = {', "\n"; my ($i, $alen) = (0, 0); my (%virts, %defvals, %i3vals, %chains); foreach my $e (@ENTS){ $e->{keyoff} = $alen; my $k = $e->{name}; my $l = length $k; my $a = join '\',\'', split(//, $k); my (@fa); if($e->{bool}) {push @fa, 'a_AMV_VF_BOOL'} if($e->{virt}){ # Virtuals are implicitly rdonly and nodel $e->{rdonly} = $e->{nodel} = 1; $virts{$k} = $e; push @fa, 'a_AMV_VF_VIRT' } if($e->{vip}) {push @fa, 'a_AMV_VF_VIP'} if($e->{rdonly}) {push @fa, 'a_AMV_VF_RDONLY'} if($e->{nodel}) {push @fa, 'a_AMV_VF_NODEL'} if(defined $e->{i3val}){ $i3vals{$k} = $e; push @fa, 'a_AMV_VF_I3VAL' } if($e->{defval}){ die "Boolean with default value: $k" if $e->{bool}; $defvals{$k} = $e; push @fa, 'a_AMV_VF_DEFVAL' } if($e->{import}){ $e->{env} = 1; push @fa, 'a_AMV_VF_IMPORT' } if($e->{env}) {push @fa, 'a_AMV_VF_ENV'} if($e->{nolopts}) {push @fa, 'a_AMV_VF_NOLOPTS'} if($e->{notempty}) {push @fa, 'a_AMV_VF_NOTEMPTY'} if($e->{nocntrls}) {push @fa, 'a_AMV_VF_NOCNTRLS'} if($e->{num}) {push @fa, 'a_AMV_VF_NUM'} if($e->{posnum}) {push @fa, 'a_AMV_VF_POSNUM'} if($e->{lower}) {push @fa, 'a_AMV_VF_LOWER'} if($e->{chain}){ $chains{$k} = $e; push @fa, 'a_AMV_VF_CHAIN' } if($e->{obsolete}) {push @fa, 'a_AMV_VF_OBSOLETE'} $e->{flags} = \@fa; my $f = join('|', @fa); $f = ', ' . $f if length $f; print F "${S}/* $i. [$alen]+$l $k$f */\n" if $VERB; print F "${S}'$a','\\0',\n"; ++$i; $alen += $l + 1 } print F '};', "\n#define a_AMV_VAR_NAME_KEY_MAXOFF ${alen}U\n\n"; # Create the management map print F 'CTA(a_AMV_VF_NONE == 0, "Value not 0 as expected");', "\n"; print F 'static struct a_amv_var_map const a_amv_var_map[] = {', "\n"; foreach my $e (@ENTS){ my $f = $VERB ? 'a_AMV_VF_NONE' : '0'; my $fa = join '|', @{$e->{flags}}; $f .= '|' . $fa if length $fa; print F "${S}{$e->{hash}u, $e->{keyoff}u, $f},"; if($VERB) {print F "${S}/* $e->{name} */\n"} else {print F "\n"} } print F '};', "\n\n"; # The rest not to be injected for this generator script print F <<_EOT; #ifndef a__CREATE_OKEY_MAP_PL # ifdef mx_HAVE_PUTENV # define a_X(X) X # else # define a_X(X) # endif _EOT # if(%chains){ my (@prefixes,$last_pstr,$last_pbeg,$last_pend,$i); print F 'CTAV(4 == ', 'FIELD_SIZEOF(struct a_amv_var_chain_map_bsrch, avcmb_prefix));', "\n"; print F 'static struct a_amv_var_chain_map const ', 'a_amv_var_chain_map[] = {', "\n"; $last_pstr = ""; $last_pend = "n_OKEYS_MAX"; $last_pbeg = $i = 0; foreach my $e (sort keys %chains){ $e = $chains{$e}; print F "${S}{$e->{keyoff}, $e->{enum}},\n"; die "Chains need length of at least 4 bytes: $e->{name}" if length $e->{name} < 4; my $p = substr $e->{name}, 0, 4; if($p ne $last_pstr){ push @prefixes, [$last_pstr, $last_pbeg, $last_pend] if $i > 0; $last_pstr = $p; $last_pbeg = $i } $last_pend = $e->{enum}; ++$i } push @prefixes, [$last_pstr, $last_pbeg, $last_pend] if $last_pstr ne ""; print F '};', "\n"; print F '#define a_AMV_VAR_CHAIN_MAP_CNT ', scalar %chains, "\n\n"; print F 'static struct a_amv_var_chain_map_bsrch const ', 'a_amv_var_chain_map_bsrch[] = {', "\n"; foreach my $e (@prefixes){ print F "${S}{\"$e->[0]\", $e->[1], $e->[2]},\n" } print F '};', "\n"; print F '#define a_AMV_VAR_CHAIN_MAP_BSRCH_CNT ', scalar @prefixes, "\n\n" } # Virtuals are _at least_ the versioning variables # The problem is that struct var uses a variable sized character buffer # which cannot be initialized in a conforming way :( print F '/* Unfortunately init of varsized buffer impossible: ' . 'define "subclass"es */' . "\n"; my @skeys = sort keys %virts; foreach(@skeys){ my $e = $virts{$_}; $e->{vname} = $1 if $e->{enum} =~ /ok_._(.*)/; $e->{vstruct} = "var_virt_$e->{vname}"; print F "static char const a_amv_$e->{vstruct}_val[] = {$e->{virt}};\n"; print F "static struct{\n"; print F "${S}struct a_amv_var *av_link;\n"; print F "${S}char const *av_value;\n"; print F "${S}a_X(char *av_env;)\n"; print F "${S}u32 av_flags;\n"; print F "${S}char const av_name[", length($e->{name}), " +1];\n"; my $f = $VERB ? 'a_AMV_VF_NONE' : '0'; my $fa = join '|', @{$e->{flags}}; $f .= '|' . $fa if length $fa; print F "} const a_amv_$e->{vstruct} = ", "{NIL, a_amv_$e->{vstruct}_val, a_X(0 su_COMMA) $f, ", "\"$e->{name}\"};\n\n" } print F "\n"; print F 'static struct a_amv_var_virt const a_amv_var_virts[] = {', "\n"; foreach(@skeys){ my $e = $virts{$_}; my $n = $1 if $e->{enum} =~ /ok_._(.*)/; print F "${S}{$e->{enum}, {0,}, (void const*)&a_amv_$e->{vstruct}},\n"; } print F "};\n"; print F '#define a_AMV_VAR_VIRTS_CNT ', scalar @skeys, "\n"; # First-time-init values @skeys = sort keys %i3vals; print F "\n"; print F 'static struct a_amv_var_defval const a_amv_var_i3vals[] = {', "\n"; foreach(@skeys){ my $e = $i3vals{$_}; print F "${S}{", $e->{enum}, ', {0,}, ', (!$e->{bool} ? $e->{i3val} : "NIL"), "},\n" } print F "};\n"; print F '#define a_AMV_VAR_I3VALS_CNT ', scalar @skeys, "\n"; # Default values @skeys = sort keys %defvals; print F "\n"; print F 'static struct a_amv_var_defval const a_amv_var_defvals[] = {', "\n"; foreach(@skeys){ my $e = $defvals{$_}; print F "${S}{", $e->{enum}, ', {0,}, ', (!$e->{bool} ? $e->{defval} : "NIL"), "},\n" } print F "};\n"; print F '#define a_AMV_VAR_DEFVALS_CNT ', scalar @skeys, "\n"; # Special var backing [#@*?]|[1-9][0-9]*|0 $i = 0; print F "\n"; foreach my $e (@ENTS){ if($e->{name} eq '--special-param'){ print F "#define a_AMV_VAR__SPECIAL_PARAM_MAP_IDX ${i}u\n" } # The rest are only speedups elsif($e->{name} eq '?'){ print F "#define a_AMV_VAR__QM_MAP_IDX ${i}u\n" }elsif($e->{name} eq '!'){ print F "#define a_AMV_VAR__EM_MAP_IDX ${i}u\n" } ++$i } print F "\n"; print F "# undef a_X\n"; print F "#endif /* !a__CREATE_OKEY_MAP_PL */\n"; die "$OUT: close: $^E" unless close F } sub reverser{ my $argv2 = $VERB ? ' verb' : ''; system("\$CC -I. -o $CTOOL_EXE $CTOOL"); my $t = (@ENTS < 0xFF ? 'u8' : (@ENTS < 0xFFFF ? 'u16' : 'u32')); `$CTOOL_EXE $t$argv2 >> $OUT` } {package main; main_fun()} # s-it-mode s-nail-14.9.15/mk/make-rc.sh000066400000000000000000000012201352610246600153400ustar00rootroot00000000000000#!/bin/sh - #@ Adjust rc file according to configuration settings (sourced in environment). # # Public Domain LC_ALL=C VERSION=`${SHELL} "${TOPDIR}"mk/make-version.sh query` export VERSION < "$1" > "$2" exec ${awk} ' BEGIN {written = 0} /^#--MKRC-START--/,/^#--MKRC-END--/{ if(written == 1) next written = 1 OFS = "" ln = tolower(ENVIRON["VAL_UAGENT"]) cn = toupper(substr(ln, 1, 1)) substr(ln, 2) print "#@ ", ENVIRON["VAL_SYSCONFDIR"], "/", ENVIRON["VAL_SYSCONFRC"] print "#@ Configuration file for ", cn, " v", ENVIRON["VERSION"], "." OFS = " " next } {print} ' # s-sh-mode s-nail-14.9.15/mk/make-release.inc000066400000000000000000000360331352610246600165250ustar00rootroot00000000000000#@ Include file for the make-release.sh generic release builder. #@ It also needs two hooks: update_stable_hook(), update_release_hook(), #@ which need to "git add" what they have modified. #@ The "grappa" mode needs a current_version() hook, which has to set $VERSION #@ to the current program version, expected as MAJOR.MINOR.UPDATE[-whatever] : ${PROGRAM:?"Need \$PROGRAM"} : ${UPROGRAM:?"Need \$UPROGRAM"} # For announcement only. : ${MANUAL:?"May need \$MANUAL for announcement references"} # When we upload balls only. : ${UPLOAD:?"Need \$UPLOAD URL for scp(1)"} # For announcement mail only. : ${MAILX:=mailx} : ${ACCOUNT:?"May need mailx(1) -A \$ACCOUNT"} : ${MAILTO:?"May need \$MAILTO for announcement"} : ${MAILBCC:?"May need \$MAILBCC for announcement"} # Program stuff : ${awk:=awk} : ${cat:=cat} : ${cmp:=cmp} : ${date:=date} : ${git:=git} : ${grep:=grep} : ${make:=make} : ${mv:=mv} : ${SHELL:=sh} : ${sed:=sed} # non-grappa only : ${gzip:=gzip} : ${openssl:=openssl} : ${gpg:=gpg} : ${rm:=rm} : ${roff:=s-roff} # optional (command(1) tested) : ${sftp:=sftp} : ${tar:=tar} : ${xz:=xz} ## -- >8 -- 8< -- ## ORIG_LC_ALL=${LC_ALL} LC_ALL=C export LC_ALL DATE_MAN=`${date} -u +'%B %d, %Y'` DATE_ISO=`${date} -u +%Y-%m-%d` yesno() { while [ 1 ]; do [ ${#} -gt 0 ] && printf '%s ' "${@}" printf '[y/n] ' read i case ${i} in [Yy]*) return 0;; [Nn]*) return 1;; *) ;; esac done } ref_status() { headref="`${git} rev-parse --verify HEAD`" brref= for i in `${git} rev-parse --branches=stable master^{commit} \ 2>/dev/null`; do if [ ${headref} = ${i} ]; then brref=${headref} break fi done if [ -z "${brref}" ]; then echo >&2 'Not on the [master] or a [stable/*] branch' [ -z "${grappa}" ] && exit 1 if yesno 'Are you sure you want grappa from '${headref}'?'; then :; else echo >&2 'Bailing out' exit 3 fi fi if [ "`${git} status --porcelain --ignored | ${awk} 'BEGIN{no=0}{++no}END{print no}'`" -ne 0 ]; then echo >&2 'Directory not clean, see git status --ignored' exit 2 fi #brname="`git branch | sed -e '/^* /b X' -e d -e :X -e 's/^* //'`" brname=`${git} symbolic-ref --short HEAD` } release_version() { vmaj=`{ echo ${VERSION}; } | ${sed} -e 's/^\([^.]\{1,\}\).*/\1/'` vmin=`{ echo ${VERSION}; } | ${sed} -e 's/^[^.]\{1,\}\.\([^.]\{1,\}\).*/\1/'` [ ${vmin} = ${VERSION} ] && VERSION=${VERSION}.0 vmin=0 vupd=`{ echo ${VERSION}; } | ${sed} -e 's/^[^.]\{1,\}\.[^.]\{1,\}\.\([^.-]\{1,\}\).*/\1/'` [ ${vupd} = ${VERSION} ] && VERSION=${VERSION}.0 vupd=0 REL=${VERSION} export VERSION if yesno 'Is '${PROGRAM}' correct?'; then :; else echo >&2 'Bailing out' exit 3 fi } release_brcheck() { stblbrname=stable/v${vmaj}.${vmin} need_stblbrname= brref=`${git} rev-parse --verify ${stblbrname} 2>/dev/null` if [ -z "${brref}" ]; then if yesno 'Create new branch '"${stblbrname}"' after release tag'; then need_stblbrname=1 fi elif [ ${brref} != ${headref} ] || [ ${brname} != ${stblbrname} ]; then echo >&2 "For ${REL} we should be on ${stblbrname}, not ${brname}" echo >&2 'Bailing out' exit 4 fi relbrname=release/v${VERSION} brref=`${git} rev-parse --verify ${relbrname} 2>/dev/null` if [ -z "${brref}" ]; then :; else echo >&2 "The ${relbrname} already exists" echo >&2 'Bailing out' exit 5 fi } release_symname() { RELSYM= stblmsg= relmsg= if yesno 'Shall '${PROGRAM}' v'${REL}' have a symbolic name?'; then printf ' ..and it shall be known as: ' read RELSYM if yesno 'Is '"${RELSYM}"' correct?'; then :; else echo >&2 'Bailing out' exit 3 fi stblmsg="Bump ${UPROGRAM} v${REL} (\"${RELSYM}\"), ${DATE_ISO}" relmsg="Bump ${UPROGRAM} v${REL}.ar (\"${RELSYM}\"), ${DATE_ISO}" RELSYM=" (\"${RELSYM}\")" else stblmsg="Bump ${UPROGRAM} v${REL}, ${DATE_ISO}" relmsg="Bump ${UPROGRAM} v${REL}.ar, ${DATE_ISO}" fi } update_stable_branch() { LC_ALL=${ORIG_LC_ALL} ${git} commit -S -n -m "${stblmsg}" LC_ALL=${ORIG_LC_ALL} ${git} tag -s -f -m "${stblmsg}" v${REL} if [ -n "${need_stblbrname}" ]; then ${git} checkout -b ${stblbrname} fi # Normally done in post-commit hook, but not once initially created if yesno 'Shall i update stable/latest "symlink"?'; then ${git} update-ref refs/heads/stable/latest ${stblbrname} fi if yesno 'Shall i update stable/stable "symlink"?'; then ${git} update-ref refs/heads/stable/stable ${stblbrname} fi } create_release_branch() { if yesno 'Create release/ branch?'; then ${git} checkout -b ${relbrname} echo 'Updating files: calling update_release_hook' update_release_hook LC_ALL=${ORIG_LC_ALL} ${git} commit -S -n -m "${relmsg}" LC_ALL=${ORIG_LC_ALL} ${git} tag -s -f -m "${relmsg}" v${REL}.ar if yesno 'Shall i update release/latest "symlink"?'; then ${git} update-ref refs/heads/release/latest ${relbrname} fi if yesno 'Shall i update release/stable "symlink"?'; then ${git} update-ref refs/heads/release/stable ${relbrname} fi else relbrname=${stblbrname} fi } check_timeline_branch() { if [ ${relbrname} != ${stblbrname} ] && `${git} rev-parse --verify timeline^{commit} >/dev/null 2>&1` && yesno 'Shall i update [timeline]?'; then ${git} checkout timeline ${git} rm -rf '*' ${git} archive --format=tar "v${REL}.ar" | ${tar} -x -f - ${git} add . LC_ALL=${ORIG_LC_ALL} ${git} commit -S -n -m "${relmsg}" fi } repo_push() { [ ${relbrname} != ${stblbrname} ] && ${git} checkout ${stblbrname} ${git} log --no-walk --decorate --oneline --branches --remotes yesno 'Push git(1) repo?' && ${git} push } big_balls() { if [ ${relbrname} != ${stblbrname} ] && yesno 'Create tarballs?'; then bigballs=y ( umask 0022 # Repack with standard tar(1) to avoid new-style headers ${git} archive --format=tar --prefix="${PROGRAM}-${REL}/" v${REL}.ar | ( cd "${TMPDIR}" && ${tar} -x -f - ) cd "${TMPDIR}" ${tar} -c -f "${PROGRAM}-${REL}.tar" "${PROGRAM}-${REL}" < "${PROGRAM}-${REL}.tar" ${xz} -e -C sha256 > "${PROGRAM}-${REL}.tar.xz" < "${PROGRAM}-${REL}.tar" ${gzip} > "${PROGRAM}-${REL}.tar.gz" ${rm} "${PROGRAM}-${REL}.tar" printf '' > "${PROGRAM}-${REL}.cksum" ${openssl} sha1 "${PROGRAM}-${REL}.tar.xz" >> "${PROGRAM}-${REL}.cksum" ${openssl} sha256 "${PROGRAM}-${REL}.tar.xz" >> "${PROGRAM}-${REL}.cksum" ${openssl} sha512 "${PROGRAM}-${REL}.tar.xz" >> "${PROGRAM}-${REL}.cksum" ${openssl} sha1 "${PROGRAM}-${REL}.tar.gz" >> "${PROGRAM}-${REL}.cksum" ${openssl} sha256 "${PROGRAM}-${REL}.tar.gz" >> "${PROGRAM}-${REL}.cksum" ${openssl} sha512 "${PROGRAM}-${REL}.tar.gz" >> "${PROGRAM}-${REL}.cksum" echo >> "${PROGRAM}-${REL}.cksum" ${gpg} --detach-sign --armor "${PROGRAM}-${REL}.tar.xz" ${cat} "${PROGRAM}-${REL}.tar.xz.asc" >> "${PROGRAM}-${REL}.cksum" ${gpg} --detach-sign --armor "${PROGRAM}-${REL}.tar.gz" ${cat} "${PROGRAM}-${REL}.tar.gz.asc" >> "${PROGRAM}-${REL}.cksum" ) else bigballs= fi } announcement_prepare() { anntxt= if yesno 'Prepare announcement?'; then :; else return fi anntxt=y if `${git} cat-file -e ${relbr}:NEWS 2>/dev/null`; then ${git} show ${relbr}:NEWS > "${TMPDIR}/.${PROGRAM}-${REL}.news" else printf '' > "${TMPDIR}/.${PROGRAM}-${REL}.news" fi { echo "${relmsg}"; echo; } > "${TMPDIR}/${PROGRAM}-${REL}.txt" if [ -f .git/make-release.txt ]; then # For the checksums if [ -n "${bigballs}" ] && [ -f "${TMPDIR}/${PROGRAM}-${REL}.cksum" ] then cks=`< "${TMPDIR}/${PROGRAM}-${REL}.cksum" \ ${sed} -e 's/ //' -e '/^$/,$d'` < "${TMPDIR}/${PROGRAM}-${REL}.cksum" ${sed} '1,/^$/d' \ > "${TMPDIR}/.${PROGRAM}-${REL}.sigs" < .git/make-release.txt ${awk} \ -v INS="${cks}" -v SIGS="${TMPDIR}/.${PROGRAM}-${REL}.sigs" \ -v NEWS="${TMPDIR}/.${PROGRAM}-${REL}.news" ' /-----CHECKSUMS-----/{ atop = split(INS, a) fn = "" for(i = 1; i <= atop; ++i){ match(a[i], /(\(.+\))/) tfn = substr(a[i], RSTART + 1, RLENGTH - 2) tpre = substr(a[i], 1, RSTART - 1) tsuf = substr(a[i], RSTART + RLENGTH + 1) if(fn == "" || fn != tfn) printf "%s:\n", (fn = tfn) printf " %6s %s\n", tpre, tsuf } next } /-----SIGNATURES-----/{ while(getline sl < SIGS) print sl next } /-----NEWS-----/{ while(getline sl < NEWS) print sl next } {print} ' >> "${TMPDIR}/${PROGRAM}-${REL}.txt" ${rm} -f "${TMPDIR}/.${PROGRAM}-${REL}.sigs" else < .git/make-release.txt ${awk} \ -v NEWS="${TMPDIR}/.${PROGRAM}-${REL}.news" ' /-----NEWS-----/{ while(getline sl < NEWS) print sl next } {print} ' >> "${TMPDIR}/${PROGRAM}-${REL}.txt" fi elif [ -f "${TMPDIR}/.${PROGRAM}-${REL}.news" ]; then ${cat} "${TMPDIR}/.${PROGRAM}-${REL}.news" >> \ "${TMPDIR}/${PROGRAM}-${REL}.txt" fi ${rm} -f "${TMPDIR}/.${PROGRAM}-${REL}.news" LC_ALL=${ORIG_LC_ALL} ${EDITOR} "${TMPDIR}/${PROGRAM}-${REL}.txt" # HTML convert ready for S-Web42 APO=\' < "${TMPDIR}/${PROGRAM}-${REL}.txt" ${awk} -v manual="${MANUAL}" ' BEGIN{ hot = 0 print "
"
   }
   function strips(){
      gsub("&", "\\&")
      gsub("<", "\\<")
      gsub(">", "\\>")
   }
   function urls(){
      any = 0
      res = ""
      s = $0

      while(match(s, /(\\?https?\??:\/\/[^ ]*)/)){
         pre = substr(s, 1, RSTART - 1)
         mat = substr(s, RSTART, RLENGTH)
         s = substr(s, RSTART + RLENGTH)
         if("\\" == substr(mat, 1, 1))
            mat = substr(mat, 2)
         else{
            xt = 0
            if(match(mat, /^https\?/))
               mat = "https" substr(xt = mat, RSTART + 6)
            if(match(mat, /sdaoden\.eu/))
               mat = "" xt : "") "?>"
            else
               mat = "" xt : "") "?>"
         }
         res = res pre mat
         any = 1
      }
      if(any && length(s))
         res = res s
      $0 = any ? res : s
   }
   /^[[:space:]]*s-.*-mode[[:space:]]*$/{
      exit 0
   }
   /^(NOTES|ChangeLog)/{
      hot = 1
      strips()
      print
      next
   }
   /^(Appendix|git\(1\) shortlog)/{
      hot = -1
      strips()
      print
      next
   }
   {
      strips()
      urls()
      if(hot <= 0){
         print
         next
      }
      any = 0
      res = ""
      s = $0
      # Create S-Web42 local references for the possible anchors:
      #     *XY*# / $XY# / -XY# / `XY${APO}# / `~XY${APO}# / "XY"#
      # (where the mdocmx(7) anchor follows the number sign).
      # Ideally the anchors have been automatically expanded by
      # make-news-anchors.sh before.
      while(match(s,
            /(^|\(|[[:space:]]+)("[^"]+"|\*[^\*]+\*|`[^'${APO}']+'${APO}'|[-~][-#\/:_.[:alnum:]]+|\$[_[:alnum:]]+)#[0-9]+/))
      {
         pre = (RSTART > 1) ? substr(s, 1, RSTART - 1) : ""
         mat = substr(s, RSTART, RLENGTH)
         s = substr(s, RSTART + RLENGTH)

         # Unfortunately groups are not supported
         if(match(mat, /^(\(|[[:space:]]+)/) != 0 && RLENGTH > 0){
            pre = pre substr(mat, 1, RLENGTH)
            mat = substr(mat, RSTART + RLENGTH)
         }

         match(mat, /#[0-9]+/)
         targ = substr(mat, RSTART + 1, RLENGTH)
         mat = substr(mat, 1, RSTART - 1)
         res = res pre "" mat "?>"
         any = 1
      }
      if(any && length(s))
         res = res s
      print any ? res : s
   }
   END{
      print "
" } ' > "${TMPDIR}/.${PROGRAM}-ann.html" } upload() ( if [ -n "${bigballs}" ] && yesno 'Upload archives'; then :; else return fi cd "${TMPDIR}" { echo "-put ${PROGRAM}-${REL}.tar.xz" echo "-rm ${PROGRAM}-latest.tar.xz" echo "-ln -s ${PROGRAM}-${REL}.tar.xz ${PROGRAM}-latest.tar.xz" echo "-put ${PROGRAM}-${REL}.tar.xz.asc" echo "-rm ${PROGRAM}-latest.tar.xz.asc" echo "-ln -s ${PROGRAM}-${REL}.tar.xz.asc ${PROGRAM}-latest.tar.xz.asc" echo "-put ${PROGRAM}-${REL}.tar.gz" echo "-rm ${PROGRAM}-latest.tar.gz" echo "-ln -s ${PROGRAM}-${REL}.tar.gz ${PROGRAM}-latest.tar.gz" echo "-put ${PROGRAM}-${REL}.tar.gz.asc" echo "-rm ${PROGRAM}-latest.tar.gz.asc" echo "-ln -s ${PROGRAM}-${REL}.tar.gz.asc ${PROGRAM}-latest.tar.gz.asc" if [ -n "${anntxt}" ]; then echo "-put ${PROGRAM}-${REL}.txt" echo "-rm ${PROGRAM}-latest.txt" echo "-ln -s ${PROGRAM}-${REL}.txt ${PROGRAM}-latest.txt" fi echo "-chmod 0644 ${PROGRAM}-${REL}.*" } | ${sftp} -b - ${UPLOAD} ) announcement_send() { if [ -n "${anntxt}" ] && yesno 'Send announcement mail?'; then LC_ALL=${ORIG_LC_ALL} ${MAILX} -A ${ACCOUNT} \ -s "[ANN]ounce of ${UPROGRAM} v${REL}${RELSYM}" \ -q "${TMPDIR}/${PROGRAM}-${REL}.txt" \ -b ${MAILBCC} ${MAILTO} fi } create_grappa_env() { echo 'Updating files: calling update_release_hook' update_release_hook echo 'E allora io quasi quasi prendo il treno' } grappa= if [ ${#} -ne 0 ]; then if [ ${#} != 2 ] || [ "${1}" != grappa ] || [ -z "${2}" ]; then echo >&2 'You have a hell of a lot to learn about Rock'"'"'n Roll' exit 55 fi grappa=${2} fi ref_status echo 'Preparing a release on commit '"${headref}" if [ -z "${grappa}" ]; then printf ' The HEAD is %s\nName of release tag: ' "${brname}" read REL VERSION=${REL} release_version release_brcheck release_symname else echo 'Grappa to be brought from '"${brname}"' to '"${grappa}" current_version printf 'Program version is %s, packager release addition shall be: ' \ "${VERSION}" read REL VERSION="${VERSION}-${REL}" release_version i= if ${git} rev-parse --verify ${grappa} >/dev/null 2>/dev/null; then :; else i=-B fi ${git} checkout ${i} ${grappa} ${git} rm -f '*' ${git} archive --format=tar ${headref} | ${tar} -x -f - ${git} add . fi echo 'Updating files: calling update_stable_hook' update_stable_hook if [ -z "${grappa}" ]; then update_stable_branch create_release_branch check_timeline_branch repo_push big_balls announcement_prepare upload announcement_send else create_grappa_env fi # Finally remove the temporary instances than ran this ${rm} -f .git/make-release.* echo 'Done' exit # s-sh-mode s-nail-14.9.15/mk/make-release.sh000066400000000000000000000106151352610246600163640ustar00rootroot00000000000000#!/bin/sh - #@ make-release.sh: simple somewhat generic release builder # In order to be able to remove the release scripts from the release tarball, # we must delete them, which some shells may not like while they are running. # So be safe and move instances temporarily to .git/, the .inc will remove them if [ "`basename \`pwd\``" != .git ]; then [ -d mk ] || exit 84 cp mk/make-release.* .git/ cd .git exec sh ./make-release.sh "${@}" fi cd .. ## Variables : ${PROGRAM:=s-nail} : ${UPROGRAM:=S-nail} : ${MANUAL:=code-nail.html} : ${UPLOAD:=steffen@sdaoden.eu:/var/www/localhost/downloads} : ${MAILX:=s-nail -Snofollowup-to -Sreply-to=mailx -Ssmime-sign} : ${ACCOUNT:=ich} : ${MAILBCC:=mailx-announce-bcc} : ${MAILTO:=mailx-announce} have_perl= if command -v perl >/dev/null 2>&1; then have_perl=1 fi if command -v s-nail >/dev/null 2>&1; then :; else echo >&2 'We need s-nail in the path in order to update errors etc.!' echo >&2 '(To create hashtables of those, to be exact.)' exit 1 fi ## Hooks current_version() { VERSION=`VERSION= TOPDIR= \ awk=${awk} grep=${grep} sed=${sed} cmp=${cmp} mv=${mv} \ ${SHELL} mk/make-version.sh create` } update_stable_hook() { [ -n "${grappa}${have_perl}" ] || exit 86 # echo 'gen-version.h: update' TOPDIR= awk=${awk} grep=${grep} sed=${sed} cmp=${cmp} mv=${mv} \ ${SHELL} mk/make-version.sh create ${git} add -f include/mx/gen-version.h # echo 'nail.1: expanding MKREL' < nail.1 > nail.1x ${awk} ' BEGIN { written = 0 } /\.\\"--MKREL-START--/, /\.\\"--MKREL-END--/ { if (written++ != 0) next print ".\\\"--MKREL-START--" print ".\\\"@ '"${UPROGRAM}"' v'"${REL}"' / '"${DATE_ISO}"'" print ".Dd '"${DATE_MAN}"'" print ".ds VV \\\\%v'"${REL}"'" print ".\\\"--MKREL-END--" next } {print} ' && ${mv} -f nail.1x nail.1 ${git} add nail.1 # if [ -z "${grappa}" ] && command -v ${roff} >/dev/null 2>&1; then echo 'NEWS: updating anchors' < nail.1 ${SHELL} mk/mdocmx.sh | MDOCMX_ENABLE=1 ${roff} -U -Tutf8 -mdoc \ -dmx-anchor-dump=/tmp/anchors -dmx-toc-force=tree >/dev/null ${SHELL} mk/make-news-anchors.sh /tmp/anchors < NEWS > NEWSx ${mv} -f NEWSx NEWS ${git} add NEWS fi # echo 'nail.rc: expanding MKREL' < nail.rc > nail.rcx ${awk} ' BEGIN { written = 0 } /^#--MKREL-START--/, /^#--MKREL-END--/ { if (written++ != 0) next print "#--MKREL-START--" print "#@ '"${UPROGRAM}"' v'"${REL}"' / '"${DATE_ISO}"'" print "#--MKREL-END--" next } {print} ' && ${mv} -f nail.rcx nail.rc ${git} add nail.rc # if [ -n "${have_perl}" ]; then ${make} d-okeys && ${git} add -f src/mx/gen-okeys.h ${make} d-tcaps && ${git} add -f src/mx/gen-tcaps.h ${make} d-errors && ${git} add -f src/su/gen-errors.h ${make} d-cs-ctype && ${git} add -f src/su/gen-cs-ctype.h fi } update_release_hook() { # echo 'nail.1: stripping MKREL etc.' ${sed} -E -e '/^\.\\"--MKREL-(START|END)--/d' \ -e '/--BEGINSTRIP--/,$ {' \ -e '/^\.[[:space:]]*$/d' -e '/^\.[[:space:]]*\\"/d' \ -e '}' \ -e '/^\.$/d' \ < nail.1 > nail.1x ${mv} -f nail.1x nail.1 ${SHELL} mk/mdocmx.sh < nail.1 > nail.1x ${mv} -f nail.1x nail.1 # And generate the HTML manual, while here if [ -z "${grappa}" ] && command -v ${roff} >/dev/null 2>&1; then < nail.1 MDOCMX_ENABLE=1 ${roff} -Thtml -mdoc > /tmp/nail-manual.html fi ${git} add nail.1 # echo 'nail.rc: stripping MKREL etc.' ${sed} -Ee '/^#--MKREL-(START|END)--/d' < nail.rc > nail.rcx ${mv} -f nail.rcx nail.rc ${git} add nail.rc if [ -n "${have_perl}" ]; then ${make} d-okeys-nv && ${git} add -f src/mx/gen-okeys.h ${make} d-tcaps-nv && ${git} add -f src/mx/gen-tcaps.h ${make} d-errors-nv && ${git} add -f src/su/gen-errors.h ${make} d-cs-ctype-nv && ${git} add -f src/su/gen-cs-ctype.h perl mk/su-doc-strip.pl include/su/*.h && ${git} add include/su fi ${git} rm -f .gitignore .mailmap TODO \ \ mk/make-news-anchors.sh mk/make-release.* \ \ mk/mdocmx.sh \ \ mk/su-doc-strip.pl mk/su-doxygen.rc mk/su-make-cs-ctype.sh \ \ `${git} grep -l ^su_USECASE_MX_DISABLED` } . .git/make-release.inc # s-sh-mode s-nail-14.9.15/mk/make-release.txt000066400000000000000000000023501352610246600165660ustar00rootroot00000000000000Hello list, hereby i announce S-nail vXX.X.X, the "". It is . Credits, in order of commit appearance: We welcome in THANKS. Number games ^^^^^^^^^^^^ The release commit is [v .ar] ([release/v..]:[]). , and it has also been stored as [timeline]:[]. The master/stable branch was tagged [v..] ([master stable/v.]:[]). The git(1) release commits and tags, as well as the release balls have been signed with the OpenPGP signing subkey EEC8C2FF of key steffen@sdaoden.eu / 1883A0DD (EE19 E1C1 F2F7 054F 8D39 54D8 3089 64B5 1883 A0DD) available on OpenPGP key servers, my website and download area. Release balls and OpenPGP signatures (.asc) can be downloaded via HTTPS/HTTP at \https?://ftp.sdaoden.eu. Copies of the signatures can also be found at the end of this message. -----CHECKSUMS----- All files are available as "-latest" symbolic links, too, e.g., s-nail-latest.txt (a copy of this announcement text). Announcement : https?://www.sdaoden.eu/code-nail-ann.html Manual : https?://www.sdaoden.eu/code-nail.html Web : https?://www.sdaoden.eu/code.html#s-mailx git(1) clone : \https?://git.sdaoden.eu/scm/s-nail.git git(1) browse: https?://git.sdaoden.eu/cgit/s-nail.git -----NEWS----- -----SIGNATURES----- s-nail-14.9.15/mk/make-rules.sh000066400000000000000000000071341352610246600161000ustar00rootroot00000000000000#!/bin/sh - #@ make-rules.sh: create make rules for the given .cc? files. #@ All given files must reside in a single directory. #@ Comments following '#include "file"' directives will replace the default #@ $(DIRNAME_INCDIR) prefix; use - to not add a dependency for that header. # Public Domain : ${awk:=awk} : ${sort:=sort} # Whether we shall generate object names based on numbers (shorter)? : ${COUNT_MODE:=1} ## -- >8 - - 8< -- ## syno() { if [ ${#} -gt 0 ]; then echo >&2 "ERROR: ${*}" echo >&2 fi echo >&2 'Synopsis: make-rules.sh :FILES:' exit 1 } [ ${#} -gt 0 ] || syno APO=\' #' (vimsyn) printf '%s\n' "${@}" | ${sort} | ${awk} -v COUNT_MODE=${COUNT_MODE} ' BEGIN {fno = 0; cono = cxxono = 0; dname = ""; DNAME = ""} {farr[++fno] = $0} END{ for(i = 1; i <= fno; ++i) parse_one(i) if(cono > 0){ printf DNAME "_C_OBJ =" for(i = 1; i <= cono; ++i) printf " " coarr[i] print "" } if(cxxono > 0){ printf DNAME "_CXX_OBJ =" for(i = 1; i <= cxxono; ++i) printf " " cxxoarr[i] print "" } } function parse_one(no){ # The source file (basename) bname = farr[no] for(po_i = bname;; bname = po_i) if(!sub(".+/", "", po_i)) break # On first invocation, create our dirname prefixes if(no == 1){ dname = farr[no] if(index(dname, "/")){ sub("/+[^/]*$", "", dname) if(length(input) == 0) input = "/" } DNAME = dname dname = tolower(dname) gsub("/", "-", dname) DNAME = toupper(DNAME) gsub("/", "_", DNAME) gsub("-", "_", DNAME) } # Classify file type po_i = bname if(po_i ~ /\.c$/){ sub(".c$", "", po_i) is_c = 1 ++cono }else if(po_i ~ /\.cc$/){ sub(".cc$", "", po_i) is_c = 0 ++cxxono }else{ print "ERROR: not a C or C++ file: " bname exit(2) } # Object file name po_i = COUNT_MODE ? dname "-" sprintf("%03u", cono + cxxono) ".o" \ : dname "-" po_i ".o" if(is_c) coarr[cono] = po_i else cxxoarr[cxxono] = po_i po_i = po_i ": $(" DNAME "_SRCDIR)" dname "/" bname printf po_i # Parse the file and generate dependencies for(any = 0;;){ po_i = getline < farr[no] if(po_i == 0) break if(po_i == -1) exit(1) po_i = match($0, /^[ ]*#[ ]*include "/) if(po_i == 0) continue h = substr($0, RSTART + RLENGTH) po_i = match(h, /"/) xh = substr(h, 1, RSTART - 1) # Do we have a comment suffix that clarifies our path? h = substr(h, RSTART + 1) po_i = match(h, /[ ]*\/\*[ ]*[^ ]/) if(po_i == 0){ if(!any++) printf " \\\n\t\t" printf " $(" DNAME "_INCDIR)" xh }else{ h = substr(h, RSTART + RLENGTH - 1) # include first non-space po_i = match(h, /[ ]|\*\//) h = substr(h, 1, RSTART - 1) # No dependency for this? if(h != "-"){ if(!any++) printf " \\\n\t\t" printf " " h xh } } } printf "\n\t$(ECHO_CC)$(" if(is_c) printf "CC) $(" DNAME "_CFLAGS)" else printf "CXX) $(" DNAME "_CXXFLAGS)" print " $(" DNAME "_INCS) -c -o $(@) $(" DNAME "_SRCDIR)" dname "/" bname } ' # s-sh-mode s-nail-14.9.15/mk/make-tcap-map.pl000066400000000000000000000074441352610246600164550ustar00rootroot00000000000000#!/usr/bin/env perl require 5.008_001; use utf8; #@ Parse 'enum mx_termcap_{cmd,query}' from termcap.h and create gen-tcaps.h. # Public Domain my $IN = 'include/mx/termcap.h'; my $OUT = 'src/mx/gen-tcaps.h'; # Generate a more verbose output. Not for shipout versions. my $VERB = 1; ## -- >8 -- 8< -- ## use diagnostics -verbose; use strict; use warnings; my ($S, $CAPS_LEN, $BIND_START, @CAPS_NAMES, @ENTS) = (($VERB ? ' ' : ''), 0, -1); sub main_fun{ if(@ARGV) {$VERB = 0; $S = ''} parse_in_h(); dump_data(); exit 0 } sub basen{ my $n = $_[0]; $n =~ s/^(.*\/)?([^\/]+)$/$2/; $n } sub parse_in_h{ die "$IN: open: $^E" unless open F, '<', $IN; my ($init) = (0); while(){ chomp; # Only want the enum okeys content if(/^enum mx_termcap_cmd/){ $init = 1; next } if(!$init && /^enum mx_termcap_query/){ $init = 2; next } if(/^\};/){ if($init == 2){ $init = 3; last } $init = 0; next } $init || next; # Ignore empty and comment lines /^$/ && next; if(/^\s*\/\*/){ # However, one special directive we know $BIND_START = @CAPS_NAMES + 1 if /--make-tcap-map--/; next } # We need to preserve preprocessor conditionals if(/^\s*#/){ push @ENTS, [$_]; next } # An entry is a constant followed by a specially crafted comment; # ignore anything else /^\s*(\w+),\s* \/\*\s*(\w+|-)\s*\/\s*([^,\s]+|-), \s*(\w+)\s* (?:,\s*(\w+)\s*)? (?:\||\*\/) /x; next unless $1 && $2 && $3 && $4; die "Unsupported terminal capability type: $4" unless($4 eq 'BOOL' || $4 eq 'NUMERIC' || $4 eq 'STRING'); my $e = 'mx_TERMCAP_CAPTYPE_' . $4; $e = $e . '|a_TERMCAP_F_QUERY' if $init == 2; $e = $e . '|a_TERMCAP_F_ARG_' . $5 if $5; push @ENTS, [$1, $e, $CAPS_LEN]; # termcap(5) names are always two chars, place them first, don't add NUL my ($ti, $tc) = ($2, $3); $tc = '' if $tc eq '-'; $ti = '' if $ti eq '-'; my $l = 2 +0 + length($ti) +1; push @CAPS_NAMES, [$1, $CAPS_LEN, $l, $tc, $ti]; $CAPS_LEN += $l; } die 'I do not see the expected content' unless $init == 3; close F } sub dump_data{ die "$OUT: open: $^E" unless open F, '>', $OUT; print F '/*@ ', scalar basen($OUT), ', generated by ', scalar basen($0), ".\n *@ See termcap.c for more */\n\n"; print F 'static char const a_termcap_namedat[] = {', "\n"; foreach my $np (@CAPS_NAMES){ sub _exp{ if(length $_[0]){ $_[0] = '\'' . join('\',\'', split(//, $_[0])) . '\','; }elsif($_[1] > 0){ $_[0] = '\'\0\',' x $_[1]; } } if($BIND_START > 0){ print F '#ifdef mx_HAVE_KEY_BINDINGS', "\n" if(--$BIND_START == 0) } my ($tcn, $tin) = (_exp(scalar $np->[3], 2), _exp(scalar $np->[4], 0)); if($VERB){ print F "${S}/* [$np->[1]]+$np->[2], $np->[0] */ $tcn $tin'\\0',\n" }else{ print F "${S}$tcn $tin'\\0',\n" } } print F '#endif /* mx_HAVE_KEY_BINDINGS */', "\n" if($BIND_START == 0); print F '};', "\n\n"; print F 'static struct a_termcap_control const a_termcap_control[] = {', "\n"; my $i = 0; foreach my $ent (@ENTS){ if($#$ent == 0){ print F $ent->[0], "\n" }else{ if($VERB){ print F ${S}, '{/* ', $i, '. ', $ent->[0], ' */ ', $ent->[1], ', ', $ent->[2], "},\n" }else{ print F "{$ent->[1], $ent->[2]},\n" } ++$i } } print F '};', "\n"; die "$OUT: close: $^E" unless close F } {package main; main_fun()} # s-it-mode s-nail-14.9.15/mk/make-version.sh000066400000000000000000000044301352610246600164270ustar00rootroot00000000000000#!/bin/sh - #@ Generate new include/mx/gen-version.h (via environment settings). #@ MUST work with "TOPDIR= awk=awk grep=grep sed=sed cmp=cmp mv=mv". # # Public Domain LC_ALL=C query() { VERSION="`< ${CWDDIR}include/mx/gen-version.h ${sed} \ -e '/ n_VERSION /b X' -e d -e ':X' \ -e 's/[^\"]*\"v\([^\"]\{1,\}\)\"/\1/'`" echo $VERSION } c__gitver() { [ -n "${TOPDIR}" ] && cd "${TOPDIR}" if [ -d .git ] && command -v git >/dev/null 2>&1; then git describe --tags fi } c__isdirty() { [ -n "${TOPDIR}" ] && cd "${TOPDIR}" _id=`git status --porcelain | ${awk} ' BEGIN {n=0} /gen-version\.h/ {next} /^\?\?/ {next} {++n} END {print n} '` [ "${_id}" != 0 ] } create() { if [ -z "${VERSION}" ]; then VERSION=`c__gitver` if [ -n "${VERSION}" ]; then VERSION="`echo ${VERSION} | ${sed} -e 's/^v\{0,1\}\(.*\)/\1/'`" c__isdirty && VERSION="${VERSION}-dirty" else query | ${grep} -q -F dirty || VERSION="${VERSION}-dirty" fi fi vmaj="`echo \"${VERSION}\" | ${sed} -e 's/^\([^.]\{1,\}\).*/\1/'`" vmin="`echo \"${VERSION}\" | ${sed} -e 's/^[^.]\{1,\}\.\([^.]\{1,\}\).*/\1/'`" [ "${vmin}" = "${VERSION}" ] && VERSION="${VERSION}.0" vmin=0 vupd="`echo \"${VERSION}\" | ${sed} -e 's/^[^.]\{1,\}\.[^.]\{1,\}\.\([^.-]\{1,\}\).*/\1/'`" [ "${vupd}" = "${VERSION}" ] && VERSION="${VERSION}.0" vupd=0 trap "${rm} -f ./version.tmp" 0 1 2 15 printf > ./version.tmp "#define n_VERSION \"v${VERSION}\"\n" printf >> ./version.tmp "#define n_VERSION_DATE \"`date -u +'%Y-%m-%d'`\"\n" printf >> ./version.tmp "#define n_VERSION_MAJOR \"${vmaj}\"\n" printf >> ./version.tmp "#define n_VERSION_MINOR \"${vmin}\"\n" printf >> ./version.tmp "#define n_VERSION_UPDATE \"${vupd}\"\n" printf >> ./version.tmp "#define n_VERSION_HEXNUM \"0x%02X%03X%03X\"\n" \ "${vmaj}" "${vmin}" "${vupd}" ${cmp} ./version.tmp ${CWDDIR}include/mx/gen-version.h >/dev/null 2>&1 && exit ${mv} ./version.tmp ${CWDDIR}include/mx/gen-version.h trap : 0 1 2 15 } syno() { echo >&2 'Synopsis: make-version.sh create|query' exit 1 } [ $# -ne 1 ] && syno [ "$1" = create ] && { create; query; exit 0; } [ "$1" = query ] && { query; exit 0; } syno # s-sh-mode s-nail-14.9.15/mk/mdocmx.sh000066400000000000000000000512421352610246600153210ustar00rootroot00000000000000#!/bin/sh - #@ mdocmx.sh - mdocmx(7) preprocessor for single-pass troff. #@ mdocmx(7) extends the mdoc(7) semantic markup language by references, #@ allowing mdoc(7) to create anchors and table of contents. #@ Synopsis: mdocmx[.sh] [:-v:] [-t | -T Sh|sh|Ss|ss [-c]] [FILE] #@ -v: increase verbosity #@ -t: whether -toc lines shall be expanded to a flat .Sh TOC #@ -T: whether -toc lines shall be expanded as specified: only .Sh / .Sh + .Ss #@ -c: only with -t or -T: whether compact TOC display shall be generated #@ Set $AWK environment to force a special awk(1) interpreter. # # Written 2014 - 2019 Steffen (Daode) Nurpmeso . # Public Domain : ${TMPDIR:=/tmp} : ${ENV_TMP="${TMPDIR}:${TMP}:${TEMP}"} # -- >8 -- 8< -- # # For heaven's sake add special treatment for SunOS/Solaris if [ -d /usr/xpg4/bin ]; then PATH=/usr/xpg4/bin:$PATH export PATH fi #AWK= EX_OK=0 EX_USAGE=64 EX_DATAERR=65 EX_TEMPFAIL=75 V=0 T= TT= F= ( set -o noglob ) >/dev/null 2>&1 && set -o noglob find_awk() { [ -n "${AWK}" ] && return 0 i=${IFS} IFS=: set -- ${PATH}:/bin IFS=${i} # for i; do -- new in POSIX Issue 7 + TC1 for i do if [ -z "${i}" ] || [ "${i}" = . ]; then if [ -d "${PWD}" ]; then i=${PWD} else i=. fi fi for j in n m '' g; do AWK="${i}/${j}awk" [ -f "${AWK}" ] && [ -x "${AWK}" ] && return 0 done done return 1 } synopsis() { ex=${1} msg=${2} [ -n "${msg}" ] && echo >&2 ${msg} [ ${ex} -eq 0 ] && f=1 || f=2 ( echo "${0##*/}" ) >/dev/null 2>&1 && eval 'p="${0##*/}"' || p="${0}" echo >&${f} "Synopsis: ${p} [-h]" echo >&${f} " ${p} [:-v:] [-t | -T Sh|sh|Ss|ss [-c]] [FILE]" exit ${ex} } ## if ( set -C ) >/dev/null 2>&1; then set +C else # For heaven's sake auto-redirect on SunOS/Solaris if [ -f /usr/xpg4/bin/sh ] && [ -x /usr/xpg4/bin/sh ]; then exec /usr/xpg4/bin/sh "${0}" "${@}" else synopsis 1 'Not a sh(1)ell with "set -C" (for save temporary file creation)' fi fi find_awk || synopsis 1 'Cannot find a usable awk(1) implementation' while getopts hvtT:c i; do case ${i} in h) synopsis ${EX_OK};; v) V=`expr ${V} + 1`;; t) [ x != x"${T}" ] && synopsis ${EX_USAGE} '-toc line expansion yet defined' T=Sh;; T) [ x != x"${T}" ] && synopsis ${EX_USAGE} '-toc line expansion yet defined' case "${OPTARG}" in [Ss]h) T=Sh;; [Ss]s) T=Ss;; *) synopsis ${EX_USAGE} "Invalid -T argument: -- ${OPTARG}";; esac;; c) TT=-compact;; ?) synopsis ${EX_USAGE} '';; esac done [ -n "${TT}" ] && [ -z "${T}" ] && synopsis ${EX_USAGE} '-c requires -t or -T' i=`expr ${OPTIND} - 1` [ ${i} -gt 0 ] && shift ${i} [ ${#} -gt 1 ] && synopsis ${EX_USAGE} 'Excess arguments given' [ ${#} -eq 0 ] && F=- || F=${1} ## # awk(1) doesn't support signal handlers, which means that, when we're part of # a pipe which the user terminates, we are not capable to deal with the broken # pipe case that our END{} handler will generate when we had to perform any # preprocessing, and that in turn would result in a dangling temporary file! # Thus the only sane option seems to be to always create the temporary file, # whether we need it or not, not to exec(1) awk(1) but keep on running the shell # in order to remove the temporary after awk(1) has finished, whichever way. find_tmpdir() { i=${IFS} IFS=: set -- ${ENV_TMP} IFS=${i} # for i; do -- new in POSIX Issue 7 + TC1 for tmpdir do [ -d "${tmpdir}" ] && return 0 done tmpdir=${TMPDIR} [ -d "${tmpdir}" ] && return 0 echo >&2 'Cannot find a usable temporary directory, please set $TMPDIR' exit ${EX_TEMPFAIL} } find_tmpdir max=421 [ ${V} -gt 1 ] && max=3 i=1 # RW by user only, avoid overwriting of existing files old_umask=`umask` umask 077 while [ 1 ]; do tmpfile="${tmpdir}/mdocmx-${i}.mx" ( set -C : > "${tmpfile}" ) >/dev/null 2>&1 && break i=`expr ${i} + 1` if [ ${i} -gt ${max} ]; then echo >&2 'Cannot create a temporary file within '"${tmpdir}" exit ${EX_TEMPFAIL} fi done trap "exit ${EX_TEMPFAIL}" HUP INT QUIT PIPE TERM trap "trap \"\" HUP INT QUIT PIPE TERM EXIT; rm -f ${tmpfile}" EXIT umask ${old_umask} # Let's go awk(1) {{{ APOSTROPHE=\' ${AWK} -v VERBOSE=${V} -v TOC="${T}" -v TOCTYPE="${TT}" -v MX_FO="${tmpfile}" \ -v EX_USAGE="${EX_USAGE}" -v EX_DATAERR="${EX_DATAERR}" \ 'BEGIN{ # The mdoc macros that support referenceable anchors. # .Sh and .Ss also create anchors, but since they do not require .Mx they are # treated special and handled directly -- update manual on change! UMACS = "Ar Cd Cm Dv Er Ev Fl Fn Fo Ic In Pa Va Vt" # Some of those impose special rules for their arguments; mdocmx(1) solves # this by outsourcing such desires in argument parse preparation hooks UMACS_KEYHOOKS = "Fl Fn" # A list of all mdoc commands; taken from mdocml, "mdoc.c,v 1.226 2014/10/16" UCOMMS = \ "%A %B %C %D %I %J %N %O %P %Q %R %T %U %V " \ "Ac Ad An Ao Ap Aq Ar At Bc Bd " \ "Bf Bk Bl Bo Bq Brc Bro Brq Bsx Bt Bx " \ "Cd Cm D1 Db Dc Dd Dl Do Dq Dt Dv Dx " \ "Ec Ed Ef Ek El Em En Eo Er Es Ev Ex Fa Fc Fd Fl Fn Fo Fr Ft Fx " \ "Hf " \ "Ic In It " \ "Lb Li Lk Lp " \ "Ms Mt Nd Nm No Ns Nx " \ "Oc Oo Op Os Ot Ox Pa Pc Pf Po Pp Pq " \ "Qc Ql Qo Qq Re Rs Rv " \ "Sc Sh Sm So Sq Ss St Sx Sy Ta Tn " \ "Ud Ux Va Vt Xc Xo Xr " \ "br ll sp" # Punctuation to be ignored (without changing current mode) UPUNCTS = ". , : ; ( ) [ ] ? !" # -- >8 -- 8< -- # i = split(UMACS, savea) for(j = 1; j <= i; ++j){ k = savea[j] MACS[k] = k } i = split(UMACS_KEYHOOKS, savea) for(j = 1; j <= i; ++j){ k = savea[j] MACS_KEYHOOKS[k] = k } i = split(UCOMMS, savea) for(j = 1; j <= i; ++j){ k = savea[j] COMMS[k] = k } i = split(UPUNCTS, savea) for(j = 1; j <= i; ++j){ k = savea[j] PUNCTS[k] = k } mx_bypass = 0 # No work if parsing already preprocessed file! mx_nlcont = "" # Line continuation in progress? (Then: data so far) mx_nlcontfun = 0 # Which function to call once line complete NLCONT_SH_SS_COMM = 1 NLCONT_MX_COMM = 2 NLCONT_MX_CHECK_LINE = 3 #mx_sh[] # Arrays which store headlines, and their sizes #mx_sh_cnt #mx_sh_toc # Special avoidance of multiple TOC anchors needed, ++ #mx_ss[] #mx_ss_cnt #mx_sh_ss[] # With TOC we need relation of .Ss with its .Sh #mx_fo = "" # Our temporary output fork (cleaned of .Mx) #mx_macros[] # Readily prepared anchors: macros.. #mx_keys[] # ..: keys #mx_anchors_cnt # ..number of anchors #mx_stack[] # Stack of future anchors to be parsed off.. #mx_stack_cnt # ..number thereof #mx_keystack[] # User specified ".Mx MACRO KEY": store KEY somewhere #ARG, [..] # Next parsed argument (from arg_parse() helper) } END{ # If we were forced to create referenceable anchors, dump the temporary file # after writing our table-of-anchors (TAO :) if(mx_fo){ close(mx_fo) if(mx_stack_cnt > 0) warn("At end of file: \".Mx\" stack not empty (" mx_stack_cnt " levels)") for(i = 1; i <= mx_sh_cnt; ++i){ printf ".Mx -anchor-spass Sh \"%s\" %d\n", arg_quote(mx_sh[i]), i for(j = 1; j <= mx_ss_cnt; ++j) if(mx_sh_ss[j] == i) printf ".Mx -anchor-spass Ss \"%s\" %d\n", arg_quote(mx_ss[j]), mx_sh_ss[j] } for(i = 1; i <= mx_anchors_cnt; ++i) printf ".Mx -anchor-spass %s \"%s\"\n", mx_macros[i], arg_quote(mx_keys[i]) # If we are about to produce a TOC, intercept ".Mx -toc" lines and replace # them with the desired TOC content if(!TOC){ while(getline < mx_fo) print }else{ while(getline < mx_fo){ if($0 ~ /^[[:space:]]*\.[[:space:]]*Mx[[:space:]]+-toc[[:space:]]*/){ print ".Sh \"\\*[mx-toc-name]\"" if(mx_sh_cnt > 0){ print ".Bl -inset", TOCTYPE for(i = 1; i <= mx_sh_cnt; ++i){ printf ".It Sx \"%s\"\n", arg_quote(mx_sh[i]) if(TOC == "Ss") toc_print_ss(i) } print ".El" } # Rather illegal, but maybe we have seen .Ss yet no .Sh: go! else if(TOC == "Ss" && mx_ss_cnt > 0){ print ".Bl -tag -compact" for(i = 1; i <= mx_ss_cnt; ++i) print ".It Sx \"%s\"\n", arg_quote(mx_ss[i]) print ".El" } }else print } } } } function f_a_l(){ if(!fal){ fal = FILENAME if(!fal || fal == "-") fal = "" } return fal ":" NR } function dbg(s){ if(VERBOSE > 1) print "DBG@" f_a_l() ": " s > "/dev/stderr" } function warn(s){ if(VERBOSE > 0) print "WARN@" f_a_l() ": mdocmx(7): " s "." > "/dev/stderr" } function fatal(e, s){ print "FATAL@" f_a_l() ": mdocmx(7): " s "." > "/dev/stderr" exit e } # Dump all .Ss which belong to the .Sh with the index sh_idx, if any function toc_print_ss(sh_idx){ tps_any = 0 for(tps_i = 1; tps_i <= mx_ss_cnt; ++tps_i){ tps_j = mx_sh_ss[tps_i] if(tps_j < sh_idx) continue if(tps_j > sh_idx) break if(!tps_any){ tps_any = 1 print ".Bl -tag -offset indent -compact" } printf ".It Sx \"%s\"\n", arg_quote(mx_ss[tps_i]) } if(tps_any) print ".El" } # Parse the next _roff_ argument from the awk(1) line (in $0). # If "no" < 0, reset the parser and return whether the former state would # have parsed another argument from the line. # If "no" is >0 we start at $(no); if it is 0, iterate to the next argument. # Returns ARG. Only used when "hot" function arg_pushback(arg){ ap_pushback = arg } function arg_parse(no){ if(no < 0){ no = ap_no ap_no = 0 ap_pushback = "" return no < NF } if(no == 0){ if(ap_pushback){ ARG = ap_pushback ap_pushback = "" return ARG } no = ap_no + 1 } ap_pushback = "" ARG = "" for(ap_i = 0; no <= NF; ++no){ ap_j = $(no) # The good news about quotation mode is that entering it requires # a preceeding space: we get it almost for free with awk(1)! if(!ap_i){ if(ap_j ~ /^"/){ ap_i = 1 ap_j = substr(ap_j, 2) }else{ ARG = ap_j break } }else ARG = ARG " " if((ap_k = index(ap_j, "\"")) != 0){ do{ # The bad news on quotation mode are: # - "" inside it resolves to a single " # - " need not mark EOS, but any " that is not followed by " # ends quotation mode and marks the beginning of the next arg # - awk(1) has no goto; if(ap_k == length(ap_j)){ ARG = ARG substr(ap_j, 1, ap_k - 1) ap_no = no ap_i = 0 return ARG }else if(substr(ap_j, ap_k + 1, 1) == "\""){ ARG = ARG substr(ap_j, 1, ap_k) ap_j = substr(ap_j, ap_k + 2) }else{ ARG = ARG substr(ap_j, 1, ap_k) ap_j = substr(ap_j, ap_k + 1) $(no) = ap_j ap_no = no ap_i = 0 return ARG } }while((ap_k = index(ap_j, "\"")) > 0) }else ARG = ARG ap_j } ap_no = no return ARG } function arg_cleanup(arg){ # Deal with common special glyphs etc. # Note: must be in sync with mdocmx(7) macros (mx:cleanup-string)! ac_i = match(arg, /([ \t]|\\&|\\%|\\\/|\\c)+$/) if(ac_i) arg = substr(arg, 1, ac_i - 1) while(arg ~ /^[ \t]/) arg = substr(arg, 1) while(arg ~ /^(\\&|\\%)/ && arg !~ /^\\&\\&/) arg = substr(arg, 3) return arg } function arg_quote(arg){ aq_a = arg gsub("\"", "\"\"", aq_a) return aq_a } # ".Mx -enable" seen function mx_enable(){ # However, are we running on an already preprocessed document? Bypass! if(NF > 2){ if($3 == "-preprocessed"){ print mx_bypass = 1 return } } # If we generate the TOC ourselfs better ensure the string mx-toc-name! # mdocml.bsd.lv (mandoc(1)) does not offer any ".if !d NAME" way, so.. # But even otherwise we need it, since mandoc(1) complains about unknown # \*[] strings in quoted strings, and we *may* have a ".Mx -toc" anyway! if(!TOC) printf ".\\\" Uncomment for mandoc(1) compat.:\n.\\\"" print ".ds mx-toc-name TABLE OF CONTENTS" mx_fo = MX_FO $1 = $2 = "" $0 = substr($0, 2) print ".Mx -enable -preprocessed" $0 } # Deal with a non-"-enable" ".Mx" request function mx_comm(){ # No argument: plain push if(NF == 1){ ++mx_stack_cnt dbg(".Mx: [noarg] -> +1, stack size=" mx_stack_cnt) return } # ".Mx -disable" if($2 == "-disable"){ # Nothing to do here (and do not check device arguments) return } # ".Mx -ix" / ".Mx -sx" freely definable anchors if($2 == "-sx") # Nothing to do here (xxx check argument content validity?) return else if($2 == "-ix"){ mxc_macro = arg_parse(3) if(!mxc_macro) fatal(EX_USAGE, "\".Mx -ix\": synopsis: \".Mx -ix [category] key\"") if(!(mxc_key = arg_parse(0))){ mxc_key = mxc_macro mxc_macro = "ixsx" }else if(arg_parse(-1)) fatal(EX_DATAERR, "\".Mx -ix\": data after USER KEY is faulty syntax") mxc_key = arg_cleanup(mxc_key) dbg(".Mx -ix mac<" mxc_macro "> key <" mxc_key ">") anchor_add(mxc_macro, mxc_key) return } # ".Mx -toc" if($2 == "-toc"){ # With TOC creation we surely want the TOC to have an anchor, too! if(!mx_sh_toc++) mx_sh[++mx_sh_cnt] = "\\*[mx-toc-name]" else warn("\".Mx -toc\": multiple TOCs? Duplicate anchor avoided") return } # This explicitly specifies the macro to create an anchor for next mxc_i = $2 if(mxc_i ~ /^\./){ warn("\".Mx\": stripping dot prefix from \"" mxc_i "\"") mxc_i = substr(mxc_i, 2) } mxc_j = MACS[mxc_i] if(!mxc_j) fatal(EX_DATAERR, "\".Mx\": macro \"" mxc_i "\" not supported") mx_stack[++mx_stack_cnt] = mxc_i dbg(".Mx: for next \"." mxc_i "\", stack size=" mx_stack_cnt) # Do we also have a fixed key? if(NF == 2) return mx_keystack[mx_stack_cnt] = arg_parse(3) dbg(" ... USER KEY given: <" ARG ">") if(arg_parse(-1)) fatal(EX_DATAERR, "\".Mx\": data after USER KEY is faulty syntax") } # mx_stack_cnt is >0, check whether this line will pop the stack function mx_check_line(){ # May be line continuation in the middle of nowhere if(!mx_stack_cnt) return # Must be a non-comment, non-escaped macro line if($0 !~ /^[[:space:]]*[.'${APOSTROPHE}'][[:space:]]*[^"#]/) return # Iterate over all arguments and try to classify them, comparing them against # stack content as applicable mcl_mac = "" mcl_cont = 0 mcl_firstmac = 1 for(arg_parse(-1); arg_parse(0);){ # Solely ignore punctuation (xxx are we too stupid here?) if(PUNCTS[ARG]) continue # (xxx Do this at the end of the loop instead, after decrement?) if(mx_stack_cnt == 0){ dbg("stack empty, stopping arg processing before <" ARG ">") break } mcl_j = mx_stack[mx_stack_cnt] # Is this something we consider a macro? For convenience and documentation # of roff stuff do auto-ignore a leading dot of the name in question mcl_cont = 0 if(mcl_firstmac && ARG ~ /^\./) ARG = substr(ARG, 2) mcl_firstmac = 0 mcl_i = MACS[ARG] if(mcl_i) mcl_mac = mcl_i else{ if(!mcl_mac) continue # It may be some mdoc command nonetheless, ensure it does not fool our # simpleminded processing, and end possible mcl_mac savings if(COMMS[ARG]){ if(mcl_j) dbg("NO POP due macro (got<" ARG "> want<" mcl_j ">)") mcl_mac = "" continue } mcl_i = mcl_mac mcl_cont = 1 } # Current command matches the one on the stack, if there is any if(mcl_j){ if(mcl_i != mcl_j){ dbg("NO POP due macro (got<" mcl_i "> want<" mcl_j ">)") mcl_mac = "" continue } } # We need the KEY if(!mcl_cont && !arg_parse(0)) fatal(EX_DATAERR, "\".Mx\": expected KEY after \"" mcl_mac "\"") ARG = arg_cleanup(ARG) if(ARG ~ /^\\&\\&/) warn("\".Mx\": KEY starting with \"\\&\\&\" will never match: " ARG) if(MACS_KEYHOOKS[mcl_mac]) _mx_check_line_keyhook() if(mx_keystack[mx_stack_cnt]){ mcl_i = mx_keystack[mx_stack_cnt] if(mcl_i != ARG){ dbg("NO POP mac<" mcl_mac "> due key (got<" ARG "> want <" mcl_i ">)") continue } delete mx_keystack[mx_stack_cnt] mcl_i = "STACK" }else mcl_i = "USER" delete mx_stack[mx_stack_cnt--] dbg("POP mac<" mcl_mac "> " mcl_i " key <" ARG \ "> stack size=" mx_stack_cnt) anchor_add(mcl_mac, ARG) } } function _mx_check_line_keyhook(){ # .Fl: arguments may be continued via |, as in ".Fl a | b | c" if(mcl_mac == "Fl"){ mclpkh_i = ARG for(mclpkh_j = 0;; ++mclpkh_j){ if(!arg_parse(0)) break if(ARG != "|"){ arg_pushback(ARG) break } if(!arg_parse(0)){ warn("Premature end of \".Fl\" continuation via \"|\"") break } # Be aware that this argument may indeed be a macro # XXX However, only support another Fl as in # XXX .Op Fl T | Fl t Ar \&Sh | sh | \&Ss | ss # XXX We are too stupid to recursively process any possible thing, # XXX more complicated recursions are simply not supported if(ARG == "Fl"){ arg_pushback(ARG) break } ARG = arg_cleanup(ARG) mclpkh_i = mclpkh_i " | " ARG } ARG = mclpkh_i } # .Fn: in ".Fn const char *funcname" all we want is "funcname" else if(mcl_mac == "Fn"){ if(ARG ~ /[*&[:space:]]/){ mclpkh_i = match(ARG, /[^*&[:space:]]+$/) ARG = arg_cleanup(substr(ARG, mclpkh_i)) } } } # Add one -anchor-spass macro/key pair function anchor_add(macro, key){ for(aa_i = 1; aa_i <= mx_anchors_cnt; ++aa_i) if(mx_macros[aa_i] == macro && mx_keys[aa_i] == key){ warn("\".Mx\": mac<" macro ">: duplicate anchor avoided: " key) return } ++mx_anchors_cnt mx_macros[mx_anchors_cnt] = macro mx_keys[mx_anchors_cnt] = key } # Handle a .Sh or .Ss function sh_ss_comm(){ ssc_s = "" ssc_i = 0 for(arg_parse(-1); arg_parse(0); ++ssc_i){ if(ssc_i < 1) continue if(ssc_i > 1) ssc_s = ssc_s " " ssc_s = ssc_s ARG } ssc_s = arg_cleanup(ssc_s) if($1 ~ /Sh/) mx_sh[++mx_sh_cnt] = ssc_s else{ if(mx_sh_cnt == 0) fatal(EX_DATAERR, ".Ss at beginning of document not allowed by mdoc(7)") mx_ss[++mx_ss_cnt] = ssc_s mx_sh_ss[mx_ss_cnt] = mx_sh_cnt } } # This is our *very* primitive way of dealing with line continuation function line_nlcont_add(fun){ mx_nlcont = mx_nlcont $0 mx_nlcont = substr(mx_nlcont, 1, length(mx_nlcont) - 1) if(!mx_nlcontfun) mx_nlcontfun = fun } function line_nlcont_done(){ lnd_save = $0 $0 = mx_nlcont $0 mx_nlcont = "" if(mx_nlcontfun == NLCONT_SH_SS_COMM) sh_ss_comm() else if(mx_nlcontfun == NLCONT_MX_COMM) mx_comm() else if(mx_nlcontfun == NLCONT_MX_CHECK_LINE) mx_check_line() else fatal(EX_DATAERR, "mdocmx(1) implementation error: line_nlcont_done()") mx_nlcontfun = 0 $0 = lnd_save # simplify callees life } # .Mx is a line that we care about /^[[:space:]]*[.'${APOSTROPHE}'][[:space:]]*M[Xx][[:space:]]*/{ if(mx_bypass) print else if(mx_fo){ if(NF > 1 && $2 == "-enable") fatal(EX_USAGE, "\".Mx -enable\" may be used only once") if(mx_nlcont) fatal(EX_DATAERR, "Line continuation too complicated for mdocmx(1)") if($0 ~ /\\$/) line_nlcont_add(NLCONT_MX_COMM) else mx_comm() print >> mx_fo }else if(NF < 2 || $2 != "-enable") fatal(EX_USAGE, "\".Mx -enable\" must be the first \".Mx\" command") else mx_enable() next } # .Sh and .Ss are also lines we care about, but always store the data in # main memory, since those commands occur in each mdoc file /^[[:space:]]*[.'${APOSTROPHE}'][[:space:]]*S[hs][[:space:]]+/{ if(mx_fo){ if(mx_nlcont) fatal(EX_DATAERR, "Line continuation too complicated for mdocmx(1)") if($0 ~ /\\$/) line_nlcont_add(NLCONT_SH_SS_COMM) else sh_ss_comm() print >> mx_fo next } } # All other lines are uninteresting unless mdocmx is -enabled and we have # pending anchor creation requests on the stack { if(!mx_fo) print else{ # TODO No support for any macro END but .. if(/^[[:space:]]*[.'${APOSTROPHE}'][[:space:]]*dei?1?[[:space:]]+/){ if(mx_nlcont) fatal(EX_DATAERR, "Line continuation too complicated for mdocmx(1)") print >> mx_fo while(getline && $0 !~ /^\.\.$/) print >> mx_fo }else if($0 ~ /\\$/) line_nlcont_add(NLCONT_MX_CHECK_LINE) else if(mx_nlcont) line_nlcont_done() else if(mx_stack_cnt) mx_check_line() else if(/^[[:space:]]*\.(\\"|[[:space:]]*$)/) next print >> mx_fo } }' "${F}" # }}} exit # s-it2-mode s-nail-14.9.15/mk/su-doc-strip.pl000066400000000000000000000030611352610246600163600ustar00rootroot00000000000000#!/usr/bin/env perl require 5.008_001; #@ Strip *! style documents from C/C++ code. #@ Assumes that such lines are exclusive, and that /*!< */ lines #@ are followed by no other such comment. #@ Synopsis: su-doc-strip.pl :FILE: # Public Domain ## -- >8 -- 8< -- ## use diagnostics -verbose; use strict; use warnings; sub main_fun{ die 'False usage' if @ARGV == 0; while(@ARGV > 0){ my ($f, $i, $fd, @lines) = (shift @ARGV, 0); # Read it, stripping the comments die "Cannot open $f for reading: $^E" unless open $fd, '<:raw', $f; while(<$fd>){ chomp; if($i){ if($_ =~ /^(.*?)\*\/[[:space:]]*(.*)$/){ $i = 0; push @lines, $2 if length $2 } }elsif($_ =~ /^(.*?)[[:space:]]*\/\*!(.*)$/){ my ($m1, $m2) = ($1, $2); if($m2 =~ /^(.*?)\*\/[[:space:]]*(.*)$/){ my $l = $m1 . $2; push @lines, $l if length($l) }else{ $i = 1; push @lines, $m1 if length $m1 } }else{ push @lines, $_ if length $_ } } die "Cannot close $f after reading: $^E" unless close $fd; die "Cannot open $f for writing: $^E" unless open $fd, '>:raw', $f; while(@lines){ $i = shift @lines; die "Cannot write to $f: $^E" unless print $fd $i, "\n" } die "Cannot close $f after writing: $^E" unless close $fd } exit 0 } {package main; main_fun()} # s-it-mode s-nail-14.9.15/mk/su-doxygen.rc000066400000000000000000000050401352610246600161210ustar00rootroot00000000000000#@ SU: doxygen(1) configuration PROJECT_NAME = S-nail PROJECT_NUMBER = 14.9.12 PROJECT_BRIEF = "Send and receive Internet mail" INPUT = include/su EXCLUDE_PATTERNS = gen-*.* OUTPUT_DIRECTORY = .doc #OPTIMIZE_OUTPUT_FOR_C = YES HIDE_UNDOC_MEMBERS = YES SHOW_INCLUDE_FILES = NO MAX_INITIALIZER_LINES = 0 MACRO_EXPANSION = YES #SKIP_FUNCTION_MACROS = NO # Grrr: cannot include code-in.h, need to defined ALL the stuff again??! # Define CXX_DOXYGEN to include C++ docu PREDEFINED = DOXYGEN CXX_DOXYGEN \ \ su_HAVE_DEBUG \ su_HAVE_DEVEL \ su_HAVE_MEM_BAG_AUTO \ su_HAVE_MEM_BAG_LOFI \ su_HAVE_MEM_CANARIES_DISABLE \ \ __STDC_VERSION__=999912L C_LANG \ C_DECL_BEGIN C_DECL_END NSPC_BEGIN NSPC_END \ EXPORT EXPORT_DATA SINLINE INLINE \ su_DBG_LOC_ARGS_DECL= su_DBG_LOC_ARGS_DECL_SOLE= \ CTA(X)= MCTA(X)= NSPC(X)= \ u8:=su_u8 s8:=su_s8 u16:=su_u16 s16:=su_s16 \ u32:=su_u32 s32:=su_s32 u64:=su_u64 s64:=su_s64 \ uz:=su_uz sz:=su_sz up:=su_up sp:=su_sp \ boole:=su_boole \ #EXPAND_ONLY_PREDEF = YES #-> EXPAND_AS_DEFINED = C_DECL_BEGIN EXPORT EXPORT_DATA #RECURSIVE = YES INLINE_GROUPED_CLASSES = YES INLINE_SIMPLE_STRUCTS = YES GROUP_NESTED_COMPOUNDS = YES REPEAT_BRIEF = YES SHORT_NAMES = YES #GENERATE_HTML = YES HTML_DYNAMIC_MENUS = NO HTML_DYNAMIC_SECTIONS = YES GENERATE_LATEX = NO #GENERATE_MAN = yes ## #ALIASES += test{1}="\ref \1" #-> \test{ARG} #ALIASES += test{2}="\ref \1 \"\2\"" #-> \test{ARG,some text} [this overloads \test!] #ALIASES += title{1}= # NOTE: we need the HTML tags since doxygen will merge "\r{BLA}." to # "\ref BLA.", i.e., we need to somehow inject a separator! ALIASES += SU="\em SU" ALIASES += SELF="\em self" ALIASES += THIS="\em this" ALIASES += NIL="\r{su_NIL}" ALIASES += FAL0="\r{su_FAL0}" ALIASES += TRU1="\r{su_TRU1}" ALIASES += TRUM1="\r{su_TRUM1}" ALIASES += ASSERT{2}="Debug-assertion: returns \1 if \2." ALIASES += a{1}="\a \1" ALIASES += c{1}="\c \1" ALIASES += cb{1}="
\code \1\endcode
" ALIASES += copydoc{1}="\r{\1}:  \copydoc \1  " ALIASES += ERR{1}="\c{su_ERR_\1} (\r{su_err_number})" ALIASES += err{1}="\c{err::\1} (\r{err})" ALIASES += fn{1}="\c{\1}" ALIASES += head1{1}="\par \1^^" ALIASES += head2{1}="\par \1^^" ALIASES += li{1}="\li \pb{\1}" ALIASES += list{1}="
\1
" ALIASES += ln{1}="\link \1\endlink" ALIASES += pb{1}="
\parblock \1\endparblock
" ALIASES += r{1}="\ref \1" ALIASES += remarks{1}="\remarks
\parblock \1\endparblock
" ALIASES += vr{1}="\c{\1}" ALIASES += xln{1}="\c{\1}" s-nail-14.9.15/mk/su-find-command.sh000066400000000000000000000067771352610246600170300ustar00rootroot00000000000000#!/bin/sh - #@ Find an executable command within a POSIX shell. #@ which(1) is not standardized, and command(1) -v may return non-executable, #@ so here is how it is possible to really find a usable executable file. #@ Use like this: #@ thecmd_testandset chown chown || #@ PATH="/sbin:${PATH}" thecmd_set chown chown || #@ PATH="/usr/sbin:${PATH}" thecmd_set_fail chown chown #@ or #@ thecmd_testandset_fail MAKE make #@ or #@ MAKE=/usr/bin/make thecmd_testandset_fail MAKE make # # 2017 - 2019 Steffen (Daode) Nurpmeso . # Thanks to Robert Elz (kre). # Public Domain ## First of all, the actual functions need some environment: if [ -z "${SU_FIND_COMMAND_INCLUSION}" ]; then VERBOSE=1 ( set -o noglob ) >/dev/null 2>&1 && noglob_shell=1 || unset noglob_shell msg() { fmt=${1} shift printf >&2 -- "${fmt}\n" "${@}" } fi ## The actual functions acmd_test() { fc__acmd "${1}" 1 0 0; } acmd_test_fail() { fc__acmd "${1}" 1 1 0; } acmd_set() { fc__acmd "${2}" 0 0 0 "${1}"; } acmd_set_fail() { fc__acmd "${2}" 0 1 0 "${1}"; } acmd_testandset() { fc__acmd "${2}" 1 0 0 "${1}"; } acmd_testandset_fail() { fc__acmd "${2}" 1 1 0 "${1}"; } thecmd_set() { fc__acmd "${2}" 0 0 1 "${1}"; } thecmd_set_fail() { fc__acmd "${2}" 0 1 1 "${1}"; } thecmd_testandset() { fc__acmd "${2}" 1 0 1 "${1}"; } thecmd_testandset_fail() { fc__acmd "${2}" 1 1 1 "${1}"; } ## -- >8 - - 8< -- ## fc__pathsrch() { # pname=$1 exec=$2 varname=$3 verbok=$4 fcps__pname=$1 fcps__exec=$2 fcps__varname=$3 fcps__verbok=$4 # It may be an absolute path, check that first if [ "${fcps__exec}" != "${fcps__exec#/}" ] && [ -f "${fcps__exec}" ] && [ -x "${fcps__exec}" ]; then [ -n "${VERBOSE}" ] && [ ${fcps__verbok} -ne 0 ] && msg ' . ${%s} ... %s' \ "${fcps__pname}" "${fcps__exec}" [ -n "${fcps__varname}" ] && eval "${fcps__varname}"="${fcps__exec}" return 0 fi # Manual search over $PATH fcps__oifs=${IFS} IFS=: [ -n "${noglob_shell}" ] && set -o noglob set -- ${PATH} [ -n "${noglob_shell}" ] && set +o noglob IFS=${fcps__oifs} for fcps__path do if [ -z "${fcps__path}" ] || [ "${fcps__path}" = . ]; then if [ -d "${PWD}" ]; then fcps__path=${PWD} else fcps__path=. fi fi if [ -f "${fcps__path}/${fcps__exec}" ] && [ -x "${fcps__path}/${fcps__exec}" ]; then [ -n "${VERBOSE}" ] && [ ${fcps__verbok} -ne 0 ] && msg ' . ${%s} ... %s' \ "${fcps__pname}" "${fcps__path}/${fcps__exec}" [ -n "${fcps__varname}" ] && eval "${fcps__varname}"="${fcps__path}/${fcps__exec}" return 0 fi done return 1 } fc__acmd() { fca__pname=${1} fca__dotest=${2} fca__dofail=${3} \ fca__verbok=${4} fca__varname=${5} if [ "${fca__dotest}" -ne 0 ]; then eval fca__dotest=\$${fca__varname} if [ -n "${fca__dotest}" ]; then if fc__pathsrch "${fca__pname}" "${fca__dotest}" "${fca__varname}" \ ${fca__verbok}; then return 0 fi msg 'WARN: ignoring non-executable ${%s}=%s' \ "${fca__pname}" "${fca__dotest}" fi fi if fc__pathsrch "${fca__pname}" "${fca__pname}" "${fca__varname}" \ ${fca__verbok}; then return 0 fi [ -n "${fca__varname}" ] && eval "${fca__varname}"= [ ${fca__dofail} -eq 0 ] && return 1 msg 'ERROR: no trace of utility '"${fca__pname}" exit 1 } # s-sh-mode s-nail-14.9.15/mk/su-make-cs-ctype.sh000066400000000000000000000076621352610246600171300ustar00rootroot00000000000000#!/bin/sh - #@ Create src/su/gen-cs-ctype.h. # Public Domain OUT=src/su/gen-cs-ctype.h # Generate a more verbose output. Not for shipout versions. VERB=1 ## LC_ALL=C export LC_ALL OUT create() { # Note this may be ISO C89, so we cannot cat <<__EOT__ #include #include #include #include #define su_S(T,I) ((T)(I)) #define u8 uint8_t #define S8_MAX INT8_MAX static void a_csc_init(FILE *fp){ u8 i, any; char const *xpre, *pre; fprintf(fp, "CTAV(su_CS_CTYPE_NONE == 0);\n" "#undef a_X\n" "#define a_X(X) su_CONCAT(su_CS_CTYPE_,X)\n" "u16 const su__cs_ctype[S8_MAX + 1] = {\n"); xpre = $VERB ? " | " : "|"; i = 0; do{ any = 0, pre = ""; if($VERB) fprintf(fp, " /* 0x%02X=%c */ ", (unsigned)i, (isprint(i) ? (char)i : '?')); if(isalnum(i)) fprintf(fp, "%sa_X(ALNUM)", pre), any = 1, pre = xpre; if(isalpha(i)) fprintf(fp, "%sa_X(ALPHA)", pre), any = 1, pre = xpre; if(isblank(i)) fprintf(fp, "%sa_X(BLANK)", pre), any = 1, pre = xpre; if(iscntrl(i)) fprintf(fp, "%sa_X(CNTRL)", pre), any = 1, pre = xpre; if(isdigit(i)) fprintf(fp, "%sa_X(DIGIT)", pre), any = 1, pre = xpre; if(isgraph(i)) fprintf(fp, "%sa_X(GRAPH)", pre), any = 1, pre = xpre; if(islower(i)) fprintf(fp, "%sa_X(LOWER)", pre), any = 1, pre = xpre; if(isprint(i)) fprintf(fp, "%sa_X(PRINT)", pre), any = 1, pre = xpre; if(ispunct(i)) fprintf(fp, "%sa_X(PUNCT)", pre), any = 1, pre = xpre; if(isspace(i)) fprintf(fp, "%sa_X(SPACE)", pre), any = 1, pre = xpre; if(isupper(i)) fprintf(fp, "%sa_X(UPPER)", pre), any = 1, pre = xpre; if(isblank(i) || i == '\n') fprintf(fp, "%sa_X(WHITE)", pre), any = 1, pre = xpre; if(isxdigit(i)) fprintf(fp, "%sa_X(XDIGIT)", pre), any = 1, pre = xpre; if(!any) fprintf(fp, "a_X(NONE)"); fprintf(fp, ",\n"); }while(i++ != S8_MAX); fprintf(fp, "};\n#undef a_X\n"); if($VERB) fprintf(fp, "\n"); fprintf(fp, "u8 const su__cs_tolower[S8_MAX + 1] = {\n"); i = any = 0; do{ if(any + 7 >= 78){ fprintf(fp, "\n"); any = 0; } if(any == 0 && $VERB) fprintf(fp, " "), any = 3; fprintf(fp, "'\\\x%02X',", (u8)tolower((char)i)); any += 7; }while(i++ != S8_MAX); fprintf(fp, "\n};\n"); if($VERB) fprintf(fp, "\n"); fprintf(fp, "u8 const su__cs_toupper[S8_MAX + 1] = {\n"); i = any = 0; do{ if(any + 7 >= 78){ fprintf(fp, "\n"); any = 0; } if(any == 0 && $VERB) fprintf(fp, " "), any = 3; fprintf(fp, "'\\\x%02X',", (u8)toupper((char)i)); any += 7; }while(i++ != S8_MAX); fprintf(fp, "\n};\n"); if($VERB) fprintf(fp, "\n"); } int main(void){ FILE *ofp = fopen("${OUT}", "a"); if(ofp == NULL){ fprintf(stderr, "ERROR: cannot open output\n"); return 1; } fprintf(ofp, "/*@ ${OUT}, generated by su-make-cs-ctype.sh.\n" " *@ See cs-ctype.c for more */\n\n"); a_csc_init(ofp); fclose(ofp); return 0; } __EOT__ } if [ ${#} -ne 0 ]; then if [ "${1}" = noverbose ]; then shift VERB=0 export VERB fi fi if [ ${#} -eq 0 ]; then create > "${TMPDIR}"/su-mk-cs-ctype.c || exit 2 ${CC} -o "${TMPDIR}"/su-mk-cs-ctype "${TMPDIR}"/su-mk-cs-ctype.c || exit 3 rm -f src/su/gen-cs-ctype.h "${TMPDIR}"/su-mk-cs-ctype || exit 4 rm -f "${TMPDIR}"/su-mk-cs-ctype "${TMPDIR}"/su-mk-cs-ctype.c || exit 5 exit 0 fi echo >&2 'Invalid usage' exit 1 # s-it-mode s-nail-14.9.15/mk/su-make-errors.sh000066400000000000000000000374351352610246600167160ustar00rootroot00000000000000#!/bin/sh - #@ Either create src/su/gen-errors.h, or, at compile time, the OS<>SU map. # Public Domain IN="${SRCDIR}"su/gen-errors.h XOUT=src/su/gen-errors.h # We use `vexpr' for hashing MAILX='LC_ALL=C s-nail -#:/' # Acceptable "longest distance" from hash-modulo-index to key MAXDISTANCE_PENALTY=5 # Generate a more verbose output. Not for shipout versions. VERB=1 ## LC_ALL=C export LC_ALL MAXDISTANCE_PENALTY VERB MAILX IN XOUT : ${awk:=awk} # The set of errors we support ERRORS="\ NONE='No error' \ 2BIG='Argument list too long' \ ACCES='Permission denied' \ ADDRINUSE='Address already in use' \ ADDRNOTAVAIL='Cannot assign requested address' \ AFNOSUPPORT='Address family not supported by protocol family' \ AGAIN='Resource temporarily unavailable' \ ALREADY='Operation already in progress' \ BADF='Bad file descriptor' \ BADMSG='Bad message' \ BUSY='Device busy' \ CANCELED='Operation canceled' \ CHILD='No child processes' \ CONNABORTED='Software caused connection abort' \ CONNREFUSED='Connection refused' \ CONNRESET='Connection reset by peer' \ DEADLK='Resource deadlock avoided' \ DESTADDRREQ='Destination address required' \ DOM='Numerical argument out of domain' \ DQUOT='Disc quota exceeded' \ EXIST='File exists' \ FAULT='Bad address' \ FBIG='File too large' \ HOSTUNREACH='No route to host' \ IDRM='Identifier removed' \ ILSEQ='Illegal byte sequence' \ INPROGRESS='Operation now in progress' \ INTR='Interrupted system call' \ INVAL='Invalid argument' \ IO='Input/output error' \ ISCONN='Socket is already connected' \ ISDIR='Is a directory' \ LOOP='Too many levels of symbolic links' \ MFILE='Too many open files' \ MLINK='Too many links' \ MSGSIZE='Message too long' \ MULTIHOP='Multihop attempted' \ NAMETOOLONG='File name too long' \ NETDOWN='Network is down' \ NETRESET='Network dropped connection on reset' \ NETUNREACH='Network is unreachable' \ NFILE='Too many open files in system' \ NOBUFS='No buffer space available' \ NODATA='No data available' \ NODEV='Operation not supported by device' \ NOENT='No such entry, file or directory' \ NOEXEC='Exec format error' \ NOLCK='No locks available' \ NOLINK='Link has been severed' \ NOMEM='Cannot allocate memory' \ NOMSG='No message of desired type' \ NOPROTOOPT='Protocol not available' \ NOSPC='No space left on device' \ NOSR='Out of streams resource' \ NOSTR='Device not a stream' \ NOSYS='Function not implemented' \ NOTCONN='Socket is not connected' \ NOTDIR='Not a directory' \ NOTEMPTY='Directory not empty' \ NOTOBACCO='No tobacco, snorkeling on empty pipe' \ NOTSOCK='Socket operation on non-socket' \ NOTSUP='Operation not supported' \ NOTTY='Inappropriate ioctl for device' \ NXIO='Device not configured' \ OPNOTSUPP='Operation not supported' \ OVERFLOW='Value too large to be stored in data type' \ PERM='Operation not permitted' \ PIPE='Broken pipe' \ PROTO='Protocol error' \ PROTONOSUPPORT='Protocol not supported' \ PROTOTYPE='Protocol wrong type for socket' \ RANGE='Result too large' \ ROFS='Read-only filesystem' \ SPIPE='Invalid seek' \ SRCH='No such process' \ STALE='Stale NFS file handle' \ TIME='Timer expired' \ TIMEDOUT='Operation timed out' \ TXTBSY='Text file busy' \ WOULDBLOCK='Operation would block' \ XDEV='Cross-device link' \ " export ERRORS error_parse() { j=\' ${awk} -v dodoc="${1}" -v incnone="${2}" -v input="${ERRORS}" ' BEGIN{ for(i = 0;;){ voff = match(input, /[0-9a-zA-Z_]+(='${j}'[^'${j}']+)?/) if(voff == 0) break v = substr(input, voff, RLENGTH) input = substr(input, voff + RLENGTH) doff = index(v, "=") if(doff > 0){ d = substr(v, doff + 2, length(v) - doff - 1) v = substr(v, 1, doff - 1) } if(!incnone && v == "NONE") continue print dodoc ? d : v } } ' } config() { [ -n "${TARGET}" ] || { echo >&2 'Invalid usage' exit 1 } # Note this may be ISO C89, so we cannot cat <<__EOT__ #include #include #include #include #include #include #if defined __STDC_VERSION__ && __STDC_VERSION__ + 0 >= 199901L # include #else # include #endif #include <${IN}> #ifdef UINT32_MAX typedef uint32_t u32; typedef int32_t s32; #elif ULONG_MAX == 0xFFFFFFFFu typedef unsigned long int u32; typedef signed long int s32; #else typedef unsigned int u32; typedef signed int s32; #endif struct a_in {struct a_in *next; char const *name; s32 no; u32 uno;}; static int a_sortin(void const *a, void const *b){ return (*(struct a_in const* const *)a)->uno - (*(struct a_in const* const *)b)->uno; } int main(void){ struct a_in *head, *tail, *np, **nap; u32 maxsub, umax; s32 xavail = 0, total = 1, imin = 0, imax = 0, voidoff = 0, i, j; FILE *ofp = fopen("${TARGET}", "a"); if(ofp == NULL){ fprintf(stderr, "ERROR: cannot open output\n"); return 1; } /* Create a list of all errors */ head = tail = (struct a_in*)malloc(sizeof *head); head->next = NULL; head->name = "su_ERR_NONE"; head->no = 0; __EOT__ for n in `error_parse 0 0`; do cat <<__EOT__ ++total; #ifdef E${n} i = E${n}; #else i = --xavail; #endif if(imin > i) {imin = i;} if(imax < i) {imax = i;} np = (struct a_in*)malloc(sizeof *np); np->next = NULL; np->name = "su_ERR_${n}"; np->no = i; tail->next = np; tail = np; __EOT__ done cat <<__EOT__ /* The unsigned type used for storage */ fputs("#define su__ERR_NUMBER_TYPE ", ofp); if((u32)imax <= 0xFFu && (u32)-imin <= 0xFFu){ fputs("u8\n", ofp); maxsub = 0xFFu; }else if(((u32)imax <= 0xFFFFu && (u32)-imin <= 0xFFFFu)){ fputs("u16\n", ofp); maxsub = 0xFFFFu; }else{ fputs("u32\n", ofp); maxsub = 0xFFFFFFFFu; } /* Now that we know the storage type, create the unsigned numbers */ for(umax = 0, np = head; np != NULL; np = np->next){ if(np->no < 0) np->uno = maxsub + np->no + 1; else np->uno = np->no; if(np->uno > umax) umax = np->uno; } if(umax <= (u32)imax){ fprintf(stderr, "ERROR: errno ranges overlap\n"); return 1; } /* Sort this list */ nap = (struct a_in**)malloc(sizeof(*nap) * (unsigned)total); for(i = 0, np = head; np != NULL; ++i, np = np->next) nap[i] = np; if(i != total){ fprintf(stderr, "ERROR: implementation error i != total\n"); return 1; } qsort(nap, (u32)i, sizeof *nap, &a_sortin); /* The enumeration of numbers */ fputs("#define su__ERR_NUMBER_ENUM_C \\\\\\n", ofp); for(i = 0; i < total; ++i) fprintf(ofp, " %s = %lu,\\\\\\n", nap[i]->name, (unsigned long)nap[i]->uno); fprintf(ofp, " su__ERR_NUMBER = %ld\\n", (long)total); fputs("#ifdef __cplusplus\n# define su__CXX_ERR_NUMBER_ENUM \\\\\\n", ofp); for(i = 0; i < total; ++i){ char b[64], *cbp = b; char const *cp; cp = &nap[i]->name[sizeof("su_") -1]; *cbp++ = 'e'; for(cp += sizeof("ERR_") -1; *cp != '\0'; ++cp) *cbp++ = tolower(*cp); *cbp = '\0'; fprintf(ofp, " %s = %s,\\\\\\n", b, nap[i]->name); } fprintf(ofp, " e__number = su__ERR_NUMBER\\n#endif /* __cplusplus */\n"); /* The binary search mapping table from OS error value to our internal * a_corerr_map[] error description table */ fprintf(ofp, "#define su__ERR_NUMBER_TO_MAPOFF \\\\\\n"); for(xavail = 0, i = 0; i < total; ++i){ if(i == 0 || nap[i]->no != nap[i - 1]->no){ for(j = 0; a_names_alphasort[j] != NULL; ++j){ if(!strcmp(&nap[i]->name[sizeof("su_ERR_") -1], a_names_alphasort[j])) break; } fprintf(ofp, "\ta_X(%lu, %lu) %s%s%s\\\\\\n", (unsigned long)nap[i]->uno, (long)(u32)j, ((${VERB}) ? "/* " : ""), ((${VERB}) ? nap[i]->name : ""), ((${VERB}) ? " */ " : "")); if(!strcmp("su_ERR_NOTOBACCO", nap[i]->name)) voidoff = j; ++xavail; } } fprintf(ofp, "\\t/* %ld unique members */\\n", (long)xavail); fprintf(ofp, "#define su__ERR_NUMBER_VOIDOFF %ld\\n", (long)voidoff); fclose(ofp); while((np = head) != NULL){ head = np->next; free(np); } free(nap); return 0; } __EOT__ exit 0 } if [ ${#} -ne 0 ]; then if [ "${1}" = noverbose ]; then shift VERB=0 export VERB fi fi if [ ${#} -eq 1 ]; then [ "${1}" = config ] && config elif [ ${#} -eq 0 ]; then # Now start perl(1) without PERL5OPT set to avoid multibyte sequence errors PERL5OPT= PERL5LIB= exec perl -x "${0}" fi echo >&2 'Invalid usage' exit 1 # PERL {{{ # Thanks to perl(5) and it's -x / #! perl / __END__ mechanism! # Why can env(1) not be used for such easy things in #!? #!perl #use diagnostics -verbose; use strict; use warnings; use FileHandle; use IPC::Open2; use sigtrap qw(handler cleanup normal-signals); my ($S, @ENTS, $CTOOL, $CTOOL_EXE) = ($ENV{VERB} ? ' ' : ''); sub main_fun{ create_ents(); create_c_tool(); hash_em(); dump_map(); # Starts output file reverser(); # Ends output file cleanup(undef); exit 0 } sub cleanup{ die "$CTOOL_EXE: couldn't unlink: $^E" if $CTOOL_EXE && -f $CTOOL_EXE && 1 != unlink $CTOOL_EXE; die "$CTOOL: couldn't unlink: $^E" if $CTOOL && -f $CTOOL && 1 != unlink $CTOOL; die "Terminating due to signal $_[0]" if $_[0] } sub basen{ my $n = $_[0]; $n =~ s/^(.*\/)?([^\/]+)$/$2/; $n } sub create_ents{ my $input = $ENV{ERRORS}; while($input =~ /([[:alnum:]_]+)='([^']+)'(.*)/){ $input = $3; my %vals; $vals{name} = $1; $vals{doc} = $2; push @ENTS, \%vals } } sub create_c_tool{ $CTOOL = './tmp-errors-tool-' . $$ . '.c'; $CTOOL_EXE = $CTOOL . '.exe'; die "$CTOOL: open: $^E" unless open F, '>', $CTOOL; print F '#define MAX_DISTANCE_PENALTY ', $ENV{MAXDISTANCE_PENALTY}, "\n"; # >>>>>>>>>>>>>>>>>>> print F <<'_EOT'; #define __CREATE_ERRORS_SH #define su_SOURCE #include #include #include #include #define su_NELEM(A) (sizeof(A) / sizeof((A)[0])) #define u32 uint32_t #define s32 int32_t #define u16 uint16_t #define u8 uint8_t struct a_corerr_map{ u32 cem_hash; /* Hash of name */ u32 cem_nameoff; /* Into a_corerr_names[] */ u32 cem_docoff; /* Into a_corerr_docs[] */ s32 cem_errno; /* OS errno value for this one */ }; _EOT print F '#include "', $ENV{XOUT}, "\"\n\n"; print F <<'_EOT'; static u8 seen_wraparound; static size_t longest_distance; static size_t next_prime(size_t no){ /* blush (brute force) */ jredo: ++no; for(size_t i = 3; i < no; i += 2) if(no % i == 0) goto jredo; return no; } static size_t * reversy(size_t size){ struct a_corerr_map const *cemp = a_corerr_map, *cemaxp = cemp + su_NELEM(a_corerr_map); size_t ldist = 0, *arr; arr = (size_t*)malloc(sizeof *arr * size); for(size_t i = 0; i < size; ++i) arr[i] = su_NELEM(a_corerr_map); seen_wraparound = 0; longest_distance = 0; while(cemp < cemaxp){ u32 hash = cemp->cem_hash, i = hash % size, l; for(l = 0; arr[i] != su_NELEM(a_corerr_map); ++l) if(++i == size){ seen_wraparound = 1; i = 0; } if(l > longest_distance) longest_distance = l; arr[i] = (size_t)(cemp++ - a_corerr_map); } return arr; } int main(int argc, char **argv){ size_t *arr, size = su_NELEM(a_corerr_map); fprintf(stderr, "Starting reversy, okeys=%zu\n", size); for(;;){ arr = reversy(size = next_prime(size)); fprintf(stderr, " - size=%zu longest_distance=%zu seen_wraparound=%d\n", size, longest_distance, seen_wraparound); if(longest_distance <= MAX_DISTANCE_PENALTY) break; free(arr); } printf( "#ifdef su_SOURCE /* Lock-out compile-time-tools */\n" "# define a_CORERR_REV_ILL %zuu\n" "# define a_CORERR_REV_PRIME %zuu\n" "# define a_CORERR_REV_LONGEST %zuu\n" "# define a_CORERR_REV_WRAPAROUND %d\n" "static %s const a_corerr_revmap[a_CORERR_REV_PRIME] = {\n%s", su_NELEM(a_corerr_map), size, longest_distance, seen_wraparound, argv[1], (argc > 2 ? " " : "")); for(size_t i = 0; i < size; ++i) printf("%s%zuu", (i == 0 ? "" : (i % 10 == 0 ? (argc > 2 ? ",\n " : ",\n") : (argc > 2 ? ", " : ","))), arr[i]); printf("\n};\n#endif /* su_SOURCE */\n"); return 0; } _EOT # <<<<<<<<<<<<<<<<<<< close F } sub hash_em{ die "hash_em: open: $^E" unless my $pid = open2 *RFD, *WFD, $ENV{MAILX}; foreach my $e (@ENTS){ print WFD "vexpr hash32 $e->{name}\n"; my $h = ; chomp $h; $e->{hash} = $h } print WFD "x\n"; waitpid $pid, 0; } sub dump_map{ my ($i, $alen); die "$ENV{XOUT}: open: $^E" unless open F, '>', $ENV{XOUT}; print F '/*@ ', scalar basen($ENV{XOUT}), ', generated by ', scalar basen($0), ".\n *@ See core-errors.c for more */\n\n"; print F '#ifndef su_SOURCE /* For compile-time tools only */', "\n", 'static char const * const a_names_alphasort[] = {'; ($i, $alen) = (0, 0); foreach my $e (@ENTS){ $i = 1 + 3 + length $e->{name}; if($alen == 0 || $alen + $i > 75){ print F "\n${S}"; $alen = length $S }else{ print F ' '; ++$i } $alen += $i; print F "\"$e->{name}\"," } print F " NULL\n};\n#endif /* !su_SOURCE */\n\n"; ($i, $alen) = (0, 0); print F '#ifdef su_SOURCE', "\n", 'static char const a_corerr_names[] = {', "\n"; ($i, $alen) = (0, 0); foreach my $e (@ENTS){ $e->{nameoff} = $alen; my $k = $e->{name}; my $l = length $k; my $a = join '\',\'', split(//, $k); my (@fa); print F "${S}/* $i. [$alen]+$l $k */\n" if $ENV{VERB}; print F "${S}'$a','\\0',\n"; ++$i; $alen += $l + 1 } print F '};', "\n\n"; print F '# ifdef su_HAVE_DOCSTRINGS', "\n"; print F '# undef a_X', "\n", '# define a_X(X)', "\n"; print F 'static char const a_corerr_docs[] = {', "\n"; ($i, $alen) = (0, 0); foreach my $e (@ENTS){ $e->{docoff} = $alen; my $k = $e->{doc}; my $l = length $k; my $a = join '\',\'', split(//, $k); my (@fa); print F "${S}/* $i. [$alen]+$l $e->{name} */ ", "a_X(N_(\"$e->{doc}\"))\n" if $ENV{VERB}; print F "${S}'$a','\\0',\n"; ++$i; $alen += $l + 1 } print F '};', "\n", '# undef a_X', "\n# endif /* su_HAVE_DOCSTRINGS */\n\n"; print F <<_EOT; # undef a_X # ifndef __CREATE_ERRORS_SH # define a_X(X) X # else # define a_X(X) 0 # endif static struct a_corerr_map const a_corerr_map[] = { _EOT foreach my $e (@ENTS){ print F "${S}{$e->{hash}u, $e->{nameoff}u, $e->{docoff}u, ", "a_X(su_ERR_$e->{name})},\n" } print F '};', "\n", '# undef a_X', "\n", '#endif /* su_SOURCE */', "\n\n"; die "$ENV{XOUT}: close: $^E" unless close F } sub reverser{ my $argv2 = $ENV{VERB} ? ' verb' : ''; system("\$CC -I. -o $CTOOL_EXE $CTOOL"); my $t = (@ENTS < 0xFF ? 'u8' : (@ENTS < 0xFFFF ? 'u16' : 'u32')); `$CTOOL_EXE $t$argv2 >> $ENV{XOUT}` } {package main; main_fun()} # }}} # s-it-mode s-nail-14.9.15/mk/su-quote-rndtrip.sh000066400000000000000000000026641352610246600173000ustar00rootroot00000000000000#!/bin/sh - #@ Round trip quote strings in POSIX shell. E.g., #@ set -- x 'a \ b' "foo'" "\\'b\\a\\r\\" Aä #@ printf "%s: <%s><%s><%s><%s><%s>\n" "$#" "${1}" "${2}" "${3}" "$4" "$5" #@ saved_parameters=`quote_rndtrip "$@"` #@ eval "set -- $saved_parameters" #@ printf "%s: <%s><%s><%s><%s><%s>\n" "$#" "${1}" "${2}" "${3}" "$4" "$5" # # 2017 Robert Elz (kre). # 2017 - 2019 Steffen (Daode) Nurpmeso . # Public Domain # Though slower use a subshell version instead of properly restoring $IFS # and flags, as elder shells may not be able to properly restore flags via # "set +o" as later standardized in POSIX, and it seems overkill to handle # all possible forms of output "set +o" may or may not actually generate. quote__rndtrip() ( case "$1" in *\'*) ;; *) printf "'%s'" "$1"; return 0;; esac a="$1" s= e= while case "$a" in \'*) a=${a#?}; s="${s}\\\\'";; *\') a=${a%?}; e="${e}\\\\'";; '') printf "${s}${e}"; exit 0;; *) false;; esac do continue done IFS=\' set -f set -- $a r="${1}" shift for a do r="${r}'\\''${a}" done printf "${s}'%s'${e}" "${r}" exit 0 ) quote_rndtrip() ( j= for i do [ -n "$j" ] && printf ' ' j=' ' quote__rndtrip "$i" done ) quote_string() ( j= for i do [ -n "$j" ] && printf '\\ ' j=' ' quote__rndtrip "$i" done ) # s-sh-mode s-nail-14.9.15/mx-config.h000066400000000000000000000143751352610246600151350ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Some constants etc. for which adjustments may be desired. *@ This is included (as mx/config.h) after all the (system) headers. *@ TODO It is a wild name convention mess, to say the least. * * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #ifndef mx_CONFIG_H # define mx_CONFIG_H #define ACCOUNT_NULL "null" /* Name of "null" account */ #define n_ALIAS_MAXEXP 25 /* Maximum expansion of aliases */ /* Protocol version for *on-compose-splice** -- update manual on change! */ #define mx_DIG_MSG_PLUMBING_VERSION "0 0 1" #define mx_DOTLOCK_TRIES 5 /* Number of open(2) calls for dotlock */ #define n_ERROR "ERROR" /* Is-error? Also as n_error[] */ #define mx_ERRORS_MAX 5000 /* Error queue size (s32) TODO configurable */ #define n_ESCAPE "~" /* Default escape for sending (POSIX standard) */ #define mx_FILE_LOCK_TRIES 10 /* Maximum tries before file_lock() fails */ #define mx_FILE_LOCK_MILLIS 200 /* If UZ_MAX, fall back to that */ #define n_FORWARD_INJECT_HEAD "-------- Original Message --------\n" /* DOC! */ #define n_FORWARD_INJECT_TAIL NULL /* DOC! */ #define mx_FS_FILETYPE_CAT_PROG "cat" /* cat(1) */ #define mx_FS_TMP_OPEN_TRIES 10 /* Maximum number of fs_tmp_open() tries */ #define n_IMAP_DELIM "/." /* Directory separator ([0] == replacer, too) */ #define n_LINE_EDITOR_CPL_WORD_BREAKS "\"'@=;|:" /* Fallback in case the systems reports an empty hostname (?) */ #define n_LOCALHOST_DEFAULT_NAME "localhost.localdomain" #define n_MAILDIR_SEPARATOR ':' /* Flag separator character */ #define n_MAXARGC 512 /* Maximum list of raw strings TODO dyn vector! */ #define n_PATH_DEVNULL "/dev/null" /* Note: manual uses /dev/null as such */ #define n_QUOTE_INJECT_HEAD "%f wrote:\n\n" /* DOC! */ #define n_QUOTE_INJECT_TAIL NULL /* DOC! */ #define REFERENCES_MAX 20 /* Maximum entries in References: */ #define mx_VEXPR_REGEX_MAX 16 /* Maximum address. `vexpr' regex(7) matches */ /* * */ /* Default log level */ #define n_LOG_LEVEL su_DBGOR(su_LOG_WARN, su_LOG_CRIT) /* * */ /* Fallback MIME charsets, if *charset-7bit* and *charset-8bit* are not set. * Note: must be lowercase! * (Keep in SYNC: ./nail.1:"Character sets", mx-config.h:CHARSET_*!) */ #define CHARSET_7BIT "us-ascii" #ifdef mx_HAVE_ICONV # define CHARSET_8BIT "utf-8" # define CHARSET_8BIT_OKEY charset_8bit #else # ifdef mx_HAVE_ALWAYS_UNICODE_LOCALE # define CHARSET_8BIT "utf-8" # else # define CHARSET_8BIT "iso-8859-1" # endif # define CHARSET_8BIT_OKEY ttycharset #endif #ifndef HOST_NAME_MAX # ifdef _POSIX_HOST_NAME_MAX # define HOST_NAME_MAX _POSIX_HOST_NAME_MAX # else # define HOST_NAME_MAX 255 # endif #endif /* Supported IDNA implementations TODO should not be here!?! */ #define n_IDNA_IMPL_LIBIDN2 0 #define n_IDNA_IMPL_LIBIDN 1 #define n_IDNA_IMPL_IDNKIT 2 /* 1 + 2 */ /* Max readable line width TODO simply use BUFSIZ? */ #if BUFSIZ + 0 > 2560 # define LINESIZE BUFSIZ #else # define LINESIZE 2560 #endif #define BUFFER_SIZE (BUFSIZ >= (1u << 13) ? BUFSIZ : (1u << 14)) /* Default *mime-encoding* as enum mime_enc; one of _B64, _QP, _8B */ #define MIME_DEFAULT_ENCODING MIMEE_QP /* Maximum allowed line length in a mail before QP folding is necessary), and * the real limit we go for */ #define MIME_LINELEN_MAX 998 /* Plus CRLF */ #define MIME_LINELEN_LIMIT (MIME_LINELEN_MAX - 48) /* Ditto, SHOULD */ #define MIME_LINELEN 78 /* Plus CRLF */ /* And in headers which contain an encoded word according to RFC 2047 there is * yet another limit; also RFC 2045: 6.7, (5). */ #define MIME_LINELEN_RFC2047 76 /* TODO PATH_MAX: fixed-size buffer is always wrong (think NFS) */ #ifndef PATH_MAX # ifdef MAXPATHLEN # define PATH_MAX MAXPATHLEN # else # define PATH_MAX 1024 /* _XOPEN_PATH_MAX POSIX 2008/Cor 1-2013 */ # endif #endif /* Some environment variables for pipe hooks etc. */ #define n_PIPEENV_FILENAME "MAILX_FILENAME" #define n_PIPEENV_FILENAME_GENERATED "MAILX_FILENAME_GENERATED" #define n_PIPEENV_FILENAME_TEMPORARY "MAILX_FILENAME_TEMPORARY" #define n_PIPEENV_CONTENT "MAILX_CONTENT" #define n_PIPEENV_CONTENT_EVIDENCE "MAILX_CONTENT_EVIDENCE" #define n_PIPEENV_EXTERNAL_BODY_URL "MAILX_EXTERNAL_BODY_URL" /* Maximum number of quote characters (not bytes!) that will be used on * follow lines when compressing leading quote characters */ #define n_QUOTE_MAX 42u /* How much spaces a counts when *quote-fold*ing? (power-of-two!) */ #define n_QUOTE_TAB_SPACES 8 /* Smells fishy after, or asks for shell expansion, dependent on context */ #define n_SHEXP_MAGIC_PATH_CHARS "|&;<>{}()[]*?$`'\"\\" /* Port to native MS-Windows and to ancient UNIX */ #if !defined S_ISDIR && defined S_IFDIR && defined S_IFMT # define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) #endif /* Maximum size of a message that is passed through to the spam system */ #define SPAM_MAXSIZE 420000 #ifndef NAME_MAX # ifdef _POSIX_NAME_MAX # define NAME_MAX _POSIX_NAME_MAX # else # define NAME_MAX 14 # endif #endif #if NAME_MAX + 0 < 8 # error NAME_MAX is too small #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 #ifdef O_NOCTTY # define mx_O_NOCTTY O_NOCTTY #else # define mx_O_NOCTTY 0 #endif /* #ifdef O_NOFOLLOW # define mx_O_NOFOLLOW O_NOFOLLOW #else # define mx_O_NOFOLLOW 0 #endif */ #define mx_O_NOXY_BITS (mx_O_NOCTTY /*| mx_O_NOFOLLOW*/) #ifdef NSIG_MAX # undef NSIG # define NSIG NSIG_MAX #elif !defined NSIG # define NSIG ((sizeof(sigset_t) * 8) - 1) #endif #endif /* mx_CONFIG_H */ /* s-it-mode */ s-nail-14.9.15/mx-test.sh000077500000000000000000006771631352610246600150470ustar00rootroot00000000000000#!/bin/sh - #@ Synopsis: [OBJDIR=XY] ./mx-test.sh --check-only [s-mailx-binary] #@ [OBJDIR=XY] ./mx-test.sh --run-test s-mailx-binary [:TESTNAME:] #@ [./mx-test.sh # Note: performs hundreds of compilations!] #@ --no-jobs can be used to prevent spawning concurrent tests. # Public Domain : ${OBJDIR:=.obj} # Instead of figuring out the environment in here, require a configured build # system and include that! Our makefile and configure ensure that this test # does not run in the configured, but the user environment nonetheless! i= while true; do if [ -f ./mk-config.env ]; then break elif [ -f snailmail.jpg ] && [ -f "${OBJDIR}"/mk-config.env ]; then i=`pwd`/ # not from environment, sic cd "${OBJDIR}" break else echo >&2 'S-nail/S-mailx is not configured.' echo >&2 'This test script requires the shell environment that only the' echo >&2 'configuration script can figure out, even if used to test' echo >&2 'a different binary than the one that would be produced!' echo >&2 '(The information will be in ${OBJDIR:=.obj}/mk-config.env.)' echo >&2 'Hit RETURN to run "make config CONFIG=null' read l make config CONFIG=null fi done . ./mk-config.env if [ -z "${MAILX__CC_TEST_RUNNING}" ]; then MAILX__CC_TEST_RUNNING=1 export MAILX__CC_TEST_RUNNING exec "${SHELL}" "${i}${0}" "${@}" fi # We need *stealthmua* regardless of $SOURCE_DATE_EPOCH, the program name as # such is a compile-time variable ARGS='-Sv15-compat -:/ -Sdotlock-disable -Sexpandaddr=restrict -Smemdebug' ARGS="${ARGS}"' -Smime-encoding=quoted-printable -Snosave -Sstealthmua' NOBATCH_ARGS="${ARGS}"' -Sexpandaddr' ARGS="${ARGS}"' -#' ADDARG_UNI=-Sttycharset=UTF-8 CONF=../make.rc BODY=./.cc-body.txt MBOX=./.cc-test.mbox ERR=./.cc-test.err # Covers some which cannot be checksummed; not quoted! MAIL=/dev/null #UTF8_LOCALE= autodetected unless set TMPDIR=`pwd` # When testing mass mail/loops, maximum number of receivers/loops. # TODO note we do not gracefully handle ARG_MAX excess yet! # Those which use this have checksums for 2001 and 201. # Some use the smaller automatically if +debug LOOPS_BIG=2001 LOOPS_SMALL=201 LOOPS_MAX=$LOOPS_SMALL # How long unless started tests get reaped (avoid endless looping) # In debug built the value is doubled (due to memory checks). JOBWAIT=66 # Note valgrind has problems with FDs in forked childs, which causes some tests # to fail (the FD is rewound and thus will be dumped twice) MEMTESTER= #MEMTESTER='valgrind --leak-check=full --log-file=.vl-%p ' ## -- >8 -- 8< -- ## t_all() { # Absolute Basics jspawn X_Y_opt_input_go_stack jspawn X_errexit jspawn Y_errexit jspawn S_freeze jspawn input_inject_semicolon_seq jspawn wysh jspawn commandalias # test now, save space later on! jsync # Basics jspawn shcodec jspawn ifelse jspawn localopts jspawn local jspawn environ jspawn macro_param_shift jspawn addrcodec jspawn csop jspawn vexpr jspawn call_ret jspawn xcall jspawn vpospar jspawn atxplode jspawn read jsync # Send/RFC absolute basics jspawn can_send_rfc jsync # VFS jspawn mbox jspawn maildir jsync # MIME and RFC basics jspawn mime_if_not_ascii jspawn mime_encoding jspawn xxxheads_rfc2047 jspawn iconv_mbyte_base64 jspawn iconv_mainbody jspawn mime_force_sendout jspawn binary_mainbody jspawn C_opt_customhdr jsync # Operational basics with trivial tests jspawn alias jspawn charsetalias jspawn shortcut jsync # Operational basics with easy tests jspawn expandaddr # (after t_alias) jspawn mta_aliases # (after t_expandaddr) jspawn filetype jspawn record_a_resend jspawn e_H_L_opts jspawn q_t_etc_opts jspawn message_injections jspawn attachments jspawn rfc2231 # (after attachments) jspawn mime_types_load_control jsync # Around state machine, after basics jspawn alternates jspawn quote_a_cmd_escapes jspawn compose_edits jspawn digmsg jspawn on_main_loop_tick jspawn on_program_exit jsync # Heavy use of/rely on state machine (behaviour) and basics jspawn compose_hooks jspawn mass_recipients jspawn lreply_futh_rth_etc jspawn pipe_handlers jsync # Rest jspawn s_mime jspawn z jsync jsync 1 } ## Now it is getting really weird. You have been warned. # Setup and support {{{ export ARGS ADDARG_UNI CONF BODY MBOX MAIL TMPDIR \ MAKE awk cat cksum rm sed grep LC_ALL=C LANG=C TZ=UTC # Wed Oct 2 01:50:07 UTC 1996 SOURCE_DATE_EPOCH=844221007 export LC_ALL LANG TZ SOURCE_DATE_EPOCH unset POSIXLY_CORRECT LOGNAME USER # usage {{{ usage() { ${cat} >&2 <<_EOT Synopsis: mx-test.sh [--no-jobs] --check-only s-mailx-binary Synopsis: mx-test.sh [--no-jobs] --run-test s-mailx-binary [:TEST:] Synopsis: mx-test.sh [--no-jobs] --check-only EXE run the test series, exit success or error; if run in a git(1) checkout then failed tests create test output data files --run-test EXE [:TEST:] run all or only the given TESTs, and create test output data files; if run in a git(1) checkout with the [test-out] branch available, it will also create file differences --no-jobs do not spawn multiple jobs simultaneously The last invocation style will compile and test as many different configurations as possible. _EOT exit 1 } NOJOBS= DUMPERR= CHECK_ONLY= RUN_TEST= MAXJOBS=1 GIT_REPO= MAILX= if [ "${1}" = --no-jobs ]; then NOJOBS=y shift fi if [ "${1}" = --check-only ]; then [ ${#} -eq 2 ] || usage CHECK_ONLY=1 MAILX=${2} [ -x "${MAILX}" ] || usage echo 'Mode: --check-only, binary: '"${MAILX}" [ -d ../.git ] && [ -z "${MAILX__CC_TEST_NO_DATA_FILES}" ] && GIT_REPO=1 elif [ "${1}" = --run-test ]; then [ ${#} -ge 2 ] || usage RUN_TEST=1 MAILX=${2} [ -x "${MAILX}" ] || usage shift 2 echo 'Mode: --run-test, binary: '"${MAILX}" [ -d ../.git ] && GIT_REPO=1 else [ ${#} -eq 0 ] || usage echo 'Mode: full compile test, this will take a long time...' MAILX__CC_TEST_NO_DATA_FILES=1 export MAILX__CC_TEST_NO_DATA_FILES fi # }}} MAILX=`${pwd}`/${MAILX} RAWMAILX=${MAILX} MAILX="${MEMTESTER}${MAILX}" export RAWMAILX MAILX # We want an UTF-8 locale {{{ if [ -n "${CHECK_ONLY}${RUN_TEST}" ]; then if [ -z "${UTF8_LOCALE}" ]; then # Try ourselfs via nl_langinfo(CODESET) first (requires a new version) if command -v "${RAWMAILX}" >/dev/null 2>&1 && ("${RAWMAILX}" -:/ -Xxit) >/dev/null 2>&1; then echo 'Trying to detect UTF-8 locale via '"${RAWMAILX}" i=`LC_ALL=C.utf8 "${RAWMAILX}" ${ARGS} -X ' \define cset_test { \if "${ttycharset}" =%?case utf \echo $LC_ALL \xit 0 \end \if "${#}" -gt 0 \wysh set LC_ALL=${1} \shift \eval xcall cset_test "${@}" \end \xit 1 } \call cset_test C.UTF-8 POSIX.utf8 POSIX.UTF-8 \ en_EN.utf8 en_EN.UTF-8 en_US.utf8 en_US.UTF-8 '` [ $? -eq 0 ] && UTF8_LOCALE=$i fi if [ -z "${UTF8_LOCALE}" ] && (locale yesexpr) >/dev/null 2>&1; then echo 'Trying to detect UTF-8 locale via locale -a' UTF8_LOCALE=`locale -a | { m= while read n; do if { echo ${n} | ${grep} -i -e utf8 -e utf-8; } >/dev/null 2>&1; then m=${n} if { echo ${n} | ${grep} -e POSIX -e en_EN -e en_US; } \ >/dev/null 2>&1; then break fi fi done echo ${m} }` fi fi if [ -n "${UTF8_LOCALE}" ]; then echo 'Using Unicode locale '"${UTF8_LOCALE}" else echo 'No Unicode locale found, disabling Unicode tests' fi fi # }}} TESTS_PERFORMED=0 TESTS_OK=0 TESTS_FAILED=0 TESTS_SKIPPED=0 JOBS=0 JOBLIST= JOBDESC= JOBREAPER= JOBSYNC= COLOR_ERR_ON= COLOR_ERR_OFF= COLOR_WARN_ON= COLOR_WARN_OFF= COLOR_OK_ON= COLOR_OK_OFF= ESTAT=0 TEST_NAME= trap "${rm} -rf ./t.*.d ./t.*.io ./t.*.result; jobreaper_stop" EXIT trap "exit 1" HUP INT TERM # JOBS {{{ if [ -n "${NOJOBS}" ]; then jobs_max() { :; } else jobs_max() { # Do only use half of the CPUs, since we live in pipes or do a lot of # shell stuff, and i have seen lesser timeouts like this (on SunOS 5.9) # ..the user desired variant if ( echo "${MAKEFLAGS}" | ${grep} -- -j ) >/dev/null 2>&1; then i=`echo "${MAKEFLAGS}" | ${sed} -e 's/^.*-j[ ]*\([0-9]\{1,\}\).*$/\1/'` if ( echo "${i}" | grep -q -e '^[0-9]\{1,\}$' ); then printf 'Job number derived from MAKEFLAGS: %s\n' ${i} MAXJOBS=${i} [ "${MAXJOBS}" -eq 0 ] && MAXJOBS=1 return fi fi # The actual hardware if ( ${MAKE} -j 10 --version ) >/dev/null 2>&1; then if command -v nproc >/dev/null 2>&1; then i=`nproc 2>/dev/null` [ ${?} -eq 0 ] && MAXJOBS=${i} else i=`getconf _NPROCESSORS_ONLN 2>/dev/null` j=${?} if [ ${j} -ne 0 ]; then i=`getconf NPROCESSORS_ONLN 2>/dev/null` j=${?} fi if [ ${j} -ne 0 ]; then # SunOS 5.9 ++ if command -v kstat >/dev/null 2>&1; then i=`PERL5OPT= kstat -p cpu | ${awk} ' BEGIN{no=0; FS=":"} {if($2 > no) max = $2; next} END{print ++max} ' 2>/dev/null` j=${?} fi fi if [ ${j} -eq 0 ] && [ -n "${i}" ]; then printf 'Job number derived from CPU number: %s\n' ${i} MAXJOBS=${i} fi fi [ "${MAXJOBS}" -eq 0 ] && MAXJOBS=1 fi } fi jobreaper_start() { ( sleep .1 ) >/dev/null 2>&1 && sleep_subsecond=1 || sleep_subsecond= i= trap 'i=1' USR1 printf 'Starting job reaper\n' ( parent=${$} sleeper= int=0 hot=0 trap ' [ -n "${sleeper}" ] && kill -TERM ${sleeper} >/dev/null 2>&1 int=1 hot=1 ' USR1 trap ' [ -n "${sleeper}" ] && kill -TERM ${sleeper} >/dev/null 2>&1 int=1 hot=0 ' USR2 trap ' [ -n "${sleeper}" ] && kill -TERM ${sleeper} >/dev/null 2>&1 echo "Stopping job reaper" exit 0 ' TERM # traps are setup, notify parent that we are up and running kill -USR1 ${parent} while :; do int=0 sleep ${JOBWAIT} & sleeper=${!} wait ${!} sleeper= [ "${int}${hot}" = 01 ] && kill -USR1 ${parent} >/dev/null 2>&1 done ) /dev/null 2>&1 & JOBREAPER=${!} if [ ${?} -eq 0 ]; then while :; do if [ -n "${i}" ]; then trap '' USR1 return fi printf '.. waiting for job reaper to come up\n' sleep 1 & wait ${!} done fi JOBREAPER= printf '%s!! Cannot start wild job reaper%s\n' \ "${COLOR_ERR_ON}" "${COLOR_ERR_OFF}" } jobreaper_stop() { if [ -n "${JOBREAPER}" ]; then kill -TERM ${JOBREAPER} JOBREAPER= fi } jspawn() { if [ ${MAXJOBS} -gt 1 ]; then # We are spawning multiple jobs.. [ ${JOBS} -eq 0 ] && printf '...' JOBS=`add ${JOBS} 1` printf ' [%s=%s]' ${JOBS} "${1}" else JOBS=1 fi ( ${mkdir} t.${JOBS}.d && cd t.${JOBS}.d && eval t_${1} ${JOBS} ${1} && ${rm} -f ../t.${JOBS}.id ) > t.${JOBS}.io &1 t.${JOBS}.id # ..until we should sync or reach the maximum concurrent number [ ${JOBS} -lt ${MAXJOBS} ] && return jsync 1 } jsync() { [ ${JOBS} -eq 0 ] && return [ -z "${JOBSYNC}" ] && [ ${#} -eq 0 ] && return [ ${MAXJOBS} -ne 1 ] && printf ' .. waiting\n' if [ -n "${JOBREAPER}" ]; then timeout= alldone= trap 'timeout=1' USR1 kill -USR1 ${JOBREAPER} loops=0 while [ -z "${timeout}" ]; do alldone=1 i=0 while [ ${i} -lt ${JOBS} ]; do i=`add ${i} 1` [ -f t.${i}.id ] || continue alldone= break done [ -n "${alldone}" ] && break if [ -z "${sleep_subsecond}" ]; then loops=`add ${loops} 1` [ ${loops} -lt 111 ] && continue sleep 1 & else sleep .1 & fi wait ${!} done kill -USR2 ${JOBREAPER} trap '' USR1 if [ -n "${timeout}" ]; then i=0 while [ ${i} -lt ${JOBS} ]; do i=`add ${i} 1` if [ -f t.${i}.id ] && read pid < t.${i}.id >/dev/null 2>&1; then kill -KILL ${pid} fi done fi fi # Now collect the zombies wait ${JOBLIST} JOBLIST= # Update global counters i=0 while [ ${i} -lt ${JOBS} ]; do i=`add ${i} 1` [ -s t.${i}.io ] && ${cat} t.${i}.io if [ -n "${DUMPERR}" ] && [ -s ./t.${i}.d/${ERR} ]; then printf '%s [Debug/Devel: nullified errors]\n' "${COLOR_ERR_ON}" while read l; do printf ' %s\n' "${l}" done < t.${i}.d/${ERR} printf '%s' "${COLOR_ERR_OFF}" fi if [ -f t.${i}.id ]; then { read pid; read desc; } < t.${i}.id desc=${desc#${desc%%[! ]*}} desc=${desc%${desc##*[! ]}} printf >&2 '%s!! Timeout: reaped job %s [%s]%s\n' \ "${COLOR_ERR_ON}" ${i} "${desc}" "${COLOR_ERR_OFF}" TESTS_FAILED=`add ${TESTS_FAILED} 1` elif [ -s t.${i}.result ]; then read es tp to tf ts < t.${i}.result TESTS_PERFORMED=`add ${TESTS_PERFORMED} ${tp}` TESTS_OK=`add ${TESTS_OK} ${to}` TESTS_FAILED=`add ${TESTS_FAILED} ${tf}` TESTS_SKIPPED=`add ${TESTS_SKIPPED} ${ts}` [ "${es}" != 0 ] && ESTAT=${es} else TESTS_FAILED=`add ${TESTS_FAILED} 1` ESTAT=1 fi done ${rm} -rf ./t.*.d ./t.*.id ./t.*.io t.*.result JOBS=0 } # }}} # echoes, checks, etc. {{{ t_prolog() { shift ESTAT=0 TESTS_PERFORMED=0 TESTS_OK=0 TESTS_FAILED=0 TESTS_SKIPPED=0 \ TEST_NAME=${1} TEST_ANY= printf '%s[%s]%s\n' "" "${TEST_NAME}" "" } t_epilog() { [ -n "${TEST_ANY}" ] && printf '\n' printf '%s %s %s %s %s\n' \ ${ESTAT} \ ${TESTS_PERFORMED} ${TESTS_OK} ${TESTS_FAILED} ${TESTS_SKIPPED} \ > ../t.${1}.result } t_echo() { [ -n "${TEST_ANY}" ] && __i__=' ' || __i__= printf "${__i__}"'%s' "${*}" TEST_ANY=1 } t_echook() { [ -n "${TEST_ANY}" ] && __i__=' ' || __i__= printf "${__i__}"'%s%s:ok%s' "${COLOR_OK_ON}" "${*}" "${COLOR_OK_OFF}" TEST_ANY=1 } t_echoerr() { ESTAT=1 [ -n "${TEST_ANY}" ] && __i__="\n" || __i__= printf "${__i__}"'%sERROR: %s%s\n' \ "${COLOR_ERR_ON}" "${*}" "${COLOR_ERR_OFF}" TEST_ANY= } t_echowarn() { [ -n "${TEST_ANY}" ] && __i__=' ' || __i__= printf "${__i__}"'%s%s%s' "${COLOR_WARN_ON}" "${*}" "${COLOR_WARN_OFF}" TEST_ANY=1 } t_echoskip() { [ -n "${TEST_ANY}" ] && __i__=' ' || __i__= printf "${__i__}"'%s%s[skip]%s' \ "${COLOR_WARN_ON}" "${*}" "${COLOR_WARN_OFF}" TEST_ANY=1 TESTS_SKIPPED=`add ${TESTS_SKIPPED} 1` } check() { restat=${?} tid=${1} eestat=${2} f=${3} s=${4} optmode=${5} TESTS_PERFORMED=`add ${TESTS_PERFORMED} 1` case "${optmode}" in '') ;; async) [ "$eestat" = - ] || exit 200 while :; do [ -f "${f}" ] && break t_echowarn "[${tid}:async=wait]" sleep 1 & wait ${!} done ;; *) exit 222;; esac check__bad= check__runx= if [ "${eestat}" != - ] && [ "${restat}" != "${eestat}" ]; then ESTAT=1 t_echoerr "${tid}: bad-status: ${restat} != ${eestat}" check__bad=1 fi csum="`${cksum} < "${f}" | ${sed} -e 's/[ ]\{1,\}/ /g'`" if [ "${csum}" = "${s}" ]; then t_echook "${tid}" else ESTAT=1 t_echoerr "${tid}: checksum mismatch (got ${csum})" check__bad=1 check__runx=1 fi if [ -z "${check__bad}" ]; then TESTS_OK=`add ${TESTS_OK} 1` else TESTS_FAILED=`add ${TESTS_FAILED} 1` fi if [ -n "${CHECK_ONLY}${RUN_TEST}" ]; then x="t.${TEST_NAME}-${tid}" if [ -n "${RUN_TEST}" ] || [ -n "${check__runx}" -a -n "${GIT_REPO}" ]; then ${cp} -f "${f}" ../"${x}" fi if [ -n "${check__runx}" ] && [ -n "${GIT_REPO}" ] && command -v diff >/dev/null 2>&1 && (git rev-parse --verify test-out) >/dev/null 2>&1 && git show test-out:"${x}" > ../"${x}".old 2>/dev/null; then diff -ru ../"${x}".old ../"${x}" > ../"${x}".diff fi fi } check_ex0() { # $1=test name [$2=status] __qm__=${?} [ ${#} -gt 1 ] && __qm__=${2} TESTS_PERFORMED=`add ${TESTS_PERFORMED} 1` if [ ${__qm__} -ne 0 ]; then ESTAT=1 t_echoerr "${1}: unexpected non-0 exit status: ${__qm__}" TESTS_FAILED=`add ${TESTS_FAILED} 1` else t_echook "${1}" TESTS_OK=`add ${TESTS_OK} 1` fi } check_exn0() { # $1=test name [$2=status] __qm__=${?} [ ${#} -gt 1 ] && __qm__=${2} TESTS_PERFORMED=`add ${TESTS_PERFORMED} 1` if [ ${__qm__} -eq 0 ]; then ESTAT=1 t_echoerr "${1}: unexpected 0 exit status: ${__qm__}" TESTS_FAILED=`add ${TESTS_FAILED} 1` else t_echook "${1}" TESTS_OK=`add ${TESTS_OK} 1` fi } # }}} color_init() { if (command -v tput && tput setaf 1 && tput sgr0) >/dev/null 2>&1; then COLOR_ERR_ON=`tput setaf 1``tput bold` COLOR_ERR_OFF=`tput sgr0` COLOR_WARN_ON=`tput setaf 3``tput bold` COLOR_WARN_OFF=`tput sgr0` COLOR_OK_ON=`tput setaf 2` COLOR_OK_OFF=`tput sgr0` fi } if ( [ "$((1 + 1))" = 2 ] ) >/dev/null 2>&1; then add() { echo "$((${1} + ${2}))" } else add() { ${awk} 'BEGIN{print '${1}' + '${2}'}' } fi if ( [ "$((2 % 3))" = 2 ] ) >/dev/null 2>&1; then modulo() { echo "$((${1} % ${2}))" } else modulo() { ${awk} 'BEGIN{print '${1}' % '${2}'}' } fi have_feat() { ( "${RAWMAILX}" ${ARGS} -X'echo $features' -Xx | ${grep} +${1} ) >/dev/null 2>&1 } # }}} # Absolute Basics {{{ t_X_Y_opt_input_go_stack() { t_prolog "${@}" ${cat} <<- '__EOT' > "${BODY}" echo 1 define mac0 { echo mac0-1 via1 $0 } call mac0 echo 2 source '\ echo "define mac1 {";\ echo " echo mac1-1 via1 \$0";\ echo " call mac0";\ echo " echo mac1-2";\ echo " call mac2";\ echo " echo mac1-3";\ echo "}";\ echo "echo 1-1";\ echo "define mac2 {";\ echo " echo mac2-1 via1 \$0";\ echo " call mac0";\ echo " echo mac2-2";\ echo "}";\ echo "echo 1-2";\ echo "call mac1";\ echo "echo 1-3";\ echo "source \"\ echo echo 1-1-1 via1 \$0;\ echo call mac0;\ echo echo 1-1-2;\ | \"";\ echo "echo 1-4";\ | ' echo 3 call mac2 echo 4 undefine * __EOT # The -X option supports multiline arguments, and those can internally use # reverse solidus newline escaping. And all -X options are joined... APO=\' < "${BODY}" ${MAILX} ${ARGS} \ -X 'e\' \ -X ' c\' \ -X ' h\' \ -X ' o \' \ -X 1 \ -X' define mac0 { echo mac0-1 via2 $0 } call mac0 echo 2 ' \ -X' source '${APO}'\ echo "define mac1 {";\ echo " echo mac1-1 via2 \$0";\ echo " call mac0";\ echo " echo mac1-2";\ echo " call mac2";\ echo " echo mac1-3";\ echo "}";\ echo "echo 1-1";\ echo "define mac2 {";\ echo " echo mac2-1 via2 \$0";\ echo " call mac0";\ echo " echo mac2-2";\ echo "}";\ echo "echo 1-2";\ echo "call mac1";\ echo "echo 1-3";\ echo "source \"\ echo echo 1-1-1 via2 \$0;\ echo call mac0;\ echo echo 1-1-2;\ | \"";\ echo "echo 1-4";\ | '${APO}' echo 3 ' \ -X' call mac2 echo 4 undefine * ' > "${MBOX}" check 1 0 "${MBOX}" '1786542668 416' # The -Y option supports multiline arguments, and those can internally use # reverse solidus newline escaping. APO=\' < "${BODY}" ${MAILX} ${ARGS} \ -X 'echo FIRST_X' \ -X 'echo SECOND_X' \ -Y 'e\' \ -Y ' c\' \ -Y ' h\' \ -Y ' o \' \ -Y 1 \ -Y' define mac0 { echo mac0-1 via2 $0 } call mac0 echo 2 ' \ -Y' source '${APO}'\ echo "define mac1 {";\ echo " echo mac1-1 via2 \$0";\ echo " call mac0";\ echo " echo mac1-2";\ echo " call mac2";\ echo " echo mac1-3";\ echo "}";\ echo "echo 1-1";\ echo "define mac2 {";\ echo " echo mac2-1 via2 \$0";\ echo " call mac0";\ echo " echo mac2-2";\ echo "}";\ echo "echo 1-2";\ echo "call mac1";\ echo "echo 1-3";\ echo "source \"\ echo echo 1-1-1 via2 \$0;\ echo call mac0;\ echo echo 1-1-2;\ | \"";\ echo "echo 1-4";\ | '${APO}' echo 3 ' \ -Y' call mac2 echo 4 undefine * ' \ -Y 'echo LAST_Y' > "${MBOX}" check 2 0 "${MBOX}" '1845176711 440' # Compose mode, too! "${MBOX}" 2>&1 check 3 0 ./.tybox '264636255 125' check 4 - "${MBOX}" '467429373 22' ${cat} <<-_EOT | ${MAILX} ${ARGS} -t \ -X 'echo X before compose mode' \ -Y '~s Subject via -Y' \ -Y 'Additional body via -Y' -. ./.tybox > "${MBOX}" 2>&1 from: heya@exam.ple subject:diet not to be seen! this body via -t. _EOT check 5 0 ./.tybox '3313167452 299' check 6 - "${MBOX}" '467429373 22' # printf 'this body via stdin pipe.\n' | ${MAILX} ${NOBATCH_ARGS} \ -X 'echo X before compose mode' \ -Y '~s Subject via -Y (not!)' \ -Y 'Additional body via -Y, nobatch mode' -. ./.tybox > "${MBOX}" 2>&1 check 7 0 ./.tybox '1561798488 476' check 8 - "${MBOX}" '467429373 22' printf 'this body via stdin pipe.\n' | ${MAILX} ${ARGS} \ -X 'echo X before compose mode' \ -Y '~s Subject via -Y' \ -Y 'Additional body via -Y, batch mode' -. ./.tybox > "${MBOX}" 2>&1 check 9 0 ./.tybox '3245082485 650' check 10 - "${MBOX}" '467429373 22' # Test for [8412796a] (n_cmd_arg_parse(): FIX token error -> crash, e.g. # "-RX 'bind;echo $?' -Xx".., 2018-08-02) ${MAILX} ${ARGS} -RX'call;echo $?' -Xx > ./.tall 2>&1 ${MAILX} ${ARGS} -RX'call ;echo $?' -Xx >> ./.tall 2>&1 ${MAILX} ${ARGS} -RX'call ;echo $?' -Xx >> ./.tall 2>&1 ${MAILX} ${ARGS} -RX'call ;echo $?' -Xx >> ./.tall 2>&1 check cmdline 0 ./.tall '1867586969 8' t_epilog "${@}" } t_X_errexit() { t_prolog "${@}" if have_feat uistrings; then :; else t_echoskip '[test unsupported]' t_epilog "${@}" return fi ${cat} <<- '__EOT' > "${BODY}" echo one echos nono echo two __EOT "${MBOX}" 2>&1 check 1 0 "${MBOX}" '916157812 53' "${MBOX}" 2>&1 check 2 0 "${MBOX}" '916157812 53' "${MBOX}" 2>&1 check 3 0 "${MBOX}" '916157812 53' ## "${MBOX}" 2>&1 check 4 1 "${MBOX}" '2118430867 49' "${MBOX}" 2>&1 check 5 1 "${MBOX}" '2118430867 49' "${MBOX}" 2>&1 check 6 1 "${MBOX}" '12955965 172' "${MBOX}" 2>&1 check 7 1 "${MBOX}" '12955965 172' ## Repeat 4-7 with ignerr set ${sed} -e 's/^echos /ignerr echos /' < "${BODY}" > "${MBOX}" "${BODY}" 2>&1 check 8 0 "${BODY}" '916157812 53' "${BODY}" 2>&1 check 9 0 "${BODY}" '916157812 53' "${BODY}" 2>&1 check 10 0 "${BODY}" '916157812 53' "${BODY}" 2>&1 check 11 0 "${BODY}" '916157812 53' t_epilog "${@}" } t_Y_errexit() { t_prolog "${@}" if have_feat uistrings; then :; else t_echoskip '[test unsupported]' t_epilog "${@}" return fi ${cat} <<- '__EOT' > "${BODY}" echo one echos nono echo two __EOT "${MBOX}" 2>&1 check 1 0 "${MBOX}" '916157812 53' "${MBOX}" 2>&1 check 2 0 "${MBOX}" '916157812 53' ## "${MBOX}" 2>&1 check 3 1 "${MBOX}" '2118430867 49' "${MBOX}" 2>&1 check 4 1 "${MBOX}" '2118430867 49' ## Repeat 3-4 with ignerr set ${sed} -e 's/^echos /ignerr echos /' < "${BODY}" > "${MBOX}" "${BODY}" 2>&1 check 5 0 "${BODY}" '916157812 53' "${BODY}" 2>&1 check 6 0 "${BODY}" '916157812 53' t_epilog "${@}" } t_S_freeze() { t_prolog "${@}" oterm=$TERM unset TERM # Test basic assumption dietcurd<$dietcurd>' \ -Xx > "${MBOX}" 2>&1 check 1 0 "${MBOX}" '270686329 21' # ${cat} <<- '__EOT' > "${BODY}" echo asksub<$asksub> set asksub echo asksub<$asksub> __EOT ' -X'set asksub' -X'echo asksub<$asksub>' \ -Xx > "${MBOX}" 2>&1 check 2 0 "${MBOX}" '3182942628 37' ${cat} <<- '__EOT' > "${BODY}" echo asksub<$asksub> unset asksub echo asksub<$asksub> __EOT ' -X'unset asksub' -X'echo asksub<$asksub>' \ -Xx > "${MBOX}" 2>&1 check 3 0 "${MBOX}" '2006554293 39' # ${cat} <<- '__EOT' > "${BODY}" echo dietcurd<$dietcurd> set dietcurd=cherry echo dietcurd<$dietcurd> __EOT ' -X'unset dietcurd' \ -X'echo dietcurd<$dietcurd>' \ -Xx > "${MBOX}" 2>&1 check 4 0 "${MBOX}" '1985768109 65' ${cat} <<- '__EOT' > "${BODY}" echo dietcurd<$dietcurd> unset dietcurd echo dietcurd<$dietcurd> __EOT ' -X'set dietcurd=vanilla' \ -X'echo dietcurd<$dietcurd>' \ -Xx > "${MBOX}" 2>&1 check 5 0 "${MBOX}" '151574279 51' # TODO once we have a detached one with env=1.. if [ -n "` "${BODY}" !echo "shell says TERM<$TERM>" echo TERM<$TERM> !echo "shell says TERM<$TERM>" set TERM=cherry !echo "shell says TERM<$TERM>" echo TERM<$TERM> !echo "shell says TERM<$TERM>" __EOT ' -X'unset TERM' \ -X'!echo "shell says TERM<$TERM>"' -X'echo TERM<$TERM>' \ -Xx > "${MBOX}" 2>&1 check 6 0 "${MBOX}" '1211476036 167' ${cat} <<- '__EOT' > "${BODY}" !echo "shell says TERM<$TERM>" echo TERM<$TERM> !echo "shell says TERM<$TERM>" set TERM=cherry !echo "shell says TERM<$TERM>" echo TERM<$TERM> !echo "shell says TERM<$TERM>" __EOT ' -X'set TERM=vanilla' \ -X'!echo "shell says TERM<$TERM>"' -X'echo TERM<$TERM>' \ -Xx > "${MBOX}" 2>&1 check 7 0 "${MBOX}" '3365080441 132' fi TERM=$oterm t_epilog "${@}" } t_input_inject_semicolon_seq() { t_prolog "${@}" ${cat} <<- '__EOT' | ${MAILX} ${ARGS} > "${MBOX}" define mydeepmac { echon '(mydeepmac)'; } define mymac { echon this_is_mymac;call mydeepmac;echon ';'; } echon one';';call mymac;echon two";";call mymac;echo three$';'; define mymac { echon this_is_mymac;call mydeepmac;echon ,TOO'!;'; } echon one';';call mymac;echon two";";call mymac;echo three$';'; __EOT check 1 0 "${MBOX}" '512117110 140' t_epilog "${@}" } t_wysh() { t_prolog "${@}" ${cat} <<- '__EOT' > "${BODY}" # echo abcd echo a'b'c'd' echo a"b"c"d" echo a$'b'c$'d' echo 'abcd' echo "abcd" echo $'abcd' echo a\ b\ c\ d echo a 'b c' d echo a "b c" d echo a $'b c' d # echo 'a$`"\' echo "a\$\`'\"\\" echo $'a\$`\'\"\\' echo $'a\$`\'"\\' # DIET=CURD TIED= echo 'a${DIET}b${TIED}c\${DIET}d\${TIED}e' # COMMENT echo "a${DIET}b${TIED}c\${DIET}d\${TIED}e" echo $'a${DIET}b${TIED}c\${DIET}d\${TIED}e' # echo a$'\101\0101\x41\u0041\u41\U00000041\U41'c echo a$'\u0041\u41\u0C1\U00000041\U41'c echo a$'\377'c echo a$'\0377'c echo a$'\400'c echo a$'\0400'c echo a$'\U1100001'c # echo a$'b\0c'd echo a$'b\00c'de echo a$'b\000c'df echo a$'b\0000c'dg echo a$'b\x0c'dh echo a$'b\x00c'di echo a$'b\u0'dj echo a$'b\u00'dk echo a$'b\u000'dl echo a$'b\u0000'dm echo a$'b\U0'dn echo a$'b\U00'do echo a$'b\U000'dp echo a$'b\U0000'dq echo a$'b\U00000'dr echo a$'b\U000000'ds echo a$'b\U0000000'dt echo a$'b\U00000000'du # echo a$'\cI'b echo a$'\011'b echo a$'\x9'b echo a$'\u9'b echo a$'\U9'b echo a$'\c@'b c d __EOT if [ -z "${UTF8_LOCALE}" ]; then t_echoskip 'wysh-unicode:[no UTF-8 locale]' else < "${BODY}" DIET=CURD TIED= \ LC_ALL=${UTF8_LOCALE} ${MAILX} ${ARGS} > "${MBOX}" 2>>${ERR} check unicode 0 "${MBOX}" '475805847 317' fi < "${BODY}" DIET=CURD TIED= ${MAILX} ${ARGS} > "${MBOX}" 2>>${ERR} check c 0 "${MBOX}" '1473887148 321' ${cat} <<- '__EOT' | ${MAILX} ${ARGS} > "${MBOX}" wysh set mager='\hey\' varshow mager wysh set mager="\hey\\" varshow mager wysh set mager=$'\hey\\' varshow mager __EOT check 3 0 "${MBOX}" '1289698238 69' t_epilog "${@}" } t_commandalias() { t_prolog "${@}" ${cat} <<- '__EOT' | ${MAILX} ${ARGS} > "${MBOX}" commandalias echo echo hoho echo stop. commandalias X Xx commandalias Xx XxX commandalias XxX XxXx commandalias XxXx XxXxX commandalias XxXxX XxXxXx commandalias XxXxXx echo huhu commandalias XxXxXxX echo huhu X commandalias XxXxXx XxXxXxX X uncommandalias echo commandalias XxXxXx echo huhu X __EOT check 1 0 "${MBOX}" '1638809585 36' t_epilog "${@}" } # }}} # Basics {{{ t_shcodec() { t_prolog "${@}" # XXX the first needs to be checked, it is quite dumb as such ${cat} <<- '__EOT' | ${MAILX} ${ARGS} > "${MBOX}" 2>&1 commandalias x echo '$?/$^ERRNAME' shcodec e abcd x shcodec d abcd x shcodec e a'b'c'd' x shcodec d a'b'c'd' x shcodec e a"b"c"d" x shcodec d a"b"c"d" x shcodec e a$'b'c$'d' x shcodec d a$'b'c$'d' x shcodec e 'abcd' x shcodec d 'abcd' x shcodec e "abcd" x shcodec d "abcd" x shcodec e $'abcd' x shcodec d $'abcd' x # same but with vput commandalias y echo '$?/$^ERRNAME $res' vput shcodec res e abcd y eval shcodec d $res x vput shcodec res d abcd y eval shcodec d $res x vput shcodec res e a'b'c'd' y eval shcodec d $res x vput shcodec res d a'b'c'd' y eval shcodec d $res x vput shcodec res e a"b"c"d" y eval shcodec d $res x vput shcodec res d a"b"c"d" y eval shcodec d $res x vput shcodec res e a$'b'c$'d' y eval shcodec d $res x vput shcodec res d a$'b'c$'d' y eval shcodec d $res x vput shcodec res e 'abcd' y eval shcodec d $res x vput shcodec res d 'abcd' y eval shcodec d $res x vput shcodec res e "abcd" y eval shcodec d $res x vput shcodec res d "abcd" y eval shcodec d $res x vput shcodec res e $'abcd' y eval shcodec d $res x vput shcodec res d $'abcd' y eval shcodec d $res x # vput shcodec res e a b\ c d y eval shcodec d $res x vput shcodec res d a b\ c d y vput shcodec res e ab cd y eval shcodec d $res x vput shcodec res d 'ab cd' y vput shcodec res e a 'b c' d y eval shcodec d $res x vput shcodec res d a 'b c' d y vput shcodec res e a "b c" d y eval shcodec d $res x vput shcodec res d a "b c" d y vput shcodec res e a $'b c' d y eval shcodec d $res x vput shcodec res d a $'b c' d y # vput shcodec res e 'a$`"\' y eval shcodec d $res x vput shcodec res d 'a$`"\' y vput shcodec res e "a\$\`'\"\\" y eval shcodec d $res x vput shcodec res d "a\$\`'\"\\" y vput shcodec res e $'a\$`\'\"\\' y eval shcodec d $res x vput shcodec res d $'a\$`\'\"\\' y vput shcodec res e $'a\$`\'"\\' y eval shcodec d $res x vput shcodec res d $'a\$`\'"\\' y # set diet=curd vput shcodec res e a${diet}c y eval shcodec d $res x eval vput shcodec res e a${diet}c y eval shcodec d $res x vput shcodec res e "a${diet}c" y eval shcodec d $res x eval vput shcodec res e "a${diet}c" y eval shcodec d $res x __EOT check 1 0 "${MBOX}" '3316745312 1241' if [ -z "${UTF8_LOCALE}" ]; then t_echoskip 'unicode:[no UTF-8 locale]' else ${cat} <<- '__EOT' | LC_ALL=${UTF8_LOCALE} \ ${MAILX} ${ARGS} > "${MBOX}" 2>>${ERR} # shcodec e täst shcodec +e täst shcodec d $'t\u00E4st' shcodec e aՍc shcodec +e aՍc shcodec d $'a\u054Dc' shcodec e a𝕂c shcodec +e a𝕂c shcodec d $'a\U0001D542c' __EOT check unicode 0 "${MBOX}" '1175985867 77' fi t_epilog "${@}" } t_ifelse() { t_prolog "${@}" # v15compat: old and new tests share the same result files! # v15compat: i.e., just throw away -old tests one day # Nestable conditions test ${cat} <<- '__EOT' | ${MAILX} ${ARGS} > "${MBOX}" if 0 echo 1.err else echo 1.ok endif if 1 echo 2.ok else echo 2.err endif if $dietcurd echo 3.err else echo 3.ok endif set dietcurd=yoho if $dietcurd echo 4.ok else echo 4.err endif if $dietcurd == 'yoho' echo 5.ok else echo 5.err endif if $dietcurd ==? 'Yoho' echo 5-1.ok else echo 5-1.err endif if $dietcurd == 'Yoho' echo 5-2.err else echo 5-2.ok endif if $dietcurd != 'yoho' echo 6.err else echo 6.ok endif if $dietcurd !=?case 'Yoho' echo 6-1.err else echo 6-1.ok endif if $dietcurd != 'Yoho' echo 6-2.ok else echo 6-2.err endif # Nesting if faLse echo 7.err1 if tRue echo 7.err2 if yEs echo 7.err3 else echo 7.err4 endif echo 7.err5 endif echo 7.err6 else echo 7.ok7 if YeS echo 7.ok8 if No echo 7.err9 else echo 7.ok9 endif echo 7.ok10 else echo 7.err11 if yeS echo 7.err12 else echo 7.err13 endif endif echo 7.ok14 endif if r echo 8.ok1 if R echo 8.ok2 else echo 8.err2 endif echo 8.ok3 else echo 8.err1 endif if s echo 9.err1 else echo 9.ok1 if S echo 9.err2 else echo 9.ok2 endif echo 9.ok3 endif # `elif' if $dietcurd == 'yohu' echo 10.err1 elif $dietcurd == 'yoha' echo 10.err2 elif $dietcurd == 'yohe' echo 10.err3 elif $dietcurd == 'yoho' echo 10.ok1 if $dietcurd == 'yohu' echo 10.err4 elif $dietcurd == 'yoha' echo 10.err5 elif $dietcurd == 'yohe' echo 10.err6 elif $dietcurd == 'yoho' echo 10.ok2 if $dietcurd == 'yohu' echo 10.err7 elif $dietcurd == 'yoha' echo 10.err8 elif $dietcurd == 'yohe' echo 10.err9 elif $dietcurd == 'yoho' echo 10.ok3 else echo 10.err10 endif else echo 10.err11 endif else echo 10.err12 endif # integer set dietcurd=10 if $dietcurd -lt 11 echo 11.ok1 if $dietcurd -gt 9 echo 11.ok2 else echo 11.err2 endif if $dietcurd -eq 10 echo 11.ok3 else echo 11.err3 endif if $dietcurd -ge 10 echo 11.ok4 else echo 11.err4 endif if $dietcurd -le 10 echo 11.ok5 else echo 11.err5 endif if $dietcurd -ge 11 echo 11.err6 else echo 11.ok6 endif if $dietcurd -le 9 echo 11.err7 else echo 11.ok7 endif else echo 11.err1 endif set dietcurd=Abc if $dietcurd < aBd echo 12.ok1 if $dietcurd >? abB echo 12.ok2 else echo 12.err2 endif if $dietcurd ==?case aBC echo 12.ok3 else echo 12.err3 endif if $dietcurd >=?ca AbC echo 12.ok4 else echo 12.err4 endif if $dietcurd <=? ABc echo 12.ok5 else echo 12.err5 endif if $dietcurd >=?case abd echo 12.err6 else echo 12.ok6 endif if $dietcurd <=? abb echo 12.err7 else echo 12.ok7 endif else echo 12.err1 endif if $dietcurd < aBc echo 12-1.ok else echo 12-1.err endif if $dietcurd ABc echo 12-3.ok else echo 12-3.err endif if $dietcurd >? ABc echo 12-3.err else echo 12-3.ok endif if $dietcurd =%?case aB echo 13.ok else echo 13.err endif if $dietcurd =% aB echo 13-1.err else echo 13-1.ok endif if $dietcurd =%? bC echo 14.ok else echo 14.err endif if $dietcurd !% aB echo 15-1.ok else echo 15-1.err endif if $dietcurd !%? aB echo 15-2.err else echo 15-2.ok endif if $dietcurd !% bC echo 15-3.ok else echo 15-3.err endif if $dietcurd !%? bC echo 15-4.err else echo 15-4.ok endif if $dietcurd =% Cd echo 16.err else echo 16.ok endif if $dietcurd !% Cd echo 17.ok else echo 17.err endif set diet=abc curd=abc if $diet == $curd echo 18.ok else echo 18.err endif set diet=abc curd=abcd if $diet != $curd echo 19.ok else echo 19.err endif # 1. Shitty grouping capabilities as of today unset diet curd ndefined if [ [ false ] || [ false ] || [ true ] ] && \ [ [ false ] || [ true ] ] && \ [ yes ] echo 20.ok else echo 20.err endif if [ [ [ [ 0 ] || [ 1 ] ] && [ [ 1 ] || [ 0 ] ] ] && [ 1 ] ] && [ yes ] echo 21.ok else echo 21.err endif if [ [ 1 ] || [ 0 ] || [ 0 ] || [ 0 ] ] echo 22.ok else echo 22.err endif if [ [ 1 ] || [ 0 ] || [ 0 ] || [ 0 ] || [ [ 1 ] ] ] echo 23.ok else echo 23.err endif if [ [ 1 ] || [ 0 ] || [ 0 ] || [ 0 ] || [ [ 1 ] ] || [ 1 ] ] && [ no ] echo 24.err else echo 24.ok endif if [ [ 1 ] || [ 0 ] || [ 0 ] || [ 0 ] || [ [ 1 ] ] || [ 1 ] ] \ && [ no ] || [ yes ] echo 25.ok else echo 25.err endif if [ [ [ [ [ [ [ 1 ] ] && [ 1 ] ] && [ 1 ] ] && [ 1 ] ] ] && [ 1 ] ] echo 26.ok else echo 26.err endif if [ [ [ [ [ [ [ 1 ] ] && [ 1 ] ] && [ 1 ] ] && [ 1 ] ] ] && [ 0 ] ] echo 27.err else echo 27.ok endif if [ [ [ [ [ [ [ 1 ] ] && [ 1 ] ] && [ 0 ] ] && [ 1 ] ] ] && [ 1 ] ] echo 28.err else echo 28.ok endif if [ [ [ [ [ [ [ 0 ] ] && [ 1 ] ] && [ 1 ] ] && [ 1 ] ] ] && [ 1 ] ] echo 29.err else echo 29.ok endif if [ 1 ] || [ 0 ] || [ 0 ] || [ 0 ] && [ 0 ] echo 30.err else echo 30.ok endif if [ 1 ] || [ 0 ] || [ 0 ] || [ 0 ] && [ 1 ] echo 31.ok else echo 31.err endif if [ 0 ] || [ 0 ] || [ 0 ] || [ 1 ] && [ 0 ] echo 32.err else echo 32.ok endif if [ 0 ] || [ 0 ] || [ 0 ] || [ 1 ] && [ 1 ] echo 33.ok else echo 33.err endif if [ 0 ] || [ 0 ] || [ 0 ] || [ 1 ] && [ 0 ] || [ 1 ] && [ 0 ] echo 34.err else echo 34.ok endif if [ 0 ] || [ 0 ] || [ 0 ] || [ 1 ] && [ 0 ] || [ 1 ] && [ 1 ] echo 35.ok else echo 35.err endif set diet=yo curd=ho if [ [ $diet == 'yo' ] && [ $curd == 'ho' ] ] && [ $ndefined ] echo 36.err else echo 36.ok endif set ndefined if [ [ $diet == 'yo' ] && [ $curd == 'ho' ] ] && [ $ndefined ] echo 37.ok else echo 37.err endif # 2. Shitty grouping capabilities as of today unset diet curd ndefined if [ false || false || true ] && [ false || true ] && yes echo 40.ok else echo 40.err endif if [ [ [ 0 || 1 ] && [ 1 || 0 ] ] && 1 ] && [ yes ] echo 41.ok else echo 41.err endif if [ 1 || 0 || 0 || 0 ] echo 42.ok else echo 42.err endif if [ 1 || 0 || 0 || 0 || [ 1 ] ] echo 43.ok else echo 43.err endif if [ 1 || 0 || 0 || 0 || [ 1 ] || 1 ] && no echo 44.err else echo 44.ok endif if [ 1 || 0 || 0 || 0 || 1 || [ 1 ] ] && no || [ yes ] echo 45.ok else echo 45.err endif if [ [ [ [ [ [ 1 ] && 1 ] && 1 ] && 1 ] ] && [ 1 ] ] echo 46.ok else echo 46.err endif if [ [ [ [ [ [ 1 ] && 1 ] && 1 ] && [ 1 ] ] ] && 0 ] echo 47.err else echo 47.ok endif if [ [ [ [ [ [ [ 1 ] ] && 1 ] && 0 ] && [ 1 ] ] ] && 1 ] echo 48.err else echo 48.ok endif if [ [ [ [ [ [ 0 ] && 1 ] && 1 ] && 1 ] ] && 1 ] echo 49.err else echo 49.ok endif if 1 || 0 || 0 || 0 && 0 echo 50.err else echo 50.ok endif if 1 || 0 || 0 || 0 && 1 echo 51.ok else echo 51.err endif if 0 || 0 || 0 || 1 && 0 echo 52.err else echo 52.ok endif if 0 || 0 || 0 || 1 && 1 echo 53.ok else echo 53.err endif if 0 || 0 || 0 || 1 && 0 || 1 && 0 echo 54.err else echo 54.ok endif if 0 || 0 || 0 || 1 && 0 || 1 && 1 echo 55.ok else echo 55.err endif set diet=yo curd=ho if [ $diet == 'yo' && $curd == 'ho' ] && $ndefined echo 56.err else echo 56.ok endif if $diet == 'yo' && $curd == 'ho' && $ndefined echo 57.err else echo 57.ok endif set ndefined if [ $diet == 'yo' && $curd == 'ho' ] && $ndefined echo 57.ok else echo 57.err endif if $diet == 'yo' && $curd == 'ho' && $ndefined echo 58.ok else echo 58.err endif if [ [ [ [ [ [ $diet == 'yo' && $curd == 'ho' && $ndefined ] ] ] ] ] ] echo 59.ok else echo 59.err endif # Some more en-braced variables set diet=yo curd=ho if ${diet} == ${curd} echo 70.err else echo 70.ok endif if ${diet} != ${curd} echo 71.ok else echo 71.err endif if $diet == ${curd} echo 72.err else echo 72.ok endif if ${diet} == $curd echo 73.err else echo 73.ok endif # Unary ! if ! 0 && ! ! 1 && ! ! ! ! 2 && 3 echo 80.ok else echo 80.err endif if ! 0 && ! [ ! 1 ] && ! [ ! [ ! [ ! 2 ] ] ] && 3 echo 81.ok else echo 81.err endif if [ ! 0 ] && [ ! [ ! 1 ] ] && [ ! [ ! [ ! [ ! [ 2 ] ] ] ] ] && 3 echo 82.ok else echo 82.err endif if [ ! 0 ] && [ ! [ ! 1 ] ] && [ ! [ ! [ ! [ ! [ 2 ] ] ] ] ] && ! 3 echo 83.err else echo 83.ok endif if [ ! 0 ] && [ ! [ ! 1 ] ] && ! [ [ ! [ ! [ ! [ 2 ] ] ] ] ] && ! 3 echo 84.err else echo 84.ok endif if [ ! 0 ] && ! [ ! [ ! 1 ] ] && [ ! [ ! [ ! [ ! [ 2 ] ] ] ] ] && 3 echo 85.err else echo 85.ok endif if ! [ ! 0 ] && [ ! [ ! 1 ] ] && [ ! [ ! [ ! [ ! [ 2 ] ] ] ] ] && 3 echo 86.err else echo 86.ok endif if [ ! 0 ] && [ ! [ ! 1 ] ] && [ ! [ ! [ ! [ ! [ 2 ] ] ] ] ] || 3 echo 87.ok else echo 87.err endif if [ ! 0 ] && [ ! ! [ ! ! 1 ] ] && [ ! ! [ ! ! [ ! ! [ ! ! [ 2 ] ] ] ] ] echo 88.ok else echo 88.err endif # Unary !, odd if ! 0 && ! ! 1 && ! ! ! 0 && 3 echo 90.ok else echo 90.err endif if ! 0 && ! [ ! 1 ] && ! [ ! [ ! [ 0 ] ] ] && 3 echo 91.ok else echo 91.err endif if [ ! 0 ] && [ ! [ ! 1 ] ] && [ ! [ ! [ ! [ [ 0 ] ] ] ] ] && 3 echo 92.ok else echo 92.err endif if [ ! 0 ] && [ ! [ ! 1 ] ] && [ ! [ ! ! [ ! [ ! 0 ] ] ] ] && ! 3 echo 93.err else echo 93.ok endif if [ ! 0 ] && [ ! [ ! 1 ] ] && ! [ ! [ ! [ ! [ ! 0 ] ] ] ] && 3 echo 94.ok else echo 94.err endif if [ ! 0 ] && ! [ ! [ ! 1 ] ] && [ ! ! [ ! [ ! [ ! [ 0 ] ] ] ] ] && 3 echo 95.err else echo 95.ok endif if ! [ ! 0 ] && [ ! [ ! 1 ] ] && [ ! [ ! [ ! [ ! ! 0 ] ] ] ] && 3 echo 96.err else echo 96.ok endif if [ ! 0 ] && [ ! [ ! 1 ] ] && [ ! [ ! [ ! [ ! [ ! 0 ] ] ] ] ] || 3 echo 97.ok else echo 97.err endif if [ ! 0 ] && [ ! ! [ ! ! 1 ] ] && [ ! ! [ ! ! [ ! ! [ ! [ 0 ] ] ] ] ] echo 98.ok else echo 98.err endif __EOT check normal-old 0 "${MBOX}" '1688759742 719' # pre v15compat if have_feat regex; then ${cat} <<- '__EOT' | ${MAILX} ${ARGS} > "${MBOX}" set dietcurd=yoho if $dietcurd =~ '^yo.*' echo 1.ok else echo 1.err endif if $dietcurd =~ '^Yo.*' echo 1-1.err else echo 1-1.ok endif if $dietcurd =~?case '^Yo.*' echo 1-2.ok else echo 1-2.err endif if $dietcurd =~ '^yOho.+' echo 2.err else echo 2.ok endif if $dietcurd !~? '.*Ho$' echo 3.err else echo 3.ok endif if $dietcurd !~ '.+yohO$' echo 4.ok else echo 4.err endif if [ $dietcurd !~?cas '.+yoho$' ] echo 5.ok else echo 5.err endif if ! [ $dietcurd =~?case '.+yoho$' ] echo 6.ok else echo 6.err endif if ! ! [ $dietcurd !~? '.+yoho$' ] echo 7.ok else echo 7.err endif if ! [ ! [ $dietcurd !~? '.+yoho$' ] ] echo 8.ok else echo 8.err endif if [ ! [ ! [ $dietcurd !~? '.+yoho$' ] ] ] echo 9.ok else echo 9.err endif if ! [ ! [ ! [ $dietcurd !~ '.+yoho$' ] ] ] echo 10.err else echo 10.ok endif if ! ! ! $dietcurd !~ '.+yoho$' echo 11.err else echo 11.ok endif if ! ! ! $dietcurd =~ '.+yoho$' echo 12.ok else echo 12.err endif if ! [ ! ! [ ! [ $dietcurd !~ '.+yoho$' ] ] ] echo 13.ok else echo 13.err endif set diet=abc curd='^abc$' if $diet =~ $curd echo 14.ok else echo 14.err endif set diet=abc curd='^abcd$' if $diet !~ $curd echo 15.ok else echo 15.err endif __EOT check regex-old 0 "${MBOX}" '1115671789 95' # pre v15compat else t_echoskip 'regex-old:[no regex option]' fi ## post v15compat ${cat} <<- '__EOT' | ${MAILX} ${ARGS} -Sv15-compat=X > "${MBOX}" \if -N xyz; echo 1.err-1; \ \elif ! -Z xyz;echo 1.err-2;\ \elif -n "$xyz" ; echo 1.err-3 ; \ \elif ! -z "$xyz" ; echo 1.err-4 ; \ \else;echo 1.ok;\ \end \set xyz \if ! -N xyz; echo 2.err-1; \ \elif -Z xyz;echo 2.err-2;\ \elif -n "$xyz" ; echo 2.err-3 ; \ \elif ! -z "$xyz" ; echo 2.err-4 ; \ \else;echo 2.ok;\ \end \set xyz=notempty \if ! -N xyz; echo 3.err-1; \ \elif -Z xyz;echo 3.err-2;\ \elif ! -n "$xyz";echo 3.err-3;\ \elif -z "$xyz";echo 3.err-4;\ \else;echo 3.ok;\ \end \if $xyz != notempty;echo 4.err-1;else;echo 4.ok;\end \if $xyz == notempty;echo 5.ok;else;echo 5.err-1;\end __EOT check NnZz_whiteout 0 "${MBOX}" '4280687462 25' # TODO t_ifelse: individual tests as for NnZz_whiteout # Nestable conditions test ${cat} <<- '__EOT' | ${MAILX} ${ARGS} -Sv15-compat=x > "${MBOX}" if 0 echo 1.err else echo 1.ok endif if 1 echo 2.ok else echo 2.err endif if [ "$dietcurd" != "" ] echo 3.err else echo 3.ok endif set dietcurd=yoho if $'\$dietcurd' != "" echo 4.ok else echo 4.err endif if "$dietcurd" == 'yoho' echo 5.ok else echo 5.err endif if $'\$dietcurd' ==? 'Yoho' echo 5-1.ok else echo 5-1.err endif if $dietcurd == 'Yoho' echo 5-2.err else echo 5-2.ok endif if $dietcurd != 'yoho' echo 6.err else echo 6.ok endif if $dietcurd !=?case 'Yoho' echo 6-1.err else echo 6-1.ok endif if $dietcurd != 'Yoho' echo 6-2.ok else echo 6-2.err endif # Nesting if faLse echo 7.err1 if tRue echo 7.err2 if yEs echo 7.err3 else echo 7.err4 endif echo 7.err5 endif echo 7.err6 else echo 7.ok7 if YeS echo 7.ok8 if No echo 7.err9 else echo 7.ok9 endif echo 7.ok10 else echo 7.err11 if yeS echo 7.err12 else echo 7.err13 endif endif echo 7.ok14 endif if r echo 8.ok1 if R echo 8.ok2 else echo 8.err2 endif echo 8.ok3 else echo 8.err1 endif if s echo 9.err1 else echo 9.ok1 if S echo 9.err2 else echo 9.ok2 endif echo 9.ok3 endif # `elif' if $dietcurd == 'yohu' echo 10.err1 elif $dietcurd == 'yoha' echo 10.err2 elif $dietcurd == 'yohe' echo 10.err3 elif $dietcurd == 'yoho' echo 10.ok1 if $dietcurd == 'yohu' echo 10.err4 elif $dietcurd == 'yoha' echo 10.err5 elif $dietcurd == 'yohe' echo 10.err6 elif $dietcurd == 'yoho' echo 10.ok2 if $dietcurd == 'yohu' echo 10.err7 elif $dietcurd == 'yoha' echo 10.err8 elif $dietcurd == 'yohe' echo 10.err9 elif $dietcurd == 'yoho' echo 10.ok3 else echo 10.err10 endif else echo 10.err11 endif else echo 10.err12 endif # integer set dietcurd=10 if $dietcurd -lt 11 echo 11.ok1 if $dietcurd -gt 9 echo 11.ok2 else echo 11.err2 endif if $dietcurd -eq 10 echo 11.ok3 else echo 11.err3 endif if $dietcurd -ge 10 echo 11.ok4 else echo 11.err4 endif if $dietcurd -le 10 echo 11.ok5 else echo 11.err5 endif if $dietcurd -ge 11 echo 11.err6 else echo 11.ok6 endif if $dietcurd -ge?satu -0xFFFFFFFFFFFFFFFF1 echo 11.err7 else echo 11.ok7 endif else echo 11.err1 endif set dietcurd=Abc if $dietcurd < aBd echo 12.ok1 if $dietcurd >? abB echo 12.ok2 else echo 12.err2 endif if $dietcurd ==?case aBC echo 12.ok3 else echo 12.err3 endif if $dietcurd >=?ca AbC echo 12.ok4 else echo 12.err4 endif if $dietcurd <=? ABc echo 12.ok5 else echo 12.err5 endif if $dietcurd >=?case abd echo 12.err6 else echo 12.ok6 endif if $dietcurd <=? abb echo 12.err7 else echo 12.ok7 endif else echo 12.err1 endif if $dietcurd < aBc echo 12-1.ok else echo 12-1.err endif if $dietcurd ABc echo 12-3.ok else echo 12-3.err endif if $dietcurd >? ABc echo 12-3.err else echo 12-3.ok endif if $dietcurd =%?case aB echo 13.ok else echo 13.err endif if $dietcurd =% aB echo 13-1.err else echo 13-1.ok endif if $dietcurd =%? bC echo 14.ok else echo 14.err endif if $dietcurd !% aB echo 15-1.ok else echo 15-1.err endif if $dietcurd !%? aB echo 15-2.err else echo 15-2.ok endif if $dietcurd !% bC echo 15-3.ok else echo 15-3.err endif if $dietcurd !%? bC echo 15-4.err else echo 15-4.ok endif if $dietcurd =% Cd echo 16.err else echo 16.ok endif if $dietcurd !% Cd echo 17.ok else echo 17.err endif set diet='ab c' curd='ab c' if "$diet" == "$curd" echo 18.ok else echo 18.err endif set diet='ab c' curd='ab cd' if "$diet" != "$curd" echo 19.ok else echo 19.err endif # 1. Shitty grouping capabilities as of today unset diet curd ndefined if [ [ false ] || [ false ] || [ true ] ] && \ [ [ false ] || [ true ] ] && \ [ yes ] echo 20.ok else echo 20.err endif if [ [ [ [ 0 ] || [ 1 ] ] && [ [ 1 ] || [ 0 ] ] ] && [ 1 ] ] && [ yes ] echo 21.ok else echo 21.err endif if [ [ 1 ] || [ 0 ] || [ 0 ] || [ 0 ] ] echo 22.ok else echo 22.err endif if [ [ 1 ] || [ 0 ] || [ 0 ] || [ 0 ] || [ [ 1 ] ] ] echo 23.ok else echo 23.err endif if [ [ 1 ] || [ 0 ] || [ 0 ] || [ 0 ] || [ [ 1 ] ] || [ 1 ] ] && [ no ] echo 24.err else echo 24.ok endif if [ [ 1 ] || [ 0 ] || [ 0 ] || [ 0 ] || [ [ 1 ] ] || [ 1 ] ] \ && [ no ] || [ yes ] echo 25.ok else echo 25.err endif if [ [ [ [ [ [ [ 1 ] ] && [ 1 ] ] && [ 1 ] ] && [ 1 ] ] ] && [ 1 ] ] echo 26.ok else echo 26.err endif if [ [ [ [ [ [ [ 1 ] ] && [ 1 ] ] && [ 1 ] ] && [ 1 ] ] ] && [ 0 ] ] echo 27.err else echo 27.ok endif if [ [ [ [ [ [ [ 1 ] ] && [ 1 ] ] && [ 0 ] ] && [ 1 ] ] ] && [ 1 ] ] echo 28.err else echo 28.ok endif if [ [ [ [ [ [ [ 0 ] ] && [ 1 ] ] && [ 1 ] ] && [ 1 ] ] ] && [ 1 ] ] echo 29.err else echo 29.ok endif if [ 1 ] || [ 0 ] || [ 0 ] || [ 0 ] && [ 0 ] echo 30.err else echo 30.ok endif if [ 1 ] || [ 0 ] || [ 0 ] || [ 0 ] && [ 1 ] echo 31.ok else echo 31.err endif if [ 0 ] || [ 0 ] || [ 0 ] || [ 1 ] && [ 0 ] echo 32.err else echo 32.ok endif if [ 0 ] || [ 0 ] || [ 0 ] || [ 1 ] && [ 1 ] echo 33.ok else echo 33.err endif if [ 0 ] || [ 0 ] || [ 0 ] || [ 1 ] && [ 0 ] || [ 1 ] && [ 0 ] echo 34.err else echo 34.ok endif if [ 0 ] || [ 0 ] || [ 0 ] || [ 1 ] && [ 0 ] || [ 1 ] && [ 1 ] echo 35.ok else echo 35.err endif set diet=yo curd=ho if [ [ $diet == 'yo' ] && [ $curd == 'ho' ] ] && \ [ -N ndefined || -n "$ndefined" || \ ! -Z ndefined || ! -z "$ndefined" ] echo 36.err else echo 36.ok endif set ndefined if [ [ $diet == 'yo' ] && [ $curd == 'ho' ] ] && \ -N ndefined && ! -n "$ndefined" && \ ! -Z ndefined && -z "$ndefined" echo 37.ok else echo 37.err endif # 2. Shitty grouping capabilities as of today unset diet curd ndefined if [ false || false || true ] && [ false || true ] && yes echo 40.ok else echo 40.err endif if [ [ [ 0 || 1 ] && [ 1 || 0 ] ] && 1 ] && [ yes ] echo 41.ok else echo 41.err endif if [ 1 || 0 || 0 || 0 ] echo 42.ok else echo 42.err endif if [ 1 || 0 || 0 || 0 || [ 1 ] ] echo 43.ok else echo 43.err endif if [ 1 || 0 || 0 || 0 || [ 1 ] || 1 ] && no echo 44.err else echo 44.ok endif if [ 1 || 0 || 0 || 0 || 1 || [ 1 ] ] && no || [ yes ] echo 45.ok else echo 45.err endif if [ [ [ [ [ [ 1 ] && 1 ] && 1 ] && 1 ] ] && [ 1 ] ] echo 46.ok else echo 46.err endif if [ [ [ [ [ [ 1 ] && 1 ] && 1 ] && [ 1 ] ] ] && 0 ] echo 47.err else echo 47.ok endif if [ [ [ [ [ [ [ 1 ] ] && 1 ] && 0 ] && [ 1 ] ] ] && 1 ] echo 48.err else echo 48.ok endif if [ [ [ [ [ [ 0 ] && 1 ] && 1 ] && 1 ] ] && 1 ] echo 49.err else echo 49.ok endif if 1 || 0 || 0 || 0 && 0 echo 50.err else echo 50.ok endif if 1 || 0 || 0 || 0 && 1 echo 51.ok else echo 51.err endif if 0 || 0 || 0 || 1 && 0 echo 52.err else echo 52.ok endif if 0 || 0 || 0 || 1 && 1 echo 53.ok else echo 53.err endif if 0 || 0 || 0 || 1 && 0 || 1 && 0 echo 54.err else echo 54.ok endif if 0 || 0 || 0 || 1 && 0 || 1 && 1 echo 55.ok else echo 55.err endif set diet=yo curd=ho if [ $diet == 'yo' && $curd == 'ho' ] && \ [ -N ndefined || -n "$ndefined" || \ ! -Z ndefined || ! -z "$ndefined" ] echo 56.err else echo 56.ok endif if [ $diet == 'yo' && $curd == 'ho' && \ [ [ -N ndefined || -n "$ndefined" || \ ! -Z ndefined || ! -z "$ndefined" ] ] ] echo 57.err else echo 57.ok endif set ndefined if [ $diet == 'yo' && $curd == 'ho' ] && \ -N ndefined && ! -n "$ndefined" && \ ! -Z ndefined && -z "$ndefined" echo 57.ok else echo 57.err endif if $diet == 'yo' && $curd == 'ho' && ! -Z ndefined echo 58.ok else echo 58.err endif if [ [ [ [ [ [ $diet == 'yo' && $curd == 'ho' && -N ndefined ] ] ] ] ] ] echo 59.ok else echo 59.err endif # Some more en-braced variables set diet=yo curd=ho if ${diet} == ${curd} echo 70.err else echo 70.ok endif if "${diet}" != "${curd}" echo 71.ok else echo 71.err endif if $diet == ${curd} echo 72.err else echo 72.ok endif if ${diet} == $curd echo 73.err else echo 73.ok endif # Unary ! if ! 0 && ! ! 1 && ! ! ! ! 2 && 3 echo 80.ok else echo 80.err endif if ! 0 && ! [ ! 1 ] && ! [ ! [ ! [ ! 2 ] ] ] && 3 echo 81.ok else echo 81.err endif if [ ! 0 ] && [ ! [ ! 1 ] ] && [ ! [ ! [ ! [ ! [ 2 ] ] ] ] ] && 3 echo 82.ok else echo 82.err endif if [ ! 0 ] && [ ! [ ! 1 ] ] && [ ! [ ! [ ! [ ! [ 2 ] ] ] ] ] && ! 3 echo 83.err else echo 83.ok endif if [ ! 0 ] && [ ! [ ! 1 ] ] && ! [ [ ! [ ! [ ! [ 2 ] ] ] ] ] && ! 3 echo 84.err else echo 84.ok endif if [ ! 0 ] && ! [ ! [ ! 1 ] ] && [ ! [ ! [ ! [ ! [ 2 ] ] ] ] ] && 3 echo 85.err else echo 85.ok endif if ! [ ! 0 ] && [ ! [ ! 1 ] ] && [ ! [ ! [ ! [ ! [ 2 ] ] ] ] ] && 3 echo 86.err else echo 86.ok endif if [ ! 0 ] && [ ! [ ! 1 ] ] && [ ! [ ! [ ! [ ! [ 2 ] ] ] ] ] || 3 echo 87.ok else echo 87.err endif if [ ! 0 ] && [ ! ! [ ! ! 1 ] ] && [ ! ! [ ! ! [ ! ! [ ! ! [ 2 ] ] ] ] ] echo 88.ok else echo 88.err endif # Unary !, odd if ! 0 && ! ! 1 && ! ! ! 0 && 3 echo 90.ok else echo 90.err endif if ! 0 && ! [ ! 1 ] && ! [ ! [ ! [ 0 ] ] ] && 3 echo 91.ok else echo 91.err endif if [ ! 0 ] && [ ! [ ! 1 ] ] && [ ! [ ! [ ! [ [ 0 ] ] ] ] ] && 3 echo 92.ok else echo 92.err endif if [ ! 0 ] && [ ! [ ! 1 ] ] && [ ! [ ! ! [ ! [ ! 0 ] ] ] ] && ! 3 echo 93.err else echo 93.ok endif if [ ! 0 ] && [ ! [ ! 1 ] ] && ! [ ! [ ! [ ! [ ! 0 ] ] ] ] && 3 echo 94.ok else echo 94.err endif if [ ! 0 ] && ! [ ! [ ! 1 ] ] && [ ! ! [ ! [ ! [ ! [ 0 ] ] ] ] ] && 3 echo 95.err else echo 95.ok endif if ! [ ! 0 ] && [ ! [ ! 1 ] ] && [ ! [ ! [ ! [ ! ! 0 ] ] ] ] && 3 echo 96.err else echo 96.ok endif if [ ! 0 ] && [ ! [ ! 1 ] ] && [ ! [ ! [ ! [ ! [ ! 0 ] ] ] ] ] || 3 echo 97.ok else echo 97.err endif if [ ! 0 ] && [ ! ! [ ! ! 1 ] ] && [ ! ! [ ! ! [ ! ! [ ! [ 0 ] ] ] ] ] echo 98.ok else echo 98.err endif __EOT check normal 0 "${MBOX}" '1688759742 719' if have_feat regex; then ${cat} <<- '__EOT' | ${MAILX} ${ARGS} -Sv15-compat=X > "${MBOX}" set dietcurd=yoho if $dietcurd =~ '^yo.*' echo 1.ok else echo 1.err endif if "$dietcurd" =~ '^Yo.*' echo 1-1.err else echo 1-1.ok endif if $dietcurd =~?case '^Yo.*' echo 1-2.ok else echo 1-2.err endif if $dietcurd =~ '^yOho.+' echo 2.err else echo 2.ok endif if $dietcurd !~? '.*Ho$' echo 3.err else echo 3.ok endif if $dietcurd !~ '.+yohO$' echo 4.ok else echo 4.err endif if [ $dietcurd !~?cas '.+yoho$' ] echo 5.ok else echo 5.err endif if ! [ "$dietcurd" =~?case '.+yoho$' ] echo 6.ok else echo 6.err endif if ! ! [ $'\$dietcurd' !~? '.+yoho$' ] echo 7.ok else echo 7.err endif if ! [ ! [ $dietcurd !~? '.+yoho$' ] ] echo 8.ok else echo 8.err endif if [ ! [ ! [ $dietcurd !~? '.+yoho$' ] ] ] echo 9.ok else echo 9.err endif if ! [ ! [ ! [ $dietcurd !~ '.+yoho$' ] ] ] echo 10.err else echo 10.ok endif if ! ! ! $dietcurd !~ '.+yoho$' echo 11.err else echo 11.ok endif if ! ! ! $dietcurd =~ '.+yoho$' echo 12.ok else echo 12.err endif if ! [ ! ! [ ! [ $dietcurd !~ '.+yoho$' ] ] ] echo 13.ok else echo 13.err endif set diet=abc curd='^abc$' if $diet =~ $curd echo 14.ok else echo 14.err endif set diet=abc curd='^abcd$' if "$diet" !~ $'\$curd' echo 15.ok else echo 15.err endif __EOT check regex 0 "${MBOX}" '1115671789 95' else t_echoskip 'regex:[no regex option]' fi t_epilog "${@}" } t_localopts() { t_prolog "${@}" # Nestable conditions test ${cat} <<- '__EOT' | ${MAILX} ${ARGS} > "${MBOX}" 2>&1 define t2 { echo in: t2 set t2=t2 echo $t2 } define t1 { echo in: t1 set gv1=gv1 localopts on set lv1=lv1 lv2=lv2 set lv3=lv3 call t2 localopts off set gv2=gv2 echo $gv1 $lv1 ${lv2} ${lv3} ${gv2}, $t2 } define t0 { echo in: t0 call t1 echo $gv1 $lv1 ${lv2} ${lv3} ${gv2}, $t2 echo "$gv1 $lv1 ${lv2} ${lv3} ${gv2}, $t2" } account trouble { echo in: trouble call t0 } call t0 unset gv1 gv2 account trouble echo active trouble: $gv1 $lv1 ${lv2} ${lv3} ${gv2}, $t3 account null echo active null: $gv1 $lv1 ${lv2} ${lv3} ${gv2}, $t3 # define ll2 { localopts $1 set x=2 echo ll2=$x } define ll1 { wysh set y=$1; shift; eval localopts $y; localopts $1; shift set x=1 echo ll1.1=$x call ll2 $1 echo ll1.2=$x } define ll0 { wysh set y=$1; shift; eval localopts $y; localopts $1; shift set x=0 echo ll0.1=$x call ll1 $y "$@" echo ll0.2=$x } define llx { echo ----- $1: $2 -> $3 -> $4 echo ll-1.1=$x eval localopts $1 call ll0 "$@" echo ll-1.2=$x unset x } define lly { call llx 'call off' on on on call llx 'call off' off on on call llx 'call off' on off on call llx 'call off' on off off localopts call-fixate on call llx 'call-fixate on' on on on call llx 'call-fixate on' off on on call llx 'call-fixate on' on off on call llx 'call-fixate on' on off off unset x;localopts call on call llx 'call on' on on on call llx 'call on' off on on call llx 'call on' on off on call llx 'call on' on off off } call lly __EOT check 1 0 "${MBOX}" '4016155249 1246' t_epilog "${@}" } t_local() { t_prolog "${@}" ${cat} <<- '__EOT' | ${MAILX} ${ARGS} > "${MBOX}" 2>&1 define du2 { echo du2-1 du=$du local set du=$1 echo du2-2 du=$du local unset du echo du2-3 du=$du } define du { local set du=dudu echo du-1 du=$du call du2 du2du2 echo du-2 du=$du local set nodu echo du-3 du=$du } define ich { echo ich-1 du=$du call du echo ich-2 du=$du } define wir { localopts $1 set du=wirwir echo wir-1 du=$du call ich echo wir-2 du=$du } echo ------- global-1 du=$du call ich echo ------- global-2 du=$du set du=global call ich echo ------- global-3 du=$du call wir on echo ------- global-4 du=$du call wir off echo ------- global-5 du=$du __EOT check 1 0 "${MBOX}" '2411598140 641' t_epilog "${@}" } t_environ() { t_prolog "${@}" ${cat} <<- '__EOT' | EK1=EV1 EK2=EV2 ${MAILX} ${ARGS} > "${MBOX}" 2>&1 echo "we: EK1<$EK1> EK2<$EK2> EK3<$EK3> EK4<$EK4> NEK5<$NEK5>" !echo "shell: EK1<$EK1> EK2<$EK2> EK3<$EK3> EK4<$EK4> NEK5<$NEK5>" varshow EK1 EK2 EK3 EK4 NEK5 echo environ set EK3 EK4, set NEK5 environ set EK3=EV3 EK4=EV4 set NEK5=NEV5 echo "we: EK1<$EK1> EK2<$EK2> EK3<$EK3> EK4<$EK4> NEK5<$NEK5>" !echo "shell: EK1<$EK1> EK2<$EK2> EK3<$EK3> EK4<$EK4> NEK5<$NEK5>" varshow EK1 EK2 EK3 EK4 NEK5 echo removing NEK5 EK3 unset NEK5 environ unset EK3 echo "we: EK1<$EK1> EK2<$EK2> EK3<$EK3> EK4<$EK4> NEK5<$NEK5>" !echo "shell: EK1<$EK1> EK2<$EK2> EK3<$EK3> EK4<$EK4> NEK5<$NEK5>" varshow EK1 EK2 EK3 EK4 NEK5 echo changing EK1, EK4 set EK1=EV1_CHANGED EK4=EV4_CHANGED echo "we: EK1<$EK1> EK2<$EK2> EK3<$EK3> EK4<$EK4> NEK5<$NEK5>" !echo "shell: EK1<$EK1> EK2<$EK2> EK3<$EK3> EK4<$EK4> NEK5<$NEK5>" varshow EK1 EK2 EK3 EK4 NEK5 echo linking EK4, rechanging EK1, EK4 environ link EK4 set EK1=EV1 EK4=EV4 echo "we: EK1<$EK1> EK2<$EK2> EK3<$EK3> EK4<$EK4> NEK5<$NEK5>" !echo "shell: EK1<$EK1> EK2<$EK2> EK3<$EK3> EK4<$EK4> NEK5<$NEK5>" varshow EK1 EK2 EK3 EK4 NEK5 echo unset all unset EK1 EK2 EK4 echo "we: EK1<$EK1> EK2<$EK2> EK3<$EK3> EK4<$EK4> NEK5<$NEK5>" !echo "shell: EK1<$EK1> EK2<$EK2> EK3<$EK3> EK4<$EK4> NEK5<$NEK5>" varshow EK1 EK2 EK3 EK4 NEK5 __EOT check 1 0 "${MBOX}" '1685686686 1342' t_epilog "${@}" return ${cat} <<- '__EOT' | ${MAILX} ${ARGS} > "${MBOX}" 2>&1 define l4 { echo '-------> L4 (environ unlink EK1, own localopts)' localopts yes environ unlink EK1 set LK1=LK1_L4 EK1=EK1_L4 echo "we: L4: LK1<$LK1> EK1<$EK1>" !echo "shell: L4: LK1<$LK1> EK1<$EK1>" varshow LK1 EK1 echo '-------< L4' } define l3 { echo '-------> L3' set LK1=LK1_L3 EK1=EK1_L3 echo "we: L3-pre: LK1<$LK1> EK1<$EK1>" !echo "shell: L3-pre: LK1<$LK1> EK1<$EK1>" varshow LK1 EK1 call l4 echo "we: L3-post: LK1<$LK1> EK1<$EK1>" !echo "shell: L3-post: LK1<$LK1> EK1<$EK1>" varshow LK1 EK1 echo '-------< L3' } define l2 { echo '-------> L2' set LK1=LK1_L2 EK1=EK1_L2 echo "we: L2-pre: LK1<$LK1> EK1<$EK1>" !echo "shell: L2-pre: LK1<$LK1> EK1<$EK1>" varshow LK1 EK1 call l3 echo "we: L2-post: LK1<$LK1> EK1<$EK1>" !echo "shell: L2-post: LK1<$LK1> EK1<$EK1>" varshow LK1 EK1 echo '-------< L2' } define l1 { echo '-------> L1 (environ link EK1; localopts call-fixate)' localopts call-fixate yes set LK1=LK1_L1 EK1=EK1_L1 environ link EK1 echo "we: L1-pre: LK1<$LK1> EK1<$EK1>" !echo "shell: L1-pre: LK1<$LK1> EK1<$EK1>" varshow LK1 EK1 call l2 echo "we: L1-post: LK1<$LK1> EK1<$EK1>" !echo "shell: L1-post: LK1<$LK1> EK1<$EK1>" varshow LK1 EK1 echo '-------< L1' } echo "we: outer-pre: LK1<$LK1> EK1<$EK1>" !echo "shell: outer-pre: LK1<$LK1> EK1<$EK1>" varshow LK1 EK1 call l1 echo "we: outer-post: LK1<$LK1> EK1<$EK1>" !echo "shell: outer-post: LK1<$LK1> EK1<$EK1>" varshow LK1 EK1 __EOT check 2 0 "${MBOX}" '1903030743 1131' t_epilog "${@}" } t_macro_param_shift() { t_prolog "${@}" ${cat} <<- '__EOT' | ${MAILX} ${ARGS} > "${MBOX}" 2>>${ERR} define t2 { echo in: t2 echo t2.0 has $#/${#} parameters: "$1,${2},$3" (${*}) [$@] localopts on wysh set ignerr=$1 shift localopts off echo t2.1 has $#/${#} parameters: "$1,${2},$3" (${*}) [$@] if [ $# > 1 ] || [ $ignerr == '' ] shift 2 else ignerr shift 2 endif echo t2.2:$? has $#/${#} parameters: "$1,${2},$3" (${*}) [$@] shift 0 echo t2.3:$? has $#/${#} parameters: "$1,${2},$3" (${*}) [$@] if [ $# > 0 ] shift endif echo t2.4:$? has $#/${#} parameters: "$1,${2},$3" (${*}) [$@] } define t1 { set errexit echo in: t1 call t2 1 you get four args echo t1.1: $?';' ignerr ($ignerr) should not exist call t2 1 you get 'three args' echo t1.2: $?';' ignerr ($ignerr) should not exist call t2 1 you 'get two args' echo t1.3: $?';' ignerr ($ignerr) should not exist call t2 1 'you get one arg' echo t1.4: $?';' ignerr ($ignerr) should not exist ignerr call t2 '' 'you get one arg' echo t1.5: $?';' ignerr ($ignerr) should not exist } call t1 __EOT check 1 0 "${MBOX}" '1402489146 1682' t_epilog "${@}" } t_addrcodec() { t_prolog "${@}" ${cat} <<- '__EOT' | ${MAILX} ${ARGS} > "${MBOX}" 2>&1 commandalias x echo '$?/$^ERRNAME $res' vput addrcodec res e 1 x eval vput addrcodec res d $res x vput addrcodec res e 2 . x eval vput addrcodec res d $res x vput addrcodec res e 3 Sauer Dr. x eval vput addrcodec res d $res x vput addrcodec res e 3.50 Sauer (Ma) Dr. x eval vput addrcodec res d $res x vput addrcodec res e 3.51 Sauer (Ma) "Dr." x eval vput addrcodec res d $res x # vput addrcodec res +e 4 Sauer (Ma) Dr. x eval vput addrcodec res d $res x vput addrcodec res +e 5 Sauer (Ma) Braten Dr. x eval vput addrcodec res d $res x vput addrcodec res +e 6 Sauer (Ma) Braten Dr. (Heu) x eval vput addrcodec res d $res x vput addrcodec res +e 7 Sauer (Ma) Braten Dr. (Heu) (bu) x eval vput addrcodec res d $res x vput addrcodec res +e 8 \ Dr. Sauer (Ma) Braten Dr. (Heu) (bu) Boom. Boom x eval vput addrcodec res d $res x vput addrcodec res +e 9 Dr.Sauer(Ma)Braten Dr. (Heu) x eval vput addrcodec res d $res x vput addrcodec res +e 10 (Ma)Braten Dr. (Heu) x eval vput addrcodec res d $res x vput addrcodec res +e 11 (Ma)Braten Dr"." (Heu) x eval vput addrcodec res d $res x vput addrcodec res +e 12 Dr. Sauer (Ma) Braten Dr. (u) x eval vput addrcodec res d $res x vput addrcodec res +e 13(Ma)Braten Dr. (Heu) x eval vput addrcodec res d $res x vput addrcodec res +e 14 Hey, Du Wie() findet Dr. das? () x eval vput addrcodec res d $res x vput addrcodec res +e 15 \ Hey, Du Wie() findet "" Dr. "" das? () x eval vput addrcodec res d $res x vput addrcodec res +e 16 \ "Hey," "Du" "Wie()" findet "" Dr. "" das? () x eval vput addrcodec res d $res x vput addrcodec res +e 17 \ "Hey" Du "Wie() findet " " Dr. """ das? () x eval vput addrcodec res d $res x vput addrcodec res +e 18 \ "Hey" Du "Wie() findet " " Dr. """ das? () x eval vput addrcodec res d $res x vput addrcodec res +e 19 Hey\,\" "Wie()" findet \" Dr. \" das? x eval vput addrcodec res d $res x # vput addrcodec res ++e 20 Hey\,\" "Wie()" findet \" Dr. \" das? x vput addrcodec res ++e 21 Hey\,\"" "Wie()" findet \" Dr. \" das? x eval vput addrcodec res d $res x # vput addrcodec res \ +++e 22 Hey\\,\" "Wie()" findet \" Dr. \" das? x eval vput addrcodec res d $res x # vput addrcodec res s \ "23 Hey\\,\\\" \"Wie" () "\" findet \\\" Dr. \\\" das?" x # # Fix for [f3852f88] vput addrcodec res ++e 100 (comment) "Quot(e)d" x eval vput addrcodec res d $res x vput addrcodec res e 100 (comment) "Quot(e)d" x eval vput addrcodec res d $res x __EOT check 1 0 "${MBOX}" '1047317989 2612' ${cat} <<- '__EOT' | ${MAILX} ${ARGS} > "${MBOX}" 2>&1 commandalias x echo '$?/$^ERRNAME $res' mlist isa1@list mlsubscribe isa2@list # vput addrcodec res skin Hey\\,\" "Wie()" find \" Dr. \" das? x vput addrcodec res skinlist Hey\\,\" "Wie()" find \" Dr. \" das? x vput addrcodec res skin Hey\\,\" "Wie()" find \" Dr. \" das? x vput addrcodec res skinlist Hey\\,\" "Wie()" find \" Dr. \" das? x vput addrcodec res skin Hey\\,\" "Wie()" find \" Dr. \" das? x vput addrcodec res skinlist Hey\\,\" "Wie()" find \" Dr. \" das? x __EOT check 2 0 "${MBOX}" '1391779299 104' if have_feat idna; then ${cat} <<- '__EOT' | ${MAILX} ${ARGS} ${ADDARG_UNI} > "${MBOX}" 2>&1 commandalias x echo '$?/$^ERRNAME $res' vput addrcodec res e (heu) "stroh" du x eval vput addrcodec res d $res x vput addrcodec res e du x eval vput addrcodec res d $res x vput addrcodec res e du x eval vput addrcodec res d $res x vput addrcodec res e x eval vput addrcodec res d $res x vput addrcodec res e du@blödiän x eval vput addrcodec res d $res x __EOT check idna 0 "${MBOX}" '498775983 326' else t_echoskip 'idna:[no IDNA option]' fi t_epilog "${@}" } t_csop() { t_prolog "${@}" if have_feat cmd-csop; then :; else t_echoskip '[test unsupported]' t_epilog "${@}" return fi ${cat} <<- '__EOT' | ${MAILX} ${ARGS} > "${MBOX}" 2>&1 commandalias x echo '$?/$^ERRNAME :$res:' echo ' #-2' vput csop res find you y;x vput csop res find you o;x vput csop res find you u;x vput csop res find you yo;x vput csop res find you ou;x vput csop res find you you;x echo ' #-1' vput csop res find you Y;x vput csop res find? you Y;x vput csop res find?case you O;x vput csop res find? you U;x vput csop res find?ca you yO;x vput csop res find? you oU;x vput csop res find? you YoU;x echo ' #0' vput csop res find 'bananarama' 'nana';x vput csop res find 'bananarama' 'bana';x vput csop res find 'bananarama' 'Bana';x vput csop res find 'bananarama' 'rama';x echo ' #1' vput csop res find? 'bananarama' 'nana';x vput csop res find? 'bananarama' 'bana';x vput csop res find? 'bananarama' 'Bana';x vput csop res find? 'bananarama' 'rama';x echo ' #2' vput csop res substring 'bananarama' 1;x vput csop res substring 'bananarama' 3;x vput csop res substring 'bananarama' 5;x vput csop res substring 'bananarama' 7;x vput csop res substring 'bananarama' 9;x vput csop res substring 'bananarama' 10;x vput csop res substring 'bananarama' 1 3;x vput csop res substring 'bananarama' 3 3;x vput csop res substring 'bananarama' 5 3;x vput csop res substring 'bananarama' 7 3;x vput csop res substring 'bananarama' 9 3;x vput csop res substring 'bananarama' 10 3;x echo ' #3' vput csop res substring 'bananarama' -1;x vput csop res substring 'bananarama' -3;x vput csop res substring 'bananarama' -5;x vput csop res substring 'bananarama' -7;x vput csop res substring 'bananarama' -9;x vput csop res substring 'bananarama' -10;x vput csop res substring 'bananarama' 1 -3;x vput csop res substring 'bananarama' 3 -3;x vput csop res substring 'bananarama' 5 -3;x vput csop res substring 'bananarama' 7 -3;x vput csop res substring 'bananarama' 9 -3;x vput csop res substring 'bananarama' 10 -3;x echo ' #4' vput csop res trim 'Cocoon Cocoon';x vput csop res trim ' Cocoon Cocoon ';x vput csop res trim-front 'Cocoon Cocoon';x vput csop res trim-front ' Cocoon Cocoon ';x vput csop res trim-end 'Cocoon Cocoon';x vput csop res trim-end ' Cocoon Cocoon ';x __EOT check 1 0 "${MBOX}" '1892119538 755' t_epilog "${@}" } t_vexpr() { t_prolog "${@}" if have_feat cmd-vexpr; then :; else t_echoskip '[test unsupported]' t_epilog "${@}" return fi ${cat} <<- '__EOT' | ${MAILX} ${ARGS} > "${MBOX}" 2>>${ERR} commandalias x echo '$?/$^ERRNAME $res' echo ' #0.0' vput vexpr res = 9223372036854775807;x vput vexpr res = 9223372036854775808;x vput vexpr res = u9223372036854775808;x vput vexpr res =? 9223372036854775808;x vput vexpr res = -9223372036854775808;x vput vexpr res = -9223372036854775809;x vput vexpr res =?saturated -9223372036854775809;x vput vexpr res = U9223372036854775809;x echo ' #0.1' vput vexpr res = \ 0b0111111111111111111111111111111111111111111111111111111111111111;x vput vexpr res = \ S0b1000000000000000000000000000000000000000000000000000000000000000;x vput vexpr res =? \ S0b1000000000000000000000000000000000000000000000000000000000000000;x vput vexpr res = \ U0b1000000000000000000000000000000000000000000000000000000000000000;x vput vexpr res = \ 0b1000000000000000000000000000000000000000000000000000000000000000;x vput vexpr res =? \ 0b1000000000000000000000000000000000000000000000000000000000000000;x vput vexpr res = \ -0b1000000000000000000000000000000000000000000000000000000000000000;x vput vexpr res = \ S0b1000000000000000000000000000000000000000000000000000000000000001;x vput vexpr res =? \ S0b1000000000000000000000000000000000000000000000000000000000000001;x vput vexpr res =? \ -0b1000000000000000000000000000000000000000000000000000000000000001;x vput vexpr res = \ U0b1000000000000000000000000000000000000000000000000000000000000001;x echo ' #0.2' vput vexpr res = 0777777777777777777777;x vput vexpr res = S01000000000000000000000;x vput vexpr res =? S01000000000000000000000;x vput vexpr res = U01000000000000000000000;x vput vexpr res = 01000000000000000000000;x vput vexpr res =?satur 01000000000000000000000;x vput vexpr res = -01000000000000000000000;x vput vexpr res = S01000000000000000000001;x vput vexpr res =?sat S01000000000000000000001;x vput vexpr res = -01000000000000000000001;x vput vexpr res = U01000000000000000000001;x echo ' #0.3' vput vexpr res = 0x7FFFFFFFFFFFFFFF;x vput vexpr res = S0x8000000000000000;x vput vexpr res =? S0x8000000000000000;x vput vexpr res = U0x8000000000000000;x vput vexpr res = 0x8000000000000000;x vput vexpr res =? 0x8000000000000000;x vput vexpr res = -0x8000000000000000;x vput vexpr res = S0x8000000000000001;x vput vexpr res =? S0x8000000000000001;x vput vexpr res = -0x8000000000000001;x vput vexpr res = u0x8000000000000001;x vput vexpr res = 9223372036854775809;x vput vexpr res =? 9223372036854775809;x vput vexpr res = u9223372036854775809;x echo ' #1' vput vexpr res ~ 0;x vput vexpr res ~ 1;x vput vexpr res ~ -1;x echo ' #1.1' vput vexpr res - 0;x vput vexpr res - 1;x vput vexpr res - -1;x vput vexpr res - -0xAFFE;x vput vexpr res - 0xAFFE;x vput vexpr res - u0x8000000000000001;x vput vexpr res - 0x8000000000000001;x vput vexpr res - 0x8000000000000001;x vput vexpr res - 9223372036854775809;x vput vexpr res -? 9223372036854775809;x echo ' #1.2' vput vexpr res + 0;x vput vexpr res + 1;x vput vexpr res + -1;x vput vexpr res + -0xAFFE;x vput vexpr res + 0xAFFE;x vput vexpr res + u0x8000000000000001;x vput vexpr res + 0x8000000000000001;x vput vexpr res + 9223372036854775809;x vput vexpr res +? 9223372036854775809;x echo ' #2' vput vexpr res + 0 0;x vput vexpr res + 0 1;x vput vexpr res + 1 1;x echo ' #3' vput vexpr res + 9223372036854775807 0;x vput vexpr res + 9223372036854775807 1;x vput vexpr res +? 9223372036854775807 1;x vput vexpr res + 0 9223372036854775807;x vput vexpr res + 1 9223372036854775807;x vput vexpr res +? 1 9223372036854775807;x echo ' #4' vput vexpr res + -9223372036854775808 0;x vput vexpr res + -9223372036854775808 -1;x vput vexpr res +? -9223372036854775808 -1;x vput vexpr res + 0 -9223372036854775808;x vput vexpr res + -1 -9223372036854775808;x vput vexpr res +? -1 -9223372036854775808;x echo ' #5' vput vexpr res - 0 0;x vput vexpr res - 0 1;x vput vexpr res - 1 1;x echo ' #6' vput vexpr res - 9223372036854775807 0;x vput vexpr res - 9223372036854775807 -1;x vput vexpr res -? 9223372036854775807 -1;x vput vexpr res - 0 9223372036854775807;x vput vexpr res - -1 9223372036854775807;x vput vexpr res - -2 9223372036854775807;x vput vexpr res -? -2 9223372036854775807;x echo ' #7' vput vexpr res - -9223372036854775808 +0;x vput vexpr res - -9223372036854775808 +1;x vput vexpr res -? -9223372036854775808 +1;x vput vexpr res - 0 -9223372036854775808;x vput vexpr res - +1 -9223372036854775808;x vput vexpr res -? +1 -9223372036854775808;x echo ' #8' vput vexpr res + -13 -2;x vput vexpr res - 0 0;x vput vexpr res - 0 1;x vput vexpr res - 1 1;x vput vexpr res - -13 -2;x echo ' #9' vput vexpr res * 0 0;x vput vexpr res * 0 1;x vput vexpr res * 1 1;x vput vexpr res * -13 -2;x echo ' #10' vput vexpr res / 0 0;x vput vexpr res / 0 1;x vput vexpr res / 1 1;x vput vexpr res / -13 -2;x echo ' #11' vput vexpr res % 0 0;x vput vexpr res % 0 1;x vput vexpr res % 1 1;x vput vexpr res % -13 -2;x echo ' #12' vput vexpr res pbase 10 u0x8000000000000001;x vput vexpr res pbase 16 0x8000000000000001;x vput vexpr res pbase 16 s0x8000000000000001;x vput vexpr res pbase 16 u0x8000000000000001;x vput vexpr res pbase 36 0x8000000000000001;x vput vexpr res pbase 36 u0x8000000000000001;x __EOT check numeric 0 "${MBOX}" '163128733 2519' # v15compat: vexpr byte ops <> csop compat: drop this! ${cat} <<- '__EOT' | ${MAILX} ${ARGS} > "${MBOX}" 2>&1 commandalias x echo '$?/$^ERRNAME :$res:' echo ' #-2' vput vexpr res find you y;x vput vexpr res find you o;x vput vexpr res find you u;x vput vexpr res find you yo;x vput vexpr res find you ou;x vput vexpr res find you you;x echo ' #-1' vput vexpr res find you Y;x vput vexpr res find? you Y;x vput vexpr res find? you O;x vput vexpr res find? you U;x vput vexpr res find? you yO;x vput vexpr res find? you oU;x vput vexpr res find? you YoU;x echo ' #0' vput vexpr res find 'bananarama' 'nana';x vput vexpr res find 'bananarama' 'bana';x vput vexpr res find 'bananarama' 'Bana';x vput vexpr res find 'bananarama' 'rama';x echo ' #1' vput vexpr res ifind 'bananarama' 'nana';x vput vexpr res ifind 'bananarama' 'bana';x vput vexpr res ifind 'bananarama' 'Bana';x vput vexpr res ifind 'bananarama' 'rama';x echo ' #2' vput vexpr res substring 'bananarama' 1;x vput vexpr res substring 'bananarama' 3;x vput vexpr res substring 'bananarama' 5;x vput vexpr res substring 'bananarama' 7;x vput vexpr res substring 'bananarama' 9;x vput vexpr res substring 'bananarama' 10;x vput vexpr res substring 'bananarama' 1 3;x vput vexpr res substring 'bananarama' 3 3;x vput vexpr res substring 'bananarama' 5 3;x vput vexpr res substring 'bananarama' 7 3;x vput vexpr res substring 'bananarama' 9 3;x vput vexpr res substring 'bananarama' 10 3;x echo ' #3' vput vexpr res substring 'bananarama' -1;x vput vexpr res substring 'bananarama' -3;x vput vexpr res substring 'bananarama' -5;x vput vexpr res substring 'bananarama' -7;x vput vexpr res substring 'bananarama' -9;x vput vexpr res substring 'bananarama' -10;x vput vexpr res substring 'bananarama' 1 -3;x vput vexpr res substring 'bananarama' 3 -3;x vput vexpr res substring 'bananarama' 5 -3;x vput vexpr res substring 'bananarama' 7 -3;x vput vexpr res substring 'bananarama' 9 -3;x vput vexpr res substring 'bananarama' 10 -3;x echo ' #4' vput vexpr res trim 'Cocoon Cocoon';x vput vexpr res trim ' Cocoon Cocoon ';x vput vexpr res trim-front 'Cocoon Cocoon';x vput vexpr res trim-front ' Cocoon Cocoon ';x vput vexpr res trim-end 'Cocoon Cocoon';x vput vexpr res trim-end ' Cocoon Cocoon ';x __EOT check byte 0 "${MBOX}" '1892119538 755' if have_feat regex; then ${cat} <<- '__EOT' | ${MAILX} ${ARGS} > "${MBOX}" 2>&1 commandalias x echo '$?/$^ERRNAME :$res:' echo ' #-2' vput vexpr res regex you y;x vput vexpr res regex you o;x vput vexpr res regex you u;x vput vexpr res regex you yo;x vput vexpr res regex you ou;x vput vexpr res regex you you;x echo ' #-1' vput vexpr res regex you Y;x vput vexpr res regex? you Y;x vput vexpr res regex? you O;x vput vexpr res regex? you U;x vput vexpr res regex? you yO;x vput vexpr res regex? you oU;x vput vexpr res regex? you YoU;x echo ' #0' vput vexpr res regex 'bananarama' 'nana';x vput vexpr res regex 'bananarama' 'bana';x vput vexpr res regex 'bananarama' 'Bana';x vput vexpr res regex 'bananarama' 'rama';x echo ' #1' vput vexpr res iregex 'bananarama' 'nana';x vput vexpr res iregex 'bananarama' 'bana';x vput vexpr res iregex 'bananarama' 'Bana';x vput vexpr res iregex 'bananarama' 'rama';x echo ' #2' vput vexpr res regex 'bananarama' '(.*)nana(.*)' '\${1}a\${0}u{\$2}';x vput vexpr res regex 'bananarama' '(.*)bana(.*)' '\${1}a\${0}u\$2';x vput vexpr res regex 'bananarama' 'Bana(.+)' '\$1\$0';x vput vexpr res regex 'bananarama' '(.+)rama' '\$1\$0';x echo ' #3' vput vexpr res iregex 'bananarama' '(.*)nana(.*)' '\${1}a\${0}u{\$2}';x vput vexpr res iregex 'bananarama' '(.*)bana(.*)' '\${1}a\${0}u\$2';x vput vexpr res iregex 'bananarama' 'Bana(.+)' '\$1\$0';x vput vexpr res iregex 'bananarama' '(.+)rama' '\$1\$0';x echo ' #4' vput vexpr res regex 'banana' '(club )?(.*)(nana)(.*)' \ '\$1\${2}\$4\${3}rama';x vput vexpr res regex 'Banana' '(club )?(.*)(nana)(.*)' \ '\$1\$2\${2}\$2\$4\${3}rama';x vput vexpr res regex 'Club banana' '(club )?(.*)(nana)(.*)' \ '\$1\${2}\$4\${3}rama';x echo ' #5' __EOT check regex 0 "${MBOX}" '2831099111 542' else t_echoskip 'regex:[no regex option]' fi t_epilog "${@}" } t_call_ret() { t_prolog "${@}" if have_feat cmd-vexpr; then :; else t_echoskip '[test unsupported]' t_epilog "${@}" return fi ${cat} <<- '__EOT' | ${MAILX} ${ARGS} -Snomemdebug > "${MBOX}" 2>&1 define w1 { echon ">$1 " vput vexpr i + $1 1 if [ $i -le 42 ] vput vexpr j '&' $i 7 if [ $j -eq 7 ] echo . end call w1 $i wysh set i=$? k=$! vput vexpr j '&' $i 7 echon "<$1/$i/$k " if [ $j -eq 7 ] echo . end else echo ! The end for $1 end return $1 } # Transport $?/$! up the call chain define w2 { echon ">$1 " vput vexpr i + $1 1 if [ $1 -lt 42 ] call w2 $i wysh set i=$? j=$! k=$^ERRNAME echon "<$1/$i/$k " return $i $j else echo ! The end for $1 return $i $^ERR-BUSY end echoerr au } # Up and down it goes define w3 { echon ">$1/$2 " vput vexpr i + $1 1 if [ $1 -lt 42 ] call w3 $i $2 wysh set i=$? j=$! vput vexpr k - $1 $2 if [ $k -eq 21 ] vput vexpr i + $1 1 vput vexpr j + $2 1 echo "# <$i/$j> .. " call w3 $i $j wysh set i=$? j=$! end eval echon "<\$1=\$i/\$^ERRNAME-$j " return $i $j else echo ! The end for $1=$i/$2 if [ "$2" != "" ] return $i $^ERR-DOM else return $i $^ERR-BUSY end end echoerr au } call w1 0; echo ?=$? !=$!; echo -----; call w2 0; echo ?=$? !=$^ERRNAME; echo -----; call w3 0 1; echo ?=$? !=$^ERRNAME; echo -----; __EOT check 1 0 "${MBOX}" '1572045517 5922' t_epilog "${@}" } t_xcall() { t_prolog "${@}" if have_feat cmd-vexpr; then :; else t_echoskip '[test unsupported]' t_epilog "${@}" return fi ${cat} <<- '__EOT' | \ ${MAILX} ${ARGS} -Snomemdebug \ -Smax=${LOOPS_MAX} \ > "${MBOX}" 2>&1 define work { echon "$1 " vput vexpr i + $1 1 if [ $i -le "$max" ] vput vexpr j '&' $i 7 if [ $j -eq 7 ] echo . end \xcall work $i $2 end echo ! The end for $1/$2 if [ "$2" != "" ] return $i $^ERR-BUSY end } define xwork { \xcall work 0 $2 } call work 0 echo ?=$? !=$! call xwork echo ?=$? !=$! xcall xwork echo ?=$? !=$^ERRNAME # call work 0 yes echo ?=$? !=$^ERRNAME call xwork 0 yes echo ?=$? !=$^ERRNAME __EOT i=${?} if [ ${LOOPS_MAX} -eq ${LOOPS_BIG} ]; then check_ex0 1-${LOOPS_BIG} ${i} check 1-${LOOPS_BIG} - "${MBOX}" '1069764187 47161' else check_ex0 1-${LOOPS_SMALL} ${i} check 1-${LOOPS_SMALL} - "${MBOX}" '859201011 3894' fi ## if have_feat uistrings; then ${cat} <<- '__EOT' > "${BODY}" define __w { echon "$1 " vput vexpr i + $1 1 if [ $i -le 111 ] vput vexpr j '&' $i 7 if [ $j -eq 7 ] echo . end \xcall __w $i $2 end echo ! The end for $1 if [ $2 -eq 0 ] nonexistingcommand echo would be err with errexit return end echo calling exit exit } define work { echo eins call __w 0 0 echo zwei, ?=$? !=$! localopts yes; set errexit ignerr call __w 0 0 echo drei, ?=$? !=$^ERRNAME call __w 0 $1 echo vier, ?=$? !=$^ERRNAME, this is an error } ignerr call work 0 echo outer 1, ?=$? !=$^ERRNAME xxxign call work 0 echo outer 2, ?=$? !=$^ERRNAME, could be error if xxxign non-empty call work 1 echo outer 3, ?=$? !=$^ERRNAME echo this is definitely an error __EOT < "${BODY}" ${MAILX} ${ARGS} -X'commandalias xxxign ignerr' \ -Snomemdebug > "${MBOX}" 2>&1 check 2 0 "${MBOX}" '3900716531 4200' < "${BODY}" ${MAILX} ${ARGS} -X'commandalias xxxign " "' \ -Snomemdebug > "${MBOX}" 2>&1 check 3 1 "${MBOX}" '1006776201 2799' else t_echoskip '2-3:[test unsupported]' fi t_epilog "${@}" } t_vpospar() { t_prolog "${@}" ${cat} <<- '__EOT' | ${MAILX} ${ARGS} > "${MBOX}" 2>&1 vpospar set hey, "'you ", world! echo $?/$^ERRNAME/$#: $* / "$@" / <$1><$2><$3><$4> vput vpospar x quote; echo x<$x> vpospar clear;echo $?/$^ERRNAME/$#: $* / "$@" / <$1><$2><$3><$4> vput vpospar y quote;echo y<$y> eval vpospar set ${x};echo $?/$^ERRNAME/$#: $* / "$@" / <$1><$2><$3><$4> eval vpospar set ${y};echo $?/$^ERRNAME/$#: $* / "$@" / <$1><$2><$3><$4> eval vpospar set ${x};echo $?/$^ERRNAME/$#: $* / "$@" / <$1><$2><$3><$4> define infun2 { echo infun2:$?/$^ERRNAME/$#:$*/"$@"/<$1><$2><$3><$4> vput vpospar z quote;echo infun2:z<$z> } define infun { echo infun:$?/$^ERRNAME/$#:$*/"$@"/<$1><$2><$3><$4> vput vpospar y quote;echo infun:y<$y> eval vpospar set ${x};echo infun:$?/$^ERRNAME/$#:$*/"$@"/<$1><$2><$3><$4> vpospar clear;echo infun:$?/$^ERRNAME/$#:$*/"$@"/<$1><$2><$3><$4> eval call infun2 $x echo infun:$?/$^ERRNAME/$#:$*/"$@"/<$1><$2><$3><$4> eval vpospar set ${y};echo infun:$?/$^ERRNAME/$#:$*/"$@"/<$1><$2><$3><$4> } call infun This "in a" fun echo $?/$^ERRNAME/$#: $* / "$@" / <$1><$2><$3><$4> vpospar clear;echo $?/$^ERRNAME/$#: $* / "$@" / <$1><$2><$3><$4> __EOT check 1 0 "${MBOX}" '155175639 866' # ${cat} <<- '__EOT' | ${MAILX} ${ARGS} > "${MBOX}" 2>&1 set ifs=\' echo ifs<$ifs> ifs-ws<$ifs-ws> vpospar set hey, "'you ", world! echo $?/$^ERRNAME/$#: $* / "$@" / <$1><$2><$3><$4> vput vpospar x quote; echo x<$x> vpospar clear;echo $?/$^ERRNAME/$#: $* / "$@" / <$1><$2><$3><$4> eval vpospar set ${x};echo $?/$^ERRNAME/$#: $* / "$@" / <$1><$2><$3><$4> set ifs=, echo ifs<$ifs> ifs-ws<$ifs-ws> vpospar set hey, "'you ", world! unset ifs;echo $?/$^ERRNAME/$#: $* / "$@" / <$1><$2><$3><$4> set ifs=, vput vpospar x quote; echo x<$x> vpospar clear;echo $?/$^ERRNAME/$#: $* / "$@" / <$1><$2><$3><$4> eval vpospar set ${x};\ unset ifs;echo $?/$^ERRNAME/$#: $* / "$@" / <$1><$2><$3><$4> wysh set ifs=$',\t' echo ifs<$ifs> ifs-ws<$ifs-ws> vpospar set hey, "'you ", world! unset ifs; echo $?/$^ERRNAME/$#: $* / "$@" / <$1><$2><$3><$4> wysh set ifs=$',\t' vput vpospar x quote; echo x<$x> vpospar clear;echo $?/$^ERRNAME/$#: $* / "$@" / <$1><$2><$3><$4> eval vpospar set ${x};\ unset ifs;echo $?/$^ERRNAME/$#: $* / "$@" / <$1><$2><$3><$4> __EOT check ifs 0 "${MBOX}" '2015927702 706' t_epilog "${@}" } t_atxplode() { t_prolog "${@}" ${cat} > ./.t.sh <<- '___'; ${cat} > ./.t.rc <<- '___' x() { echo $#; } xxx() { printf " (1/$#: <$1>)" shift if [ $# -gt 0 ]; then xxx "$@" else echo fi } yyy() { eval "$@ ' ball" } set -- x "$@" x "$@"'' x " $@" x "$@ " printf yyy;yyy 'xxx' "b\$'\t'u ' " printf xxx;xxx arg ,b u. printf xxx;xxx arg , . printf xxx;xxx arg ,ball. ___ define x { echo $# } define xxx { echon " (1/$#: <$1>)" shift if [ $# -gt 0 ] \xcall xxx "$@" endif echo } define yyy { eval "$@ ' ball" } vpospar set call x "$@" call x "$@"'' call x " $@" call x "$@ " echon yyy;call yyy '\call xxx' "b\$'\t'u ' " echon xxx;call xxx arg ,b u. echon xxx;call xxx arg , . echon xxx;call xxx arg ,ball. ___ ${MAILX} ${ARGS} -X'source ./.t.rc' -Xx > "${MBOX}" 2>&1 check 1 0 "${MBOX}" '41566293 164' #${SHELL} ./.t.sh > ./.tshout 2>&1 #check disproof-1 0 ./.tshout '41566293 164' t_epilog "${@}" } t_read() { t_prolog "${@}" ${cat} <<- '__EOT' > .tin hey1, "'you ", world! hey2, "'you ", bugs bunny! hey3, "'you ", hey4, "'you " __EOT ${cat} <<- '__EOT' |\ ${MAILX} ${ARGS} -X'readctl create ./.tin' > "${MBOX}" 2>&1 commandalias x echo '$?/$^ERRNAME / <$a><$b><$c>' read a b c;x read a b c;x read a b c;x read a b c;x unset a b c;read a b c;x readctl remove ./.tin;echo readctl remove:$?/$^ERRNAME __EOT check 1 0 "${MBOX}" '1527910147 173' ${cat} <<- '__EOT' > .tin2 hey2.0,:"'you ",:world!:mars.: hey2.1,:"'you ",:world! hey2.2,:"'you ",:bugs bunny! hey2.3,:"'you ",: hey2.4,:"'you ": : __EOT ${cat} <<- '__EOT' |\ 6< .tin2 ${MAILX} ${ARGS} -X 'readctl create 6' > "${MBOX}" 2>&1 commandalias x echo '$?/$^ERRNAME / <$a><$b><$c>' set ifs=: read a b c;x read a b c;x read a b c;x read a b c;x read a b c;x read a b c;x unset a b c;read a b c;x read a b c;x readctl remove 6;echo readctl remove:$?/$^ERRNAME __EOT check ifs 0 "${MBOX}" '890153490 298' ${cat} <<- '__EOT' | ${MAILX} ${ARGS} > "${MBOX}" 2>&1 commandalias x echo '$?/$^ERRNAME / <$d>' readctl create .tin readall d;x wysh set d;readall d;x readctl create .tin2 readall d;x wysh set d;readall d;x readctl remove .tin;echo $?/$^ERRNAME;\ readctl remove .tin2;echo $?/$^ERRNAME echo '### now with empty lines' ! printf 'one line\n\ntwo line\n\n' > ./.temptynl readctl create .temptynl;echo $?/$^ERRNAME readall d;x readctl remove .temptynl;echo $?/$^ERRNAME __EOT check readall 0 "${MBOX}" '4113506527 405' t_epilog "${@}" } # }}} # Send/RFC absolute basics {{{ t_can_send_rfc() { t_prolog "${@}" ./.terr 2>&1 check 1 0 "${MBOX}" '550126528 126' check 1-err - .terr '4294967295 0' ./.terr 2>&1 check 2 0 "${MBOX}" '3259888945 324' check 2-err - .terr '4294967295 0' ./.terr 2>&1 check 2no 4 "${MBOX}" '3350946897 468' if have_feat uistrings; then check 2no-err - .terr '3397557940 190' else check 2no-err - .terr '4294967295 0' fi # XXX NOTE we cannot test "cc@no1 " because our stupid parser # XXX would not treat that as a list but look for "," as a separator ' -T bcc:\ bcc@no.3 \ -T cc?si\ \ :\ \ 'cc@no.1, ' -T cc:\ cc@no.3 \ -T to?:\ to@no.1,'' -T to:\ to@no.3 \ > ./.terr 2>&1 check 3 0 "${MBOX}" '1453534480 678' check 3-err - .terr '4294967295 0' ' -T bcc:\ bcc@no.3 \ -T cc:\ 'cc@no.1, ' -T cc\ \ :\ \ cc@no.3 \ -T to\ :to@no.1,'' -T to:\ to@no.3 \ > ./.terr 2>&1 check 4 0 "${MBOX}" '535767201 882' check 4-err - .terr '4294967295 0' # Two test with a file-based MTA "${cat}" <<-_EOT > .tmta.sh #!${SHELL} - (echo 'From reproducible_build Wed Oct 2 01:50:07 1996' && "${cat}" && echo pardauz && echo) > "${MBOX}" _EOT chmod 0755 .tmta.sh ./.terr 2>&1 check 5 0 "${MBOX}" '2384401657 138' check 5-err - .terr '4294967295 0' ./.terr 2>&1 check 6 0 "${MBOX}" '3006460737 138' check 6-err - .terr '4294967295 0' t_epilog "${@}" } # }}} # VFS {{{ t_mbox() { t_prolog "${@}" ( i=1 while [ ${i} -lt 113 ]; do printf 'm file://%s\n~s Subject %s\nHello %s!\n~.\n' \ "${MBOX}" "${i}" "${i}" i=`add ${i} 1` done ) | ${MAILX} ${ARGS} > .tall 2>&1 check 1 0 "${MBOX}" '1785801373 13336' check 1-outerr - ./.tall '4294967295 0' # empty file printf 'File "%s"\ncopy * "%s"\nFile "%s"\nfrom*' "${MBOX}" .tmbox1 .tmbox1 | ${MAILX} ${ARGS} -Sshowlast > .tall 2>&1 check 2 0 .tall '3467540956 8991' printf 'File "%s"\ncopy * "file://%s"\nFile "file://%s"\nfrom*' \ "${MBOX}" .tmbox2 .tmbox2 | ${MAILX} ${ARGS} -Sshowlast > .tall 2>&1 check 3 0 .tall '2410946529 8998' # copy only the odd (but the first), move the even ( printf 'File "file://%s"\ncopy ' .tmbox2 i=1 while [ ${i} -lt 113 ]; do printf '%s ' "${i}" i=`add ${i} 2` done printf 'file://%s\nFile "file://%s"\nfrom*' .tmbox3 .tmbox3 ) | ${MAILX} ${ARGS} -Sshowlast > .tall 2>&1 check 4 0 .tmbox3 '2554734733 6666' check 5 - .tall '2062382804 4517' # ... ( printf 'file "file://%s"\nmove ' .tmbox2 i=2 while [ ${i} -lt 113 ]; do printf '%s ' "${i}" i=`add ${i} 2` done printf 'file://%s\nFile "file://%s"\nfrom*\nFile "file://%s"\nfrom*' \ .tmbox3 .tmbox3 .tmbox2 ) | ${MAILX} ${ARGS} -Sshowlast > .tall 2>>${ERR} check 6 0 .tmbox3 '1429216753 13336' if have_feat uistrings; then ${sed} 2d < .tall > .tallx else ${cp} .tall .tallx fi check 7 - .tallx '169518319 13477' # Invalid MBOXes (after [f4db93b3]) echo > .tinvmbox printf 'copy 1 ./.tinvmbox' | ${MAILX} ${ARGS} -Rf "${MBOX}" > .tall 2>&1 check 8 0 .tinvmbox '2848412822 118' check 9 - ./.tall '461280182 33' echo ' ' > .tinvmbox printf 'copy 1 ./.tinvmbox' | ${MAILX} ${ARGS} -Rf "${MBOX}" > .tall 2>&1 check 10 0 .tinvmbox '624770486 120' check 11 - ./.tall '461280182 33' { echo; echo; } > .tinvmbox # (not invalid) printf 'copy 1 ./.tinvmbox' | ${MAILX} ${ARGS} -Rf "${MBOX}" > .tall 2>&1 check 12 0 .tinvmbox '1485640875 119' check 13 - ./.tall '461280182 33' # *mbox-rfc4155*, plus ${cat} <<-_EOT > ./.tinv1 From MAILER-DAEMON-1 Wed Oct 2 01:50:07 1996 Date: Wed, 02 Oct 1996 01:50:07 +0000 To: Subject: Bad bad message 1 From me to you, blinde Kuh! From MAILER-DAEMON-2 Wed Oct 2 01:50:07 1996 Date: Wed, 02 Oct 1996 01:50:07 +0000 To: Subject: Bad bad message 2 From me to you, blindes Kalb! _EOT ${cp} ./.tinv1 ./.tinv2 printf \ 'define mboxfix { \\localopts yes; \\wysh set mbox-rfc4155;\\wysh File "${1}";\\ \\eval copy * "${2}" } call mboxfix ./.tinv1 ./.tok' | ${MAILX} ${ARGS} > .tall 2>&1 check_ex0 14-estat ${cat} ./.tinv1 ./.tok >> .tall check 14 - ./.tall '739301109 616' printf \ 'wysh file ./.tinv1 # ^From not repaired, but missing trailing NL is wysh File ./.tok # Just move away to nowhere set mbox-rfc4155 wysh file ./.tinv2 # Fully repaired File ./.tok' | ${MAILX} ${ARGS} >>${ERR} 2>&1 check_ex0 15-estat # Equal since [Auto-fix when MBOX had From_ errors on read (Dr. Werner # Fink).] check 15-1 - ./.tinv1 '4151504442 314' check 15-2 - ./.tinv2 '4151504442 314' # *mbox-fcc-and-pcc* ${cat} > ./.ttmpl <<-'_EOT' Fcc: ./.tfcc1 Bcc: | cat >> ./.tpcc1 Fcc: ./.tfcc2 Subject: fcc and pcc, and *mbox-fcc-and-pcc* one line body _EOT < ./.ttmpl ${MAILX} ${ARGS} -t > "${MBOX}" 2>&1 check 16 0 "${MBOX}" '4294967295 0' check 17 - ./.tfcc1 '2301294938 148' check 18 - ./.tfcc2 '2301294938 148' check 19 - ./.tpcc1 '2301294938 148' < ./.ttmpl ${MAILX} ${ARGS} -t -Snombox-fcc-and-pcc > "${MBOX}" 2>&1 check 20 0 "${MBOX}" '4294967295 0' check 21 - ./.tfcc1 '3629108107 98' check 22 - ./.tfcc2 '3629108107 98' check 23 - ./.tpcc1 '2373220256 246' # More invalid: since in "copy * X" messages will be copied in `sort' order, # reordering may happen, and before ([f5db11fe] (a_cwrite_save1(): FIX: # ensure pre-v15 MBOX separation "in between" messages.., 2019-08-07) that # could still have created invalid MBOX files! ${cat} <<-_EOT > ./.tinv1 From MAILER-DAEMON-4 Sun Oct 4 01:50:07 1998 Date: Sun, 04 Oct 1998 01:50:07 +0000 Subject: h4 B4 From MAILER-DAEMON-0 Fri Oct 28 21:02:21 2147483649 Date: Nix, 01 Oct BAD 01:50:07 +0000 Subject: hinvalid BINV From MAILER-DAEMON-3 Fri Oct 3 01:50:07 1997 Date: Fri, 03 Oct 1997 01:50:07 +0000 Subject: h3 B3 From MAILER-DAEMON-1 Sun Oct 1 01:50:07 1995 Date: Sun, 01 Oct 1995 01:50:07 +0000 Subject:h1 B1 From MAILER-DAEMON-2 Wed Oct 2 01:50:07 1996 Date: Wed, 02 Oct 1996 01:50:07 +0000 Subject: h2 b2 _EOT printf \ 'File ./.tinv1 sort date remove ./.tinv2 copy * ./.tinv2 file ./.tinv1' | ${MAILX} ${ARGS} >>${ERR} 2>&1 check 24 0 ./.tinv1 '104184185 560' check 25 - ./.tinv2 '853754737 510' t_epilog "${@}" } t_maildir() { t_prolog "${@}" if have_feat maildir; then :; else t_echoskip '[no maildir option]' t_epilog "${@}" return fi ( i=0 while [ ${i} -lt 112 ]; do printf 'm file://%s\n~s Subject %s\nHello %s!\n~.\n' \ "${MBOX}" "${i}" "${i}" i=`add ${i} 1` done ) | ${MAILX} ${ARGS} check 1 0 "${MBOX}" '2366902811 13332' printf 'File "%s" copy * "%s" File "%s" from* ' "${MBOX}" .tmdir1 .tmdir1 | ${MAILX} ${ARGS} -Snewfolders=maildir -Sshowlast > .tlst check 2 0 .tlst '3442251309 8991' printf 'File "%s" copy * "maildir://%s" File "maildir://%s" from* ' "${MBOX}" .tmdir2 .tmdir2 | ${MAILX} ${ARGS} -Sshowlast > .tlst check 3 0 .tlst '3524806062 9001' printf 'File "maildir://%s" copy * "file://%s" File "file://%s" from* ' .tmdir2 .tmbox1 .tmbox1 | ${MAILX} ${ARGS} -Sshowlast > .tlst check 4 0 .tmbox1 '4096198846 12772' check 5 - .tlst '1262452287 8998' # only the odd (even) ( printf 'File "maildir://%s" copy ' .tmdir2 i=0 while [ ${i} -lt 112 ]; do j=`modulo ${i} 2` [ ${j} -eq 1 ] && printf '%s ' "${i}" i=`add ${i} 1` done printf ' file://%s File "file://%s" from* ' .tmbox2 .tmbox2 ) | ${MAILX} ${ARGS} -Sshowlast > .tlst check 6 0 .tmbox2 '4228337024 6386' check 7 - .tlst '2078821439 4517' # ... ( printf 'file "maildir://%s" move ' .tmdir2 i=0 while [ ${i} -lt 112 ]; do j=`modulo ${i} 2` [ ${j} -eq 0 ] && [ ${i} -ne 0 ] && printf '%s ' "${i}" i=`add ${i} 1` done printf ' file://%s File "file://%s" from* File "maildir://%s" from* ' .tmbox2 .tmbox2 .tmdir2 ) | ${MAILX} ${ARGS} -Sshowlast > .tlst check 8 0 .tmbox2 '978751761 12656' ${sed} 2d < .tlst > .tlstx check 9 - .tlstx '2172297531 13477' # More invalid: since in "copy * X" messages will be copied in `sort' order, # reordering may happen, and before ([f5db11fe] (a_cwrite_save1(): FIX: # ensure pre-v15 MBOX separation "in between" messages.., 2019-08-07) that # could still have created invalid MBOX files! ${cat} <<-_EOT > ./.tinv1 From MAILER-DAEMON-4 Sun Oct 4 01:50:07 1998 Date: Sun, 04 Oct 1998 01:50:07 +0000 Subject: h4 B4 From MAILER-DAEMON-0 Fri Oct 28 21:02:21 2147483649 Date: Nix, 01 Oct BAD 01:50:07 +0000 Subject: hinvalid BINV From MAILER-DAEMON-3 Fri Oct 3 01:50:07 1997 Date: Fri, 03 Oct 1997 01:50:07 +0000 Subject: h3 B3 From MAILER-DAEMON-1 Sun Oct 1 01:50:07 1995 Date: Sun, 01 Oct 1995 01:50:07 +0000 Subject:h1 B1 From MAILER-DAEMON-2 Wed Oct 2 01:50:07 1996 Date: Wed, 02 Oct 1996 01:50:07 +0000 Subject: h2 b2 _EOT printf \ 'File ./.tinv1 sort date copy * maildir://./.tmdir10 !{ for f in ./.tmdir10/new/*; do echo ===; %s $f; done; } > ./.t11 File ./.tmdir10 sort date copy * ./.t10warp ' "${cat}" | ${MAILX} ${ARGS} >>${ERR} 2>&1 # Note that substdate() fixes all but one From_ line to $SOURCE_DATE_EPOCH! check 10 0 ./.t10warp '3551111321 502' check 11 - ./.t11 '642719592 302' t_epilog "${@}" } # }}} # MIME and RFC basics {{{ t_mime_if_not_ascii() { t_prolog "${@}" > "${MBOX}" 2>&1 check 1 0 "${MBOX}" '3647956381 106' > "${MBOX}" 2>&1 check 2 0 "${MBOX}" '3964303752 274' t_epilog "${@}" } t_mime_encoding() { t_prolog "${@}" # 8B printf 'Hey, you.\nFrom me to you\nCiao\n' | ${MAILX} ${ARGS} -s Subject -Smime-encoding=8b "${MBOX}" \ >> "${MBOX}" 2>&1 check 1 0 "${MBOX}" '3835153597 136' printf 'Hey, you.\n\nFrom me to you\nCiao.\n' | ${MAILX} ${ARGS} -s Subject -Smime-encoding=8b "${MBOX}" \ >> "${MBOX}" 2>&1 check 2 0 "${MBOX}" '63875210 275' # QP printf 'Hey, you.\n From me to you\nCiao\n' | ${MAILX} ${ARGS} -s Subject -Smime-encoding=qp "${MBOX}" \ >> "${MBOX}" 2>&1 check 3 0 "${MBOX}" '465798521 412' printf 'Hey, you.\nFrom me to you\nCiao\n' | ${MAILX} ${ARGS} -s Subject -Smime-encoding=qp "${MBOX}" \ >> "${MBOX}" 2>&1 check 4 0 "${MBOX}" '2075263697 655' # B64 printf 'Hey, you.\n From me to you\nCiao\n' | ${MAILX} ${ARGS} -s Subject -Smime-encoding=b64 "${MBOX}" \ >> "${MBOX}" 2>&1 check 5 0 "${MBOX}" '601672771 792' printf 'Hey, you.\nFrom me to you\nCiao\n' | ${MAILX} ${ARGS} -s Subject -Smime-encoding=b64 "${MBOX}" \ >> "${MBOX}" 2>&1 check 6 0 "${MBOX}" '3926760595 1034' t_epilog "${@}" } t_xxxheads_rfc2047() { t_prolog "${@}" echo | ${MAILX} ${ARGS} ${ADDARG_UNI} \ -s 'a̲b̲c̲d̲e̲f̲h̲i̲k̲l̲m̲n̲o̲r̲s̲t̲u̲v̲w̲x̲z̲a̲b̲c̲d̲e̲f̲h̲i̲k̲l̲m̲n̲o̲r̲s̲t̲u̲v̲w̲x̲z̲' \ "${MBOX}" check 1 0 "${MBOX}" '3422562347 371' # Single word (overlong line split -- bad standard! Requires injection of # artificial data!! But can be prevented by using RFC 2047 encoding) ${rm} "${MBOX}" i=`${awk} 'BEGIN{for(i=0; i<92; ++i) printf "0123456789_"}'` echo | ${MAILX} ${ARGS} -s "${i}" "${MBOX}" check 2 0 "${MBOX}" '3317256266 1714' # Combination of encoded words, space and tabs of varying sort ${rm} "${MBOX}" echo | ${MAILX} ${ARGS} ${ADDARG_UNI} \ -s "1Abrä Kaspas1 2Abra Katä b_kaspas2 \ 3Abrä Kaspas3 4Abrä Kaspas4 5Abrä Kaspas5 \ 6Abra Kaspas6 7Abrä Kaspas7 8Abra Kaspas8 \ 9Abra Kaspastäb4-3 10Abra Kaspas1 _ 11Abra Katäb1 \ 12Abra Kadabrä1 After Tab after Täb this is NUTS" \ "${MBOX}" check 3 0 "${MBOX}" '786672837 587' # Overlong multibyte sequence that must be forcefully split # todo This works even before v15.0, but only by accident ${rm} "${MBOX}" echo | ${MAILX} ${ARGS} ${ADDARG_UNI} \ -s "✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄\ ✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄\ ✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄✄" \ "${MBOX}" check 4 0 "${MBOX}" '2889557767 655' # Trailing WS ${rm} "${MBOX}" echo | ${MAILX} ${ARGS} \ -s "1-1 B2 B3 B4 B5 B6 B\ 1-2 B2 B3 B4 B5 B6 B\ 1-3 B2 B3 B4 B5 B6 B\ 1-4 B2 B3 B4 B5 B6 B\ 1-5 B2 B3 B4 B5 B6 B\ 1-6 B2 B3 B4 B5 B6 " \ "${MBOX}" check 5 0 "${MBOX}" '3135161683 293' # Leading and trailing WS ${rm} "${MBOX}" echo | ${MAILX} ${ARGS} \ -s " 2-1 B2 B3 B4 B5 B6 B\ 1-2 B2 B3 B4 B5 B6 B\ 1-3 B2 B3 B4 B5 B6 B\ 1-4 B2 B3 B4 B5 B6 " \ "${MBOX}" check 6 0 "${MBOX}" '3221845405 232' # RFC 2047 in an address field! (Missing test caused v14.9.6!) ${rm} "${MBOX}" echo "Dat Früchtchen riecht häußlich" | ${MAILX} ${ARGS} ${ADDARG_UNI} -Sfullnames -Smta=test://"$MBOX" \ -s Hühöttchen \ 'Schnödes "Früchtchen" (Hä!)' check 7 0 "${MBOX}" '3681801246 373' # RFC 2047 in an address field, and iconv involved if have_feat iconv; then ${rm} "${MBOX}" ${cat} > ./.trebox <<_EOT From zaza@exam.ple Fri Mar 2 21:31:56 2018 Date: Fri, 2 Mar 2018 20:31:45 +0000 From: z=?iso-8859-1?Q?=E1?=za To: dude Subject: houston(...) Message-ID: MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-1 Content-Disposition: inline Content-Transfer-Encoding: 8bit _EOT echo reply | ${MAILX} ${ARGS} ${ADDARG_UNI} \ -Sfullnames -Sreply-in-same-charset \ -Smta=test://"$MBOX" -Rf ./.trebox check 8 0 "${MBOX}" '3499372945 285' else t_echoskip '8:[no iconv option]' fi t_epilog "${@}" } t_iconv_mbyte_base64() { # TODO uses sed(1) and special *headline*!! t_prolog "${@}" if [ -n "${UTF8_LOCALE}" ] && have_feat iconv; then if (/dev/null 2>&1 || (/dev/null 2>&1; then : else t_echoskip '[iconv(1) missing conversion]' t_epilog "${@}" return fi else t_echoskip '[no UTF-8 locale/no iconv option]' t_epilog "${@}" return fi if (/dev/null 2>&1; then ${cat} <<-'_EOT' | LC_ALL=${UTF8_LOCALE} ${MAILX} ${ARGS} \ -Smta=test://"$MBOX" \ -Sescape=! -Smime-encoding=base64 2>./.terr set ttycharset=utf-8 sendcharsets=iso-2022-jp m t1@exam.ple !s Japanese from UTF-8 to ISO-2022-JP シジュウカラ科(シジュウカラか、学名 Paridae)は、鳥類スズメ目の科である。シジュウカラ(四十雀)と総称されるが、狭義にはこの1種をシジュウカラと呼ぶ。 カンムリガラ(学名Parus cristatus)は、スズメ目シジュウカラ科に分類される鳥類の一種。 カンムリガラ(学名Parus cristatus)は、スズメ目シジュウカラ科に分類される鳥類の一種。 シジュウカラ科(シジュウカラか、学名 Paridae)は、鳥類スズメ目の科である。シジュウカラ(四十雀)と総称されるが、狭義にはこの1種をシジュウカラと呼ぶ。 !. set ttycharset=iso-2022-jp charset-7bit=iso-2022-jp sendcharsets=utf-8 m t2@exam.ple !s Japanese from ISO-2022-JP to UTF-8, eh, no, also ISO-2022-JP $B%7%8%e%&%+%i2J!J%7%8%e%&%+%i$+!"3XL>(B Paridae$B!K$O!"D;N`%9%:%aL\$N2J$G$"$k!#%7%8%e%&%+%i!J;M==?}!K$HAm>N$5$l$k$,!"695A$K$O$3$N(B1$B(BParus cristatus$B!K$O!"%9%:%aL\%7%8%e%&%+%i2J$KJ,N`$5$l$kD;N`$N0l(BParus cristatus$B!K$O!"%9%:%aL\%7%8%e%&%+%i2J$KJ,N`$5$l$kD;N`$N0l(B Paridae$B!K$O!"D;N`%9%:%aL\$N2J$G$"$k!#%7%8%e%&%+%i!J;M==?}!K$HAm>N$5$l$k$,!"695A$K$O$3$N(B1$B ./.tcksum check 1 - ./.tcksum '3314001564 516' check 2 - ./.terr '4294967295 0' printf 'eval f 1; eval write ./.twrite; eval type 1; eval type 2\n' | LC_ALL=${UTF8_LOCALE} ${MAILX} ${ARGS} \ -S headline="%>%a%m %-18f %-16d %i%-s" \ -Rf "${MBOX}" >./.tlog 2>&1 check 3 0 ./.twrite '1259742080 686' #check 4 - ./.tlog '3214068822 2123' ${sed} -e '/^\[-- M/d' < ./.tlog > ./.txlog check 4 - ./.txlog '4083300132 2030' else t_echoskip '1-4:[ISO-2022-JP unsupported]' fi if (/dev/null 2>&1; then ${rm} -f "${MBOX}" ./.twrite ${cat} <<-'_EOT' | LC_ALL=${UTF8_LOCALE} ${MAILX} ${ARGS} \ -Smta=test://"$MBOX" \ -Sescape=! -Smime-encoding=base64 2>./.terr set ttycharset=utf-8 sendcharsets=euc-jp m t1@exam.ple !s Japanese from UTF-8 to EUC-JP シジュウカラ科(シジュウカラか、学名 Paridae)は、鳥類スズメ目の科である。シジュウカラ(四十雀)と総称されるが、狭義にはこの1種をシジュウカラと呼ぶ。 カンムリガラ(学名Parus cristatus)は、スズメ目シジュウカラ科に分類される鳥類の一種。 カンムリガラ(学名Parus cristatus)は、スズメ目シジュウカラ科に分類される鳥類の一種。 シジュウカラ科(シジュウカラか、学名 Paridae)は、鳥類スズメ目の科である。シジュウカラ(四十雀)と総称されるが、狭義にはこの1種をシジュウカラと呼ぶ。 !. set ttycharset=EUC-JP sendcharsets=utf-8 m t2@exam.ple !s Japanese from EUC-JP to UTF-8 奦ʡʥ奦餫̾ ParidaeˤϡĻॹܤβʤǤ롣奦ʻͽˤΤ뤬ˤϤ1򥷥奦ȸƤ֡ ꥬʳ̾Parus cristatusˤϡܥ奦ʤʬवĻΰ ꥬʳ̾Parus cristatusˤϡܥ奦ʤʬवĻΰ 奦ʡʥ奦餫̾ ParidaeˤϡĻॹܤβʤǤ롣奦ʻͽˤΤ뤬ˤϤ1򥷥奦ȸƤ֡ !. _EOT check_ex0 5-estat ${awk} 'BEGIN{h=1}/^$/{++h;next}{if(h % 2 == 1)print}' \ < "${MBOX}" > ./.tcksum check 5 - ./.tcksum '1754179361 469' check 6 - ./.terr '4294967295 0' printf 'eval f 1; eval write ./.twrite; eval type 1; eval type 2\n' | LC_ALL=${UTF8_LOCALE} ${MAILX} ${ARGS} \ -S headline="%>%a%m %-18f %-16d %i%-s" \ -Rf "${MBOX}" >./.tlog 2>&1 check 7 0 ./.twrite '1259742080 686' #check 8 - ./.tlog '2506063395 2075' ${sed} -e '/^\[-- M/d' < ./.tlog > ./.txlog check 8 - ./.txlog '3192017734 1983' else t_echoskip '5-8:[EUC-JP unsupported]' fi t_epilog "${@}" } t_iconv_mainbody() { t_prolog "${@}" if [ -n "${UTF8_LOCALE}" ] && have_feat iconv; then :; else t_echoskip '[no UTF-8 locale/no iconv option]' t_epilog "${@}" return fi printf '–' | ${MAILX} ${ARGS} ${ADDARG_UNI} -Smta=test://"$MBOX" \ -S charset-7bit=us-ascii -S charset-8bit=utf-8 \ -s '–' over-the@rain.bow 2>./.terr check 1 0 "${MBOX}" '3559538297 250' check 2 - ./.terr '4294967295 0' printf '–' | ${MAILX} ${ARGS} ${ADDARG_UNI} -Smta=test://"$MBOX" \ -S charset-7bit=us-ascii -S charset-8bit=us-ascii \ -s '–' over-the@rain.bow 2>./.terr check_exn0 3 check 3 - "${MBOX}" '3559538297 250' if have_feat uistrings; then if have_feat docstrings; then # xxx should not be like that check 4 - ./.terr '2579894983 148' else check 4 - ./.terr '271380835 121' fi else t_echoskip '4:[test unsupported]' fi # The different iconv(3) implementations use different replacement sequence # types (character-wise, byte-wise, and the character(s) used differ) i="${MAILX_ICONV_MODE}" if [ -n "${i}" ]; then printf 'p\nx\n' | ${MAILX} ${ARGS} -Rf "${MBOX}" >./.tout 2>./.terr j=${?} check_ex0 5-1-estat ${j} check 5-1 - ./.terr '4294967295 0' if [ ${i} -eq 13 ]; then check 5-2 - ./.tout '189327996 283' # XXX old (before test MTA) elif [ ${i} -eq 12 ]; then check 5-3 - ./.tout '1959197095 283' # XXX old (before test MTA) elif [ ${i} -eq 3 ]; then check 5-4 - ./.tout '3896050050 278' else check 5-5 - ./.tout '3216374419 278' fi else t_echoskip '5:[test unsupported]' fi t_epilog "${@}" } t_binary_mainbody() { t_prolog "${@}" printf 'abra\0\nka\r\ndabra' | ${MAILX} ${ARGS} ${ADDARG_UNI} -s 'binary with carriage-return!' \ "${MBOX}" 2>./.terr check 1 0 "${MBOX}" '1629827 239' check 2 - ./.terr '4294967295 0' printf 'p\necho\necho writing now\nwrite ./.twrite\n' | ${MAILX} ${ARGS} -Rf \ -Spipe-application/octet-stream="@* ${cat} > ./.tcat" \ "${MBOX}" >./.tall 2>&1 check 3 0 ./.tall '733582513 319' check 4 - ./.tcat '3817108933 15' check 5 - ./.twrite '3817108933 15' t_epilog "${@}" } t_mime_force_sendout() { t_prolog "${@}" if have_feat iconv; then :; else t_echoskip '[option iconv missing, unsupported]' t_epilog "${@}" return fi printf '\150\303\274' > ./.tmba printf 'ha' > ./.tsba printf '' > "${MBOX}" printf '\150\303\244' | ${MAILX} ${ARGS} -Smta=test://"$MBOX" \ -s nogo \ over-the@rain.bow 2>>${ERR} check 1 4 "${MBOX}" '4294967295 0' printf '\150\303\244' | ${MAILX} ${ARGS} -Smta=test://"$MBOX" \ -s go -Smime-force-sendout \ over-the@rain.bow 2>>${ERR} check 2 0 "${MBOX}" '1866273282 219' printf ha | ${MAILX} ${ARGS} -Smta=test://"$MBOX" \ -s nogo \ -a ./.tmba over-the@rain.bow 2>>${ERR} check 3 4 "${MBOX}" '1866273282 219' printf ha | ${MAILX} ${ARGS} -Smta=test://"$MBOX" \ -s go -Smime-force-sendout \ -a ./.tmba over-the@rain.bow 2>>${ERR} check 4 0 "${MBOX}" '644433809 880' printf ha | ${MAILX} ${ARGS} -Smta=test://"$MBOX" \ -s nogo \ -a ./.tsba -a ./.tmba over-the@rain.bow 2>>${ERR} check 5 4 "${MBOX}" '644433809 880' printf ha | ${MAILX} ${ARGS} -Smta=test://"$MBOX" \ -s go -Smime-force-sendout \ -a ./.tsba -a ./.tmba over-the@rain.bow 2>>${ERR} check 6 0 "${MBOX}" '3172365123 1729' printf '\150\303\244' | ${MAILX} ${ARGS} -Smta=test://"$MBOX" \ -s nogo \ -a ./.tsba -a ./.tmba over-the@rain.bow 2>>${ERR} check 7 4 "${MBOX}" '3172365123 1729' printf '\150\303\244' | ${MAILX} ${ARGS} -Smta=test://"$MBOX" \ -s go -Smime-force-sendout \ -a ./.tsba -a ./.tmba over-the@rain.bow 2>>${ERR} check 8 0 "${MBOX}" '4002905306 2565' t_epilog "${@}" } t_C_opt_customhdr() { t_prolog "${@}" echo bla | ${MAILX} ${ARGS} -Smta=test://"$MBOX" \ -C 'C-One : Custom One Body' \ -C 'C-Two:CustomTwoBody' \ -C 'C-Three: CustomThreeBody ' \ -S customhdr='chdr1: chdr1 body, chdr2:chdr2 body, chdr3: chdr3 body ' \ this-goes@nowhere >./.tall 2>&1 check_ex0 1-estat ${cat} ./.tall >> "${MBOX}" check 1 0 "${MBOX}" '3555856637 241' ${rm} "${MBOX}" printf 'm this-goes@nowhere\nbody\n!. unset customhdr m this-goes2@nowhere\nbody2\n!. set customhdr=%ccustom1 : custom1 body%c m this-goes3@nowhere\nbody3\n!. set customhdr=%ccustom1 : custom1\\, body , \\ custom2: custom2 body , custom-3 : custom3 body ,\\ custom-4:custom4-body %c m this-goes4@nowhere\nbody4\n!. ' "'" "'" "'" "'" | ${MAILX} ${ARGS} -Smta=test://"$MBOX" -Sescape=! \ -C 'C-One : Custom One Body' \ -C 'C-Two:CustomTwoBody' \ -C 'C-Three: CustomThreeBody ' \ -C ' C-Four:CustomFourBody ' \ -C 'C-Five:CustomFiveBody' \ -S customhdr='ch1: b1 , ch2:b2, ch3:b3 ,ch4:b4, ch5: b5 ' \ >./.tall 2>&1 check_ex0 2-estat ${cat} ./.tall >> "${MBOX}" check 2 0 "${MBOX}" '1826639044 1102' t_epilog "${@}" } # }}} # Operational basics with trivial tests {{{ t_alias() { t_prolog "${@}" ${cat} <<- '__EOT' | ${MAILX} ${ARGS} -Smta=test://"$MBOX" > ./.tall 2>&1 alias a1 ex1@a1.ple alias a1 ex2@a1.ple "EX3 " alias a1 ex4@a1.ple alias a2 ex1@a2.ple ex2@a2.ple ex3@a2.ple ex4@a2.ple alias a3 a4 alias a4 a5 ex1@a4.ple alias a5 a6 alias a6 a7 ex1@a6.ple alias a7 a8 alias a8 ex1@a8.ple alias a1 alias a2 alias a3 m a1 ~c a2 ~b a3 ~r - '_EOT' This body is! This also body is!! _EOT __EOT check 1 0 "${MBOX}" '139467786 277' check 2 - .tall '1598893942 133' if have_feat uistrings; then ${cat} <<- '__EOT' | ${MAILX} ${ARGS} > "${MBOX}" 2>&1 commandalias x echo '$?/$^ERRNAME' echo 1 alias :abra! ha@m beb@ra ha@m '' zeb@ra ha@m; x alias :abra!; x alias ha@m ham-expansion ha@m '';x alias ha@m;x alias beb@ra ceb@ra beb@ra1;x alias beb@ra;x alias ceb@ra ceb@ra1;x alias ceb@ra;x alias deb@ris '';x alias deb@ris;x echo 2 alias - :abra!;x alias - ha@m;x alias - beb@ra;x alias - ceb@ra;x alias - deb@ris;x echo 3 unalias ha@m;x alias - :abra!;x unalias beb@ra;x alias - :abra!;x echo 4 unalias*;x;alias;x echo 5 \alias noexpa@and this@error1;x \alias ha@m '\noexp@and' expa@and \\noexp@and2;x \alias ha@m;x \alias - ha@m;x \alias noexpa@and2 this@error2;x \alias expa1@and this@error3;x \alias expa@and \\expa1@and;x \alias expa@and;x \alias - ha@m;x \alias - expa@and;x __EOT check 3 0 "${MBOX}" '1072772360 789' else t_echoskip '3:[test unsupported]' fi # TODO t_alias: n_ALIAS_MAXEXP is compile-time constant, # TODO need to somehow provide its contents to the test, then test t_epilog "${@}" } t_charsetalias() { t_prolog "${@}" ${cat} <<- '__EOT' | ${MAILX} ${ARGS} > "${MBOX}" 2>&1 commandalias x echo '$?/$^ERRNAME' echo 1 charsetalias latin1 latin15;x charsetalias latin1;x charsetalias - latin1;x echo 2 charsetalias cp1252 latin1 latin15 utf8 utf8 utf16;x charsetalias cp1252;x charsetalias latin15;x charsetalias utf8;x echo 3 charsetalias - cp1252;x charsetalias - latin15;x charsetalias - utf8;x echo 4 charsetalias latin1;x charsetalias - latin1;x uncharsetalias latin15;x charsetalias latin1;x charsetalias - latin1;x __EOT check 1 0 "${MBOX}" '3551595280 433' t_epilog "${@}" } t_shortcut() { t_prolog "${@}" ${cat} <<- '__EOT' | ${MAILX} ${ARGS} > "${MBOX}" 2>&1 commandalias x echo '$?/$^ERRNAME' echo 1 shortcut file1 expansion-of-file1;x shortcut file2 expansion-of-file2;x shortcut file3 expansion-of-file3;x shortcut file4 'expansion of file4' 'file 5' 'expansion of file5';x echo 2 shortcut file1;x shortcut file2;x shortcut file3;x shortcut file4;x shortcut 'file 5';x echo 3 shortcut;x __EOT check 1 0 "${MBOX}" '1970515669 430' t_epilog "${@}" } # }}} # Operational basics with easy tests {{{ t_expandaddr() { # after: t_alias # MTA alias specific part in t_mta_aliases() # This only tests from command line, rest later on (iff any) t_prolog "${@}" if have_feat uistrings; then :; else t_echoskip '[test unsupported]' t_epilog "${@}" return fi echo "${cat}" > ./.tcat chmod 0755 ./.tcat # ./.tpipe' 'talias' 'taddr@exam.ple' \ > ./.tall 2>&1 check 1 4 "${MBOX}" '1216011460 138' check 2 - .tall '4169590008 162' # ./.tpipe' 'talias' 'taddr@exam.ple' \ > ./.tall 2>&1 check 3 0 "${MBOX}" '847567042 276' check 4 - .tall '4294967295 0' check 5 - .tfile '1216011460 138' check 6 - .tpipe '1216011460 138' # ./.tpipe' 'talias' 'taddr@exam.ple' \ > ./.tall 2>&1 check 7 0 "${MBOX}" '3682360102 414' check 8 - .tall '4294967295 0' check 9 - .tfile '847567042 276' check 10 - .tpipe '1216011460 138' # ./.tpipe' 'talias' 'taddr@exam.ple' \ > ./.tall 2>&1 check 11 4 "${MBOX}" '1010907786 552' check 12 - .tall '673208446 70' check 13 - .tfile '847567042 276' check 14 - .tpipe '1216011460 138' printf '' > ./.tpipe ./.tpipe' 'talias' 'taddr@exam.ple' \ > ./.tall 2>&1 check 15 4 "${MBOX}" '1010907786 552' check 16 - .tall '3280630252 179' check 17 - .tfile '847567042 276' check 18 - .tpipe '4294967295 0' # ./.tpipe' 'talias' 'taddr@exam.ple' \ > ./.tall 2>&1 check 19 4 "${MBOX}" '3359494254 690' check 20 - .tall '4052857227 91' check 21 - .tfile '3682360102 414' check 22 - .tpipe '4294967295 0' ./.tpipe' 'talias' 'taddr@exam.ple' \ > ./.tall 2>&1 check 23 4 "${MBOX}" '3359494254 690' check 24 - .tall '2168069102 200' check 25 - .tfile '3682360102 414' check 26 - .tpipe '4294967295 0' # ./.tpipe' 'talias' 'taddr@exam.ple' \ > ./.tall 2>&1 check 27 0 "${MBOX}" '3735108703 828' check 28 - .tall '4294967295 0' check 29 - .tfile '1010907786 552' check 30 - .tpipe '1216011460 138' ./.tpipe' 'talias' 'taddr@exam.ple' \ > ./.tall 2>&1 check 31 4 "${MBOX}" '4225234603 949' check 32 - .tall '3486613973 73' check 33 - .tfile '452731060 673' check 34 - .tpipe '1905076731 121' printf '' > ./.tpipe ./.tpipe' 'talias' 'taddr@exam.ple' \ > ./.tall 2>&1 check 35 4 "${MBOX}" '4225234603 949' check 36 - .tall '3032065285 182' check 37 - .tfile '452731060 673' check 38 - .tpipe '4294967295 0' # ./.tpipe' 'talias' 'taddr@exam.ple' \ > ./.tall 2>&1 check 39 4 "${MBOX}" '4225234603 949' check 40 - .tall '3863610168 169' check 41 - .tfile '1975297706 775' check 42 - .tpipe '130065764 102' ./.tpipe' 'talias' 'taddr@exam.ple' \ > ./.tall 2>&1 check 43 4 "${MBOX}" '4225234603 949' check 44 - .tall '3863610168 169' check 45 - .tfile '3291831864 911' check 46 - .tpipe '4072000848 136' printf '' > ./.tpipe ./.tpipe' 'talias' 'taddr@exam.ple' \ > ./.tall 2>&1 check 47 4 "${MBOX}" '4225234603 949' check 48 - .tall '851041772 278' check 49 - .tfile '3291831864 911' check 50 - .tpipe '4294967295 0' # ./.tall 2>&1 check 51 4 "${MBOX}" '473729143 1070' check 52 - .tall '2646392129 66' ./.tall 2>&1 check 53 4 "${MBOX}" '473729143 1070' check 54 - .tall '887391555 175' # ./.tall 2>&1 check 55 4 "${MBOX}" '473729143 1070' check 56 - .tall '1144578880 139' ./.tall 2>&1 check 57 0 "${MBOX}" '398243793 1191' check 58 - .tall '4294967295 0' # printf '' > "${MBOX}" ${cat} <<-_EOT |\ ${MAILX} ${ARGS} -Snoexpandaddr -Smta=test://"$MBOX" -t -ssub \ -Sadd-file-recipients \ -Sexpandaddr=-all,+fcc \ > ./.tall 2>&1 Fcc: .tfile1 Fcc: .tfile2 _EOT check 59 0 "${MBOX}" '4294967295 0' check 60 - .tall '4294967295 0' check 61 - .tfile1 '1067276522 124' check 62 - .tfile2 '1067276522 124' printf '' > "${MBOX}" ${cat} <<-_EOT |\ ${MAILX} ${ARGS} -Snoexpandaddr -Smta=test://"$MBOX" -t -ssub \ -Sadd-file-recipients \ -Sexpandaddr=-all,+file \ > ./.tall 2>&1 Fcc: .tfile1 Fcc: .tfile2 _EOT check 63 0 "${MBOX}" '4294967295 0' check 64 - .tall '4294967295 0' check 65 - .tfile1 '2677253527 248' check 66 - .tfile2 '2677253527 248' printf '' > "${MBOX}" ${cat} <<-_EOT |\ ${MAILX} ${ARGS} -Snoexpandaddr -Smta=test://"$MBOX" -t -ssub \ -Sadd-file-recipients \ -Sexpandaddr=-all,+file,-fcc \ > ./.tall 2>&1 Fcc: .tfile1 Fcc: .tfile2 _EOT check 67 0 "${MBOX}" '4294967295 0' check 68 - .tall '4294967295 0' check 69 - .tfile1 '3493511004 372' check 70 - .tfile2 '3493511004 372' printf '' > "${MBOX}" ${cat} <<-_EOT |\ ${MAILX} ${ARGS} -Snoexpandaddr -Smta=test://"$MBOX" -t -ssub \ -Sadd-file-recipients \ -Sexpandaddr=-all,+fcc,-file \ > ./.tall 2>&1 Fcc: .tfile1 Fcc: .tfile2 _EOT check 71 4 "${MBOX}" '4294967295 0' check 72 - .tall '203687556 223' check 73 - .tfile1 '3493511004 372' check 74 - .tfile2 '3493511004 372' printf '' > "${MBOX}" ${cat} <<-_EOT |\ ${MAILX} ${ARGS} -Snoexpandaddr -Smta=test://"$MBOX" -t -ssub \ -Sadd-file-recipients \ -Sexpandaddr=-all,fail,+addr \ > ./.tall 2>&1 Fcc: .tfile1 Fcc: .tfile2 To: never@exam.ple _EOT check 75 4 "${MBOX}" '4294967295 0' check 76 - .tall '4060426468 247' check 77 - .tfile1 '3493511004 372' check 78 - .tfile2 '3493511004 372' # printf '' > "${MBOX}" ${cat} <<-_EOT |\ ${MAILX} ${ARGS} -Snoexpandaddr -Smta=test://"$MBOX" -t -ssub \ -Sexpandaddr=fail,domaincheck \ > ./.tall 2>&1 To: one@localhost _EOT check 79 0 "${MBOX}" '171635532 120' check 80 - .tall '4294967295 0' ${cat} <<-_EOT |\ ${MAILX} ${ARGS} -Snoexpandaddr -Smta=test://"$MBOX" -t -ssub \ -Sexpandaddr=domaincheck \ > ./.tall 2>&1 To: one@localhost , Hey two , Trouble _EOT check 81 4 "${MBOX}" '2659464839 240' check 82 - .tall '1119895397 158' ${cat} <<-_EOT |\ ${MAILX} ${ARGS} -Snoexpandaddr -Smta=test://"$MBOX" -t -ssub \ -Sexpandaddr=fail,domaincheck \ > ./.tall 2>&1 To: one@localhost , Hey two , Trouble _EOT check 83 4 "${MBOX}" '2659464839 240' check 84 - .tall '1577313789 267' ${cat} <<-_EOT |\ ${MAILX} ${ARGS} -Snoexpandaddr -Smta=test://"$MBOX" -t -ssub \ -Sexpandaddr=fail,domaincheck \ -Sexpandaddr-domaincheck=exam.ple,tro.uble \ > ./.tall 2>&1 To: one@localhost , Hey two , Trouble _EOT check 85 0 "${MBOX}" '1670655701 410' check 86 - .tall '4294967295 0' t_epilog "${@}" } t_mta_aliases() { # after: t_expandaddr t_prolog "${@}" if have_feat mta-aliases; then :; else t_echoskip '[test unsupported]' t_epilog "${@}" return fi ${cat} > ./.tali <<- '__EOT' # Comment a1: ex1@a1.ple , ex2@a1.ple, , ex4@a1.ple a2: ex1@a2.ple , ex2@a2.ple,a2_2 a2_2:ex3@a2.ple,ex4@a2.ple a3: a4 a4: a5, # Comment # More comment ex1@a4.ple # Comment a5: a6 a6: a7 , ex1@a6.ple a7: a8,a9 a8: ex1@a8.ple __EOT echo | ${MAILX} ${ARGS} -Smta=test://"$MBOX" \ -Smta-aliases=./.tali \ -b a3 -c a2 a1 > ./.tall 2>&1 check 1 0 "${MBOX}" '1172368381 238' check 2 - .tall '4294967295 0' ## xxx The following are actually *expandaddr* tests!! # May not send plain names over SMTP! echo | ${MAILX} ${ARGS} -Smta=smtp://laber.backe \ -Smta-aliases=./.tali \ -b a3 -c a2 a1 > ./.tall 2>&1 check_exn0 3 check 4 - "${MBOX}" '1172368381 238' if have_feat uistrings; then check 5 - .tall '771616226 179' else t_echoskip '5:[test unsupported]' fi # xxx for false-positive SMTP test we would need some mocking echo | ${MAILX} ${ARGS} -Smta=test://"$MBOX" \ -Sexpandaddr=fail,-name \ -Smta-aliases=./.tali \ -b a3 -c a2 a1 > ./.tall 2>&1 check_exn0 6 check 7 - "${MBOX}" '1172368381 238' if have_feat uistrings; then check 8 - .tall '2834389894 178' else t_echoskip '8:[test unsupported]' fi echo | ${MAILX} ${ARGS} -Smta=test://"$MBOX" \ -Sexpandaddr=-name \ -Smta-aliases=./.tali \ -b a3 -c a2 a1 > ./.tall 2>&1 check 9 4 "${MBOX}" '2322273994 472' if have_feat uistrings; then check 10 - .tall '2136559508 69' else t_echoskip '10:[test unsupported]' fi echo 'a9:nine@nine.nine' >> ./.tali echo | ${MAILX} ${ARGS} -Smta=test://"$MBOX" \ -Sexpandaddr=fail,-name \ -Smta-aliases=./.tali \ -b a3 -c a2 a1 > ./.tall 2>&1 check 11 0 "${MBOX}" '2422268299 722' check 12 - .tall '4294967295 0' printf '# set expandaddr=-name mail a1 !c a2 !:echo $?/$^ERRNAME !^header insert bcc a3 !:echo $?/$^ERRNAME !:set expandaddr !t a1 !c a2 !:echo $?/$^ERRNAME !^header insert bcc a3 !:echo $?/$^ERRNAME !. echo and, once again, check that cache is updated # Enclose one pipe in quotes: immense stress for our stupid address parser:( !echo "a10:./.tf1,|%s>./.tp1,\\"|%s > ./.tp2\\",./.tf2" >> ./.tali mail a1 !c a2 !:echo $?/$^ERRNAME !^header insert bcc a3 !:echo $?/$^ERRNAME !. echo trigger happiness mail a1 !c a2 !:echo $?/$^ERRNAME !^header insert bcc a3 a10 !:echo $?/$^ERRNAME !. ' "${cat}" "${cat}" | ${MAILX} ${ARGS} -Smta=test://"$MBOX" -Sescape=! \ -Smta-aliases=./.tali \ > ./.tall 2>&1 check 13 0 "${MBOX}" '550955032 1469' if have_feat uistrings; then check 14 - .tall '1795496020 473' else t_echoskip '14:[test unsupported]' fi check 15 - .tf1 '3056269950 249' check 16 - .tp1 '3056269950 249' check 17 - .tp2 '3056269950 249' check 18 - .tf2 '3056269950 249' # TODO t_mta_aliases: n_ALIAS_MAXEXP is compile-time constant, # TODO need to somehow provide its contents to the test, then test t_epilog "${@}" } t_filetype() { t_prolog "${@}" printf 'm m1@e.t\nL1\nHy1\n~.\nm m2@e.t\nL2\nHy2\n~@ %s\n~.\n' \ "${TOPDIR}snailmail.jpg" | ${MAILX} ${ARGS} -Smta=test://"$MBOX" check 1 0 "${MBOX}" '1314354444 13536' if (echo | gzip -c) >/dev/null 2>&1; then { printf 'File "%s"\ncopy 1 ./.t.mbox.gz\ncopy 2 ./.t.mbox.gz' \ "${MBOX}" | ${MAILX} ${ARGS} \ -X'filetype gz gzip\ -dc gzip\ -c' printf 'File ./.t.mbox.gz\ncopy * ./.t.mbox\n' | ${MAILX} ${ARGS} -X'filetype gz gzip\ -dc gzip\ -c' } > ./.t.out 2>&1 check 2 - ./.t.mbox '1314354444 13536' check 3 - ./.t.out '635961640 103' else t_echoskip '2:[missing gzip(1)]' t_echoskip '3:[missing gzip(1)]' fi { ${rm} ./.t.mbox* printf 'File "%s"\ncopy 1 ./.t.mbox.gz copy 2 ./.t.mbox.gz copy 1 ./.t.mbox.gz copy 2 ./.t.mbox.gz ' "${MBOX}" | ${MAILX} ${ARGS} \ -X'filetype gz gzip\ -dc gzip\ -c' \ -X'filetype mbox.gz "${sed} 1,3d|${cat}" \ "echo eins;echo zwei;echo und mit ${sed} bist Du dabei;${cat}"' printf 'File ./.t.mbox.gz\ncopy * ./.t.mbox\n' | ${MAILX} ${ARGS} \ -X'filetype gz gzip\ -dc gzip\ -c' \ -X'filetype mbox.gz "${sed} 1,3d|${cat}" kill\ 0' } > ./.t.out 2>&1 check 4 - ./.t.mbox '2687765142 27092' check 5 - ./.t.out '2230192693 173' t_epilog "${@}" } t_record_a_resend() { t_prolog "${@}" printf ' set record=%s m %s\n~s Subject 1.\nHello.\n~. set record-files add-file-recipients m %s\n~s Subject 2.\nHello.\n~. File %s resend 2 ./.t.resent Resend 1 ./.t.resent set record-resent resend 2 ./.t.resent Resend 1 ./.t.resent ' ./.t.record "${MBOX}" "${MBOX}" "${MBOX}" | ${MAILX} ${ARGS} check 1 0 "${MBOX}" '2632690399 252' check 2 - .t.record '3337485450 456' check 3 - .t.resent '1560890069 640' t_epilog "${@}" } t_e_H_L_opts() { t_prolog "${@}" touch ./.t.mbox ${MAILX} ${ARGS} -ef ./.t.mbox echo ${?} > "${MBOX}" printf 'm me@exam.ple\nLine 1.\nHello.\n~.\n' | ${MAILX} ${ARGS} -Smta=test://./.t.mbox printf 'm you@exam.ple\nLine 1.\nBye.\n~.\n' | ${MAILX} ${ARGS} -Smta=test://./.t.mbox ${MAILX} ${ARGS} -ef ./.t.mbox 2>> "${MBOX}" echo ${?} >> "${MBOX}" ${MAILX} ${ARGS} -efL @t@me ./.t.mbox 2>> "${MBOX}" echo ${?} >> "${MBOX}" ${MAILX} ${ARGS} -efL @t@you ./.t.mbox 2>> "${MBOX}" echo ${?} >> "${MBOX}" ${MAILX} ${ARGS} -efL '@>@Line 1' ./.t.mbox 2>> "${MBOX}" echo ${?} >> "${MBOX}" ${MAILX} ${ARGS} -efL '@>@Hello.' ./.t.mbox 2>> "${MBOX}" echo ${?} >> "${MBOX}" ${MAILX} ${ARGS} -efL '@>@Bye.' ./.t.mbox 2>> "${MBOX}" echo ${?} >> "${MBOX}" ${MAILX} ${ARGS} -efL '@>@Good bye.' ./.t.mbox 2>> "${MBOX}" echo ${?} >> "${MBOX}" ${MAILX} ${ARGS} -fH ./.t.mbox >> "${MBOX}" 2>&1 echo ${?} >> "${MBOX}" ${MAILX} ${ARGS} -fL @t@me ./.t.mbox >> "${MBOX}" 2>&1 echo ${?} >> "${MBOX}" ${MAILX} ${ARGS} -fL @t@you ./.t.mbox >> "${MBOX}" 2>&1 echo ${?} >> "${MBOX}" ${MAILX} ${ARGS} -fL '@>@Line 1' ./.t.mbox >> "${MBOX}" 2>&1 echo ${?} >> "${MBOX}" ${MAILX} ${ARGS} -fL '@>@Hello.' ./.t.mbox >> "${MBOX}" 2>&1 echo ${?} >> "${MBOX}" ${MAILX} ${ARGS} -fL '@>@Bye.' ./.t.mbox >> "${MBOX}" 2>&1 echo ${?} >> "${MBOX}" ${MAILX} ${ARGS} -fL '@>@Good bye.' ./.t.mbox >> "${MBOX}" 2>>${ERR} echo ${?} >> "${MBOX}" check 1 - "${MBOX}" '1369201287 670' ## printf 'm me1@exam.ple\n~s subject cab\nLine 1.\n~.\n' | ${MAILX} ${ARGS} -Smta=test://./.t.mbox \ -r '' -X 'wysh set from=pony1@$LOGNAME' printf 'm me2@exam.ple\n~s subject bac\nLine 12.\n~.\n' | ${MAILX} ${ARGS} -Smta=test://./.t.mbox \ -r '' -X 'wysh set from=pony2@$LOGNAME' printf 'm me3@exam.ple\n~s subject abc\nLine 123.\n~.\n' | ${MAILX} ${ARGS} -Smta=test://./.t.mbox \ -r '' -X 'wysh set from=pony3@$LOGNAME' ${MAILX} ${ARGS} -S folder-hook=fh-test -X 'define fh-test { echo fh-test size; set autosort=size showname showto }' -fH ./.t.mbox > "${MBOX}" 2>&1 check 2 0 "${MBOX}" '4286438644 413' ${MAILX} ${ARGS} -S folder-hook=fh-test -X 'define fh-test { echo fh-test subject; set autosort=subject showname showto }' -fH ./.t.mbox > "${MBOX}" 2>&1 check 3 0 "${MBOX}" '3208053922 416' ${MAILX} ${ARGS} -S folder-hook=fh-test -X 'define fh-test { echo fh-test from; set autosort=from showto }' -fH ./.t.mbox > "${MBOX}" 2>&1 check 4 0 "${MBOX}" '4209767839 413' ${MAILX} ${ARGS} -S folder-hook=fh-test -X 'define fh-test { echo fh-test to; set autosort=to showto }' -fH ./.t.mbox > "${MBOX}" 2>&1 check 5 0 "${MBOX}" '2785342736 411' t_epilog "${@}" } t_q_t_etc_opts() { # Simple, if we need more here, place in a later vim fold! t_prolog "${@}" # Three tests for MIME encoding and (a bit) content classification. # At the same time testing -q FILE, < FILE and -t FILE t__put_body > ./.tin < ./.tin ${MAILX} ${ARGS} ${ADDARG_UNI} \ -a ./.tin -s "`t__put_subject`" "${MBOX}" check 1 0 "${MBOX}" '1088822685 6642' ${rm} "${MBOX}" < /dev/null ${MAILX} ${ARGS} ${ADDARG_UNI} \ -a ./.tin -s "`t__put_subject`" -q ./.tin "${MBOX}" check 2 0 "${MBOX}" '1088822685 6642' ${rm} "${MBOX}" ( echo "To: ${MBOX}" && echo "Subject: `t__put_subject`" && echo && ${cat} ./.tin ) | ${MAILX} ${ARGS} ${ADDARG_UNI} -Snodot -a ./.tin -t check 3 0 "${MBOX}" '1088822685 6642' # Check comments in the header ${rm} "${MBOX}" ${cat} <<-_EOT | ${MAILX} ${ARGS} -Snodot -t "${MBOX}" # Ein Kommentar From: du@da # Noch ein Kommentar Subject : hey you # Nachgestelltes Kommentar BOOOM _EOT check 4 0 "${MBOX}" '4161555890 124' # ?MODifier suffix printf '' > "${MBOX}" ( echo 'To?single : ./.tout1 .tout2 ' && echo 'CC: ./.tcc1 ./.tcc2' && echo 'BcC?sin : ./.tbcc1 .tbcc2 ' && echo 'To? : ./.tout3 .tout4 ' && echo && echo body ) | ${MAILX} ${ARGS} ${ADDARG_UNI} -Snodot -t -Smta=test://"$MBOX" check 5 0 './.tout1 .tout2' '2948857341 94' check 6 - ./.tcc1 '2948857341 94' check 7 - ./.tcc2 '2948857341 94' check 8 - './.tbcc1 .tbcc2' '2948857341 94' check 9 - './.tout3 .tout4' '2948857341 94' check 10 - "${MBOX}" '4294967295 0' t_epilog "${@}" } t_message_injections() { # Simple, if we need more here, place in a later vim fold! t_prolog "${@}" echo mysig > ./.tmysig echo some-body | ${MAILX} ${ARGS} -Smta=test://"$MBOX" \ -Smessage-inject-head=head-inject \ -Smessage-inject-tail=tail-inject \ -Ssignature=./.tmysig \ ex@am.ple > ./.tall 2>&1 check 1 0 "${MBOX}" '701778583 143' check 2 - .tall '4294967295 0' # empty file ${rm} "${MBOX}" ${cat} <<-_EOT > ./.template From: me To: ex1@am.ple Cc: ex2@am.ple Subject: This subject is Body, body, body me. _EOT < ./.template ${MAILX} ${ARGS} -t -Smta=test://"$MBOX" \ -Smessage-inject-head=head-inject \ -Smessage-inject-tail=tail-inject \ -Ssignature=./.tmysig \ > ./.tall 2>&1 check 3 0 "${MBOX}" '2189109479 207' check 4 - .tall '4294967295 0' # empty file t_epilog "${@}" } t_attachments() { # Relatively Simple, if we need more here, place in a later vim fold! t_prolog "${@}" ${cat} <<-_EOT > ./.tx From steffen Sun Feb 18 02:48:40 2018 Date: Sun, 18 Feb 2018 02:48:40 +0100 To: Subject: m1 User-Agent: s-nail v14.9.7 From steffen Sun Feb 18 02:48:42 2018 Date: Sun, 18 Feb 2018 02:48:42 +0100 To: Subject: m2 User-Agent: s-nail v14.9.7 _EOT echo att1 > ./.t1 printf 'att2-1\natt2-2\natt2-4\n' > ./'.t 2' printf 'att3-1\natt3-2\natt3-4\n' > ./.t3 printf 'att4-1\natt4-2\natt4-4\n' > './.t 4' printf \ '!@ ./.t3 "./.t 4" "" !p !@ ./.t3 "./.t 2" !p !.' \ | ${MAILX} ${ARGS} -Sescape=! -Smta=test://"$MBOX" \ -a ./.t1 -a './.t 2' \ -s attachment-test \ ex@am.ple > ./.tall 2>&1 check 1 0 "${MBOX}" '2484200149 644' if have_feat uistrings; then check 2 - .tall '1928331872 720' else t_echoskip '2:[test unsupported]' fi ${rm} "${MBOX}" printf \ 'mail ex@amp.ple !s This the subject is !@ ./.t3 "#2" "./.t 4" "#1" "" !p !@ "./.t 4" "#2" !p !. mail ex@amp.ple !s Subject two !@ ./.t3 "#2" "./.t 4" "#1" "" !p !@ !p !. mail ex@amp.ple !s Subject three !@ ./.t3 "" "#2" "" "./.t 4" "" "#1" "" !p !@ ./.t3 !p !. mail ex@amp.ple !s Subject Four !@ ./.t3 "" "#2" "" "./.t 4" "" "#1" "" !p !@ "#1" !p !. mail ex@amp.ple !s Subject Five !@ "#2" !p !.' \ | ${MAILX} ${ARGS} -Sescape=! -Smta=test://"$MBOX" -Rf ./.tx \ > ./.tall 2>&1 check 3 0 "${MBOX}" '3637385058 2335' if have_feat uistrings; then check 4 - .tall '2526106274 1910' else t_echoskip '4:[test unsupported]' fi ${rm} "${MBOX}" printf \ 'mail ex@amp.ple !s Subject One !@ "#." Body one. !p !. from 2 mail ex@amp.ple !s Subject Two !@ "#." Body two. !p !. reply 1 2 !@ "#." !p !. !@ "#." !p !.' \ | ${MAILX} ${ARGS} -Sescape=! -Smta=test://"$MBOX" -Rf ./.tx \ > ./.tall 2>&1 check 5 0 "${MBOX}" '1604688179 2316' if have_feat uistrings; then check 6 - .tall '1210753005 508' else t_echoskip '6:[test unsupported]' fi t_epilog "${@}" } t_rfc2231() { # (after attachments) t_prolog "${@}" ( mkdir ./.ttt || exit 1 cd ./.ttt || exit 2 : > "ma'ger.txt" : > "mä'ger.txt" : > 'diet\ is \curd.txt' : > 'diet "is" curd.txt' : > höde-tröge.txt : > höde__tröge__müde__dätte__hätte__vülle__gülle__äse__äße__säuerliche__kräuter__österliche__grüße__mäh.txt : > höde__tröge__müde__dätte__hätte__vuelle__guelle__aese__aesse__sauerliche__kräuter__österliche__grüße__mäh.txt : > hööööööööööööööööö_nöööööööööööööööööööööö_düüüüüüüüüüüüüüüüüüü_bäääääääääääääääääääääääh.txt : > ✆✆✆✆✆✆✆✆✆✆✆✆✆✆✆✆✆✆✆✆✆✆✆✆.txt ) echo bla | ${MAILX} ${ARGS} ${ADDARG_UNI} \ -a "./.ttt/ma'ger.txt" -a "./.ttt/mä'ger.txt" \ -a './.ttt/diet\ is \curd.txt' -a './.ttt/diet "is" curd.txt' \ -a ./.ttt/höde-tröge.txt \ -a ./.ttt/höde__tröge__müde__dätte__hätte__vülle__gülle__äse__äße__säuerliche__kräuter__österliche__grüße__mäh.txt \ -a ./.ttt/höde__tröge__müde__dätte__hätte__vuelle__guelle__aese__aesse__sauerliche__kräuter__österliche__grüße__mäh.txt \ -a ./.ttt/hööööööööööööööööö_nöööööööööööööööööööööö_düüüüüüüüüüüüüüüüüüü_bäääääääääääääääääääääääh.txt \ -a ./.ttt/✆✆✆✆✆✆✆✆✆✆✆✆✆✆✆✆✆✆✆✆✆✆✆✆.txt \ "${MBOX}" check 1 0 "${MBOX}" '3720896054 3088' # `resend' test, reusing $MBOX printf "Resend ./.t2\nx\n" | ${MAILX} ${ARGS} -Rf "${MBOX}" check 2 0 ./.t2 '3720896054 3088' printf "resend ./.t3\nx\n" | ${MAILX} ${ARGS} -Rf "${MBOX}" check 3 0 ./.t3 '3979736592 3133' # And a primitive test for reading messages with invalid parameters ${cat} <<-_EOT > ./.tinv From a@b.invalid Wed May 15 12:43:00 2018 MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="1" This is a multi-part message in MIME format. --1 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable foo --1 Content-Type: text/plain; name*17="na"; name*18="me-c-t" Content-Transfer-Encoding: 7bit Content-Disposition: inline bar --1-- From a@b.invalid Wed May 15 12:43:00 2018 MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="2" This is a multi-part message in MIME format. --2 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable foo --2 Content-Type: text/plain; name*17="na"; name*18="me-c-t" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename*0="na"; filename*998999999999999999999999999999="me-c-d" bar --2-- From a@b.invalid Wed May 15 12:43:00 2018 MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="3" This is a multi-part message in MIME format. --3 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable foo --3 Content-Type: text/plain; name*17="na"; name*18="me-c-t" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename*0="na"; filename*998="me-c-d" bar --3-- _EOT printf '\\# \\headerpick type ignore Content-Type Content-Disposition \\type 1 2 3 \\xit ' | ${MAILX} ${ARGS} -Rf ./.tinv > ./.tall 2> ./.terr check 4 0 ./.tall '1842050412 902' if have_feat uistrings; then check 5 - ./.terr '3713266499 473' else t_echoskip '5:[test unsupported]' fi t_epilog "${@}" } t_mime_types_load_control() { t_prolog "${@}" if have_feat uistrings; then :; else t_echoskip '[test unsupported]' t_epilog "${@}" return fi ${cat} <<-_EOT > ./.tmts1 ? application/mathml+xml mathml _EOT ${cat} <<-_EOT > ./.tmts2 ? x-conference/x-cooltalk ice ?t aga-aga aga ? application/aga-aga aga _EOT ${cat} <<-_EOT > ./.tmts1.mathml nonsense ML _EOT ${cat} <<-_EOT > ./.tmts2.ice Icy, icy road. _EOT printf 'of which the crack is coming soon' > ./.tmtsx.doom printf 'of which the crack is coming soon' > ./.tmtsx.aga printf ' m %s Schub-di-du ~@ ./.tmts1.mathml ~@ ./.tmts2.ice ~@ ./.tmtsx.doom ~@ ./.tmtsx.aga ~. File %s from* type xit ' "${MBOX}" "${MBOX}" | ${MAILX} ${ARGS} \ -Smimetypes-load-control=f=./.tmts1,f=./.tmts2 \ > ./.tout 2>&1 check_ex0 1-estat ${cat} "${MBOX}" >> ./.tout check 1 - ./.tout '919615295 2440' echo type | ${MAILX} ${ARGS} -R \ -Smimetypes-load-control=f=./.tmts1,f=./.tmts3 \ -f "${MBOX}" >> ./.tout 2>&1 check 2 0 ./.tout '3998003232 3633' t_epilog "${@}" } # }}} # Around state machine, after basics {{{ t_alternates() { t_prolog "${@}" ${cat} <<- '__EOT' | ${MAILX} ${ARGS} -Smta=test://"$MBOX" > ./.tall 2>&1 commandalias x echo '$?/$^ERRNAME' commandalias y echo '$?/$^ERRNAME <$rv>' echo --0 alternates;x alternates a1@b1 a2@b2 a3@b3;x alternates;x vput alternates rv;y echo --1 unalternates a2@b2 vput alternates rv;y unalternates a3@b3 vput alternates rv;y unalternates a1@b1 vput alternates rv;y echo --2 unalternates * alternates a1@b1 a2@b2 a3@b3 unalternates a3@b3 vput alternates rv;y unalternates a2@b2 vput alternates rv;y unalternates a1@b1 vput alternates rv;y echo --3 alternates a1@b1 a2@b2 a3@b3 unalternates a1@b1 vput alternates rv;y unalternates a2@b2 vput alternates rv;y unalternates a3@b3 vput alternates rv;y echo --4 unalternates * alternates a1@b1 a2@b2 a3@b3 unalternates * vput alternates rv;y echo --5 unalternates * alternates a1@b1 a1@c1 a1@d1 a2@b2 a3@b3 a3@c3 a3@d3 m a1@b1 a1@c1 a1@d1 ~s all alternates, only a1@b1 remains ~c a2@b2 ~b a3@b3 a3@c3 a3@d3 ~r - '_EOT' This body is! This also body is!! _EOT ~. echo --6 unalternates * alternates a1@b1 a1@c1 a2@b2 a3@b3 m a1@b1 a1@c1 a1@d1 ~s a1@b1 a1@d1, and a3@c3 a3@d3 remain ~c a2@b2 ~b a3@b3 a3@c3 a3@d3 ~r - '_EOT' This body2 is! _EOT ~. echo --7 alternates a1@b1 a2@b2 a3; set allnet m a1@b1 a1@c1 a1@d1 ~s all alternates via allnet, only a1@b1 remains ~c a2@b2 ~b a3@b3 a3@c3 a3@d3 ~r - '_EOT' This body3 is! _EOT ~. echo --10 unalternates * alternates a1@b1;x vput alternates rv;y alternates a2@b2;x vput alternates rv;y alternates a3@b3;x vput alternates rv;y alternates a4@b4;x vput alternates rv;y unalternates * vput alternates rv;y echo --11 set posix alternates a1@b1 a2@b2;x vput alternates rv;y alternates a3@b3 a4@b4;x vput alternates rv;y __EOT check 1 0 "${MBOX}" '3901995195 542' if have_feat uistrings; then check 2 - .tall '1878598364 505' else t_echoskip '2:[test unsupported]' fi # Automatic alternates, also from command line (freezing etc.) ${rm} "${MBOX}" ${cat} <<- __EOT > ./.tin From trouble-report@desy Wed Jun 6 20:19:28 2018 Date: Wed, 06 Jun 2018 19:58:02 +0200 From: a@b.org, b@b.org, c@c.org Sender: a@b.org To: b@b.org Cc: a@b.org, c@c.org Subject: test Message-ID: <20180606175802.dw-cn%a@b.org> sultry __EOT printf '# reply !h b@b.org a@b.org b@b.org c@c.org my body !. ' | ${MAILX} ${ARGS} -Smta=test://"$MBOX" -Sescape=! \ -S from=a@b.org,b@b.org,c@c.org -S sender=a@b.org \ -Rf ./.tin > ./.tall 2>&1 check 3 0 "${MBOX}" '3184203976 265' check 4 - .tall '4294967295 0' # same, per command printf '# set from=a@b.org,b@b.org,c@c.org sender=a@b.org reply !h b@b.org a@b.org b@b.org c@c.org my body !. ' | ${MAILX} ${ARGS} -Smta=test://"$MBOX" -Sescape=! \ -Rf ./.tin > ./.tall 2>&1 check 5 0 "${MBOX}" '98184290 530' check 6 - .tall '4294967295 0' # And more, with/out -r # TODO -r should be the Sender:, which should automatically propagate to # TODO From: if possible and/or necessary. It should be possible to # TODO suppres -r stuff from From: and Sender:, but fallback to special -r # TODO arg as appropriate. # TODO For now we are a bit messy ${rm} "${MBOX}" ./.tall 2>&1 check 7 0 "${MBOX}" '2052716617 201' check 8 - .tout '2052716617 201' check 9 - .tall '4294967295 0' ./.tall 2>&1 check 10 0 "${MBOX}" '3213404599 382' check 11 - .tout '3213404599 382' check 12 - .tall '4294967295 0' ./.tall 2>&1 check 13 0 "${MBOX}" '337984804 609' check 14 - .tall '4294967295 0' t_epilog "${@}" } t_quote_a_cmd_escapes() { # quote and cmd escapes because this (since Mail times) is worked in the # big collect() monster of functions t_prolog "${@}" echo 'included file' > ./.ttxt ${cat} <<-_EOT > ./.tmbox From neverneverland Sun Jul 23 13:46:25 2017 Subject: Bugstop: five miles out 1 Reply-To: mister originator1 From: mister originator1 To: bugstop-commit@five.miles.out Cc: is1@a.list In-reply-to: <20170719111113.bkcMz%laber1@backe.eu> Date: Wed, 19 Jul 2017 09:22:57 -0400 Message-Id: <20170719132257.766AF781267-1@originator> Status: RO That's appalling, I. From neverneverland Sun Jul 23 13:47:25 2017 Subject: Bugstop: five miles out 2 Reply-To: mister originator2 From: mister originator2 To: bugstop-commit@five.miles.out Cc: is2@a.list In-reply-to: <20170719111113.bkcMz%laber2@backe.eu> Date: Wed, 19 Jul 2017 09:23:57 -0400 Message-Id: <20170719132257.766AF781267-2@originator> Status: RO That's appalling, II. From neverneverland Sun Jul 23 13:48:25 2017 Subject: Bugstop: five miles out 3 Reply-To: mister originator3 From: mister originator3 To: bugstop-commit@five.miles.out Cc: is3@a.list In-reply-to: <20170719111113.bkcMz%laber3@backe.eu> Date: Wed, 19 Jul 2017 09:24:57 -0400 Message-Id: <20170719132257.766AF781267-3@originator> Status: RO That's appalling, III. _EOT printf '# set indentprefix=" |" set quote reply 2 !. set quote=noheading reply 2 !. headerpick type retain cc date from message-id reply-to subject to set quote=headers reply 2 !. set quote=allheaders reply 2 !. ' | ${MAILX} ${ARGS} -Smta=test://"$MBOX" -Rf \ -Sescape=! -Sindentprefix=' >' \ ./.tmbox >./.tall 2>&1 check_ex0 1-estat ${cat} ./.tall >> "${MBOX}" check 1 0 "${MBOX}" '1960457897 2031' # ~@ is tested with other attachment stuff, ~^ is in compose_hooks; we also # have some in compose_edits and digmsg ${rm} "${MBOX}" printf '# set Sign=SignVar sign=signvar DEAD=./.ttxt headerpick type retain Subject reply 2 !!1 Not escaped. And shell test last, right before !.. !: echo 2 only echoed via colon !_ echo 3 only echoed via underscore !< ./.ttxt ! !c 10 added ~c c 11 next ~d / $DEAD !d 12: ~F !F 13: ~F 1 3 !F 1 3 14: ~f (headerpick: subject) !f 15: ~f 1 !f 1 16, 17: ~I Sign, ~i Sign !I Sign !i Sign 18: ~M !M 19: ~M 1 !M 1 20: ~m !m 21: ~m 3 !m 3 28-32: ~Q; 28: ~Q !Q 29: ~Q 1 3 !Q 1 3 set quote !:set quote 30: ~Q !Q 31: ~Q 1 3 !Q 1 3 set quote-inject-head quote-inject-tail indentprefix !:wysh set quote-inject-head=%%a quote-inject-tail=--%%r 32: ~Q !Q unset quote stuff !:unset quote quote-inject-head quote-inject-tail 22: ~R ./.ttxt !R ./.ttxt 23: ~r ./.ttxt !r ./.ttxt 24: ~s this new subject !s 24 did new ~s ubject !t 25 added ~t o 26: ~U !U 27: ~U 1 !U 1 and i ~w rite this out to ./.tmsg !w ./.tmsg !:wysh set x=$escape;set escape=~ ~!echo shell command output ~:wysh set escape=$x !. ' | ${MAILX} ${ARGS} -Smta=test://"$MBOX" -Rf \ -Sescape=! -Sindentprefix=' |' \ ./.tmbox >./.tall 2>&1 check_ex0 2-estat ${cat} ./.tall >> "${MBOX}" check 2 - "${MBOX}" '209404720 4092' check 3 - ./.tmsg '2771314896 3186' # Simple return/error value after *expandaddr* failure test printf 'body !:echo --one !s This a new subject is !:set expandaddr=-name !t two@to.invalid !:echo $?/$^ERRNAME !:echo --two !c no-name-allowed !:echo $?/$^ERRNAME !c one@cc.invalid !:echo $?/$^ERRNAME !:echo --three !:alias abcc one@bcc.invalid !b abcc !:echo $?/$^ERRNAME !:set expandaddr=+addr !b abcc !:echo $!/$?/$^ERRNAME !. ' | ${MAILX} ${ARGS} -Smta=test://"$MBOX" \ -Sescape=! \ -s testsub one@to.invalid >./.tall 2>&1 check 4 0 "${MBOX}" '3995224952 4293' if have_feat uistrings; then check 5 - ./.tall '2336041127 212' else check 5 - ./.tall '1818580177 59' fi t_epilog "${@}" } t_compose_edits() { # XXX very rudimentary # after: t_quote_a_cmd_escapes t_prolog "${@}" ${cat} <<-_EOT > ./.ted.sh #!${SHELL} ${cat} <<-__EOT > \${1} Fcc: .tout1 To: Fcc: .tout2 Subject: Fcc test 1 Fcc: .tout3 A body __EOT exit 0 _EOT chmod 0755 .ted.sh # > All these are in-a-row! printf 'mail ./.tout\n~s This subject is\nThis body is\n~.' | ${MAILX} ${ARGS} -Seditheaders >./.tall 2>&1 check 1 0 ./.tout '3993703854 127' check 2 - ./.tall '4294967295 0' ${mv} ./.tall ./.tout printf 'mail ./.tout\n~s This subject is\nThis body is\n~e\n~.' | ${MAILX} ${ARGS} -Seditheaders -SEDITOR=./.ted.sh >./.tall 2>&1 check 3 0 ./.tout1 '285981670 116' check 4 - ./.tout2 '285981670 116' check 5 - ./.tout3 '285981670 116' check 6 - ./.tout '4294967295 0' check 7 - ./.tall '4294967295 0' ${rm} ./.tout1 ./.tout2 ./.tout3 # t_compose_hooks will test ~^ at edge ${mv} ./.tout ./.tout1 ${mv} ./.tall ./.tout2 printf '# mail ./.tout\n!s This subject is\nThis body is !^header list !^header list fcc !^header show fcc !^header remove to !^header insert fcc ./.tout !^header insert fcc .tout1 !^header insert fcc ./.tout2 !^header list !^header show fcc !^header remove-at fcc 2 !^header remove-at fcc 2 !^header show fcc !^head remove fcc !^header show fcc !^header insert fcc ./.tout !^header show fcc !^header list !. ' | ${MAILX} ${ARGS} -Sescape=! >./.tall 2>&1 check 8 0 ./.tout '3993703854 127' check 9 - ./.tout1 '4294967295 0' check 10 - ./.tout2 '4294967295 0' check 11 - ./.tall '4280910245 300' # < No longer in-a-row ${cat} <<-_EOT | ${MAILX} ${ARGS} -t >./.tall 2>&1 Fcc: .ttout Subject: Fcc via -t test My body _EOT check 12 0 ./.ttout '1289478830 122' check 13 - ./.tall '4294967295 0' # This test assumes code of `^' and `digmsg' is shared: see t_digmsg() t_epilog "${@}" } t_digmsg() { # XXX rudimentary t_prolog "${@}" printf '# mail ./.tout\n!s This subject is\nThis body is !:echo --one !:digmsg create - - !:digmsg - header list !:digmsg - header show subject !:digmsg - header show to !:digmsg - header remove to !:digmsg - header list !:digmsg - header show to !:digmsg remove - !:echo --two !:digmsg create - !:digmsg - header list; readall x; echon "<$x>"; !:digmsg - header show subject;readall x;echon "<$x>";; !:digmsg remove - !:echo --three !: # nothing here as is comment !^header insert fcc ./.tbox !:echo --four !:digmsg create - - !:digmsg - header list !:digmsg - header show fcc !:echo --five !^head remove fcc !:echo --six !:digmsg - header list !:digmsg - header show fcc !:digmsg - header insert fcc ./.tfcc !:echo --seven !:digmsg remove - !:echo bye !. echo --hello again File ./.tfcc echo --one digmsg create 1 - digmsg 1 header list digmsg 1 header show subject echo --two ! : > ./.tempty File ./.tempty echo --three digmsg 1 header list; echo $?/$^ERRNAME digmsg create -; echo $?/$^ERRNAME echo ========== ! %s ./.tfcc > ./.tcat ! %s "s/This subject is/There subject was/" < ./.tfcc >> ./.tcat File ./.tcat mail nowhere@exam.ple !:echo ===1 !:digmsg create -; echo $?/$^ERRNAME;\\ digmsg create 1; echo $?/$^ERRNAME;\\ digmsg create 2; echo $?/$^ERRNAME !:echo ===2.1 !:digmsg - h l;echo $?/$^ERRNAME;readall d;echo "$?/$^ERRNAME <$d>" !:echo =2.2 !:digmsg 1 h l;echo $?/$^ERRNAME;readall d;echo "$?/$^ERRNAME <$d>" !:echo =2.3 !^ h l !:echo =2.4 !:digmsg 2 h l;echo $?/$^ERRNAME;readall d;echo "$?/$^ERRNAME <$d>" !:echo ===3.1 !:digmsg - h s to;echo $?/$^ERRNAME;readall d;echo "$?/$^ERRNAME <$d>" !:echo =3.2 !:digmsg 1 h s subject;echo $?/$^ERRNAME;readall d;echo "$?/$^ERRNAME <$d>" !:echo =3.3 !^ h s to !:echo =3.4 !:digmsg 2 h s subject;echo $?/$^ERRNAME;readall d;echo "$?/$^ERRNAME <$d>" !:echo ==4.1 !:digmsg remove -; echo $?/$^ERRNAME;\\ digmsg remove 1; echo $?/$^ERRNAME;\\ digmsg remove 2; echo $?/$^ERRNAME; !x echo ======= new game new fun! mail one@to.invalid !s hossa !:set expandaddr=-name !:echo -oneo !^ header insert to two@to.invalid !:echo $?/$^ERRNAME !:echo --two !^ header insert cc no-name-allowed !:echo $?/$^ERRNAME !^ header insert cc one@cc.invalid !:echo $?/$^ERRNAME !:echo --three !:alias abcc one@bcc.invalid !^ header insert bcc abcc !:echo $?/$^ERRNAME !:set expandaddr=+addr !^ header insert bcc abcc !:echo $!/$?/$^ERRNAME !. echo --bye ' "${cat}" "${sed}" | ${MAILX} ${ARGS} -Smta=test://"$MBOX" -Sescape=! >./.tall 2>&1 check 1 0 "$MBOX" '665881681 179' if have_feat uistrings; then check 2 - ./.tall '2554217728 1366' else check 2 - ./.tall '121327235 1093' fi check 3 - ./.tfcc '3993703854 127' check 4 - ./.tempty '4294967295 0' check 5 - ./.tcat '2157992522 256' t_epilog "${@}" } t_on_main_loop_tick() { t_prolog "${@}" printf '# echo hello; set i=1 define bla { echo bla1 echo bla2 } define omlt { echo in omlt: $i vput vexpr i + 1 $i } echo one set on-main-loop-tick=omlt echo two echo three echo calling bla;call bla echo four echo --bye;xit' | ${MAILX} ${ARGS} -Smta=test://"$MBOX" -Sescape=! >./.tall 2>&1 check 1 0 ./.tall '367031402 108' t_epilog "${@}" } t_on_program_exit() { t_prolog "${@}" ${MAILX} ${ARGS} \ -X 'define x {' -X 'echo jay' -X '}' -X x -Son-program-exit=x \ > ./.tall 2>&1 check 1 0 ./.tall '2820891503 4' ${MAILX} ${ARGS} \ -X 'define x {' -X 'echo jay' -X '}' -X q -Son-program-exit=x \ > ./.tall 2>&1 check 2 0 ./.tall '2820891503 4' ./.tall 2>&1 check 3 0 ./.tall '2820891503 4' ./.tall 2>&1 check 4 0 ./.tall '2820891503 4' check 5 - "$MBOX" '561900352 118' t_epilog "${@}" } # }}} # Heavy use of/rely on state machine (behaviour) and basics {{{ t_compose_hooks() { # {{{ TODO monster t_prolog "${@}" if have_feat uistrings && have_feat cmd-csop && have_feat cmd-vexpr; then :; else t_echoskip '[test unsupported]' t_epilog "${@}" return fi (echo line one&&echo line two&&echo line three) > ./.treadctl (echo echo four&&echo echo five&&echo echo six) > ./.tattach ${cat} <<'__EOT__' > ./.trc define bail { echoerr "Failed: $1. Bailing out"; echo "~x"; xit } define xerr { vput csop es substr "$1" 0 1 if [ "$es" != 2 ] xcall bail "$2" end } define read_mline_res { read hl; wysh set len=$? es=$! en=$^ERRNAME;\ echo $len/$es/$^ERRNAME: $hl if [ $es -ne $^ERR-NONE ] xcall bail read_mline_res elif [ $len -ne 0 ] \xcall read_mline_res end } define ins_addr { wysh set xh=$1 echo "~^header list"; read hl; echo $hl;\ call xerr "$hl" "in_addr ($xh) 0-1" echo "~^header insert $xh diet <$xh@exam.ple> spliced";\ read es; echo $es; call xerr "$es" "ins_addr $xh 1-1" echo "~^header insert $xh <${xh}2@exam.ple>";\ read es; echo $es; call xerr "$es" "ins_addr $xh 1-2" echo "~^header insert $xh ${xh}3@exam.ple";\ read es; echo $es; call xerr "$es" "ins_addr $xh 1-3" echo "~^header list $xh"; read hl; echo $hl;\ call xerr "$hl" "ins_addr $xh 1-4" echo "~^header show $xh"; read es; call xerr $es "ins_addr $xh 1-5" call read_mline_res if [ "$t_remove" == "" ] return end echo "~^header remove $xh"; read es; call xerr $es "ins_addr $xh 2-1" echo "~^header remove $xh"; read es; vput csop es substr $es 0 3 if [ $es != 501 ] xcall bail "ins_addr $xh 2-2" end echo "~^header list $xh"; read es; vput csop es substr $es 0 3 if [ $es != 501 ] xcall bail "ins_addr $xh 2-3" end echo "~^header show $xh"; read es; vput csop es substr $es 0 3 if [ $es != 501 ] xcall bail "ins_addr $xh 2-4" end # echo "~^header insert $xh diet <$xh@exam.ple> spliced";\ read es; echo $es; call xerr "$es" "ins_addr $xh 3-1" echo "~^header insert $xh <${xh}2@exam.ple>";\ read es; echo $es; call xerr "$es" "ins_addr $xh 3-2" echo "~^header insert $xh ${xh}3@exam.ple";\ read es; echo $es; call xerr "$es" "ins_addr $xh 3-3" echo "~^header list $xh"; read hl; echo $hl;\ call xerr "$hl" "ins_addr $xh 3-4" echo "~^header show $xh"; read es; call xerr $es "ins_addr $xh 3-5" call read_mline_res echo "~^header remove-at $xh 1"; read es;\ call xerr $es "ins_addr $xh 3-6" echo "~^header remove-at $xh 1"; read es;\ call xerr $es "ins_addr $xh 3-7" echo "~^header remove-at $xh 1"; read es;\ call xerr $es "ins_addr $xh 3-8" echo "~^header remove-at $xh 1"; read es;\ vput csop es substr $es 0 3 if [ $es != 501 ] xcall bail "ins_addr $xh 3-9" end echo "~^header remove-at $xh T"; read es;\ vput csop es substr $es 0 3 if [ $es != 505 ] xcall bail "ins_addr $xh 3-10" end echo "~^header list $xh"; read es;\ vput csop es substr $es 0 3 if [ $es != 501 ] xcall bail "ins_addr $xh 3-11" end echo "~^header show $xh"; read es;\ vput csop es substr $es 0 3 if [ $es != 501 ] xcall bail "ins_addr $xh 3-12" end # echo "~^header insert $xh diet <$xh@exam.ple> spliced";\ read es; echo $es; call xerr "$es" "ins_addr $xh 4-1" echo "~^header insert $xh <${xh}2@exam.ple> (comment) \"Quot(e)d\"";\ read es; echo $es; call xerr "$es" "ins_addr $xh 4-2" echo "~^header insert $xh ${xh}3@exam.ple";\ read es; echo $es; call xerr "$es" "ins_addr $xh 4-3" echo "~^header list $xh"; read hl; echo $hl;\ call xerr "$hl" "header list $xh 3-4" echo "~^header show $xh"; read es; call xerr $es "ins_addr $xh 4-5" call read_mline_res echo "~^header remove-at $xh 3"; read es;\ call xerr $es "ins_addr $xh 4-6" echo "~^header remove-at $xh 2"; read es;\ call xerr $es "ins_addr $xh 4-7" echo "~^header remove-at $xh 1"; read es;\ call xerr $es "ins_addr $xh 4-8" echo "~^header remove-at $xh 1"; read es;\ vput csop es substr $es 0 3 if [ $es != 501 ] xcall bail "ins_addr $xh 4-9" end echo "~^header remove-at $xh T"; read es;\ vput csop es substr $es 0 3 if [ $es != 505 ] xcall bail "ins_addr $xh 4-10" end echo "~^header list $xh"; read es;\ vput csop es substr $es 0 3 if [ $es != 501 ] xcall bail "ins_addr $xh 4-11" end echo "~^header show $xh"; read es;\ vput csop es substr $es 0 3 if [ $es != 501 ] xcall bail "ins_addr $xh 4-12" end } define ins_ref { wysh set xh=$1 mult=$2 echo "~^header list"; read hl; echo $hl;\ call xerr "$hl" "ins_ref ($xh) 0-1" echo "~^header insert $xh <$xh@exam.ple>";\ read es; echo $es; call xerr "$es" "ins_ref $xh 1-1" if [ $mult -ne 0 ] echo "~^header insert $xh <${xh}2@exam.ple>";\ read es; echo $es; call xerr "$es" "ins_ref $xh 1-2" echo "~^header insert $xh ${xh}3@exam.ple";\ read es; echo $es; call xerr "$es" "ins_ref $xh 1-3" else echo "~^header insert $xh <${xh}2@exam.ple>"; read es;\ vput csop es substr $es 0 3 if [ $es != 506 ] xcall bail "ins_ref $xh 1-4" end end echo "~^header list $xh"; read hl; echo $hl;\ call xerr "$hl" "ins_ref $xh 1-5" echo "~^header show $xh"; read es; call xerr $es "ins_ref $xh 1-6" call read_mline_res if [ "$t_remove" == "" ] return end echo "~^header remove $xh"; read es;\ call xerr $es "ins_ref $xh 2-1" echo "~^header remove $xh"; read es;\ vput csop es substr $es 0 3 if [ $es != 501 ] xcall bail "ins_ref $xh 2-2" end echo "~^header list $xh"; read es;\ vput csop es substr $es 0 3 if [ $es != 501 ] xcall bail "$es ins_ref $xh 2-3" end echo "~^header show $xh"; read es;\ vput csop es substr $es 0 3 if [ $es != 501 ] xcall bail "ins_ref $xh 2-4" end # echo "~^header insert $xh <$xh@exam.ple>";\ read es; echo $es; call xerr "$es" "ins_ref $xh 3-1" if [ $mult -ne 0 ] echo "~^header insert $xh <${xh}2@exam.ple>";\ read es; echo $es; call xerr "$es" "ins_ref $xh 3-2" echo "~^header insert $xh ${xh}3@exam.ple";\ read es; echo $es; call xerr "$es" "ins_ref $xh 3-3" end echo "~^header list $xh";\ read hl; echo $hl; call xerr "$hl" "ins_ref $xh 3-4" echo "~^header show $xh";\ read es; call xerr $es "ins_ref $xh 3-5" call read_mline_res echo "~^header remove-at $xh 1"; read es;\ call xerr $es "ins_ref $xh 3-6" if [ $mult -ne 0 ] && [ $xh != subject ] echo "~^header remove-at $xh 1"; read es;\ call xerr $es "ins_ref $xh 3-7" echo "~^header remove-at $xh 1"; read es;\ call xerr $es "ins_ref $xh 3-8" end echo "~^header remove-at $xh 1"; read es;\ vput csop es substr $es 0 3 if [ $es != 501 ] xcall bail "ins_ref $xh 3-9" end echo "~^header remove-at $xh T"; read es;\ vput csop es substr $es 0 3 if [ $es != 505 ] xcall bail "ins_ref $xh 3-10" end echo "~^header show $xh"; read es;\ vput csop es substr $es 0 3 if [ $es != 501 ] xcall bail "ins_ref $xh 3-11" end # echo "~^header insert $xh <$xh@exam.ple> ";\ read es; echo $es; call xerr "$es" "ins_ref $xh 4-1" if [ $mult -ne 0 ] echo "~^header insert $xh <${xh}2@exam.ple> ";\ read es; echo $es; call xerr "$es" "ins_ref $xh 4-2" echo "~^header insert $xh ${xh}3@exam.ple";\ read es; echo $es; call xerr "$es" "ins_ref $xh 4-3" end echo "~^header list $xh"; read hl; echo $hl;\ call xerr "$hl" "ins_ref $xh 4-4" echo "~^header show $xh"; read es; call xerr $es "ins_ref $xh 4-5" call read_mline_res if [ $mult -ne 0 ] && [ $xh != subject ] echo "~^header remove-at $xh 3"; read es;\ call xerr $es "ins_ref $xh 4-6" echo "~^header remove-at $xh 2"; read es;\ call xerr $es "ins_ref $xh 4-7" end echo "~^header remove-at $xh 1"; read es;\ call xerr $es "ins_ref $xh 4-8" echo "~^header remove-at $xh 1"; read es;\ vput csop es substr $es 0 3 if [ $es != 501 ] xcall bail "ins_ref $xh 4-9" end echo "~^header remove-at $xh T"; read es;\ vput csop es substr $es 0 3 if [ $es != 505 ] xcall bail "ins_ref $xh 4-10" end echo "~^header show $xh"; read es;\ vput csop es substr $es 0 3 if [ $es != 501 ] xcall bail "ins_ref $xh 4-11" end } define t_header { echo t_header ENTER # In collect.c order call ins_addr from call ins_ref sender 0 # Not a "ref", but works call ins_addr To call ins_addr cC call ins_addr bCc call ins_addr reply-To call ins_addr mail-Followup-to call ins_ref messAge-id 0 call ins_ref rEfErEncEs 1 call ins_ref in-Reply-to 1 call ins_ref subject 1 # Not a "ref", but works (with tweaks) call ins_addr freeForm1 call ins_addr freeform2 echo "~^header show MAILX-Command"; read es; call xerr $es "t_header 1000" call read_mline_res echo "~^header show MAILX-raw-TO"; read es; call xerr $es "t_header 1001" call read_mline_res echo t_header LEAVE } define t_attach { echo t_attach ENTER echo "~^attachment";\ read hl; echo $hl; vput csop es substr "$hl" 0 3 if [ "$es" != 501 ] xcall bail "attach 0-1" end echo "~^attach attribute ./.treadctl";\ read hl; echo $hl; vput csop es substr "$hl" 0 3 if [ "$es" != 501 ] xcall bail "attach 0-2" end echo "~^attachment attribute-at 1";\ read hl; echo $hl; vput csop es substr "$hl" 0 3 if [ "$es" != 501 ] xcall bail "attach 0-3" end echo "~^attachment insert ./.treadctl=ascii";\ read hl; echo $hl; call xerr "$hl" "attach 1-1" echo "~^attachment list";\ read es; echo $es;call xerr "$es" "attach 1-2" call read_mline_res echo "~^attachment attribute ./.treadctl";\ read es; echo $es;call xerr "$es" "attach 1-3" call read_mline_res echo "~^attachment attribute .treadctl";\ read es; echo $es;call xerr "$es" "attach 1-4" call read_mline_res echo "~^attachment attribute-at 1";\ read es; echo $es;call xerr "$es" "attach 1-5" call read_mline_res echo "~^attachment attribute-set ./.treadctl filename rctl";\ read es; echo $es;call xerr "$es" "attach 1-6" echo "~^attachment attribute-set .treadctl content-description Au";\ read es; echo $es;call xerr "$es" "attach 1-7" echo "~^attachment attribute-set-at 1 content-id <10.du@ich>";\ read es; echo $es;call xerr "$es" "attach 1-8" echo "~^attachment attribute ./.treadctl";\ read es; echo $es;call xerr "$es" "attach 1-9" call read_mline_res echo "~^attachment attribute .treadctl";\ read es; echo $es;call xerr "$es" "attach 1-10" call read_mline_res echo "~^attachment attribute rctl";\ read es; echo $es;call xerr "$es" "attach 1-11" call read_mline_res echo "~^attachment attribute-at 1";\ read es; echo $es;call xerr "$es" "attach 1-12" call read_mline_res # echo "~^attachment insert ./.tattach=latin1";\ read hl; echo $hl; call xerr "$hl" "attach 2-1" echo "~^attachment list";\ read es; echo $es;call xerr "$es" "attach 2-2" call read_mline_res echo "~^attachment attribute ./.tattach";\ read es; echo $es;call xerr "$es" "attach 2-3" call read_mline_res echo "~^attachment attribute .tattach";\ read es; echo $es;call xerr "$es" "attach 2-4" call read_mline_res echo "~^attachment attribute-at 2";\ read es; echo $es;call xerr "$es" "attach 2-5" call read_mline_res echo "~^attachment attribute-set ./.tattach filename tat";\ read es; echo $es;call xerr "$es" "attach 2-6" echo \ "~^attachment attribute-set .tattach content-description Au2";\ read es; echo $es;call xerr "$es" "attach 2-7" echo "~^attachment attribute-set-at 2 content-id <20.du@wir>";\ read es; echo $es;call xerr "$es" "attach 2-8" echo \ "~^attachment attribute-set-at 2 content-type application/x-sh";\ read es; echo $es;call xerr "$es" "attach 2-9" echo "~^attachment attribute ./.tattach";\ read es; echo $es;call xerr "$es" "attach 2-10" call read_mline_res echo "~^attachment attribute .tattach";\ read es; echo $es;call xerr "$es" "attach 2-11" call read_mline_res echo "~^attachment attribute tat";\ read es; echo $es;call xerr "$es" "attach 2-12" call read_mline_res echo "~^attachment attribute-at 2";\ read es; echo $es;call xerr "$es" "attach 2-13" call read_mline_res # if [ "$t_remove" == "" ] return end echo "~^attachment remove ./.treadctl"; read es;\ call xerr $es "attach 3-1" echo "~^attachment remove ./.tattach"; read es;\ call xerr $es "attach 3-2" echo "~^ attachment remove ./.treadctl"; read es;\ vput csop es substr $es 0 3 if [ $es != 501 ] xcall bail "attach 3-3" end echo "~^ attachment remove ./.tattach"; read es;\ vput csop es substr $es 0 3 if [ $es != 501 ] xcall bail "attach 3-4" end echo "~^attachment list"; read es;\ vput csop es substr $es 0 3 if [ $es != 501 ] xcall bail "attach 3-5" end # echo "~^attachment insert ./.tattach=latin1";\ read hl; echo $hl; call xerr "$hl" "attach 4-1" echo "~^attachment insert ./.tattach=latin1";\ read hl; echo $hl; call xerr "$hl" "attach 4-2" echo "~^attachment list";\ read es; echo $es;call xerr "$es" "attach 4-3" call read_mline_res echo "~^ attachment remove .tattach"; read es;\ vput csop es substr $es 0 3 if [ $es != 506 ] xcall bail "attach 4-4 $es" end echo "~^attachment remove-at T"; read es;\ vput csop es substr $es 0 3 if [ $es != 505 ] xcall bail "attach 4-5" end echo "~^attachment remove ./.tattach"; read es;\ call xerr $es "attach 4-6" echo "~^attachment remove ./.tattach"; read es;\ call xerr $es "attach 4-7" echo "~^ attachment remove ./.tattach"; read es;\ vput csop es substr $es 0 3 if [ $es != 501 ] xcall bail "attach 4-8 $es" end echo "~^attachment list"; read es;\ vput csop es substr $es 0 3 if [ $es != 501 ] xcall bail "attach 4-9" end # echo "~^attachment insert ./.tattach=latin1";\ read hl; echo $hl; call xerr "$hl" "attach 5-1" echo "~^attachment insert ./.tattach=latin1";\ read hl; echo $hl; call xerr "$hl" "attach 5-2" echo "~^attachment insert ./.tattach=latin1";\ read hl; echo $hl; call xerr "$hl" "attach 5-3" echo "~^attachment list";\ read es; echo $es;call xerr "$es" "attach 5-4" call read_mline_res echo "~^attachment remove-at 3"; read es;\ call xerr $es "attach 5-5" echo "~^attachment remove-at 3"; read es;\ vput csop es substr $es 0 3 if [ $es != 501 ] xcall bail "attach 5-6" end echo "~^attachment remove-at 2"; read es;\ call xerr $es "attach 5-7" echo "~^attachment remove-at 2"; read es;\ vput csop es substr $es 0 3 if [ $es != 501 ] xcall bail "attach 5-8" end echo "~^attachment remove-at 1"; read es;\ call xerr $es "attach 5-9" echo "~^attachment remove-at 1"; read es;\ vput csop es substr $es 0 3 if [ $es != 501 ] xcall bail "attach 5-10" end echo "~^attachment list"; read es;\ vput csop es substr $es 0 3 if [ $es != 501 ] xcall bail "attach 5-11" end # echo "~^attachment insert ./.tattach=latin1";\ read hl; echo $hl; call xerr "$hl" "attach 6-1" echo "~^attachment insert ./.tattach=latin1";\ read hl; echo $hl; call xerr "$hl" "attach 6-2" echo "~^attachment insert ./.tattach=latin1";\ read hl; echo $hl; call xerr "$hl" "attach 6-3" echo "~^attachment list";\ read es; echo $es;call xerr "$es" "attach 6-4" call read_mline_res echo "~^attachment remove-at 1"; read es;\ call xerr $es "attach 6-5" echo "~^attachment remove-at 1"; read es;\ call xerr $es "attach 6-6" echo "~^attachment remove-at 1"; read es;\ call xerr $es "attach 6-7" echo "~^attachment remove-at 1"; read es;\ vput csop es substr $es 0 3 if [ $es != 501 ] xcall bail "attach 6-8" end echo "~^attachment list"; read es;\ vput csop es substr $es 0 3 if [ $es != 501 ] xcall bail "attach 6-9" end echo t_attach LEAVE } define t_ocs { read ver echo t_ocs call t_header call t_attach } define t_oce { echo on-compose-enter, mailx-command<$mailx-command> alternates alter1@exam.ple alter2@exam.ple alternates set autocc='alter1@exam.ple alter2@exam.ple' echo mailx-from<$mailx-from> mailx-sender<$mailx-sender> echo mailx-subject<$mailx-subject> echo mailx-to<$mailx-to> mailx-cc<$mailx-cc> mailx-bcc<$mailx-bcc> echo mailx-raw-to<$mailx-raw-to> mailx-raw-cc<$mailx-raw-cc> \ mailx-raw-bcc<$mailx-raw-bcc> echo mailx-orig-from<$mailx-orig-from> mailx-orig-to<$mailx-orig-to> \ mailx-orig-cc<$mailx-orig-cc> mailx-orig-bcc<$mailx-orig-bcc> digmsg create - -;echo $?/$!/$^ERRNAME;\ digmsg - header list;\ digmsg remove -;echo $?/$!/$^ERRNAME digmsg create -;echo $?/$!/$^ERRNAME;\ digmsg - header list;readall x;echon $x;\ digmsg remove -;echo $?/$!/$^ERRNAME } define t_ocl { echo on-compose-leave, mailx-command<$mailx-command> vput alternates al eval alternates $al alter3@exam.ple alter4@exam.ple alternates set autobcc='alter3@exam.ple alter4@exam.ple' echo mailx-from<$mailx-from> mailx-sender<$mailx-sender> echo mailx-subject<$mailx-subject> echo mailx-to<$mailx-to> mailx-cc<$mailx-cc> mailx-bcc<$mailx-bcc> echo mailx-raw-to<$mailx-raw-to> mailx-raw-cc<$mailx-raw-cc> \ mailx-raw-bcc<$mailx-raw-bcc> echo mailx-orig-from<$mailx-orig-from> mailx-orig-to<$mailx-orig-to> \ mailx-orig-cc<$mailx-orig-cc> mailx-orig-bcc<$mailx-orig-bcc> digmsg create - -;echo $?/$!/$^ERRNAME;\ digmsg - header list;\ digmsg remove -;echo $?/$!/$^ERRNAME digmsg create -;echo $?/$!/$^ERRNAME;\ digmsg - header list;readall x;echon $x;\ digmsg remove -;echo $?/$!/$^ERRNAME } define t_occ { echo on-compose-cleanup, mailx-command<$mailx-command> unalternates * alternates echo mailx-from<$mailx-from> mailx-sender<$mailx-sender> echo mailx-subject<$mailx-subject> echo mailx-to<$mailx-to> mailx-cc<$mailx-cc> mailx-bcc<$mailx-bcc> echo mailx-raw-to<$mailx-raw-to> mailx-raw-cc<$mailx-raw-cc> \ mailx-raw-bcc<$mailx-raw-bcc> echo mailx-orig-from<$mailx-orig-from> mailx-orig-to<$mailx-orig-to> \ mailx-orig-cc<$mailx-orig-cc> mailx-orig-bcc<$mailx-orig-bcc> # XXX error message variable digmsg create - -;echo $?/$!/$^ERRNAME;\ digmsg - header list;\ digmsg remove -;echo $?/$!/$^ERRNAME # ditto digmsg create -;echo $?/$!/$^ERRNAME;\ digmsg - header list;readall x;echon $x;\ digmsg remove -;echo $?/$!/$^ERRNAME } wysh set on-compose-splice=t_ocs \ on-compose-enter=t_oce on-compose-leave=t_ocl \ on-compose-cleanup=t_occ __EOT__ printf 'm this-goes@nowhere\nbody\n!.\n' | ${MAILX} ${ARGS} -Snomemdebug -Sescape=! -Sstealthmua=noagent \ -X'source ./.trc' -Smta=test://"$MBOX" \ >./.tall 2>&1 ${cat} ./.tall >> "${MBOX}" check 1 0 "${MBOX}" '3199865751 10529' ${rm} "${MBOX}" printf 'm this-goes@nowhere\nbody\n!.\n' | ${MAILX} ${ARGS} -Snomemdebug -Sescape=! -Sstealthmua=noagent \ -St_remove=1 -X'source ./.trc' -Smta=test://"$MBOX" \ >./.tall 2>&1 ${cat} ./.tall >> "${MBOX}" check 2 0 "${MBOX}" '473642362 12743' ## # Some state machine stress, shell compose hook, localopts for hook, etc. # readctl in child. ~r as HERE document ${rm} "${MBOX}" printf 'm ex@am.ple\nbody\n!. echon ${mailx-command}${mailx-subject} echon ${mailx-from}${mailx-sender} echon ${mailx-to}${mailx-cc}${mailx-bcc} echon ${mailx-raw-to}${mailx-raw-cc}${mailx-raw-bcc} echon ${mailx-orig-from}${mailx-orig-to}${mailx-orig-gcc}${mailx-orig-bcc} var t_oce t_ocs t_ocs_sh t_ocl t_occ autocc ' | ${MAILX} ${ARGS} -Snomemdebug -Sescape=! \ -Smta=test://"$MBOX" \ -X' define bail { echoerr "Failed: $1. Bailing out"; echo "~x"; xit } define xerr { vput csop es substr "$1" 0 1 if [ "$es" != 2 ] xcall bail "$2" end } define read_mline_res { read hl; wysh set len=$? es=$! en=$^ERRNAME;\ echo $len/$es/$^ERRNAME: $hl if [ $es -ne $^ERR-NONE ] xcall bail read_mline_res elif [ $len -ne 0 ] \xcall read_mline_res end } define _work { vput vexpr i + 1 "$2" if [ $i -lt 111 ] vput vexpr j % $i 10 if [ $j -ne 0 ] set j=xcall else echon "$i.. " set j=call end eval \\$j _work $1 $i return $? end vput vexpr i + $i "$1" return $i } define _read { wysh set line; read line;wysh set es=$? en=$^ERRNAME ;\ echo read:$es/$en: $line if [ "${es}" -ne -1 ] xcall _read end readctl remove $cwd/.treadctl; echo readctl remove:$?/$^ERRNAME } define t_ocs { read ver echo t_ocs echo "~^header list"; read hl; echo $hl;\ vput csop es substr "$hl" 0 1 if [ "$es" != 2 ] xcall bail "header list" endif # call _work 1; echo $? echo "~^header insert cc splicy diet spliced";\ read es; echo $es; vput csop es substr "$es" 0 1 if [ "$es" != 2 ] xcall bail "be diet" endif echo "~^header insert cc ";\ read es; echo $es; vput csop es substr "$es" 0 1 if [ "$es" != 2 ] xcall bail "be diet2" endif # call _work 2; echo $? echo "~^header insert bcc juicy juice spliced";\ read es; echo $es;vput csop es substr "$es" 0 1 if [ "$es" != 2 ] xcall bail "be juicy" endif echo "~^header insert bcc juice2@exam.ple";\ read es; echo $es;vput csop es substr "$es" 0 1 if [ "$es" != 2 ] xcall bail "be juicy2" endif echo "~^header insert bcc juice3 ";\ read es; echo $es;vput csop es substr "$es" 0 1 if [ "$es" != 2 ] xcall bail "be juicy3" endif echo "~^header insert bcc juice4@exam.ple";\ read es; echo $es;vput csop es substr "$es" 0 1 if [ "$es" != 2 ] xcall bail "be juicy4" endif # echo "~^header remove-at bcc 3";\ read es; echo $es;vput csop es substr "$es" 0 1 if [ "$es" != 2 ] xcall bail "remove juicy5" endif echo "~^header remove-at bcc 2";\ read es; echo $es;vput csop es substr "$es" 0 1 if [ "$es" != 2 ] xcall bail "remove juicy6" endif echo "~^header remove-at bcc 3";\ read es; echo $es;vput csop es substr "$es" 0 3 if [ "$es" != 501 ] xcall bail "failed to remove-at" endif # Add duplicates which ought to be removed! echo "~^header insert bcc juice4@exam.ple";\ read es; echo $es;vput csop es substr "$es" 0 1 if [ "$es" != 2 ] xcall bail "be juicy4-1" endif echo "~^header insert bcc juice4@exam.ple";\ read es; echo $es;vput csop es substr "$es" 0 1 if [ "$es" != 2 ] xcall bail "be juicy4-2" endif echo "~^header insert bcc juice4@exam.ple";\ read es; echo $es;vput csop es substr "$es" 0 1 if [ "$es" != 2 ] xcall bail "be juicy4-3" endif echo "~:set t_ocs" # call _work 3; echo $? echo "~r - '__EOT'" vput ! i echo just knock if you can hear me;\ i=0;\ while [ $i -lt 24 ]; do printf "%s " $i; i=`expr $i + 1`; done;\ echo relax echon shell-cmd says $?/$^ERRNAME: $i echo "~x will not become interpreted, we are reading until __EOT" echo "__EOT" read r_status; echo "~~r status output: $r_status" echo "~:echo $? $! $^ERRNAME" read r_status echo "~~r status from parent: $r_status" # call _work 4; echo $? vput cwd cwd;echo cwd:$? readctl create $cwd/.treadctl ;echo readctl:$?/$^ERRNAME;\ call _read # call _work 5; echo $? echo "~^header show MAILX-Command"; read es;\ call xerr $es "t_header 1000"; call read_mline_res echo "~^header show MAILX-raw-TO"; read es;\ call xerr $es "t_header 1001"; xcall read_mline_res echoerr IT IS WRONG IF YOU SEE THIS } define t_oce { echo on-compose-enter, mailx-command<$mailx-command> set t_oce autobcc=oce@exam.ple alternates alter1@exam.ple alter2@exam.ple alternates echo mailx-from<$mailx-from> mailx-sender<$mailx-sender> echo mailx-subject<$mailx-subject> echo mailx-to<$mailx-to> mailx-cc<$mailx-cc> mailx-bcc<$mailx-bcc> echo mailx-raw-to<$mailx-raw-to> mailx-raw-cc<$mailx-raw-cc> \ mailx-raw-bcc<$mailx-raw-bcc> echo mailx-orig-from<$mailx-orig-from> \ mailx-orig-to<$mailx-orig-to> \ mailx-orig-cc<$mailx-orig-cc> mailx-orig-bcc<$mailx-orig-bcc> } define t_ocl { echo on-compose-leave, mailx-command<$mailx-command> set t_ocl autocc=ocl@exam.ple unalternates * alternates alter3@exam.ple alter4@exam.ple alternates echo mailx-from<$mailx-from> mailx-sender<$mailx-sender> echo mailx-subject<$mailx-subject> echo mailx-to<$mailx-to> mailx-cc<$mailx-cc> mailx-bcc<$mailx-bcc> echo mailx-raw-to<$mailx-raw-to> mailx-raw-cc<$mailx-raw-cc> \ mailx-raw-bcc<$mailx-raw-bcc> echo mailx-orig-from<$mailx-orig-from> \ mailx-orig-to<$mailx-orig-to> \ mailx-orig-cc<$mailx-orig-cc> mailx-orig-bcc<$mailx-orig-bcc> } define t_occ { echo on-compose-cleanup, mailx-command<$mailx-command> set t_occ autocc=occ@exam.ple unalternates * alternates echo mailx-from<$mailx-from> mailx-sender<$mailx-sender> echo mailx-subject<$mailx-subject> echo mailx-to<$mailx-to> mailx-cc<$mailx-cc> mailx-bcc<$mailx-bcc> echo mailx-raw-to<$mailx-raw-to> mailx-raw-cc<$mailx-raw-cc> \ mailx-raw-bcc<$mailx-raw-bcc> echo mailx-orig-from<$mailx-orig-from> \ mailx-orig-to<$mailx-orig-to> \ mailx-orig-cc<$mailx-orig-cc> mailx-orig-bcc<$mailx-orig-bcc> } wysh set on-compose-splice=t_ocs \ on-compose-splice-shell="read ver;echo t_ocs-shell;\ echo \"~t shell@exam.ple\"; echo \"~:set t_ocs_sh\"" \ on-compose-enter=t_oce on-compose-leave=t_ocl \ on-compose-cleanup=t_occ ' > ./.tnotes 2>&1 check_ex0 3-estat ${cat} ./.tnotes >> "${MBOX}" check 3 - "${MBOX}" '3986011319 2437' # Reply, forward, resend, Resend ${rm} "${MBOX}" printf '# set from="f1@z m t1@z b1 !. set from="du " stealthmua=noagent m t2@z b2 !. ' | ${MAILX} ${ARGS} -Smta=test://"$MBOX" -Snomemdebug -Sescape=! printf ' echo start: $? $! $^ERRNAME File %s echo File: $? $! $^ERRNAME;echo;echo reply 1 this is content of reply 1 !. echo reply 1: $? $! $^ERRNAME;echo;echo Reply 1 2 this is content of Reply 1 2 !. echo Reply 1 2: $? $! $^ERRNAME;echo;echo forward 1 fwdex@am.ple this is content of forward 1 !. echo forward 1: $? $! $^ERRNAME;echo;echo wysh set forward-inject-head=$'"'"'-- \\ forward (%%a)(%%d)(%%f)(%%i)(%%n)(%%r) --\\n'"'"' wysh set forward-inject-tail=$'"'"'-- \\ end of forward (%%i) --\\n'"'"' forward 2 fwdex@am.ple this is content of forward 2 !. echo forward 2: $? $! $^ERRNAME;echo;echo set showname forward 2 fwdex2@am.ple this is content of forward 2, 2nd, with showname set !. echo forward 2, 2nd: $? $! $^ERRNAME;echo;echo resend 1 2 resendex@am.ple echo resend 1 2: $? $! $^ERRNAME;echo;echo Resend 1 2 Resendex@am.ple echo Resend 1 2: $? $! $^ERRNAME;echo;echo ' "${MBOX}" | ${MAILX} ${ARGS} -Snomemdebug -Sescape=! -Sfullnames \ -Smta=test://"$MBOX" \ -X' define bail { echoerr "Failed: $1. Bailing out"; echo "~x"; xit } define xerr { vput csop es substr "$1" 0 1 if [ "$es" != 2 ] xcall bail "$2" end } define read_mline_res { read hl; wysh set len=$? es=$! en=$^ERRNAME;\ echo mline_res:$len/$es/$^ERRNAME: $hl if [ $es -ne $^ERR-NONE ] xcall bail read_mline_res elif [ $len -ne 0 ] \xcall read_mline_res end } define work_hl { echo "~^header show $1"; read es;\ call xerr $es "work_hl $1"; echo $1; call read_mline_res if [ $# -gt 1 ] shift xcall work_hl "$@" end } define t_ocs { read ver echo t_ocs version $ver echo "~^header list"; read hl; echo $hl;\ echoerr the header list is $hl;\ call xerr "$hl" "header list" eval vpospar set $hl shift xcall work_hl "$@" echoerr IT IS WRONG IF YOU SEE THIS } define t_oce { echo on-XY-enter, mailx-command<$mailx-command> set t_oce autobcc=oce@exam.ple echo mailx-from<$mailx-from> mailx-sender<$mailx-sender> echo mailx-subject<$mailx-subject> echo mailx-to<$mailx-to> mailx-cc<$mailx-cc> mailx-bcc<$mailx-bcc> echo mailx-raw-to<$mailx-raw-to> mailx-raw-cc<$mailx-raw-cc> \ mailx-raw-bcc<$mailx-raw-bcc> echo mailx-orig-from<$mailx-orig-from> \ mailx-orig-to<$mailx-orig-to> \ mailx-orig-cc<$mailx-orig-cc> mailx-orig-bcc<$mailx-orig-bcc> } define t_ocl { echo on-XY-leave, mailx-command<$mailx-command> set t_ocl autocc=ocl@exam.ple echo mailx-from<$mailx-from> mailx-sender<$mailx-sender> echo mailx-subject<$mailx-subject> echo mailx-to<$mailx-to> mailx-cc<$mailx-cc> mailx-bcc<$mailx-bcc> echo mailx-raw-to<$mailx-raw-to> mailx-raw-cc<$mailx-raw-cc> \ mailx-raw-bcc<$mailx-raw-bcc> echo mailx-orig-from<$mailx-orig-from> \ mailx-orig-to<$mailx-orig-to> \ mailx-orig-cc<$mailx-orig-cc> mailx-orig-bcc<$mailx-orig-bcc> } define t_occ { echo on-XY-cleanup, mailx-command<$mailx-command> set t_occ autocc=occ@exam.ple echo mailx-from<$mailx-from> mailx-sender<$mailx-sender> echo mailx-subject<$mailx-subject> echo mailx-to<$mailx-to> mailx-cc<$mailx-cc> mailx-bcc<$mailx-bcc> echo mailx-raw-to<$mailx-raw-to> mailx-raw-cc<$mailx-raw-cc> \ mailx-raw-bcc<$mailx-raw-bcc> echo mailx-orig-from<$mailx-orig-from> \ mailx-orig-to<$mailx-orig-to> \ mailx-orig-cc<$mailx-orig-cc> mailx-orig-bcc<$mailx-orig-bcc> } wysh set on-compose-splice=t_ocs \ on-compose-enter=t_oce on-compose-leave=t_ocl \ on-compose-cleanup=t_occ \ on-resend-enter=t_oce on-resend-cleanup=t_occ ' > ./.tnotes 2>&1 check_ex0 4-estat ${cat} ./.tnotes >> "${MBOX}" check 4 - "${MBOX}" '1818661134 11250' t_epilog "${@}" } # }}} t_mass_recipients() { t_prolog "${@}" if have_feat cmd-vexpr; then :; else t_echoskip '[test unsupported]' t_epilog "${@}" return fi ${cat} <<'__EOT__' > ./.trc define bail { echoerr "Failed: $1. Bailing out"; echo "~x"; xit } define ins_addr { wysh set nr=$1 hn=$2 echo "~$hn $hn$nr@$hn"; echo '~:echo $?'; read es if [ "$es" -ne 0 ] xcall bail "ins_addr $hn 1-$nr" end vput vexpr nr + $nr 1 if [ "$nr" -le "$maximum" ] xcall ins_addr $nr $hn end } define bld_alter { wysh set nr=$1 hn=$2 alternates $hn$nr@$hn vput vexpr nr + $nr 2 if [ "$nr" -le "$maximum" ] xcall bld_alter $nr $hn end } define t_ocs { read ver call ins_addr 1 t call ins_addr 1 c call ins_addr 1 b } define t_ocl { if [ "$t_remove" != '' ] call bld_alter 1 t call bld_alter 2 c end } set on-compose-splice=t_ocs on-compose-leave=t_ocl __EOT__ printf 'm this-goes@nowhere\nbody\n!.\n' | ${MAILX} ${ARGS} -Snomemdebug -Sescape=! -Sstealthmua=noagent \ -X'source ./.trc' -Smta=test://"$MBOX" -Smaximum=${LOOPS_MAX} \ >./.tall 2>&1 check_ex0 1-estat ${cat} ./.tall >> "${MBOX}" if [ ${LOOPS_MAX} -eq ${LOOPS_BIG} ]; then check 1-${LOOPS_BIG} - "${MBOX}" '3835365533 51534' elif [ ${LOOPS_MAX} -eq ${LOOPS_SMALL} ]; then check 1-${LOOPS_SMALL} - "${MBOX}" '3647549277 4686' fi ${rm} "${MBOX}" printf 'm this-goes@nowhere\nbody\n!.\n' | ${MAILX} ${ARGS} -Snomemdebug -Sescape=! -Sstealthmua=noagent \ -St_remove=1 -X'source ./.trc' -Smta=test://"$MBOX" \ -Smaximum=${LOOPS_MAX} \ >./.tall 2>&1 check_ex0 2-estat ${cat} ./.tall >> "${MBOX}" if [ ${LOOPS_MAX} -eq ${LOOPS_BIG} ]; then check 2-${LOOPS_BIG} - "${MBOX}" '3768249992 34402' elif [ $LOOPS_MAX -eq ${LOOPS_SMALL} ]; then check 2-${LOOPS_SMALL} - "${MBOX}" '4042568441 3170' fi t_epilog "${@}" } t_lreply_futh_rth_etc() { t_prolog "${@}" ${cat} <<-_EOT > ./.tmbox From neverneverland Sun Jul 23 13:46:25 2017 Subject: Bugstop: five miles out 1 Reply-To: mister originator2 , bugstop@five.miles.out From: mister originator To: bugstop-commit@five.miles.out, laber@backe.eu Cc: is@a.list Mail-Followup-To: bugstop@five.miles.out, laber@backe.eu, is@a.list In-reply-to: <20170719111113.bkcMz%laber@backe.eu> Date: Wed, 19 Jul 2017 09:22:57 -0400 Message-Id: <20170719132257.766AF781267@originator> Status: RO > |Sorry, I think I misunderstand something. I would think that That's appalling. From neverneverland Fri Jul 7 22:39:11 2017 Subject: Bugstop: five miles out 2 Reply-To: mister originator2,bugstop@five.miles.out,is@a.list Content-Transfer-Encoding: 7bit From: mister originator To: bugstop-commit@five.miles.out Cc: is@a.list Message-ID: <149945963975.28888.6950788126957753723.reportbug@five.miles.out> Date: Fri, 07 Jul 2017 16:33:59 -0400 Status: R capable of changing back. From neverneverland Fri Jul 7 22:42:00 2017 Subject: Bugstop: five miles out 3 Reply-To: mister originator2 , bugstop@five.miles.out Content-Transfer-Encoding: 7bit From: mister originator To: bugstop-commit@five.miles.out Cc: is@a.list Message-ID: <149945963975.28888.6950788126957753746.reportbug@five.miles.out> Date: Fri, 07 Jul 2017 16:33:59 -0400 List-Post: Status: R are you ready, boots? From neverneverland Sat Aug 19 23:15:00 2017 Subject: Bugstop: five miles out 4 Reply-To: mister originator2 , bugstop@five.miles.out Content-Transfer-Encoding: 7bit From: mister originator To: bugstop@five.miles.out Cc: is@a.list Message-ID: <149945963975.28888.6950788126qtewrqwer.reportbug@five.miles.out> Date: Fri, 07 Jul 2017 16:33:59 -0400 List-Post: Status: R are you ready, boots? _EOT # ${cat} <<-'_EOT' | ${MAILX} ${ARGS} -Sescape=! -Smta=test://"$MBOX" \ -Rf ./.tmbox >> "${MBOX}" 2>&1 define r { wysh set m="This is text of \"reply ${1}." reply 1 2 3 !I m 1". !. !I m 2". !. !I m 3". !. echo -----After reply $1.1 - $1.3: $?/$^ERRNAME } define R { wysh set m="This is text of \"Reply ${1}." eval Reply $2 !I m !I 2 ". !. echo -----After Reply $1.$2: $?/$^ERRNAME } define _Lh { read protover echo '~I m' echo '~I n' echo '".' } define _Ls { wysh set m="This is text of \"Lreply ${1}." on-compose-splice=_Lh n=$2 eval Lreply $2 } define L { # We need two indirections for this test: one for the case that Lreply # fails because of missing recipients: we need to read EOF next, thus # place this in _Ls last; and second for the succeeding cases EOF is # not what these should read, so go over the backside and splice it in! call _Ls "$@" echo -----After Lreply $1.$2: $?/$^ERRNAME } define x { localopts call-fixate yes call r $1 call R $1 1; call R $1 2; call R $1 3; call R $1 4 call L $1 1; call L $1 2; call L $1 3 } define tweak { echo;echo '===== CHANGING === '"$*"' =====';echo eval "$@" } # set from=laber@backe.eu mlist is@a.list call x 1 call tweak set reply-to-honour call x 2 call tweak set followup-to call x 3 call tweak set followup-to-honour call x 4 call tweak mlist bugstop@five.miles.out call x 5 call tweak mlsubscribe bugstop@five.miles.out call x 6 call tweak set recipients-in-cc call x 7 # While here, test that *fullnames* works (also here) set fullnames reply 1 This message should have *fullnames* in the header. !. # Revert call tweak unmlsubscribe bugstop@five.miles.out';' \ set followup-to-add-cc nofullnames call x 8 call tweak mlsubscribe bugstop@five.miles.out call x 9 _EOT check_ex0 1-estat if have_feat uistrings; then check 1 - "${MBOX}" '564303313 39797' else t_echoskip '1:[test unsupported]' fi ## ${cat} <<-_EOT > ./.tmbox From tom@i-i.example Thu Oct 26 03:15:55 2017 Date: Wed, 25 Oct 2017 21:15:46 -0400 From: tom To: Steffen Nurpmeso Cc: tom Subject: Re: xxxx yyyyyyyy configure does not really like a missing zzzzz Message-ID: <20171026011546.GA11643@i-i.example> Reply-To: tom@i-i.example References: <20171025214601.T2pNd%steffen@sdaoden.eu> In-Reply-To: <20171025214601.T2pNd%steffen@sdaoden.eu> Status: R The report's useful :-) _EOT # Let us test In-Reply-To: removal starts a new thread.. # This needs adjustment of *stealthmua* argadd='-Sstealthmua=noagent -Shostname' ${rm} "${MBOX}" printf 'reply 1\nthread\n!.\n' | ${MAILX} ${ARGS} -Sescape=! -Smta=test://"$MBOX" -Sreply-to-honour \ ${argadd} -Rf ./.tmbox > .tall 2>&1 check 2 0 "${MBOX}" '841868335 433' check 3 - .tall '4294967295 0' printf 'reply 1\nnew <- thread!\n!||%s -e "%s"\n!.\n' \ "${sed}" '/^In-Reply-To:/d' | ${MAILX} ${ARGS} -Sescape=! -Smta=test://"$MBOX" -Sreply-to-honour \ ${argadd} -Rf "${MBOX}" > .tall 2>&1 check 4 0 "${MBOX}" '3136957908 771' check 5 - .tall '4294967295 0' printf 'reply 2\nold <- new <- thread!\n!.\n' | ${MAILX} ${ARGS} -Sescape=! -Smta=test://"$MBOX" -Sreply-to-honour \ ${argadd} -Rf "${MBOX}" > .tall 2>&1 check 6 0 "${MBOX}" '3036449053 1231' check 7 - .tall '4294967295 0' printf 'reply 3\nnew <- old <- new <- thread!\n!|| %s -e "%s"\n!.\n' \ "${sed}" '/^In-Reply-To:/d' | ${MAILX} ${ARGS} -Sescape=! -Smta=test://"$MBOX" -Sreply-to-honour \ ${argadd} -Rf "${MBOX}" > .tall 2>&1 check 8 0 "${MBOX}" '2069841383 1583' check 9 - .tall '4294967295 0' # And follow-up testing whether changing In-Reply-To: to - starts a new # thread with only the message being replied-to. printf 'reply 1\nthread with only one ref!\n!||%s -e "%s"\n!.\n' \ "${sed}" 's/^In-Reply-To:.*$/In-Reply-To:-/' | ${MAILX} ${ARGS} -Sescape=! -Smta=test://"$MBOX" -Sreply-to-honour \ ${argadd} -Rf "${MBOX}" > .tall 2>&1 check 10 0 "${MBOX}" '3155846378 2047' check 11 - .tall '4294967295 0' t_epilog "${@}" } t_pipe_handlers() { t_prolog "${@}" if have_feat cmd-vexpr; then :; else t_echoskip '[test unsupported]' t_epilog "${@}" return fi # "Test for" [d6f316a] (Gavin Troy) printf "m ${MBOX}\n~s subject1\nEmail body\n~.\nfi ${MBOX}\np\nx\n" | ${MAILX} ${ARGS} ${ADDARG_UNI} -Spipe-text/plain="?* ${cat}" > "${BODY}" check 1 0 "${MBOX}" '3942990636 118' check 2 - "${BODY}" '3951695530 170' ${rm} "${MBOX}" printf "m %s\n~s subject2\n~@%s\nBody2\n~.\nFi %s\nmimeview\nx\n" \ "${MBOX}" "${TOPDIR}snailmail.jpg" "${MBOX}" | ${MAILX} ${ARGS} ${ADDARG_UNI} \ -S 'pipe-text/plain=?' \ -S 'pipe-image/jpeg=?=&?'\ 'trap \"'"${rm}"' -f '\ '\\"${MAILX_FILENAME_TEMPORARY}\\"\" EXIT;'\ 'trap \"trap \\\"\\\" INT QUIT TERM; exit 1\" INT QUIT TERM;'\ '{ echo C=$MAILX_CONTENT;'\ 'echo C-E=$MAILX_CONTENT_EVIDENCE;'\ 'echo E-B-U=$MAILX_EXTERNAL_BODY_URL;'\ 'echo F=$MAILX_FILENAME;'\ 'echo F-G=not testable MAILX_FILENAME_GENERATED;'\ 'echo F-T=not testable MAILX_FILENAME_TEMPORARY;'\ ''"${cksum}"' < \"${MAILX_FILENAME_TEMPORARY}\" |'\ ''"${sed}"' -e "s/[ ]\{1,\}/ /g"; } > ./.tax 2>&1;'"${mv}"' ./.tax ./.tay' \ > "${BODY}" 2>&1 check 3 0 "${MBOX}" '1933681911 13435' check 4 - "${BODY}" '2275717813 469' check 4-hdl - ./.tay '144517347 151' async # Keep $MBOX.. if [ -z "${ln}" ]; then t_echoskip '5:[ln(1) not found]' else # Let us fill in tmpfile, test auto-deletion printf 'Fi %s\nmimeview\nvput vexpr v file-stat .t.one-link\n'\ 'eval wysh set $v;echo should be $st_nlink link\nx\n' "${MBOX}" | ${MAILX} ${ARGS} ${ADDARG_UNI} \ -S 'pipe-text/plain=?' \ -S 'pipe-image/jpeg=?=++?'\ 'echo C=$MAILX_CONTENT;'\ 'echo C-E=$MAILX_CONTENT_EVIDENCE;'\ 'echo E-B-U=$MAILX_EXTERNAL_BODY_URL;'\ 'echo F=$MAILX_FILENAME;'\ 'echo F-G=not testable MAILX_FILENAME_GENERATED;'\ 'echo F-T=not testable MAILX_FILENAME_TEMPORARY;'\ "${ln}"' -f $MAILX_FILENAME_TEMPORARY .t.one-link;'\ ''"${cksum}"' < \"${MAILX_FILENAME_TEMPORARY}\" |'\ ''"${sed}"' -e "s/[ ]\{1,\}/ /g"' \ > "${BODY}" 2>&1 check 5 0 "${BODY}" '79260249 637' # Fill in ourselfs, test auto-deletion printf 'Fi %s\nmimeview\nvput vexpr v file-stat .t.one-link\n'\ 'eval wysh set $v;echo should be $st_nlink link\nx\n' "${MBOX}" | ${MAILX} ${ARGS} ${ADDARG_UNI} \ -S 'pipe-text/plain=?' \ -S 'pipe-image/jpeg=?++?'\ "${cat}"' > $MAILX_FILENAME_TEMPORARY;'\ 'echo C=$MAILX_CONTENT;'\ 'echo C-E=$MAILX_CONTENT_EVIDENCE;'\ 'echo E-B-U=$MAILX_EXTERNAL_BODY_URL;'\ 'echo F=$MAILX_FILENAME;'\ 'echo F-G=not testable MAILX_FILENAME_GENERATED;'\ 'echo F-T=not testable MAILX_FILENAME_TEMPORARY;'\ ''"${cksum}"' < \"${MAILX_FILENAME_TEMPORARY}\" |'\ ''"${sed}"' -e "s/[ ]\{1,\}/ /g"' \ > "${BODY}" 2>&1 check 6 0 "${BODY}" '79260249 637' # And the same, via copiousoutput (fake) printf 'Fi %s\np\nvput vexpr v file-stat .t.one-link\n'\ 'eval wysh set $v;echo should be $st_nlink link\nx\n' "${MBOX}" | ${MAILX} ${ARGS} ${ADDARG_UNI} \ -S 'pipe-text/plain=?' \ -S 'pipe-image/jpeg=?*++?'\ "${cat}"' > $MAILX_FILENAME_TEMPORARY;'\ 'echo C=$MAILX_CONTENT;'\ 'echo C-E=$MAILX_CONTENT_EVIDENCE;'\ 'echo E-B-U=$MAILX_EXTERNAL_BODY_URL;'\ 'echo F=$MAILX_FILENAME;'\ 'echo F-G=not testable MAILX_FILENAME_GENERATED;'\ 'echo F-T=not testable MAILX_FILENAME_TEMPORARY;'\ "${ln}"' -f $MAILX_FILENAME_TEMPORARY .t.one-link;'\ ''"${cksum}"' < \"${MAILX_FILENAME_TEMPORARY}\" |'\ ''"${sed}"' -e "s/[ ]\{1,\}/ /g"' \ > "${BODY}" 2>&1 check 7 0 "${BODY}" '686281717 676' fi t_epilog "${@}" } # }}} # Rest {{{ t_s_mime() { t_prolog "${@}" if have_feat smime; then :; else t_echoskip '[no S/MIME option]' t_epilog "${@}" return fi ${cat} <<-_EOT > ./.t.conf [req] default_bits = 1024 default_keyfile = keyfile.pem distinguished_name = req_distinguished_name attributes = req_attributes prompt = no output_password = [req_distinguished_name] C = GB ST = Over the L = rainbow O = S-nail OU = S-nail.smime CN = S-nail.test emailAddress = test@localhost [req_attributes] challengePassword = _EOT openssl req -x509 -nodes -days 3650 -config ./.t.conf \ -newkey rsa:1024 -keyout ./.tkey.pem -out ./.tcert.pem >>${ERR} 2>&1 check_ex0 0 ${cat} ./.tkey.pem ./.tcert.pem > ./.tpair.pem # Sign/verify echo bla | ${MAILX} ${ARGS} \ -Ssmime-sign -Ssmime-sign-cert=./.tpair.pem -Sfrom=test@localhost \ -Ssmime-sign-digest=sha1 \ -s 'S/MIME test' ./.VERIFY check_ex0 1-estat ${awk} ' BEGIN{ skip=0 } /^Content-Description: /{ skip = 2; print; next } /^$/{ if(skip) --skip } { if(!skip) print } ' \ < ./.VERIFY > "${MBOX}" check 1 - "${MBOX}" '335634014 644' printf 'verify\nx\n' | ${MAILX} ${ARGS} -Ssmime-ca-file=./.tcert.pem -Serrexit \ -R -f ./.VERIFY >>${ERR} 2>&1 check_ex0 2 openssl smime -verify -CAfile ./.tcert.pem -in ./.VERIFY >>${ERR} 2>&1 check_ex0 3 # (signing +) encryption / decryption echo bla | ${MAILX} ${ARGS} \ -Smta=test://./.ENCRYPT \ -Ssmime-force-encryption -Ssmime-encrypt-recei@ver.com=./.tpair.pem \ -Ssmime-sign-digest=sha1 \ -Ssmime-sign -Ssmime-sign-cert=./.tpair.pem -Sfrom=test@localhost \ -s 'S/MIME test' recei@ver.com check_ex0 4-estat ${sed} -e '/^$/,$d' < ./.ENCRYPT > "${MBOX}" check 4 - "${MBOX}" '2359655411 336' printf 'decrypt ./.DECRYPT\nfi ./.DECRYPT\nverify\nx\n' | ${MAILX} ${ARGS} \ -Smta=test://./.ENCRYPT \ -Ssmime-ca-file=./.tcert.pem \ -Ssmime-sign-cert=./.tpair.pem \ -Serrexit -R -f ./.ENCRYPT >>${ERR} 2>&1 check_ex0 5-estat ${awk} ' BEGIN{ skip=0 } /^Content-Description: /{ skip = 2; print; next } /^$/{ if(skip) --skip } { if(!skip) print } ' \ < ./.DECRYPT > "${MBOX}" check 5 - "${MBOX}" '2602978204 940' (openssl smime -decrypt -inkey ./.tkey.pem -in ./.ENCRYPT | openssl smime -verify -CAfile ./.tcert.pem) >>${ERR} 2>&1 check_ex0 6 ${rm} ./.ENCRYPT echo bla | ${MAILX} ${ARGS} \ -Smta=test://./.ENCRYPT \ -Ssmime-force-encryption -Ssmime-encrypt-recei@ver.com=./.tpair.pem \ -Sfrom=test@localhost \ -s 'S/MIME test' recei@ver.com check_ex0 7-estat ${sed} -e '/^$/,$d' < ./.ENCRYPT > "${MBOX}" check 7 - "${MBOX}" '2359655411 336' ${rm} ./.DECRYPT printf 'decrypt ./.DECRYPT\nx\n' | ${MAILX} ${ARGS} \ -Smta=test://./.ENCRYPT \ -Ssmime-sign-cert=./.tpair.pem \ -Serrexit -R -f ./.ENCRYPT >>${ERR} 2>&1 check 8 0 ./.DECRYPT '2453471323 431' openssl smime -decrypt -inkey ./.tkey.pem -in ./.ENCRYPT >>${ERR} 2>&1 check_ex0 9 t_epilog "${@}" } # }}} # xxx Note: t_z() was the first test (series) written. Today many # xxx aspects are (better) covered by other tests above, some are not. # xxx At some future date and time, convert the last remains not covered # xxx elsewhere to a real t_* test and drop it t_z() { t_prolog "${@}" # Test for [260e19d] (Juergen Daubert) echo body | ${MAILX} ${ARGS} "${MBOX}" check 4 0 "${MBOX}" '2948857341 94' # "Test for" [c299c45] (Peter Hofmann) TODO shouldn't end up QP-encoded? ${rm} "${MBOX}" ${awk} 'BEGIN{ for(i = 0; i < 10000; ++i) printf "\xC3\xBC" #printf "\xF0\x90\x87\x90" }' | ${MAILX} ${ARGS} ${ADDARG_UNI} -s TestSubject "${MBOX}" check 7 0 "${MBOX}" '1707496413 61812' t_epilog "${@}" } # Test support {{{ t__put_subject() { # MIME encoding (QP) stress message subject printf 'Äbrä Kä?dä=brö Fü?di=bus? '\ 'adadaddsssssssddddddddddddddddddddd'\ 'ddddddddddddddddddddddddddddddddddd'\ 'ddddddddddddddddddddddddddddddddddd'\ 'dddddddddddddddddddd Hallelulja? Od'\ 'er?? eeeeeeeeeeeeeeeeeeeeeeeeeeeeee'\ 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'\ 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee f'\ 'fffffffffffffffffffffffffffffffffff'\ 'fffffffffffffffffffff ggggggggggggg'\ 'ggggggggggggggggggggggggggggggggggg'\ 'ggggggggggggggggggggggggggggggggggg'\ 'ggggggggggggggggggggggggggggggggggg'\ 'gggggggggggggggg' } t__put_body() { # MIME encoding (QP) stress message body printf \ 'Ich bin eine DÖS-Datäi mit sehr langen Zeilen und auch '\ 'sonst bin ich ganz schön am Schleudern, da kannste denke '\ "wasde willst, gelle, gelle, gelle, gelle, gelle.\r\n"\ "Ich bin eine DÖS-Datäi mit langen Zeilen und auch sonst \r\n"\ "Ich bin eine DÖS-Datäi mit langen Zeilen und auch sonst 1\r\n"\ "Ich bin eine DÖS-Datäi mit langen Zeilen und auch sonst 12\r\n"\ "Ich bin eine DÖS-Datäi mit langen Zeilen und auch sonst 123\r\n"\ "Ich bin eine DÖS-Datäi mit langen Zeilen und auch sonst 1234\r\n"\ "Ich bin eine DÖS-Datäi mit langen Zeilen und auch sonst 12345\r\n"\ "Ich bin eine DÖS-Datäi mit langen Zeilen und auch sonst 123456\r\n"\ "Ich bin eine DÖS-Datäi mit langen Zeilen und auch sonst 1234567\r\n"\ "Ich bin eine DÖS-Datäi mit langen Zeilen und auch sonst 12345678\r\n"\ "Ich bin eine DÖS-Datäi mit langen Zeilen und auch sonst 123456789\r\n"\ "Unn ausserdem habe ich trailing SP/HT/SP/HT whitespace \r\n"\ "Unn ausserdem habe ich trailing HT/SP/HT/SP whitespace \r\n"\ "auf den zeilen vorher.\r\n"\ "From am Zeilenbeginn und From der Mitte gibt es auch.\r\n"\ ".\r\n"\ "Die letzte Zeile war nur ein Punkt.\r\n"\ "..\r\n"\ "Das waren deren zwei.\r\n"\ " \r\n"\ "Die letzte Zeile war ein Leerschritt.\n"\ "=VIER = EQUAL SIGNS=ON A LINE=\r\n"\ "Prösterchen.\r\n"\ ".\n"\ "Die letzte Zeile war nur ein Punkt, mit Unix Zeilenende.\n"\ "..\n"\ "Das waren deren zwei. ditto.\n"\ "Prösterchen.\n"\ "Unn ausseerdem habe ich trailing SP/HT/SP/HT whitespace \n"\ "Unn ausseerdem habe ich trailing HT/SP/HT/SP whitespace \n"\ "auf den zeilen vorher.\n"\ "ditto.\n"\ "Ich bin eine ziemlich lange, steile, scharfe Zeile mit Unix Zeilenende.\n"\ "Ich bin eine ziemlich lange, steile, scharfe Zeile mit Unix Zeilenende.1"\ "\n"\ "Ich bin eine ziemlich lange, steile, scharfe Zeile mit Unix Zeilenende.12"\ "\n"\ "Ich bin eine ziemlich lange, steile, scharfe Zeile mit Unix Zeilenende.12"\ "3\n"\ "Ich bin eine ziemlich lange, steile, scharfe Zeile mit Unix Zeilenende.12"\ "34\n"\ "Ich bin eine ziemlich lange, steile, scharfe Zeile mit Unix Zeilenende.12"\ "345\n"\ "Ich bin eine ziemlich lange, steile, scharfe Zeile mit Unix Zeilenende.12"\ "3456\n"\ "QP am Zeilenende über soft-nl hinweg\n"\ "Ich bin eine ziemlich lange, steile, scharfe Zeile mit Unix Zeilenende."\ "ö123\n"\ "Ich bin eine ziemlich lange, steile, scharfe Zeile mit Unix Zeilenende."\ "1ö23\n"\ "Ich bin eine ziemlich lange, steile, scharfe Zeile mit Unix Zeilenende."\ "12ö3\n"\ "Ich bin eine ziemlich lange, steile, scharfe Zeile mit Unix Zeilenende."\ "123ö\n"\ "=VIER = EQUAL SIGNS=ON A LINE=\n"\ " \n"\ "Die letzte Zeile war ein Leerschritt.\n"\ ' ' } # }}} # cc_all_configs() {{{ # Test all configs TODO doesn't cover all *combinations*, stupid! cc_all_configs() { if [ ${MAXJOBS} -gt 1 ]; then MAXJOBS='-j '${MAXJOBS} else MAXJOBS= fi < ${CONF} ${awk} ' BEGIN{ ALWAYS = "OPT_AUTOCC=1 OPT_AMALGAMATION=1" NOTME["OPT_ALWAYS_UNICODE_LOCALE"] = 1 NOTME["OPT_CROSS_BUILD"] = 1 NOTME["OPT_AUTOCC"] = 1 NOTME["OPT_AMALGAMATION"] = 1 NOTME["OPT_DEBUG"] = 1 NOTME["OPT_DEVEL"] = 1 NOTME["OPT_ASAN_ADDRESS"] = 1 NOTME["OPT_ASAN_MEMORY"] = 1 NOTME["OPT_FORCED_STACKPROT"] = 1 NOTME["OPT_NOMEMDBG"] = 1 #OPTVALS OPTNO = 0 MULCHOICE["OPT_IDNA"] = "VAL_IDNA" MULVALS["VAL_IDNA"] = 1 #VALKEYS[0] = "VAL_RANDOM" VALVALS["VAL_RANDOM"] = 1 VALNO = 0 } /^[ ]*OPT_/{ sub(/^[ ]*/, "") # This bails for UnixWare 7.1.4 awk(1), but preceeding = with \ # does not seem to be a compliant escape for = #sub(/=.*$/, "") $1 = substr($1, 1, index($1, "=") - 1) if(!NOTME[$1]) OPTVALS[OPTNO++] = $1 next } /^[ ]*VAL_/{ sub(/^[ ]*/, "") val = substr($0, index($0, "=") + 1) if(val ~ /^\"/){ val = substr(val, 2) val = substr(val, 1, length(val) - 1) } $1 = substr($1, 1, index($1, "=") - 1) if(MULVALS[$1]) MULVALS[$1] = val else if(VALVALS[$1]){ VALKEYS[VALNO++] = $1 VALVALS[$1] = val } next } function onepass(addons){ a_onepass__worker(addons, "1", "0") a_onepass__worker(addons, "0", "1") } function a_onepass__worker(addons, b0, b1){ # Doing this completely sequentially and not doing make distclean in # between runs should effectively result in lesser compilations. # It is completely dumb nonetheless... TODO for(ono = 0; ono < OPTNO; ++ono){ myconf = mula = "" for(i = 0; i < ono; ++i){ myconf = myconf " " OPTVALS[i] "=" b0 " " if(b0 == "1"){ j = MULCHOICE[OPTVALS[i]] if(j){ if(i + 1 == ono) mula = j else myconf = myconf " " MULCHOICE[OPTVALS[i]] "=any " } } } for(i = ono; i < OPTNO; ++i){ myconf = myconf " " OPTVALS[i] "=" b1 " " if(b1 == "1"){ j = MULCHOICE[OPTVALS[i]] if(j){ if(i + 1 == OPTNO) mula = j; else myconf = myconf " " MULCHOICE[OPTVALS[i]] "=any " } } } for(i in VALKEYS) myconf = VALKEYS[i] "=any " myconf myconf = myconf " " ALWAYS " " addons if(mula == "") print myconf else{ i = split(MULVALS[mula], ia) j = "any" while(i >= 1){ j = ia[i--] " " j print mula "=\"" j "\" " myconf } } } } END{ # We cannot test NULL because of missing UI strings, which will end # up with different checksums print "CONFIG=NULLI OPT_AUTOCC=1" for(i in VALKEYS){ j = split(VALVALS[VALKEYS[i]], ia) k = "any" while(j >= 1){ k = ia[j--] " " k print VALKEYS[i] "=\"" k "\" CONFIG=NULLI OPT_AUTOCC=1" } } print "CONFIG=MINIMAL OPT_AUTOCC=1" print "CONFIG=NETSEND OPT_AUTOCC=1" print "CONFIG=MAXIMAL OPT_AUTOCC=1" for(i in VALKEYS){ j = split(VALVALS[VALKEYS[i]], ia) k = "any" while(j >= 1){ k = ia[j--] " " k print VALKEYS[i] "=\"" k "\" CONFIG=MAXIMAL OPT_AUTOCC=1" } } print "CONFIG=DEVEL OPT_AUTOCC=1" print "CONFIG=ODEVEL OPT_AUTOCC=1" onepass("OPT_DEBUG=1") onepass("") } ' | while read c; do [ -f mk-config.h ] && ${cp} mk-config.h .ccac.h printf "\n\n##########\n$c\n" printf "\n\n##########\n$c\n" >&2 ${SHELL} -c "cd .. && ${MAKE} ${c} config" if [ -f .ccac.h ] && ${cmp} mk-config.h .ccac.h; then printf 'Skipping after config, nothing changed\n' printf 'Skipping after config, nothing changed\n' >&2 continue fi ${SHELL} -c "cd ../ && ${MAKE} ${MAXJOBS} build test" done ${rm} -f .ccac.h cd .. && ${MAKE} distclean } # }}} ssec=$SECONDS if [ -z "${CHECK_ONLY}${RUN_TEST}" ]; then jobs_max cc_all_configs else if have_feat debug; then JOBWAIT=`add ${JOBWAIT} ${JOBWAIT}` if have_feat devel; then JOBSYNC=1 DUMPERR=y ARGS="${ARGS} -Smemdebug" fi elif have_feat devel; then LOOPS_MAX=${LOOPS_BIG} fi color_init if [ -z "${RUN_TEST}" ] || [ ${#} -eq 0 ]; then jobs_max printf 'Will do up to %s tests in parallel, with a %s second timeout\n' \ ${MAXJOBS} ${JOBWAIT} jobreaper_start t_all jobreaper_stop else MAXJOBS=1 #jobreaper_start while [ ${#} -gt 0 ]; do jspawn ${1} shift done #jobreaper_stop fi fi esec=$SECONDS printf '%u tests: %s%u ok%s, %s%u failure(s)%s. %s%u test(s) skipped%s\n' \ "${TESTS_PERFORMED}" "${COLOR_OK_ON}" "${TESTS_OK}" "${COLOR_OK_OFF}" \ "${COLOR_ERR_ON}" "${TESTS_FAILED}" "${COLOR_ERR_OFF}" \ "${COLOR_WARN_ON}" "${TESTS_SKIPPED}" "${COLOR_WARN_OFF}" if [ -n "${ssec}" ] && [ -n "${esec}" ]; then ( echo 'Elapsed seconds: '`$awk 'BEGIN{print '"${esec}"' - '"${ssec}"'}'` ) fi exit ${ESTAT} # s-sh-mode s-nail-14.9.15/nail.1000066400000000000000000014723701352610246600141060ustar00rootroot00000000000000.\"@ nail.1 - S-nail(1) reference manual. .\" .\" Copyright (c) 2000-2008 Gunnar Ritter, Freiburg i. Br., Germany. .\" Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . .\" .\" Copyright (c) 1980, 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. .\" .\"--MKREL-START-- .\"@ S-nail v14.9.15 / 2019-08-17 .Dd August 17, 2019 .ds VV \\%v14.9.15 .\"--MKREL-END-- .\"--MKMAN-START-- .ds UU \\%S-NAIL .ds UA \\%S-nail .ds uA \\%s-nail .ds UR \\%s-nail.rc .ds ur \\%~/.mailrc .ds VD \\%~/dead.letter .ds VM \\%~/mbox .ds VN \\%~/.netrc .ds VT \\%/tmp .ds vS /etc/mime.types .ds vU ~/.mime.types .\"--MKMAN-END-- .\" --BEGINSTRIP-- .\" .ds OB [Obsolete] .ds OP [Option] .ds IN [v15-compat] .ds OU [no v15-compat] .ds ID [v15 behaviour may differ] .ds NQ [Only new quoting rules] .ds BO (Boolean) .ds RO (Read-only) .\" .if !d str-Lb-libterminfo \ .ds str-Lb-libterminfo Terminal Information Library (libterminfo, \-lterminfo) . .Dt "\*(UU" 1 .Os .Mx -enable . . .Sh NAME .Nm \*(UA \%[\*(VV] .Nd send and receive Internet mail . . .\" .Sh SYNOPSIS {{{ .Sh SYNOPSIS . .\" Keep in SYNC: ./nail.1:"SYNOPSIS, main() .Nm \*(uA .Bk -words .Op Fl DdEFinv~# .Op Fl \&: Ar spec .Op Fl A Ar account .Op : Ns Fl a Ar attachment Ns \&: .Op : Ns Fl b Ar bcc-addr Ns \&: .Op : Ns Fl C Ar """field:\0body""" Ns \&: .Op : Ns Fl c Ar cc-addr Ns \&: .Op Fl M Ar type | Fl m Ar file | Fl q Ar file | Fl t .Op Fl r Ar from-addr .Oo : Ns .Fl S\0 Ns Ar var Ns Op Ns = Ns Ar value Ns .Pf \&: Oc .Op Fl s Ar subject .Op : Ns Fl T Ar """field:\0addr""" Ns \&: .Op : Ns Fl X Ar cmd Ns \&: .Op : Ns Fl Y Ar cmd Ns \&: .Op Fl \&. .Pf : Ar to-addr Ns \&: .Op Fl Fl \~ Ns : Ns Ar mta-option Ns \&: .Ek .Pp .Nm \*(uA .Bk -words .Op Fl DdEeHiNnRv~# .Op Fl \&: Ar spec .Op Fl A Ar account .Op : Ns Fl C Ar """field:\0body""" Ns \&: .Op Fl L Ar spec .Op Fl r Ar from-addr .Oo : Ns .Fl S Ar var Ns Op Ns = Ns Ar value Ns .Pf \&: Oc .Op Fl u Ar user .Op : Ns Fl X Ar cmd Ns \&: .Op : Ns Fl Y Ar cmd Ns \&: .Op Fl Fl \~ Ns : Ns Ar mta-option Ns \&: .Ek .Nm \*(uA .Bk -words .Op Fl DdEeHiNnRv~# .Op Fl \&: Ar spec .Op Fl A Ar account .Op : Ns Fl C Ar """field:\0body""" Ns \&: .Fl f .Op Fl L Ar spec .Op Fl r Ar from-addr .Oo : Ns .Fl S Ar var Ns Op Ns = Ns Ar value Ns .Pf \&: Oc .Op : Ns Fl X Ar cmd Ns \&: .Op : Ns Fl Y Ar cmd Ns \&: .Op Ar file .Op Fl Fl \~ Ns : Ns Ar mta-option Ns \&: .Ek .Pp .Nm \*(uA .Fl h | Fl Fl help .Nm \*(uA .Fl V | Fl Fl version . .\" }}} . . .Mx -toc -tree html pdf ps xhtml . . .\" .Sh DESCRIPTION {{{ .Sh DESCRIPTION . .Bd -filled -compact -offset indent .Sy Note: S-nail (\*(UA) will see major changes in v15.0 (circa 2020). Some backward incompatibilities cannot be avoided. .Sx COMMANDS change to .Sx "Shell-style argument quoting" , and shell metacharacters will become (more) meaningful. Some commands accept new syntax today via .Cm wysh .Pf ( Sx "Command modifiers" ) . Behaviour is flagged \*(IN and \*(OU, .Ic set Ns ting .Va v15-compat .Pf ( Sx "INTERNAL VARIABLES" ) will choose new behaviour when applicable; giving it a value makes .Cm wysh an implied default. \*(OB flags what will vanish. Using .Fl d or .Fl v enables obsoletion warnings. .Pp .Sy Warning! .Va v15-compat (with value) will be a default in v14.10.0! .Ed . .Pp \*(UA provides a simple and friendly environment for sending and receiving mail. It is intended to provide the functionality of the POSIX .Xr mailx 1 command, but is MIME capable and optionally offers extensions for line editing, S/MIME, SMTP and POP3, among others. \*(UA divides incoming mail into its constituent messages and allows the user to deal with them in any order. It offers many .Sx COMMANDS and .Sx "INTERNAL VARIABLES" for manipulating messages and sending mail. It provides the user simple editing capabilities to ease the composition of outgoing messages, and increasingly powerful and reliable non-interactive scripting capabilities. . .\" .Ss "Options" {{{ .Ss "Options" . .Bl -tag -width ".It Fl BaNg" .Mx .It Fl \&: Ar spec , Fl Fl resource-files Ns =.. Explicitly control which of the .Sx "Resource files" shall be .Ic source Ns d (loaded): if the letter .Ql s is (case-insensitively) part of the .Ar spec then the system wide .Pa \*(UR is sourced, likewise the letter .Ql u controls sourcing of the user's personal .Pa \*(ur file, whereas the letters .Ql - and .Ql / explicitly forbid sourcing of any resource files. Scripts should use this option: to avoid environmental noise they should .Dq detach from any configuration and create a script-specific environment, setting any of the desired .Sx "INTERNAL VARIABLES" via .Fl S and running configurating commands via .Fl X . This option overrides .Fl n . . .Mx .It Fl A Ar name , Fl Fl account Ns =.. Executes an .Ic account command for the given user email account .Ar name after program startup is complete (all resource files are loaded, any .Fl S setting is being established, but .Fl X commands have not been evaluated yet). Being a special incarnation of .Ic define Ns d macros for the purpose of bundling longer-lived .Ic set Ns tings, activating such an email account also switches to the accounts .Mx -sx .Sx "primary system mailbox" (most likely the .Va inbox ) . If the operation fails the program will exit if it is used non-interactively, or if any of .Va errexit or .Va posix are set. . .Mx .It Fl a Ar file Ns Oo Ar =input-charset Ns Oo Ar #output-charset Oc Oc , \ Fl Fl attach Ns =.. Attach .Ar file to the message (for compose mode opportunities refer to .Ic ~@ and .Ic ~^ ) . .Sx "Filename transformations" (also see .Ic file ) will be performed, except that shell variables are not expanded. Shall .Ar file not be accessible but contain a .Ql = character, then anything before the last .Ql = will be used as the filename, anything thereafter as a character set specification. .Pp If an input character set is specified, .Mx -ix "character set specification" but no output character set, then the given input character set is fixed as-is, and no conversion will be applied; giving the empty string or the special string hyphen-minus .Ql - will be treated as if .Va ttycharset has been specified (the default). .Pp If an output character set has also been given then the conversion will be performed exactly as specified and on-the-fly, not considering the file type and content. As an exception the empty string or hyphen-minus .Ql - , select the default conversion algorithm (see .Sx "Character sets" ) : no conversion is performed on-the-fly, .Ar file and its contents will be MIME-classified .Pf ( Sx "HTML mail and MIME attachments" , "The mime.types files") ; Only this mode is supported without support for character set conversions .Pf ( Va features does not mention .Ql +iconv ) . . .It Fl B (\*(OB: \*(UA will always use line-buffered output, to gain line-buffered input even in batch mode enable batch mode via .Fl # . ) . .Mx .It Fl b Ar addr , Fl Fl bcc Ns =.. Send a blind carbon copy to recipient .Ar addr , if the .Ic set Ns ting of .Va expandaddr , one of the .Sx "INTERNAL VARIABLES" , allows; the .Ql shquote .Va expandaddr flag is supported. The option may be used multiple times. Also see the section .Sx "On sending mail, and non-interactive mode" . . .Mx .It Fl C Ar """field: body""" , Fl Fl custom-header Ns =.. Create a custom header which persists for an entire session. A custom header consists of the field name followed by a colon .Ql \&: and the field content body, e.g., .Ql -C """Blah: Neminem laede; imo omnes, quantum potes, juva""" . Standard header field names cannot be overwritten by custom headers. Runtime adjustable custom headers are available via the variable .Va customhdr , and in compose mode .Ic ~^ , one of the .Sx "COMMAND ESCAPES" , as well as .Ic digmsg are the most flexible and powerful options to manage message headers. This option may be used multiple times. . .Mx .It Fl c Ar addr , Fl Fl cc Ns =.. Just like .Fl b , except it places the argument in the list of carbon copies. . .Mx .It Fl D , Fl Fl disconnected (\*(OP) Startup with .Va disconnected .Ic set . . .Mx .It Fl d , Fl Fl debug Almost enable a sandbox mode with the internal variable .Va debug ; the same can be achieved via .Ql Fl S Va \&\&debug or .Ql Ic set Va \&\&debug . . .Mx .It Fl E , Fl Fl discard-empty-messages .Ic set .Va skipemptybody and thus discard messages with an empty message part body. . .Mx .It Fl e , Fl Fl check-and-exit Just check if mail is present (in the system .Va inbox or the one specified via .Fl f ) : if yes, return an exit status of zero, a non-zero value otherwise. To restrict the set of mails to consider in this evaluation a message specification can be added with the option .Fl L . Quickrun: does not open an interactive session. . .Mx .It Fl F Save the message to send in a file named after the local part of the first recipient's address (instead of in .Va record Ns ). . .Mx .It Fl f , Fl Fl file Read in the contents of the user's .Mx -sx .Sx "secondary mailbox" .Ev MBOX (or the specified file) for processing; when \*(UA is quit, it writes undeleted messages back to this file (but be aware of the .Va hold option). The optional .Ar file argument will undergo some special .Sx "Filename transformations" (as via .Ic file ) . Note that .Ar file is not an argument to the flag .Fl \&\&f , but is instead taken from the command line after option processing has been completed. In order to use a .Ar file that starts with a hyphen-minus, prefix with a relative path, as in .Ql ./-hyphenbox.mbox . . .Mx .It Fl H , Fl Fl header-summary Display a summary of .Ic headers for the given .Ic file (depending on .Fl u , .Va inbox or .Ev MAIL , or as specified via .Fl f ) , then exit. A configurable summary view is available via the option .Fl L . This mode does not honour .Va showlast . Quickrun: does not open an interactive session. . .Mx .It Fl h , Fl Fl help Show a brief usage summary; use .Fl Fl long-help for a list long options. . .Mx .It Fl i .Ic set .Va ignore to ignore tty interrupt signals. . .Mx .It Fl L Ar spec , Fl Fl header-search Ns =.. Display a summary of .Ic headers of all messages that match the given .Ar spec in the .Ic file found by the same algorithm used by .Fl H , then exit. See the section .Sx "Specifying messages" for the format of .Ar spec . This mode does not honour .Va showlast . .Pp If the .Fl e option has been given in addition no header summary is produced, but \*(UA will instead indicate via its exit status whether .Ar spec matched any messages .Pf ( Ql 0 ) or not .Pf ( Ql 1 ) ; note that any verbose output is suppressed in this mode and must instead be enabled explicitly (e.g., by using the option .Fl v ) . Quickrun: does not open an interactive session. . .Mx .It Fl M Ar type Special send mode that will flag standard input with the MIME .Ql Content-Type: set to the given known .Ar type .Pf ( Sx "HTML mail and MIME attachments" , "The mime.types files" ) and use it as the main message body. \*(ID Using this option will bypass processing of .Va message-inject-head and .Va message-inject-tail . Also see .Fl q , m , t . . .Mx .It Fl m Ar file Special send mode that will MIME classify the specified .Ar file , and use it as the main message body. \*(ID Using this option will bypass processing of .Va message-inject-head and .Va message-inject-tail . Also see .Fl q , M , t . . .Mx .It Fl N , Fl Fl no-header-summary inhibit the initial display of message headers when reading mail or editing a mailbox .Ic folder by calling .Ic unset for the internal variable .Va header . . .Mx .It Fl n Standard flag that inhibits reading the system wide .Pa \*(UR upon startup. The option .Fl \&: allows more control over the startup sequence; also see .Sx "Resource files" . . .Mx .It Fl q Ar file , Fl Fl quote-file Ns =.. Special send mode that will initialize the message body with the contents of the specified .Ar file , which may be standard input .Ql - only in non-interactive context. Also see .Fl M , m , t . . .Mx .It Fl R , Fl Fl read-only Any mailbox .Ic folder aka\& .Ic file opened will be in read-only mode. . . .Mx .It Fl r Ar from-addr , Fl Fl from-address Ns =.. Whereas the source address that appears in the .Va from header of a message (or in the .Va sender header if the former contains multiple addresses) is honoured by the built-in SMTP transport, it is not used by a file-based .Va mta (Mail-Transfer-Agent) for the RFC 5321 reverse-path used for relaying and delegating a message to its destination(s), for delivery errors etc., but it instead uses the local identity of the initiating user. . .Pp When this command line option is used the given single addressee .Ar from-addr will be assigned to the internal variable .Va from , but in addition the command line option .Fl \&\&f Ar from-addr will be passed to a file-based .Va mta whenever a message is sent. Shall .Ar from-addr include a user name the address components will be separated and the name part will be passed to a file-based .Va mta individually via .Fl \&\&F Ar name . Even though not a recipient the .Ql shquote .Va expandaddr flag is supported. . .Pp If an empty string is passed as .Ar from-addr then the content of the variable .Va from (or, if that contains multiple addresses, .Va sender ) will be evaluated and used for this purpose whenever the file-based .Va mta is contacted. By default, without .Fl \&\&r that is, neither .Fl \&\&f nor .Fl \&\&F command line options are used when contacting a file-based MTA, unless this automatic deduction is enforced by .Ic set Ns ing the internal variable .Va r-option-implicit . . .Pp Remarks: many default installations and sites disallow overriding the local user identity like this unless either the MTA has been configured accordingly or the user is member of a group with special privileges. Passing an invalid address will cause an error. . . .Mx .It Fl S Ar var Ns Oo = Ns value Oc , Fl Fl set Ns =.. .Ic set (or, with a prefix string .Ql no , as documented in .Sx "INTERNAL VARIABLES" , .Ic unset ) .Ar var Ns iable and optionally assign .Ar value , if supported; \*(ID the entire expression is evaluated as if specified within dollar-single-quotes (see .Sx "Shell-style argument quoting" ) if the internal variable .Va v15-compat is set. If the operation fails the program will exit if any of .Va errexit or .Va posix are set. Settings established via .Fl \&\&S cannot be changed from within .Sx "Resource files" or an account switch initiated by .Fl A . They will become mutable again before commands registered via .Fl X are executed. . .Mx .It Fl s Ar subject , Fl Fl subject Ns =.. Specify the subject of the message to be sent. Newline (NL) and carriage-return (CR) bytes are invalid and will be normalized to space (SP) characters. . .Mx .It Fl T Ar """field: addr""" , Fl Fl target Ns =.. Add .Ar addr to the list of receivers targeted by .Ar field , for now supported are only .Ql bcc , .Ql cc , .Ql fcc , and .Ql to . Field and body (address) are separated by a colon .Ql \&: and optionally blank (space, tabulator) characters. The .Ql shquote .Va expandaddr flag is supported. .Ar addr is parsed like a message header address line, as if it would be part of a template message fed in via .Fl t , and the same modifier suffix is supported. This option may be used multiple times. . .Mx .It Fl t , Fl Fl template The text message given (on standard input) is expected to contain, separated from the message body by an empty line, one or multiple plain text message headers. \*(ID Readily prepared MIME mail messages cannot be passed. Headers can span multiple consecutive lines if follow lines start with any amount of whitespace. A line starting with the number sign .Ql # in the first column is ignored. Message recipients can be given via the message headers .Ql To: , .Ql Cc: , .Ql Bcc: (the .Ql ?single modifier enforces treatment as a single addressee, e.g., .Ql To?single: exa, ) or .Ql Fcc: , they will be added to any recipients specified on the command line, and are likewise subject to .Va expandaddr validity checks. If a message subject is specified via .Ql Subject: then it will be used in favour of one given on the command line. .Pp More optional headers are .Ql Reply-To: (possibly overriding .Va reply-to ) , .Ql Sender: .Pf ( Va sender ) , .Ql From: .Pf ( Va from and / or option .Fl r ) . .Ql Message-ID: , .Ql In-Reply-To: , .Ql References: and .Ql Mail-Followup-To: , by default created automatically dependent on message context, will be used if specified (a special address massage will however still occur for the latter). Any other custom header field (also see .Fl C , .Va customhdr and .Ic ~^ ) is passed through entirely unchanged, and in conjunction with the options .Fl ~ or .Fl # it is possible to embed .Sx "COMMAND ESCAPES" . Also see .Fl M , m , q . . .Mx .It Fl u Ar user , Fl Fl inbox-of Ns =.. Initially read the .Mx -sx .Sx "primary system mailbox" of .Ar user , appropriate privileges presumed; effectively identical to .Ql Fl \&\&f Ns \0%user . . .Mx .It Fl V , Fl Fl version Show \*(UAs .Va version and exit. The command .Ic version will also show the list of .Va features : .Ql $ \*(uA -:/ -Xversion -Xx . . .Mx .It Fl v , Fl Fl verbose .Ic set Ns ting the internal variable .Va verbose enables display of some informational context messages. (Will increase the level of verbosity when used multiple times.) . .Mx .It Fl X Ar cmd , Fl Fl startup-cmd Ns =.. Add the given (or multiple for a multiline argument) .Ar cmd to a list of commands to be executed before normal operation starts. The commands will be evaluated as a unit, just as via .Ic source . Correlates with .Fl # and .Va errexit . . .Mx .It Fl Y Ar cmd , Fl Fl cmd Ns =.. Add the given (or multiple for a multiline argument) .Ar cmd to a list of commands to be executed after normal operation has started. The commands will be evaluated successively in the given order, and as if given on the program's standard input \(em before interactive prompting begins in interactive mode, after standard input has been consumed otherwise. . .Mx .It Fl ~ , Fl Fl enable-cmd-escapes Enable .Sx "COMMAND ESCAPES" in compose mode even in non-interactive use cases. This can be used to, e.g., automatically format the composed message text before sending the message: .Bd -literal -offset indent $ ( echo 'line one. Word. Word2.';\e echo '~| /usr/bin/fmt -tuw66' ) |\e LC_ALL=C \*(uA -d~:/ -Sttycharset=utf-8 bob@exam.ple .Ed . .Mx .It Fl # , Fl Fl batch-mode Enables batch mode: standard input is made line buffered, the complete set of (interactive) commands is available, processing of .Sx "COMMAND ESCAPES" is enabled in compose mode, and diverse .Sx "INTERNAL VARIABLES" are adjusted for batch necessities, exactly as if done via .Fl S : .Va emptystart , .Pf no Va errexit , .Pf no Va header , .Pf no Va posix , .Va quiet , .Va sendwait , .Va typescript-mode as well as .Ev MAIL , .Ev MBOX and .Va inbox (the latter three to .Pa /dev/null ) . Also, the values of .Ev COLUMNS and .Ev LINES are looked up, and acted upon. The following prepares an email message in a batched dry run: .Bd -literal -offset indent $ LC_ALL=C printf 'm bob\en~s ubject\enText\en~.\enx\en' |\e LC_ALL=C \*(uA -d#:/ -X'alias bob bob@exam.ple' .Ed . .Mx .It Fl \&. , Fl Fl end-options This flag forces termination of option processing in order to prevent .Dq option injection (attacks). It also forcefully puts \*(UA into send mode, see .Sx "On sending mail, and non-interactive mode" . .El . .Pp All given .Ar to-addr arguments and all receivers established via .Fl b and .Fl c as well as .Fl T are subject to the checks established by .Va expandaddr , one of the .Sx "INTERNAL VARIABLES" ; they all support the flag .Ql shquote . If the setting of .Va expandargv allows their recognition all .Ar mta-option arguments given at the end of the command line after a .Ql -- separator will be passed through to a file-based .Va mta (Mail-Transfer-Agent) and persist for the entire session. .Va expandargv constraints do not apply to the content of .Va mta-arguments . .\" }}} . .\" .Ss "A starter" {{{ .Ss "A starter" . \*(UA is a direct descendant of .Bx Mail, itself a successor to the Research .Ux mail which .Dq was there from the start according to .Sx HISTORY . It thus represents the user side of the .Ux mail system, whereas the system side (Mail-Transfer-Agent, MTA) was traditionally taken by .Xr sendmail 8 , and most MTAs provide a binary of this name for compatibility purposes. If the \*(OPal SMTP .Va mta is included in the .Va features of \*(UA then the system side is not a mandatory precondition for mail delivery. . .Pp Because \*(UA strives for compliance with POSIX .Xr mailx 1 it is likely that some configuration settings have to be adjusted before using it is a smooth experience. (Rather complete configuration examples can be found in the section .Sx EXAMPLES . ) The provided global .Pa \*(UR (one of the .Sx "Resource files" ) template bends those standard imposed settings of the .Sx "INTERNAL VARIABLES" a bit towards more user friendliness and safety, however. . .Pp For example, it .Ic set Ns s .Va hold and .Va keepsave in order to suppress the automatic moving of messages to the .Mx -sx .Sx "secondary mailbox" .Ev MBOX that would otherwise occur (see .Sx "Message states" ) , and .Va keep to not remove empty system MBOX mailbox files (or all empty such files if .Va posix .Pf aka\0 Ev POSIXLY_CORRECT mode has been enabled) to avoid mangling of file permissions when files eventually get recreated. .Pp To enter interactive mode even if the initial mailbox is empty it sets .Va emptystart , .Va editheaders to allow editing of headers as well as .Va fullnames to not strip down addresses in compose mode, and .Va quote to include the message that is being responded to when .Ic reply Ns ing, which is indented by an .Va indentprefix that also deviates from standard imposed settings. .Va mime-counter-evidence is fully enabled, too. . .Pp Some random remarks. The file mode creation mask can be managed explicitly via the variable .Va umask . Files and shell pipe output can be .Ic source Ns d for evaluation, also during startup from within the .Sx "Resource files" . .\" }}} . .\" .Ss "On sending mail, and non-interactive mode" {{{ .Ss "On sending mail, and non-interactive mode" . To send a message to one or more people, using a local or built-in .Va mta (Mail-Transfer-Agent) transport to actually deliver the generated mail message, \*(UA can be invoked with arguments which are the names of people to whom the mail will be sent, and the command line options .Fl b and .Fl c can be used to add (blind) carbon copy receivers: . .Bd -literal -offset indent # Via test MTA $ echo Hello, world | \*(uA -:/ -Smta=test -s test $LOGNAME # Via sendmail(1) MTA $ ' eric@exam.ple # With SMTP (no real sending due to -d debug dry-run) $ LC_ALL=C \*(uA -d -:/ -Sv15-compat -Sttycharset=utf8 \e -S mta=smtps://mylogin@exam.ple:465 -Ssmtp-auth=none \e -S from=scriptreply@exam.ple \e -a /etc/mail.rc -. \e eric@exam.ple < /tmp/letter.txt .Ed . .Pp If standard input is a terminal rather than the message to be sent, the user is expected to type in the message contents. In this compose mode \*(UA treats lines beginning with the character .Ql ~ special \(en these are so-called .Sx "COMMAND ESCAPES" , which can be used to read in files, process shell commands, add and edit attachments and more; e.g., .Ic ~v or .Ic ~e will start the .Ev VISUAL text .Ev EDITOR , respectively, to revise the message in its current state, .Ic ~h allows editing of the most important message headers, with the potent .Ic ~^ custom headers can be created, for example (more specifically than with .Fl C and .Va customhdr ) . \*(OPally .Ic ~? gives an overview of most other available command escapes. . .Pp The command escape .Ic ~. (see there) will call hooks, insert automatic injections and receivers, leave compose mode and send the message once it is completed. Aborting letter composition is possible with either of .Ic ~x or .Ic ~q , the latter of which will save the message in the file denoted by .Ev DEAD unless .Pf no Va save is set. And unless .Va ignoreeof is set the effect of .Ic ~. can also be achieved by typing end-of-transmission (EOT) via .Ql control-D .Pf ( Ql ^D ) at the beginning of an empty line, and .Ic ~q is always reachable by typing end-of-text (ETX) twice via .Ql control-C .Pf ( Ql ^C ) . . .Pp A number of .Sx ENVIRONMENT and .Sx "INTERNAL VARIABLES" can be used to alter default behavior. .Ic set Ns ting (also via .Fl S ) .Va editalong will automatically startup an editor when compose mode is entered, and editing of headers additionally to plain body content can be enabled via .Va editheaders : \*(ID some, but not all headers can be created, edited or deleted in an editor, then. .Va askcc and .Va askbcc will cause the user to be prompted actively for (blind) carbon-copy recipients, respectively, and (the default) .Va asksend will request confirmation whether the message shall be sent. . .Pp The envelope sender address is defined by .Va from , explicitly defining an originating .Va hostname may be desirable, especially with the built-in SMTP Mail-Transfer-Agent .Va mta . .Sx "Character sets" for outgoing message and MIME part content are configurable via .Va sendcharsets , whereas input data is assumed to be in .Va ttycharset . Message data will be passed over the wire in a .Va mime-encoding . MIME parts aka attachments need to be assigned a .Ic mimetype , usually taken out of .Sx "The mime.types files" . Saving a copy of sent messages in a .Va record mailbox may be desirable \(en as for most mailbox .Ic file targets the value will undergo .Sx "Filename transformations" . Some introductional .Fl d or .Va debug sandbox dry-run tests will prove correctness. . .Pp Message recipients are subject to .Ic alternates filtering, and may not only be email addresses, but can also be names of mailboxes and even complete shell command pipe specifications. If the variable .Va expandaddr is not set then only email addresses like .Ql bob@exam.ple and plain user names (including MTA aliases) may be used, other types will be filtered out, giving a warning message. .Va expandaddr indeed allows further control over and adjustments of message recipients, e.g., user names can be expanded to network addresses by specifying .Ql namehostex . A network address that contains no domain-, but only a valid local user .Ql in angle brackets will be automatically expanded to a valid address when .Va hostname is not set, or set to a non-empty value; setting it to the empty value instructs \*(UA that the used .Va mta will perform the necessary expansion. The command .Ic addrcodec may help to generate standard compliant network addresses. . .\" When changing any of the following adjust any RECIPIENTADDRSPEC; .\" grep the latter for the complete picture .Pp If the variable .Va expandaddr is set then an extended set of recipient addresses will be accepted: Any name that starts with a vertical bar .Ql | character specifies a command pipe \(en the command string following the .Ql | is executed and the message is sent to its standard input; Likewise, any name that consists only of hyphen-minus .Ql - or starts with the character solidus .Ql / or the character sequence dot solidus .Ql ./ is treated as a file, regardless of the remaining content. Any other name which contains a commercial at .Ql @ character is a network address; Any other name which starts with a plus sign .Ql + character is a mailbox name; Any other name which contains a solidus .Ql / character but no exclamation mark .Ql \&! or percent sign .Ql % character before is also a mailbox name; What remains is treated as a network address. . .Bd -literal -offset indent $ echo bla | \*(uA -Sexpandaddr -s test ./mbox.mbox $ echo bla | \*(uA -Sexpandaddr -s test '|cat >> ./mbox.mbox' $ echo safe | LC_ALL=C \e \*(uA -:/ -Sv15-compat -Sttycharset=utf8 \e --set mime-force-sendout \e -Sexpandaddr=fail,-all,+addr,failinvaddr -s test \e -. bob@exam.ple .Ed . .Pp To create file-carbon-copies the special recipient header .Ql Fcc: may be used as often as desired. Its entire value (or body in standard terms) is interpreted as a .Ic file target, after having been subject to .Sx "Filename transformations" . Beside using the command escape .Ic ~^ (to create a .Ql Fcc header) this is the only way to create a file-carbon-copy without introducing an ambiguity regarding the interpretation of the address, e.g., to use file names with leading vertical bars or commercial ats. Like all other recipients .Ql Fcc: is subject to the checks of .Va expandaddr . Any local file and pipe command addressee honours the setting of .Va mbox-fcc-and-pcc . . .Pp It is possible to create personal distribution lists via the .Ic alias command, so that, for instance, the user can send mail to .Ql cohorts and have it go to a group of people. Different to the alias mechanism of most local .Va mta Ns s, often documented in .Xr aliases 5 and subject to the .Ql name constraint of .Va expandaddr , personal aliases will be expanded by \*(UA before the message is sent. They are thus a convenient alternative to specifying each addressee by itself, correlate with the active set of .Ic alternates , and are subject to .Va metoo filtering. \*(OPally MTA aliases can be expanded before sending messages by setting .Va mta-aliases . . .Bd -literal -offset indent ? alias cohorts bill jkf mark kridle@ucbcory ~/cohorts.mbox ? alias mark mark@exam.ple ? set mta-aliases=/etc/aliases .Ed . .Pp For the purpose of arranging a complete environment of settings that can be switched to with a single command or command line option there are .Ic account Ns s . Alternatively it is also possible to use a flat configuration, making use of so-called variable chains which automatically pick .Ql USER@HOST or .Ql HOST context-dependent variable variants: for example addressing .Ql Ic File Ns \& pop3://yaa@exam.ple would find .Va \&\&pop3-no-apop-yaa@exam.ple , .Va \&\&pop3-no-apop-exam.ple and .Va pop3-no-apop in order. See .Sx "On URL syntax and credential lookup" and .Sx "INTERNAL VARIABLES" . . .Pp The compose mode hooks .Va on-compose-enter , on-compose-splice , on-compose-leave and .Va on-compose-cleanup may be set to .Ic define Ns d macros and provide reliable and increasingly powerful mechanisms to perform automated message adjustments dependent on message context, for example addition of message signatures .Pf ( Va message-inject-head , message-inject-tail ) or creation of additional receiver lists (also by setting .Va autocc , autobcc ) . To achieve that the command .Ic digmsg may be used in order to query and adjust status of message(s). The splice hook can also make use of .Sx "COMMAND ESCAPES" . (\*(ID The compose mode hooks work for .Ic forward , mail , reply and variants; .Ic resend and .Ic Resend only provide the hooks .Va on-resend-enter and .Va on-resend-cleanup , which are pretty restricted due to the nature of the operation.) . .Pp To avoid environmental noise scripts should .Dq detach \*(UA from any configuration files and create a script-local environment, ideally with the command line options .Fl \&: to disable any configuration file in conjunction with repetitions of .Fl S to specify variables: . .Bd -literal -offset indent $ env LC_ALL=C \*(uA -:/ \e -Sv15-compat \e -Sttycharset=utf-8 -Smime-force-sendout \e -Sexpandaddr=fail,-all,failinvaddr \e -S mta=smtps://mylogin@exam.ple:465 -Ssmtp-auth=login \e -S from=scriptreply@exam.ple \e -s 'Subject to go' -a attachment_file \e -Sfullnames -. \e 'Recipient 1 ' rec2@exam.ple \e < content_file .Ed . .Pp As shown, scripts can .Dq fake a locale environment, the above specifies the all-compatible 7-bit clean .Ev LC_ALL .Dq C , but will nonetheless take and send UTF-8 in the message text by using .Va ttycharset . If character set conversion is compiled in .Pf ( Va features includes the term .Ql +iconv ) invalid (according to .Va ttycharset ) character input data would normally cause errors; setting .Va mime-force-sendout will instead, as a last resort, classify the input as binary data, and therefore allow message creation to be successful. (Such content can then be inspected either by installing a .Va pipe-TYPE/SUBTYPE handler for .Ql application/octet-stream , or possibly automatically through .Va mime-counter-evidence ) . . .Pp In interactive mode, which is introduced in the next section, messages can be sent by calling the .Ic mail command with a list of recipient addresses: . .Bd -literal -offset indent $ \*(uA -d -Squiet -Semptystart "/var/spool/mail/user": 0 messages ? mail "Recipient 1 ", rec2@exam.ple \&... ? # Will do the right thing (tm) ? m rec1@exam.ple rec2@exam.ple .Ed .\" }}} . .\" .Ss "On reading mail, and interactive mode" {{{ .Ss "On reading mail, and interactive mode" . When invoked without addressees \*(UA enters interactive mode in which mails may be read. When used like that the user's system .Va inbox (for more on mailbox types please see the command .Ic file ) is read in and a one line header of each message therein is displayed if the variable .Va header is set. The visual style of this summary of .Ic headers can be adjusted through the variable .Va headline and the possible sorting criterion via .Va autosort . Scrolling through .Va screen Ns fuls of .Ic headers can be performed with the command .Ic z . If the initially opened mailbox is empty \*(UA will instead exit immediately (after displaying a message) unless the variable .Va emptystart is set. . .Pp At the .Va prompt the command .Ic list will give a listing of all available commands and .Ic help will \*(OPally give a summary of some common ones. If the \*(OPal documentation strings are available (see .Va features ) one can type .Ql help X .Pf "(or " Ql \&?X ) and see the actual expansion of .Ql X and what its purpose is, i.e., commands can be abbreviated (note that POSIX defines some abbreviations, so that the alphabetical order of commands does not necessarily relate to the abbreviations; it is however possible to define overwrites with .Ic commandalias ) . These commands can also produce a more .Va verbose output. . .Pp Messages are given numbers (starting at 1) which uniquely identify messages; the current message \(en the .Dq dot \(en will either be the first new message, or the first unread message, or the first message of the mailbox; the internal variable .Va showlast will instead cause usage of the last message for this purpose. The command .Ic headers will display a .Va screen Ns ful of header summaries containing the .Dq dot , whereas .Ic from will display only the summaries of the given messages, defaulting to the .Dq dot . . .Pp Message content can be displayed with the command .Ic type .Pf ( Ql t , alias .Ic print ) . Here the variable .Va crt controls whether and when \*(UA will use the configured .Ev PAGER for display instead of directly writing to the user terminal .Va screen , the sole difference to the command .Ic more , which will always use the .Ev PAGER . The command .Ic top will instead only show the first .Va toplines of a message (maybe even compressed if .Va topsqueeze is set). Message display experience may improve by setting and adjusting .Va mime-counter-evidence , and also see .Sx "HTML mail and MIME attachments" . . .Pp By default the current message .Pf ( Dq dot ) is displayed, but like with many other commands it is possible to give a fancy message specification (see .Sx "Specifying messages" ) , e.g., .Ql t:u will display all unread messages, .Ql t. will display the .Dq dot , .Ql t 1 5 will type the messages 1 and 5, .Ql t 1-5 will type the messages 1 through 5, and .Ql t- and .Ql t+ will display the previous and the next message, respectively. The command .Ic search (a more substantial alias for .Ic from ) will display a header summary of the given message specification list instead of their content, e.g., the following will search for subjects: . .Pp .Dl ? from "'@Some subject to search for'" . .Pp In the default setup all header fields of a message will be .Ic type Ns d, but fields can be white- or blacklisted for a variety of applications by using the command .Ic headerpick , e.g., to restrict their display to a very restricted set for .Ic type : .Ql Ic \:headerpick Cd \:type retain Ar \:from to cc subject . In order to display all header fields of a message regardless of currently active ignore or retain lists, use the commands .Ic Type and .Ic Top ; .Ic Show will show the raw message content. Note that historically the global .Pa \*(UR not only adjusts the list of displayed headers, but also sets .Va crt . (\*(ID A yet somewhat restricted) Reliable scriptable message inspection is available via .Ic digmsg . . .Pp Dependent upon the configuration a line editor (see the section .Sx "On terminal control and line editor" ) aims at making the user experience with the many .Sx COMMANDS a bit nicer. When reading the system .Va inbox , or when .Fl f (or .Ic file ) specified a mailbox explicitly prefixed with the special .Ql %: modifier (to propagate it to a .Mx -sx .Sx "primary system mailbox" ) , then messages which have been read .Pf (see\0 Sx "Message states" ) will be automatically moved to a .Mx -sx .Sx "secondary mailbox" , the user's .Ev MBOX file, when the mailbox is left, either by changing the active mailbox or by quitting \*(UA \(en this automatic moving from a system- or primary- to the secondary mailbox is not performed when the variable .Va hold is set. Messages can also be explicitly .Ic move Ns d to other mailboxes, whereas .Ic copy keeps the original message. .Ic write can be used to write out data content of specific parts of messages. . .Pp After examining a message the user can .Ic reply Ql r to the sender and all recipients (which will also be placed in .Ql To: unless .Va recipients-in-cc is set), or .Ic Reply Ql R exclusively to the sender(s). The command .Ic Lreply knows how to apply a special addressee massage, see .Sx "Mailing lists" . Dependent on the presence and value of .Va quote the message being replied to will be included in a quoted form. .Ic forward Ns ing a message will allow editing the new message: the original message will be contained in the message body, adjusted according to .Ic headerpick . It is possible to .Ic resend or .Ic Resend messages: the former will add a series of .Ql Resent- headers, whereas the latter will not; different to newly created messages editing is not possible and no copy will be saved even with .Va record unless the additional variable .Va record-resent is set. When sending, replying or forwarding messages comments and full names will be stripped from recipient addresses unless the internal variable .Va fullnames is set. . .Pp Of course messages can be .Ic delete Ql d , and they can spring into existence again via .Ic undelete , or when the \*(UA session is ended via the .Ic exit or .Ic xit commands to perform a quick program termation. To end a mail processing session regulary and perform a full program exit one may issue the command .Ic quit . It will, among others, move read messages to the .Mx -sx .Sx "secondary mailbox" .Ev MBOX as necessary, discard deleted messages in the current mailbox, and update the \*(OPal (see .Va features ) line editor .Va history-file . By the way, whenever the main event loop is about to look out for the next input line it will trigger the hook .Va on-main-loop-tick . .\" }}} . .\" .Ss "HTML mail and MIME attachments" {{{ .Ss "HTML mail and MIME attachments" . Messages which are HTML-only become more and more common, and of course many messages come bundled with a bouquet of MIME (Multipurpose Internet Mail Extensions) parts. To get a notion of MIME types \*(UA has a default set of types built-in, onto which the content of .Sx "The mime.types files" will be added (as configured and allowed by .Va mimetypes-load-control ) . Types can also become registered with the command .Ic mimetype . To improve interaction with faulty MIME part declarations which are often seen in real-life messages, setting .Va mime-counter-evidence will allow verification of the given assertion, and possible provision of an alternative, better MIME type. . .Pp Whereas \*(UA \*(OPally supports a simple HTML-to-text filter for displaying HTML messages (indicated by .Ql +filter-html-tagsoup in .Va features ) , it cannot handle MIME types other than plain text itself. Instead programs need to become registered to deal with specific MIME types or file extensions. These programs may either prepare plain text versions of their input in order to enable \*(UA to integrate their output neatlessly in its own message visualization (a mode which is called .Cd copiousoutput ) , or display the content themselves, for example in an external graphical window: such handlers will only be considered by and for the command .Ic mimeview . . .Pp To install a handler program for a specific MIME type an according .Va pipe-TYPE/SUBTYPE variable needs to be set; to instead define a handler for a specific file extension the respective .Va pipe-EXTENSION variable can be used \(en these handlers take precedence. \*(OPally \*(UA supports mail user agent configuration as defined in RFC 1524; this mechanism (see .Sx "The Mailcap files" ) will be queried for display or quote handlers if none of the former two .\" TODO v15-compat "will be" -> "is" did; it will be the sole source for handlers of other purpose. A last source for handlers is the MIME type definition itself, if a type-marker has been registered with the command .Ic mimetype , which many of the built-in MIME types do. . .Pp For example, to display a HTML message inline (converted to a more fancy plain text representation than the built-in filter is capable to produce) with either of the text-mode browsers .Xr lynx 1 or .Xr elinks 1 , teach \*(UA about MathML documents and make it display them as plain text, and to open PDF attachments in an external PDF viewer, asynchronously and with some other magic attached: . .Bd -literal -offset indent ? if [ "$features" !% +filter-html-tagsoup ] ? #set pipe-text/html='?* elinks -force-html -dump 1' ? set pipe-text/html='?* lynx -stdin -dump -force_html' ? # Display HTML as plain text instead ? #set pipe-text/html=? ? endif ? mimetype ? application/mathml+xml mathml ? wysh set pipe-application/pdf='?&=? \e trap "rm -f \e"${MAILX_FILENAME_TEMPORARY}\e"" EXIT;\e trap "trap \e"\e" INT QUIT TERM; exit 1" INT QUIT TERM;\e mupdf "${MAILX_FILENAME_TEMPORARY}"' .Ed .\" }}} . .\" .Ss "Mailing lists" {{{ .Ss "Mailing lists" . Known or subscribed-to mailing lists may be flagged in the summary of .Ic headers .Pf ( Va headline format character .Ql %L ) , and will gain special treatment when sending mails: the variable .Va followup-to-honour will ensure that a .Ql Mail-\:Followup-\:To: header is honoured when a message is being replied to .Pf ( Ic reply and .Ic Lreply ) , and .Va followup-to controls creation of this header when creating .Ic mail Ns s, if the necessary user setup .Pf ( from , sender ) ; is available; then, it may also be created automatically, e.g., when list-replying via .Ic Lreply , when .Ic reply is used and the messages .Ql Mail-Followup-To: is honoured etc. . .Pp The commands .Ic mlist and .Ic mlsubscribe manage \*(UAs notion of which addresses are mailing lists. With the \*(OPal regular expression support any such address .Mx -ix "magic regular expression characters" which contains magic regular expression characters .Ql ( ^[]*+?|$ ; see .Xr re_format 7 or .Xr regex 7 , dependent on the host system) will be compiled and used as one, possibly matching many addresses. . .Bd -literal -offset indent ? set followup-to followup-to-honour=ask-yes \e reply-to-honour=ask-yes ? mlist a1@b1.c1 a2@b2.c2 '.*@lists\e.c3$' ? mlsubscribe a4@b4.c4 exact@lists.c3 .Ed . .Pp Known and subscribed lists differ in that for the latter the .Va user Ns s address is not part of a generated .Ql Mail-Followup-To: . There are exceptions, for example if multiple lists are addressed and not all have the subscription attribute. When replying to a message its list address .Pf ( Ql List-Post: header) is automatically and temporarily treated like a known .Ic mlist ; dependent on the variable .Va reply-to-honour an existing .Ql Reply-To: is used instead (if it is a single address on the same domain as .Ql List-Post: ) in order to accept a list administrator's wish that is supposed to have been manifested like that. . .Pp For convenience and compatibility with mail programs that do not honour the non-standard M-F-T, an automatic user entry in the carbon-copy .Ql Cc: address list of generated message can be created by setting .Va followup-to-add-cc . This entry will be added whenever the user will be placed in the .Ql Mail-Followup-To: list, and is not a regular addressee already. .\" }}} . .\" .Ss "Signed and encrypted messages with S/MIME" {{{ .Ss "Signed and encrypted messages with S/MIME" . \*(OP S/MIME provides two central mechanisms: message signing and message encryption. A signed message contains some data in addition to the regular text. The data can be used to verify that the message has been sent using a valid certificate, that the sender address matches that in the certificate, and that the message text has not been altered. Signing a message does not change its regular text; it can be read regardless of whether the recipients software is able to handle S/MIME. It is thus usually possible to sign all outgoing messages if so desired. . .Pp Encryption, in contrast, makes the message text invisible for all people except those who have access to the secret decryption key. To encrypt a message, the specific recipients public encryption key must be known. It is therefore not possible to send encrypted mail to people unless their key has been retrieved from either previous communication or public key directories. Because signing is performed with private keys, and encryption with public keys, messages should always be signed before becoming encrypted. . .Pp A central concept to S/MIME is that of the certification authority (CA). A CA is a trusted institution that issues certificates. For each of these certificates it can be verified that it really originates from the CA, provided that the CA's own certificate is previously known. A set of CA certificates is usually delivered and installed together with the cryptographical library that is used on the local system. Therefore reasonable security for S/MIME on the Internet is provided if the source that provides that library installation is trusted. It is also possible to use a specific pool of trusted certificates. If this is desired, .Va smime-ca-no-defaults should be set to avoid using the default certificate pool, and .Va smime-ca-file and/or .Va smime-ca-dir should be pointed to a trusted pool of certificates. A certificate cannot be more secure than the method its CA certificate has been retrieved with. . .Pp This trusted pool of certificates is used by the command .Ic verify to ensure that the given S/MIME messages can be trusted. If so, verified sender certificates that were embedded in signed messages can be saved locally with the command .Ic certsave , and used by \*(UA to encrypt further communication with these senders: . .Bd -literal -offset indent ? certsave FILENAME ? set smime-encrypt-USER@HOST=FILENAME \e smime-cipher-USER@HOST=AES256 .Ed . .Pp To sign outgoing messages, in order to allow receivers to verify the origin of these messages, a personal S/MIME certificate is required. \*(UA supports password-protected personal certificates (and keys), see .Va smime-sign-cert . The section .Sx "On URL syntax and credential lookup" gives an overview of the possible sources of user credentials, and .Sx "S/MIME step by step" shows examplarily how a private S/MIME certificate can be obtained. In general, if such a private key plus certificate .Dq pair is available, all that needs to be done is to set some variables: . .Bd -literal -offset indent ? set smime-sign-cert=ME@exam.ple.paired \e smime-sign-digest=SHA512 \e smime-sign .Ed . .Pp Variables of interest for S/MIME in general are .Va smime-ca-dir , .Va smime-ca-file , .Va smime-ca-flags , .Va smime-ca-no-defaults , .Va smime-crl-dir , .Va smime-crl-file . For S/MIME signing of interest are .Va smime-sign , .Va smime-sign-cert , .Va smime-sign-include-certs and .Va smime-sign-digest . Additional variables of interest for S/MIME en- and decryption: .Va smime-cipher and .Va smime-encrypt-USER@HOST . S/MIME is available if .Ql +smime is included in .Va features . . .Pp \*(ID Note that neither S/MIME signing nor encryption applies to message subjects or other header fields yet. Thus they may not contain sensitive information for encrypted messages, and cannot be trusted even if the message content has been verified. When sending signed messages, it is recommended to repeat any important header information in the message text. .\" }}} . .\" .Ss "On URL syntax and credential lookup" {{{ .Ss "On URL syntax and credential lookup" . \*(IN For accessing protocol-specific resources usage of Uniform Resource Locators (URL, RFC 3986) has become omnipresent. \*(UA expects and understands URLs in a .Dq normalized variant which is not used in data exchange, but only meant as a compact, easy-to-use way of defining and representing information in a well-known notation; as such they do not conform to any real standard. Optional parts are placed in brackets .Ql [] , optional either because there also exist other ways to define the information in question, or because the part is protocol-specific, e.g., .Ql /path is used by the \*(OPal Maildir .Ic file type and the IMAP protocol, but not by POP3. If as part of the URL any of .Ql USER and .Ql PASSWORD is specified, then the URL percent encoded form must be used (RFC 3986; the command .Ic urlcodec can be used to perform the encoding): . .Pp .Dl PROTOCOL://[USER[:PASSWORD]@]server[:port][/path] . .Pp Many internal variables of \*(UA exist in multiple versions, called variable chains for the rest of this document: the plain .Ql variable as well as .Ql variable-HOST and .Ql variable-USER@HOST . Here .Ql HOST indeed means .Ql server:port if a .Ql port had been specified in the respective URL, otherwise it refers to the plain .Ql server . Also, .Ql USER is not truly the .Ql USER that had been found when doing the user chain lookup as is described below, i.e., this .Ql USER will never be in URL percent encoded form, whether it came from an URL or not; i.e., variable chain name extensions of .Sx "INTERNAL VARIABLES" must not be URL percent encoded. . .Pp For example, whether an hypothetical URL .Ql smtp://hey%3Ayou@our.house had been given that includes a user, or whether the URL was .Ql smtp://our.house and the user had been found differently, to lookup the variable chain .Va smtp-use-starttls \*(UA first looks for whether .Ql smtp-\:use-\:starttls-\:hey:you@our.house is defined, then whether .Ql smtp-\:use-\:starttls-\:our.house exists before finally ending up looking at the plain variable itself. . .Pp \*(UA obeys the following logic scheme when dealing with the necessary credential information of an account: . .Bl -bullet .It A user is always required. If no .Ql USER has been given in the URL the variables .Va user-HOST and .Va user are looked up. If no such variable(s) can be found then \*(UA will, when enforced by the \*(OPal variables .Va netrc-lookup-HOST or .Va netrc-lookup , search .Sx "The .netrc file" of the user for a .Ql HOST specific entry which provides a .Ql login name: this lookup will only succeed if unambiguous (one possible matching entry for .Ql HOST ) . .Pp If there is still no .Ql USER then \*(UA will fall back to the user who is supposed to run \*(UA, the identity of which has been fixated during \*(UA startup and is known to be a valid user on the current host. . .It Authentication: unless otherwise noted this will lookup the .Va PROTOCOL-auth-USER@HOST , PROTOCOL-auth-HOST , PROTOCOL-auth variable chain, falling back to a protocol-specific default should this have no success. . .It If no .Ql PASSWORD has been given in the URL, then if the .Ql USER has been found through the \*(OPal .Va netrc-lookup that may have already provided the password, too. Otherwise the variable chain .Va password-USER@HOST , password-HOST , password is looked up and used if existent. .Pp Afterwards the complete \*(OPal variable chain .Va netrc-lookup-USER@HOST , netrc-lookup-HOST , netrc-lookup is looked up. If set, the .Ic netrc cache is searched for a password only (multiple user accounts for a single machine may exist as well as a fallback entry without user but with a password). .Pp If at that point there is still no password available, but the (protocols') chosen authentication type requires a password, then in interactive mode the user will be prompted on the terminal. .El . .Pp .Sy Note: S/MIME verification works relative to the values found in the .Ql From: (or .Ql Sender: ) header field(s), which means that the values of .Va smime-sign , smime-sign-cert , smime-sign-include-certs and .Va smime-sign-digest will not be looked up using the .Ql USER and .Ql HOST chains from above but instead use the corresponding values from the message that is being worked on. In unusual cases multiple and different .Ql USER and .Ql HOST combinations may therefore be involved \(en on the other hand those unusual cases become possible. The usual case is as short as: . .Bd -literal -offset indent set mta=smtp://USER:PASS@HOST smtp-use-starttls \e smime-sign smime-sign-cert=+smime.pair .Ed . .Pp The section .Sx EXAMPLES contains complete example configurations. .\" }}} . .\" .Ss "Encrypted network communication" {{{ .Ss "Encrypted network communication" . \*(OP SSL (Secure Sockets Layer) aka its successor TLS (Transport Layer Security) are protocols which aid in securing communication by providing a safely initiated and encrypted network connection. A central concept of TLS is that of certificates: as part of each network connection setup a (set of) certificates will be exchanged, and by using those the identity of the network peer can be cryptographically verified; if possible the TLS/SNI (ServerNameIndication) extension will be enabled in order to allow servers fine-grained control over the certificates being used. TLS works by using a locally installed pool of trusted certificates, and verifying the connection peer succeeds if that provides a certificate which has been issued or is trusted by any certificate in the trusted local pool. . .Pp The local pool of trusted so-called CA (Certification Authority) certificates is usually delivered with the used TLS library, and will be selected automatically. It is also possible to use a specific pool of trusted certificates. If this is desired, .Va tls-ca-no-defaults should be set to avoid using the default certificate pool, and .Va tls-ca-file and/or (with special preparation) .Va tls-ca-dir should be pointed to a trusted pool of certificates. A certificate cannot be more secure than the method its CA certificate has been retrieved with. For inspection or other purposes, the certificate of a server (as seen when connecting to it) can be fetched like this: . .Bd -literal -offset indent $ &1 | tee log.txt .Ed . .Pp \*(UA also supports a mode of operation in which certificates are not at all matched against a local pool of CA certificates. Instead a message digest will be calculated for the certificate presented by the connection peer, and be compared against .Va tls-fingerprint (a variable chain that picks up .Ql USER@HOST or .Ql HOST context-dependent variable variants), and the connection will succeed if the calculated digest equals the expected one. The used message digest can be configured via (the chain) .Va tls-fingerprint-digest . The command .Ic tls may be helpful. . .Pp It depends on the used protocol whether encrypted communication is possible, and which configuration steps have to be taken to enable it. Some protocols, e.g., POP3S, are implicitly encrypted, others, like POP3, can upgrade a plain text connection if so requested. For example, to use the .Ql STLS that POP3 offers (a member of) the variable (chain) .Va pop3-use-starttls needs to be set, with convenience via .Ic shortcut : . .Bd -literal -offset indent shortcut encpop1 pop3s://pop1.exam.ple shortcut encpop2 pop3://pop2.exam.ple set pop3-use-starttls-pop2.exam.ple set mta=smtps://smtp.exam.ple:465 set mta=smtp://smtp.exam.ple smtp-use-starttls .Ed . .Pp Normally that is all there is to do, given that TLS libraries try to provide safe defaults, plenty of knobs however exist to adjust settings. For example certificate verification settings can be fine-tuned via .Va tls-ca-flags , and the TLS configuration basics are accessible via .Va tls-config-pairs , for example to specify the allowed protocols or cipher lists that a communication channel may use. In the past hints on how to restrict the set of protocols to highly secure ones were indicated, but as of the time of this writing the list of protocols or ciphers may need to become relaxed in order to be able to connect to some servers; the following example allows connecting to a .Dq Lion that uses OpenSSL 0.9.8za from June 2014 (refer to .Sx "INTERNAL VARIABLES" for more on variable chains): . .Bd -literal -offset indent wysh set tls-config-pairs-lion@exam.ple='MinProtocol=TLSv1.1,\e CipherString=TLSv1.2:!aNULL:!eNULL:\e ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:\e DHE-RSA-AES256-SHA:@STRENGTH' .Ed . .Pp The OpenSSL program .Xr ciphers 1 can be used and should be referred to when creating a custom cipher list. Variables of interest for TLS in general are .Va tls-ca-dir , .Va tls-ca-file , .Va tls-ca-flags , .Va tls-ca-no-defaults , .Va tls-config-file , .Va tls-config-module , .Va tls-config-pairs , .Va tls-crl-dir , .Va tls-crl-file , .Va tls-rand-file as well as .Va tls-verify . Also see .Va tls-features . TLS is available if .Ql +tls is included in .Va features . .\" }}} . .\" .Ss "Character sets" {{{ .Ss "Character sets" . \*(OP \*(UA detects the character set of the terminal by using mechanisms that are controlled by the .Ev LC_CTYPE environment variable (in fact .Ev LC_ALL , .Ev \&\&LC_CTYPE , .Ev LANG , in that order, see there). The internal variable .Va ttycharset will be set to the detected terminal character set accordingly, and will thus show up in the output of commands like, e.g., .Ic set and .Ic varshow . . .Pp However, the user may give .Va ttycharset a value during startup, making it possible to send mail in a completely .Dq faked locale environment, an option which can be used to generate and send, e.g., 8-bit UTF-8 input data in a pure 7-bit US-ASCII .Ql LC_ALL=C environment (an example of this can be found in the section .Sx "On sending mail, and non-interactive mode" ) . Changing the value does not mean much beside that, because several aspects of the real character set are implied by the locale environment of the system, which stays unaffected by .Va ttycharset . . .Pp Messages and attachments which consist of 7-bit clean data will be classified as consisting of .Va charset-7bit character data. This is a problem if the .Va ttycharset character set is a multibyte character set that is also 7-bit clean. For example, the Japanese character set ISO-2022-JP is 7-bit clean but capable to encode the rich set of Japanese Kanji, Hiragana and Katakana characters: in order to notify receivers of this character set the mail message must be MIME encoded so that the character set ISO-2022-JP can be advertised! To achieve this, the variable .Va charset-7bit must be set to ISO-2022-JP. (Today a better approach regarding email is the usage of UTF-8, which uses 8-bit bytes for non-US-ASCII data.) . .Pp If the \*(OPal character set conversion capabilities are not available .Pf ( Va features does not include the term .Ql +iconv ) , then .Va ttycharset will be the only supported character set, it is simply assumed that it can be used to exchange 8-bit messages (over the wire an intermediate, configurable .Va mime-encoding may be applied), and the rest of this section does not apply; it may however still be necessary to explicitly set it if automatic detection fails, since in that case it defaults to .\" (Keep in SYNC: ./nail.1:"Character sets", mx-config.h:CHARSET_*!) LATIN1 aka ISO-8859-1 unless the operating system environment is known to always and exclusively support UTF-8 locales. . .Pp \*(OP When reading messages, their text is converted into .Va ttycharset as necessary in order to display them on the user's terminal. Unprintable characters and invalid byte sequences are detected and replaced by proper substitution characters. Character set mappings for source character sets can be established with the command .Ic charsetalias , which may be handy to work around faulty character set catalogues (e.g., to add a missing LATIN1 to ISO-8859-1 mapping), or to enforce treatment of one character set as another one (e.g., to interpret LATIN1 as CP1252). Also see .Va charset-unknown-8bit to deal with another hairy aspect of message interpretation. . .Pp When sending messages their parts and attachments are classified. Whereas no character set conversion is performed on those parts which appear to be binary data, the character set being used must be declared within the MIME header of an outgoing text part if it contains characters that do not conform to the set of characters that are allowed by the email standards. Permissible values for character sets used in outgoing messages can be declared using the .Va sendcharsets variable, and .Va charset-8bit , which defines a catch-all last-resort fallback character set that is implicitly appended to the list of character sets in .Va sendcharsets . . .Pp When replying to a message and the variable .Va reply-in-same-charset is set, then the character set of the message being replied to is tried first (still being a subject of .Ic charsetalias ) . And it is also possible to make \*(UA work even more closely related to the current locale setting automatically by using the variable .Va sendcharsets-else-ttycharset , please see there for more information. . .Pp All the specified character sets are tried in order unless the conversion of the part or attachment succeeds. If none of the tried (8-bit) character sets is capable to represent the content of the part or attachment, then the message will not be send and its text will optionally be .Va save Ns d in .Ev DEAD . If that is not acceptable, the variable .Va mime-force-sendout can be set in order to force sending of non-convertible text as .Ql application/octet-stream classified binary content instead; like this receivers still have the option to inspect message content (for example by setting .Va mime-counter-evidence ) . . .Pp In general, if a message saying .Dq cannot convert from a to b appears, either some characters are not appropriate for the currently selected (terminal) character set, or the needed conversion is not supported by the system. In the first case, it is necessary to set an appropriate .Ev LC_CTYPE locale and/or the variable .Va ttycharset . The best results are usually achieved when \*(UA is run in a UTF-8 locale on an UTF-8 capable terminal, in which case the full Unicode spectrum of characters is available. In this setup characters from various countries can be displayed, while it is still possible to use more simple character sets for sending to retain maximum compatibility with older mail clients. . .Pp On the other hand the POSIX standard defines a locale-independent 7-bit .Dq portable character set that should be used when overall portability is an issue, the even more restricted subset named .Dq portable filename character set consists of A-Z, a-z, 0-9, period .Ql \&. , underscore .Ql _ and hyphen-minus .Ql - . .\" }}} . .\" .Ss "Message states" {{{ .Ss "Message states" . \*(UA differentiates in between several message states; the current state will be reflected in the summary of .Ic headers if the .Va attrlist of the configured .Va headline allows, and .Sx "Specifying messages" dependent on their state is possible. When operating on the system .Va inbox , or in any other .Mx -sx .Sx "primary system mailbox" , special actions, like the automatic moving of messages to the .Mx -sx .Sx "secondary mailbox" .Ev MBOX , may be applied when the mailbox is left (also implicitly by program termination, unless the command .Ic exit was used) \(en however, because this may be irritating to users which are used to .Dq more modern mail-user-agents, the provided global .Pa \*(UR template sets the internal .Va hold and .Va keepsave variables in order to suppress this behaviour. . .Bl -hang -width ".It Ql new" .It Ql new Message has neither been viewed nor moved to any other state. Such messages are retained even in the .Mx -sx .Sx "primary system mailbox" . . .It Ql unread Message has neither been viewed nor moved to any other state, but the message was present already when the mailbox has been opened last: Such messages are retained even in the .Mx -sx .Sx "primary system mailbox" . . .It Ql read The message has been processed by one of the following commands: .Ic ~f , .Ic ~m , .Ic ~F , .Ic ~M , .Ic copy , .Ic mbox , .Ic next , .Ic pipe , .Ic Print , .Ic print , .Ic top , .Ic Type , .Ic type , .Ic undelete . The commands .Ic dp and .Ic dt will always try to automatically .Dq step and .Ic type the .Dq next logical message, and may thus mark multiple messages as read, the .Ic delete command will do so if the internal variable .Va autoprint is set. .Pp Except when the .Ic exit command is used, messages that are in a .Mx -sx .Sx "primary system mailbox" and are in .Ql read state when the mailbox is left will be saved in the .Mx -sx .Sx "secondary mailbox" .Ev MBOX unless the internal variable .Va hold it set. . .It Ql deleted The message has been processed by one of the following commands: .Ic delete , .Ic dp , .Ic dt . Only .Ic undelete can be used to access such messages. . .It Ql preserved The message has been processed by a .Ic preserve command and it will be retained in its current location. . .It Ql saved The message has been processed by one of the following commands: .Ic save or .Ic write . Unless when the .Ic exit command is used, messages that are in a .Mx -sx .Sx "primary system mailbox" and are in .Ql saved state when the mailbox is left will be deleted; they will be saved in the .Mx -sx .Sx "secondary mailbox" .Ev MBOX when the internal variable .Va keepsave is set. .El . .Pp In addition to these message states, flags which otherwise have no technical meaning in the mail system except allowing special ways of addressing them when .Sx "Specifying messages" can be set on messages. These flags are saved with messages and are thus persistent, and are portable between a set of widely used MUAs. . .Bl -hang -width ".It Ic answered" .It Ic answered Mark messages as having been answered. .It Ic draft Mark messages as being a draft. .It Ic flag Mark messages which need special attention. .El .\" }}} . .\" .Ss "Specifying messages" {{{ .Ss "Specifying messages" . \*(NQ Commands which take .Sx "Message list arguments" , such as .Ic from aka\& .Ic search , .Ic type and .Ic delete , can be given a list of message numbers as arguments to apply to a number of messages at once. Thus .Ql delete 1 2 deletes messages 1 and 2, whereas .Ql delete 1-5 will delete the messages 1 through 5. In sorted or threaded mode (see the .Ic sort command), .Ql delete 1-5 will delete the messages that are located between (and including) messages 1 through 5 in the sorted/threaded order, as shown in the .Ic headers summary. The following special message names exist: . . .Bl -tag -width ".It Ar BaNg" .It Ar \&. The current message, the so-called .Dq dot . . .It Ar \&; The message that was previously the current message; needs to be quoted. . .It Ar \&, The parent message of the current message, that is the message with the Message-ID given in the .Ql In-Reply-To: field or the last entry of the .Ql References: field of the current message. . .It Ar - The previous undeleted message, or the previous deleted message for the .Ic undelete command; In .Ic sort Ns ed or .Ql thread Ns ed mode, the previous such message in the according order. . .It Ar + The next undeleted message, or the next deleted message for the .Ic undelete command; In .Ic sort Ns ed or .Ql thread Ns ed mode, the next such message in the according order. . .It Ar ^ The first undeleted message, or the first deleted message for the .Ic undelete command; In .Ic sort Ns ed or .Ql thread Ns ed mode, the first such message in the according order. . .It Ar $ The last message; In .Ic sort Ns ed or .Ql thread Ns ed mode, the last such message in the according order. Needs to be quoted. . .It Ar & Ns Ar x In .Ql thread Ns ed .Ic sort mode, selects the message addressed with .Ar x , where .Ar x is any other message specification, and all messages from the thread that begins at it. Otherwise it is identical to .Ar x . If .Ar x is omitted, the thread beginning with the current message is selected. . .It Ar * All messages. . .It Ar ` All messages that were included in the .Sx "Message list arguments" of the previous command; needs to be quoted. . .It Ar x-y An inclusive range of message numbers. Selectors that may also be used as endpoints include any of .Ar .;-+^$ . . .It Ar address A case-insensitive .Dq any substring matches search against the .Ql From: header, which will match addresses (too) even if .Va showname is set (and POSIX says .Dq any address as shown in a header summary shall be matchable in this form ) ; However, if the .Va allnet variable is set, only the local part of the address is evaluated for the comparison, not ignoring case, and the setting of .Va showname is completely ignored. For finer control and match boundaries use the .Ql @ search expression. . .It Ar / Ns Ar string All messages that contain .Ar string in the subject field (case ignored according to locale). See also the .Va searchheaders variable. If .Ar string is empty, the string from the previous specification of that type is used again. . . .It Xo Op Ar @ Ns Ar name-list Ns .Ar @ Ns Ar expr .Xc All messages that contain the given case-insensitive search .Ar expr Ns ession; If the \*(OPal regular expression support is available .Ar expr will be interpreted as (an extended) one if any of the .Mx -sx .Sx "magic regular expression characters" is seen. If the optional .Ar @ Ns Ar name-list part is missing the search is restricted to the subject field body, but otherwise .Ar name-list specifies a comma-separated list of header fields to search, e.g., . .Pp .Dl '@to,from,cc@Someone i ought to know' . .Pp In order to search for a string that includes a .Ql @ (commercial at) character the .Ar name-list is effectively non-optional, but may be given as the empty string. Also, specifying an empty search .Ar expr Ns ession will effectively test for existence of the given header fields. Some special header fields may be abbreviated: .Ql f , .Ql t , .Ql c , .Ql b and .Ql s will match .Ql From , .Ql To , .Ql Cc , .Ql Bcc and .Ql Subject , respectively and case-insensitively. \*(OPally, and just like .Ar expr , .Ar name-list will be interpreted as (an extended) regular expression if any of the .Mx -sx .Sx "magic regular expression characters" is seen. . .Pp The special names .Ql header or .Ql < can be used to search in (all of) the header(s) of the message, and the special names .Ql body or .Ql > and .Ql text or .Ql = will perform full text searches \(en whereas the former searches only the body, the latter also searches the message header (\*(ID this mode yet brute force searches over the entire decoded content of messages, including administrativa strings). . .Pp This specification performs full text comparison, but even with regular expression support it is almost impossible to write a search expression that safely matches only a specific address domain. To request that the body content of the header is treated as a list of addresses, and to strip those down to the plain email address which the search expression is to be matched against, prefix the effective .Ar name-list with a tilde .Ql ~ : . .Pp .Dl '@~f,c@@a\e.safe\e.domain\e.match$' . . .It Ar :c All messages of state or with matching condition .Ql c , where .Ql c is one or multiple of the following colon modifiers: .Pp .Bl -tag -compact -width ".It Ar :M" .It Ar a .Ic answered messages (cf. the variable .Va markanswered ) . .It Ar d .Ql deleted messages (for the .Ic undelete and .Ic from commands only). .It Ar f .Ic flag Ns ged messages. .It Ar L Messages with receivers that match .Ic mlsubscribe Ns d addresses. .It Ar l Messages with receivers that match .Ic mlist Ns ed addresses. .It Ar n .Ql new messages. .It Ar o Old messages (any not in state .Ql read or .Ql new ) . .It Ar r .Ql read messages. .It Ar S \*(OP Messages with unsure spam classification (see .Sx "Handling spam" ) . .It Ar s \*(OP Messages classified as spam. .It Ar t Messages marked as .Ic draft . .It Ar u .Ql unread messages. .El .El . . .Pp \*(OP IMAP-style SEARCH expressions may also be used. These consist of keywords and criterions, and because .Sx "Message list arguments" are split into tokens according to .Sx "Shell-style argument quoting" it is necessary to quote the entire IMAP search expression in order to ensure that it remains a single token. This addressing mode is available with all types of mailbox .Ic folder Ns s; \*(UA will perform the search locally as necessary. Strings must be enclosed by double quotes .Ql \&" in their entirety if they contain whitespace or parentheses; within the quotes, only reverse solidus .Ql \e is recognized as an escape character. All string searches are case-insensitive. When the description indicates that the .Dq envelope representation of an address field is used, this means that the search string is checked against both a list constructed as . .Bd -literal -offset indent \&'(\*qname\*q \*qsource\*q \*qlocal-part\*q \*qdomain-part\*q)' .Ed . .Pp for each address, and the addresses without real names from the respective header field. These search expressions can be nested using parentheses, see below for examples. . .Pp .Bl -tag -compact -width ".It Ar _n_u" .It Ar ( criterion ) All messages that satisfy the given .Ar criterion . .It Ar ( criterion1 criterion2 ... criterionN ) All messages that satisfy all of the given criteria. . .It Ar ( or criterion1 criterion2 ) All messages that satisfy either .Ar criterion1 or .Ar criterion2 , or both. To connect more than two criteria using .Ql or specifications have to be nested using additional parentheses, as with .Ql (or a (or b c)) , since .Ql (or a b c) really means .Ql ((a or b) and c) . For a simple .Ql or operation of independent criteria on the lowest nesting level, it is possible to achieve similar effects by using three separate criteria, as with .Ql (a) (b) (c) . . .It Ar ( not criterion ) All messages that do not satisfy .Ar criterion . .It Ar ( bcc \*q Ns Ar string Ns Ar \*q ) All messages that contain .Ar string in the envelope representation of the .Ql Bcc: field. .It Ar ( cc \*q Ns Ar string Ns Ar \*q ) All messages that contain .Ar string in the envelope representation of the .Ql Cc: field. .It Ar ( from \*q Ns Ar string Ns Ar \*q ) All messages that contain .Ar string in the envelope representation of the .Ql From: field. .It Ar ( subject \*q Ns Ar string Ns Ar \*q ) All messages that contain .Ar string in the .Ql Subject: field. .It Ar ( to \*q Ns Ar string Ns Ar \*q ) All messages that contain .Ar string in the envelope representation of the .Ql To: field. .It Ar ( header name \*q Ns Ar string Ns Ar \*q ) All messages that contain .Ar string in the specified .Ql Name: field. .It Ar ( body \*q Ns Ar string Ns Ar \*q ) All messages that contain .Ar string in their body. .It Ar ( text \*q Ns Ar string Ns Ar \*q ) All messages that contain .Ar string in their header or body. .It Ar ( larger size ) All messages that are larger than .Ar size (in bytes). .It Ar ( smaller size ) All messages that are smaller than .Ar size (in bytes). . .It Ar ( before date ) All messages that were received before .Ar date , which must be in the form .Ql d[d]-mon-yyyy , where .Ql d denotes the day of the month as one or two digits, .Ql mon is the name of the month \(en one of .Ql Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec , and .Ql yyyy is the year as four digits, e.g., .Ql 28-Dec-2012 . . .It Ar ( on date ) All messages that were received on the specified date. .It Ar ( since date ) All messages that were received since the specified date. .It Ar ( sentbefore date ) All messages that were sent on the specified date. .It Ar ( senton date ) All messages that were sent on the specified date. .It Ar ( sentsince date ) All messages that were sent since the specified date. .It Ar () The same criterion as for the previous search. This specification cannot be used as part of another criterion. If the previous command line contained more than one independent criterion then the last of those criteria is used. .El .\" }}} . .\" .Ss "On terminal control and line editor" {{{ .Ss "On terminal control and line editor" . \*(OP Terminal control will be realized through one of the standard .Ux libraries, either the .Lb libtermcap , or, alternatively, the .Lb libterminfo , both of which will be initialized to work with the environment variable .Ev TERM . Terminal control will enhance or enable interactive usage aspects, e.g., .Sx "Coloured display" , and extend behaviour of the Mailx-Line-Editor (MLE), which may learn the byte-sequences of keys like the cursor- and function-keys. . .Pp The internal variable .Va termcap can be used to overwrite settings or to learn (correct(ed)) keycodes. Actual library interaction can be disabled completely by setting .Va termcap-disable ; .Va termcap will be queried regardless, which is true even if the \*(OPal library support has not been enabled at configuration time as long as some other \*(OP which (may) query terminal control sequences has been enabled. \*(UA can be told to enter an alternative exclusive screen, the so-called ca-mode, by setting .Va termcap-ca-mode ; this requires sufficient terminal support, and the used .Ev PAGER may also need special configuration, dependent on the value of .Va crt . . .Pp \*(OP The built-in Mailx-Line-Editor (MLE) should work in all environments which comply to the ISO C standard .St -isoC-amd1 , and will support wide glyphs if possible (the necessary functionality had been removed from ISO C, but was included in .St -xpg4 ) . Usage of a line editor in interactive mode can be prevented by setting .Va line-editor-disable . Especially if the \*(OPal terminal control support is missing setting entries in the internal variable .Va termcap will help shall the MLE misbehave, see there for more. The MLE can support a little bit of .Ic colour . . .Pp \*(OP If the .Ic history feature is available then input from line editor prompts will be saved in a history list that can be searched in and be expanded from. Such saving can be prevented by prefixing input with any amount of whitespace. Aspects of history, like allowed content and maximum size, as well as whether history shall be saved persistently, can be configured with the internal variables .Va history-file , .Va history-gabby , .Va history-gabby-persist and .Va history-size . There also exists the macro hook .Va on-history-addition which can be used to apply fine control on what enters history. . .Pp The MLE supports a set of editing and control commands. By default (as) many (as possible) of these will be assigned to a set of single-letter control codes, which should work on any terminal (and can be generated by holding the .Dq control key while pressing the key of desire, e.g., .Ql control-D ) . If the \*(OPal .Ic bind command is available then the MLE commands can also be accessed freely by assigning the command name, which is shown in parenthesis in the list below, to any desired key-sequence, and the MLE will instead and also use .Ic bind to establish its built-in key bindings (more of them if the \*(OPal terminal control is available), an action which can then be suppressed completely by setting .Va line-editor-no-defaults . .Sx "Shell-style argument quoting" notation is used in the following; combinations not mentioned either cause job control signals or do not generate a (unique) keycode: . . .Pp .Bl -tag -compact -width ".It Ql \eBa" .It Ql \ecA Go to the start of the line .Mx .Pf ( Cd mle-go-home ) . . .It Ql \ecB Move the cursor backward one character .Mx .Pf ( Cd mle-go-bwd ) . . .It Ql \ecC .Xr raise 3 .Ql SIGINT .Mx .Pf ( Cd mle-raise-int ) . . .It Ql \ecD Forward delete the character under the cursor; quits \*(UA if used on the empty line unless the internal variable .Va ignoreeof is set .Mx .Pf ( Cd mle-del-fwd ) . . .It Ql \ecE Go to the end of the line .Mx .Pf ( Cd mle-go-end ) . . .It Ql \ecF Move the cursor forward one character .Mx .Pf ( Cd mle-go-fwd ) . . .It Ql \ecG Cancel current operation, full reset. If there is an active history search or tabulator expansion then this command will first reset that, reverting to the former line content; thus a second reset is needed for a full reset in this case .Mx .Pf ( Cd mle-reset ) . . .It Ql \ecH Backspace: backward delete one character .Mx .Pf ( Cd mle-del-bwd ) . . .It Ql \ecI \*(NQ Horizontal tabulator: try to expand the word before the cursor, supporting the usual .Sx "Filename transformations" .Mx .Pf ( Cd mle-complete ; this is affected by .Cd mle-quote-rndtrip and .Va line-editor-cpl-word-breaks ) . . .It Ql \ecJ Newline: commit the current line .Mx .Pf ( Cd mle-commit ) . . .It Ql \ecK Cut all characters from the cursor to the end of the line .Mx .Pf ( Cd mle-snarf-end ) . . .It Ql \ecL Repaint the line .Mx .Pf ( Cd mle-repaint ) . . .It Ql \ecN \*(OP Go to the next history entry .Mx .Pf ( Cd mle-hist-fwd ) . . .It Ql \ecO (\*(OPally context-dependent) Invokes the command .Ic dt . . .It Ql \ecP \*(OP Go to the previous history entry .Mx .Pf ( Cd mle-hist-bwd ) . . .It Ql \ecQ Toggle roundtrip mode shell quotes, where produced, on and off .Mx .Pf ( Cd mle-quote-rndtrip ) . This setting is temporary, and will be forgotten once the command line is committed; also see .Ic shcodec . . .It Ql \ecR \*(OP Complete the current line from (the remaining) older history entries .Mx .Pf ( Cd mle-hist-srch-bwd ) . . .It Ql \ecS \*(OP Complete the current line from (the remaining) newer history entries .Mx .Pf ( Cd mle-hist-srch-fwd ) . . .It Ql \ecT Paste the snarf buffer .Mx .Pf ( Cd mle-paste ) . . .It Ql \ecU The same as .Ql \ecA followed by .Ql \ecK .Mx .Pf ( Cd mle-snarf-line ) . . .It Ql \ecV Prompts for a Unicode character (hexadecimal number without prefix, see .Ic vexpr ) to be inserted .Mx .Pf ( Cd mle-prompt-char ) . Note this command needs to be assigned to a single-letter control code in order to become recognized and executed during input of a key-sequence (only three single-letter control codes can be used for that shortcut purpose); this control code is then special-treated and thus cannot be part of any other sequence (because it will trigger the .Cd mle-prompt-char function immediately). . .It Ql \ecW Cut the characters from the one preceding the cursor to the preceding word boundary .Mx .Pf ( Cd mle-snarf-word-bwd ) . . .It Ql \ecX Move the cursor forward one word boundary .Mx .Pf ( Cd mle-go-word-fwd ) . . .It Ql \ecY Move the cursor backward one word boundary .Mx .Pf ( Cd mle-go-word-bwd ) . . .It Ql \ecZ .Xr raise 3 .Ql SIGTSTP .Mx .Pf ( Cd mle-raise-tstp ) . . .It Ql \ec[ Escape: reset a possibly used multibyte character input state machine and \*(OPally a lingering, incomplete key binding .Mx .Pf ( Cd mle-cancel ) . This command needs to be assigned to a single-letter control code in order to become recognized and executed during input of a key-sequence (only three single-letter control codes can be used for that shortcut purpose). This control code may also be part of a multi-byte sequence, but if a sequence is active and the very control code is currently also an expected input, then the active sequence takes precedence and will consume the control code. . .It Ql \ec\e (\*(OPally context-dependent) Invokes the command .Ql Ic z Ns + . . .It Ql \ec] (\*(OPally context-dependent) Invokes the command .Ql Ic z Ns $ . . .It Ql \ec^ (\*(OPally context-dependent) Invokes the command .Ql Ic z Ns 0 . . .It Ql \ec_ Cut the characters from the one after the cursor to the succeeding word boundary .Mx .Pf ( Cd mle-snarf-word-fwd ) . . .It Ql \ec? Backspace: .Cd mle-del-bwd . . .It \(en .Mx .Cd mle-bell : ring the audible bell. . .It \(en \*(OP .Mx .Cd mle-clear-screen : move the cursor home and clear the screen. . .It \(en .Mx .Cd mle-fullreset : different to .Cd mle-reset this will immediately reset a possibly active search etc. . .It \(en .Mx .Cd mle-go-screen-bwd : move the cursor backward one screen width. . .It \(en .Mx .Cd mle-go-screen-fwd : move the cursor forward one screen width. . .It \(en .Mx .Cd mle-raise-quit: .Xr raise 3 .Ql SIGQUIT . .El .\" }}} . .\" .Ss "Coloured display" {{{ .Ss "Coloured display" . \*(OP \*(UA can be configured to support a coloured display and font attributes by emitting ANSI aka ISO 6429 SGR (select graphic rendition) escape sequences. Usage of colours and font attributes solely depends upon the capability of the detected terminal type that is defined by the environment variable .Ev TERM and which can be fine-tuned by the user via the internal variable .Va termcap . . .Pp On top of what \*(UA knows about the terminal the boolean variable .Va colour-pager defines whether the actually applicable colour and font attribute sequences should also be generated when output is going to be paged through the external program defined by the environment variable .Ev PAGER (also see .Va crt Ns ). This is not enabled by default because different pager programs need different command line switches or other configuration in order to support those sequences. \*(UA however knows about some widely used pagers and in a clean environment it is often enough to simply set .Va colour-pager ; please refer to that variable for more on this topic. . .Pp Colours and font attributes can be managed with the multiplexer command .Ic colour , and .Ic uncolour can be used to remove mappings of a given colour type. If the variable .Va colour-disable is set then any active usage of colour and font attribute sequences is suppressed without affecting possibly established .Ic colour mappings. Since colours are available if any of the standard I/O descriptors it opened on a terminal, it might make sense to conditionalize the colour setup by encapsulating it with .Ic if .Pf ( Ql terminal indeed means .Dq interactive ) : . .Bd -literal -offset indent if terminal && [ "$features" =% +colour ] colour iso view-msginfo ft=bold,fg=green colour iso view-header ft=bold,fg=red (from|subject) # regex colour iso view-header fg=red uncolour iso view-header from,subject colour iso view-header ft=bold,fg=magenta,bg=cyan colour 256 view-header ft=bold,fg=208,bg=230 "subject,from" colour mono view-header ft=bold colour mono view-header ft=bold,ft=reverse subject,from endif .Ed .\" }}} . .\" .Ss "Handling spam" {{{ .Ss "Handling spam" . \*(OP \*(UA can make use of several spam interfaces for the purpose of identification of, and, in general, dealing with spam messages. A precondition of most commands in order to function is that the .Va spam-interface variable is set to one of the supported interfaces. .Sx "Specifying messages" that have been identified as spam is possible via their (volatile) .Ql is-spam state by using the .Ql Ar :s and .Ql Ar :S specifications, and their .Va attrlist entries will be used when displaying the .Va headline in the summary of .Ic headers . . .Bl -bullet .It .Ic spamrate rates the given messages and sets their .Ql is-spam flag accordingly. If the spam interface offers spam scores these can be shown in .Va headline by using the format .Ql %$ . .It .Ic spamham , .Ic spamspam and .Ic spamforget will interact with the Bayesian filter of the chosen interface and learn the given messages as .Dq ham or .Dq spam , respectively; the last command can be used to cause .Dq unlearning of messages; it adheres to their current .Ql is-spam state and thus reverts previous teachings. .It .Ic spamclear and .Ic spamset will simply set and clear, respectively, the mentioned volatile .Ql is-spam message flag, without any interface interaction. .El . .Pp The .Xr spamassassin 1 based .Va spam-interface .Ql spamc requires a running instance of the .Xr spamd 1 server in order to function, started with the option .Fl -allow-tell shall Bayesian filter learning be possible. . .Bd -literal -offset indent $ spamd -i localhost:2142 -i /tmp/.spamsock -d [-L] [-l] $ spamd --listen=localhost:2142 --listen=/tmp/.spamsock \e --daemonize [--local] [--allow-tell] .Ed . .Pp Thereafter \*(UA can make use of these interfaces: . .Bd -literal -offset indent $ \*(uA -Sspam-interface=spamc -Sspam-maxsize=500000 \e -Sspamc-command=/usr/local/bin/spamc \e -Sspamc-arguments="-U /tmp/.spamsock" -Sspamc-user= or $ \*(uA -Sspam-interface=spamc -Sspam-maxsize=500000 \e -Sspamc-command=/usr/local/bin/spamc \e -Sspamc-arguments="-d localhost -p 2142" -Sspamc-user= .Ed . .Pp Using the generic filter approach allows usage of programs like .Xr bogofilter 1 . Here is an example, requiring it to be accessible via .Ev PATH : . .Bd -literal -offset indent $ \*(uA -Sspam-interface=filter -Sspam-maxsize=500000 \e -Sspamfilter-ham="bogofilter -n" \e -Sspamfilter-noham="bogofilter -N" \e -Sspamfilter-nospam="bogofilter -S" \e -Sspamfilter-rate="bogofilter -TTu 2>/dev/null" \e -Sspamfilter-spam="bogofilter -s" \e -Sspamfilter-rate-scanscore="1;^(.+)$" .Ed . .Pp Because messages must exist on local storage in order to be scored (or used for Bayesian filter training), it is possibly a good idea to perform the local spam check last. Spam can be checked automatically when opening specific folders by setting a specialized form of the internal variable .Va folder-hook . . .Bd -literal -offset indent define spamdelhook { # Server side DCC spamset (header x-dcc-brand-metrics "bulk") # Server-side spamassassin(1) spamset (header x-spam-flag "YES") del :s # TODO we HAVE to be able to do `spamrate :u ! :sS' move :S +maybe-spam spamrate :u del :s move :S +maybe-spam } set folder-hook-SOMEFOLDER=spamdelhook .Ed . .Pp See also the documentation for the variables .Va spam-interface , spam-maxsize , .Va spamc-command , spamc-arguments , spamc-user , .Va spamfilter-ham , spamfilter-noham , spamfilter-nospam , \ spamfilter-rate and .Va spamfilter-rate-scanscore . .\" }}} . .\" }}} (DESCRIPTION) . . .\" .Sh COMMANDS {{{ .Sh COMMANDS . \*(UA reads input in lines. An unquoted reverse solidus .Ql \e at the end of a command line .Dq escapes the newline character: it is discarded and the next line of input is used as a follow-up line, with all leading whitespace removed; once an entire line is completed, the whitespace characters .Cm space , tabulator , newline as well as those defined by the variable .Va ifs are removed from the beginning and end. Placing any whitespace characters at the beginning of a line will prevent a possible addition of the command line to the \*(OPal .Ic history . . .Pp The beginning of such input lines is then scanned for the name of a known command: command names may be abbreviated, in which case the first command that matches the given prefix will be used. .Sx "Command modifiers" may prefix a command in order to modify its behaviour. A name may also be a .Ic commandalias , which will become expanded until no more expansion is possible. Once the command that shall be executed is known, the remains of the input line will be interpreted according to command-specific rules, documented in the following. . .Pp This behaviour is different to the .Xr sh 1 Ns ell, which is a programming language with syntactic elements of clearly defined semantics, and therefore capable to sequentially expand and evaluate individual elements of a line. \*(UA will never be able to handle .Ql \&? set one=value two=$one in a single statement, because the variable assignment is performed by the command .Pf ( Ic set ) , not the language. . .Pp The command .Ic list can be used to show the list of all commands, either alphabetically sorted or in prefix search order (these do not match, also because the POSIX standard prescribes a set of abbreviations). \*(OPally the command .Ic help (or .Ic \&? ) , when given an argument, will show a documentation string for the command matching the expanded argument, as in .Ql \&?t , which should be a shorthand of .Ql \&?type ; with these documentation strings both commands support a more .Va verbose listing mode which includes the argument type of the command and other information which applies; a handy suggestion might thus be: . .Bd -literal -offset indent ? define __xv { # Before v15: need to enable sh(1)ell-style on _entire_ line! localopts yes;wysh set verbose;ignerr eval "${@}";return ${?} } ? commandalias xv '\ecall __xv' ? xv help set .Ed . .\" .Ss "Command modifiers" {{{ .Ss "Command modifiers" . Commands may be prefixed by one or multiple command modifiers. Some command modifiers can be used with a restricted set of commands only, the .Va verbose version of .Ic list will (\*(OPally) show which modifiers apply. . .Bl -bullet .It The modifier reverse solidus .Mx .Cm \e , to be placed first, prevents .Ic commandalias expansions on the remains of the line, e.g., .Ql \eecho will always evaluate the command .Ic echo , even if an (command)alias of the same name exists. .Ic commandalias content may itself contain further command modifiers, including an initial reverse solidus to prevent further expansions. . .It The modifier .Mx .Cm ignerr indicates that any error generated by the following command should be ignored by the state machine and not cause a program exit with enabled .Va errexit or for the standardized exit cases in .Va posix mode. .Va \&? , one of the .Sx "INTERNAL VARIABLES" , will be set to the real exit status of the command regardless. . .It .Mx .Cm local will alter the called command to apply changes only temporarily, local to block-scope, and can thus only be used inside of a .Ic define Ns d macro or an .Ic account definition. Specifying it implies the modifier .Cm wysh . Block-scope settings will not be inherited by macros deeper in the .Ic call chain, and will be garbage collected once the current block is left. To record and unroll changes in the global scope use the command .Ic localopts . . .It .Mx .Cm scope does yet not implement any functionality. . .It .Mx .Cm u does yet not implement any functionality. . .It Some commands support the .Mx .Cm vput modifier: if used, they expect the name of a variable, which can itself be a variable, i.e., shell expansion is applied, as their first argument, and will place their computation result in it instead of the default location (it is usually written to standard output). .Pp The given name will be tested for being a valid .Xr sh 1 variable name, and may therefore only consist of upper- and lowercase characters, digits, and the underscore; the hyphen-minus may be used as a non-portable extension; digits may not be used as first, hyphen-minus may not be used as last characters. In addition the name may either not be one of the known .Sx "INTERNAL VARIABLES" , or must otherwise refer to a writable (non-boolean) value variable. The actual put operation may fail nonetheless, e.g., if the variable expects a number argument only a number will be accepted. Any error during these operations causes the command as such to fail, and the error number .Va \&! will be set to .Va ^ERR Ns -NOTSUP , the exit status .Va \&? should be set to .Ql -1 , but some commands deviate from the latter, which is documented. . .It Last, but not least, the modifier .Mx .Cm wysh can be used for some old and established commands to choose the new .Sx "Shell-style argument quoting" rules over the traditional .Sx "Old-style argument quoting" . This modifier is implied if .Va v15-compat is set to a non-empty value. .El .\" }}} . .\" .Ss "Old-style argument quoting" {{{ .Ss "Old-style argument quoting" . \*(ID This section documents the old, traditional style of quoting non-message-list arguments to commands which expect this type of arguments: whereas still used by the majority of such commands, the new .Sx "Shell-style argument quoting" may be available even for those via .Cm wysh , one of the .Sx "Command modifiers" . Nonetheless care must be taken, because only new commands have been designed with all the capabilities of the new quoting rules in mind, which can, e.g., generate control characters. . . .Bl -bullet -offset indent .It An argument can be enclosed between paired double-quotes .Ql """argument""" or single-quotes .Ql 'argument' ; any whitespace, shell word expansion, or reverse solidus characters (except as described next) within the quotes are treated literally as part of the argument. A double-quote will be treated literally within single-quotes and vice versa. Inside such a quoted string the actually used quote character can be used nonetheless by escaping it with a reverse solidus .Ql \e , as in .Ql """y\e""ou""" . . .It An argument that is not enclosed in quotes, as above, can usually still contain space characters if those spaces are reverse solidus escaped, as in .Ql you\e are . . .It A reverse solidus outside of the enclosing quotes is discarded and the following character is treated literally as part of the argument. .El .\" }}} . .\" .Ss "Shell-style argument quoting" {{{ .Ss "Shell-style argument quoting" . .Xr sh 1 Ns ell-style, and therefore POSIX standardized, argument parsing and quoting rules are used by most commands. \*(ID Most new commands only support these new rules and are flagged \*(NQ, some elder ones can use them with the command modifier .Cm wysh ; in the future only this type of argument quoting will remain. . .Pp A command line is parsed from left to right and an input token is completed whenever an unquoted, otherwise ignored, metacharacter is seen. Metacharacters are vertical bar .Cm \&| , ampersand .Cm & , semicolon .Cm \&; , as well as all characters from the variable .Va ifs , and / or .Cm space , tabulator , newline . The additional metacharacters left and right parenthesis .Cm \&( , \&) and less-than and greater-than signs .Cm < , > that the .Xr sh 1 supports are not used, and are treated as ordinary characters: for one these characters are a vivid part of email addresses, and it seems highly unlikely that their function will become meaningful to \*(UA. . .Bd -filled -offset indent .Sy Compatibility note: \*(ID Please note that even many new-style commands do not yet honour .Va ifs to parse their arguments: whereas the .Xr sh 1 Ns ell is a language with syntactic elements of clearly defined semantics, \*(UA parses entire input lines and decides on a per-command base what to do with the rest of the line. This also means that whenever an unknown command is seen all that \*(UA can do is cancellation of the processing of the remains of the line. .Pp It also often depends on an actual subcommand of a multiplexer command how the rest of the line should be treated, and until v15 we are not capable to perform this deep inspection of arguments. Nonetheless, at least the following commands which work with positional parameters fully support .Va ifs for an almost shell-compatible field splitting: .Ic call , call_if , read , vpospar , xcall . .Ed . .Pp Any unquoted number sign .Ql # at the beginning of a new token starts a comment that extends to the end of the line, and therefore ends argument processing. An unquoted dollar sign .Ql $ will cause variable expansion of the given name, which must be a valid .Xr sh 1 Ns ell-style variable name (see .Cm vput ) : .Sx "INTERNAL VARIABLES" as well as .Sx ENVIRONMENT (shell) variables can be accessed through this mechanism, brace enclosing the name is supported (i.e., to subdivide a token). . .Pp Whereas the metacharacters .Cm space , tabulator , newline only complete an input token, vertical bar .Cm \&| , ampersand .Cm & and semicolon .Cm \&; also act as control operators and perform control functions. For now supported is semicolon .Cm \&; , which terminates a single command, therefore sequencing the command line and making the remainder of the line a subject to reevaluation. With sequencing, multiple command argument types and quoting rules may therefore apply to a single line, which can become problematic before v15: e.g., the first of the following will cause surprising results. . .Pp .Dl ? echo one; set verbose; echo verbose=$verbose. .Dl ? echo one; wysh set verbose; echo verbose=$verbose. . .Pp Quoting is a mechanism that will remove the special meaning of metacharacters and reserved words, and will prevent expansion. There are four quoting mechanisms: the escape character, single-quotes, double-quotes and dollar-single-quotes: . . .Bl -bullet -offset indent .It The literal value of any character can be preserved by preceding it with the escape character reverse solidus .Ql \e . . .It Arguments which are enclosed in .Ql 'single-\:quotes' retain their literal value. A single-quote cannot occur within single-quotes. . .It The literal value of all characters enclosed in .Ql \(dqdouble-\:quotes\(dq is retained, with the exception of dollar sign .Ql $ , which will cause variable expansion, as above, backquote (grave accent) .Ql ` , (which not yet means anything special), reverse solidus .Ql \e , which will escape any of the characters dollar sign .Ql $ (to prevent variable expansion), backquote (grave accent) .Ql ` , double-quote .Ql \(dq (to prevent ending the quote) and reverse solidus .Ql \e (to prevent escaping, i.e., to embed a reverse solidus character as-is), but has no special meaning otherwise. . .It Arguments enclosed in .Ql $'dollar-\:single-\:quotes' extend normal single quotes in that reverse solidus escape sequences are expanded as follows: .Pp .Bl -tag -compact -width ".Ql \eNNN" .It Ql \ea bell control character (ASCII and ISO-10646 BEL). .It Ql \eb backspace control character (ASCII and ISO-10646 BS). .It Ql \eE escape control character (ASCII and ISO-10646 ESC). .It Ql \ee the same. .It Ql \ef form feed control character (ASCII and ISO-10646 FF). .It Ql \en line feed control character (ASCII and ISO-10646 LF). .It Ql \er carriage return control character (ASCII and ISO-10646 CR). .It Ql \et horizontal tabulator control character (ASCII and ISO-10646 HT). .It Ql \ev vertical tabulator control character (ASCII and ISO-10646 VT). .It Ql \e\e emits a reverse solidus character. .It Ql \e' single quote. .It Ql \e" double quote (escaping is optional). .It Ql \eNNN eight-bit byte with the octal value .Ql NNN (one to three octal digits), optionally prefixed by an additional .Ql 0 . A 0 byte will suppress further output for the quoted argument. .It Ql \exHH eight-bit byte with the hexadecimal value .Ql HH (one or two hexadecimal characters, no prefix, see .Ic vexpr ) . A 0 byte will suppress further output for the quoted argument. .It Ql \eUHHHHHHHH the Unicode / ISO-10646 character with the hexadecimal codepoint value .Ql HHHHHHHH (one to eight hexadecimal characters) \(em note that Unicode defines the maximum codepoint ever to be supported as .Ql 0x10FFFF (in planes of .Ql 0xFFFF characters each). This escape is only supported in locales that support Unicode (see .Sx "Character sets" ) , in other cases the sequence will remain unexpanded unless the given code point is ASCII compatible or (if the \*(OPal character set conversion is available) can be represented in the current locale. The character NUL will suppress further output for the quoted argument. .It Ql \euHHHH Identical to .Ql \eUHHHHHHHH except it takes only one to four hexadecimal characters. .It Ql \ecX Emits the non-printable (ASCII and compatible) C0 control codes 0 (NUL) to 31 (US), and 127 (DEL). Printable representations of ASCII control codes can be created by mapping them to a different, visible part of the ASCII character set. Adding the number 64 achieves this for the codes 0 to 31, e.g., 7 (BEL): .Ql 7 + 64 = 71 = G . The real operation is a bitwise logical XOR with 64 (bit 7 set, see .Ic vexpr ) , thus also covering code 127 (DEL), which is mapped to 63 (question mark): .Ql \&?\0vexpr\0^\0127\064 . .Pp Whereas historically circumflex notation has often been used for visualization purposes of control codes, e.g., .Ql ^G , the reverse solidus notation has been standardized: .Ql \ecG . Some control codes also have standardized (ISO-10646, ISO C) aliases, as shown above (e.g., .Ql \ea , .Ql \en , .Ql \et ) : whenever such an alias exists it will be used for display purposes. The control code NUL .Pf ( Ql \ec@ , a non-standard extension) will suppress further output for the remains of the token (which may extend beyond the current quote), or, depending on the context, the remains of all arguments for the current command. .It Ql \e$NAME Non-standard extension: expand the given variable name, as above. Brace enclosing the name is supported. .It Ql \e`{command} Not yet supported, just to raise awareness: Non-standard extension. .El .El . .Pp Caveats: . .Bd -literal -offset indent ? echo 'Quotes '${HOME}' and 'tokens" differ!"# no comment ? echo Quotes ${HOME} and tokens differ! # comment ? echo Don"'"t you worry$'\ex21' The sun shines on us. $'\eu263A' .Ed .\" }}} . .\" .Ss "Message list arguments" {{{ .Ss "Message list arguments" . Many commands operate on message list specifications, as documented in .Sx "Specifying messages" . The argument input is first split into individual tokens via .Sx "Shell-style argument quoting" , which are then interpreted as the mentioned specifications. If no explicit message list has been specified, many commands will search for and use the next message forward that satisfies the commands' requirements, and if there are no messages forward of the current message, the search proceeds backwards; if there are no good messages at all to be found, an error message is shown and the command is aborted. The .Va verbose output of the command .Ic list will indicate whether a command searches for a default message, or not. .\" }}} . .\" .Ss "Raw data arguments for codec commands" {{{ .Ss "Raw data arguments for codec commands" . A special set of commands, which all have the string .Dq codec in their name, e.g., .Ic addrcodec , .Ic shcodec , .Ic urlcodec , take raw string data as input, which means that the content of the command input line is passed completely unexpanded and otherwise unchanged: like this the effect of the actual codec is visible without any noise of possible shell quoting rules etc., i.e., the user can input one-to-one the desired or questionable data. To gain a level of expansion, the entire command line can be .Ic eval Ns uated first, e.g., . .Bd -literal -offset indent ? vput shcodec res encode /usr/Sch\[:o]nes Wetter/heute.txt ? echo $res $'/usr/Sch\eu00F6nes Wetter/heute.txt' ? shcodec d $res $'/usr/Sch\eu00F6nes Wetter/heute.txt' ? eval shcodec d $res /usr/Sch\[:o]nes Wetter/heute.txt .Ed .\" }}} . .\" .Ss "Filename transformations" {{{ .Ss "Filename transformations" . Filenames, where expected, and unless documented otherwise, are subsequently subject to the following filename transformations, in sequence: . .Bl -bullet -offset indent .It If the given name is a registered .Ic shortcut , it will be replaced with the expanded shortcut. . .It The filename is matched against the following patterns or strings: .Pp .Bl -hang -compact -width ".Ar %user" .It Ar # (Number sign) is expanded to the previous file. .It Ar % (Percent sign) is replaced by the invoking .Mx -ix "primary system mailbox" user's primary system mailbox, which either is the (itself expandable) .Va inbox if that is set, the standardized absolute pathname indicated by .Ev MAIL if that is set, or a built-in compile-time default otherwise. .It Ar %user Expands to the primary system mailbox of .Ar user (and never the value of .Va inbox , regardless of its actual setting). .It Ar & (Ampersand) is replaced with the invoking user's .Mx -ix "secondary mailbox" secondary mailbox, the .Ev MBOX . .It Ar +file Refers to a .Ar file in the .Va folder directory (if that variable is set). .It Ar %:filespec Expands to the same value as .Ar filespec , but has special meaning when used with, e.g., the command .Ic file : the file will be treated as a primary system mailbox by, e.g., the .Ic mbox and .Ic save commands, meaning that messages that have been read in the current session will be moved to the .Ev MBOX mailbox instead of simply being flagged as read. .El . .It Meta expansions may be applied to the resulting filename, as allowed by the operation and applicable to the resulting access protocol (also see .Sx "On URL syntax and credential lookup" ) . For the file-protocol, a leading tilde .Ql ~ character will be replaced by the expansion of .Ev HOME , except when followed by a valid user name, in which case the home directory of the given user is used instead. .Pp A shell expansion as if specified in double-quotes (see .Sx "Shell-style argument quoting" ) may be applied, so that any occurrence of .Ql $VARIABLE (or .Ql ${VARIABLE} ) will be replaced by the expansion of the variable, if possible; .Sx "INTERNAL VARIABLES" as well as .Sx ENVIRONMENT (shell) variables can be accessed through this mechanism. .Pp Shell pathname wildcard pattern expansions .Pf ( Xr glob 7 ) may be applied as documented. If the fully expanded filename results in multiple pathnames and the command is expecting only one file, an error results. .Pp In interactive context, in order to allow simple value acceptance (via .Dq ENTER ) , arguments will usually be displayed in a properly quoted form, e.g., a file .Ql diet\e is \ecurd.txt may be displayed as .Ql 'diet\e is \ecurd.txt' . .El .\" }}} . .\" .Ss "Commands" {{{ .Ss "Commands" . The following commands are available: . .Bl -tag -width ".It Ic BaNg" . . .Mx .It Ic \&! Executes the .Ev SHELL command which follows, replacing unescaped exclamation marks with the previously executed command if the internal variable .Va bang is set. This command supports .Cm vput as documented in .Sx "Command modifiers" , and manages the error number .Va \&! . A 0 or positive exit status .Va \&? reflects the exit status of the command, negative ones that an error happened before the command was executed, or that the program did not exit cleanly, but, e.g., due to a signal: the error number is .Va ^ERR Ns -CHILD , then. . .Pp In conjunction with the .Cm vput modifier the following special cases exist: a negative exit status occurs if the collected data could not be stored in the given variable, which is a .Va ^ERR Ns -NOTSUP error that should otherwise not occur. .Va ^ERR Ns -CANCELED indicates that no temporary file could be created to collect the command output at first glance. In case of catchable out-of-memory situations .Va ^ERR Ns -NOMEM will occur and \*(UA will try to store the empty string, just like with all other detected error conditions. . . .Mx .It Ic # The comment-command causes the entire line to be ignored. .Sy Note: this really is a normal command which' purpose is to discard its arguments, not a .Dq comment-start indicating special character, which means that, e.g., trailing comments on a line are not possible (except for commands which use .Sx "Shell-style argument quoting" ) . . .Mx .It Ic + Goes to the next message in sequence and types it (like .Dq ENTER ) . . .Mx .It Ic - Display the preceding message, or the n'th previous message if given a numeric argument n. . .Mx .It Ic = Shows the message number of the current message (the .Dq dot ) when used without arguments, that of the given list otherwise. Output numbers will be separated from each other with the first character of .Va ifs , and followed by the first character of .Va if-ws , if that is not empty and not identical to the first. If that results in no separation at all a .Cm space character is used. This command supports .Cm vput (see .Sx "Command modifiers" ) , and manages the error number .Va \&! . . .Mx .It Ic \&? \*(OP Show a brief summary of commands. \*(OP Given an argument a synopsis for the command in question is shown instead; commands can be abbreviated in general and this command can be used to see the full expansion of an abbreviation including the synopsis, try, e.g., .Ql \&?h , .Ql \&?hel and .Ql \&?help and see how the output changes. This mode also supports a more .Va verbose output, which will provide the information documented for .Ic list . . .Mx .It Ic \&| A synonym for the .Ic pipe command. . .Mx .Mx .It Ic account , unaccount (ac, una) Creates, selects or lists (an) account(s). Accounts are special incarnations of .Ic define Ns d macros and group commands and variable settings which together usually arrange the environment for the purpose of creating an email account. Different to normal macros settings which are covered by .Ic localopts \(en here by default enabled! \(en will not be reverted before the .Ic account is changed again. The special account .Ql null (case-insensitive) always exists, and all but it can be deleted by the latter command, and in one operation with the special name .Ql * . Also for all but it a possibly set .Va on-account-cleanup hook is called once they are left, including program exit. .Pp Without arguments a listing of all defined accounts is shown. With one argument the given account is activated: the system .Va inbox of that account will be activated (as via .Ic file ) , a possibly installed .Va folder-hook will be run, and the internal variable .Va account will be updated. The two argument form is identical to defining a macro as via .Ic define : .Bd -literal -offset indent account myisp { set folder=~/mail inbox=+syste.mbox record=+sent.mbox set from='(My Name) myname@myisp.example' set mta=smtp://mylogin@smtp.myisp.example } .Ed . . .Mx .It Ic addrcodec Perform email address codec transformations on raw-data argument, rather according to email standards (RFC 5322; \*(ID will furtherly improve). Supports .Cm vput (see .Sx "Command modifiers" ) , and manages the error number .Va \&! . The first argument must be either .Ar [+[+[+]]]e[ncode] , .Ar d[ecode] , .Ar s[kin] or .Ar skinl[ist] and specifies the operation to perform on the rest of the line. . .Pp Decoding will show how a standard-compliant MUA will display the given argument, which should be an email address. Please be aware that most MUAs have difficulties with the address standards, and vary wildly when (comments) in parenthesis, .Dq double-quoted strings, or quoted-pairs, as below, become involved. \*(ID \*(UA currently does not perform decoding when displaying addresses. . .Pp Skinning is identical to decoding but only outputs the plain address, without any string, comment etc. components. Another difference is that it may fail with the error number .Va \&! set to .Va ^ERR Ns -INVAL if decoding fails to find a(n) (valid) email address, in which case the unmodified input will be output again. . .Pp .Ar skinlist first performs a skin operation, and thereafter checks a valid address for whether it is a registered mailing list (see .Ic mlist and .Ic mlsubscribe ) , eventually reporting that state in the error number .Va \&! as .Va ^ERR Ns -EXIST . (This state could later become overwritten by an I/O error, though.) . .Pp Encoding supports four different modes, lesser automated versions can be chosen by prefixing one, two or three plus signs: the standard imposes a special meaning on some characters, which thus have to be transformed to so-called quoted-pairs by pairing them with a reverse solidus .Ql \e in order to remove the special meaning; this might change interpretation of the entire argument from what has been desired, however! Specify one plus sign to remark that parenthesis shall be left alone, two for not turning double quotation marks into quoted-pairs, and three for also leaving any user-specified reverse solidus alone. The result will always be valid, if a successful exit status is reported (\*(ID the current parser fails this assertion for some constructs). \*(ID Addresses need to be specified in between angle brackets .Ql < , .Ql > if the construct becomes more difficult, otherwise the current parser will fail; it is not smart enough to guess right. . .Bd -literal -offset indent ? addrc enc "Hey, you",\e out\e there "\e"Hey, you\e", \e\e out\e\e there" ? addrc d "\e"Hey, you\e", \e\e out\e\e there" "Hey, you", \e out\e there ? addrc s "\e"Hey, you\e", \e\e out\e\e there" diet@exam.ple .Ed . . .Mx .Mx .It Ic alias , unalias \*(NQ (a, una) Define or list, and remove, respectively, address aliases. Address aliases are a method of creating personal distribution lists that map a single alias name to none to multiple receivers; aliases are expanded after message composing is completed. The latter command removes all given aliases, the special name asterisk .Ql * will remove all existing aliases. When used without arguments the former shows a list of all currently known aliases, with one argument only the target(s) of the given one. When given two arguments, hyphen-minus .Ql - being the first, the target(s) of the second is/are expanded recursively. .Pp In all other cases the given address alias is newly defined or will be appended to: target arguments must either be valid alias names, or any other address type. Recursive expansion of (what looks like) alias name(s) targets can be prevented by prefixing the target with the modifier reverse solidus .Cm \e . A valid alias name conforms to the Postfix MTA .Xr aliases 5 rules, and may consist of alphabetic characters, digits, the underscore, the number sign, colon, commercial at and hyphen-minus; extensions: exclamation mark .Ql \&! , period .Ql \&. as well as .Dq any character that has the high bit set may be used: .Ql [[:alnum:]_#:@!.-]+ . The number sign may need be quoted to avoid misinterpretation as the shell comment character. .Pp .\" ALIASCOLON next sentence \*(ID Unfortunately the colon is currently not supported, as it interferes with normal address parsing rules. .\" ALIASCOLON next sentence \*(ID Such high bit characters will likely cause warnings at the moment for the same reasons why colon is unsupported; also, in the future locale dependent character set validity checks will be performed. . .Mx .Mx .It Ic alternates , unalternates \*(NQ (alt) Manage a list of alternate addresses or names of the active user, members of which will be removed from recipient lists (except one). There is a set of implicit alternates which is formed of the values of .Ev LOGNAME , .Va from , .Va sender and .Va reply-to . .Va from will not be used if .Va sender is set. The latter command removes the given list of alternates, the special name .Ql * will discard all existing alternate names. .Pp The former command manages the error number .Va \&! . It shows the current set of alternates when used without arguments; in this mode only it also supports .Cm vput (see .Sx "Command modifiers" ) . Otherwise the given arguments (after being checked for validity) are appended to the list of alternate names; in .Va posix mode they replace that list instead. . .Mx .Mx .It Ic answered , unanswered Take a message lists and mark each message as (not) having been answered. Messages will be marked answered when being .Ic reply Ns d to automatically if the .Va markanswered variable is set. See the section .Sx "Message states" . . . .Mx .Mx .It Ic bind , unbind \*(OP\*(NQ The bind command extends the MLE (see .Sx "On terminal control and line editor" ) with freely configurable key bindings. The latter command removes from the given context the given key binding, both of which may be specified as a wildcard .Ql * , so that, e.g., .Ql unbind * * will remove all bindings of all contexts. Due to initialization order unbinding will not work for built-in key bindings upon program startup, however: please use .Va line-editor-no-defaults for this purpose instead. . .Pp With zero arguments, or with a context name the former command shows all key bindings (of the given context; an asterisk .Ql * will iterate over all contexts); a more verbose listing will be produced if either of .Va debug or .Va verbose are set. With two or more arguments a binding is (re)established: the first argument is the context to which the binding shall apply, the second argument is a comma-separated list of the .Dq keys which form the binding, and any remaining arguments form the expansion. To indicate that a binding shall not be auto-committed, but that the expansion shall instead be furtherly editable by the user, a commercial at .Ql @ (that will be removed) can be placed last in the expansion, from which leading and trailing whitespace will finally be removed. Reverse solidus cannot be used as the last character of expansion. An empty expansion will be rejected. . .Pp Contexts define when a binding applies, i.e., a binding will not be seen unless the context for which it is defined for is currently active. This is not true for the shared binding .Ql base , which is the foundation for all other bindings and as such always applies, its bindings, however, only apply secondarily. The available contexts are the shared .Ql base , the .Ql default context which is used in all not otherwise documented situations, and .Ql compose , which applies to compose mode only. . .Pp .Dq Keys which form the binding are specified as a comma-separated list of byte-sequences, where each list entry corresponds to one key(press). A list entry may, indicated by a leading colon character .Ql \&: , also refer to the name of a terminal capability; several dozen names will be compiled in and may be specified either by their .Xr terminfo 5 , or, if existing, by their .Xr termcap 5 name, regardless of the actually used \*(OPal terminal control library. It is possible to use any capability, as long as the name is resolvable by the \*(OPal control library or was defined via the internal variable .Va termcap . Input sequences are not case-normalized, so that an exact match is required to update or remove a binding. Examples: . .Bd -literal -offset indent ? bind base $'\eE',d mle-snarf-word-fwd # Esc(ape) ? bind base $'\eE',$'\ec?' mle-snarf-word-bwd # Esc,Delete ? bind default $'\ecA',:khome,w 'echo Editable binding@' ? bind default a,b,c rm -irf / @ # Also editable ? bind default :kf1 File % ? bind compose :kf1 ~v .Ed . .Pp Note that the entire comma-separated list is first parsed (over) as a shell-token with whitespace as the field separator, before being parsed and expanded for real with comma as the field separator, therefore whitespace needs to be properly quoted, see .Sx "Shell-style argument quoting" . Using Unicode reverse solidus escape sequences renders a binding defunctional if the locale does not support Unicode (see .Sx "Character sets" ) , and using terminal capabilities does so if no (corresponding) terminal control support is (currently) available. . .Pp The following terminal capability names are built-in and can be used in .Xr terminfo 5 or (if available) the two-letter .Xr termcap 5 notation. See the respective manual for a list of capabilities. The program .Xr infocmp 1 can be used to show all the capabilities of .Ev TERM or the given terminal type; using the .Fl \&\&x flag will also show supported (non-standard) extensions. . .Pp .Bl -tag -compact -width kcuuf_or_kcuuf .It Cd kbs Ns \0or Cd kb Backspace. .It Cd kdch1 Ns \0or Cd kD Delete character. .It Cd kDC Ns \0or Cd *4 \(em shifted variant. .It Cd kel Ns \0or Cd kE Clear to end of line. .It Cd kext Ns \0or Cd @9 Exit. .It Cd kich1 Ns \0or Cd kI Insert character. .It Cd kIC Ns \0or Cd #3 \(em shifted variant. .It Cd khome Ns \0or Cd kh Home. .It Cd kHOM Ns \0or Cd #2 \(em shifted variant. .It Cd kend Ns \0or Cd @7 End. .It Cd knp Ns \0or Cd kN Next page. .It Cd kpp Ns \0or Cd kP Previous page. .It Cd kcub1 Ns \0or Cd kl Left cursor (with more modifiers: see below). .It Cd kLFT Ns \0or Cd #4 \(em shifted variant. .It Cd kcuf1 Ns \0or Cd kr Right cursor (ditto). .It Cd kRIT Ns \0or Cd %i \(em shifted variant. .It Cd kcud1 Ns \0or Cd kd Down cursor (ditto). .It Cd kDN \(em shifted variant (only terminfo). .It Cd kcuu1 Ns \0or Cd ku Up cursor (ditto). .It Cd kUP \(em shifted variant (only terminfo). .It Cd kf0 Ns \0or Cd k0 Function key 0. Add one for each function key up to .Cd kf9 and .Cd k9 , respectively. .It Cd kf10 Ns \0or Cd k; Function key 10. .It Cd kf11 Ns \0or Cd F1 Function key 11. Add one for each function key up to .Cd kf19 and .Cd F9 , respectively. .El . .Pp Some terminals support key-modifier combination extensions, e.g., .Ql Alt+Shift+xy . For example, the delete key, .Cd kdch1 : in its shifted variant, the name is mutated to .Cd kDC , then a number is appended for the states .Ql Alt .Pf ( Cd kDC3 ) , .Ql Shift+Alt .Pf ( Cd kDC4 ) , .Ql Control .Pf ( Cd kDC5 ) , .Ql Shift+Control .Pf ( Cd kDC6 ) , .Ql Alt+Control .Pf ( Cd kDC7 ) , finally .Ql Shift+Alt+Control .Pf ( Cd kDC8 ) . The same for the left cursor key, .Cd kcub1 : .Cd KLFT , KLFT3 , KLFT4 , KLFT5 , KLFT6 , KLFT7 , KLFT8 . . .Pp It is advisable to use an initial escape or other control character (e.g., .Ql \ecA ) for bindings which describe user key combinations (as opposed to purely terminal capability based ones), in order to avoid ambiguities whether input belongs to key sequences or not; it also reduces search time. Adjusting .Va bind-timeout may help shall keys and sequences be falsely recognized. . . .Mx .It Ic call \*(NQ Calls the given macro, which must have been created via .Ic define (see there for more), otherwise an .Va ^ERR Ns -NOENT error occurs. Calling macros recursively will at some time excess the stack size limit, causing a hard program abortion; if recursively calling a macro is the last command of the current macro, consider to use the command .Ic xcall , which will first release all resources of the current macro before replacing the current macro with the called one. . . .Mx .It Ic call_if Identical to .Ic call if the given macro has been created via .Ic define , but does not fail nor warn if the macro does not exist. . .Mx .It Ic cd (ch) Change the working directory to .Ev HOME or the given argument. Synonym for .Ic chdir . . .Mx .It Ic certsave \*(OP Only applicable to S/MIME signed messages. Takes an optional message list and a filename and saves the certificates contained within the message signatures to the named file in both human-readable and PEM format. The certificates can later be used to send encrypted messages to the respective message senders by setting .Va smime-encrypt-USER@HOST variables. . .Mx .Mx .It Ic charsetalias , uncharsetalias \*(NQ Manage alias mappings for (conversion of) .Sx "Character sets" . Alias processing is not performed for .Sx "INTERNAL VARIABLES" , e.g., .Va charset-8bit , and mappings are ineffective if character set conversion is not available .Pf ( Va features does not announce .Ql +iconv ) . Expansion happens recursively for cases where aliases point to other aliases (built-in loop limit: 8). .Pp The latter command deletes all aliases given as arguments, or all at once when given the asterisk .Ql * . The former shows the list of all currently defined aliases if used without arguments, or the target of the given single argument; when given two arguments, hyphen-minus .Ql - being the first, the second is instead expanded recursively. In all other cases the given arguments are treated as pairs of character sets and their desired target alias name, creating new or updating already existing aliases. . .Mx .It Ic chdir (ch) Change the working directory to .Ev HOME or the given argument. Synonym for .Ic cd . . .Mx .Mx .It Ic collapse , uncollapse Only applicable to .Ql thread Ns ed .Ic sort mode. Takes a message list and makes all replies to these messages invisible in header summaries, except for .Ql new messages and the .Dq dot . Also when a message with collapsed replies is displayed, all of these are automatically uncollapsed. The latter command undoes collapsing. . . .\" FIXME review until this point .Mx .Mx .It Ic colour , uncolour \*(OP\*(NQ Manage colour mappings of and for a .Sx "Coloured display" . The type of colour is given as the (case-insensitive) first argument, which must be one of .Ql 256 for 256-colour terminals, .Ql 8 , .Ql ansi or .Ql iso for the standard 8-colour ANSI / ISO 6429 colour palette and .Ql 1 or .Ql mono for monochrome terminals. Monochrome terminals cannot deal with colours, but only (some) font attributes. . .Pp Without further arguments the list of all currently defined mappings for the given colour type is shown (as a special case giving .Ql all or .Ql * will show the mappings of all types). Otherwise the second argument defines the mappable slot, and the third argument a (comma-separated list of) colour and font attribute specification(s), and the optional fourth argument can be used to specify a precondition: if conditioned mappings exist they are tested in (creation) order unless a (case-insensitive) match has been found, and the default mapping (if any has been established) will only be chosen as a last resort. The types of precondition available depend on the mappable slot (see .Sx "Coloured display" for some examples), the following of which exist: . .Pp Mappings prefixed with .Ql mle- are used for the \*(OPal built-in Mailx-Line-Editor (MLE, see .Sx "On terminal control and line editor" ) and do not support preconditions. .Pp .Bl -tag -compact -width view-partinfo .It Ar mle-position This mapping is used for the position indicator that is visible when a line cannot be fully displayed on the screen. .It Ar mle-prompt Used for the .Va prompt . .It Ar mle-error Used for the occasionally appearing error indicator that is joined onto .Va prompt . \*(ID Also used for error messages written on standard error . .El . .Pp Mappings prefixed with .Ql sum- are used in header summaries, and they all understand the preconditions .Ql dot (the current message) and .Ql older for elder messages (only honoured in conjunction with .Va datefield-markout-older ) . .Pp .Bl -tag -compact -width view-partinfo .It Ar sum-dotmark This mapping is used for the .Dq dotmark that can be created with the .Ql %> or .Ql %< formats of the variable .Va headline . .It Ar sum-header For the complete header summary line except the .Dq dotmark and the thread structure. .It Ar sum-thread For the thread structure which can be created with the .Ql %i format of the variable .Va headline . .El . .Pp Mappings prefixed with .Ql view- are used when displaying messages. .Pp .Bl -tag -compact -width view-partinfo .It Ar view-from_ This mapping is used for so-called .Ql From_ lines, which are MBOX file format specific header lines (also see .Va mbox-rfc4155 ) . .It Ar view-header For header lines. A comma-separated list of headers to which the mapping applies may be given as a precondition; if the \*(OPal regular expression support is available then if any of the .Mx -sx .Sx "magic regular expression characters" is seen the precondition will be evaluated as (an extended) one. .It Ar view-msginfo For the introductional message info line. .It Ar view-partinfo For MIME part info lines. .El . .Pp The following (case-insensitive) colour definitions and font attributes are understood, multiple of which can be specified in a comma-separated list: . .Bl -tag -width ft= .It Ar ft= a font attribute: .Ql bold , .Ql reverse or .Ql underline . It is possible (and often applicable) to specify multiple font attributes for a single mapping. . .It Ar fg= foreground colour attribute: .Ql black , .Ql blue , .Ql green , .Ql red , .Ql brown , .Ql magenta , .Ql cyan or .Ql white . To specify a 256-colour mode a decimal number colour specification in the range 0 to 255, inclusive, is supported, and interpreted as follows: .Pp .Bl -tag -compact -width "999 - 999" .It 0 - 7 the standard ISO 6429 colours, as above. .It 8 - 15 high intensity variants of the standard colours. .It 16 - 231 216 colours in tuples of 6. .It 232 - 255 grayscale from black to white in 24 steps. .El .Bd -literal -offset indent #!/bin/sh - fg() { printf "\e033[38;5;${1}m($1)"; } bg() { printf "\e033[48;5;${1}m($1)"; } i=0 while [ $i -lt 256 ]; do fg $i; i=$(($i + 1)); done printf "\e033[0m\en" i=0 while [ $i -lt 256 ]; do bg $i; i=$(($i + 1)); done printf "\e033[0m\en" .Ed . .It Ar bg= background colour attribute (see .Cd fg= for possible values). .El . .Pp The command .Ic \&uncolour will remove for the given colour type (the special type .Ql * selects all) the given mapping; if the optional precondition argument is given only the exact tuple of mapping and precondition is removed. The special name .Ql * will remove all mappings (no precondition allowed), thus .Ql uncolour * * will remove all established mappings. . . .Mx .Mx .It Ic commandalias , uncommandalias \*(NQ Define or list, and remove, respectively, command aliases. An (command)alias can be used everywhere a normal command can be used, but always takes precedence: any arguments that are given to the command alias are joined onto the alias expansion, and the resulting string forms the command line that is, in effect, executed. The latter command removes all given aliases, the special name asterisk .Ql * will remove all existing aliases. When used without arguments the former shows a list of all currently known aliases, with one argument only the expansion of the given one. .Pp With two or more arguments a command alias is defined or updated: the first argument is the name under which the remaining command line should be accessible, the content of which can be just about anything. An alias may itself expand to another alias, but to avoid expansion loops further expansion will be prevented if an alias refers to itself or if an expansion depth limit is reached. Explicit expansion prevention is available via reverse solidus .Cm \e , one of the .Sx "Command modifiers" . .Bd -literal -offset indent ? commandalias xx \*(uA: `commandalias': no such alias: xx ? commandalias xx echo hello, ? commandalias xx commandalias xx 'echo hello,' ? xx hello, ? xx world hello, world .Ed . .Mx .It Ic Copy (C) Copy messages to files whose names are derived from the author of the respective message and do not mark them as being saved; otherwise identical to .Ic Save . . .Mx .It Ic copy (c) Copy messages to the named file and do not mark them as being saved; otherwise identical to .Ic save . . . .Mx .It Ic csop \*(NQ A multiplexer command which provides C-style string operations on 8-bit bytes without a notion of locale settings and character sets, effectively assuming ASCII data. For numeric and other operations refer to .Ic vexpr . .Cm vput , one of the .Sx "Command modifiers" , is supported. The error result is .Ql -1 for usage errors and numeric results, the empty string otherwise; missing data errors, as for unsuccessful searches, result in the .Va \&! error number being set to .Va ^ERR Ns -NODATA . Where the question mark .Ql \&? modifier suffix is supported, a case-insensitive (ASCII mapping) operation mode is supported; the keyword .Ql case is optional, e.g., .Ql find? and .Ql find?case are identical. . .Bl -hang -width ".It Cm length" .It Cm length Queries the length of the given argument. . .It Cm hash , Cm hash32 Calculates a hash value of the given argument. The latter will return a 32-bit result regardless of host environment. .Ql \&? modifier suffix is supported. These use Chris Torek's hash algorithm, the resulting hash value is bit mixed as shown by Bret Mulvey. . .It Cm find Search for the second in the first argument. Shows the resulting 0-based offset shall it have been found. .Ql \&? modifier suffix is supported. . .It Cm substring Creates a substring of its first argument. The optional second argument is the 0-based starting offset, a negative one counts from the end; the optional third argument specifies the length of the desired result, a negative length leaves off the given number of bytes at the end of the original string; by default the entire string is used. This operation tries to work around faulty arguments (set .Va verbose for error logs), but reports them via the error number .Va \&! as .Va ^ERR Ns -OVERFLOW . . .It Cm trim Trim away whitespace characters from both ends of the argument. . .It Cm trim-front Trim away whitespace characters from the begin of the argument. . .It Cm trim-end Trim away whitespace characters from the end of the argument. .El . . .Mx .It Ic cwd Show the name of the current working directory, as reported by .Xr getcwd 3 . Supports .Cm vput (see .Sx "Command modifiers" ) . The return status is tracked via .Va \&? . . .Mx .It Ic Decrypt \*(OP For unencrypted messages this command is identical to .Ic Copy ; Encrypted messages are first decrypted, if possible, and then copied. . .Mx .It Ic decrypt \*(OP For unencrypted messages this command is identical to .Ic copy ; Encrypted messages are first decrypted, if possible, and then copied. . . .Mx .Mx .It Ic define , undefine The latter command deletes the given macro, the special name .Ql * will discard all existing macros. Deletion of (a) macro(s) can be performed from within running (a) macro(s), including self-deletion. Without arguments the former command prints the current list of macros, including their content, otherwise it defines a macro, replacing an existing one of the same name as applicable. . .Pp A defined macro can be invoked explicitly by using the .Ic call , .Ic call_if and .Ic xcall commands, or implicitly if a macro hook is triggered, e.g., a .Va folder-hook . Execution of a macro body can be stopped from within by calling .Ic return . . .Pp Temporary macro block-scope variables can be created or deleted with the .Cm local command modifier in conjunction with the commands .Ic set and .Ic unset , respectively. To enforce unrolling of changes made to (global) .Sx "INTERNAL VARIABLES" the command .Ic localopts can be used instead; its covered scope depends on how (i.e., .Dq as what : normal macro, folder hook, hook, .Ic account switch) the macro is invoked. . .Pp Inside a .Ic call Ns ed macro, the given positional parameters are implicitly local to the macro's scope, and may be accessed via the variables .Va * , .Va @ , .Va # and .Va 1 and any other positive unsigned decimal number less than or equal to .Va # . Positional parameters can be .Ic shift Ns ed, or become completely replaced, removed etc. via .Ic vpospar . A helpful command for numeric computation and string evaluations is .Ic vexpr , .Ic csop offers C-style byte string operations. . .Bd -literal -offset indent define name { command1 command2 ... commandN } # E.g. define exmac { echo Parameter 1 of ${#} is ${1}, all: ${*} / ${@} return 1000 0 } call exmac Hello macro exmac! echo ${?}/${!}/${^ERRNAME} .Ed . . .Mx .Mx .It Ic delete , undelete (d, u) Marks the given message list as being or not being .Ql deleted , respectively; if no argument has been specified then the usual search for a visible message is performed, as documented for .Sx "Message list arguments" , showing only the next input prompt if the search fails. Deleted messages will neither be saved in the .Mx -sx .Sx "secondary mailbox" .Ev MBOX nor will they be available for most other commands. If the .Va autoprint variable is set, the new .Dq dot or the last message restored, respectively, is automatically .Ic type Ns d; also see .Ic dp , .Ic dt . . . .Mx .It Ic digmsg \*(NQ Digging (information out of) messages is possible through .Ic \&\&digmsg objects, which can be .Cm create Ns d for the given message number; in compose mode the hyphen-minus .Ql - will instead open the message that is being composed. If a hyphen-minus is given as the optional third argument then output will be generated on the standard output channel instead of being subject to consumation by the .Ic read or .Ic readall commands. .Pp The objects may be .Cm remove Ns d again by giving the same identifier used for creation; this step could be omitted: objects will be automatically closed when the active mailbox or the compose mode is left, respectively. In all other use cases the second argument is an object identifier, and the third and all following arguments are interpreted as via .Ic ~^ (see .Sx "COMMAND ESCAPES" ) : .Bd -literal -offset indent ? vput = msgno; digmsg create $msgno ? digmsg $msgno header list; readall x; echon $x 210 Subject From To Message-ID References In-Reply-To Status ? digmsg $msgno header show Status;readall x;echon $x 212 Status RO ? digmsg remove $msgno .Ed . . .Mx .It Ic discard (di) Identical to .Ic ignore . Superseded by the multiplexer .Ic headerpick . . .Mx .Mx .It Ic dp , dt Delete the given messages and automatically .Ic type the new .Dq dot if one exists, regardless of the setting of .Va autoprint . . .Mx .It Ic dotmove Move the .Dq dot up or down by one message when given .Ql + or .Ql - argument, respectively. . .Mx .Mx .It Ic draft , undraft Take message lists and mark each given message as being draft, or not being draft, respectively, as documented in the section .Sx "Message states" . . .Mx .It Ic echo \*(NQ (ec) Echoes arguments to standard output and writes a trailing newline, whereas the otherwise identical .Ic echon does not. .Sx "Shell-style argument quoting" is used, .Sx "Filename transformations" are applied to the expanded arguments. This command also supports .Cm vput as documented in .Sx "Command modifiers" , and manages the error number .Va \&! : if data is stored in a variable then the return value reflects the length of the result string in case of success and is .Ql -1 on error. . .Mx .It Ic echoerr \*(NQ Identical to .Ic echo except that is echoes to standard error. Also see .Ic echoerrn . In interactive sessions the \*(OPal message ring queue for .Ic errors will be used instead, if available and .Cm vput was not used. . .Mx .It Ic echon \*(NQ Identical to .Ic echo , but does not write or store a trailing newline. . .Mx .It Ic echoerrn \*(NQ Identical to .Ic echoerr , but does not write or store a trailing newline. . .Mx .It Ic edit (e) Point the text .Ev EDITOR at each message from the given list in turn. Modified contents are discarded unless the .Va writebackedited variable is set, and are not used unless the mailbox can be written to and the editor returns a successful exit status. .Ic visual can be used instead for a more display oriented editor. . .Mx .It Ic elif Part of the .Ic if (see there for more), .Ic elif , else , endif conditional \(em if the condition of a preceding .Ic if was false, check the following condition and execute the following block if it evaluates true. . .Mx .It Ic else (el) Part of the .Ic if (see there for more), .Ic elif , else , endif conditional \(em if none of the conditions of the preceding .Ic if and .Ic elif commands was true, the .Ic else block is executed. . .Mx .It Ic endif (en) Marks the end of an .Ic if (see there for more), .Ic elif , else , endif conditional execution block. . . .Mx .It Ic environ \*(NQ \*(UA has a strict notion about which variables are .Sx "INTERNAL VARIABLES" and which are managed in the program .Sx ENVIRONMENT . Since some of the latter are a vivid part of \*(UAs functioning, however, they are transparently integrated into the normal handling of internal variables via .Ic set and .Ic unset . To integrate other environment variables of choice into this transparent handling, and also to export internal variables into the process environment where they normally are not, a .Ql link needs to become established with this command, as in, e.g., . .Pp .Dl environ link PERL5LIB TZ . .Pp Afterwards changing such variables with .Ic set will cause automatic updates of the program environment, and therefore be inherited by newly created child processes. Sufficient system support provided (it was in BSD as early as 1987, and is standardized since Y2K) removing such variables with .Ic unset will remove them also from the program environment, but in any way the knowledge they ever have been .Ql link Ns ed will be lost. Note that this implies that .Ic localopts may cause loss of such links. . .Pp The command .Ql unlink will remove an existing link, but leaves the variables as such intact. Additionally the subcommands .Ql set and .Ql unset are provided, which work exactly the same as the documented commands .Ic set and .Ic unset , but (additionally un)link the variable(s) with the program environment and thus immediately export them to, or remove them from (if possible), respectively, the program environment. . . .Mx .It Ic errors \*(OP Since \*(UA uses the console as a user interface it can happen that messages scroll by too fast to become recognized. Therefore an error log queue is available which can be managed by .Ic errors : .Ar show or no argument will display and clear the queue, .Ar clear will only clear the queue. The queue is finite: if its maximum size is reached any new message replaces the eldest. There are also the variables .Va ^ERRQUEUE-COUNT and .Va ^ERRQUEUE-EXISTS . . .Mx .It Ic eval \*(NQ Construct a command by concatenating the arguments, separated with a single space character, and then evaluate the result. This command passes through the exit status .Va \&? and error number .Va \&! of the evaluated command; also see .Ic call . .Bd -literal -offset indent define xxx { echo "xxx arg <$1>" shift if [ $# -gt 0 ] \excall xxx "$@" endif } define yyy { eval "$@ ' ball" } call yyy '\ecall xxx' "b\e$'\et'u ' " call xxx arg call xxx arg < > call xxx arg .Ed . .Mx .It Ic exit (ex or x) Exit from \*(UA without changing the active mailbox and skip any saving of messages in the .Mx -sx .Sx "secondary mailbox" .Ev MBOX , as well as a possibly tracked line editor .Va history-file . A possibly set .Va on-account-cleanup will be invoked, however. The optional status number argument will be passed through to .Xr exit 3 . \*(ID For now it can happen that the given status will be overwritten, later this will only occur if a later error needs to be reported onto an otherwise success indicating status. . .Mx .It Ic File (Fi) Like .Ic file , but open the mailbox read-only. . . .Mx .It Ic file (fi) The file command switches to a new mailbox. Without arguments it shows status information of the current mailbox. If an argument is given, it will write out changes (such as deletions) the user has made, open a new mailbox, update the internal variables .Va mailbox-resolved and .Va mailbox-display , execute an according .Va folder-hook , if one is installed, and optionally display a summary of .Ic headers if the variable .Va header is set. . .Pp .Sx "Filename transformations" will be applied to the .Ar name argument, and .Ql protocol:// prefixes are, i.e., URL syntax is understood, e.g., .Ql mbox:///tmp/mdirbox : if a protocol prefix is used the mailbox type is fixated and neither the auto-detection (read on) nor the .Va newfolders mechanisms apply. \*(OPally URLs can also be used to access network resources, which may be accessed securely via .Sx "Encrypted network communication" if so supported. Network communication socket timeouts are configurable, e.g., .Va socket-connect-timeout . All generated network traffic may be proxied over the SOCKS5 server given in .Va socks-proxy . . .Pp .Dl \*(IN protocol://[user[:password]@]host[:port][/path] .Dl \*(OU protocol://[user@]host[:port][/path] . .Pp \*(OPally supported network protocols are .Ar pop3 (POP3) and .Ar pop3s (POP3 with TLS encrypted transport), .Ar imap and .Ar imaps . The .Ar [/path] part is valid only for IMAP; there it defaults to .Ar INBOX . Network URLs require a special encoding as documented in the section .Sx "On URL syntax and credential lookup" . . .Pp If the resulting file protocol (MBOX database) .Ar name is located on a local filesystem then the list of all registered .Ic filetype Ns s is traversed in order to see whether a transparent intermediate conversion step is necessary to handle the given mailbox, in which case \*(UA will use the found hook to load and save data into and from a temporary file, respectively. Changing hooks will not affect already opened mailboxes. For example, the following creates hooks for the .Xr gzip 1 compression tool and a combined compressed and encrypted format: . .Bd -literal -offset indent ? filetype \e gzip 'gzip -dc' 'gzip -c' \e zst.pgp 'gpg -d | zstd -dc' 'zstd -19 -zc | gpg -e' .Ed . .Pp .Ic filetype Ns s also provide limited (case-sensitive) auto-completion capabilities. For example .Ql mbox.gz will be found for .Ql \&? file mbox provided that a corresponding handler is installed. It will neither find .Ql mbox.GZ nor .Ql mbox.Gz however, on the other hand doing an explicit .Ql \&? file mbox.GZ will find and use the handler for .Ql gz . . .Pp MBOX databases will always be protected via file-region locks .Pf ( Xr fcntl 2 ) during file operations in order to avoid inconsistencies due to concurrent modifications. .Mx -ix "dotlock files" \*(OP In addition mailbox files treated as the system .Va inbox .Pf ( Ev MAIL ) , as well as .Mx -sx .Sx "primary system mailbox" Ns es in general will also be protected by so-called dotlock files, the traditional way of mail spool file locking: for any file .Ql x a lock file .Ql x.lock will be created for the duration of the synchronization \(em as necessary an external privileged dotlock helper will be used to create the dotlock file in the same directory and with the same user and group identities as the file of interest. .Va dotlock-disable can be used to turn off additional dotlock files, shall the need arise. There is also a related entry in the .Sx FAQ : .Sx "Howto handle stale dotlock files" . . .Pp \*(UA by default uses tolerant POSIX rules when reading MBOX database files, but it will detect invalid message boundaries in this mode and complain (even more with .Va debug ) if any is seen: in this case .Va mbox-rfc4155 can be used to create a valid MBOX database from the invalid input. . .Pp \*(OP If no protocol has been fixated, and .Ar name refers to a directory with the subdirectories .Ql tmp , .Ql new and .Ql cur , then it is treated as a folder in .Dq Maildir format. The maildir format stores each message in its own file, and has been designed so that file locking is not necessary when reading or writing files. . .Pp \*(ID If no protocol has been fixated and no existing file has been found, the variable .Va newfolders controls the format of mailboxes yet to be created. . . .Mx .Mx .It Ic filetype , unfiletype \*(NQ Define, list, and remove, respectively, file handler hooks, which provide (shell) commands that enable \*(UA to load and save MBOX files from and to files with the registered file extensions, as shown and described for .Ic file . The extensions are used case-insensitively, yet the auto-completion feature of, e.g., .Ic file will only work case-sensitively. An intermediate temporary file will be used to store the expanded data. The latter command will remove hooks for all given extensions, asterisk .Ql * will remove all existing handlers. .Pp When used without arguments the former shows a list of all currently defined file hooks, with one argument the expansion of the given alias. Otherwise three arguments are expected, the first specifying the file extension for which the hook is meant, and the second and third defining the load- and save commands to deal with the file type, respectively, both of which must read from standard input and write to standard output. Changing hooks will not affect already opened mailboxes (\*(ID except below). \*(ID For now too much work is done, and files are oftened read in twice where once would be sufficient: this can cause problems if a filetype is changed while such a file is opened; this was already so with the built-in support of .gz etc. in Heirloom, and will vanish in v15. \*(ID For now all handler strings are passed to the .Ev SHELL for evaluation purposes; in the future a .Ql \&! prefix to load and save commands may mean to bypass this shell instance: placing a leading space will avoid any possible misinterpretations. .Bd -literal -offset indent ? filetype bz2 'bzip2 -dc' 'bzip2 -zc' \e gz 'gzip -dc' 'gzip -c' xz 'xz -dc' 'xz -zc' \e zst 'zstd -dc' 'zstd -19 -zc' \e zst.pgp 'gpg -d | zstd -dc' 'zstd -19 -zc | gpg -e' ? set record=+sent.zst.pgp .Ed . .Mx .Mx .It Ic flag , unflag Take message lists and mark the messages as being flagged, or not being flagged, respectively, for urgent/special attention. See the section .Sx "Message states" . . .Mx .It Ic folder (fold) The same as .Ic file . . .Mx .It Ic folders With no arguments, list the names of the folders in the folder directory. With an existing folder as an argument, lists the names of folders below the named folder. . .Mx .It Ic Followup (F) Similar to .Ic Respond , but saves the message in a file named after the local part of the first recipient's address (instead of in .Va record Ns ). . .Mx .It Ic followup (fo) Similar to .Ic respond , but saves the message in a file named after the local part of the first recipient's address (instead of in .Va record Ns ). . .Mx .It Ic followupall Similar to .Ic followup , but responds to all recipients regardless of the .Va flipr variable. . .Mx .It Ic followupsender Similar to .Ic Followup , but responds to the sender only regardless of the .Va flipr variable. . .Mx .It Ic Forward Similar to .Ic forward , but saves the message in a file named after the local part of the recipient's address (instead of in .Va record Ns ). . .Mx .It Ic forward Takes a message and the address of a recipient and forwards the message to him. The text of the original message is included in the new one, with the value of the .Va forward-inject-head variable preceding, and the value of .Va forward-inject-tail succeeding it. To filter the included header fields to the desired subset use the .Ql forward slot of the white- and blacklisting command .Ic headerpick . Only the first part of a multipart message is included unless .Va forward-as-attachment , and recipient addresses will be stripped from comments, names etc. unless the internal variable .Va fullnames is set. .Pp This may generate the errors .Va ^ERR Ns -DESTADDRREQ if no receiver has been specified, .Va ^ERR Ns -PERM if some addressees where rejected by .Va expandaddr , .Va ^ERR Ns -NODATA if no applicable messages have been given, .Va ^ERR Ns -NOTSUP if multiple messages have been specified, .Va ^ERR Ns -IO if an I/O error occurs, .Va ^ERR Ns -NOTSUP if a necessary character set conversion fails, and .Va ^ERR Ns -INVAL for other errors. . .Mx .It Ic from (f) Takes a list of message specifications and displays a summary of their message headers, exactly as via .Ic headers , making the first message of the result the new .Dq dot (the last message if .Va showlast is set). An alias of this command is .Ic search . Also see .Sx "Specifying messages" . . .It Ic Fwd \*(OB Alias for .Ic Forward . . .It Ic fwd \*(OB Alias for .Ic forward . . .It Ic fwdignore \*(OB Superseded by the multiplexer .Ic headerpick . . .It Ic fwdretain \*(OB Superseded by the multiplexer .Ic headerpick . . .It Ic ghost , unghost \*(OB Replaced by .Ic commandalias , .Ic uncommandalias . . .Mx .Mx .It Ic headerpick , unheaderpick \*(NQ Multiplexer command to manage white- and blacklisting selections of header fields for a variety of applications. Without arguments the set of contexts that have settings is displayed. When given arguments, the first argument is the context to which the command applies, one of (case-insensitive) .Ql type for display purposes (via, e.g., .Ic type ) , .Ql save for selecting which headers shall be stored persistently when .Ic save , .Ic copy , .Ic move or even .Ic decrypt Ns ing messages (note that MIME related etc. header fields should not be ignored in order to not destroy usability of the message in this case), .Ql forward for stripping down messages when .Ic forward Ns ing message (has no effect if .Va forward-as-attachment is set), and .Ql top for defining user-defined set of fields for the command .Ic top . .Pp The current settings of the given context are displayed if it is the only argument. A second argument denotes the type of restriction that is to be chosen, it may be (a case-insensitive prefix of) .Ql retain or .Ql ignore for white- and blacklisting purposes, respectively. Establishing a whitelist suppresses inspection of the corresponding blacklist. .Pp If no further argument is given the current settings of the given type will be displayed, otherwise the remaining arguments specify header fields, which \*(OPally may be given as regular expressions, to be added to the given type. The special wildcard field (asterisk, .Ql * ) will establish a (fast) shorthand setting which covers all fields. .Pp The latter command always takes three or more arguments and can be used to remove selections, i.e., from the given context, the given type of list, all the given headers will be removed, the special argument .Ql * will remove all headers. . .Mx .It Ic headers (h) Show the current group of headers, the size of which depends on the variable .Va screen in interactive mode, and the format of which can be defined with .Va headline . If a message-specification is given the group of headers containing the first message therein is shown and the message at the top of the screen becomes the new .Dq dot ; the last message is targeted if .Va showlast is set. . .Mx .It Ic help (hel) A synonym for .Ic \&? . . .Mx .It Ic history \*(OP Without arguments or when given .Cm show all history entries are shown (this mode also supports a more .Va verbose output). .Cm load will replace the list of entries with the content of .Va history-file , and .Cm save will dump the current list to said file, replacing former content. .Cm clear will delete all history entries. The argument can also be a signed decimal .Ar NUMBER , which will select and evaluate the respective history entry, and move it to the top of the history; a negative number is used as an offset to the current command, e.g., .Ql -1 will select the last command, the history top. Please see .Sx "On terminal control and line editor" for more on this topic. . .Mx .It Ic hold (ho, also .Ic preserve ) Takes a message list and marks each message therein to be saved in the user's system .Va inbox instead of in the .Mx -sx .Sx "secondary mailbox" .Ev MBOX . Does not override the .Ic delete command. \*(UA deviates from the POSIX standard with this command, because a .Ic next command issued after .Ic hold will display the following message, not the current one. . . .Mx .It Ic if (i) Part of the .Ic \&\&if , Ic elif , else , endif conditional execution construct \(em if the given condition is true then the encapsulated block is executed. The POSIX standard only supports the (case-insensitive) conditions .Ql r Ns eceive and .Ql s Ns end, the remaining are non-portable extensions. \*(ID In conjunction with the .Cm wysh command prefix(es) .Sx "Shell-style argument quoting" and more test operators are available. . .Bd -literal -offset indent if receive commands ... else commands ... endif .Ed . .Pp Further (case-insensitive) one-argument conditions are .Ql t Ns erminal which evaluates to true in interactive terminal sessions (running with standard input or standard output attached to a terminal, and none of the .Dq quickrun command line options .Fl e , .Fl H and .Fl L have been used), as well as any boolean value (see .Sx "INTERNAL VARIABLES" for textual boolean representations) to mark an enwrapped block as .Dq never execute or .Dq always execute . (Remarks: condition syntax errors skip all branches until .Ic endif . ) . .Pp \*(OU and without .Cm wysh : It is possible to check .Sx "INTERNAL VARIABLES" as well as .Sx ENVIRONMENT variables for existence or compare their expansion against a user given value or another variable by using the .Ql $ .Pf ( Dq variable next ) conditional trigger character; a variable on the right hand side may be signalled using the same mechanism. Variable names may be enclosed in a pair of matching braces. When this mode has been triggered, several operators are available (\*(IN and .Cm wysh : they are always available, and there is no trigger: variables will have been expanded by the shell-compatible parser before the .Ic if etc. command sees them). . .Pp \*(IN Two argument conditions. Variables can be tested for existence and expansion: .Ql -N will test whether the given variable exists, e.g., .Ql -N editalong will evaluate to true when .Va editalong is set, whereas .Ql -Z editalong will if it is not. .Ql -n """$editalong""" will be true if the variable is set and expands to a non-empty string, .Ql -z $'\e$editalong' only if the expansion is empty, whether the variable exists or not. The remaining conditions take three arguments. . .Pp Integer operators treat the arguments on the left and right hand side of the operator as integral numbers and compare them arithmetically. It is an error if any of the operands is not a valid integer, an empty argument (which implies it had been quoted) is treated as if it were 0. Via the question mark .Ql \&? modifier suffix a saturated operation mode is available where numbers will linger at the minimum or maximum possible value, instead of overflowing (or trapping), the keyword .Ql saturated is optional, e.g., .Ql ==? , .Ql ==?satu and .Ql ==?saturated are identical. Available operators are .Ql -lt (less than), .Ql -le (less than or equal to), .Ql -eq (equal), .Ql -ne (not equal), .Ql -ge (greater than or equal to), and .Ql -gt (greater than). . .Pp String and regular expression data operators compare the left and right hand side according to their textual content. Unset variables are treated as the empty string. Via the question mark .Ql \&? modifier suffix a case-insensitive operation mode is available, the keyword .Ql case is optional, e.g., .Ql ==? and .Ql ==?case are identical. . .Pp Available string operators are .Ql < (less than), .Ql <= (less than or equal to), .Ql == (equal), .Ql != (not equal), .Ql >= (greater than or equal to), .Ql > (greater than), .Ql =% (is substring of) and .Ql !% (is not substring of). By default these operators work on bytes and (therefore) do not take into account character set specifics. If the case-insensitivity modifier has been used, case is ignored according to the rules of the US-ASCII encoding, i.e., bytes are still compared. . .Pp When the \*(OPal regular expression support is available, the additional string operators .Ql =~ and .Ql !~ can be used. They treat the right hand side as an extended regular expression that is matched according to the active locale (see .Sx "Character sets" ) , i.e., character sets should be honoured correctly. . .Pp Conditions can be joined via AND-OR lists (where the AND operator is .Ql && and the OR operator is .Ql || ) , which have equal precedence and will be evaluated with left associativity, thus using the same syntax that is known for the .Xr sh 1 . It is also possible to form groups of conditions and lists by enclosing them in pairs of brackets .Ql [\ \&.\&.\&.\ ] , which may be interlocked within each other, and also be joined via AND-OR lists. . .Pp The results of individual conditions and entire groups may be modified via unary operators: the unary operator .Ql \&! will reverse the result. . .Bd -literal -offset indent wysh set v15-compat=yes # with value: automatic "wysh"! if -N debug;echo *debug* set;else;echo not;endif if [ "$ttycharset" == UTF-8 ] || \e [ "$ttycharset" ==?case UTF8 ] echo *ttycharset* is UTF-8, the former case-sensitive! endif set t1=one t2=one if [ "${t1}" == "${t2}" ] echo These two variables are equal endif if "$features" =% +regex && "$TERM" =~?case "^xterm\&.*" echo ..in an X terminal endif if [ [ true ] && [ [ "${debug}" != '' ] || \e [ "$verbose" != '' ] ] ] echo Noisy, noisy endif if true && [ -n "$debug" || -n "${verbose}" ] echo Left associativity, as is known from the shell endif .Ed . . .Mx .It Ic ignore (ig) Identical to .Ic discard . Superseded by the multiplexer .Ic headerpick . . .Mx .It Ic list Shows the names of all available commands, alphabetically sorted. If given any non-whitespace argument the list will be shown in the order in which command prefixes are searched. \*(OP In conjunction with a set variable .Va verbose additional information will be provided for each command: the argument type will be indicated, the documentation string will be shown, and the set of command flags will show up: .Pp .Bl -tag -compact -width ".It Ql NEEDS_BOX" .It Ql "`local'" command supports the command modifier .Cm local . .It Ql "`vput'" command supports the command modifier .Cm vput . .It Ql "*!*" the error number is tracked in .Va \&! . .It Ql needs-box whether the command needs an active mailbox, a .Ic file . .It Ql ok: indicators whether command is \&.\h'.3m'.\h'.3m'. .Bl -tag -compact -width ".It Ql SUBPROCESS" .It Ql batch/interactive usable in interactive or batch mode .Pf ( Fl # ) . .It Ql send-mode usable in send mode. .It Ql subprocess allowed to be used when running in a subprocess instance, e.g., from within a macro that is called via .Va on-compose-splice . .El .It Ql not ok: indicators whether command is not \&.\h'.3m'.\h'.3m'. .Bl -tag -compact -width ".It Ql COMPOSE_MODE" .It Ql compose-mode available in compose mode. .It Ql startup available during program startup, e.g., in .Sx "Resource files" . .El .It Ql "gabby" The command produces .Va history-gabby .Ic history entries. .El . . .Mx .It Ic localopts Enforce change localization of .Ic environ (linked) .Sx ENVIRONMENT as well as (global) .Sx "INTERNAL VARIABLES" , meaning that their state will be reverted to the former one once the .Dq covered scope is left. Just like the command modifier .Cm local , which provides block-scope localization for some commands (instead), it can only be used inside of macro definition blocks introduced by .Ic account or .Ic define . The covered scope of an .Ic account is left once a different account is activated, and some macros, notably .Va folder-hook Ns s , use their own specific notion of covered scope, here it will be extended until the folder is left again. . .Pp This setting stacks up: i.e., if .Ql macro1 enables change localization and calls .Ql macro2 , which explicitly resets localization, then any value changes within .Ql macro2 will still be reverted when the scope of .Ql macro1 is left. (Caveats: if in this example .Ql macro2 changes to a different .Ic account which sets some variables that are already covered by localizations, their scope will be extended, and in fact leaving the .Ic account will (thus) restore settings in (likely) global scope which actually were defined in a local, macro private context!) . .Pp This command takes one or two arguments, the optional first one specifies an attribute that may be one of .Cm \&\&scope , which refers to the current scope and is thus the default, .Cm call , which causes any macro that is being .Ic call Ns ed to be started with localization enabled by default, as well as .Cm call-fixate , which (if enabled) disallows any called macro to turn off localization: like this it can be ensured that once the current scope regains control, any changes made in deeper levels have been reverted. The latter two are mutually exclusive, and neither affects .Ic xcall . The (second) argument is interpreted as a boolean (string, see .Sx "INTERNAL VARIABLES" ) and states whether the given attribute shall be turned on or off. . .Bd -literal -offset indent define temporary_settings { set possibly_global_option1 localopts on set localized_option1 set localized_option2 localopts scope off set possibly_global_option2 } .Ed . . .Mx .It Ic Lreply Reply to messages that come in via known .Pf ( Ic mlist ) or subscribed .Pf ( Ic mlsubscribe ) mailing lists, or pretend to do so (see .Sx "Mailing lists" ) : on top of the usual .Ic reply functionality this will actively resort and even remove message recipients in order to generate a message that is supposed to be sent to a mailing list. For example it will also implicitly generate a .Ql Mail-Followup-To: header if that seems useful, regardless of the setting of the variable .Va followup-to . For more documentation please refer to .Sx "On sending mail, and non-interactive mode" . .Pp This may generate the errors .Va ^ERR Ns -DESTADDRREQ if no receiver has been specified, .Va ^ERR Ns -PERM if some addressees where rejected by .Va expandaddr , .Va ^ERR Ns -NODATA if no applicable messages have been given, .Va ^ERR Ns -IO if an I/O error occurs, .Va ^ERR Ns -NOTSUP if a necessary character set conversion fails, and .Va ^ERR Ns -INVAL for other errors. Occurrence of some of the errors depend on the value of .Va expandaddr . Any error stops processing of further messages. . .Mx .It Ic Mail Similar to .Ic mail , but saves the message in a file named after the local part of the first recipient's address (instead of in .Va record Ns ). . .Mx .It Ic mail (m) Takes a (list of) recipient address(es) as (an) argument(s), or asks on standard input if none were given; then collects the remaining mail content and sends it out. Unless the internal variable .Va fullnames is set recipient addresses will be stripped from comments, names etc. For more documentation please refer to .Sx "On sending mail, and non-interactive mode" . .Pp This may generate the errors .Va ^ERR Ns -DESTADDRREQ if no receiver has been specified, .Va ^ERR Ns -PERM if some addressees where rejected by .Va expandaddr , .Va ^ERR Ns -NODATA if no applicable messages have been given, .Va ^ERR Ns -NOTSUP if multiple messages have been specified, .Va ^ERR Ns -IO if an I/O error occurs, .Va ^ERR Ns -NOTSUP if a necessary character set conversion fails, and .Va ^ERR Ns -INVAL for other errors. Occurrence of some of the errors depend on the value of .Va expandaddr . . .Mx .It Ic mbox (mb) The given message list is to be sent to the .Mx -sx .Sx "secondary mailbox" .Ev MBOX when \*(UA is quit; this is the default action unless the variable .Va hold is set. \*(ID This command can only be used in a .Mx -sx .Sx "primary system mailbox" . . .Mx .Mx .It Ic mimetype , unmimetype \*(NQ Without arguments the content of the MIME type cache will displayed; a more verbose listing will be produced if either of .Va debug or .Va verbose are set. When given arguments they will be joined, interpreted as shown in .Sx "The mime.types files" (also see .Sx "HTML mail and MIME attachments" ) , and the resulting entry will be added (prepended) to the cache. In any event MIME type sources are loaded first as necessary \(en .Va mimetypes-load-control can be used to fine-tune which sources are actually loaded. .Pp The latter command deletes all specifications of the given MIME type, thus .Ql \&? unmimetype text/plain will remove all registered specifications for the MIME type .Ql text/plain . The special name .Ql * will discard all existing MIME types, just as will .Ql reset , but which also reenables cache initialization via .Va mimetypes-load-control . . .Mx .It Ic mimeview \*(ID Only available in interactive mode, this command allows one to display MIME parts which require external MIME handler programs to run which do not integrate in \*(UAs normal .Ic type output (see .Sx "HTML mail and MIME attachments" ) . (\*(ID No syntax to directly address parts, this restriction may vanish.) The user will be asked for each non-text part of the given message in turn whether the registered handler shall be used to display the part. . .Mx .Mx .It Ic mlist , unmlist \*(NQ Manage the list of known .Sx "Mailing lists" ; subscriptions are controlled via .Ic mlsubscribe . The latter command deletes all given arguments, or all at once when given the asterisk .Ql * . The former shows the list of all currently known lists if used without arguments, otherwise the given arguments will become known. \*(OP In the latter case, arguments which contain any of the .Mx -sx .Sx "magic regular expression characters" will be interpreted as one, possibly matching many addresses; these will be sequentially matched via linked lists instead of being looked up in a dictionary. . .Mx .Mx .It Ic mlsubscribe , unmlsubscribe Building upon the command pair .Ic mlist , unmlist , but only managing the subscription attribute of mailing lists. (The former will also create not yet existing mailing lists.) . .Mx .It Ic Move Similar to .Ic move , but moves the messages to a file named after the local part of the sender address of the first message (instead of in .Va record Ns ). . .Mx .It Ic move Acts like .Ic copy but marks the messages for deletion if they were transferred successfully. . .Mx .It Ic More Like .Ic more , but also displays header fields which would not pass the .Ic headerpick selection, and all MIME parts. Identical to .Ic Page . . .Mx .It Ic more Invokes the .Ev PAGER on the given messages, even in non-interactive mode and as long as the standard output is a terminal. Identical to .Ic page . . .Mx .It Ic netrc \*(OP When used without arguments or if .Ar show has been given the content of the .Pa \*(VN cache is shown, loading it first as necessary. If the argument is .Ar load then the cache will only be initialized and .Ar clear will remove its contents. Note that \*(UA will try to load the file only once, use .Ql Ic \&\&netrc Ns \:\0\:clear to unlock further attempts. See .Va netrc-lookup , .Va netrc-pipe and the section .Sx "On URL syntax and credential lookup" ; the section .Sx "The .netrc file" documents the file format in detail. . .Mx .It Ic newmail Checks for new mail in the current folder without committing any changes before. If new mail is present, a message is shown. If the .Va header variable is set, the headers of each new message are also shown. This command is not available for all mailbox types. . .Mx .It Ic next (n) (like .Ql + or .Dq ENTER ) Goes to the next message in sequence and types it. With an argument list, types the next matching message. . .Mx .It Ic New Same as .Ic Unread . . .Mx .It Ic new Same as .Ic unread . . .Mx .It Ic noop If the current folder is accessed via a network connection, a .Dq NOOP command is sent, otherwise no operation is performed. . .Mx .It Ic Page Like .Ic page , but also displays header fields which would not pass the .Ic headerpick selection, and all MIME parts. Identical to .Ic More . . .Mx .It Ic page Invokes the .Ev PAGER on the given messages, even in non-interactive mode and as long as the standard output is a terminal. Identical to .Ic more . . .Mx .It Ic Pipe Like .Ic pipe but also pipes header fields which would not pass the .Ic headerpick selection, and all parts of MIME .Ql multipart/alternative messages. . .Mx .It Ic pipe (pi) Takes an optional message list and shell command (that defaults to .Va cmd ) , and pipes the messages through the command. If the .Va page variable is set, every message is followed by a formfeed character. . .Mx .It Ic preserve (pre) A synonym for .Ic hold . . .Mx .It Ic Print (P) Alias for .Ic Type . . .Mx .It Ic print (p) Research .Ux equivalent of .Ic type . . .Mx .It Ic quit (q) Terminates the session, saving all undeleted, unsaved messages in the current .Mx -sx .Sx "secondary mailbox" .Ev MBOX , preserving all messages marked with .Ic hold or .Ic preserve or never referenced in the system .Va inbox , and removing all other messages from the .Mx -sx .Sx "primary system mailbox" . If new mail has arrived during the session, the message .Dq You have new mail will be shown. If given while editing a mailbox file with the command line option .Fl f , then the edit file is rewritten. A return to the shell is effected, unless the rewrite of edit file fails, in which case the user can escape with the exit command. The optional status number argument will be passed through to .Xr exit 3 . \*(ID For now it can happen that the given status will be overwritten, later this will only occur if a later error needs to be reported onto an otherwise success indicating status. . .Mx .It Ic read \*(NQ Read a line from standard input, or the channel set active via .Ic readctl , and assign the data, which will be split as indicated by .Va ifs , to the given variables. The variable names are checked by the same rules as documented for .Cm vput , and the same error codes will be seen in .Va \&! ; the exit status .Va \&? indicates the number of bytes read, it will be .Ql -1 with the error number .Va \&! set to .Va ^ERR Ns -BADF in case of I/O errors, or .Va ^ERR Ns -NONE upon End-Of-File. If there are more fields than variables, assigns successive fields to the last given variable. If there are less fields than variables, assigns the empty string to the remains. .Bd -literal -offset indent ? read a b c H e l l o ? echo "<$a> <$b> <$c>" ? wysh set ifs=:; read a b c;unset ifs hey2.0,:"'you ",:world!:mars.: ? echo $?/$^ERRNAME / <$a><$b><$c> 0/NONE / <"'you ",><><> .Ed . .Mx .It Ic readall \*(NQ Read anything from standard input, or the channel set active via .Ic readctl , and assign the data to the given variable. The variable name is checked by the same rules as documented for .Cm vput , and the same error codes will be seen in .Va \&! ; the exit status .Va \&? indicates the number of bytes read, it will be .Ql -1 with the error number .Va \&! set to .Va ^ERR Ns -BADF in case of I/O errors, or .Va ^ERR Ns -NONE upon End-Of-File. \*(ID The input data length is restricted to 31-bits. . .Mx .It Ic readctl \*(NQ Manages input channels for .Ic read and .Ic readall , to be used to avoid complicated or impracticable code, like calling .Ic \&\&read from within a macro in non-interactive mode. Without arguments, or when the first argument is .Cm show , a listing of all known channels is printed. Channels can otherwise be .Cm create Ns d, and existing channels can be .Cm set active and .Cm remove Ns d by giving the string used for creation. .Pp The channel name is expected to be a file descriptor number, or, if parsing the numeric fails, an input file name that undergoes .Sx "Filename transformations" . E.g. (this example requires a modern shell): .Bd -literal -offset indent $ LC_ALL=C printf 'echon "hey, "\enread a\enyou\enecho $a' |\e LC_ALL=C \*(uA -R# hey, you $ LC_ALL=C printf 'echon "hey, "\enread a\enecho $a' |\e LC_ALL=C 6<<< 'you' \*(uA -R#X'readctl create 6' hey, you .Ed . .Mx .It Ic remove Removes the named files or directories. .Sx "Filename transformations" including shell pathname wildcard pattern expansions .Pf ( Xr glob 7 ) are performed on the arguments. If a name refer to a mailbox, e.g., a Maildir mailbox, then a mailbox type specific removal will be performed, deleting the complete mailbox. The user is asked for confirmation in interactive mode. . .Mx .It Ic rename Takes the name of an existing folder and the name for the new folder and renames the first to the second one. .Sx "Filename transformations" including shell pathname wildcard pattern expansions .Pf ( Xr glob 7 ) are performed on both arguments. Both folders must be of the same type. . .Mx .It Ic Reply (R) Identical to .Ic reply except that it replies to only the sender of each message of the given list, by using the first message as the template to quote, for the .Ql Subject: etc.; setting .Va flipr will exchange this command with .Ic reply . . .Mx .It Ic reply (r) Take a message and group-responds to it by addressing the sender and all recipients, subject to .Ic alternates processing. .Va followup-to , .Va followup-to-honour , .Va reply-to-honour as well as .Va recipients-in-cc influence response behaviour. Unless the internal variable .Va fullnames is set recipient addresses will be stripped from comments, names etc. .Va quote as well as .Va quote-as-attachment configure whether responded-to message shall be quoted etc.; setting .Va flipr will exchange this command with .Ic Reply . The command .Ic Lreply offers special support for replying to mailing lists. For more documentation please refer to .Sx "On sending mail, and non-interactive mode" . .Pp This may generate the errors .Va ^ERR Ns -DESTADDRREQ if no receiver has been specified, .Va ^ERR Ns -PERM if some addressees where rejected by .Va expandaddr , .Va ^ERR Ns -NODATA if no applicable messages have been given, .Va ^ERR Ns -IO if an I/O error occurs, .Va ^ERR Ns -NOTSUP if a necessary character set conversion fails, and .Va ^ERR Ns -INVAL for other errors. Occurrence of some of the errors depend on the value of .Va expandaddr . Any error stops processing of further messages. . .Mx .It Ic replyall Similar to .Ic reply , but initiates a group-reply regardless of the value of .Va flipr . . .Mx .It Ic replysender Similar to .Ic Reply , but responds to the sender only regardless of the value of .Va flipr . . .Mx .It Ic Resend Like .Ic resend , but does not add any header lines. This is not a way to hide the sender's identity, but useful for sending a message again to the same recipients. . .Mx .It Ic resend Takes a list of messages and a name, and sends each message to the given, fully expanded addressee. .Ql Resent-From: and related header fields are prepended to the new copy of the message. Saving in .Va record is only performed if .Va record-resent is set. .Pp This may generate the errors .Va ^ERR Ns -DESTADDRREQ if no receiver has been specified, .Va ^ERR Ns -PERM if the addressee was rejected by .Va expandaddr , .Va ^ERR Ns -NODATA if no applicable messages have been given, .Va ^ERR Ns -IO if an I/O error occurs, .Va ^ERR Ns -NOTSUP if a necessary character set conversion fails, and .Va ^ERR Ns -INVAL for other errors. Occurrence of some of the errors depend on the value of .Va expandaddr . Any error stops processing of further messages. . .Mx .It Ic Respond Same as .Ic Reply . . .Mx .It Ic respond Same as .Ic reply . . .Mx .It Ic respondall Same as .Ic replyall . . .Mx .It Ic respondsender Same as .Ic replysender . . .Mx .It Ic retain (ret) Superseded by the multiplexer .Ic headerpick . . .Mx .It Ic return Only available inside the scope of a .Ic define Ns d macro or an .Ic account , this will stop evaluation of any further macro content, and return execution control to the caller. The two optional parameters must be specified as positive decimal numbers and default to the value 0: the first argument specifies the signed 32-bit return value (stored in .Va \&? \*(ID and later extended to signed 64-bit), the second the signed 32-bit error number (stored in .Va \&! ) . As documented for .Va \&? a non-0 exit status may cause the program to exit. . .Mx .It Ic Save (S) Similar to .Ic save, but saves the messages in a file named after the local part of the sender of the first message instead of (in .Va record and) taking a filename argument; the variable .Va outfolder is inspected to decide on the actual storage location. . .Mx .It Ic save (s) Takes a message list and a filename and appends each message in turn to the end of the file. .Sx "Filename transformations" including shell pathname wildcard pattern expansions .Pf ( Xr glob 7 ) is performed on the filename. If no filename is given, the .Mx -sx .Sx "secondary mailbox" .Ev MBOX is used. The filename in quotes, followed by the generated character count is echoed on the user's terminal. If editing a .Mx -sx .Sx "primary system mailbox" the messages are marked for deletion. .Sx "Filename transformations" will be applied. To filter the saved header fields to the desired subset use the .Ql save slot of the white- and blacklisting command .Ic headerpick . . .It Ic savediscard \*(OB Superseded by the multiplexer .Ic headerpick . . .It Ic saveignore \*(OB Superseded by the multiplexer .Ic headerpick . . .It Ic saveretain \*(OB Superseded by the multiplexer .Ic headerpick . . .Mx .It Ic search Takes a message specification (list) and displays a header summary of all matching messages, as via .Ic headers . This command is an alias of .Ic from . Also see .Sx "Specifying messages" . . .Mx .It Ic seen Takes a message list and marks all messages as having been read. . . .Mx .Mx .It Ic set , unset (se, \*(NQ uns) The latter command will delete all given global variables, or only block-scope local ones if the .Cm local command modifier has been used. The former, when used without arguments, will show all currently known variables, being more verbose if either of .Va debug or .Va verbose is set. Remarks: this list mode will not automatically link-in known .Sx ENVIRONMENT variables, but only explicit addressing will, e.g., via .Ic varshow , using a variable in an .Ic if condition or a string passed to .Ic echo , explicit .Ic \&\&set Ns ting, as well as some program-internal use cases. . .Pp Otherwise the given variables (and arguments) are set or adjusted. Arguments are of the form .Ql name=value (no space before or after .Ql = ) , or plain .Ql name if there is no value, i.e., a boolean variable. If a name begins with .Ql no , as in .Ql set nosave , the effect is the same as invoking the .Ic \&\&unset command with the remaining part of the variable .Pf ( Ql unset save ) . \*(ID In conjunction with the .Cm wysh .Pf (or\0 Cm local ) command prefix(es) .Sx "Shell-style argument quoting" can be used to quote arguments as necessary. \*(ID Otherwise quotation marks may be placed around any part of the assignment statement to quote blanks or tabs. . .Pp When operating in global scope any .Ql name that is known to map to an environment variable will automatically cause updates in the program environment (unsetting a variable in the environment requires corresponding system support) \(em use the command .Ic environ for further environmental control. If the command modifier .Cm local has been used to alter the command to work in block-scope all variables have values (may they be empty), and creation of names which shadow .Sx "INTERNAL VARIABLES" is actively prevented (\*(ID shadowing of linked .Sx ENVIRONMENT variables and free-form versions of variable chains is not yet detected). Also see .Ic varshow and the sections .Sx "INTERNAL VARIABLES" and .Sx ENVIRONMENT . . .Bd -literal -offset indent ? wysh set indentprefix=' -> ' ? wysh set atab=$'\t' aspace=' ' zero=0 .Ed . . .Mx .It Ic shcodec Apply shell quoting rules to the given raw-data arguments. Supports .Cm vput (see .Sx "Command modifiers" ) . The first argument specifies the operation: .Ar [+]e[ncode] or .Ar d[ecode] cause shell quoting to be applied to the remains of the line, and expanded away thereof, respectively. If the former is prefixed with a plus-sign, the quoted result will not be roundtrip enabled, and thus can be decoded only in the very same environment that was used to perform the encode; also see .Cd mle-quote-rndtrip . If the coding operation fails the error number .Va \&! is set to .Va ^ERR Ns -CANCELED , and the unmodified input is used as the result; the error number may change again due to output or result storage errors. . .Mx .It Ic shell \*(NQ (sh) Invokes an interactive version of the shell, and returns its exit status. . .Mx .Mx .It Ic shortcut , unshortcut \*(NQ Manage the file- or pathname shortcuts as documented for .Ic file . The latter command deletes all shortcuts given as arguments, or all at once when given the asterisk .Ql * . The former shows the list of all currently defined shortcuts if used without arguments, the target of the given with a single argument. Otherwise arguments are treated as pairs of shortcuts and their desired expansion, creating new or updating already existing ones. . .Mx .It Ic shift \*(NQ Shift the positional parameter stack (starting at .Va 1 ) by the given number (which must be a positive decimal), or 1 if no argument has been given. It is an error if the value exceeds the number of positional parameters. If the given number is 0, no action is performed, successfully. The stack as such can be managed via .Ic vpospar . Note this command will fail in .Ic account and hook macros unless the positional parameter stack has been explicitly created in the current context via .Ic vpospar . . .Mx .It Ic show Like .Ic type , but performs neither MIME decoding nor decryption, so that the raw message text is shown. . .Mx .It Ic size (si) Shows the size in characters of each message of the given message-list. . .Mx .It Ic sleep \*(NQ Sleep for the specified number of seconds (and optionally milliseconds), by default interruptable. If a third argument is given the sleep will be uninterruptible, otherwise the error number .Va \&! will be set to .Va ^ERR Ns -INTR if the sleep has been interrupted. The command will fail and the error number will be .Va ^ERR Ns -OVERFLOW if the given duration(s) overflow the time datatype, and .Va ^ERR Ns -INVAL if the given durations are no valid integers. . . .Mx .Mx .It Ic sort , unsort The latter command disables sorted or threaded mode, returns to normal message order and, if the .Va header variable is set, displays a header summary. The former command shows the current sorting criterion when used without an argument, but creates a sorted representation of the current folder otherwise, and changes the .Ic next command and the addressing modes such that they refer to messages in the sorted order. Message numbers are the same as in regular mode. If the .Va header variable is set, a header summary in the new order is also displayed. Automatic folder sorting can be enabled by setting the .Va autosort variable, as in, e.g., .Ql set autosort=thread . Possible sorting criterions are: . .Pp .Bl -tag -compact -width "subject" .It Ar date Sort the messages by their .Ql Date: field, that is by the time they were sent. .It Ar from Sort messages by the value of their .Ql From: field, that is by the address of the sender. If the .Va showname variable is set, the sender's real name (if any) is used. .It Ar size Sort the messages by their size. .It spam \*(OP Sort the message by their spam score, as has been classified by .Ic spamrate . .It Ar status Sort the messages by their message status. .It Ar subject Sort the messages by their subject. .It Ar thread Create a threaded display. .It Ar to Sort messages by the value of their .Ql To: field, that is by the address of the recipient. If the .Va showname variable is set, the recipient's real name (if any) is used. .El . . .Mx .It Ic source \*(NQ (so) The source command reads commands from the given file. .Sx "Filename transformations" will be applied. If the given expanded argument ends with a vertical bar .Ql | then the argument will instead be interpreted as a shell command and \*(UA will read the output generated by it. Dependent on the settings of .Va posix and .Va errexit , and also dependent on whether the command modifier .Cm ignerr had been used, encountering errors will stop sourcing of the given input. \*(ID Note that .Ic \&\&source cannot be used from within macros that execute as .Va folder-hook Ns s or .Ic account Ns s , i.e., it can only be called from macros that were .Ic call Ns ed . . .Mx .It Ic source_if \*(NQ The difference to .Ic source (beside not supporting pipe syntax aka shell command input) is that this command will not generate an error nor warn if the given file argument cannot be opened successfully. . .Mx .It Ic spamclear \*(OP Takes a list of messages and clears their .Ql is-spam flag. . .Mx .It Ic spamforget \*(OP Takes a list of messages and causes the .Va spam-interface to forget it has ever used them to train its Bayesian filter. Unless otherwise noted the .Ql is-spam flag of the message is inspected to chose whether a message shall be forgotten to be .Dq ham or .Dq spam . . .Mx .It Ic spamham \*(OP Takes a list of messages and informs the Bayesian filter of the .Va spam-interface that they are .Dq ham . This also clears the .Ql is-spam flag of the messages in question. . .Mx .It Ic spamrate \*(OP Takes a list of messages and rates them using the configured .Va spam-interface , without modifying the messages, but setting their .Ql is-spam flag as appropriate; because the spam rating headers are lost the rate will be forgotten once the mailbox is left. Refer to the manual section .Sx "Handling spam" for the complete picture of spam handling in \*(UA. . .Mx .It Ic spamset \*(OP Takes a list of messages and sets their .Ql is-spam flag. . .Mx .It Ic spamspam \*(OP Takes a list of messages and informs the Bayesian filter of the .Va spam-interface that they are .Dq spam . This also sets the .Ql is-spam flag of the messages in question. . .It Ic thread \*(OB The same as .Ql sort thread (consider using a .Ql commandalias as necessary). . . .Mx .It Ic tls \*(NQ TLS information and management command multiplexer to aid in .Sx "Encrypted network communication" . Commands support .Cm vput if so documented (see .Sx "Command modifiers" ) . The result that is shown in case of errors is always the empty string, errors can be identified via the error number .Va \&! . For example, string length overflows are caught and set .Va \&! to .Va ^ERR Ns -OVERFLOW . Note this command of course honours the overall TLS configuration. .Bd -literal -offset indent ? vput tls result fingerprint pop3s://ex.am.ple ? echo $?/$!/$^ERRNAME: $result .Ed . .Bl -hang -width ".It Cm random" .It Cm fingerprint Show the .Va tls-fingerprint-digest Ns ed fingerprint of the certificate of the given HOST .Pf ( Ql server:port , where the port defaults to the HTTPS port, 443). .Va tls-fingerprint is actively ignored for the runtime of this command. Only available if the term .Ql +sockets is included in .Va features . .El . . .Mx .It Ic Top Like .Ic top but always uses the .Ic headerpick .Ql type slot for white- and blacklisting header fields. . .Mx .It Ic top (to) Takes a message list and types out the first .Va toplines lines of each message on the user's terminal. Unless a special selection has been established for the .Ql top slot of the .Ic headerpick command, the only header fields that are displayed are .Ql From: , .Ql To: , .Ql CC: , and .Ql Subject: . .Ic Top will always use the .Ql type .Ic headerpick selection instead. It is possible to apply compression to what is displayed by setting .Va topsqueeze . Messages are decrypted and converted to the terminal character set if necessary. . .Mx .It Ic touch (tou) Takes a message list and marks the messages for saving in the .Mx -sx .Sx "secondary mailbox" .Ev MBOX . \*(UA deviates from the POSIX standard with this command, as a following .Ic next command will display the following message instead of the current one. . .Mx .It Ic Type (T) Like .Ic type but also displays header fields which would not pass the .Ic headerpick selection, and all visualizable parts of MIME .Ql multipart/alternative messages. . .Mx .It Ic type (t) Takes a message list and types out each message on the user's terminal. The display of message headers is selectable via .Ic headerpick . For MIME multipart messages, all parts with a content type of .Ql text , all parts which have a registered MIME type handler (see .Sx "HTML mail and MIME attachments" ) which produces plain text output, and all .Ql message parts are shown, others are hidden except for their headers. Messages are decrypted and converted to the terminal character set if necessary. The command .Ic mimeview can be used to display parts which are not displayable as plain text. . .It Ic unaccount See .Ic account . . .It Ic unalias (una) See .Ic alias . . .It Ic unanswered See .Ic answered . . .It Ic unbind See .Ic bind . . .It Ic uncollapse See .Ic collapse . . .It Ic uncolour See .Ic colour . . .It Ic undefine See .Ic define . . .It Ic undelete See .Ic delete . . .It Ic undraft See .Ic draft . . .It Ic unflag See .Ic flag . . .It Ic unfwdignore \*(OB Superseded by the multiplexer .Ic headerpick . . .It Ic unfwdretain \*(OB Superseded by the multiplexer .Ic headerpick . . .Mx .It Ic unignore Superseded by the multiplexer .Ic headerpick . . .It Ic unmimetype See .Ic mimetype . . .It Ic unmlist See .Ic mlist . . .It Ic unmlsubscribe See .Ic mlsubscribe . . .Mx .It Ic Unread Same as .Ic unread . . .Mx .It Ic unread Takes a message list and marks each message as not having been read. . .Mx .It Ic unretain Superseded by the multiplexer .Ic headerpick . . .It Ic unsaveignore \*(OB Superseded by the multiplexer .Ic headerpick . . .It Ic unsaveretain \*(OB Superseded by the multiplexer .Ic headerpick . . .It Ic unset \*(NQ (uns) See .Ic set . . .It Ic unshortcut See .Ic shortcut . . .It Ic unsort See .Ic short . . .It Ic unthread \*(OB Same as .Ic unsort . . .Mx .It Ic urlcodec Perform URL percent codec operations on the raw-data argument, rather according to RFC 3986. The first argument specifies the operation: .Ar e[ncode] or .Ar d[ecode] perform plain URL percent en- and decoding, respectively. .Ar p[ath]enc[ode] and .Ar p[ath]dec[ode] perform a slightly modified operation which should be better for pathnames: it does not allow a tilde .Ql ~ , and will neither accept hyphen-minus .Ql - nor dot .Ql . as an initial character. The remains of the line form the URL data which is to be converted. This is a character set agnostic operation, and it may thus decode bytes which are invalid in the current .Va ttycharset . .Pp Supports .Cm vput (see .Sx "Command modifiers" ) , and manages the error number .Va \&! . If the coding operation fails the error number .Va \&! is set to .Va ^ERR Ns -CANCELED , and the unmodified input is used as the result; the error number may change again due to output or result storage errors. \*(ID This command does not know about URLs beside what is documented. .Pf ( Ic vexpr offers a .Cm makeprint subcommand, shall the URL be displayed.) . .Mx .It Ic varshow \*(NQ This command produces the same output as the listing mode of .Ic set , including .Va verbose Ns ity adjustments, but only for the given variables. . .Mx .It Ic verify \*(OP Takes a message list and verifies each message. If a message is not a S/MIME signed message, verification will fail for it. The verification process checks if the message was signed using a valid certificate, if the message sender's email address matches one of those contained within the certificate, and if the message content has been altered. . .Mx .It Ic version Shows the .Va version and .Va features of \*(UA, optionally in a more .Va verbose form which also includes the build and running system environment. This command supports .Cm vput (see .Sx "Command modifiers" ) . . . .Mx .It Ic vexpr \*(NQ A multiplexer command which offers signed 64-bit numeric calculations, as well as other, mostly string-based operations. C-style byte string operations are available via .Ic csop . The first argument defines the number, type, and meaning of the remaining arguments. An empty number argument is treated as 0. Supports .Cm vput (see .Sx "Command modifiers" ) . The result shown in case of errors is .Ql -1 for usage errors and numeric operations, the empty string otherwise; .Dq soft errors, like when a search operation failed, will also set the .Va \&! error number to .Va ^ERR Ns -NODATA . Except when otherwise noted numeric arguments are parsed as signed 64-bit numbers, and errors will be reported in the error number .Va \&! as the numeric error .Va ^ERR Ns -RANGE . . .Pp Numeric operations work on one or two signed 64-bit integers. Numbers prefixed with .Ql 0x or .Ql 0X are interpreted as hexadecimal (base 16) numbers, whereas .Ql 0 indicates octal (base 8), and .Ql 0b as well as .Ql 0B denote binary (base 2) numbers. It is possible to use any base in between 2 and 36, inclusive, with the .Ql BASE#number notation, where the base is given as an unsigned decimal number, e.g., .Ql 16#AFFE is a different way of specifying a hexadecimal number. Unsigned interpretation of a number can be enforced by prefixing an .Ql u (case-insensitively), e.g., .Ql u-110 ; this is not necessary for power-of-two bases (2, 4, 8, 16 and 32), which will be interpreted as unsigned by default, but it still makes a difference regarding overflow detection and overflow constant. It is possible to enforce signed interpretation by (instead) prefixing a .Ql s (case-insensitively). The number sign notation uses a permissive parse mode and as such supports complicated conditions out of the box: .Bd -literal -offset indent ? wysh set ifs=:;read i;unset ifs;echo $i;vexpr pb 2 10#$i -009 < -009> 0b1001 .Ed . .Pp One integer is expected by assignment (equals sign .Ql = ) , which does nothing but parsing the argument, thus detecting validity and possible overflow conditions, unary not (tilde .Ql ~ ) , which creates the bitwise complement, and unary plus and minus. Two integers are used by addition (plus sign .Ql + ) , subtraction (hyphen-minus .Ql - ) , multiplication (asterisk .Ql * ) , division (solidus .Ql / ) and modulo (percent sign .Ql % ) , as well as for the bitwise operators logical or (vertical bar .Ql | , to be quoted) , bitwise and (ampersand .Ql \&& , to be quoted) , bitwise xor (circumflex .Ql ^ ) , the bitwise signed left- and right shifts .Pf ( Ql << , .Ql >> ) , as well as for the unsigned right shift .Ql >>> . . .Pp Another numeric operation is .Cm pbase , which takes a number base in between 2 and 36, inclusive, and will act on the second number given just the same as what equals sign .Ql = does, but the number result will be formatted in the base given, as a signed 64-bit number unless unsigned interpretation of the input number had been forced (with an u prefix). . .Pp Numeric operations support a saturated mode via the question mark .Ql \&? modifier suffix; the keyword .Ql saturated is optional, e.g., .Ql +? , .Ql +?satu , and .Ql +?saturated are identical. In saturated mode overflow errors and division and modulo by zero are no longer reported via the exit status, but the result will linger at the minimum or maximum possible value, instead of overflowing (or trapping). This is true also for the argument parse step. For the bitwise shifts, the saturated maximum is 63. Any caught overflow will be reported via the error number .Va \&! as .Va ^ERR Ns -OVERFLOW . . .Bd -literal -offset indent ? vput vexpr res -? +1 -9223372036854775808 ? echo $?/$!/$^ERRNAME:$res 0/75/OVERFLOW:-9223372036854775808 .Ed . .Pp Character set agnostic string functions have no notion of locale settings and character sets. . .Bl -hang -width ".It Cm random" .It Cm file-expand Performs the usual .Sx "Filename transformations" on its argument. .It Cm file-stat , file-lstat Perform the usual .Sx "Filename transformations" on the argument, then call .Xr stat 2 and .Xr lstat 2 , respectively, in order to echo some stat fields such that .Ql vput vexpr v file-stat FILE; eval wysh set $v creates accessible variables. .It Cm random Generates a random string of the given length, or of .Dv \&\&PATH_MAX bytes (a constant from .Pa /usr/include ) if the value 0 is given; the random string will be base64url encoded according to RFC 4648, and thus be usable as a (portable) filename. .El . .Pp String operations work, sufficient support provided, according to the active user's locale encoding and character set (see .Sx "Character sets" ) . Where the question mark .Ql \&? modifier suffix is supported, a case-insensitive operation mode is available; the keyword .Ql case is optional, e.g., .Ql regex? and .Ql regex?case are identical. . .Bl -hang -width ".It Cm regex" .It Cm makeprint (One-way) Converts the argument to something safely printable on the terminal. . .It Cm regex \*(OP A string operation that will try to match the first argument with the regular expression given as the second argument. .Ql \&? modifier suffix is supported. If the optional third argument has been given then instead of showing the match offset a replacement operation is performed: the third argument is treated as if specified within dollar-single-quote (see .Sx "Shell-style argument quoting" ) , and any occurrence of a positional parameter, e.g., .Va \&0 , 1 etc. is replaced with the according match group of the regular expression: .Bd -literal -offset indent ? vput vexpr res regex bananarama \e (.*)NanA(.*) '\e${1}au\e$2' ? echo $?/$!/$^ERRNAME:$res: 1/61/NODATA:: ? vput vexpr res regex?case bananarama \e (.*)NanA(.*) '\e${1}uauf\e$2' ? echo $?/$!/$^ERRNAME:$res: 0/0/NONE:bauauframa: .Ed .El . . .Mx .It Ic vpospar \*(NQ Manage the positional parameter stack (see .Va 1 , # , * , @ as well as .Ic shift ) . If the first argument is .Ql clear , then the positional parameter stack of the current context, or the global one, if there is none, is cleared. If it is .Ql set , then the remaining arguments will be used to (re)create the stack, if the parameter stack size limit is excessed an .Va ^ERR Ns -OVERFLOW error will occur. . .Pp If the first argument is .Ql quote , a round-trip capable representation of the stack contents is created, with each quoted parameter separated from each other with the first character of .Va ifs , and followed by the first character of .Va if-ws , if that is not empty and not identical to the first. If that results in no separation at all a .Cm space character is used. This mode supports .Cm vput (see .Sx "Command modifiers" ) . I.e., the subcommands .Ql set and .Ql quote can be used (in conjunction with .Ic eval ) to (re)create an argument stack from and to a single variable losslessly. . .Bd -literal -offset indent ? vpospar set hey, "'you ", world! ? echo $#: <${1}><${2}><${3}> ? vput vpospar x quote ? vpospar clear ? echo $#: <${1}><${2}><${3}> ? eval vpospar set ${x} ? echo $#: <${1}><${2}><${3}> .Ed . . .Mx .It Ic visual (v) Takes a message list and invokes the .Ev VISUAL display editor on each message. Modified contents are discarded unless the .Va writebackedited variable is set, and are not used unless the mailbox can be written to and the editor returns a successful exit status. .Ic edit can be used instead for a less display oriented editor. . .Mx .It Ic write (w) For conventional messages the body without all headers is written. The original message is never marked for deletion in the originating mail folder. The output is decrypted and converted to its native format as necessary. If the output file exists, the text is appended. If a message is in MIME multipart format its first part is written to the specified file as for conventional messages, handling of the remains depends on the execution mode. No special handling of compressed files is performed. .Pp In interactive mode the user is consecutively asked for the filenames of the processed parts. For convience saving of each part may be skipped by giving an empty value, the same result as writing it to .Pa /dev/null . Shell piping the part content by specifying a leading vertical bar .Ql | character for the filename is supported. Other user input undergoes the usual .Sx "Filename transformations" , including shell pathname wildcard pattern expansions .Pf ( Xr glob 7 ) and shell variable expansion for the message as such, not the individual parts, and contents of the destination file are overwritten if the file previously existed. .Pp \*(ID In non-interactive mode any part which does not specify a filename is ignored, and suspicious parts of filenames of the remaining parts are URL percent encoded (as via .Ic urlcodec ) to prevent injection of malicious character sequences, resulting in a filename that will be written into the current directory. Existing files will not be overwritten, instead the part number or a dot are appended after a number sign .Ql # to the name until file creation succeeds (or fails due to other reasons). . .Mx .It Ic xcall \*(NQ The sole difference to .Ic call is that the new macro is executed in place of the current one, which will not regain control: all resources of the current macro will be released first. This implies that any setting covered by .Ic localopts will be forgotten and covered variables will become cleaned up. If this command is not used from within a .Ic call Ns ed macro it will silently be (a more expensive variant of) .Ic call . . .Mx .It Ic xit (x) A synonym for .Ic exit . . .Mx .It Ic z \*(NQ \*(UA presents message headers in .Va screen Ns fuls as described under the .Ic headers command. Without arguments this command scrolls to the next window of messages, likewise if the argument is .Ql + . An argument of .Ql - scrolls to the last, .Ql ^ scrolls to the first, and .Ql $ to the last .Va \&\&screen of messages. A number argument prefixed by .Ql + or .Ql \- indicates that the window is calculated in relation to the current position, and a number without a prefix specifies an absolute position. . .Mx .It Ic Z \*(NQ Similar to .Ic z , but scrolls to the next or previous window that contains at least one .Ql new or .Ic flag Ns ged message. .El .\" }}} . .\" }}} . . .\" .Sh COMMAND ESCAPES {{{ .Sh "COMMAND ESCAPES" . Command escapes are available in compose mode, and are used to perform special functions when composing messages. Command escapes are only recognized at the beginning of lines, and consist of a trigger (escape), and a command character. The actual escape character can be set via the internal variable .Va escape , it defaults to the tilde .Ql ~ . Otherwise ignored whitespace characters following the escape character will prevent a possible addition of the command line to the \*(OPal history. . .Pp Unless otherwise noted all compose mode command escapes ensure proper updates of the variables which represent the error number .Va \&! and the exit status .Va \&? . If the variable .Va errexit is set they will, unless stated otherwise, error out message compose mode and cause a program exit if an operation fails; an effect equivalent to the command modifier .Cm ignerr can however be achieved by placing a hyphen-minus .Ql - after (possible whitespace following) the escape character. If the \*(OPal key bindings are available it is possible to create .Ic bind Ns ings specifically for the compose mode. . . .Bl -tag -width ".It Ic BaNg" .Mx .It Ic ~~ Ar string Insert the string of text in the message prefaced by a single .Ql ~ . (If the escape character has been changed, that character must be doubled instead.) . .Mx .It Ic ~! Ar command Execute the indicated shell .Ar command which follows, replacing unescaped exclamation marks with the previously executed command if the internal variable .Va bang is set, then return to the message. . .Mx .It Ic ~. End compose mode and send the message. The hooks .Va on-compose-splice-shell and .Va on-compose-splice , in order, will be called when set, after which, in interactive mode .Va askatend (leading to .Va askcc , askbcc ) and .Va askattach will be checked as well as .Va asksend , after which a set .Va on-compose-leave hook will be called, .Va autocc and .Va autobcc will be joined in if set, finally a given .Va message-inject-tail will be incorporated, after which the compose mode is left. . .Mx .It Ic ~: Ar \*(UA-command Ns \0or Ic ~_ Ar \*(UA-command Execute the given \*(UA command. Not all commands, however, are allowed. . .Mx .It Ic ~< Ar filename Identical to .Ic ~r . . .Mx .It Ic ~ . Non-network addresses use the first field to indicate the type (hyphen-minus .Ql - for files, vertical bar .Ql | for pipes, and number sign .Ql # for names which will undergo .Ic alias processing) instead, the actual value will be in the second field. . .It Ql 212 Status ok; the rest of the line is optionally used for more status. What follows are lines of furtherly unspecified string content, terminated by an empty line. All the input, including the empty line, must be consumed before further commands can be issued. . .It Ql 500 Syntax error; invalid command. . .It Ql 501 Syntax error in parameters or arguments. . .It Ql 505 Error: an argument fails verification. For example an invalid address has been specified (also see .Va expandaddr ) , or an attempt was made to modify anything in \*(UA's own namespace, or a modifying subcommand has been used on a read-only message. . .It Ql 506 Error: an otherwise valid argument is rendered invalid due to context. For example, a second address is added to a header which may consist of a single address only. .El . . .Pp If a command indicates failure then the message will have remained unmodified. Most commands can fail with .Ql 500 if required arguments are missing (false command usage). The following (case-insensitive) commands are supported: . . .Bl -hang -width ".It Cm version" .It Cm attachment This command allows listing, removal and addition of message attachments. The second argument specifies the subcommand to apply, one of: . .Bl -hang -width ".It Cm remove" .It Cm attribute This uses the same search mechanism as described for .Cm remove and prints any known attributes of the first found attachment via .Ql 212 upon success or .Ql 501 if no such attachment can be found. The attributes are written as lines of keyword and value tuples, the keyword being separated from the rest of the line with an ASCII SP space character. . .It Cm attribute-at This uses the same search mechanism as described for .Cm remove-at and is otherwise identical to .Cm attribute . . .It Cm attribute-set This uses the same search mechanism as described for .Cm remove , and will assign the attribute given as the fourth argument, which is expected to be a value tuple of keyword and other data, separated by a ASCII SP space or TAB tabulator character. If the value part is empty, then the given attribute is removed, or reset to a default value if existence of the attribute is crucial. .Pp It returns via .Ql 210 upon success, with the index of the found attachment following, .Ql 505 for message attachments or if the given keyword is invalid, and .Ql 501 if no such attachment can be found. The following keywords may be used (case-insensitively): .Pp .Bl -hang -compact -width ".It Ql filename" .It Ql filename Sets the filename of the MIME part, i.e., the name that is used for display and when (suggesting a name for) saving (purposes). .It Ql content-description Associate some descriptive information to the attachment's content, used in favour of the plain filename by some MUAs. .It Ql content-id May be used for uniquely identifying MIME entities in several contexts; this expects a special reference address format as defined in RFC 2045 and generates a .Ql 505 upon address content verification failure. .It Ql content-type Defines the media type/subtype of the part, which is managed automatically, but can be overwritten. .It Ql content-disposition Automatically set to the string .Ql attachment . .El . .It Cm attribute-set-at This uses the same search mechanism as described for .Cm remove-at and is otherwise identical to .Cm attribute-set . . .It Cm insert Adds the attachment given as the third argument, specified exactly as documented for the command line option .Fl a , and supporting the message number extension as documented for .Ic ~@ . This reports .Ql 210 upon success, with the index of the new attachment following, .Ql 505 if the given file cannot be opened, .Ql 506 if an on-the-fly performed character set conversion fails, otherwise .Ql 501 is reported; this is also reported if character set conversion is requested but not available. . .It Cm list List all attachments via .Ql 212 , or report .Ql 501 if no attachments exist. This command is the default command of .Cm attachment if no second argument has been given. . .It Cm remove This will remove the attachment given as the third argument, and report .Ql 210 upon success or .Ql 501 if no such attachment can be found. If there exists any path component in the given argument, then an exact match of the path which has been used to create the attachment is used directly, but if only the basename of that path matches then all attachments are traversed to find an exact match first, and the removal occurs afterwards; if multiple basenames match, a .Ql 506 error occurs. Message attachments are treated as absolute pathnames. .Pp If no path component exists in the given argument, then all attachments will be searched for .Ql filename= parameter matches as well as for matches of the basename of the path which has been used when the attachment has been created; multiple matches result in a .Ql 506 . . .It Cm remove-at This will interpret the third argument as a number and remove the attachment at that list position (counting from one!), reporting .Ql 210 upon success or .Ql 505 if the argument is not a number or .Ql 501 if no such attachment exists. .El . . .It Cm header This command allows listing, inspection, and editing of message headers. Header name case is not normalized, and case-insensitive comparison should be used when matching names. The second argument specifies the subcommand to apply, one of: . . .Bl -hang -width ".It Cm remove" .It Cm insert Create a new or an additional instance of the header given in the third argument, with the header body content as given in the fourth argument (the remains of the line). It may return .Ql 501 if the third argument specifies a free-form header field name that is invalid, or if body content extraction fails to succeed, .Ql 505 if any extracted address does not pass syntax and/or security checks or on \*(UA namespace violations, and .Ql 506 to indicate prevention of excessing a single-instance header \(em note that .Ql Subject: can be appended to (a space separator will be added automatically first). .Ql To: , .Ql Cc: or .Ql Bcc: support the .Ql ?single modifier to enforce treatment as a single addressee, e.g., .Ql header insert To?single: exa, ; the word .Ql single is optional. .Pp .Ql 210 is returned upon success, followed by the name of the header and the list position of the newly inserted instance. The list position is always 1 for single-instance header fields. All free-form header fields are managed in a single list. . .It Cm list Without a third argument a list of all yet existing headers is given via .Ql 210 ; this command is the default command of .Cm header if no second argument has been given. A third argument restricts output to the given header only, which may fail with .Ql 501 if no such field is defined. . .It Cm remove This will remove all instances of the header given as the third argument, reporting .Ql 210 upon success, .Ql 501 if no such header can be found, and .Ql 505 on \*(UA namespace violations. . .It Cm remove-at This will remove from the header given as the third argument the instance at the list position (counting from one!) given with the fourth argument, reporting .Ql 210 upon success or .Ql 505 if the list position argument is not a number or on \*(UA namespace violations, and .Ql 501 if no such header instance exists. . .It Cm show Shows the content of the header given as the third argument. Dependent on the header type this may respond with .Ql 211 or .Ql 212 ; any failure results in .Ql 501 . .El . . .Pp In compose-mode read-only access to optional pseudo headers in the \*(UA private namespace is available: . . .Pp .Bl -tag -compact -width ".It Va BaNg" .It Ql Mailx-Command: The name of the command that generates the message, one of .Ql forward , .Ql Lreply , .Ql mail , .Ql Reply , .Ql reply , .Ql resend . This pseudo header always exists (in compose-mode). . .It Ql Mailx-Raw-To: .It Ql Mailx-Raw-Cc: .It Ql Mailx-Raw-Bcc: Represent the frozen initial state of these headers before any transformation (e.g., .Ic alias , .Ic alternates , .Va recipients-in-cc etc.) took place. . .It Ql Mailx-Orig-From: .It Ql Mailx-Orig-To: .It Ql Mailx-Orig-Cc: .It Ql Mailx-Orig-Bcc: The values of said headers of the original message which has been addressed by any of .Ic reply , forward , resend . .El . . .It Cm help , \&? Show an abstract of the above commands via .Ql 211 . . .It Cm version This command will print the protocol version via .Ql 210 . .El . . .Mx .It Ic ~A The same as .Ql Ic ~i Ns \| Va Sign . . .Mx .It Ic ~a The same as .Ql Ic ~i Ns \| Va sign . . .Mx .It Ic ~b Ar name ... Add the given names to the list of blind carbon copy recipients. . .Mx .It Ic ~c Ar name ... Add the given names to the list of carbon copy recipients. . .Mx .It Ic ~d Read the file specified by the .Ev DEAD variable into the message. . .Mx .It Ic ~e Invoke the text .Ev EDITOR on the message collected so far, then return to compose mode. .Ic ~v can be used for a more display oriented editor, and .Ic ~| Ns | offers a pipe-based editing approach. . .Mx .It Ic ~F Ar messages Read the named messages into the message being sent, including all message headers and MIME parts. If no messages are specified, read in the current message, the .Dq dot . . .Mx .It Ic ~f Ar messages Read the named messages into the message being sent. If no messages are specified, read in the current message, the .Dq dot . Strips down the list of header fields according to the .Ql type white- and blacklist selection of .Ic headerpick . For MIME multipart messages, only the first displayable part is included. . .Mx .It Ic ~H Edit the message header fields .Ql From: , .Ql Reply-To: and .Ql Sender: by typing each one in turn and allowing the user to edit the field. The default values for these fields originate from the .Va from , reply-to and .Va sender variables. . .Mx .It Ic ~h Edit the message header fields .Ql To: , .Ql Cc: , .Ql Bcc: and .Ql Subject: by typing each one in turn and allowing the user to edit the field. . .Mx .It Ic ~I Ar variable Insert the value of the specified variable into the message. The message remains unaltered if the variable is unset or empty. Any embedded character sequences .Ql \et horizontal tabulator and .Ql \en line feed are expanded in .Va posix mode; otherwise the expansion should occur at .Ic set time (\*(ID by using the command modifier .Va wysh ) . . .Mx .It Ic ~i Ar variable Like .Ic ~I , but appends a newline character. . .Mx .It Ic ~M Ar messages Read the named messages into the message being sent, indented by .Va indentprefix . If no messages are specified, read the current message, the .Dq dot . . .Mx .It Ic ~m Ar messages Read the named messages into the message being sent, indented by .Va indentprefix . If no messages are specified, read the current message, the .Dq dot . Strips down the list of header fields according to the .Ql type white- and blacklist selection of .Ic headerpick . For MIME multipart messages, only the first displayable part is included. . .Mx .It Ic ~p Display the message collected so far, prefaced by the message header fields and followed by the attachment list, if any. . .Mx .It Ic ~Q Read in the given / current message(s) according to the algorithm of .Va quote . . .Mx .It Ic ~q Abort the message being sent, copying it to the file specified by the .Ev DEAD variable if .Va save is set. . .Mx .It Ic ~R Ar filename Identical to .Ic ~r , but indent each line that has been read by .Va indentprefix . . .Mx .It Ic ~r Ar filename Op Ar HERE-delimiter Read the named file, object to the usual .Sx "Filename transformations" , into the message; if (the expanded) .Ar filename is the hyphen-minus .Ql - then standard input is used, e.g., for pasting purposes. Only in this latter mode .Ar HERE-delimiter may be given: if it is data will be read in until the given .Ar HERE-delimiter is seen on a line by itself, and encountering EOF is an error; the .Ar HERE-delimiter is a required argument in non-interactive mode; if it is single-quote quoted then the pasted content will not be expanded, \*(ID otherwise a future version of \*(UA may perform shell-style expansion on the content. . .Mx .It Ic ~s Ar string Cause the named string to become the current subject field. Newline (NL) and carriage-return (CR) bytes are invalid and will be normalized to space (SP) characters. . .Mx .It Ic ~t Ar name ... Add the given name(s) to the direct recipient list. . .Mx .It Ic ~U Ar messages Read in the given / current message(s) excluding all headers, indented by .Va indentprefix . . .Mx .It Ic ~u Ar messages Read in the given / current message(s), excluding all headers. . .Mx .It Ic ~v Invoke the .Ev VISUAL editor on the message collected so far, then return to compose mode. .Ic ~e can be used for a less display oriented editor, and .Ic ~| Ns | offers a pipe-based editing approach. . .Mx .It Ic ~w Ar filename Write the message onto the named file, which is object to the usual .Sx "Filename transformations" . If the file exists, the message is appended to it. . .Mx .It Ic ~x Same as .Ic ~q , except that the message is not saved at all. .El . .\" }}} . . .\" .Sh INTERNAL VARIABLES {{{ .Sh "INTERNAL VARIABLES" . Internal \*(UA variables are controlled via the .Ic set and .Ic unset commands; prefixing a variable name with the string .Ql no and calling .Ic set has the same effect as using .Ic unset : .Ql unset crt and .Ql set nocrt do the same thing. .Ic varshow will give more insight on the given variable(s), and .Ic set , when called without arguments, will show a listing of all variables. Both commands support a more .Va verbose listing mode. Some well-known variables will also become inherited from the program .Sx ENVIRONMENT implicitly, others can be imported explicitly with the command .Ic environ and henceforth share said properties. . .Pp Two different kinds of internal variables exist, and both of which can also form chains. There are boolean variables, which can only be in one of the two states .Dq set and .Dq unset , and value variables with a(n optional) string value. For the latter proper quoting is necessary upon assignment time, the introduction of the section .Sx COMMANDS documents the supported quoting rules. . .Bd -literal -offset indent ? wysh set one=val\e 1 two="val 2" \e three='val "3"' four=$'val \e'4\e''; \e varshow one two three four; \e unset one two three four .Ed . .Pp Dependent upon the actual option string values may become interpreted as colour names, command specifications, normal text, etc. They may be treated as numbers, in which case decimal values are expected if so documented, but otherwise any numeric format and base that is valid and understood by the .Ic vexpr command may be used, too. . .Pp There also exists a special kind of string value, the .Dq boolean string , which must either be a decimal integer (in which case .Ql 0 is false and .Ql 1 and any other value is true) or any of the (case-insensitive) strings .Ql off , .Ql no , .Ql n and .Ql false for a false boolean and .Ql on , .Ql yes , .Ql y and .Ql true for a true boolean; a special kind of boolean string is the .Dq quadoption , which is a boolean string that can optionally be prefixed with the (case-insensitive) term .Ql ask- , as in .Ql ask-yes , which causes prompting of the user in interactive mode, with the given boolean as the default value. . .Pp Variable chains extend a plain .Ql variable with .Ql variable-HOST and .Ql variable-USER@HOST variants. Here .Ql HOST will be converted to all lowercase when looked up (but not when the variable is set or unset!), \*(OPally IDNA converted, and indeed means .Ql server:port if a .Ql port had been specified in the contextual Uniform Resource Locator URL, see .Sx "On URL syntax and credential lookup" . Even though this mechanism is based on URLs no URL percent encoding may be applied to neither of .Ql USER nor .Ql HOST , variable chains need to be specified using raw data; the mentioned section contains examples. Variables which support chains are explicitly documented as such, and \*(UA treats the base name of any such variable special, meaning that users should not create custom names like .Ql variable-xyz in order to avoid false classifications and treatment of such variables. . .\" .Ss "Initial settings" {{{ .\" (Keep in SYNC: mx/nail.h:okeys, ./nail.rc, ./nail.1:"Initial settings") .Ss "Initial settings" . The standard POSIX 2008/Cor 2-2016 mandates the following initial variable settings: .Pf no Va allnet , .Pf no Va append , .Va asksub , .Pf no Va askbcc , .Pf no Va autoprint , .Pf no Va bang , .Pf no Va cmd , .Pf no Va crt , .Pf no Va debug , .Pf no Va dot , .Va escape set to .Ql ~ , .Pf no Va flipr , .Pf no Va folder , .Va header , .Pf no Va hold , .Pf no Va ignore , .Pf no Va ignoreeof , .Pf no Va keep , .Pf no Va keepsave , .Pf no Va metoo , .Pf no Va outfolder , .Pf no Va page , .Va prompt set to .Ql \&?\0 , .Pf no Va quiet , .Pf no Va record , .Va save , .Pf no Va sendwait , .Pf no Va showto , .Pf no Va Sign , .Pf no Va sign , .Va toplines set to .Ql 5 . . .Pp However, \*(UA has built-in some initial (and some default) settings which (may) diverge, others may become adjusted by one of the .Sx "Resource files" . Displaying the former is accomplished via .Ic set : .Ql $ \*(uA -:/ -v -Xset -Xx . In general this implementation sets (and has extended the meaning of) .Va sendwait , and does not support the .Pf no Va onehop variable \(en use command line options or .Va mta-arguments to pass options through to a .Va mta . The default global resource file sets, among others, the variables .Va hold , .Va keep and .Va keepsave , establishes a default .Ic headerpick selection etc., and should thus be taken into account. .\" }}} . .\" .Ss "Variables" {{{ .Ss "Variables" . .Bl -tag -width ".It Va BaNg" . .Mx .It Va \&? \*(RO The exit status of the last command, or the .Ic return value of the macro .Ic call Ns ed last. This status has a meaning in the state machine: in conjunction with .Va errexit any non-0 exit status will cause a program exit, and in .Va posix mode any error while loading (any of the) resource files will have the same effect. .Cm ignerr , one of the .Sx "Command modifiers" , can be used to instruct the state machine to ignore errors. . .Mx .It Va \&! \*(RO The current error number .Pf ( Xr errno 3 ) , which is set after an error occurred; it is also available via .Va ^ERR , and the error name and documentation string can be queried via .Va ^ERRNAME and .Va ^ERRDOC . \*(ID This machinery is new and the error number is only really usable if a command explicitly states that it manages the variable .Va \&! , for others errno will be used in case of errors, or .Va ^ERR Ns -INVAL if that is 0: it thus may or may not reflect the real error. The error number may be set with the command .Ic return . . . .Mx .It Va ^ \*(RO This is a multiplexer variable which performs dynamic expansion of the requested state or condition, of which there are: . .Pp .Bl -tag -compact -width ".It Va BaNg" .Mx .Mx .Mx .It Va ^ERR , ^ERRDOC , ^ERRNAME The number, documentation, and name of the current .Xr errno 3 , respectively, which is usually set after an error occurred. The documentation is an \*(OP, the name is used if not available. \*(ID This machinery is new and is usually reliable only if a command explicitly states that it manages the variable .Va \&! , which is effectively identical to .Va \&\&^ERR . Each of those variables can be suffixed with a hyphen minus followed by a name or number, in which case the expansion refers to the given error. Note this is a direct mapping of (a subset of) the system error values: .Bd -literal -offset indent define work { eval echo \e$1: \e$^ERR-$1:\e \e$^ERRNAME-$1: \e$^ERRDOC-$1 vput vexpr i + "$1" 1 if [ $i -lt 16 ] \excall work $i end } call work 0 .Ed . .Mx .Mx .It Va ^ERRQUEUE-COUNT , ^ERRQUEUE-EXISTS The number of messages present in the \*(OPal log queue of .Ic errors , and a boolean which indicates whether the queue is not empty, respectively; both are always 0 unless .Va features indicates .Ql +errors . .El . . .Mx .It Va * \*(RO Expands all positional parameters (see .Va 1 ) , separated by the first character of the value of .Va ifs . \*(ID The special semantics of the equally named special parameter of the .Xr sh 1 are not yet supported. . .Mx .It Va @ \*(RO Expands all positional parameters (see .Va 1 ) , separated by a space character. If placed in double quotation marks, each positional parameter is properly quoted to expand to a single parameter again. . .Mx .It Va # \*(RO Expands to the number of positional parameters, i.e., the size of the positional parameter stack in decimal. . .Mx .It Va \&0 \*(RO Inside the scope of a .Ic define Ns d and .Ic call Ns ed macro this expands to the name of the calling macro, or to the empty string if the macro is running from top-level. For the \*(OPal regular expression search and replace operator of .Ic vexpr this expands to the entire matching expression. It represents the program name in global context. . .Mx .It Va 1 \*(RO Access of the positional parameter stack. All further parameters can be accessed with this syntax, too, e.g., .Ql 2 , .Ql 3 etc.; positional parameters can be shifted off the stack by calling .Ic shift . The parameter stack contains, e.g., the arguments of a .Ic call Ns ed .Ic define Ns d macro, the matching groups of the \*(OPal regular expression search and replace expression of .Ic vexpr , and can be explicitly created or overwritten with the command .Ic vpospar . . .Mx .It Va account \*(RO Is set to the active .Ic account . . .Mx .It Va add-file-recipients \*(BO When file or pipe recipients have been specified, mention them in the corresponding address fields of the message instead of silently stripping them from their recipient list. By default such addressees are not mentioned. . .Mx .It Va allnet \*(BO Causes only the local part to be evaluated when comparing addresses. . .Mx .It Va append \*(BO Causes messages saved in the .Mx -sx .Sx "secondary mailbox" .Ev MBOX to be appended to the end rather than prepended. This should always be set. . .Mx .It Va askatend \*(BO Causes the prompts for .Ql Cc: and .Ql Bcc: lists to appear after the message has been edited. . .Mx .It Va askattach \*(BO If set, \*(UA asks an interactive user for files to attach at the end of each message; An empty line finalizes the list. . .Mx .It Va askcc \*(BO Causes the interactive user to be prompted for carbon copy recipients (at the end of each message if .Va askatend or .Va bsdcompat are set). . .Mx .It Va askbcc \*(BO Causes the interactive user to be prompted for blind carbon copy recipients (at the end of each message if .Va askatend or .Va bsdcompat are set). . .Mx .It Va asksend \*(BO Causes the interactive user to be prompted for confirmation to send the message or reenter compose mode after having been shown an envelope summary. This is by default enabled. . .Mx .It Va asksign \*(BO\*(OP Causes the interactive user to be prompted if the message is to be signed at the end of each message. The .Va smime-sign variable is ignored when this variable is set. . .Mx .It Va asksub .\" The alternative *ask* is not documented on purpose \*(BO Causes \*(UA to prompt the interactive user for the subject upon entering compose mode unless a subject already exists. . .Mx .It Va attrlist A sequence of characters to display in the .Ql attribute column of the .Va headline as shown in the display of .Ic headers ; each for one type of messages (see .Sx "Message states" ) , with the default being .Ql NUROSPMFAT+\-$~ or .Ql NU\ \ *HMFAT+\-$~ if the .Va bsdflags variable is set, in the following order: .Pp .Bl -tag -compact -width ".It Ql _" .It Ql N new. .It Ql U unread but old. .It Ql R new but read. .It Ql O read and old. .It Ql S saved. .It Ql P preserved. .It Ql M mboxed. .It Ql F flagged. .It Ql A answered. .It Ql T draft. .It Ql + \*(ID start of a (collapsed) thread in threaded mode (see .Va autosort , .Ic thread ) ; .It Ql - \*(ID an uncollapsed thread in threaded mode; only used in conjunction with .Fl L . .It Ql $ classified as spam. .It Ql ~ classified as possible spam. .El . . .Mx .It Va autobcc Specifies a list of recipients to which a blind carbon copy of each outgoing message will be sent automatically. . .Mx .It Va autocc Specifies a list of recipients to which a carbon copy of each outgoing message will be sent automatically. . .Mx .It Va autocollapse \*(BO Causes threads to be collapsed automatically when .Ql thread Ns ed .Ic sort mode is entered (see the .Ic collapse command). . .Mx .It Va autoprint \*(BO Enable automatic .Ic type Ns ing of a(n existing) .Dq successive message after .Ic delete and .Ic undelete commands, e.g., the message that becomes the new .Dq dot is shown automatically, as via .Ic dp or .Ic dt . . .Mx .It Va autosort Causes sorted mode (see the .Ic sort command) to be entered automatically with the value of this variable as sorting method when a folder is opened, e.g., .Ql set autosort=thread . . .Mx .It Va bang \*(BO Enables the substitution of all not (reverse-solidus) escaped exclamation mark .Ql \&! characters by the contents of the last executed command for the .Ic \&! shell escape command and .Ic ~! , one of the compose mode .Sx "COMMAND ESCAPES" . If this variable is not set no reverse solidus stripping is performed. . .Mx .It Va bind-timeout \*(OP Terminals generate multi-byte sequences for certain forms of input, for example for function and other special keys. Some terminals however do not write these multi-byte sequences as a whole, but byte-by-byte, and the latter is what \*(UA actually reads. This variable specifies the timeout in milliseconds that the MLE (see .Sx "On terminal control and line editor" ) waits for more bytes to arrive unless it considers a sequence .Dq complete . The default is 200. . .Mx .It Va bsdcompat \*(BO Sets some cosmetical features to traditional BSD style; has the same affect as setting .Va askatend and all other variables prefixed with .Ql bsd ; it also changes the behaviour of .Va emptystart (which does not exist in BSD). . .Mx .It Va bsdflags \*(BO Changes the letters shown in the first column of a header summary to traditional BSD style. . .Mx .It Va bsdheadline \*(BO Changes the display of columns in a header summary to traditional BSD style. . .Mx .It Va bsdmsgs \*(BO Changes some informational messages to traditional BSD style. . .Mx .It Va bsdorder \*(BO Causes the .Ql Subject: field to appear immediately after the .Ql To: field in message headers and with the .Ic ~h .Sx "COMMAND ESCAPES" . . .Mx .Mx .Mx .Mx .It Va build-cc , build-ld , build-os , build-rest \*(RO The build environment, including the compiler, the linker, the operating system \*(UA has been build for, usually taken from .Xr uname 1 via .Ql uname -s , and then lowercased, as well as all the possibly interesting rest of the configuration and build environment. This information is also available in the .Va verbose output of the command .Ic version . . .Mx .It Va charset-7bit The value that should appear in the .Ql charset= parameter of .Ql Content-Type: MIME header fields when no character set conversion of the message data was performed. This defaults to US-ASCII, and the chosen character set should be US-ASCII compatible. . .Mx .It Va charset-8bit \*(OP The default 8-bit character set that is used as an implicit last member of the variable .Va sendcharsets . This defaults to UTF-8 if character set conversion capabilities are available, and to ISO-8859-1 otherwise (unless the operating system environment is known to always and exclusively support UTF-8 locales), in which case the only supported character set is .Va ttycharset and this variable is effectively ignored. . .Mx .It Va charset-unknown-8bit \*(OP RFC 1428 specifies conditions when internet mail gateways shall .Dq upgrade the content of a mail message by using a character set with the name .Ql unknown-8bit . Because of the unclassified nature of this character set \*(UA will not be capable to convert this character set to any other character set. If this variable is set any message part which uses the character set .Ql unknown-8bit is assumed to really be in the character set given in the value, otherwise the (final) value of .Va charset-8bit is used for this purpose. .Pp This variable will also be taken into account if a MIME type (see .Sx "The mime.types files" ) of a MIME message part that uses the .Ql binary character set is forcefully treated as text. . .Mx .It Va cmd The default value for the .Ic pipe command. . .Mx .It Va colour-disable \*(BO\*(OP Forcefully disable usage of colours. Also see the section .Sx "Coloured display" . . .Mx .It Va colour-pager \*(BO\*(OP Whether colour shall be used for output that is paged through .Ev PAGER . Note that pagers may need special command line options, e.g., .Xr less 1 requires the option .Fl \&\&R and .Xr lv 1 the option .Fl \&\&c in order to support colours. Often doing manual adjustments is unnecessary since \*(UA may perform adjustments dependent on the value of the environment variable .Ev PAGER (see there for more). . .Mx .Mx .It Va contact-mail , contact-web \*(RO Addresses for contact per email and web, respectively, e.g., for bug reports, suggestions, or help regarding \*(UA. The former can be used directly: .Ql \&? Ns \| Ic eval Ns \| Ic mail Ns \| $contact-mail . . .Mx .It Va crt In a(n interactive) terminal session, then if this valued variable is set it will be used as a threshold to determine how many lines the given output has to span before it will be displayed via the configured .Ev PAGER ; Usage of the .Ev PAGER can be forced by setting this to the value .Ql 0 , setting it without a value will deduce the current height of the terminal screen to compute the threshold (see .Ev LINES , .Va screen and .Xr stty 1 ) . \*(ID At the moment this uses the count of lines of the message in wire format, which, dependent on the .Va mime-encoding of the message, is unrelated to the number of display lines. (The software is old and historically the relation was a given thing.) . .Mx .It Va customhdr Define a set of custom headers to be injected into newly composed or forwarded messages. A custom header consists of the field name followed by a colon .Ql \&: and the field content body. Standard header field names cannot be overwritten by a custom header. Different to the command line option .Fl C the variable value is interpreted as a comma-separated list of custom headers: to include commas in header bodies they need to become escaped with reverse solidus .Ql \e . Headers can be managed more freely in compose mode via .Ic ~^ . .Pp .Dl ? set customhdr='Hdr1: Body1-1\e, Body1-2, Hdr2: Body2' . .Mx .It Va datefield Controls the appearance of the .Ql %d date and time format specification of the .Va headline variable, that is used, for example, when viewing the summary of .Ic headers . If unset, then the local receiving date is used and displayed unformatted, otherwise the message sending .Ql Date: . It is possible to assign a .Xr strftime 3 format string and control formatting, but embedding newlines via the .Ql %n format is not supported, and will result in display errors. The default is .Ql %Y-%m-%d %H:%M , and also see .Va datefield-markout-older . . .Mx .It Va datefield-markout-older Only used in conjunction with .Va datefield . Can be used to create a visible distinction of messages dated more than a day in the future, or older than six months, a concept comparable to the .Fl \&\&l option of the POSIX utility .Xr ls 1 . If set to the empty string, then the plain month, day and year of the .Ql Date: will be displayed, but a .Xr strftime 3 format string to control formatting can be assigned. The default is .Ql %Y-%m-%d . . .Mx .It Va debug \*(BO Enables debug messages and obsoletion warnings, disables the actual delivery of messages and also implies .Pf no Va record as well as .Pf no Va save . . .Mx .It Va disposition-notification-send \*(BO\*(OP Emit a .Ql Disposition-Notification-To: header (RFC 3798) with the message. This requires the .Va from variable to be set. .\" TODO .It Va disposition-notification-send-HOST .\"Overrides .\".Va disposition-notification-send .\" for SMTP accounts on a specific host. .\" TODO .It Va disposition-notification-send-USER@HOST .\"Overrides .\".Va disposition-notification-send .\"for a specific account. . .Mx .It Va dot \*(BO When dot is set, a period .Ql \&. on a line by itself during message input in (interactive or batch .Fl # ) compose mode will be treated as end-of-message (in addition to the normal end-of-file condition). This behaviour is implied in .Va posix mode with a set .Va ignoreeof . . .Mx .It Va dotlock-disable \*(BO\*(OP Disable creation of .Mx -sx .Sx "dotlock files" for MBOX databases. . .It Va dotlock-ignore-error \*(OB\*(BO\*(OP Ignore failures when creating .Mx -sx .Sx "dotlock files" . Please use .Va dotlock-disable instead. . .Mx .It Va editalong If this variable is set then the editor is started automatically when a message is composed in interactive mode. If the value starts with the letter .Ql v then this acts as if .Ic ~v , otherwise as if .Ic ~e .Pf (see\0 Sx "COMMAND ESCAPES" ) had been specified. The .Va editheaders variable is implied for this automatically spawned editor session. . .Mx .It Va editheaders \*(BO When a message is edited while being composed, its header is included in the editable text. . .Mx .It Va emptystart \*(BO When entering interactive mode \*(UA normally writes .Dq \&No mail for user and exits immediately if a mailbox is empty or does not exist. If this variable is set \*(UA starts even with an empty or non-existent mailbox (the latter behaviour furtherly depends upon .Va bsdcompat , though). . .Mx .It Va errexit \*(BO Let each command with a non-0 exit status, including every .Ic call Ns ed macro which .Ic return Ns s a non-0 status, cause a program exit unless prefixed by .Cm ignerr (see .Sx "Command modifiers" ) . This also affects .Sx "COMMAND ESCAPES" , but which use a different modifier for ignoring the error. Please refer to the variable .Va \&? for more on this topic. . .Mx .It Va escape The first character of this value defines the escape character for .Sx "COMMAND ESCAPES" in compose mode. The default value is the character tilde .Ql ~ . If set to the empty string, command escapes are disabled. . . .Mx .It Va expandaddr If unset then file and command pipeline address targets are not allowed, and any such address will be filtered out, giving a warning message. If set then all possible recipient address specifications will be accepted, unless the optional value is more specific (also see .Sx "On sending mail, and non-interactive mode" ) . If the value contains .Ql restrict then behaviour equals the former unless in interactive mode, or when tilde commands were enabled explicitly via .Fl ~ or .Fl # , in which case it equals the latter, and thus allows all addressees. .Ql restrict really acts like .Ql restrict,\:-all,\:+name,\:+addr , so care for ordering issues must be taken. . .Pp Indeed the value is interpreted as a comma-separated list of case-insensitive strings. Hard send errors can be enforced for disallowed address types by setting .Ql fail ; by default these are only filtered out. User name receivers addressing valid local users can be expanded to a network address (also see .Va hostname ) by setting .Ql namehostex . Address targets can be added and removed with a plus sign .Ql + or hyphen-minus .Ql - prefix, respectively: the value .Ql all addresses all possible specifications, .Ql fcc whitelists targets specified via .Ql Fcc: headers regardless of other settings, .Ql file file targets (it includes .Ql fcc ) , .Ql pipe command pipeline targets, .Ql name plain user names left for further expansion by the MTA (implicitly disallowed for the SMTP based .Va mta ) , and .Ql addr network addresses. Targets are interpreted in the given order, so that .Ql restrict,\:fail,\:+file,\:-all,\:+addr will cause hard errors for any non-network address recipient address unless running interactively or having been started with the option .Fl ~ or .Fl # ; in the latter case(s) any address may be used, then. . .Pp Historically invalid network addressees were silently stripped off \(em shall they cause hard errors instead it must be ensured that .Ql failinvaddr is an entry of the list (it really acts like .Ql failinvaddr,\:+addr ) . Likewise, .Ql domaincheck .Pf (actually\0\: Ql domaincheck,\:+addr ) compares address domain names against a whitelist and strips off .Pf ( Ql fail for hard errors) addressees which fail this test; the domain name .Ql localhost and the non-empty value of .Va hostname (the real hostname otherwise) are always whitelisted, .Va expandaddr-domaincheck can be set to extend this list. Finally some address providers (for example .Fl b , c and all other command line recipients) will be evaluated as if specified within dollar-single-quotes (see .Sx "Shell-style argument quoting" ) if the value list contains the string .Ql shquote . . . .Mx .It Va expandaddr-domaincheck Can be set to a comma-separated list of domain names which should be whitelisted for the evaluation of the .Ql domaincheck mode of .Va expandaddr . IDNA encoding is not automatically performed, .Ic addrcodec can be used to prepare the domain (of an address). . .Mx .It Va expandargv Unless this variable is set additional .Va mta (Mail-Transfer-Agent) arguments from the command line, as can be given after a .Fl \&\&- separator, results in a program termination with failure status. The same can be accomplished by using the special (case-insensitive) value .Ql fail . A lesser strict variant is the otherwise identical .Ql restrict , which does accept such arguments in interactive mode, or if tilde commands were enabled explicitly by using one of the command line options .Fl ~ or .Fl # . The empty value will allow unconditional usage. . .Mx .It Va features \*(RO String giving a list of optional features. Features are preceded with a plus sign .Ql + if they are available, with a hyphen-minus .Ql - otherwise. The output of the command .Ic version includes this information in a more pleasant output. . .Mx .It Va flipr \*(BO This setting reverses the meanings of a set of reply commands, turning the lowercase variants, which by default address all recipients included in the header of a message .Pf ( Ic reply , respond , followup ) into the uppercase variants, which by default address the sender only .Pf ( Ic Reply , Respond , Followup ) and vice versa. The commands .Ic replysender , respondsender , followupsender as well as .Ic replyall , respondall , followupall are not affected by the current setting of .Va flipr . . .Mx .It Va folder The default path under which mailboxes are to be saved: filenames that begin with the plus sign .Ql + will have the plus sign replaced with the value of this variable if set, otherwise the plus sign will remain unchanged when doing .Sx "Filename transformations" ; also see .Ic file for more on this topic, and know about standard imposed implications of .Va outfolder . The value supports a subset of transformations itself, and if the non-empty value does not start with a solidus .Ql / , then the value of .Ev HOME will be prefixed automatically. Once the actual value is evaluated first, the internal variable .Va folder-resolved will be updated for caching purposes. . .Mx Va folder-hook .It Va folder-hook-FOLDER , Va folder-hook Names a .Ic define Ns d macro which will be called whenever a .Ic file is opened. The macro will also be invoked when new mail arrives, but message lists for commands executed from the macro only include newly arrived messages then. .Ic localopts are activated by default in a folder hook, causing the covered settings to be reverted once the folder is left again. .Pp The specialized form will override the generic one if .Ql FOLDER matches the file that is opened. Unlike other folder specifications, the fully expanded name of a folder, without metacharacters, is used to avoid ambiguities. However, if the mailbox resides under .Va folder then the usual .Ql + specification is tried in addition, e.g., if .Va \&\&folder is .Dq mail (and thus relative to the user's home directory) then .Pa /home/usr1/mail/sent will be tried as .Ql folder-hook-/home/usr1/mail/sent first, but then followed by .Ql folder-hook-+sent . . .Mx .It Va folder-resolved \*(RO Set to the fully resolved path of .Va folder once that evaluation has occurred; rather internal. . .Mx .It Va followup-to \*(BO Controls whether a .Ql Mail-Followup-To: header is generated when sending messages to known mailing lists. The user as determined via .Va from (or, if that contains multiple addresses, .Va sender ) will be placed in there if any list addressee is not a subscribed list. Also see .Va followup-to-honour and the commands .Ic mlist , mlsubscribe , reply and .Ic Lreply . . .Mx .It Va followup-to-add-cc \*(BO Controls whether the user will be added to the messages' .Ql Cc: list in addition to placing an entry in .Ql Mail-Followup-To: (see .Va followup-to ) . . .Mx .It Va followup-to-honour Controls whether a .Ql Mail-Followup-To: header is honoured when group-replying to a message via .Ic reply or .Ic Lreply . This is a quadoption; if set without a value it defaults to .Dq yes , and see .Va followup-to . . .Mx .It Va forward-as-attachment \*(BO Original messages are normally sent as inline text with the .Ic forward command, and only the first part of a multipart message is included. With this setting enabled messages are sent as unmodified MIME .Ql message/rfc822 attachments with all of their parts included. . .Mx .Mx .It Va forward-inject-head , forward-inject-tail The strings to put before and after the text of a message with the .Ic forward command, respectively. The former defaults to .Ql -------- Original Message --------\en . Special format directives in these strings will be expanded if possible, and if so configured the output will be folded according to .Va quote-fold ; for more please refer to .Va quote-inject-head . These variables are ignored if the .Va forward-as-attachment variable is set. . . .Mx .It Va from The address (or a list of addresses) to put into the .Ql From: field of the message header, quoting RFC 5322: the author(s) of the message, that is, the mailbox(es) of the person(s) or system(s) responsible for the writing of the message. According to that RFC setting the .Va sender variable is required if .Va \&\&from contains more than one address. Dependent on the context these addresses are handled as if they were in the list of .Ic alternates . . .Pp If a file-based MTA is used, then .Va \&\&from (or, if that contains multiple addresses, .Va sender ) can nonetheless be enforced to appear as the envelope sender address at the MTA protocol level (the RFC 5321 reverse-path), either by using the .Fl r command line option (with an empty argument; see there for the complete picture on this topic), or by setting the internal variable .Va r-option-implicit . . .Pp If the machine's hostname is not valid at the Internet (for example at a dialup machine) then either this variable or .Va hostname (\*(IN a SMTP-based .Va mta adds even more fine-tuning capabilities with .Va smtp-hostname ) have to be set: if so the message and MIME part related unique ID fields .Ql Message-ID: and .Ql Content-ID: will be created (except when disallowed by .Va message-id-disable or .Va stealthmua ) . . . .Mx .It Va fullnames \*(BO Due to historical reasons comments and name parts of email addresses are removed by default when sending mail, replying to or forwarding a message. If this variable is set such stripping is not performed. . .It Va fwdheading \*(OB Predecessor of .Va forward-inject-head . . .Mx .It Va header \*(BO Causes the header summary to be written at startup and after commands that affect the number of messages or the order of messages in the current .Ic folder . Unless in .Va posix mode a header summary will also be displayed on folder changes. The command line option .Fl N can be used to set .Pf no Va header . . . .Mx .It Va headline A format string to use for the summary of .Ic headers . Format specifiers in the given string start with a percent sign .Ql % and may be followed by an optional decimal number indicating the field width \(em if that is negative, the field is to be left-aligned. Names and addresses are subject to modifications according to .Va showname and .Va showto . Valid format specifiers are: . .Pp .Bl -tag -compact -width ".It Ql _%%_" .It Ql %% A plain percent sign. .It Ql %> .Dq Dotmark : a space character but for the current message .Pf ( Dq dot ) , for which it expands to .Ql > (dependent on .Va headline-plain ) . .It Ql %< .Dq Dotmark : a space character but for the current message .Pf ( Dq dot ) , for which it expands to .Ql < (dependent on .Va headline-plain ) . .It Ql %$ \*(OP The spam score of the message, as has been classified via the command .Ic spamrate . Shows only a replacement character if there is no spam support. .It Ql %a Message attribute character (status flag); the actual content can be adjusted by setting .Va attrlist . .It Ql %d The date found in the .Ql Date: header of the message when .Va datefield is set (the default), otherwise the date when the message was received. Formatting can be controlled by assigning a .Xr strftime 3 format string to .Va datefield (and .Va datefield-markout-older ) . .It Ql %e The indenting level in .Ql thread Ns ed .Ic sort mode. .It Ql %f The address of the message sender. .It Ql %i The message thread tree structure. (Note that this format does not support a field width, and honours .Va headline-plain . ) .It Ql %L Mailing list status: is the addressee of the message a known .Pf ( Ic mlist ) or .Ic mlsubscribe Ns d mailing list? .It Ql %l The number of lines of the message, if available. .It Ql %m Message number. .It Ql %o The number of octets (bytes) in the message, if available. .It Ql %S Message subject (if any) in double quotes. .It Ql %s Message subject (if any). .It Ql %t The position in threaded/sorted order. .It Ql \&%U The value 0 except in an IMAP mailbox, where it expands to the UID of the message. .El .Pp The default is .Ql %>\&%a\&%m\ %-18f\ %16d\ %4l/%\-5o\ %i%-s , or .Ql %>\&%a\&%m\ %20-f\ \ %16d\ %3l/%\-5o\ %i%-S if .Va bsdcompat is set. Also see .Va attrlist , .Va headline-plain and .Va headline-bidi . . . .Mx .It Va headline-bidi Bidirectional text requires special treatment when displaying headers, because numbers (in dates or for file sizes etc.) will not affect the current text direction, in effect resulting in ugly line layouts when arabic or other right-to-left text is to be displayed. On the other hand only a minority of terminals is capable to correctly handle direction changes, so that user interaction is necessary for acceptable results. Note that extended host system support is required nonetheless, e.g., detection of the terminal character set is one precondition; and this feature only works in an Unicode (i.e., UTF-8) locale. .Pp In general setting this variable will cause \*(UA to encapsulate text fields that may occur when displaying .Va headline (and some other fields, like dynamic expansions in .Va prompt ) with special Unicode control sequences; it is possible to fine-tune the terminal support level by assigning a value: no value (or any value other than .Ql 1 , .Ql 2 and .Ql 3 ) will make \*(UA assume that the terminal is capable to properly deal with Unicode version 6.3, in which case text is embedded in a pair of U+2068 (FIRST STRONG ISOLATE) and U+2069 (POP DIRECTIONAL ISOLATE) characters. In addition no space on the line is reserved for these characters. .Pp Weaker support is chosen by using the value .Ql 1 (Unicode 6.3, but reserve the room of two spaces for writing the control sequences onto the line). The values .Ql 2 and .Ql 3 select Unicode 1.1 support (U+200E, LEFT-TO-RIGHT MARK); the latter again reserves room for two spaces in addition. . .Mx .It Va headline-plain \*(BO On Unicode (UTF-8) aware terminals enhanced graphical symbols are used by default for certain entries of .Va headline . If this variable is set only basic US-ASCII symbols will be used. . .Mx .It Va history-file \*(OP If a line editor is available then this can be set to name the (expandable) path of the location of a permanent .Ic history file; also see .Va history-size . . .Mx .It Va history-gabby \*(BO\*(OP Add more entries to the .Ic history as is normally done. . .Mx .It Va history-gabby-persist \*(BO\*(OP \*(UA's own MLE will not save the additional .Va history-gabby entries in persistent storage unless this variable is set. On the other hand it will not loose the knowledge of whether a persistent entry was gabby or not. Also see .Va history-file . . .Mx .It Va history-size \*(OP Setting this variable imposes a limit on the number of concurrent .Ic history entries. If set to the value 0 then no further history entries will be added, and loading and incorporation of the .Va history-file upon program startup can also be suppressed by doing this. Runtime changes will not be reflected before the .Ic history is saved or loaded (again). . .Mx .It Va hold \*(BO This setting controls whether messages are held in the system .Va inbox , and it is set by default. . .Mx .It Va hostname Used instead of the value obtained from .Xr uname 3 and .Xr getaddrinfo 3 as the hostname when expanding local addresses, e.g., in .Ql From: (also see .Sx "On sending mail, and non-interactive mode" , e.g., for expansion of addresses that have a valid user-, but no domain name in angle brackets). If either of .Va from or this variable is set the message and MIME part related unique ID fields .Ql Message-ID: and .Ql Content-ID: will be created (except when disallowed by .Va message-id-disable or .Va stealthmua ) . If the \*(OPal IDNA support is available (see .Va idna-disable ) variable assignment is aborted when a necessary conversion fails. .Pp Setting it to the empty string will cause the normal hostname to be used, but nonetheless enables creation of said ID fields. \*(IN in conjunction with the built-in SMTP .Va mta .Va smtp-hostname also influences the results: one should produce some test messages with the desired combination of .Va \&\&hostname , and/or .Va from , .Va sender etc. first. . .Mx .It Va idna-disable \*(BO\*(OP Can be used to turn off the automatic conversion of domain names according to the rules of IDNA (internationalized domain names for applications). Since the IDNA code assumes that domain names are specified with the .Va ttycharset character set, an UTF-8 locale charset is required to represent all possible international domain names (before conversion, that is). . .Mx .It Va ifs The input field separator that is used (\*(ID by some functions) to determine where to split input data. .Pp .Bl -tag -compact -width ".It MMM" .It 1. Unsetting is treated as assigning the default value, .Ql \& \et\en . .It 2. If set to the empty value, no field splitting will be performed. .It 3. If set to a non-empty value, all whitespace characters are extracted and assigned to the variable .Va ifs-ws . .El .Pp .Bl -tag -compact -width ".It MMM" .It a. .Va \&\&ifs-ws will be ignored at the beginning and end of input. Diverging from POSIX shells default whitespace is removed in addition, which is owed to the entirely different line content extraction rules. .It b. Each occurrence of a character of .Va \&\&ifs will cause field-splitting, any adjacent .Va \&\&ifs-ws characters will be skipped. .El . .Mx .It Va ifs-ws \*(RO Automatically deduced from the whitespace characters in .Va ifs . . .Mx .It Va ignore \*(BO Ignore interrupt signals from the terminal while entering messages; instead echo them as .Ql @ characters and discard the current line. . .Mx .It Va ignoreeof \*(BO Ignore end-of-file conditions .Pf ( Ql control-D ) in compose mode on message input and in interactive command input. If set an interactive command input session can only be left by explicitly using one of the commands .Ic exit and .Ic quit , and message input in compose mode can only be terminated by entering a period .Ql \&. on a line by itself or by using the .Ic ~. .Sx "COMMAND ESCAPES" ; Setting this implies the behaviour that .Va dot describes in .Va posix mode. . .Mx .It Va inbox If this is set to a non-empty string it will specify the user's .Mx -sx .Sx "primary system mailbox" , overriding .Ev MAIL and the system-dependent default, and (thus) be used to replace .Ql % when doing .Sx "Filename transformations" ; also see .Ic file for more on this topic. The value supports a subset of transformations itself. . .Mx .It Va indentprefix String used by the .Ic ~m , ~M and .Ic ~R .Sx "COMMAND ESCAPES" and by the .Va quote option for indenting messages, in place of the POSIX mandated default tabulator character .Ql \et . Also see .Va quote-chars . . .Mx .It Va keep \*(BO If set, an empty .Mx -sx .Sx "primary system mailbox" file is not removed. Note that, in conjunction with .Va posix mode any empty file will be removed unless this variable is set. This may improve the interoperability with other mail user agents when using a common folder directory, and prevents malicious users from creating fake mailboxes in a world-writable spool directory. \*(ID Only local regular (MBOX) files are covered, Maildir and other mailbox types will never be removed, even if empty. . .Mx .It Va keep-content-length \*(BO When (editing messages and) writing MBOX mailbox files \*(UA can be told to keep the .Ql Content-Length: and .Ql Lines: header fields that some MUAs generate by setting this variable. Since \*(UA does neither use nor update these non-standardized header fields (which in itself shows one of their conceptual problems), stripping them should increase interoperability in between MUAs that work with with same mailbox files. Note that, if this is not set but .Va writebackedited , as below, is, a possibly performed automatic stripping of these header fields already marks the message as being modified. \*(ID At some future time \*(UA will be capable to rewrite and apply an .Va mime-encoding to modified messages, and then those fields will be stripped silently. . .Mx .It Va keepsave \*(BO When a message is saved it is usually discarded from the originating folder when \*(UA is quit. This setting causes all saved message to be retained. . .Mx .It Va line-editor-cpl-word-breaks \*(OP List of bytes which are used by the .Cd mle-complete tabulator completion to decide where word boundaries exist, by default .Ql """'@=;|: \*(ID This mechanism is yet restricted. . .Mx .It Va line-editor-disable \*(BO Turn off any line editing capabilities (from \*(UAs POW, see .Sx "On terminal control and line editor" for more). . .Mx .It Va line-editor-no-defaults \*(BO\*(OP Do not establish any default key binding. . .Mx .It Va log-prefix Error log message prefix string .Pf ( Ql "\*(uA: " ) . . .Mx .It Va mailbox-display \*(RO The name of the current mailbox .Pf ( Ic file ) , possibly abbreviated for display purposes. . .Mx .It Va mailbox-resolved \*(RO The fully resolved path of the current mailbox. . .Mx .It Va mailx-extra-rc An additional startup file that is loaded as the last of the .Sx "Resource files" . Use this file for commands that are not understood by other POSIX .Xr mailx 1 implementations, i.e., mostly anything which is not covered by .Sx "Initial settings" . . .Mx .It Va markanswered \*(BO When a message is replied to and this variable is set, it is marked as having been .Ic answered . See the section .Sx "Message states" . . .Mx .It Va mbox-fcc-and-pcc \*(BO By default all file and pipe message receivers (see .Va expandaddr ) will be fed valid MBOX database entry message data (see .Ic file , .Va mbox-rfc4155 ) , and existing file targets will become extended in compliance to RFC 4155. If this variable is unset then a plain standalone RFC 5322 message will be written, and existing file targets will be overwritten. . .Mx .It Va mbox-rfc4155 \*(BO When opening MBOX mailbox databases, and in order to achieve compatibility with old software, the very tolerant POSIX standard rules for detecting message boundaries (so-called .Ql From_ lines) are used instead of the stricter rules from the standard RFC 4155. This behaviour can be switched by setting this variable. .Pp This may temporarily be handy when \*(UA complains about invalid .Ql From_ lines when opening a MBOX: in this case setting this variable and re-opening the mailbox in question may correct the result. If so, copying the entire mailbox to some other file, as in .Ql copy * SOME-FILE , will perform proper, all-compatible .Ql From_ quoting for all detected messages, resulting in a valid MBOX mailbox. (\*(ID The better and non-destructive approach is to re-encode invalid messages, as if it would be created anew, instead of mangling the .Ql From_ lines; this requires the structural code changes of the v15 rewrite.) Finally the variable can be unset again: .Bd -literal -offset indent define mboxfix { localopts yes; wysh set mbox-rfc4155;\e wysh File "${1}"; copy * "${2}" } call mboxfix /tmp/bad.mbox /tmp/good.mbox .Ed . .Mx .It Va memdebug \*(BO Internal development variable. (Keeps memory debug enabled even if .Va debug is not set.) . .Mx .It Va message-id-disable \*(BO By setting this variable the generation of .Ql Message-ID: and .Ql Content-ID: message and MIME part headers can be completely suppressed, effectively leaving this task up to the .Va mta (Mail-Transfer-Agent) or the SMTP server. Note that according to RFC 5321 a SMTP server is not required to add this field by itself, so it should be ensured that it accepts messages without .Ql Message-ID . . .Mx .It Va message-inject-head A string to put at the beginning of each new message, followed by a newline. \*(OB The escape sequences tabulator .Ql \et and newline .Ql \en are understood (use the .Cm wysh prefix when .Ic set Ns ting the variable(s) instead). . .Mx .It Va message-inject-tail A string to put at the end of each new message, followed by a newline. \*(OB The escape sequences tabulator .Ql \et and newline .Ql \en are understood (use the .Cm wysh prefix when .Ic set Ns ting the variable(s) instead). Also see .Va on-compose-leave . . .Mx .It Va metoo \*(BO Usually, when an .Ic alias expansion contains the sender, the sender is removed from the expansion. Setting this option suppresses these removals. Note that a set .Va metoo also causes a .Ql -m option to be passed through to the .Va mta (Mail-Transfer-Agent); though most of the modern MTAs no longer document this flag, no MTA is known which does not support it (for historical compatibility). . .Mx .It Va mime-allow-text-controls \*(BO When sending messages, each part of the message is MIME-inspected in order to classify the .Ql Content-Type: and .Ql Content-Transfer-Encoding: (see .Va mime-encoding ) that is required to send this part over mail transport, i.e., a computation rather similar to what the .Xr file 1 command produces when used with the .Ql --mime option. .Pp This classification however treats text files which are encoded in UTF-16 (seen for HTML files) and similar character sets as binary octet-streams, forcefully changing any .Ql text/plain or .Ql text/html specification to .Ql application/octet-stream : If that actually happens a yet unset charset MIME parameter is set to .Ql binary , effectively making it impossible for the receiving MUA to automatically interpret the contents of the part. .Pp If this variable is set, and the data was unambiguously identified as text data at first glance (by a .Ql .txt or .Ql .html file extension), then the original .Ql Content-Type: will not be overwritten. . .Mx .It Va mime-alternative-favour-rich \*(BO If this variable is set then rich MIME alternative parts (e.g., HTML) will be preferred in favour of included plain text versions when displaying messages, provided that a handler exists which produces output that can be (re)integrated into \*(UA's normal visual display. (E.g., at the time of this writing some newsletters ship their full content only in the rich HTML part, whereas the plain text part only contains topic subjects.) . .Mx .It Va mime-counter-evidence Normally the .Ql Content-Type: field is used to decide how to handle MIME parts. Some MUAs, however, do not use .Sx "The mime.types files" (also see .Sx "HTML mail and MIME attachments" ) or a similar mechanism to correctly classify content, but specify an unspecific MIME type .Pf ( Ql application/octet-stream ) even for plain text attachments. If this variable is set then \*(UA will try to re-classify such MIME message parts, if possible, for example via a possibly existing attachment filename. A non-empty value may also be given, in which case a number is expected, actually a carrier of bits, best specified as a binary value, e.g., .Ql 0b1111 . .Pp .Bl -bullet -compact .It If bit two is set (counting from 1, decimal 2) then the detected .Ic mimetype will be carried along with the message and be used for deciding which MIME handler is to be used, for example; when displaying such a MIME part the part-info will indicate the overridden content-type by showing a plus sign .Ql + . .It If bit three is set (decimal 4) then the counter-evidence is always produced and a positive result will be used as the MIME type, even forcefully overriding the parts given MIME type. .It If bit four is set (decimal 8) as a last resort the actual content of .Ql application/octet-stream parts will be inspected, so that data which looks like plain text can be treated as such. This mode is even more relaxed when data is to be displayed to the user or used as a message quote (data consumers which mangle data for display purposes, which includes masking of control characters, for example). .El . . .Mx .It Va mime-encoding The MIME .Ql Content-Transfer-Encoding to use in outgoing text messages and message parts, where applicable (7-bit clean text messages are without an encoding if possible): . .Pp .Bl -tag -compact -width ".It Ql _%%_" .It Ql 8bit .Pf (Or\0 Ql 8b . ) 8-bit transport effectively causes the raw data be passed through unchanged, but may cause problems when transferring mail messages over channels that are not ESMTP (RFC 1869) compliant. Also, several input data constructs are not allowed by the specifications and may cause a different transfer-encoding to be used. By established rules and popular demand occurrences of .Ql ^From_ (see .Va mbox-rfc4155 ) will be MBOXO quoted (prefixed with greater-than sign .Ql > ) instead of causing a non-destructive encoding like .Ql quoted-printable to be chosen, unless context (e.g., message signing) requires otherwise. . .It Ql quoted-printable .Pf (Or\0 Ql qp . ) Quoted-printable encoding is 7-bit clean and has the property that ASCII characters are passed through unchanged, so that an english message can be read as-is; it is also acceptable for other single-byte locales that share many characters with ASCII, like, e.g., ISO-8859-1. The encoding will cause a large overhead for messages in other character sets: e.g., it will require up to twelve (12) bytes to encode a single UTF-8 character of four (4) bytes. It is the default encoding. . .It Ql base64 .Pf (Or\0 Ql b64 . ) This encoding is 7-bit clean and will always be used for binary data. This encoding has a constant input:output ratio of 3:4, regardless of the character set of the input data it will encode three bytes of input to four bytes of output. This transfer-encoding is not human readable without performing a decoding step. .El . . .Mx .It Va mime-force-sendout \*(BO\*(OP Whenever it is not acceptable to fail sending out messages because of non-convertible character content this variable may be set. It will, as a last resort, classify the part content as .Ql application/octet-stream . Please refer to the section .Sx "Character sets" for the complete picture of character set conversion in \*(UA. . .Mx .It Va mimetypes-load-control Can be used to control which of .Sx "The mime.types files" are loaded: if the letter .Ql u is part of the option value, then the user's personal .Pa \*(vU file will be loaded (if it exists); likewise the letter .Ql s controls loading of the system wide .Pa \*(vS ; directives found in the user file take precedence, letter matching is case-insensitive. If this variable is not set \*(UA will try to load both files. Incorporation of the \*(UA-built-in MIME types cannot be suppressed, but they will be matched last (the order can be listed via .Ic mimetype ) . .Pp More sources can be specified by using a different syntax: if the value string contains an equals sign .Ql = then it is instead parsed as a comma-separated list of the described letters plus .Ql f=FILENAME pairs; the given filenames will be expanded and loaded, and their content may use the extended syntax that is described in the section .Sx "The mime.types files" . Directives found in such files always take precedence (are prepended to the MIME type cache). . . .Mx .It Va mta Select an alternate Mail-Transfer-Agent by either specifying the full pathname of an executable (optionally prefixed with the protocol .Ql file:// ) , or \*(OPally a SMTP aka SUBMISSION protocol URL, e.g., \*(IN .Pp .Dl submissions://[user[:password]@]server[:port] .Pp (\*(OU: .Ql [smtp://]server[:port] . ) The default has been chosen at compile time. MTA data transfers are always performed in asynchronous child processes, and without supervision unless either the .Va sendwait or the .Va verbose variable is set. \*(OPally \*(UA can take care of expansion of the usual .Va mta-aliases .Pf ( Xr aliases 5 ) . . .Pp For testing purposes there is the .Ql test pseudo-MTA, which dumps to standard output or optionally to a file, and honours .Va mbox-fcc-and-pcc : . .Bd -literal -offset indent $ echo text | \*(uA -:/ -Smta=test -s ubject user@exam.ple $ '; read es;\e vput csop es substring "${es}" 0 1 if [ "$es" != 2 ] echoerr 'Cannot insert Cc: header'; echo '~x' # (no xit, macro finishs anyway) endif endif } set on-compose-splice=ocsm .Ed . . .Mx .It Va on-history-addition This hook will be called if an entry is about to be added to the .Ic history of the MLE, as is documented in .Sx "On terminal control and line editor" . It will be called with three arguments: the first is the name of the input context (see .Ic bind ) , the second whether the command relates to .Va history-gabby , and the third being the complete command line to be added. The entry will not be added to history if the hook uses a non-0 .Ic return . \*(ID A future version will give the expanded command name as the third argument, followed by the tokenized command line as parsed in the remaining arguments, the first of which is the original unexpanded command name; i.e., one may do .Ql Ic shift Ns \| 4 and will then be able to access the positional parameters as usual via .Va * , # , 1 etc. . .Mx .It Va on-main-loop-tick This hook will be called whenever the program's main event loop is about to read the next input line. Note variable and other changes it performs are not scoped, e.g., via .Ic localopts ! . .Mx .It Va on-program-exit This hook will be called when the program exits, whether via .Ic exit or .Ic quit , or because the send mode is done. . .Mx .It Va on-resend-cleanup \*(ID Identical to .Va on-compose-cleanup , but is only triggered by .Ic resend . . .Mx .It Va on-resend-enter \*(ID Identical to .Va on-compose-enter , but is only triggered by .Ic resend ; currently there is no .Ic digmsg support, for example. . .Mx .It Va page \*(BO If set, each message feed through the command given for .Ic pipe is followed by a formfeed character .Ql \ef . . .Mx Va password .It Va password-USER@HOST , password-HOST , password \*(IN Variable chain that sets a password, which is used in case none has been given in the protocol and account-specific URL; as a last resort \*(UA will ask for a password on the user's terminal if the authentication method requires a password. Specifying passwords in a startup file is generally a security risk; the file should be readable by the invoking user only. . .It Va password-USER@HOST \*(OU (see the chain above for \*(IN) Set the password for .Ql USER when connecting to .Ql HOST . If no such variable is defined for a host, the user will be asked for a password on standard input. Specifying passwords in a startup file is generally a security risk; the file should be readable by the invoking user only. . .Mx .It Va piperaw \*(BO Send messages to the .Ic pipe command without performing MIME and character set conversions. . . .Mx .It Va pipe-TYPE/SUBTYPE When a MIME message part of type .Ql TYPE/SUBTYPE (case-insensitive) is displayed or quoted, its text is filtered through the value of this variable interpreted as a shell command. Note that only parts which can be displayed inline as plain text (see .Cd copiousoutput ) are displayed unless otherwise noted, other MIME parts will only be considered by and for the command .Ic mimeview . . .Pp The special value question mark .Ql \&? forces interpretation of the message part as plain text, e.g., .Ql set pipe-application/xml=? will henceforth display XML .Dq as is . (The same could also be achieved by adding a MIME type marker with the .Ic mimetype command. And \*(OPally MIME type handlers may be defined via .Sx "The Mailcap files" \(em these directives, .Cd copiousoutput has already been used, should be referred to for further documentation. . .Pp The question mark .Ql \&? can in fact be used as a trigger character to adjust usage and behaviour of a following shell command specification more thoroughly by appending more special characters which refer to further mailcap directives, e.g., the following hypothetical command specification could be used: . .Bd -literal -offset indent ? set pipe-X/Y='?!++=? vim ${MAILX_FILENAME_TEMPORARY}' .Ed . .Pp .Bl -tag -compact -width ".It Ql __" .It Ql * The command produces plain text to be integrated in \*(UAs output: .Cd copiousoutput . . .It Ql # If set the handler will not be invoked when a message is to be quoted, but only when it will be displayed: .Cd x-mailx-noquote . . .It Ql & Run the command asynchronously, i.e., without blocking \*(UA: .Cd x-mailx-async . The standard output of the command will go to .Pa /dev/null . . .It Ql \&! The command must be run on an interactive terminal, \*(UA will temporarily release the terminal to it: .Cd needsterminal . . .It Ql + Request creation of a zero-sized temporary file, the absolute pathname of which will be made accessible via the environment variable .Ev MAILX_FILENAME_TEMPORARY : .Cd x-mailx-tmpfile . If given twice then the file will be unlinked automatically by \*(UA when the command loop is entered again at latest: .Cd x-mailx-tmpfile-unlink ; it is an error to use automatic deletion in conjunction with .Cd x-mailx-async . . .It Ql = Normally the MIME part content is passed to the handler via standard input; if this flag is set then the data will instead be written into .Ev MAILX_FILENAME_TEMPORARY .Pf ( Cd x-mailx-tmpfile-fill ) , the creation of which is implied; in order to cause automatic deletion of the temporary file two plus signs .Ql ++ still have to be used. . .It Ql \&? To avoid ambiguities with normal shell command content another question mark can be used to forcefully terminate interpretation of remaining characters. (Any character not in this list will have the same effect.) .El . .Pp Some information about the MIME part to be displayed is embedded into the environment of the shell command: . .Pp .Bl -tag -compact -width ".It Ev _AIL__ILENAME__ENERATED" .Mx .It Ev MAILX_CONTENT The MIME content-type of the part, if known, the empty string otherwise. . .Mx .It Ev MAILX_CONTENT_EVIDENCE If .Va mime-counter-evidence includes the carry-around-bit (2), then this will be set to the detected MIME content-type; not only then identical to .Ev \&\&MAILX_CONTENT otherwise. . .Mx .It Ev MAILX_EXTERNAL_BODY_URL MIME parts of type .Ql message/external-body access-type=url will store the access URL in this variable, it is empty otherwise. URL targets should not be activated automatically, without supervision. . .Mx .It Ev MAILX_FILENAME The filename, if any is set, the empty string otherwise. . .Mx .It Ev MAILX_FILENAME_GENERATED A random string. . .Mx .It Ev MAILX_FILENAME_TEMPORARY If temporary file creation has been requested through the command prefix this variable will be set and contain the absolute pathname of the temporary file. .El . . .Mx .It Va pipe-EXTENSION This is identical to .Va pipe-TYPE/SUBTYPE except that .Ql EXTENSION (normalized to lowercase using character mappings of the ASCII charset) names a file extension, e.g., .Ql xhtml . Handlers registered using this method take precedence. . .Mx Va pop3-auth .It Va pop3-auth-USER@HOST , pop3-auth-HOST , pop3-auth \*(OP\*(IN Variable chain that sets the POP3 authentication method. Supported are the default .Ql plain , \*(IN .Ql oauthbearer (see .Sx FAQ entry .Sx "But, how about XOAUTH2 / OAUTHBEARER?" ) , as well as \*(IN .Ql external and .Ql externanon for TLS secured connections which pass a client certificate via .Va tls-config-pairs . There may be the \*(OPal method \*(IN .Ql gssapi . .Ql externanon does not need any user credentials, .Ql external and .Ql gssapi need a .Va user , the remains also require a .Va password . .Ql externanon solely builds upon the credentials passed via a client certificate, and is usually the way to go since tested servers do not actually follow RFC 4422, and fail if additional credentials are actually passed. Unless .Va pop3-no-apop is set the .Ql plain method will \*(OPally be replaced with APOP if possible (see there). . .Mx Va pop3-bulk-load .It Va pop3-bulk-load-USER@HOST , pop3-bulk-load-HOST , pop3-bulk-load \*(BO\*(OP When accessing a POP3 server \*(UA loads the headers of the messages, and only requests the message bodies on user request. For the POP3 protocol this means that the message headers will be downloaded twice. If this variable is set then \*(UA will download only complete messages from the given POP3 server(s) instead. . .Mx Va pop3-keepalive .It Va pop3-keepalive-USER@HOST , pop3-keepalive-HOST , pop3-keepalive \*(OP POP3 servers close the connection after a period of inactivity; the standard requires this to be at least 10 minutes, but practical experience may vary. Setting this variable to a numeric value greater than .Ql 0 causes a .Ql NOOP command to be sent each value seconds if no other operation is performed. . .Mx Va pop3-no-apop .It Va pop3-no-apop-USER@HOST , pop3-no-apop-HOST , pop3-no-apop \*(BO\*(OP Unless this variable is set the MD5 based .Ql APOP authentication method will be used instead of a chosen .Ql plain .Va pop3-auth when connecting to a POP3 server that advertises support. The advantage of .Ql APOP is that only a single packet is sent for the user/password tuple. (Originally also that the password is not sent in clear text over the wire, but for one MD5 does not any longer offer sufficient security, and then today transport is almost ever TLS secured.) Note that .Va pop3-no-apop-HOST requires \*(IN. . .Mx Va pop3-use-starttls .It Va pop3-use-starttls-USER@HOST , pop3-use-starttls-HOST , pop3-use-starttls \*(BO\*(OP Causes \*(UA to issue a .Ql STLS command to make an unencrypted POP3 session TLS encrypted. This functionality is not supported by all servers, and is not used if the session is already encrypted by the POP3S method. Note that .Va pop3-use-starttls-HOST requires \*(IN. . . .Mx .It Va posix \*(BO This flag enables POSIX mode, which changes behaviour of \*(UA where that deviates from standardized behaviour. It will be set implicitly before the .Sx "Resource files" are loaded if the environment variable .Ev POSIXLY_CORRECT is set, and adjusting any of those two will be reflected by the other one implicitly. The following behaviour is covered and enforced by this mechanism: . .Pp .Bl -bullet -compact .It In non-interactive mode, any error encountered while loading resource files during program startup will cause a program exit, whereas in interactive mode such errors will stop loading of the currently loaded (stack of) file(s, i.e., recursively). These exits can be circumvented on a per-command base by using .Cm ignerr , one of the .Sx "Command modifiers" , for each command which shall be allowed to fail. . .It .Ic alternates will replace the list of alternate addresses instead of appending to it. In addition alternates will only be honoured for any sort of message .Ic reply , and for aliases. . .It The variable inserting .Sx "COMMAND ESCAPES" .Ic ~A , .Ic ~a , .Ic ~I and .Ic ~i will expand embedded character sequences .Ql \et horizontal tabulator and .Ql \en line feed. \*(ID For compatibility reasons this step will always be performed. . .It Upon changing the active .Ic file no summary of .Ic headers will be displayed even if .Va header is set. . .It Setting .Va ignoreeof implies the behaviour described by .Va dot . . .It The variable .Va keep is extended to cover any empty mailbox, not only empty .Mx -sx .Sx "primary system mailbox" Ns es: they will be removed when they are left in empty state otherwise. .El . . .Mx .It Va print-alternatives \*(BO When a MIME message part of type .Ql multipart/alternative is displayed and it contains a subpart of type .Ql text/plain , other parts are normally discarded. Setting this variable causes all subparts to be displayed, just as if the surrounding part was of type .Ql multipart/mixed . . .Mx .It Va prompt The string used as a prompt in interactive mode. Whenever the variable is evaluated the value is treated as if specified within dollar-single-quotes (see .Sx "Shell-style argument quoting" ) . This (post-assignment, i.e., second) expansion can be used to embed status information, for example .Va \&? , .Va \&! , .Va account or .Va mailbox-display . .Pp In order to embed characters which should not be counted when calculating the visual width of the resulting string, enclose the characters of interest in a pair of reverse solidus escaped brackets: .Ql \e[\eE[0m\e] ; a slot for coloured prompts is also available with the \*(OPal command .Ic colour . Prompting may be prevented by setting this to the null string (aka\| .Ql set noprompt ) . . .Mx .It Va prompt2 This string is used for secondary prompts, but is otherwise identical to .Va prompt . The default is .Ql ..\0 . . .Mx .It Va quiet \*(BO Suppresses the printing of the version when first invoked. . .Mx .It Va quote If set a .Ic reply message is started with the quoted original message, the lines of which are prefixed by the value of the variable .Va indentprefix , taking into account .Va quote-chars and .Va quote-fold . If set to the empty value, the quoted message will be preceded and followed by the expansions of the values of .Va quote-inject-head and .Va quote-inject-tail , respectively. None of the headers of the quoted message is included in the quote if the value equals .Ql noheading , and only the headers selected by the .Ql type .Ic headerpick selection are put above the message body for .Ql headers , whereas all headers and all MIME parts are included for .Ql allheaders . Also see .Va quote-as-attachment and .Ic ~Q , one of the .Sx "COMMAND ESCAPES" . . .Mx .It Va quote-as-attachment \*(BO Add the original message in its entirety as a .Ql message/rfc822 MIME attachment when replying to a message. Note this works regardless of the setting of .Va quote . . .Mx .It Va quote-chars Can be set to a string consisting of non-whitespace ASCII characters which shall be treated as quotation leaders, the default being .Ql >|}: . . .Mx .It Va quote-fold \*(OP Can be set in addition to .Va indentprefix , and creates a more fancy quotation in that leading quotation characters .Pf ( Va quote-chars ) are compressed and overlong lines are folded. .Va \&\"e-fold can be set to either one, two or three (space separated) numeric values, which are interpreted as the maximum (goal) and the minimum line length, respectively, in a spirit rather equal to the .Xr fmt 1 program, but line- instead of paragraph-based. The third value is used as the maximum line length instead of the first if no better break point can be found; it is ignored unless it is larger than the minimum and smaller than the maximum. If not set explicitly the minimum will reflect the goal algorithmically. The goal cannot be smaller than the length of .Va indentprefix plus some additional pad; necessary adjustments take place silently. . . .Mx .Mx .It Va quote-inject-head , quote-inject-tail The strings to put before and after the text of a .Va quote Ns d message, respectively. The former defaults to .Ql %f wrote:\en\en . Special format directives will be expanded if possible, and if so configured the output will be folded according to .Va quote-fold . Format specifiers in the given strings start with a percent sign .Ql % and expand values of the original message, unless noted otherwise. Note that names and addresses are not subject to the setting of .Va showto . Valid format specifiers are: . .Pp .Bl -tag -compact -width ".It Ql _%%_" .It Ql %% A plain percent sign. .It Ql %a The address(es) of the sender(s). .It Ql %d The date found in the .Ql Date: header of the message when .Va datefield is set (the default), otherwise the date when the message was received. Formatting can be controlled by assigning a .Xr strftime 3 format string to .Va datefield (and .Va datefield-markout-older ) . .It Ql %f The full name(s) (name and address, as given) of the sender(s). .It Ql %i The .Ql Message-ID: . .It Ql %n The real name(s) of the sender(s) if there is one and .Va showname allows usage, the address(es) otherwise. .It Ql %r The senders real name(s) if there is one, the address(es) otherwise. .El . . .Mx .It Va r-option-implicit \*(BO Setting this option evaluates the contents of .Va from (or, if that contains multiple addresses, .Va sender ) and passes the results onto the used (file-based) MTA as described for the .Fl r option (empty argument case). . .Mx .It Va recipients-in-cc \*(BO When doing a .Ic reply , the original .Ql From: and .Ql To: are by default merged into the new .Ql To: . If this variable is set, only the original .Ql From: ends in the new .Ql To: , the rest is merged into .Ql Cc: . . .Mx .It Va record Unless this variable is defined, no copies of outgoing mail will be saved. If defined it gives the pathname, subject to the usual .Sx "Filename transformations" , of a folder where all new, replied-to or forwarded messages are saved: when saving to this folder fails the message is not sent, but instead .Va save Ns d to .Ev DEAD . The standard defines that relative (fully expanded) paths are to be interpreted relative to the current directory .Pf ( Ic cwd ) , to force interpretation relative to .Va folder .Va outfolder needs to be set in addition. . .Mx .It Va record-files \*(BO If this variable is set the meaning of .Va record will be extended to cover messages which target only file and pipe recipients (see .Va expandaddr ) . These address types will not appear in recipient lists unless .Va add-file-recipients is also set. . .Mx .It Va record-resent \*(BO If this variable is set the meaning of .Va record will be extended to also cover the .Ic resend and .Ic Resend commands. . .Mx .It Va reply-in-same-charset \*(BO If this variable is set \*(UA first tries to use the same character set of the original message for replies. If this fails, the mechanism described in .Sx "Character sets" is evaluated as usual. . .Mx .It Va reply-strings Can be set to a comma-separated list of (case-insensitive according to ASCII rules) strings which shall be recognized in addition to the built-in strings as .Ql Subject: reply message indicators \(en built-in are .Ql Re: , which is mandated by RFC 5322, as well as the german .Ql Aw: , .Ql Antw: , and the .Ql Wg: which often has been seen in the wild; I.e., the separating colon has to be specified explicitly. . .Mx .It Va reply-to A list of addresses to put into the .Ql Reply-To: field of the message header. Members of this list are handled as if they were in the .Ic alternates list. . .It Va replyto \*(OB Variant of .Va reply-to . . .Mx .It Va reply-to-honour Controls whether a .Ql Reply-To: header is honoured when replying to a message via .Ic reply or .Ic Lreply . This is a quadoption; if set without a value it defaults to .Dq yes . . .Mx .It Va rfc822-body-from_ \*(BO This variable can be used to force displaying a so-called .Ql From_ line for messages that are embedded into an envelope mail via the .Ql message/rfc822 MIME mechanism, for more visual convenience, also see .Va mbox-rfc4155 . . .Mx .It Va save \*(BO Enable saving of (partial) messages in .Ev DEAD upon interrupt or delivery error. . .Mx .It Va screen The number of lines that represents a .Dq screenful of lines, used in .Ic headers summary display, .Ic from .Ic search Ns ing, message .Ic top Ns line display and scrolling via .Ic z . If this variable is not set \*(UA falls back to a calculation based upon the detected terminal window size and the baud rate: the faster the terminal, the more will be shown. Overall screen dimensions and pager usage is influenced by the environment variables .Ev COLUMNS and .Ev LINES and the variable .Va crt . . .Mx .It Va searchheaders \*(BO Expand message-list specifiers in the form .Ql /x:y to all messages containing the substring .Dq y in the header field .Ql x . The string search is case insensitive. . .Mx .It Va sendcharsets \*(OP A comma-separated list of character set names that can be used in outgoing internet mail. The value of the variable .Va charset-8bit is automatically appended to this list of character sets. If no character set conversion capabilities are compiled into \*(UA then the only supported charset is .Va ttycharset . Also see .Va sendcharsets-else-ttycharset and refer to the section .Sx "Character sets" for the complete picture of character set conversion in \*(UA. . .Mx .It Va sendcharsets-else-ttycharset \*(BO\*(OP If this variable is set, but .Va sendcharsets is not, then \*(UA acts as if .Va sendcharsets had been set to the value of the variable .Va ttycharset . In effect this combination passes through the message data in the character set of the current locale encoding: therefore mail message text will be (assumed to be) in ISO-8859-1 encoding when send from within a ISO-8859-1 locale, and in UTF-8 encoding when send from within an UTF-8 locale. .Pp The 8-bit fallback .Va charset-8bit never comes into play as .Va ttycharset is implicitly assumed to be 8-bit and capable to represent all files the user may specify (as is the case when no character set conversion support is available in \*(UA and the only supported character set is .Va ttycharset , see .Sx "Character sets" ) . This might be a problem for scripts which use the suggested .Ql LC_ALL=C setting, since in this case the character set is US-ASCII by definition, so that it is better to also override .Va ttycharset , then; and/or do something like the following in the resource file: .Bd -literal -offset indent if [ "$LC_ALL" == C ] || [ "$LC_CTYPE" == C ] unset sendcharsets-else-ttycharset end .Ed . .Mx .It Va sender An address that is put into the .Ql Sender: field of outgoing messages, quoting RFC 5322: the mailbox of the agent responsible for the actual transmission of the message. This field should normally not be used unless the .Va from field contains more than one address, on which case it is required. Dependent on the context this address is handled as if it were in the list of .Ic alternates . Also see .Fl r , .Va r-option-implicit . . .It Va sendmail \*(OB Predecessor of .Va mta . . .It Va sendmail-arguments \*(OB Predecessor of .Va mta-arguments . . .It Va sendmail-no-default-arguments \*(OB\*(BO Predecessor of .Va mta-no-default-arguments . . .It Va sendmail-progname \*(OB Predecessor of .Va mta-argv0 . . .Mx .It Va sendwait Sending messages to the chosen .Va mta or to command-pipe receivers (see .Sx "On sending mail, and non-interactive mode" ) will be performed asynchronously. This means that only startup errors of the respective program will be recognizable, but no delivery errors. Also, no guarantees can be made as to when the respective program will actually run, as well as to when they will have produced output. .Pp If this variable is set then child program exit is waited for, and its exit status code is used to decide about success. Remarks: in conflict with the POSIX standard this variable is built-in to be initially set. Another difference is that it can have a value, which is interpreted as a comma-separated list of case-insensitive strings naming specific subsystems for which synchronousness shall be ensured (only). Possible values are .Ql mta for .Va mta delivery, and .Ql pcc for command-pipe receivers. . .Mx .It Va showlast \*(BO This setting causes \*(UA to start at the last message instead of the first one when opening a mail folder, as well as with .Ic from and .Ic headers . . .Mx .It Va showname \*(BO Causes \*(UA to use the sender's real name instead of the plain address in the header field summary and in message specifications. . .Mx .It Va showto \*(BO Causes the recipient of the message to be shown in the header summary if the message was sent by the user. . .Mx .It Va Sign The value backing .Ic ~A , one of the .Sx "COMMAND ESCAPES" . Also see .Va message-inject-tail , .Va on-compose-leave and .Va on-compose-splice . . .Mx .It Va sign The value backing .Ic ~a , one of the .Sx "COMMAND ESCAPES" . Also see .Va message-inject-tail , .Va on-compose-leave and .Va on-compose-splice . . .Mx .It Va signature \*(OB Please use .Va on-compose-splice or .Va on-compose-splice-shell or .Va on-compose-leave and (if necessary) .Va message-inject-tail instead! . .Mx .It Va skipemptybody \*(BO If an outgoing message does not contain any text in its first or only message part, do not send it but discard it silently (see also the command line option .Fl E ) . . .Mx .Mx .It Va smime-ca-dir , smime-ca-file \*(OP Specify the location of trusted CA certificates in PEM (Privacy Enhanced Mail) for the purpose of verification of S/MIME signed messages. .Va tls-ca-dir documents the necessary preparation steps to use the former. The set of CA certificates which are built into the TLS library can be explicitly turned off by setting .Va smime-ca-no-defaults , and further fine-tuning is possible via .Va smime-ca-flags . . .Mx .It Va smime-ca-flags \*(OP Can be used to fine-tune behaviour of the X509 CA certificate storage, and the certificate verification that is used. The actual values and their meanings are documented for .Va tls-ca-flags . . .Mx .It Va smime-ca-no-defaults \*(BO\*(OP Do not load the default CA locations that are built into the used to TLS library to verify S/MIME signed messages. . .Mx Va smime-cipher .It Va smime-cipher-USER@HOST , smime-cipher \*(OP Specifies the cipher to use when generating S/MIME encrypted messages (for the specified account). RFC 5751 mandates a default of .Ql aes128 (AES-128 CBC). Possible values are (case-insensitive and) in decreasing cipher strength: .Ql aes256 (AES-256 CBC), .Ql aes192 (AES-192 CBC), .Ql aes128 (AES-128 CBC), .Ql des3 (DES EDE3 CBC, 168 bits; default if .Ql aes128 is not available) and .Ql des (DES CBC, 56 bits). .Pp The actually available cipher algorithms depend on the cryptographic library that \*(UA uses. \*(OP Support for more cipher algorithms may be available through dynamic loading via, e.g., .Xr EVP_get_cipherbyname 3 (OpenSSL) if \*(UA has been compiled to support this. . .Mx .It Va smime-crl-dir \*(OP Specifies a directory that contains files with CRLs in PEM format to use when verifying S/MIME messages. . .Mx .It Va smime-crl-file \*(OP Specifies a file that contains a CRL in PEM format to use when verifying S/MIME messages. . .Mx .It Va smime-encrypt-USER@HOST \*(OP If this variable is set, messages send to the given receiver are encrypted before sending. The value of the variable must be set to the name of a file that contains a certificate in PEM format. .Pp If a message is sent to multiple recipients, each of them for whom a corresponding variable is set will receive an individually encrypted message; other recipients will continue to receive the message in plain text unless the .Va smime-force-encryption variable is set. It is recommended to sign encrypted messages, i.e., to also set the .Va smime-sign variable. . .Mx .It Va smime-force-encryption \*(BO\*(OP Causes \*(UA to refuse sending unencrypted messages. . .Mx .It Va smime-sign \*(BO\*(OP S/MIME sign outgoing messages with the user's private key and include the user's certificate as a MIME attachment. Signing a message enables a recipient to verify that the sender used a valid certificate, that the email addresses in the certificate match those in the message header and that the message content has not been altered. It does not change the message text, and people will be able to read the message as usual. Also see .Va smime-sign-cert , smime-sign-include-certs and .Va smime-sign-digest . . .Mx Va smime-sign-cert .It Va smime-sign-cert-USER@HOST , smime-sign-cert \*(OP Points to a file in PEM format. For the purpose of signing and decryption this file needs to contain the user's private key, followed by his certificate. .Pp For message signing .Ql USER@HOST is always derived from the value of .Va from (or, if that contains multiple addresses, .Va sender ) . For the purpose of encryption the recipient's public encryption key (certificate) is expected; the command .Ic certsave can be used to save certificates of signed messages (the section .Sx "Signed and encrypted messages with S/MIME" gives some details). This mode of operation is usually driven by the specialized form. .Pp When decrypting messages the account is derived from the recipient fields .Pf ( Ql To: and .Ql Cc: ) of the message, which are searched for addresses for which such a variable is set. \*(UA always uses the first address that matches, so if the same message is sent to more than one of the user's addresses using different encryption keys, decryption might fail. .Pp For signing and decryption purposes it is possible to use encrypted keys, and the pseudo-host(s) .Ql USER@HOST.smime-cert-key for the private key (and .Ql USER@HOST.smime-cert-cert for the certificate stored in the same file) will be used for performing any necessary password lookup, therefore the lookup can be automated via the mechanisms described in .Sx "On URL syntax and credential lookup" . For example, the hypothetical address .Ql bob@exam.ple could be driven with a private key / certificate pair path defined in .Va \&\&smime-sign-cert-bob@exam.ple , and needed passwords would then be looked up via the pseudo hosts .Ql bob@exam.ple.smime-cert-key (and .Ql bob@exam.ple.smime-cert-cert ) . To include intermediate certificates, use .Va smime-sign-include-certs . . .Mx Va smime-sign-digest .It Va smime-sign-digest-USER@HOST , smime-sign-digest \*(OP Specifies the message digestto use when signing S/MIME messages. Please remember that for this use case .Ql USER@HOST refers to the variable .Va from (or, if that contains multiple addresses, .Va sender ) . The available algorithms depend on the used cryptographic library, but at least one usable built-in algorithm is ensured as a default. If possible the standard RFC 5751 will be violated by using .Ql SHA512 instead of the mandated .Ql SHA1 due to security concerns. .Pp \*(UA will try to add built-in support for the following message digests, names are case-insensitive: .Ql BLAKE2b512 , .Ql BLAKE2s256 , .Ql SHA3-512 , .Ql SHA3-384 , .Ql SHA3-256 , .Ql SHA3-224 , as well as the widely available .Ql SHA512 , .Ql SHA384 , .Ql SHA256 , .Ql SHA224 , and the proposed insecure .Ql SHA1 , finally .Ql MD5 . More digests may \*(OPally be available through dynamic loading via, e.g., the OpenSSL function .Xr EVP_get_digestbyname 3 . . .Mx Va smime-sign-include-certs .It Va smime-sign-include-certs-USER@HOST , smime-sign-include-certs \*(OP If used, this is supposed to a consist of a comma-separated list of files, each of which containing a single certificate in PEM format to be included in the S/MIME message in addition to the .Va smime-sign-cert certificate. This can be used to include intermediate certificates of the certificate authority, in order to allow the receiver's S/MIME implementation to perform a verification of the entire certificate chain, starting from a local root certificate, over the intermediate certificates, down to the .Va smime-sign-cert . Even though top level certificates may also be included in the chain, they will not be used for the verification on the receiver's side. .Pp For the purpose of the mechanisms involved here, .Ql USER@HOST refers to the content of the internal variable .Va from (or, if that contains multiple addresses, .Va sender ) . The pseudo-host .Ql USER@HOST.smime-include-certs will be used for performing password lookups for these certificates, shall they have been given one, therefore the lookup can be automated via the mechanisms described in .Sx "On URL syntax and credential lookup" . . .It Va smime-sign-message-digest-USER@HOST , smime-sign-message-digest \*(OB\*(OP Predecessor(s) of .Va smime-sign-digest . . .It Va smtp \*(OB\*(OP To use the built-in SMTP transport, specify a SMTP URL in .Va mta . \*(ID For compatibility reasons a set .Va smtp is used in preference of .Va mta . . .Mx Va smtp-auth .It Va smtp-auth-USER@HOST , smtp-auth-HOST , smtp-auth \*(OP Variable chain that controls the SMTP .Va mta authentication method, possible values are .Ql none (\*(OU default), .Ql plain (\*(IN default), .Ql login , \*(IN .Ql oauthbearer (see .Sx FAQ entry .Sx "But, how about XOAUTH2 / OAUTHBEARER?" ) as well as \*(IN .Ql external and .Ql externanon for TLS secured connections which pass a client certificate via .Va tls-config-pairs . There may be the \*(OPal methods .Ql cram-md5 and .Ql gssapi . .Ql none and .Ql externanon do not need any user credentials, .Ql external and .Ql gssapi require a user name, and all other methods require a .Va user name and a .Va password . .Ql externanon solely builds upon the credentials passed via a client certificate, and is usually the way to go since tested servers do not actually follow RFC 4422 aka RFC 4954, and fail if additional credentials are passed. Also see .Va mta . Note that .Va smtp-auth-HOST is \*(IN. (\*(OU Requires .Va smtp-auth-password and .Va smtp-auth-user . Note for .Va smtp-auth-USER@HOST : may override dependent on sender address in the variable .Va from . ) . .It Va smtp-auth-password \*(OP\*(OU Sets the global fallback password for SMTP authentication. If the authentication method requires a password, but neither .Va smtp-auth-password nor a matching .Va smtp-auth-password-USER@HOST can be found, \*(UA will ask for a password on the user's terminal. . .It Va smtp-auth-password-USER@HOST \*(OU Overrides .Va smtp-auth-password for specific values of sender addresses, dependent upon the variable .Va from . . .It Va smtp-auth-user \*(OP\*(OU Sets the global fallback user name for SMTP authentication. If the authentication method requires a user name, but neither .Va smtp-auth-user nor a matching .Va smtp-auth-user-USER@HOST can be found, \*(UA will ask for a user name on the user's terminal. . .It Va smtp-auth-user-USER@HOST \*(OU Overrides .Va smtp-auth-user for specific values of sender addresses, dependent upon the variable .Va from . . .Mx .It Va smtp-hostname \*(OP\*(IN Normally \*(UA uses the variable .Va from to derive the necessary .Ql USER@HOST information in order to issue a .Ql MAIL FROM:<> SMTP .Va mta command. Setting .Va smtp-hostname can be used to use the .Ql USER from the SMTP account .Pf ( Va mta or the .Va user variable chain) and the .Ql HOST from the content of this variable (or, if that is the empty string, .Va hostname or the local hostname as a last resort). This often allows using an address that is itself valid but hosted by a provider other than which (in .Va from ) is about to send the message. Setting this variable also influences generated .Ql Message-ID: and .Ql Content-ID: header fields. If the \*(OPal IDNA support is available (see .Va idna-disable ) variable assignment is aborted when a necessary conversion fails. . .Mx Va smtp-use-starttls .It Va smtp-use-starttls-USER@HOST , smtp-use-starttls-HOST , smtp-use-starttls \*(BO\*(OP Causes \*(UA to issue a .Ql STARTTLS command to make an SMTP .Va mta session TLS encrypted, i.e., to enable transport layer security. . .Mx .It Va socket-connect-timeout \*(OP A positive number that defines the timeout to wait for establishing a socket connection before forcing .Va ^ERR Ns -TIMEDOUT . . .Mx Va socks-proxy .It Va socks-proxy-USER@HOST , socks-proxy-HOST , socks-proxy \*(OP If this is set to the hostname (SOCKS URL) of a SOCKS5 server then \*(UA will proxy all of its network activities through it. This can be used to proxy SMTP, POP3 etc. network traffic through the Tor anonymizer, for example. The following would create a local SOCKS proxy on port 10000 that forwards to the machine .Ql HOST , and from which the network traffic is actually instantiated: .Bd -literal -offset indent # Create local proxy server in terminal 1 forwarding to HOST $ ssh -D 10000 USER@HOST # Then, start a client that uses it in terminal 2 $ \*(uA -Ssocks-proxy-USER@HOST=localhost:10000 .Ed . .Mx .It Va spam-interface \*(OP In order to use any of the spam-related commands (like, e.g., .Ic spamrate ) the desired spam interface must be defined by setting this variable. Please refer to the manual section .Sx "Handling spam" for the complete picture of spam handling in \*(UA. All or none of the following interfaces may be available: . .Bl -tag -width ".It Ql _ilte_" .It Ql spamc Interaction with .Xr spamc 1 from the .Xr spamassassin 1 .Pf ( Lk http://spamassassin.apache.org SpamAssassin ) suite. Different to the generic filter interface \*(UA will automatically add the correct arguments for a given command and has the necessary knowledge to parse the program's output. A default value for .Va spamc-command will have been compiled into the \*(UA binary if .Xr spamc 1 has been found in .Ev PATH during compilation. Shall it be necessary to define a specific connection type (rather than using a configuration file for that), the variable .Va spamc-arguments can be used as in, e.g., .Ql -d server.example.com -p 783 . It is also possible to specify a per-user configuration via .Va spamc-user . Note that this interface does not inspect the .Ql is-spam flag of a message for the command .Ic spamforget . . .It Ql filter generic spam filter support via freely configurable hooks. This interface is meant for programs like .Xr bogofilter 1 and requires according behaviour in respect to the hooks' exit status for at least the command .Ic spamrate .Pf ( Ql 0 meaning a message is spam, .Ql 1 for non-spam, .Ql 2 for unsure and any other return value indicating a hard error); since the hooks can include shell code snippets diverting behaviour can be intercepted as necessary. The hooks are .Va spamfilter-ham , spamfilter-noham , spamfilter-nospam , \ spamfilter-rate and .Va spamfilter-spam ; the manual section .Sx "Handling spam" contains examples for some programs. The process environment of the hooks will have the variable .Ev MAILX_FILENAME_GENERATED set. Note that spam score support for .Ic spamrate is not supported unless the \*(OPtional regular expression support is available and the .Va spamfilter-rate-scanscore variable is set. .El . . .Mx .It Va spam-maxsize \*(OP Messages that exceed this size will not be passed through to the configured .Va spam-interface . If unset or 0, the default of 420000 bytes is used. . .Mx .It Va spamc-command \*(OP The path to the .Xr spamc 1 program for the .Ql spamc .Va spam-interface . Note that the path is not expanded, but used .Dq as is . A fallback path will have been compiled into the \*(UA binary if the executable had been found during compilation. . .Mx .It Va spamc-arguments \*(OP Even though \*(UA deals with most arguments for the .Ql spamc .Va spam-interface automatically, it may at least sometimes be desirable to specify connection-related ones via this variable, e.g., .Ql -d server.example.com -p 783 . . .Mx .It Va spamc-user \*(OP Specify a username for per-user configuration files for the .Ql spamc .Va spam-interface . If this is set to the empty string then \*(UA will use the name of the current .Va user . . .Mx .Mx .Mx .Mx .Mx .It Va spamfilter-ham , spamfilter-noham , \ spamfilter-nospam , spamfilter-rate , spamfilter-spam \*(OP Command and argument hooks for the .Ql filter .Va spam-interface . The manual section .Sx "Handling spam" contains examples for some programs. . .Mx .It Va spamfilter-rate-scanscore \*(OP Because of the generic nature of the .Ql filter .Va spam-interface spam scores are not supported for it by default, but if the \*(OPnal regular expression support is available then setting this variable can be used to overcome this restriction. It is interpreted as follows: first a number (digits) is parsed that must be followed by a semicolon .Ql \&; and an extended regular expression. Then the latter is used to parse the first output line of the .Va spamfilter-rate hook, and, in case the evaluation is successful, the group that has been specified via the number is interpreted as a floating point scan score. . .It Va ssl-ca-dir-USER@HOST , ssl-ca-dir-HOST , ssl-ca-dir ,\ ssl-ca-file-USER@HOST , ssl-ca-file-HOST , ssl-ca-file \*(OB\*(OP Predecessors of .Va tls-ca-file , .Va tls-ca-dir . . .It Va ssl-ca-flags-USER@HOST , ssl-ca-flags-HOST , ssl-ca-flags \*(OB\*(OP Predecessor of .Va tls-ca-flags . . .It Va ssl-ca-no-defaults-USER@HOST , ssl-ca-no-defaults-HOST ,\ ssl-ca-no-defaults \*(OB\*(BO\*(OP Predecessor of .Va tls-ca-no-defaults . . .It Va ssl-cert-USER@HOST , ssl-cert-HOST , ssl-cert \*(OB\*(OP Please use the .Cd Certificate slot of .Va tls-config-pairs . . .It Va ssl-cipher-list-USER@HOST , ssl-cipher-list-HOST , ssl-cipher-list \*(OB\*(OP Please use the .Cd CipherString slot of .Va tls-config-pairs . . .It Va ssl-config-file \*(OB\*(OP Predecessor of .Va tls-config-file . . .It Va ssl-config-module-USER@HOST , ssl-config-module-HOST ,\ ssl-config-module \*(OB\*(OP Predecessor of .Va tls-config-module . . .It Va ssl-config-pairs-USER@HOST , ssl-config-pairs-HOST , ssl-config-pairs \*(OB\*(OP Predecessor of .Va tls-config-pairs . . .It Va ssl-crl-dir , ssl-crl-file \*(OB\*(OP Predecessors of .Va tls-crl-dir , .Va tls-crl-file . . .It Va ssl-curves-USER@HOST , ssl-curves-HOST , ssl-curves \*(OB\*(OP Please use the .Cd Curves slot of .Va tls-config-pairs . . .It Va ssl-features \*(OB\*(OP\*(RO Predecessor of .Va tls-features . . .It Va ssl-key-USER@HOST , ssl-key-HOST , ssl-key \*(OB\*(OP Please use the .Cd PrivateKey slot of .Va tls-config-pairs . . .It Va ssl-method-USER@HOST , ssl-method-HOST , ssl-method \*(OB\*(OP Please use the .Cd Protocol slot of .Va tls-config-pairs . . .It Va ssl-protocol-USER@HOST , ssl-protocol-HOST , ssl-protocol \*(OB\*(OP Please use the .Cd Protocol slot of .Va tls-config-pairs . . .It Va ssl-rand-file \*(OB\*(OP Predecessor of .Va tls-rand-file . . .It Va ssl-verify-USER@HOST , ssl-verify-HOST , ssl-verify \*(OB\*(OP Predecessor of .Va tls-verify . . .Mx .It Va stealthmua If only set without an assigned value, then this setting inhibits the generation of the .Ql Message-ID: , .Ql Content-ID: and .Ql User-Agent: header fields that include obvious references to \*(UA. There are two pitfalls associated with this: First, the message id of outgoing messages is not known anymore. Second, an expert may still use the remaining information in the header to track down the originating mail user agent. If set to the value .Ql noagent , then the mentioned .Ql Message-ID: and .Ql Content-ID: suppression does not occur. . .Mx .It Va system-mailrc \*(RO The compiled in path of the system wide initialization file one of the .Sx "Resource files" : .Pa \*(UR . . . .Mx .It Va termcap (\*(OP) This specifies a comma-separated list of .Lb libterminfo and/or .Lb libtermcap capabilities (see .Sx "On terminal control and line editor" , escape commas with reverse solidus) to be used to overwrite or define entries. .Sy Note this variable will only be queried once at program startup and can thus only be specified in resource files or on the command line. . .Pp String capabilities form .Ql cap=value pairs and are expected unless noted otherwise. Numerics have to be notated as .Ql cap#number where the number is expected in normal decimal notation. Finally, booleans do not have any value but indicate a true or false state simply by being defined or not; this indeed means that \*(UA does not support undefining an existing boolean. String capability values will undergo some expansions before use: for one notations like .Ql ^LETTER stand for .Ql control-LETTER , and for clarification purposes .Ql \eE can be used to specify .Ql escape (the control notation .Ql ^[ could lead to misreadings when a left bracket follows, which it does for the standard CSI sequence); finally three letter octal sequences, as in .Ql \e061 , are supported. To specify that a terminal supports 256-colours, and to define sequences that home the cursor and produce an audible bell, one might write: . .Bd -literal -offset indent ? set termcap='Co#256,home=\eE[H,bel=^G' .Ed . .Pp The following terminal capabilities are or may be meaningful for the operation of the built-in line editor or \*(UA in general: . .Pp .Bl -tag -compact -width ".It Cd yay" .It Cd am .Cd auto_right_margin : boolean which indicates if the right margin needs special treatment; the .Cd xenl capability is related, for more see .Ev COLUMNS . . .It Cd clear Ns \0or Cd cl .Cd clear_screen : clear the screen and home cursor. (Will be simulated via .Cd ho plus .Cd cd . ) . .\" mx_HAVE_COLOUR .It Cd colors Ns \0or Cd Co .Cd max_colors : numeric capability specifying the maximum number of colours. Note that \*(UA does not actually care about the terminal beside that, but always emits ANSI / ISO 6429 escape sequences. . .It Cd cr .Cd carriage_return : move to the first column in the current row. The default built-in fallback is .Ql \er . . .It Cd cub1 Ns \0or Cd le .Cd cursor_left : move the cursor left one space (non-destructively). The default built-in fallback is .Ql \eb . . .It Cd cuf1 Ns \0or Cd nd .Cd cursor_right : move the cursor right one space (non-destructively). The default built-in fallback is .Ql \eE[C , which is used by most terminals. Less often occur .Ql \eEC and .Ql \eEOC . . .It Cd ed Ns \0or Cd cd .Cd clr_eos : clear the screen. . .\" mx_HAVE_MLE .It Cd el Ns \0or Cd ce .Cd clr_eol : clear to the end of line. (Will be simulated via .Cd ch plus repetitions of space characters.) . .It Cd home Ns \0or Cd ho .Cd cursor_home : home cursor. . .It Cd hpa Ns \0or Cd ch .Cd column_address : move the cursor (to the given column parameter) in the current row. (Will be simulated via .Cd cr plus .Cd nd . ) . .\" mx_HAVE_TERMCAP .It Cd rmcup Ns \0or Cd te Ns \0/ Cd smcup Ns \0or Cd ti .Cd exit_ca_mode and .Cd enter_ca_mode , respectively: exit and enter the alternative screen ca-mode, effectively turning \*(UA into a fullscreen application. This must be enabled explicitly by setting .Va termcap-ca-mode . . .It Cd smkx Ns \0or Cd ks Ns \0/ Cd rmkx Ns \0or Cd ke .Cd keypad_xmit and .Cd keypad_local , respectively: enable and disable the keypad. This is always enabled if available, because it seems even keyboards without keypads generate other key codes for, e.g., cursor keys in that case, and only if enabled we see the codes that we are interested in. . .It Cd xenl Ns \0or Cd xn .Cd eat_newline_glitch : boolean which indicates whether a newline written in the last column of an .Cd auto_right_margin indicating terminal is ignored. With it the full terminal width is available even on autowrap terminals. .El . .Pp Many more capabilities which describe key-sequences are documented for .Ic bind . . . .Mx .It Va termcap-ca-mode \*(OP Allow usage of the .Cd exit_ca_mode and .Cd enter_ca_mode terminal capabilities, see .Va termcap . .Sy Note this variable will only be queried once at program startup and can thus only be specified in resource files or on the command line. . .Mx .It Va termcap-disable \*(OP Disable any interaction with a terminal control library. If set only some generic fallback built-ins and possibly the content of .Va termcap describe the terminal to \*(UA. .Sy Note this variable will only be queried once at program startup and can thus only be specified in resource files or on the command line. . .Mx Va tls-ca-file .Mx Va tls-ca-dir .It Va tls-ca-dir-USER@HOST , tls-ca-dir-HOST , tls-ca-dir ,\ tls-ca-file-USER@HOST , tls-ca-file-HOST , tls-ca-file \*(OP Directory and file, respectively, for pools of trusted CA certificates in PEM (Privacy Enhanced Mail) format, for the purpose of verification of TLS server certificates. Concurrent use is possible, the file is loaded once needed first, the directory lookup is performed anew as a last resort whenever necessary. The CA certificate pool built into the TLS library can be disabled via .Va tls-ca-no-defaults , further fine-tuning is possible via .Va tls-ca-flags . Note the directory search variant requires the certificate files to adhere special filename conventions, please see .Xr SSL_CTX_load_verify_locations 3 and .Xr verify 1 (or .Xr c_rehash 1 ) . . . .Mx Va tls-ca-flags .It Va tls-ca-flags-USER@HOST , tls-ca-flags-HOST , tls-ca-flags \*(OP Can be used to fine-tune behaviour of the X509 CA certificate storage, and the certificate verification that is used (also see .Va tls-verify ) . The value is expected to consist of a comma-separated list of configuration directives, with any intervening whitespace being ignored. The directives directly map to flags that can be passed to .Xr X509_STORE_set_flags 3 , which are usually defined in a file .Pa openssl/x509_vfy.h , and the availability of which depends on the used TLS library version: a directive without mapping is ignored (error log subject to .Va debug ) . Directives currently understood (case-insensitively) include: . .Pp .Bl -tag -compact -width ".It Cd BaNg" .It Cd no-alt-chains If the initial chain is not trusted, do not attempt to build an alternative chain. Setting this flag will make OpenSSL certificate verification match that of older OpenSSL versions, before automatic building and checking of alternative chains has been implemented; also see .Cd trusted-first . .It Cd no-check-time Do not check certificate/CRL validity against current time. .It Cd partial-chain By default partial, incomplete chains which cannot be verified up to the chain top, a self-signed root certificate, will not verify. With this flag set, a chain succeeds to verify if at least one signing certificate of the chain is in any of the configured trusted stores of CA certificates. The OpenSSL manual page .Xr SSL_CTX_load_verify_locations 3 gives some advise how to manage your own trusted store of CA certificates. .It Cd strict Disable workarounds for broken certificates. .It Cd trusted-first Try building a chain using issuers in the trusted store first to avoid problems with server-sent legacy intermediate certificates. Newer versions of OpenSSL support alternative chain checking and enable it by default, resulting in the same behaviour; also see .Cd no-alt-chains . .El . . .Mx Va tls-ca-no-defaults .It Va tls-ca-no-defaults-USER@HOST , tls-ca-no-defaults-HOST ,\ tls-ca-no-defaults \*(BO\*(OP Do not load the default CA locations that are built into the used to TLS library to verify TLS server certificates. . .Mx .It Va tls-config-file \*(OP If this variable is set .Xr CONF_modules_load_file 3 (if announced via .Ql +modules-load-file in .Va tls-features ) is used to allow resource file based configuration of the TLS library. This happens once the library is used first, which may also be early during startup (logged with .Va verbose ) ! If a non-empty value is given then the given file, after performing .Sx "Filename transformations" , will be used instead of the TLS libraries global default, and it is an error if the file cannot be loaded. The application name will always be passed as .Ql \*(uA . Some TLS libraries support application-specific configuration via resource files loaded like this, please see .Va tls-config-module . . .Mx Va tls-config-module .It Va tls-config-module-USER@HOST , tls-config-module-HOST ,\ tls-config-module \*(OP If file based application-specific configuration via .Va tls-config-file is available, announced as .Ql +ctx-config by .Va tls-features , indicating availability of .Xr SSL_CTX_config 3 , then, it becomes possible to use a central TLS configuration file for all programs, including \*(uA, e.g.: .Bd -literal -offset indent # Register a configuration section for \*(uA \*(uA = mailx_master # The top configuration section creates a relation # in between dynamic SSL configuration and an actual # program specific configuration section [mailx_master] ssl_conf = mailx_tls_config # Well that actual program specific configuration section # now can map individual tls-config-module names to sections, # e.g., tls-config-module=account_xy [mailx_tls_config] account_xy = mailx_account_xy account_yz = mailx_account_yz [mailx_account_xy] MinProtocol = TLSv1.2 Curves=P-521 [mailx_account_yz] CipherString = TLSv1.2:!aNULL:!eNULL: MinProtocol = TLSv1.1 Options = Bugs .Ed . . .Mx Va tls-config-pairs .It Va tls-config-pairs-USER@HOST , tls-config-pairs-HOST , tls-config-pairs \*(OP The value of this variable chain will be interpreted as a comma-separated list of directive/value pairs. Directives and values need to be separated by equals signs .Ql = , any whitespace surrounding pair members is removed. Keys are (usually) case-insensitive. Different to when placing these pairs in a .Va tls-config-module section of a .Va tls-config-file , commas .Ql \&, need to be escaped with a reverse solidus .Ql \e when included in pairs; also different: if the equals sign .Ql = is preceded with an asterisk .Ql * .Sx "Filename transformations" will be performed on the value; it is an error if these fail. Unless proper support is announced by .Va tls-features .Pf ( Ql +conf-ctx ) only the keys below are supported, otherwise the pairs will be used directly as arguments to the function .Xr SSL_CONF_cmd 3 . . .Pp .Bl -tag -compact -width ".It Cd C_rtificate_" .It Cd Certificate Filename of a TLS client certificate (chain) required by some servers. Fallback support via .Xr SSL_CTX_use_certificate_chain_file 3 . .Sx "Filename transformations" are performed. .Cd PrivateKey will be set to the same value if not initialized explicitly. Some services support so-called .Ql external authentication if a TLS client certificate was successfully presented during connection establishment .Pf ( Dq connecting is authenticating ) . . .It Cd CipherString A list of ciphers for TLS connections, see .Xr ciphers 1 . By default no list of ciphers is set, resulting in a .Cd Protocol Ns - Ns specific list of ciphers (the protocol standards define lists of acceptable ciphers; possibly cramped by the used TLS library). Fallback support via .Xr SSL_CTX_set_cipher_list 3 . . .It Cd Ciphersuites A list of ciphers used for TLSv1.3 connections, see .Xr ciphers 1 . These will be joined onto the list of ciphers from .Cd CipherString . Available if .Va tls-features announces .Ql +ctx-set-ciphersuites , as necessary via .Xr SSL_CTX_set_ciphersuites 3 . . .It Cd Curves A list of supported elliptic curves, if applicable. By default no curves are set. Fallback support via .Xr SSL_CTX_set1_curves_list 3 , if available. . .It Cd MaxProtocol , MinProtocol The maximum and minimum supported TLS versions, respectively. Available if .Va tls-features announces .Ql +ctx-set-maxmin-proto , as necessary via .Xr SSL_CTX_set_max_proto_version 3 and .Xr SSL_CTX_set_min_proto_version 3 ; these fallbacks use an internal parser which understands the strings .Ql SSLv3 , .Ql TLSv1 , .Ql TLSv1.1 , .Ql TLSv1.2 , .Ql TLSv1.3 , and the special value .Ql None , which disables the given limit. . .It Cd Options Various flags to set. Fallback via .Xr SSL_CTX_set_options 3 , in which case any other value but (exactly) .Ql Bugs results in an error. . .It Cd PrivateKey Filename of the private key in PEM format of a TLS client certificate. If unset, the value of .Cd Certificate is used. .Sx "Filename transformations" are performed. Fallback via .Xr SSL_CTX_use_PrivateKey_file 3 . . .It Cd Protocol The used TLS protocol. If .Va tls-features announces .Ql +conf-ctx or .Ql ctx-set-maxmin-proto then using .Cd MaxProtocol and .Cd MinProtocol is preferable. Fallback is .Xr SSL_CTX_set_options 3 , driven via an internal parser which understands the strings .Ql SSLv3 , .Ql TLSv1 , .Ql TLSv1.1 , .Ql TLSv1.2 , .Ql TLSv1.3 , and the special value .Ql ALL . Multiple protocols may be given as a comma-separated list, any whitespace is ignored, an optional plus sign .Ql + prefix enables, a hyphen-minus .Ql - prefix disables a protocol, so that .Ql -ALL, TLSv1.2 enables only the TLSv1.2 protocol. .El . . .Mx .Mx .It Va tls-crl-dir , tls-crl-file \*(OP Specify a directory / a file, respectively, that contains a CRL in PEM format to use when verifying TLS server certificates. . .Mx .It Va tls-features \*(OP\*(RO This expands to a comma-separated list of the TLS library identity and optional features. Currently supported identities are .Ql libressl (LibreSSL) , .Ql libssl-0x10100 (OpenSSL v1.1.x series) and .Ql libssl-0x10000 (elder OpenSSL series, other clones). Optional features are preceded with a plus sign .Ql + when available, and with a hyphen-minus .Ql - otherwise. .Pp Currently known features are .Ql conf-ctx .Pf ( Va tls-config-pairs ) , .Ql ctx-config .Pf ( Va tls-config-module ) , .Ql ctx-set-ciphersuites .Pf ( Cd Ciphersuites slot of .Va tls-config-pairs ) , .Ql ctx-set-maxmin-proto .Pf ( Va tls-config-pairs ) , .Ql modules-load-file .Pf ( Va tls-config-file ) , and .Ql tls-rand-file .Pf ( Va tls-rand-file ) . . .Mx Va tls-fingerprint .It Va tls-fingerprint-USER@HOST , tls-fingerprint-HOST , tls-fingerprint \*(OP It is possible to replace the verification of the connection peer certificate against the entire local pool of CAs (for more see .Sx "Encrypted network communication" ) with the comparison against a precalculated certificate message digest, the so-called fingerprint, to be specified as the used .Va tls-fingerprint-digest . This fingerprint can be calculated with, e.g., .Ql Ic tls Ns \:\0\:fingerprint HOST . . .Mx Va tls-fingerprint-digest .It Va tls-fingerprint-digest-USER@HOST , tls-fingerprint-digest-HOST , \ tls-fingerprint-digest \*(OP The message digest to be used when creating TLS certificate fingerprints, the defaults, if available, in test order, being .Ql BLAKE2s256 , .Ql SHA256 . For the complete list of digest algorithms refer to .Va smime-sign-digest . . .Mx .It Va tls-rand-file \*(OP If .Va tls-features announces .Ql +tls-rand-file then this will be queried to find a file with random entropy data which can be used to seed the P(seudo)R(andom)N(umber)G(enerator), see .Xr RAND_load_file 3 . The default filename .Pf ( Xr RAND_file_name 3 , normally .Pa ~/.rnd ) will be used if this variable is not set or empty, or if the .Sx "Filename transformations" fail. Shall seeding the PRNG have been successful, .Xr RAND_write_file 3 will be called to update the entropy. Remarks: libraries which do not announce this feature seed the PRNG by other means. . .Mx Va tls-verify .It Va tls-verify-USER@HOST , tls-verify-HOST , tls-verify \*(OP Variable chain that sets the action to be performed if an error occurs during TLS server certificate validation against the specified or default trust stores .Va tls-ca-dir , .Va tls-ca-file , or the TLS library built-in defaults (unless usage disallowed via .Va tls-ca-no-defaults ) , and as fine-tuned via .Va tls-ca-flags . Valid (case-insensitive) values are .Ql strict (fail and close connection immediately), .Ql ask (ask whether to continue on standard input), .Ql warn (show a warning and continue), .Ql ignore (do not perform validation). The default is .Ql ask . .Mx .It Va toplines If defined, gives the number of lines of a message to be displayed with the command .Ic top ; if unset, the first five lines are printed, if set to 0 the variable .Va screen is inspected. If the value is negative then its absolute value will be used for unsigned right shifting (see .Ic vexpr ) the .Va screen height. . .Mx .It Va topsqueeze \*(BO If set then the .Ic top command series will strip adjacent empty lines and quotations. . .Mx .It Va ttycharset The character set of the terminal \*(UA operates on, and the one and only supported character set that \*(UA can use if no character set conversion capabilities have been compiled into it, in which case it defaults to ISO-8859-1. Otherwise it defaults to UTF-8. Sufficient locale support provided the default will be preferably deduced from the locale environment if that is set (e.g., .Ev LC_CTYPE , see there for more); runtime locale changes will be reflected by .Va \&\&ttycharset except during the program startup phase and if .Fl S had been used to freeze the given value. Refer to the section .Sx "Character sets" for the complete picture about character sets. . .Mx .It Va typescript-mode \*(BO A special multiplex variable that disables all variables and settings which result in behaviour that interferes with running \*(UA in .Xr script 1 , e.g., it sets .Va colour-disable , .Va line-editor-disable and (before startup completed only) .Va termcap-disable . Unsetting it does not restore the former state of the covered settings. . .Mx .It Va umask For a safe-by-default policy the process file mode creation mask .Xr umask 2 will be set to .Ql 0077 on program startup after the resource files have been loaded, and unless this variable is set. By assigning this an empty value the active setting will not be changed, otherwise the given value will be made the new file mode creation mask. Child processes inherit the file mode creation mask of their parent. . .Mx Va user .It Va user-HOST , user \*(IN Variable chain that sets a global fallback user name, used in case none has been given in the protocol and account-specific URL. This variable defaults to the name of the user who runs \*(UA. . .Mx .It Va v15-compat Enable upward compatibility with \*(UA version 15.0 in respect to which configuration options are available and how they are handled. If set to a non-empty value the command modifier .Cm wysh is implied and thus enforces .Sx "Shell-style argument quoting" over .Sx "Old-style argument quoting" for all commands which support both. This manual uses \*(IN and \*(OU to refer to the new and the old way of doing things, respectively. . .Mx .It Va verbose \*(BO This setting, also controllable via the command line option .Fl v , causes \*(UA to be more verbose, e.g., it will display obsoletion warnings and TLS certificate chains. Even though marked \*(BO this option may be set up to three times in order to increase the level of verbosity, higher levels show details of the actual message delivery, protocol conversations and even variable lookups; a single .Ic unset Va verbose is sufficient to disable verbosity as such. . .Mx .Mx .Mx .Mx .Mx .Mx .It Va version , version-date , \ version-hexnum , version-major , version-minor , version-update \*(RO \*(UA version information: the first variable is a string with the complete version identification, the second the release date in ISO 8601 notation without time. The third is a 32-bit hexadecimal number with the upper 8 bits storing the major, followed by the minor and update version numbers which occupy 12 bits each. The latter three variables contain only decimal digits: the major, minor and update version numbers. The output of the command .Ic version will include this information. . .Mx .It Va writebackedited If this variable is set messages modified using the .Ic edit or .Ic visual commands are written back to the current folder when it is quit; it is only honoured for writable folders in MBOX format, though. Note that the editor will be pointed to the raw message content in that case, i.e., neither MIME decoding nor decryption will have been performed, and proper .Va mbox-rfc4155 .Ql From_ quoting of newly added or edited content is also left as an exercise to the user. .El .\" }}} (Variables) . .\" }}} (INTERNAL VARIABLES) . . .\" .Sh ENVIRONMENT {{{ .Sh ENVIRONMENT . The term .Dq environment variable should be considered an indication that these variables are either standardized as vivid parts of process environments, or that they are commonly found in there. The process environment is inherited from the .Xr sh 1 once \*(UA is started, and unless otherwise explicitly noted handling of the following variables transparently integrates into that of the .Sx "INTERNAL VARIABLES" from \*(UA's point of view. This means that, e.g., they can be managed via .Ic set and .Ic unset , causing automatic program environment updates (to be inherited by newly created child processes). . .Pp In order to integrate other environment variables equally they need to be imported (linked) with the command .Ic environ . This command can also be used to set and unset non-integrated environment variables from scratch, sufficient system support provided. The following example, applicable to a POSIX shell, sets the .Ev COLUMNS environment variable for \*(UA only, and beforehand exports the .Ev EDITOR in order to affect any further processing in the running shell: . .Bd -literal -offset indent $ EDITOR="vim -u ${HOME}/.vimrc" $ export EDITOR $ COLUMNS=80 \*(uA -R .Ed . .Bl -tag -width ".It Ev BaNg" .Mx .It Ev COLUMNS The user's preferred width in column positions for the terminal screen. Queried and used once on program startup in interactive or batch .Pf ( Fl \&# ) mode, actively managed for child processes and the MLE (see .Sx "On terminal control and line editor" ) in interactive mode thereafter. Non-interactive mode always uses, and the fallback default is a compile-time constant, by default 80 columns. If in batch mode .Ev \&\&COLUMNS and .Ev LINES are both set but not both are usable (empty, not a number, or 0) at program startup, then the real terminal screen size will be (tried to be) determined once. (Normally the .Xr sh 1 manages these variables, and unsets them for pipe specifications etc.) . .Mx .It Ev DEAD The name of the (mailbox) .Ic file to use for saving aborted messages if .Va save is set; this defaults to .Pa \*(VD . If the variable .Va debug is set no output will be generated, otherwise the contents of the file will be replaced. . .Mx .It Ev EDITOR Pathname of the text editor to use for the .Ic edit command and .Ic ~e .Pf (see\0 Sx "COMMAND ESCAPES" ) ; .Ev VISUAL is used for a more display oriented editor. . .Mx .It Ev HOME The user's home directory. This variable is only used when it resides in the process environment. The calling user's home directory will be used instead if this directory does not exist, is not accessible or cannot be read; it will always be used for the root user. (No test for being writable is performed to allow usage by non-privileged users within read-only jails, but dependent on the variable settings this directory is a default write target, e.g., for .Ev DEAD , .Ev MBOX and more.) . .Mx .Mx .Mx .It Ev LC_ALL , LC_CTYPE , LANG \*(OP The (names in lookup order of the) .Xr locale 7 (and / or see .Xr setlocale 3 ) which indicates the used .Sx "Character sets" . Runtime changes trigger automatic updates of the entire locale system, which includes updating .Va ttycharset (except during startup if the variable has been frozen via .Fl S ) . . .Mx .It Ev LINES The user's preferred number of lines for the terminal screen. The behaviour is as described for .Ev COLUMNS , yet the compile-time constant used in non-interactive mode and as a fallback defaults to 24 (lines). . .Mx .It Ev LISTER Pathname of the directory lister to use in the .Ic folders command when operating on local mailboxes. Default is .Xr ls 1 (path search through .Ev SHELL ) . . .Mx .It Ev LOGNAME Upon startup \*(UA will actively ensure that this variable refers to the name of the user who runs \*(UA, in order to be able to pass a verified name to any newly created child process. . .Mx .It Ev MAIL Is used as the user's .Mx -sx .Sx "primary system mailbox" unless .Va inbox is set. This is assumed to be an absolute pathname. If this environmental fallback is also not set, a built-in compile-time default is used. . .Mx .It Ev MAILCAPS \*(OP Overrides the default path search for .Sx "The Mailcap files" , which is defined in the standard RFC 1524 as .Ql ~/.mailcap:\:/etc/mailcap:\:/usr/etc/mailcap:\:/usr/local/etc/mailcap . .\" TODO we should have a mailcaps-default virtual RDONLY option! (\*(UA makes it a configuration option, however.) Note this is not a search path, but a path search. . .Mx .It Ev MAILRC Is used as a startup file instead of .Pa \*(ur if set. In order to avoid side-effects from configuration files scripts should either set this variable to .Pa /dev/null or the .Fl \&: command line option should be used. . .Mx .It Ev MAILX_NO_SYSTEM_RC If this variable is set then reading of .Pa \*(UR (aka\& .Va system-mailrc ) at startup is inhibited, i.e., the same effect is achieved as if \*(UA had been started up with the option .Fl \&: (and according argument) or .Fl n . This variable is only used when it resides in the process environment. . .Mx .It Ev MBOX The name of the user's .Mx -sx .Sx "secondary mailbox" file. A logical subset of the special .Sx "Filename transformations" (also see .Ic file ) are supported. The default is .Pa \*(VM . Traditionally this MBOX is used as the file to save messages from the .Mx -sx .Sx "primary system mailbox" that have been read. Also see .Sx "Message states" . . .Mx .It Ev NETRC \*(IN\*(OP This variable overrides the default location of the user's .Pa \*(VN file. . .Mx .It Ev PAGER Pathname of the program to use for backing the command .Ic more , and when the .Va crt variable enforces usage of a pager for output. The default paginator is .Xr more 1 (path search through .Ev SHELL ) . .Pp \*(UA inspects the contents of this variable: if its contains the string .Dq less then a non-existing environment variable .Ev LESS will be set to .Ql Ri , likewise for .Dq lv .Ev LV will optionally be set to .Ql -c . Alse see .Va colour-pager . . .Mx .It Ev PATH A colon-separated list of directories that is searched by the shell when looking for commands, e.g., .Ql /bin:/usr/bin:/usr/local/bin . . .Mx .It Ev POSIXLY_CORRECT This variable is automatically looked for upon startup, see .Va posix for more. . .Mx .It Ev SHELL The shell to use for the commands .Ic \&! , .Ic shell , the .Ic ~! .Sx "COMMAND ESCAPES" and when starting subprocesses. A default shell is used if this environment variable is not defined. . .Mx .It Ev SOURCE_DATE_EPOCH Specifies a time in seconds since the Unix epoch (1970-01-01) to be used in place of the current time. This variable is looked up upon program startup, and its existence will switch \*(UA to a reproducible mode .Pf ( Lk https://reproducible-builds.org ) which uses deterministic random numbers, a special fixated pseudo .Ev LOGNAME and more. This operation mode is used for development and by software packagers. \*(ID Currently an invalid setting is only ignored, rather than causing a program abortion. .Pp .Dl $ SOURCE_DATE_EPOCH=`date +%s` \*(uA . .Mx .It Ev TERM \*(OP The terminal type for which output is to be prepared. For extended colour and font control please refer to .Sx "Coloured display" , and for terminal management in general to .Sx "On terminal control and line editor" . . .Mx .It Ev TMPDIR Except for the root user this variable defines the directory for temporary files to be used instead of .Pa \*(VT (or the given compile-time constant) if set, existent, accessible as well as read- and writable. This variable is only used when it resides in the process environment, but \*(UA will ensure at startup that this environment variable is updated to contain a usable temporary directory. . .Mx .It Ev USER Identical to .Ev LOGNAME (see there), but this variable is not standardized, should therefore not be used, and is only corrected if already set. . .Mx .It Ev VISUAL Pathname of the text editor to use for the .Ic visual command and .Ic ~v .Pf (see\0 Sx "COMMAND ESCAPES" ) ; .Ev EDITOR is used for a less display oriented editor. .El . .\" }}} . . .\" .Sh FILES {{{ .Sh FILES . .\" file list {{{ .Bl -tag -width ".It Pa BaNg" .It Pa \*(ur User-specific file giving initial commands, one of the .Sx "Resource files" . The actual value is read from .Va MAILRC . . .It Pa \*(UR System wide initialization file, one of the .Sx "Resource files" . The actual value is read from .Va system-mailrc . . .Mx .It Pa ~/.mailcap \*(OP Personal MIME type handler definition file, see .Sx "The Mailcap files" . This location is part of the RFC 1524 standard search path, which is a configuration option and can be overridden via .Ev MAILCAPS . . .Mx .It Pa /etc/mailcap \*(OP System wide MIME type handler definition file, see .Sx "The Mailcap files" . This location is part of the RFC 1524 standard search path, which is a configuration option and can be overridden via . .Mx .It Pa \*(VM The default value for .Ev MBOX . . .Mx .It Pa \*(vU Personal MIME types, see .Sx "The mime.types files" . . .Mx .It Pa \*(vS System wide MIME types, see .Sx "The mime.types files" . . .Mx .It Pa \*(VN \*(IN\*(OP The default location of the user's .Pa .netrc file \(en the section .Sx "The .netrc file" documents the file format. The actually used path can be overridden via .Ev NETRC . . .Mx .It Pa /dev/null The data sink .Xr null 4 . .El .\" }}} . .\" .Ss "Resource files" {{{ .Ss "Resource files" . Upon startup \*(UA reads in several resource files, in order: . .Bl -tag -width ".It Pa BaNg" .Mx .It Pa \*(UR System wide initialization file .Pf ( Va system-mailrc ) . Reading of this file can be suppressed, either by using the .Fl \&: (and according argument) or .Fl n command line options, or by setting the .Sx ENVIRONMENT variable .Ev MAILX_NO_SYSTEM_RC . . .Mx .It Pa \*(ur File giving initial commands. A different file can be chosen by setting the .Sx ENVIRONMENT variable .Ev MAILRC . Reading of this file can be suppressed with the .Fl \&: command line option. . .It Va mailx-extra-rc Defines a startup file to be read after all other resource files. It can be used to specify settings that are not understood by other .Xr mailx 1 implementations, for example. This variable is only honoured when defined in a resource file, e.g., it is one of the .Sx "INTERNAL VARIABLES" . .El . .Pp The content of these files is interpreted as follows: . .Pp .Bl -bullet -compact .It The whitespace characters space, tabulator and newline, as well as those defined by the variable .Va ifs , are removed from the beginning and end of input lines. .It Empty lines are ignored. .It Any other line is interpreted as a command. It may be spread over multiple input lines if the newline character is .Dq escaped by placing a reverse solidus character .Ql \e as the last character of the line; whereas any leading whitespace of follow lines is ignored, trailing whitespace before a escaped newline remains in the input. .It If the line (content) starts with the number sign .Ql # then it is a comment-command and also ignored. (The comment-command is a real command, which does nothing, and therefore the usual follow lines mechanism applies!) .El . .Pp Unless \*(UA is about to enter interactive mode syntax errors that occur while loading these files are treated as errors and cause program exit. More files with syntactically equal content can be .Ic source Ns ed . The following, saved in a file, would be an examplary content: . .Bd -literal -offset indent # This line is a comment command. And y\e es, it is really continued here. set debug \e verbose set editheaders .Ed .\" }}} . .\" .Ss "The mime.types files" {{{ .Ss "The mime.types files" . As stated in .Sx "HTML mail and MIME attachments" \*(UA needs to learn about MIME (Multipurpose Internet Mail Extensions) media types in order to classify message and attachment content. One source for them are .Pa mime.types files, the loading of which can be controlled by setting the variable .Va mimetypes-load-control . Another is the command .Ic mimetype , which also offers access to \*(UAs MIME type cache. .Pa mime.types files have the following syntax: . .Bd -literal -offset indent type/subtype extension [extension ...] # E.g.: text/html html htm .Ed . .Pp where .Ql type/subtype define the MIME media type, as standardized in RFC 2046: .Ql type is used to declare the general type of data, while the .Ql subtype specifies a specific format for that type of data. One or multiple filename .Ql extension Ns s, separated by whitespace, can be bound to the media type format. Comments may be introduced anywhere on a line with a number sign .Ql # , causing the remaining line to be discarded. . \*(UA also supports an extended, non-portable syntax in especially crafted files, which can be loaded via the alternative value syntax of .Va mimetypes-load-control , and prepends an optional .Ql type-marker : . .Pp .Dl [type-marker ]type/subtype extension [extension ...] . .Pp The following type markers are supported: . .Pp .Bl -tag -compact -width ".It Ar _n_u" .It Ar \&? Treat message parts with this content as plain text. .It Ar ?t The same as plain .Ar \&? . .It Ar ?h Treat message parts with this content as HTML tagsoup. If the \*(OPal HTML-tagsoup-to-text converter is not available treat the content as plain text instead. .It Ar ?H Likewise .Ar ?h , but instead of falling back to plain text require an explicit content handler to be defined. .It Ar ?q If no handler can be found a text message is displayed which says so. This can be annoying, for example signatures serve a contextual purpose, their content is of no use by itself. This marker will avoid displaying the text message. .El . .Pp Further reading: for sending messages: .Ic mimetype , .Va mime-allow-text-controls , .Va mimetypes-load-control . For reading etc. messages: .Sx "HTML mail and MIME attachments" , .Sx "The Mailcap files" , .Ic mimetype , .Va mime-counter-evidence , .Va mimetypes-load-control , .Va pipe-TYPE/SUBTYPE , .Va pipe-EXTENSION . .\" }}} . .\" .Ss "The Mailcap files" {{{ .Ss "The Mailcap files" . .\" TODO MAILCAP DISABLED .Sy This feature is not available in v14.9.0, sorry! RFC 1524 defines a .Dq User Agent Configuration Mechanism which \*(UA \*(OPally supports (see .Sx "HTML mail and MIME attachments" ) . It defines a file format to be used to inform mail user agent programs about the locally-installed facilities for handling various data formats, i.e., about commands and how they can be used to display, edit et cetera MIME part contents, as well as a default path search that includes multiple possible locations of .Dq mailcap files and the .Ev MAILCAPS environment variable that can be used to overwrite that (repeating here that it is not a search path, but instead a path search specification). Any existing files will be loaded in sequence, appending any content to the list of MIME type handler directives. . .Pp .Dq Mailcap files consist of a set of newline separated entries. Comment lines start with a number sign .Ql # (in the first column!) and are ignored. Empty lines are also ignored. All other lines form individual entries that must adhere to the syntax described below. To extend a single entry (not comment) its line can be continued on follow lines if newline characters are .Dq escaped by preceding them with the reverse solidus character .Ql \e . The standard does not specify how leading whitespace of follow lines is to be treated, therefore \*(UA retains it. . .Pp .Dq Mailcap entries consist of a number of semicolon .Ql \&; separated fields, and the reverse solidus .Ql \e character can be used to escape any following character including semicolon and itself. The first two fields are mandatory and must occur in the specified order, the remaining fields are optional and may appear in any order. Leading and trailing whitespace of content is ignored (removed). . .Pp The first field defines the MIME .Ql TYPE/SUBTYPE the entry is about to handle (case-insensitively, and no reverse solidus escaping is possible in this field). If the subtype is specified as an asterisk .Ql * the entry is meant to match all subtypes of the named type, e.g., .Ql audio/* would match any audio type. The second field defines the shell command which shall be used to .Dq display MIME parts of the given type; it is implicitly called the .Cd view command. . .Pp For data .Dq consuming shell commands message (MIME part) data is passed via standard input unless the given shell command includes one or more instances of the (unquoted) string .Ql %s , in which case these instances will be replaced with a temporary filename and the data will have been stored in the file that is being pointed to. Likewise, for data .Dq producing shell commands data is assumed to be generated on standard output unless the given command includes (one ore multiple) .Ql %s . In any case any given .Ql %s format is replaced with a(n already) properly quoted filename. Note that when a command makes use of a temporary file via .Ql %s then \*(UA will remove it again, as if the .Cd x-mailx-tmpfile , .Cd x-mailx-tmpfile-fill and .Cd x-mailx-tmpfile-unlink flags had been set; see below for more. . .Pp The optional fields either define a shell command or an attribute (flag) value, the latter being a single word and the former being a keyword naming the field followed by an equals sign .Ql = succeeded by a shell command, and as usual for any .Dq Mailcap content any whitespace surrounding the equals sign will be removed, too. Optional fields include the following: . . .Bl -tag -width ".It Cd BaNg" .It Cd compose A program that can be used to compose a new body or body part in the given format. (Currently unused.) . .It Cd composetyped Similar to the .Cd compose field, but is to be used when the composing program needs to specify the .Ql Content-type: header field to be applied to the composed data. (Currently unused.) . .It Cd edit A program that can be used to edit a body or body part in the given format. (Currently unused.) . .It Cd print A program that can be used to print a message or body part in the given format. (Currently unused.) . .It Cd test Specifies a program to be run to test some condition, e.g., the machine architecture, or the window system in use, to determine whether or not this mailcap entry applies. If the test fails, a subsequent mailcap entry should be sought; also see .Cd x-mailx-test-once . . .Mx .It Cd needsterminal This flag field indicates that the given shell command must be run on an interactive terminal. \*(UA will temporarily release the terminal to the given command in interactive mode, in non-interactive mode this entry will be entirely ignored; this flag implies .Cd x-mailx-noquote . . .Mx .It Cd copiousoutput A flag field which indicates that the output of the .Cd view command will be an extended stream of textual output that can be (re)integrated into \*(UA's normal visual display. It is mutually exclusive with .Cd needsterminal . . .It Cd textualnewlines A flag field which indicates that this type of data is line-oriented and that, if encoded in .Ql base64 , all newlines should be converted to canonical form (CRLF) before encoding, and will be in that form after decoding. (Currently unused.) . .It Cd nametemplate This field gives a filename format, in which .Ql %s will be replaced by a random string, the joined combination of which will be used as the filename denoted by .Ev MAILX_FILENAME_TEMPORARY . One could specify that a GIF file being passed to an image viewer should have a name ending in .Ql .gif by using .Ql nametemplate=%s.gif . Note that \*(UA ignores the name template unless that solely specifies a filename suffix that consists of (ASCII) alphabetic and numeric characters, the underscore and dot only. . .It Cd x11-bitmap Names a file, in X11 bitmap (xbm) format, which points to an appropriate icon to be used to visually denote the presence of this kind of data. This field is not used by \*(UA. . .It Cd description A textual description that describes this type of data. . .Mx .It Cd x-mailx-even-if-not-interactive An extension flag test field \(em by default handlers without .Cd copiousoutput are entirely ignored in non-interactive mode, but if this flag is set then their use will be considered. It is an error if this flag is set for commands that use the flag .Cd needsterminal . . .Mx .It Cd x-mailx-noquote An extension flag field that indicates that even a .Cd copiousoutput .Cd view command shall not be used to generate message quotes (as it would be by default). . .Mx .It Cd x-mailx-async Extension flag field that denotes that the given .Cd view command shall be executed asynchronously, without blocking \*(UA. Cannot be used in conjunction with .Cd needsterminal ; the standard output of the command will go to .Pa /dev/null . . .Mx .It Cd x-mailx-test-once Extension flag which denotes whether the given .Cd test command shall be evaluated once only and the (boolean) result be cached. This is handy if some global unchanging condition is to be queried, like .Dq running under the X Window System . . .Mx .It Cd x-mailx-tmpfile Extension flag field that requests creation of a zero-sized temporary file, the name of which is to be placed in the environment variable .Ev MAILX_FILENAME_TEMPORARY . It is an error to use this flag with commands that include a .Ql %s format. . .Mx .It Cd x-mailx-tmpfile-fill Normally the MIME part content is passed to the handler via standard input; if this flag is set then the data will instead be written into the implied .Cd x-mailx-tmpfile . In order to cause deletion of the temporary file you will have to set .Cd x-mailx-tmpfile-unlink explicitly! It is an error to use this flag with commands that include a .Ql %s format. . .Mx .It Cd x-mailx-tmpfile-unlink Extension flag field that requests that the temporary file shall be deleted automatically when the command loop is entered again at latest. (Do not use this for asynchronous handlers.) It is an error to use this flag with commands that include a .Ql %s format, or in conjunction with .Cd x-mailx-async , or without also setting .Cd x-mailx-tmpfile or .Cd x-mailx-tmpfile-fill . . .Mx .It Cd x-mailx-tmpfile-keep Using the string .Ql %s implies the three tmpfile related flags above, but if you want, e.g., .Cd x-mailx-async and deal with the temporary file yourself, you can add in this flag to forcefully ignore .Cd x-mailx-tmpfile-unlink . .El . . .Pp The standard includes the possibility to define any number of additional entry fields, prefixed by .Ql x- . Flag fields apply to the entire .Dq Mailcap entry \(em in some unusual cases, this may not be desirable, but differentiation can be accomplished via separate entries, taking advantage of the fact that subsequent entries are searched if an earlier one does not provide enough information. E.g., if a .Cd view command needs to specify the .Cd needsterminal flag, but the .Cd compose command shall not, the following will help out the latter (with enabled .Va debug or an increased .Va verbose level \*(UA will show information about handler evaluation): . .Bd -literal -offset indent application/postscript; ps-to-terminal %s; needsterminal application/postscript; ps-to-terminal %s; compose=idraw %s .Ed . .Pp In fields any occurrence of the format string .Ql %t will be replaced by the .Ql TYPE/SUBTYPE specification. Named parameters from the .Ql Content-type: field may be placed in the command execution line using .Ql %{ followed by the parameter name and a closing .Ql } character. The entire parameter should appear as a single command line argument, regardless of embedded spaces; thus: . .Bd -literal -offset indent # Message Content-type: multipart/mixed; boundary=42 # Mailcap file multipart/*; /usr/local/bin/showmulti \e %t %{boundary} ; composetyped = /usr/local/bin/makemulti # Executed shell command /usr/local/bin/showmulti multipart/mixed 42 .Ed . .Pp .\" TODO v15: Mailcap: %n,%F Note that \*(UA does not support handlers for multipart MIME parts as shown in this example (as of today). \*(UA does not support the additional formats .Ql %n and .Ql %F . An example file, also showing how to properly deal with the expansion of .Ql %s , which includes any quotes that are necessary to make it a valid shell argument by itself and thus will cause undesired behaviour when placed in additional user-provided quotes: . .Bd -literal -offset indent # Comment line text/richtext; richtext %s; copiousoutput text/x-perl; perl -cWT %s application/pdf; \e infile=%s\e; \e trap "rm -f ${infile}" EXIT\e; \e trap "exit 75" INT QUIT TERM\e; \e mupdf %s; \e x-mailx-async; x-mailx-tmpfile-keep application/*; echo "This is \e"%t\e" but \e is 50 \e% Greek to me" \e; < %s head -c 1024 | cat -vet; \e copiousoutput; x-mailx-noquote .Ed . .Pp Further reading: .Sx "HTML mail and MIME attachments" , .Sx "The mime.types files" , .Ic mimetype , .Ev MAILCAPS , .Va mime-counter-evidence , .Va pipe-TYPE/SUBTYPE , .Va pipe-EXTENSION . .\" }}} . .\" .Ss "The .netrc file" {{{ .Ss "The .netrc file" . The .Pa .netrc file contains user credentials for machine accounts. The default location .Pa \*(VN may be overridden by the .Ev NETRC environment variable. It is possible to load encrypted .Pa .netrc files by using an appropriate value in .Va netrc-pipe . . .Pp The file consists of space, tabulator or newline separated tokens. \*(UA implements a parser that supports a superset of the original BSD syntax, but users should nonetheless be aware of portability glitches of that file format, shall their .Pa .netrc be usable across multiple programs and platforms: . .Pp .Bl -bullet -compact .It BSD does not support single, but only double quotation marks, e.g., .Ql password="pass with spaces" . .It BSD (only?) supports escaping of single characters via a reverse solidus (e.g., a space can be escaped via .Ql \e\0 ) , in- as well as outside of a quoted string. .It BSD does not require a final quotation mark of the last user input token. .It The original BSD (Berknet) parser also supported a format which allowed tokens to be separated with commas \(en whereas at least Hewlett-Packard still seems to support this syntax, \*(UA does not! .It As a non-portable extension some widely-used programs support shell-style comments: if an input line starts, after any amount of whitespace, with a number sign .Ql # , then the rest of the line is ignored. .It Whereas other programs may require that the .Pa .netrc file is accessible by only the user if it contains a .Cd password token for any other .Cd login than .Dq anonymous , \*(UA will always require these strict permissions. .El . .Pp Of the following list of supported tokens \*(UA only uses (and caches) .Cd machine , .Cd login and .Cd password . At runtime the command .Ic netrc can be used to control \*(UA's .Pa .netrc cache. . .Bl -tag -width ".It Cd BaNg" .It Cd machine Ar name The hostname of the entries' machine, lowercase-normalized by \*(UA before use. Any further file content, until either end-of-file or the occurrence of another .Cd machine or a .Cd default first-class token is bound (only related) to the machine .Ar name . .Pp As an extension that should not be the cause of any worries \*(UA supports a single wildcard prefix for .Ar name : .Bd -literal -offset indent machine *.example.com login USER password PASS machine pop3.example.com login USER password PASS machine smtp.example.com login USER password PASS .Ed .Pp which would match .Ql xy.example.com as well as .Ql pop3.example.com , but neither .Ql example.com nor .Ql local.smtp.example.com . Note that in the example neither .Ql pop3.example.com nor .Ql smtp.example.com will be matched by the wildcard, since the exact matches take precedence (it is however faster to specify it the other way around). . .It Cd default This is the same as .Cd machine except that it is a fallback entry that is used shall none of the specified machines match; only one default token may be specified, and it must be the last first-class token. . .It Cd login Ar name The user name on the remote machine. . .It Cd password Ar string The user's password on the remote machine. . .It Cd account Ar string Supply an additional account password. This is merely for FTP purposes. . .It Cd macdef Ar name Define a macro. A macro is defined with the specified .Ar name ; it is formed from all lines beginning with the next line and continuing until a blank line is (consecutive newline characters are) encountered. (Note that .Cd macdef entries cannot be utilized by multiple machines, too, but must be defined following the .Ic machine they are intended to be used with.) If a macro named .Ar init exists, it is automatically run as the last step of the login process. This is merely for FTP purposes. .El .\" }}} . .\" }}} . . .\" .Sh EXAMPLES {{{ .Sh EXAMPLES . .\" .Ss "An example configuration" {{{ .Ss "An example configuration" . .Bd -literal -offset indent # This example assumes v15.0 compatibility mode set v15-compat # Request strict TLL transport layer security checks set tls-verify=strict # Where are the up-to-date TLS certificates? # (Since we manage up-to-date ones explicitly, do not use any, # possibly outdated, default certificates shipped with OpenSSL) #set tls-ca-dir=/etc/ssl/certs set tls-ca-file=/etc/ssl/certs/ca-certificates.crt set tls-ca-no-defaults #set tls-ca-flags=partial-chain wysh set smime-ca-file="${tls-ca-file}" \e smime-ca-no-defaults #smime-ca-flags="${tls-ca-flags}" # This could be outsourced to a central configuration file via # tls-config-file plus tls-config-module if the used library allows. # CipherString: explicitly define the list of ciphers, which may # improve security, especially with protocols older than TLS v1.2. # See ciphers(1). Possibly best to only use tls-config-pairs-HOST # (or -USER@HOST), as necessary, again.. # Note that TLSv1.3 uses Ciphersuites= instead, which will join # with CipherString (if protocols older than v1.3 are allowed) # Curves: especially with TLSv1.3 curves selection may be desired. # MinProtocol,MaxProtocol: do not use protocols older than TLS v1.2. # Change this only when the remote server does not support it: # maybe use chain support via tls-config-pairs-HOST / -USER@HOST # to define such explicit exceptions, then, e.g., # MinProtocol=TLSv1.1 if [ "$tls-features" =% +ctx-set-maxmin-proto ] wysh set tls-config-pairs='\e CipherString=TLSv1.2:!aNULL:!eNULL:@STRENGTH,\e Curves=P-521:P-384:P-256,\e MinProtocol=TLSv1.1' else wysh set tls-config-pairs='\e CipherString=TLSv1.2:!aNULL:!eNULL:@STRENGTH,\e Curves=P-521:P-384:P-256,\e Protocol=-ALL\e,+TLSv1.1 \e, +TLSv1.2\e, +TLSv1.3' endif # Essential setting: select allowed character sets set sendcharsets=utf-8,iso-8859-1 # A very kind option: when replying to a message, first try to # use the same encoding that the original poster used herself! set reply-in-same-charset # When replying, do not merge From: and To: of the original message # into To:. Instead old From: -> new To:, old To: -> merge Cc:. set recipients-in-cc # When sending messages, wait until the Mail-Transfer-Agent finishs. # Only like this you will be able to see errors reported through the # exit status of the MTA (including the built-in SMTP one)! set sendwait # Only use built-in MIME types, no mime.types(5) files set mimetypes-load-control # Default directory where we act in (relative to $HOME) set folder=mail # A leading "+" (often) means: under *folder* # *record* is used to save copies of sent messages set MBOX=+mbox.mbox DEAD=+dead.txt \e record=+sent.mbox record-files record-resent # Make "file mymbox" and "file myrec" go to.. shortcut mymbox %:+mbox.mbox myrec +sent.mbox # Not really optional, e.g., for S/MIME set from='Your Name ' # It may be necessary to set hostname and/or smtp-hostname # if the "SERVER" of mta and "domain" of from do not match. # The `urlencode' command can be used to encode USER and PASS set mta=(smtps?|submissions?)://[USER[:PASS]@]SERVER[:PORT] \e smtp-auth=login/plain... \e smtp-use-starttls # Never refuse to start into interactive mode, and more set emptystart \e colour-pager crt= \e followup-to followup-to-honour=ask-yes fullnames \e history-file=+.\*(uAhist history-size=-1 history-gabby \e mime-counter-evidence=0b1111 \e prompt='?\e$?!\e$!/\e$^ERRNAME[\e$account#\e$mailbox-display]? ' \e reply-to-honour=ask-yes \e umask= # Only include the selected header fields when typing messages headerpick type retain from_ date from to cc subject \e message-id mail-followup-to reply-to # ...when forwarding messages headerpick forward retain subject date from to cc # ...when saving message, etc. #headerpick save ignore ^Original-.*$ ^X-.*$ # Some mailing lists mlist '@xyz-editor\e.xyz$' '@xyzf\e.xyz$' mlsubscribe '^xfans@xfans\e.xyz$' # Handle a few file extensions (to store MBOX databases) filetype bz2 'bzip2 -dc' 'bzip2 -zc' \e gz 'gzip -dc' 'gzip -c' xz 'xz -dc' 'xz -zc' \e zst 'zstd -dc' 'zstd -19 -zc' \e zst.pgp 'gpg -d | zstd -dc' 'zstd -19 -zc | gpg -e' # A real life example of a very huge free mail provider # Instead of directly placing content inside `account', # we `define' a macro: like that we can switch "accounts" # from within *on-compose-splice*, for example! define XooglX { set folder=~/spool/XooglX inbox=+syste.mbox sent=+sent set from='Your Name ' set pop3-no-apop-pop.gmXil.com shortcut pop %:pop3s://pop.gmXil.com shortcut imap %:imaps://imap.gmXil.com # Or, entirely IMAP based setup #set folder=imaps://imap.gmail.com record="+[Gmail]/Sent Mail" \e # imap-cache=~/spool/cache set mta=smtp://USER:PASS@smtp.gmXil.com smtp-use-starttls # Alternatively: set mta=smtps://USER:PASS@smtp.gmail.com:465 } account XooglX { \ecall XooglX } # Here is a pretty large one which does not allow sending mails # if there is a domain name mismatch on the SMTP protocol level, # which would bite us if the value of from does not match, e.g., # for people who have a sXXXXeforge project and want to speak # with the mailing list under their project account (in from), # still sending the message through their normal mail provider define XandeX { set folder=~/spool/XandeX inbox=+syste.mbox sent=+sent set from='Your Name ' shortcut pop %:pop3s://pop.yaXXex.com shortcut imap %:imaps://imap.yaXXex.com set mta=smtps://USER:PASS@smtp.yaXXex.com:465 \e hostname=yaXXex.com smtp-hostname= } account XandeX { \ecall Xandex } # Create some new commands so that, e.g., `ls /tmp' will.. commandalias lls '!ls ${LS_COLOUR_FLAG} -aFlrS' commandalias llS '!ls ${LS_COLOUR_FLAG} -aFlS' set pipe-message/external-body='?* echo $MAILX_EXTERNAL_BODY_URL' # We do not support gpg(1) directly yet. But simple --clearsign'd # message parts can be dealt with as follows: define V { localopts yes wysh set pipe-text/plain=$'?*#++=?\e < "${MAILX_FILENAME_TEMPORARY}" awk \e -v TMPFILE="${MAILX_FILENAME_TEMPORARY}" \e'\e BEGIN{done=0}\e /^-----BEGIN PGP SIGNED MESSAGE-----/,/^$/ {\e if(done++ != 0)\e next;\e print "--- GPG --verify ---";\e system("gpg --verify " TMPFILE " 2>&1");\e print "--- GPG --verify ---";\e print "";\e next;\e }\e /^-----BEGIN PGP SIGNATURE-----/,\e /^-----END PGP SIGNATURE-----/{\e next;\e }\e {print}\e \e'' print } commandalias V '\e'call V .Ed . .Pp When storing passwords in .Pa \*(ur appropriate permissions should be set on this file with .Ql $ chmod 0600 \*(ur . If the \*(OPal .Va netrc-lookup is available user credentials can be stored in the central .Pa \*(VN file instead; e.g., here is a different version of the example account that sets up SMTP and POP3: . .Bd -literal -offset indent define XandeX { set folder=~/spool/XandeX inbox=+syste.mbox sent=+sent set from='Your Name ' set netrc-lookup # Load an encrypted ~/.netrc by uncommenting the next line #set netrc-pipe='gpg -qd ~/.netrc.pgp' set mta=smtps://smtp.yXXXXx.ru:465 \e smtp-hostname= hostname=yXXXXx.com set pop3-keepalive=240 pop3-no-apop-pop.yXXXXx.ru commandalias xp fi pop3s://pop.yXXXXx.ru } account XandeX { \ecall XandeX } .Ed . .Pp and, in the .Pa \*(VN file: . .Bd -literal -offset indent machine *.yXXXXx.ru login USER password PASS .Ed . .Pp This configuration should now work just fine: . .Pp .Dl $ echo text | \*(uA -dvv -AXandeX -s Subject user@exam.ple .\" }}} . .\" .Ss "S/MIME step by step" {{{ .Ss "S/MIME step by step" . \*(OP The first thing that is needed for .Sx "Signed and encrypted messages with S/MIME" is a personal certificate, and a private key. The certificate contains public information, in particular a name and email address(es), and the public key that can be used by others to encrypt messages for the certificate holder (the owner of the private key), and to .Ic verify signed messages generated with that certificate('s private key). Whereas the certificate is included in each signed message, the private key must be kept secret. It is used to decrypt messages that were previously encrypted with the public key, and to sign messages. . .Pp For personal use it is recommended to get a S/MIME certificate from one of the major CAs on the Internet. Many CAs offer such certificates for free. Usually offered is a combined certificate and private key in PKCS#12 format which \*(UA does not accept directly. To convert it to PEM format, the following shell command can be used; please read on for how to use these PEM files. . .Bd -literal -offset indent $ openssl pkcs12 -in cert.p12 -out certpem.pem -clcerts -nodes $ # Alternatively $ openssl pkcs12 -in cert.p12 -out cert.pem -clcerts -nokeys $ openssl pkcs12 -in cert.p12 -out key.pem -nocerts -nodes .Ed . .Pp There is also .Lk https://www.CAcert.org which issues client and server certificates to members of their community for free; their root certificate .Pf ( Lk https://\:www.cacert.org/\:certs/\:root.crt ) is often not in the default set of trusted CA root certificates, though, which means their root certificate has to be downloaded separately, and needs to be part of the S/MIME certificate validation chain by including it in .Va smime-ca-dir or as a vivid member of the .Va smime-ca-file . But let us take a step-by-step tour on how to setup S/MIME with a certificate from CAcert.org despite this situation! . .Pp First of all you will have to become a member of the CAcert.org community, simply by registrating yourself via the web interface. Once you are, create and verify all email addresses you want to be able to create signed and encrypted messages for/with using the corresponding entries of the web interface. Now ready to create S/MIME certificates, so let us create a new .Dq client certificate , ensure to include all email addresses that should be covered by the certificate in the following web form, and also to use your name as the .Dq common name . . .Pp Create a private key and a certificate request on your local computer (please see the manual pages of the used commands for more in-depth knowledge on what the used arguments etc. do): . .Pp .Dl $ openssl req -nodes -newkey rsa:4096 -keyout key.pem -out creq.pem . .Pp Afterwards copy-and-paste the content of .Dq creq.pem into the certificate-request (CSR) field of the web form on the CAcert.org website (you may need to unfold some .Dq advanced options to see the corresponding text field). This last step will ensure that your private key (which never left your box) and the certificate belong together (through the public key that will find its way into the certificate via the certificate-request). You are now ready and can create your CAcert certified certificate. Download and store or copy-and-paste it as .Dq pub.crt . . .Pp Yay. In order to use your new S/MIME setup a combined private key/public key (certificate) file has to be created: . .Pp .Dl $ cat key.pem pub.crt > ME@HERE.com.paired . .Pp This is the file \*(UA will work with. If you have created your private key with a passphrase then \*(UA will ask you for it whenever a message is signed or decrypted, unless this operation has been automated as described in .Sx "Signed and encrypted messages with S/MIME" . Set the following variables to henceforth use S/MIME (setting .Va smime-ca-file is of interest for verification only): . .Bd -literal -offset indent ? set smime-ca-file=ALL-TRUSTED-ROOT-CERTS-HERE \e smime-sign-cert=ME@HERE.com.paired \e smime-sign-digest=SHA512 \e smime-sign .Ed . .\" }}} . .\" .Ss "Using CRLs with S/MIME or TLS" {{{ .Ss "Using CRLs with S/MIME or TLS" . \*(OP Certification authorities (CAs) issue certificate revocation lists (CRLs) on a regular basis. These lists contain the serial numbers of certificates that have been declared invalid after they have been issued. Such usually happens because the private key for the certificate has been compromised, because the owner of the certificate has left the organization that is mentioned in the certificate, etc. To seriously use S/MIME or TLS verification, an up-to-date CRL is required for each trusted CA. There is otherwise no method to distinguish between valid and invalidated certificates. \*(UA currently offers no mechanism to fetch CRLs, nor to access them on the Internet, so they have to be retrieved by some external mechanism. . .Pp \*(UA accepts CRLs in PEM format only; CRLs in DER format must be converted, like, e.\|g.: . .Pp .Dl $ openssl crl \-inform DER \-in crl.der \-out crl.pem . .Pp To tell \*(UA about the CRLs, a directory that contains all CRL files (and no other files) must be created. The .Va smime-crl-dir or .Va tls-crl-dir variables, respectively, must then be set to point to that directory. After that, \*(UA requires a CRL to be present for each CA that is used to verify a certificate. .\" }}} . .\" }}} (Examples) . . .\" .Sh "FAQ" {{{ .Sh "FAQ" . In general it is a good idea to turn on .Va debug .Pf ( Fl d ) and / or .Va verbose .Pf ( Fl v , twice) if something does not work well. Very often a diagnostic message can be produced that leads to the problems' solution. . .\" .Ss "\*(UA shortly hangs on startup" {{{ .Ss "\*(UA shortly hangs on startup" . This can have two reasons, one is the necessity to wait for a file lock and cannot be helped, the other being that \*(UA calls the function .Xr uname 2 in order to query the nodename of the box (sometimes the real one is needed instead of the one represented by the internal variable .Va hostname ) . One may have varying success by ensuring that the real hostname and .Ql localhost have entries in .Pa /etc/hosts , or, more generally, that the name service is properly setup \(en and does .Xr hostname 1 return the expected value? Does this local hostname have a domain suffix? RFC 6762 standardized the link-local top-level domain .Ql .local , try again after adding an (additional) entry with this extension. .\" }}} . .\" .Ss "I cannot login to Google mail (via OAuth)" {{{ .Ss "I cannot login to Google mail \&(via OAuth\&)" . Since 2014 some free service providers classify programs as .Dq less secure unless they use a special authentication method (OAuth 2.0) which was not standardized for non-HTTP protocol authentication token query until August 2015 (RFC 7628). . .Pp Different to Kerberos / GSSAPI, which is developed since the mid of the 1980s, where a user can easily create a local authentication ticket for her- and himself with the locally installed .Xr kinit 1 program, that protocol has no such local part but instead requires a world-wide-web query to create or fetch a token; since there is no local cache this query would have to be performed whenever \*(UA is invoked (in interactive sessions situation may differ). . .Pp \*(UA does not support OAuth. Because of this it is necessary to declare \*(UA a .Dq less secure app (on the providers account web page) in order to read and send mail. However, it also seems possible to take the following steps instead: . .Pp .Bl -enum -compact .It give the provider the number of a mobile phone, .It enable .Dq 2-Step Verification , .It create an application specific password (16 characters), and .It use that special password instead of the real Google account password in \*(UA (for more on that see the section .Sx "On URL syntax and credential lookup" ) . .El .\" }}} . .\" .Ss "But, how about XOAUTH2 / OAUTHBEARER?" {{{ .Ss "But, how about XOAUTH2 / OAUTHBEARER?" . Following up .Sx "I cannot login to Google mail \&(via OAuth\&)" one OAuth-based authentication method is available: the OAuth 2.0 bearer token usage as standardized in RFC 6750, also known as XOAUTH2 and OAUTHBEARER, allows fetching a temporary access token via the web that can locally be used as a .Va password . The protocol is simple and extendable, token updates or even password changes via a simple TLS secured server login would be possible in theory, but today a web browser and an external support tool are prerequisites for using this authentication method. The token times out and must be refreshed via the web periodically; in Kerberos / GSSAPI the local programs .Xr kinit 1 and .Xr kdestroy 1 offer user local control, the latter also while offline. . .Pp Before being able to use OAUTHBEARER, some hurdles must be taken. Using GMail as an example, an application (a simple name) needs to be registered, for which credentials need to be created. This configuration step generates a .Dq client ID and a .Dq client secret . These two strings need to be saved locally in a secure way. For GMail these initial configuration steps can be performed via .Lk https://developers.google.com/identity/protocols/OAuth2 . Thereafter access tokens can be requested, the program available for download do do this for a GMail account is .Lk https://github.com/google/\:gmail-oauth2-tools/\:blob/\:\ master/\:python/\:oauth2.py : . .Bd -literal -offset indent $ python oauth2.py --user=EMAIL \e --client-id=THE-ID --client-secret=THE-SECRET \e --generate_oauth2_token To authorize token, visit this url and follow the directions: https://accounts.google.com/o/oauth2/auth?client_id=... Enter verification code: ... Refresh Token: ... Access Token: ... Access Token Expiration Seconds: 3600 $ # The last three are the actual token responses. $ # To refresh the granted token: $ python oauth2.py --user=EMAIL \e --client-id=THE-ID --client-secret=THE-SECRET \e --refresh-token=THE-REFRESH-TOKEN .Ed . .Pp \*(UA does not (yet) offer the possibility to (lazy) expand aka run shell commandos which are embedded in variable content, or periodically run some command, therefore keeping an access token up-to-date from within it can only be performed by setting the hook .Va on-main-loop-tick , or (for sending only) .Va on-compose-splice . For more on authentication please see the section .Sx "On URL syntax and credential lookup" . .\" }}} . .\" .Ss "Not \(dqdefunctional\(dq, but the editor key does not work" {{{ .Ss "Not \(dqdefunctional\(dq, but the editor key does not work" . It can happen that the terminal library (see .Sx "On terminal control and line editor", .Ic bind , .Va termcap ) reports different codes than the terminal really sends, in which case \*(UA will tell that a key binding is functional, but will not be able to recognize it because the received data does not match anything expected. Especially without the \*(OPal terminal capability library support one reason for this may be that the (possibly even non-existing) keypad is not turned on and the resulting layout reports the keypad control codes for the normal keyboard keys. The .Va verbose listing of .Ic bind Ns ings will show the byte sequences that are expected. . .Pp To overcome the situation, use, e.g., the program .Xr cat 1 , in conjunction with the command line option .Fl \&\&v , if available, to see the byte sequences which are actually produced by keypresses, and use the variable .Va termcap to make \*(UA aware of them. E.g., the terminal this is typed on produces some false sequences, here an example showing the shifted home key: . .Bd -literal -offset indent ? set verbose ? bind* # 1B 5B=[ 31=1 3B=; 32=2 48=H bind base :kHOM z0 ? x $ cat -v ^[[H $ \*(uA -v -Stermcap='kHOM=\eE[H' ? bind* # 1B 5B=[ 48=H bind base :kHOM z0 .Ed .\" }}} . .\" .Ss "Can \*(UA git-send-email?" {{{ .Ss "Can \*(UA git-send-email?" . Yes. Put (at least parts of) the following in your .Pa ~/.gitconfig : . .Bd -literal -offset indent [sendemail] smtpserver = /usr/bin/\*(uA smtpserveroption = -t #smtpserveroption = -Sexpandaddr smtpserveroption = -Athe-account-you-need ## suppresscc = all suppressfrom = false assume8bitEncoding = UTF-8 #to = /tmp/OUT confirm = always chainreplyto = true multiedit = false thread = true quiet = true annotate = true .Ed . .Pp Patches can also be send directly, for example: . .Bd -literal -offset indent $ git mail-patch HEAD^ | \*(uA -Athe-account-you-need -t RECEIVER .Ed .\" }}} . .\" .Ss "Howto handle stale dotlock files" {{{ .Ss "Howto handle stale dotlock files" . .Ic file sometimes fails to open MBOX mail databases because creation of .Mx -sx .Sx "dotlock files" is impossible due to existing but unowned lock files. \*(UA does not offer an option to deal with those files, because it is considered a site policy what counts as unowned, and what not. The site policy is usually defined by administrator(s), and expressed in the configuration of a locally installed MTA (for example Postfix .Ql stale_lock_time=500s ) . Therefore the suggestion: . .Bd -literal -offset indent $ "Shell-style argument quoting" in the manual. # - Availability of entries marked [OPTION] is a compile-time decision. # If threaded mode is activated, automatically collapse thread. set autocollapse # Enter threaded mode automatically. #set autosort=thread # Append rather than prepend when writing to mbox automatically. # Has no effect unless *hold* is unset (it is set below). # This is a traditional entry and should usually be set. set append # Ask for a message subject. set ask # Uncomment this in order to get coloured output in $PAGER (if possible). #set colour-pager # If your $PAGER is less(1) or lv(1) you will usually be served with # ? wysh set PAGER=less; environ unset LESS # ? wysh set PAGER=lv; environ unset LV # Assume a CRT-like terminal and invoke $PAGER if output does not fit on # the screen. (Set crt=0 to always page; value treated as number of lines.) set crt # When entering compose mode, directly startup into $EDITOR, as via `~e'. # If the value is "v", startup into $VISUAL instead, as via `~v'. #set editalong=v # When spawning an editor in compose mode (`~e', `~v', *editalong*), allow # editing of headers. set editheaders # Startup into interactive mode even if the (given) mailbox is empty. set emptystart # When `reply'ing etc. name parts and comments are stripped from receiver # addresses unless this variable is set. set fullnames # [OPTION] Add more entries to the history as is done by default. # The latter will cause the built-in editor to save those entries, too. # (The *history-file* variable controls persistency of the history.) set history-gabby history-gabby-persist # Do not move read messages of system mailboxes to MBOX by default since this # is likely to be irritating for most users today; also see *keepsave*. set hold # Quote the original message in replies with "> " as usual on the Internet. # POSIX mandates tabulator ("wysh set indentprefix=$'\t'") as default. set indentprefix="> " # Mark messages that have been answered. set markanswered # Try to circumvent false or missing MIME Content-Type descriptions. # Do set a value for extended behaviour (see the manual). #set mime-counter-evidence set mime-counter-evidence=0b1111 # Control loading of mime.types(5) file, "s"ystem and/or "u"ser, etc. # Builtin types exist and may be sufficient. The default equals "us". #set mimetypes-load-control # Do not remove empty (MBOX) system mailboxes. (_No_ empty (MBOX) mailbox # at all if $POSIXLY_CORRECT a.k.a. *posix* is set!) # This may be relevant for privacy since other users could otherwise create # them with different permissions. set keep # Do not move `save'd or `write'n message to $MBOX by default since this is # likely to be irritating for most users today; also see *hold*. set keepsave # An informational prompt (and see "Gimmicks" below). # Of interest may also be \${^ERRQUEUE-EXISTS} and \${^ERRQUEUE-COUNT}. # Note the _real_ evaluation occurs once used (see *prompt* manual entry). #wysh set prompt='?\$?!\$!/\$^ERRNAME[\${account-name}#\${mailbox-display}]? ' # Automatically quote the text of the message that is responded to. set quote # When replying, do not merge From: and To: of the original message # into To:. Instead old From: -> new To:, old To: -> merge Cc:. set recipients-in-cc # When responding to a message, try to answer in the same character set # (which is subject to `charsetalias' expansion, though). #set reply-in-same-charset # [OPTION] Outgoing messages are sent in UTF-8 if possible, otherwise LATIN1. # Note: it is highly advisable to read the section "Character sets" of the # manual in order to understand all the possibilities that exist to fine-tune # charset usage (variables also of interest: *ttycharset*, *charset-8bit*, # *sendcharsets-else-ttycharset*; and of course we inherit the $LC_CTYPE / # $LC_ALL / $LANG environment variables and react upon them). set sendcharsets=utf-8,iso-8859-1 # Display real sender names in header summaries instead of only addresses. set showname # Show recipients of messages sent by the user himself in header summaries. set showto ## Commands # Most commands are not portable to other Mail(1) / mailx(1) / mail(1) # programs, which is why most commands are commented out. To remain portable, # place anything specific in its own file, and then # set mailx-extra-rc=~/.my-file" # in $MAILRC (usually ~/.mailrc). # Map ISO-8859-1 to LATIN1, and LATIN1 to CP1252. # (These mappings are not applied to character sets specified by other # variables, e.g., *sendcharsets*). #charsetalias iso-8859-1 latin1 latin1 cp1252 # Only include the selected header fields when printing messages # `headerpick' is not portable, so use the standard `retain' retain from_ date from to cc subject message-id mail-followup-to reply-to #headerpick type retain from_ date from to cc subject \ # message-id mail-followup-to reply-to # ...when forwarding messages #headerpick forward retain subject date from to cc # ...and do not include these when saving message, etc. #if [ "$features" =@ +regex ] # headerpick save ignore '^Original-.*$' '^X-.*$' #end ## Some pipe-TYPE/SUBTYPE entries # HTML as text, inline display via lynx(1). #if [ "$features" !@ +filter-html-tagsoup ] # set pipe-text/html='@* lynx -stdin -dump -force_html' #endif # "External body", URL type supported only. #wysh set pipe-message/external-body='@* echo $MAILX_EXTERNAL_BODY_URL' # PDF display, asynchronous display: via `mimeview' command only. #wysh set pipe-application/pdf='@=&@\ # trap "rm -f \"${MAILX_FILENAME_TEMPORARY}\"" EXIT;\ # trap "trap \"\" INT QUIT TERM; exit 1" INT QUIT TERM;\ # mupdf "${MAILX_FILENAME_TEMPORARY}"' ## Gimmicks # More key bindings for the Mailx-Line-Editor (when in interactive mode). #if terminal && [ "$features" =@ +key-bindings ] # \bind base $'\e',d mle-snarf-word-fwd # \bind base $'\e',$'\c?' mle-snarf-word-bwd # \bind base $'\e',f mle-go-word-fwd # \bind base $'\e',b mle-go-word-bwd # \bind base $'\cL' mle-clear-screen # \bind compose :kf1 ~v #endif # Coloured prompt for the Mailx-Line-Editor (when in interactive mode). #if terminal && [ "$features" =@ +mle ] && [ "$features" =@ +colour ] # colour 256 mle-position fg=202 # colour 256 mle-prompt fg=203 # colour 256 mle-error bg=124 # colour iso mle-position ft=bold # colour iso mle-prompt fg=brown # colour iso mle-error bg=red # colour mono mle-position ft=reverse # colour mono mle-prompt ft=bold # colour mono mle-error ft=reverse #endif # Install file-extension handlers to handle MBOXes in various formats. #filetype \ # bz2 'bzip2 -dc' 'bzip2 -zc' \ # gpg 'gpg -d' 'gpg -e' \ # gz 'gzip -dc' 'gzip -c' \ # xz 'xz -dc' 'xz -zc' \ # zst 'zstd -dc' 'zstd -19 -zc' \ # zst.pgp 'gpg -d | zstd -dc' 'zstd -19 -zc | gpg -e' # If mail is send from cron scripts and iconv(3) is compiled it, it could be # that sending fails because of invalid (according to locale) character input. # This undesired event can be prevented as follows, the (possibly) resulting # octet-stream message data can be read nonetheless via # *mime-counter-evidence*=0b1111: #if ! terminal && [ "$LOGNAME" == root ] # set mime-force-sendout #endif # s-it-mode s-nail-14.9.15/snailmail.jpg000066400000000000000000000224411352610246600155410ustar00rootroot00000000000000JFIF%%C  #!!!$'$ & !  ? !1A"Qaq2#B3Rrb$%4CD?((((^v8MkqڀTR9QMAOY1Un$GGBA&!ȩn67dGOq&3Kfv놜r*=7޶m<-o2{ZB\I$r(((((9c ds\kLFoyj mJ5F̥K@*#ѷ$X`{'*>NWZ+A˼6eJT\)KhRRJʀ=Pz*e3:iN[>BuB iB[RV6;QEQEQEQEV#*2d*4P >`y/Tv+]j]KLkA 7?Je/>+W2<ɍ1%L=kd@!^(#܊rm}0[HK}b:w鲕)i ,H:,sY:uR2lEJPZGK*.G=vaRlܷ&=7pBZ?mn}߲my c7&,"SشA+jW>F?h((((_"_m|u'2+L nvGu{RSؤ %]Ɲ)Վr\TiL<;V)ZO:"g:n+r[S+Jn,| f&i`nb]`kOc8ۉ<@=^&N+"TV@RLo!=irX6}M͡ɒW:[NIT!l!)Sc~+Ey:&Ewg7޸aJqS>>AOrg WL dIJHZ|$s[>JG-M{G=8)4%i'ޞtQEQETmc_-zS7R:‡uBwHA }jMS,@ȳ٨tfaHQ Ynt6Kjv,;!.5tkCoЮV5Wrj~ՅqnKb.AM);(p}ZH+:ZU\1wL%G^U[ 튝ݛ{^a@)'*E[F6mnlv.'P|֛.-\u5.oGu:m.:Z|v)e1PC(OBQRV+g Mե\oKa$#MD}QYAiĬGL$FQq2 8RH*l;"?nb4r2Y1R Ze#Dﴥ{ׂtQEQEP2K +jN[l-OsX,&ILw0ȒB.;<0lsǚcxQ^ [=߃׺[Ix;w:ڨwJ$m=IOovzO<É@Uz źĘ [hò$sǟo(}߆+;e ͭ-ÅEWޤRJ È?$ͱ\ٖ딨Rn/IҔ+VtʁVrpXͦ'%Z d|?;w>)5/=/pdxCvQv>v}6U$n~ ,|v 8 R㊲6@(GWk 4QEQE' !ɳtæYUSgăžP=X!cI*.<+vK8*QdddhWpl]V߬sύ{RZ,/:eZ۷7q^%CLwK lX_T_i6whzzÑYcαLgN*G(RvuG+l?*Rlֻ|J iEC$|%gσO: l=L*?>:nҨrb6RPRlww8Nݗ㘹o }ْX!L .j c[ %7v늣 :A[RBRyNSuWV.F%m:+IA8Rx]v2rPc\aq6H QZ67R}^zeK,W6ۀm.B\N>{U(W>[)LovsYp ـl٠:JzlՏLVϹ'aV2#dxnl@88ܠ<ʏlҩT:cq#Md9O79 5EwU)YMڶ"Iqßޜ[zt%gTrХn/RJl|lV;Vs.gXqT&$%NJO<*h}Hjv[0.+dfB/8-Y} 'Ip?6t VzlbUKEci=hb5 U]0?Mr˜v|p/!<y 1 yvhHyL +ni-bQ K=7JIJWpߍlUoӉm[6g)*P )V@P@(:Q~["|SnhiG=Z*Sߺʇtt=ft^\gy}GB,'3w<4:UNzGjnLJ\oOG+?844eh,.c{Sn*N]#EQ_ IR }$9nbK#:'8?>J2<(zK8+PN˚35Wp5㎶A |$=|IU<{skyQDmqŸ ?е(?~JR%HJR4W+qh!;䏞17'q$4YZWrtxVPxR|i= ɝa۬JC\ Bqm'̘'@J8>G4]-D[[s l:i>:ERRyƜOlKm׋$T<#v.4}Bk}pvL7quRR ´ "!ɴd帧#9=*NڶOǎe] =3\z: K^=j`]M%CLE9{wpw}5(w-+'6oÒ4P\~¦sK|˚J.C-8<}mi>-"<#^$CkߝIx,}:[޹]nQ;Zb _:ZOh@<WOԮXpl \YqWW~bQ RF4 R0Qћ{vO{'FlNeQW<''cD .+ᷤ"RXjdGrOx}R.NfYi繹̎vAP>ÍoPWphWt߮n]Bd\)68Hδe.@@SZ>vĻ8͸YBB~$HȺaW鍩e*ilux.3{E=*aT9:7[C:I[L{f⾔+ t%@wy܊>mp̱A]%>mh})h@?}qW$)$w{*2!ߍO;P|R [έbJYyQo Knvs>5#+hV!CXBV2Q­oHNcc-9az='"8QV6S~Sk^Cn_|Kf~H:+^B6 1B?avw-:{HPOhډ* $+=F16F&$RTj e'qZV'Z3vl,{!m6( nRHV-Xuzrŕ6\q@(X+|5ukpbLu.N\"KLR麯xM%Juo;wJv o\g׮8Cti\u~eYZ]ڀ{)h%,  jN2)juԂ{J!w̯JH2))FGj !@@=Mk !T$Ͱ(2Q$!{B}E.sr5Ky6,hJZX1j5Ϩ9,f˭[we=}b+m î Ҁ@IQZאߵJ~3H$8Q,8>AEI5])L!Š*;$px[BQW2]^Pݚ{B*┖Ԥ 69.:SRR q;W$)@56e1"-<[zl ct;aKM(RD4 xq N &rm$T$䐞~@Ṽ݊.Ä[Xuup)A Xjg'Ę n h*uuΉ zv酯\3DVS <{TB9<9>V^J5vKYPߵ}ϰy5Qg8~ML_oqC}BUƠ31W!8*x,9PI b=´ \ߧ =d~eiM--]2,%0Q׮<`;#JdLK(kBN?vuY7|E+i_I]f'/E]‭.} zmsxձ7[dU2`[sV) J0%e:$JލW.9z. O^ K%)HR>EkOoz9Z3qwѮi.?&/_fb-pb{ֿX'{:׳ǽ`^&Cd,-PRhRJ 犷ǐ-ۨE:u׃QEyq4䓠+AHPP;|P7ovbnV[DVy +Ϊ $v9mZM:y'\Nnt,ـ RlR8!u}TaߜXe ٜzL`*m*!Nw%}utTiC /:H@u'E[V~_AGQf^m=>i2'l@ twNEO0K,m{QR'R5I̩]MuR$d0"GSSǰ dxȓ ֆeTKkV6|]DySF`(%COa[@DX Ae * #9'Z{VQESol^QbsmR۟VցB@xVUwպO4S>Fc+ў`o=Ť z>- y4h6TE'jJo~Iծ|?}?G];iyq $:B$x<~ȏ;2M% ӏo'\8xFglGmnA +Kv[w1iQ y@ _5ٯwwT+2…- Gx.: lHuݼsҤ o?DҰ9 n61_ MˌtP>|r5M"niw|D|:TI?*ƅ@PJ#*;!&,kD\"O IIPsGKr12u! ?;t~GARdZ}mS)b%Q{=i)NV%̶I.NHN<䏑9|#, [3}\ZT+4uW)%)hg|W(*cxIR;`'{:tBv-TS5S"5)f4(:gBߌ8 ؑJ׺?:fvj)eYLRul Scope -> Local, all "overlay" objects. * * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany. * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1980, 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. */ #undef su_FILE #define su_FILE accmacvar #define mx_SOURCE #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif #include #include #include #include #include "mx/file-streams.h" #include "mx/iconv.h" #include "mx/names.h" #include "mx/sigs.h" #include "mx/ui-str.h" #include "mx/url.h" /* TODO fake */ #include "su/code-in.h" #if !defined mx_HAVE_SETENV && !defined mx_HAVE_PUTENV # error Exactly one of mx_HAVE_SETENV and mx_HAVE_PUTENV #endif /* Positional parameter maximum (macro arguments, `vexpr' "splitifs") */ #define a_AMV_POSPAR_MAX S16_MAX /* Special "pseudo macro" that stabs you from the back */ #define a_AMV_MACKY_MACK ((struct a_amv_mac*)-1) /* Note: changing the hash function must be reflected in `vexpr' "hash32", * because that is used by the hashtable creator scripts! */ #define a_AMV_PRIME 23 /* TODO cs_dict! */ #define a_AMV_NAME2HASH(N) ((u32)su_cs_hash(N)) #define a_AMV_HASH2PRIME(H) ((H) % a_AMV_PRIME) enum a_amv_mac_flags{ a_AMV_MF_NONE = 0, a_AMV_MF_ACCOUNT = 1u<<0, /* This macro is an `account' */ a_AMV_MF_TYPE_MASK = a_AMV_MF_ACCOUNT, a_AMV_MF_UNDEF = 1u<<1, /* Unlink after lookup */ a_AMV_MF_DELETE = 1u<<7, /* Delete in progress, free once refcnt==0 */ a_AMV_MF__MAX = 0xFFu }; enum a_amv_loflags{ a_AMV_LF_NONE = 0, a_AMV_LF_SCOPE = 1u<<0, /* Current scope `localopts' on */ a_AMV_LF_SCOPE_FIXATE = 1u<<1, /* Ditto, but fixated */ a_AMV_LF_SCOPE_MASK = a_AMV_LF_SCOPE | a_AMV_LF_SCOPE_FIXATE, a_AMV_LF_CALL = 1u<<2, /* `localopts' on for `call'ed scopes */ a_AMV_LF_CALL_FIXATE = 1u<<3, /* Ditto, but fixated */ a_AMV_LF_CALL_MASK = a_AMV_LF_CALL | a_AMV_LF_CALL_FIXATE, a_AMV_LF_CALL_TO_SCOPE_SHIFT = 2 }; /* make-okey-map.pl ensures that _VIRT implies _RDONLY and _NODEL, and that * _IMPORT implies _ENV; it doesn't verify anything... * More description at nail.h:enum okeys */ enum a_amv_var_flags{ a_AMV_VF_NONE = 0, /* The basic set of flags, also present in struct a_amv_var_map.avm_flags */ a_AMV_VF_BOOL = 1u<<0, /* ok_b_* */ a_AMV_VF_CHAIN = 1u<<1, /* Has -HOST and/or -USER@HOST variants */ a_AMV_VF_VIRT = 1u<<2, /* "Stateless" automatic variable */ a_AMV_VF_VIP = 1u<<3, /* Wants _var_check_vips() evaluation */ a_AMV_VF_RDONLY = 1u<<4, /* May not be set by user */ a_AMV_VF_NODEL = 1u<<5, /* May not be deleted */ a_AMV_VF_I3VAL = 1u<<6, /* Has an initial value */ a_AMV_VF_DEFVAL = 1u<<7, /* Has a default value */ a_AMV_VF_IMPORT = 1u<<8, /* Import ONLY from env (pre n_PSO_STARTED) */ a_AMV_VF_ENV = 1u<<9, /* Update environment on change */ a_AMV_VF_NOLOPTS = 1u<<10, /* May not be tracked by `localopts' */ a_AMV_VF_NOTEMPTY = 1u<<11, /* May not be assigned an empty value */ /* TODO _VF_NUM, _VF_POSNUM: we also need 64-bit limit numbers! */ a_AMV_VF_NUM = 1u<<12, /* Value must be a 32-bit number */ a_AMV_VF_POSNUM = 1u<<13, /* Value must be positive 32-bit number */ a_AMV_VF_LOWER = 1u<<14, /* Values will be stored in lowercase version */ a_AMV_VF_OBSOLETE = 1u<<15, /* Is obsolete? */ a_AMV_VF__MASK = (1u<<(15+1)) - 1, /* Extended flags, not part of struct a_amv_var_map.avm_flags */ /* This flag indicates the instance is actually a variant of a _VF_CHAIN, * it thus uses the a_amv_var_map of the base variable, but it is not the * base itself and therefore care must be taken */ a_AMV_VF_EXT_CHAIN = 1u<<22, a_AMV_VF_EXT_LOCAL = 1u<<23, /* `local' */ a_AMV_VF_EXT_LINKED = 1u<<24, /* `environ' link'ed */ a_AMV_VF_EXT_FROZEN = 1u<<25, /* Has been set by -S,.. */ a_AMV_VF_EXT_FROZEN_UNSET = 1u<<26, /* ..and was used to unset a variable */ a_AMV_VF_EXT__FROZEN_MASK = a_AMV_VF_EXT_FROZEN | a_AMV_VF_EXT_FROZEN_UNSET, a_AMV_VF_EXT__MASK = (1u<<(26+1)) - 1 }; enum a_amv_var_lookup_flags{ a_AMV_VLOOK_NONE = 0, a_AMV_VLOOK_LOCAL = 1u<<0, /* Query `local' layer first */ a_AMV_VLOOK_LOCAL_ONLY = 1u<<1, /* MUST be a `local' variable */ /* Do not allocate new var for _I3VAL, see _var_lookup() for more */ a_AMV_VLOOK_I3VAL_NONEW = 1u<<2, a_AMV_VLOOK_I3VAL_NONEW_REPORT = 1u<<3, /* And then we must be able to detect endless recursion, for example if * $TMPDIR is set to non-existent we can use the VAL_TMPDIR config default, * but if this also fails (filesystem read-only for example), then all bets * are off, and we must not enter an endless loop */ a_AMV_VLOOK_BELLA_CIAO_CIAO_CIAO = 1u<<30 }; enum a_amv_var_setclr_flags{ a_AMV_VSETCLR_NONE = 0, a_AMV_VSETCLR_LOCAL = 1u<<0, /* Use `local' variables only */ /* XXX Maybe something for only non-local? */ a_AMV_VSETCLR_ENV = 1u<<1 /* `environ' or otherwise environ */ }; /* We support some special parameter names for one(+)-letter variable names; * note these have counterparts in the code that manages shell expansion! * All these special variables are solely backed by n_var_vlook(), and besides * there is only a_amv_var_revlookup() which knows about them */ enum a_amv_var_special_category{ a_AMV_VSC_NONE, /* Normal variable, no special treatment */ a_AMV_VSC_GLOBAL, /* ${[?!]} are specially mapped, but global */ a_AMV_VSC_MULTIPLEX, /* ${^.*} circumflex accent multiplexer */ a_AMV_VSC_POSPAR, /* ${[1-9][0-9]*} positional parameters */ a_AMV_VSC_POSPAR_ENV /* ${[*@#]} positional parameter support variables */ }; enum a_amv_var_special_type{ /* _VSC_GLOBAL */ a_AMV_VST_QM, /* ? */ a_AMV_VST_EM, /* ! */ /* _VSC_MULTIPLEX */ /* This is special in that it is a multiplex indicator, the ^ is followed by * a normal variable */ a_AMV_VST_CACC, /* ^ (circumflex accent) */ /* _VSC_POSPAR_ENV */ a_AMV_VST_STAR, /* * */ a_AMV_VST_AT, /* @ */ a_AMV_VST_NOSIGN /* # */ }; enum a_amv_var_vip_mode{ a_AMV_VIP_SET_PRE, a_AMV_VIP_SET_POST, a_AMV_VIP_CLEAR }; struct a_amv_pospar{ u16 app_maxcount; /* == slots in .app_dat */ u16 app_count; /* Maximum a_AMV_POSPAR_MAX */ u16 app_idx; /* `shift' moves this one, decs .app_count */ boole app_not_heap; /* .app_dat stuff not dynamically allocated */ u8 app__dummy[1]; char const **app_dat; /* NULL terminated (for "$@" explosion support) */ }; CTA(a_AMV_POSPAR_MAX <= S16_MAX, "Limit exceeds datatype capabilities"); struct a_amv_mac{ struct a_amv_mac *am_next; u32 am_maxlen; /* of any line in .am_line_dat */ u32 am_line_cnt; /* of *.am_line_dat (but NULL terminated) */ struct a_amv_mac_line **am_line_dat; /* TODO use deque? */ struct a_amv_var *am_lopts; /* `localopts' unroll list */ u32 am_refcnt; /* 0-based for `un{account,define}' purposes */ u8 am_flags; /* enum a_amv_mac_flags */ char am_name[VFIELD_SIZE(3)]; /* of this macro */ }; CTA(a_AMV_MF__MAX <= U8_MAX, "Enumeration excesses storage datatype"); struct a_amv_mac_line{ u32 aml_len; u32 aml_prespc; /* Number of leading SPACEs, for display purposes */ char aml_dat[VFIELD_SIZE(0)]; }; struct a_amv_mac_call_args{ char const *amca_name; /* For MACKY_MACK, this is *0*! */ struct a_amv_mac *amca_amp; /* "const", but for am_refcnt */ struct a_amv_var **amca_unroller; void (*amca_hook_pre)(void *); void *amca_hook_arg; u8 amca_loflags; boole amca_ps_hook_mask; boole amca_no_xcall; /* XXX We want n_GO_INPUT_NO_XCALL for this */ boole amca_ignerr; /* XXX Use n_GO_INPUT_IGNERR for evaluating commands */ u8 amca__pad[4]; struct a_amv_var *(*amca_local_vars)[a_AMV_PRIME]; /* `local's, or NULL */ struct a_amv_pospar amca_pospar; }; struct a_amv_lostack{ struct a_amv_lostack *as_global_saved; /* Saved global XXX due to jump */ struct a_amv_mac_call_args *as_amcap; struct a_amv_lostack *as_up; /* Outer context */ struct a_amv_var *as_lopts; u8 as_loflags; /* enum a_amv_mac_loflags */ u8 avs__pad[7]; }; struct a_amv_var{ struct a_amv_var *av_link; char *av_value; #ifdef mx_HAVE_PUTENV char *av_env; /* Actively managed putenv(3) memory, or NULL */ #endif u32 av_flags; /* enum a_amv_var_flags inclusive extended bits */ char av_name[VFIELD_SIZE(4)]; }; CTA(a_AMV_VF_EXT__MASK <= U32_MAX, "Enumeration excesses storage datatype"); /* After inclusion of gen-okeys.h we ASSERT keyoff fits in 16-bit */ struct a_amv_var_map{ u32 avm_hash; u16 avm_keyoff; u16 avm_flags; /* enum a_amv_var_flags without extended bits */ }; CTA(a_AMV_VF__MASK <= U16_MAX, "Enumeration excesses storage datatype"); /* XXX Since there is no indicator character used for variable chains, we just * XXX cannot do better than using some s....y detection. * The length of avcmb_prefix is highly hardwired with make-okey-map.pl etc. */ struct a_amv_var_chain_map_bsrch{ char avcmb_prefix[4]; u16 avcmb_chain_map_off; u16 avcmb_chain_map_eokey; /* This is an enum okey */ }; /* Use 16-bit for enum okeys, which should always be sufficient; all around * here we use 32-bit for it instead, but that owed to faster access (?) */ struct a_amv_var_chain_map{ u16 avcm_keyoff; u16 avcm_okey; }; CTA(n_OKEYS_MAX <= U16_MAX, "Enumeration excesses storage datatype"); struct a_amv_var_virt{ u32 avv_okey; u8 avv__dummy[4]; struct a_amv_var const *avv_var; }; struct a_amv_var_defval{ u32 avdv_okey; u8 avdv__pad[4]; char const *avdv_value; /* Only for !BOOL (otherwise plain existence) */ }; struct a_amv_var_carrier{ char const *avc_name; u32 avc_hash; u32 avc_prime; struct a_amv_var *avc_var; struct a_amv_var_map const *avc_map; enum okeys avc_okey; boole avc_is_chain_variant; /* Base is a chain, this a variant thereof */ u8 avc_special_cat; /* Numerical parameter name if .avc_special_cat=a_AMV_VSC_POSPAR, * otherwise a enum a_amv_var_special_type */ u16 avc_special_prop; }; /* Include constant make-okey-map.pl output, and the generated version data */ #include "mx/gen-version.h" /* - */ #include "mx/gen-okeys.h" /* $(MX_SRCDIR) */ /* As promised above, CTAs to protect our structures */ CTA(a_AMV_VAR_NAME_KEY_MAXOFF <= U16_MAX, "Enumeration excesses storage datatype"); /* The currently active account */ static struct a_amv_mac *a_amv_acc_curr; static struct a_amv_mac *a_amv_macs[a_AMV_PRIME]; /* TODO dynamically spaced */ /* Unroll list of currently running macro stack */ static struct a_amv_lostack *a_amv_lopts; static struct a_amv_var *a_amv_vars[a_AMV_PRIME]; /* TODO dynamically spaced */ /* Global (i.e., non-local) a_AMV_VSC_POSPAR stack */ static struct a_amv_pospar a_amv_pospar; /* TODO We really deserve localopts support for *folder-hook*s, so hack it in * TODO today via a static lostack, it should be a field in mailbox, once that * TODO is a real multi-instance object */ static struct a_amv_var *a_amv_folder_hook_lopts; /* TODO Rather ditto (except for storage -> cmd_ctx), compose hooks */ static struct a_amv_var *a_amv_compose_lopts; /* Lookup for macros/accounts: if newamp is not NULL it will be linked in the * map, if _MF_UNDEF is set a possibly existing entry will be removed (first). * Returns NULL if a lookup failed, or if newamp was set, the found entry in * plain lookup cases or when _UNDEF was performed on a currently active entry * (the entry will have been unlinked, and the _MF_DELETE will be honoured once * the reference count reaches 0), and (*)-1 if an _UNDEF was performed */ static struct a_amv_mac *a_amv_mac_lookup(char const *name, struct a_amv_mac *newamp, enum a_amv_mac_flags amf); /* `call', `call_if' (and `xcall' via go.c -> c_call()) */ static int a_amv_mac_call(void *v, boole silent_nexist); /* Execute a macro; amcap must reside in LOFI memory */ static boole a_amv_mac_exec(struct a_amv_mac_call_args *amcap); static void a_amv_mac__finalize(void *vp); /* User display helpers */ static boole a_amv_mac_show(enum a_amv_mac_flags amf); /* _def() returns error for faulty definitions and already existing * names, * _undef() returns error if a named thing doesn't exist */ static boole a_amv_mac_def(char const *name, enum a_amv_mac_flags amf); static boole a_amv_mac_undef(char const *name, enum a_amv_mac_flags amf); /* */ static void a_amv_mac_free(struct a_amv_mac *amp); /* Update replay-log */ static void a_amv_lopts_add(struct a_amv_lostack *alp, char const *name, struct a_amv_var *oavp); static void a_amv_lopts_unroll(struct a_amv_var **avpp); /* Special cased value string allocation */ static char *a_amv_var_copy(char const *str); static void a_amv_var_free(char *cp); /* Check for special housekeeping. _VIP_SET_POST and _VIP_CLEAR do not fail * (or propagate errors), _VIP_SET_PRE may and should case abortion */ static boole a_amv_var_check_vips(enum a_amv_var_vip_mode avvm, enum okeys okey, char const **val); /* _VF_NUM / _VF_POSNUM */ static boole a_amv_var_check_num(char const *val, boole posnum); /* Try to reverse lookup a name to an enum okeys mapping, zeroing avcp. * Updates .avc_name and .avc_hash; .avc_map is NULL if none found. * We may try_harder to identify name: it may be an extended chain. * That test only is actually performed by the latter(, then) */ static boole a_amv_var_revlookup(struct a_amv_var_carrier *avcp, char const *name, boole try_harder); static boole a_amv_var_revlookup_chain(struct a_amv_var_carrier *avcp, char const *name); /* Lookup a variable from .avc_(map|name|hash), return whether it was found. * Sets .avc_prime; .avc_var is NULL if not found. * Here it is where we care for _I3VAL and _DEFVAL. * An _I3VAL will be "consumed" as necessary anyway, but it won't be used to * create a new variable if _VLOOK_I3VAL_NONEW is set; if * _VLOOK_I3VAL_NONEW_REPORT is set then we set .avc_var to -1 and return true * if that was the case, otherwise we'll return FAL0, then! */ static boole a_amv_var_lookup(struct a_amv_var_carrier *avcp, enum a_amv_var_lookup_flags avlf); /* Lookup functions for special category variables, _pospar drives all * positional parameter etc. special categories */ static char const *a_amv_var_vsc_global(struct a_amv_var_carrier *avcp); static char const *a_amv_var_vsc_multiplex(struct a_amv_var_carrier *avcp); static char const *a_amv_var_vsc_pospar(struct a_amv_var_carrier *avcp); /* Set var from .avc_(map|name|hash), return success */ static boole a_amv_var_set(struct a_amv_var_carrier *avcp, char const *value, enum a_amv_var_setclr_flags avscf); static boole a_amv_var__putenv(struct a_amv_var_carrier *avcp, struct a_amv_var *avp); /* Clear var from .avc_(map|name|hash); sets .avc_var=NULL, return success */ static boole a_amv_var_clear(struct a_amv_var_carrier *avcp, enum a_amv_var_setclr_flags avscf); static boole a_amv_var__clearenv(char const *name, struct a_amv_var *avp); /* List all variables */ static void a_amv_var_show_all(void); /* Actually do print one, return number of lines written */ static uz a_amv_var_show(char const *name, FILE *fp, struct n_string *msgp); /* Shared c_set() and c_environ():set impl, return success */ static boole a_amv_var_c_set(char **ap, enum a_amv_var_setclr_flags avscf); static struct a_amv_mac * a_amv_mac_lookup(char const *name, struct a_amv_mac *newamp, enum a_amv_mac_flags amf){ struct a_amv_mac *amp, **ampp; u32 h; enum a_amv_mac_flags save_amf; NYD2_IN; save_amf = amf; amf &= a_AMV_MF_TYPE_MASK; h = a_AMV_NAME2HASH(name); h = a_AMV_HASH2PRIME(h); ampp = &a_amv_macs[h]; for(amp = *ampp; amp != NULL; ampp = &(*ampp)->am_next, amp = amp->am_next){ if((amp->am_flags & a_AMV_MF_TYPE_MASK) == amf && !su_cs_cmp(amp->am_name, name)){ if(LIKELY((save_amf & a_AMV_MF_UNDEF) == 0)) goto jleave; *ampp = amp->am_next; if(amp->am_refcnt > 0){ amp->am_flags |= a_AMV_MF_DELETE; if(n_poption & n_PO_D_V) n_err(_("Delayed deletion of currently active %s: %s\n"), (amp->am_flags & a_AMV_MF_ACCOUNT ? "account" : "define"), name); }else{ a_amv_mac_free(amp); amp = (struct a_amv_mac*)-1; } break; } } if(newamp != NULL){ ampp = &a_amv_macs[h]; newamp->am_next = *ampp; *ampp = newamp; amp = NULL; } jleave: NYD2_OU; return amp; } static int a_amv_mac_call(void *v, boole silent_nexist){ struct a_amv_mac *amp; int rv; char const *name; struct n_cmd_arg_ctx *cacp; NYD_IN; cacp = v; if(cacp->cac_no == 0){ n_err(_("Synopsis: call(_if)?: name [::]\n")); n_pstate_err_no = su_ERR_INVAL; rv = 1; goto jleave; } name = cacp->cac_arg->ca_arg.ca_str.s; if(UNLIKELY(cacp->cac_no > a_AMV_POSPAR_MAX)){ n_err(_("Too many arguments to macro `call': %s\n"), name); n_pstate_err_no = su_ERR_OVERFLOW; rv = 1; }else if(UNLIKELY((amp = a_amv_mac_lookup(name, NULL, a_AMV_MF_NONE) ) == NULL)){ if(!silent_nexist) n_err(_("Undefined macro called: %s\n"), n_shexp_quote_cp(name, FAL0)); n_pstate_err_no = su_ERR_NOENT; rv = 1; }else{ char const **argv; struct a_amv_mac_call_args *amcap; uz argc; argc = cacp->cac_no + 1; amcap = n_lofi_alloc(sizeof *amcap + (argc * sizeof *argv)); argv = (void*)&amcap[1]; for(argc = 0; (cacp->cac_arg = cacp->cac_arg->ca_next) != NULL; ++argc) argv[argc] = cacp->cac_arg->ca_arg.ca_str.s; argv[argc] = NULL; su_mem_set(amcap, 0, sizeof *amcap); amcap->amca_name = name; amcap->amca_amp = amp; if(a_amv_lopts != NULL) amcap->amca_loflags = (a_amv_lopts->as_loflags & a_AMV_LF_CALL_MASK ) >> a_AMV_LF_CALL_TO_SCOPE_SHIFT; if(argc > 0){ amcap->amca_pospar.app_count = (u16)argc; amcap->amca_pospar.app_not_heap = TRU1; amcap->amca_pospar.app_dat = argv; } (void)a_amv_mac_exec(amcap); rv = n_pstate_ex_no; } jleave: NYD_OU; return rv; } static boole a_amv_mac_exec(struct a_amv_mac_call_args *amcap){ struct a_amv_lostack *losp; struct a_amv_mac_line **amlp; char **args_base, **args; struct a_amv_mac *amp; boole rv; NYD2_IN; amp = amcap->amca_amp; ASSERT(amp != NULL && amp != a_AMV_MACKY_MACK); ++amp->am_refcnt; /* XXX Unfortunately we yet need to dup the macro lines! :( */ args_base = args = n_alloc(sizeof(*args) * (amp->am_line_cnt +1)); for(amlp = amp->am_line_dat; *amlp != NULL; ++amlp) *(args++) = su_cs_dup_cbuf((*amlp)->aml_dat, (*amlp)->aml_len, 0); *args = NULL; losp = n_lofi_alloc(sizeof *losp); losp->as_global_saved = a_amv_lopts; if((losp->as_amcap = amcap)->amca_unroller == NULL){ losp->as_up = losp->as_global_saved; losp->as_lopts = NULL; }else{ losp->as_up = NULL; losp->as_lopts = *amcap->amca_unroller; } losp->as_loflags = amcap->amca_loflags; a_amv_lopts = losp; if(amcap->amca_hook_pre != NULL) n_PS_ROOT_BLOCK((*amcap->amca_hook_pre)(amcap->amca_hook_arg)); rv = n_go_macro((n_GO_INPUT_NONE | (amcap->amca_no_xcall ? n_GO_INPUT_NO_XCALL : 0) | (amcap->amca_ignerr ? n_GO_INPUT_IGNERR : 0)), amp->am_name, args_base, &a_amv_mac__finalize, losp); NYD2_OU; return rv; } static void a_amv_mac__finalize(void *vp){ struct a_amv_mac *amp; struct a_amv_mac_call_args *amcap; struct a_amv_lostack *losp; NYD2_IN; losp = vp; a_amv_lopts = losp->as_global_saved; amcap = losp->as_amcap; /* Delete positional parameter stack */ if(!amcap->amca_pospar.app_not_heap && amcap->amca_pospar.app_maxcount > 0){ u16 i; for(i = amcap->amca_pospar.app_maxcount; i-- != 0;) n_free(n_UNCONST(amcap->amca_pospar.app_dat[i])); n_free(amcap->amca_pospar.app_dat); } /* `local' variable hashmap. These have no environment map, never */ if(amcap->amca_local_vars != NULL){ struct a_amv_var **avpp_base, **avpp, *avp; for(avpp_base = *amcap->amca_local_vars, avpp = &avpp_base[a_AMV_PRIME]; avpp-- != avpp_base;) while((avp = *avpp)){ ASSERT((avp->av_flags & (a_AMV_VF_NOLOPTS | a_AMV_VF_EXT_LOCAL)) == (a_AMV_VF_NOLOPTS | a_AMV_VF_EXT_LOCAL)); ASSERT(!(avp->av_flags & ((a_AMV_VF__MASK | a_AMV_VF_EXT__MASK) & ~(a_AMV_VF_NOLOPTS | a_AMV_VF_EXT_LOCAL)))); *avpp = avp->av_link; a_amv_var_free(avp->av_value); n_free(avp); } n_free(avpp_base); } /* Unroll `localopts', if applicable */ if(amcap->amca_unroller == NULL){ if(losp->as_lopts != NULL) a_amv_lopts_unroll(&losp->as_lopts); }else *amcap->amca_unroller = losp->as_lopts; if(amcap->amca_ps_hook_mask) n_pstate &= ~n_PS_HOOK_MASK; if((amp = amcap->amca_amp) != a_AMV_MACKY_MACK && amp != NULL && --amp->am_refcnt == 0 && (amp->am_flags & a_AMV_MF_DELETE)) a_amv_mac_free(amp); n_lofi_free(losp); n_lofi_free(amcap); NYD2_OU; } static boole a_amv_mac_show(enum a_amv_mac_flags amf){ uz lc, mc, ti, i; char const *typestr; FILE *fp; boole rv; NYD2_IN; rv = FAL0; if((fp = mx_fs_tmp_open("deflist", (mx_FS_O_RDWR | mx_FS_O_UNLINK | mx_FS_O_REGISTER), NIL)) == NIL){ n_perr(_("`define' or `account' list: cannot create temporary file"), 0); goto jleave; } amf &= a_AMV_MF_TYPE_MASK; typestr = (amf & a_AMV_MF_ACCOUNT) ? "account" : "define"; for(lc = mc = ti = 0; ti < a_AMV_PRIME; ++ti){ struct a_amv_mac *amp; for(amp = a_amv_macs[ti]; amp != NULL; amp = amp->am_next){ if((amp->am_flags & a_AMV_MF_TYPE_MASK) == amf){ struct a_amv_mac_line **amlpp; if(++mc > 1){ putc('\n', fp); ++lc; } ++lc; fprintf(fp, "%s %s {\n", typestr, amp->am_name); for(amlpp = amp->am_line_dat; *amlpp != NULL; ++lc, ++amlpp){ for(i = (*amlpp)->aml_prespc; i > 0; --i) putc(' ', fp); fputs((*amlpp)->aml_dat, fp); putc('\n', fp); } fputs("}\n", fp); ++lc; } } } if(mc > 0) page_or_print(fp, lc); rv = (ferror(fp) == 0); mx_fs_close(fp); jleave: NYD2_OU; return rv; } static boole a_amv_mac_def(char const *name, enum a_amv_mac_flags amf){ struct str line; u32 line_cnt, maxlen; struct linelist{ struct linelist *ll_next; struct a_amv_mac_line *ll_amlp; } *llp, *ll_head, *ll_tail; union {uz s; int i; u32 ui; uz l;} n; struct a_amv_mac *amp; boole rv; NYD2_IN; su_mem_set(&line, 0, sizeof line); rv = FAL0; amp = NULL; /* TODO We should have our input state machine which emits Line events, * TODO and hook different consumers dependent on our content, as stated * TODO in i think lex_input: like this local macros etc. would become * TODO possible from the input side */ /* Read in the lines which form the macro content */ for(ll_tail = ll_head = NULL, line_cnt = maxlen = 0;;){ u32 leaspc; char *cp; n.i = n_go_input(n_GO_INPUT_CTX_DEFAULT | n_GO_INPUT_NL_ESC, n_empty, &line.s, &line.l, NULL, NULL); if(n.ui == 0) continue; if(n.i < 0){ n_err(_("Unterminated %s definition: %s\n"), (amf & a_AMV_MF_ACCOUNT ? "account" : "macro"), name); goto jerr; } /* Trim WS, remember amount of leading spaces for display purposes */ for(cp = line.s, leaspc = 0; n.ui > 0; ++cp, --n.ui) if(*cp == '\t') leaspc = (leaspc + 8u) & ~7u; else if(*cp == ' ') ++leaspc; else break; for(; n.ui > 0 && su_cs_is_space(cp[n.ui - 1]); --n.ui) ; if(n.ui == 0) continue; maxlen = MAX(maxlen, n.ui); cp[n.ui++] = '\0'; /* Is is the closing brace? */ if(*cp == '}') break; if(LIKELY(++line_cnt < U32_MAX)){ struct a_amv_mac_line *amlp; llp = n_autorec_alloc(sizeof *llp); if(ll_head == NULL) ll_head = llp; else ll_tail->ll_next = llp; ll_tail = llp; llp->ll_next = NULL; llp->ll_amlp = amlp = n_alloc(VSTRUCT_SIZEOF(struct a_amv_mac_line, aml_dat) + n.ui); amlp->aml_len = n.ui -1; amlp->aml_prespc = leaspc; su_mem_copy(amlp->aml_dat, cp, n.ui); }else{ n_err(_("Too much content in %s definition: %s\n"), (amf & a_AMV_MF_ACCOUNT ? "account" : "macro"), name); goto jerr; } } /* Create the new macro */ n.s = su_cs_len(name) +1; amp = n_alloc(VSTRUCT_SIZEOF(struct a_amv_mac, am_name) + n.s); su_mem_set(amp, 0, VSTRUCT_SIZEOF(struct a_amv_mac, am_name)); amp->am_maxlen = maxlen; amp->am_line_cnt = line_cnt; amp->am_flags = amf; su_mem_copy(amp->am_name, name, n.s); /* C99 */{ struct a_amv_mac_line **amlpp; amp->am_line_dat = amlpp = n_alloc(sizeof(*amlpp) * ++line_cnt); for(llp = ll_head; llp != NULL; llp = llp->ll_next) *amlpp++ = llp->ll_amlp; *amlpp = NULL; } /* Create entry, replace a yet existing one as necessary */ a_amv_mac_lookup(name, amp, amf | a_AMV_MF_UNDEF); rv = TRU1; jleave: if(line.s != NULL) n_free(line.s); NYD2_OU; return rv; jerr: for(llp = ll_head; llp != NULL; llp = llp->ll_next) n_free(llp->ll_amlp); /* * if(amp != NULL){ * n_free(amp->am_line_dat); * n_free(amp); *}*/ goto jleave; } static boole a_amv_mac_undef(char const *name, enum a_amv_mac_flags amf){ struct a_amv_mac *amp; boole rv; NYD2_IN; rv = TRU1; if(LIKELY(name[0] != '*' || name[1] != '\0')){ if((amp = a_amv_mac_lookup(name, NULL, amf | a_AMV_MF_UNDEF)) == NULL){ n_err(_("%s not defined: %s\n"), (amf & a_AMV_MF_ACCOUNT ? "Account" : "Macro"), name); rv = FAL0; } }else{ struct a_amv_mac **ampp, *lamp; for(ampp = a_amv_macs; PCMP(ampp, <, &a_amv_macs[NELEM(a_amv_macs)]); ++ampp) for(lamp = NULL, amp = *ampp; amp != NULL;){ if((amp->am_flags & a_AMV_MF_TYPE_MASK) == amf){ /* xxx Expensive but rare: be simple */ a_amv_mac_lookup(amp->am_name, NULL, amf | a_AMV_MF_UNDEF); amp = (lamp == NULL) ? *ampp : lamp->am_next; }else{ lamp = amp; amp = amp->am_next; } } } NYD2_OU; return rv; } static void a_amv_mac_free(struct a_amv_mac *amp){ struct a_amv_mac_line **amlpp; NYD2_IN; for(amlpp = amp->am_line_dat; *amlpp != NULL; ++amlpp) n_free(*amlpp); n_free(amp->am_line_dat); n_free(amp); NYD2_OU; } static void a_amv_lopts_add(struct a_amv_lostack *alp, char const *name, struct a_amv_var *oavp){ struct a_amv_var *avp; uz nl, vl; NYD2_IN; /* Propagate unrolling up the stack, as necessary */ ASSERT(alp != NULL); for(;;){ if(alp->as_loflags & a_AMV_LF_SCOPE_MASK) break; if((alp = alp->as_up) == NULL) goto jleave; } /* Check whether this variable is handled yet XXX Boost: ID check etc.!! */ for(avp = alp->as_lopts; avp != NULL; avp = avp->av_link) if(!su_cs_cmp(avp->av_name, name)) goto jleave; nl = su_cs_len(name) +1; vl = (oavp != NULL) ? su_cs_len(oavp->av_value) +1 : 0; avp = n_calloc(1, VSTRUCT_SIZEOF(struct a_amv_var, av_name) + nl + vl); avp->av_link = alp->as_lopts; alp->as_lopts = avp; if(vl != 0){ avp->av_value = &avp->av_name[nl]; avp->av_flags = oavp->av_flags; su_mem_copy(avp->av_value, oavp->av_value, vl); } su_mem_copy(avp->av_name, name, nl); jleave: NYD2_OU; } static void a_amv_lopts_unroll(struct a_amv_var **avpp){ struct a_amv_lostack *save_alp; struct a_amv_var *x, *avp; NYD2_IN; avp = *avpp; *avpp = NULL; save_alp = a_amv_lopts; a_amv_lopts = NULL; while(avp != NULL){ x = avp; avp = avp->av_link; n_PS_ROOT_BLOCK(n_var_vset(x->av_name, (up)x->av_value)); n_free(x); } a_amv_lopts = save_alp; NYD2_OU; } static char * a_amv_var_copy(char const *str){ char *news; uz len; NYD2_IN; if(*str == '\0') news = n_UNCONST(n_empty); else if(str[1] == '\0'){ if(str[0] == '1') news = n_UNCONST(n_1); else if(str[0] == '0') news = n_UNCONST(n_0); else goto jheap; }else if(str[2] == '\0' && str[0] == '-' && str[1] == '1') news = n_UNCONST(n_m1); else{ jheap: len = su_cs_len(str) +1; news = n_alloc(len); su_mem_copy(news, str, len); } NYD2_OU; return news; } static void a_amv_var_free(char *cp){ NYD2_IN; if(cp[0] != '\0' && cp != n_0 && cp != n_1 && cp != n_m1) n_free(cp); NYD2_OU; } static boole a_amv_var_check_vips(enum a_amv_var_vip_mode avvm, enum okeys okey, char const **val){ char const *emsg; boole ok; NYD2_IN; ok = TRU1; if(avvm == a_AMV_VIP_SET_PRE){ switch(okey){ default: break; case ok_v_charset_7bit: case ok_v_charset_8bit: case ok_v_charset_unknown_8bit: case ok_v_ttycharset: if((*val = n_iconv_normalize_name(*val)) == NULL) ok = FAL0; break; case ok_v_customhdr:{ char const *vp; char *buf; struct n_header_field *hflp, **hflpp, *hfp; buf = savestr(*val); hflp = NULL; hflpp = &hflp; while((vp = su_cs_sep_escable_c(&buf, ',', TRU1)) != NULL){ if(!n_header_add_custom(hflpp, vp, TRU1)){ emsg = N_("Invalid *customhdr* entry: %s\n"); ok = FAL0; break; } hflpp = &(*hflpp)->hf_next; } hflpp = ok ? &n_customhdr_list : &hflp; while((hfp = *hflpp) != NULL){ *hflpp = hfp->hf_next; n_free(hfp); } if(!ok) goto jerr; n_customhdr_list = hflp; }break; case ok_v_from: case ok_v_sender:{ struct mx_name *np; np = (okey == ok_v_sender ? n_extract_single : lextract )(*val, GEXTRA | GFULL); if(np == NIL){ jefrom: emsg = N_("*from* / *sender*: invalid address(es): %s\n"); goto jerr; }else for(; np != NIL; np = np->n_flink) if(is_addr_invalid(np, EACM_STRICT | EACM_NOLOG | EACM_NONAME)) goto jefrom; }break; case ok_v_HOME: /* Note this gets called from main.c during initialization, and they * simply set this to pw_dir as a fallback: don't verify _that_ call. * See main.c! */ if(!(n_pstate & n_PS_ROOT) && !n_is_dir(*val, TRUM1)){ emsg = N_("$HOME is not a directory or not accessible: %s\n"); goto jerr; } break; case ok_v_hostname: case ok_v_smtp_hostname: #ifdef mx_HAVE_IDNA if(**val != '\0'){ struct n_string cnv; n_string_creat_auto(&cnv); if(!n_idna_to_ascii(&cnv, *val, UZ_MAX)){ /*n_string_gut(&res);*/ emsg = N_("*hostname*/*smtp_hostname*: " "IDNA encoding failed: %s\n"); goto jerr; } *val = n_string_cp(&cnv); /*n_string_drop_ownership(&cnv);*/ } #endif break; case ok_v_quote_chars:{ char c; char const *cp; for(cp = *val; (c = *cp++) != '\0';) if(!su_cs_is_ascii(c) || su_cs_is_space(c)){ ok = FAL0; break; } }break; case ok_v_sendcharsets:{ struct n_string s_b, *s = &s_b; char *csv, *cp; s = n_string_creat_auto(s); csv = savestr(*val); while((cp = su_cs_sep_c(&csv, ',', TRU1)) != NULL){ if((cp = n_iconv_normalize_name(cp)) == NULL){ ok = FAL0; break; } if(s->s_len > 0) s = n_string_push_c(s, ','); s = n_string_push_cp(s, cp); } *val = n_string_cp(s); /* n_string_drop_ownership(so); */ }break; case ok_v_TMPDIR: if(!n_is_dir(*val, TRU1)){ emsg = N_("$TMPDIR is not a directory or not accessible: %s\n"); goto jerr; } break; case ok_v_umask: if(**val != '\0'){ u64 uib; su_idec_u64_cp(&uib, *val, 0, NULL); if(uib & ~0777u){ /* (is valid _VF_POSNUM) */ emsg = N_("Invalid *umask* setting: %s\n"); goto jerr; } } break; } }else if(avvm == a_AMV_VIP_SET_POST){ switch(okey){ default: break; case ok_b_ask: ok_bset(asksub); break; case ok_b_debug: n_poption |= n_PO_D; su_log_set_level(su_LOG_DEBUG); # define a_DEBUG_MEMCONF su_MEM_CONF_DEBUG | su_MEM_CONF_LINGER_FREE su_DBG( su_mem_set_conf(a_DEBUG_MEMCONF, TRU1); ) break; case ok_v_HOME: /* Invalidate any resolved folder then, too * FALLTHRU */ case ok_v_folder: n_PS_ROOT_BLOCK(ok_vclear(folder_resolved)); break; case ok_v_ifs:{ char *x_b, *x, c; char const *cp; cp = *val; x_b = x = n_autorec_alloc(su_cs_len(cp) +1); while((c = *cp++) != '\0') if(su_cs_is_space(c)) *x++ = c; *x = '\0'; n_PS_ROOT_BLOCK(ok_vset(ifs_ws, x_b)); }break; #ifdef mx_HAVE_SETLOCALE case ok_v_LANG: case ok_v_LC_ALL: case ok_v_LC_CTYPE: n_locale_init(); break; #endif case ok_b_memdebug: su_DBG( su_mem_set_conf(a_DEBUG_MEMCONF | su_MEM_CONF_ON_ERROR_EMERG, TRU1); ) break; case ok_b_POSIXLY_CORRECT: /* <-> *posix* */ if(!(n_pstate & n_PS_ROOT)) n_PS_ROOT_BLOCK(ok_bset(posix)); break; case ok_b_posix: /* <-> $POSIXLY_CORRECT */ if(!(n_pstate & n_PS_ROOT)) n_PS_ROOT_BLOCK(ok_bset(POSIXLY_CORRECT)); break; case ok_b_skipemptybody: n_poption |= n_PO_E_FLAG; break; case ok_b_typescript_mode: ok_bset(colour_disable); ok_bset(line_editor_disable); if(!(n_psonce & n_PSO_STARTED)) ok_bset(termcap_disable); break; case ok_v_umask: if(**val != '\0'){ u64 uib; su_idec_u64_cp(&uib, *val, 0, NULL); umask((mode_t)uib); } break; case ok_b_verbose: n_poption = (((n_poption & n_PO_V_MASK) << 1) & n_PO_V_MASK) | n_PO_V; if(!(n_poption & n_PO_D)) su_log_set_level(su_LOG_INFO); break; } }else{ switch(okey){ default: break; case ok_b_ask: ok_bclear(asksub); break; case ok_b_debug: n_poption &= ~n_PO_D; su_log_set_level((n_poption & n_PO_V) ? su_LOG_INFO : n_LOG_LEVEL); su_DBG( if(!ok_blook(memdebug)) su_mem_set_conf(a_DEBUG_MEMCONF, FAL0); ) break; case ok_v_customhdr:{ struct n_header_field *hfp; while((hfp = n_customhdr_list) != NULL){ n_customhdr_list = hfp->hf_next; n_free(hfp); } }break; case ok_v_HOME: /* Invalidate any resolved folder then, too * FALLTHRU */ case ok_v_folder: n_PS_ROOT_BLOCK(ok_vclear(folder_resolved)); break; case ok_b_memdebug: su_DBG( su_mem_set_conf((ok_blook(debug) ? 0 : a_DEBUG_MEMCONF) | su_MEM_CONF_ON_ERROR_EMERG, FAL0); ) #undef a_DEBUG_MEMCONF break; case ok_b_POSIXLY_CORRECT: /* <-> *posix* */ if(!(n_pstate & n_PS_ROOT)) n_PS_ROOT_BLOCK(ok_bclear(posix)); break; case ok_b_posix: /* <-> $POSIXLY_CORRECT */ if(!(n_pstate & n_PS_ROOT)) n_PS_ROOT_BLOCK(ok_bclear(POSIXLY_CORRECT)); break; case ok_b_skipemptybody: n_poption &= ~n_PO_E_FLAG; break; case ok_b_verbose: n_poption &= ~n_PO_V_MASK; if(!(n_poption & n_PO_D)) su_log_set_level(n_LOG_LEVEL); break; } } jleave: NYD2_OU; return ok; jerr: emsg = V_(emsg); n_err(emsg, n_shexp_quote_cp(*val, FAL0)); ok = FAL0; goto jleave; } static boole a_amv_var_check_num(char const *val, boole posnum){ /* TODO The internal/environment variables which are num= or posnum= should * TODO gain special lookup functions, or the return should be void* and * TODO castable to integer; i.e. no more strtoX() should be needed. * TODO I.e., the result of this function should instead be stored */ boole rv; NYD2_IN; rv = TRU1; if(*val != '\0'){ /* Would be _VF_NOTEMPTY if not allowed */ u64 uib; enum su_idec_state ids; ids = su_idec_cp(&uib, val, 0, (su_IDEC_MODE_LIMIT_32BIT | (posnum ? su_IDEC_MODE_SIGNED_TYPE : su_IDEC_MODE_NONE)), NULL); if((ids & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED) ) != su_IDEC_STATE_CONSUMED) rv = FAL0; /* TODO Unless we store integers we need to look and forbid, because * TODO callee may not be able to swallow, e.g., "-1" */ if(posnum && (ids & su_IDEC_STATE_SEEN_MINUS)) rv = FAL0; } NYD2_OU; return rv; } static boole a_amv_var_revlookup(struct a_amv_var_carrier *avcp, char const *name, boole try_harder){ u32 hash, i, j; struct a_amv_var_map const *avmp; char c; NYD2_IN; su_mem_set(avcp, 0, sizeof *avcp); /* XXX overkill, just set chain */ /* It may be a special a.k.a. macro-local or one-letter parameter */ c = name[0]; if(UNLIKELY(su_cs_is_digit(c))){ /* (Inline dec. atoi, ugh) */ for(j = (u8)c - '0', i = 1;; ++i){ c = name[i]; if(c == '\0') break; if(!su_cs_is_digit(c)) goto jno_special_param; j = j * 10 + (u8)c - '0'; } if(j <= a_AMV_POSPAR_MAX){ avcp->avc_special_cat = a_AMV_VSC_POSPAR; goto jspecial_param; } }else if(UNLIKELY(name[1] == '\0')){ switch(c){ case '?': case '!': avcp->avc_special_cat = a_AMV_VSC_GLOBAL; j = (c == '?') ? a_AMV_VST_QM : a_AMV_VST_EM; goto jspecial_param; case '^': goto jmultiplex; case '*': avcp->avc_special_cat = a_AMV_VSC_POSPAR_ENV; j = a_AMV_VST_STAR; goto jspecial_param; case '@': avcp->avc_special_cat = a_AMV_VSC_POSPAR_ENV; j = a_AMV_VST_AT; goto jspecial_param; case '#': avcp->avc_special_cat = a_AMV_VSC_POSPAR_ENV; j = a_AMV_VST_NOSIGN; goto jspecial_param; default: break; } }else if(c == '^'){ jmultiplex: avcp->avc_special_cat = a_AMV_VSC_MULTIPLEX; j = a_AMV_VST_CACC; goto jspecial_param; } /* This is nothing special, but a plain variable */ jno_special_param: ASSERT(a_AMV_VSC_NONE == 0);/*avcp->avc_special_cat = a_AMV_VSC_NONE;*/ avcp->avc_name = name; avcp->avc_hash = hash = a_AMV_NAME2HASH(name); /* Is it a known okey? Walk over the hashtable */ for(i = hash % a_AMV_VAR_REV_PRIME, j = 0; j <= a_AMV_VAR_REV_LONGEST; ++j){ u32 x; if((x = a_amv_var_revmap[i]) == a_AMV_VAR_REV_ILL) break; avmp = &a_amv_var_map[x]; if(avmp->avm_hash == hash && !su_cs_cmp(&a_amv_var_names[avmp->avm_keyoff], name)){ avcp->avc_map = avmp; avcp->avc_okey = (enum okeys)x; goto jleave; } if(++i == a_AMV_VAR_REV_PRIME){ #ifdef a_AMV_VAR_REV_WRAPAROUND i = 0; #else break; #endif } } /* Not a known key, but it may be a chain extension of one. * We possibly wanna know for a variety of reasons */ if(try_harder && a_amv_var_revlookup_chain(avcp, name)) goto jleave; ASSERT(avcp->avc_map == NULL);/*avcp->avc_map = NULL;*/ avcp = NULL; jleave: ASSERT(avcp == NULL || avcp->avc_map != NULL || avcp->avc_special_cat == a_AMV_VSC_NONE); NYD2_OU; return (avcp != NULL); /* All these are mapped to *--special-param* */ jspecial_param: avcp->avc_name = name; avcp->avc_special_prop = (u16)j; avmp = &a_amv_var_map[a_AMV_VAR__SPECIAL_PARAM_MAP_IDX]; avcp->avc_hash = avmp->avm_hash; avcp->avc_map = avmp; avcp->avc_okey = ok_v___special_param; goto jleave; } static boole a_amv_var_revlookup_chain(struct a_amv_var_carrier *avcp, char const *name){ uz i; struct a_amv_var_chain_map_bsrch const *avcmbp, *avcmbp_x; NYD_IN; if(su_cs_len(name) < su_FIELD_SIZEOF(struct a_amv_var_chain_map_bsrch, avcmb_prefix)){ avcp = NULL; goto jleave; } avcmbp = &a_amv_var_chain_map_bsrch[0]; i = a_AMV_VAR_CHAIN_MAP_BSRCH_CNT - 0; do{ int cres; avcmbp_x = &avcmbp[i >> 1]; cres = su_mem_cmp(name, avcmbp_x->avcmb_prefix, su_FIELD_SIZEOF(struct a_amv_var_chain_map_bsrch, avcmb_prefix)); if(cres != 0){ /* Go right instead? */ if(cres > 0){ avcmbp = ++avcmbp_x; --i; } }else{ /* Once the binary search found the right prefix we have to use * a linear walk from then on, because there is no "trigger" * character: anything could be something free-form or * a chain-extension, we just do not know. Unfortunately. * Luckily cramping the walk to a small window is possible */ struct a_amv_var_chain_map const *avcmp, *avcmp_hit; avcmp = &a_amv_var_chain_map[avcmbp_x->avcmb_chain_map_off]; avcmp_hit = NULL; do{ char c; char const *cp, *ncp; cp = &a_amv_var_names[avcmp->avcm_keyoff + su_FIELD_SIZEOF(struct a_amv_var_chain_map_bsrch, avcmb_prefix)]; ncp = &name[su_FIELD_SIZEOF(struct a_amv_var_chain_map_bsrch, avcmb_prefix)]; for(;; ++ncp, ++cp) if(*ncp != (c = *cp) || c == '\0') break; /* Is it a chain extension of this key? */ if(c == '\0' && *ncp == '-') avcmp_hit = avcmp; else if(avcmp_hit != NULL) break; }while((avcmp++)->avcm_okey < avcmbp_x->avcmb_chain_map_eokey); if(avcmp_hit != NULL){ avcp->avc_map = &a_amv_var_map[avcp->avc_okey = (enum okeys)avcmp_hit->avcm_okey]; avcp->avc_is_chain_variant = TRU1; goto jleave; } break; } }while((i >>= 1) > 0); avcp = NULL; jleave: NYD_OU; return (avcp != NULL); } static boole a_amv_var_lookup(struct a_amv_var_carrier *avcp, /* XXX too complicated! */ enum a_amv_var_lookup_flags avlf){ uz i; char const *cp; u32 f; struct a_amv_var_map const *avmp; struct a_amv_var *avp; NYD2_IN; ASSERT(!(avlf & a_AMV_VLOOK_LOCAL_ONLY) || (avlf & a_AMV_VLOOK_LOCAL)); ASSERT(!(avlf & a_AMV_VLOOK_I3VAL_NONEW_REPORT) || (avlf & a_AMV_VLOOK_I3VAL_NONEW)); ASSERT(!(avlf & a_AMV_VLOOK_BELLA_CIAO_CIAO_CIAO)); /* C99 */{ struct a_amv_var **avpp, *lavp; avcp->avc_prime = a_AMV_HASH2PRIME(avcp->avc_hash); /* Optionally macro-`local' variables first */ if(avlf & a_AMV_VLOOK_LOCAL){ if(a_amv_lopts != NULL && (avpp = *a_amv_lopts->as_amcap->amca_local_vars) != NULL){ avpp += avcp->avc_prime; for(lavp = NULL, avp = *avpp; avp != NULL; lavp = avp, avp = avp->av_link) if(!su_cs_cmp(avp->av_name, avcp->avc_name)){ /* Relink as head, hope it "sorts on usage" over time. * The code relies on this behaviour! */ if(lavp != NULL){ lavp->av_link = avp->av_link; avp->av_link = *avpp; *avpp = avp; } goto jleave; } } if(avlf & a_AMV_VLOOK_LOCAL_ONLY) goto jerr; } /* Global variable map */ avpp = &a_amv_vars[avcp->avc_prime]; for(lavp = NULL, avp = *avpp; avp != NULL; lavp = avp, avp = avp->av_link) if(!su_cs_cmp(avp->av_name, avcp->avc_name)){ /* Relink as head, hope it "sorts on usage" over time. * The code relies on this behaviour! */ if(lavp != NULL){ lavp->av_link = avp->av_link; avp->av_link = *avpp; *avpp = avp; } /* If this setting has been established via -S and we still have * not reached the _STARTED_CONFIG program state, it may have been * an explicit "clearance" that is to be treated as unset. * Because that is a special condition that (has been hacked in * later and) needs to be encapsulated in lower levels, but not * of interest if _set() or _clear() called us */ switch(avp->av_flags & a_AMV_VF_EXT__FROZEN_MASK){ case a_AMV_VF_EXT_FROZEN | a_AMV_VF_EXT_FROZEN_UNSET: if(!(avlf & a_AMV_VLOOK_I3VAL_NONEW)){ avcp->avc_var = avp; avp = NULL; goto j_leave; } /* FALLTHRU */ default: break; } goto jleave; } } /* If this is not an assembled variable we need to consider some special * initialization cases and eventually create the variable anew */ if(LIKELY((avmp = avcp->avc_map) != NULL)){ f = avmp->avm_flags; /* Does it have an import-from-environment flag? */ if(UNLIKELY((f & (a_AMV_VF_IMPORT | a_AMV_VF_ENV)) != 0)){ if(LIKELY((cp = getenv(avcp->avc_name)) != NULL)){ /* May be better not to use that one, though? */ /* TODO Outsource the tests into a _shared_ test function! */ boole isempty, isbltin; isempty = (*cp == '\0' && (f & a_AMV_VF_NOTEMPTY) != 0); isbltin = ((f & (a_AMV_VF_I3VAL | a_AMV_VF_DEFVAL)) != 0); if(UNLIKELY(isempty)){ n_err(_("Environment variable must not be empty: %s\n"), avcp->avc_name); if(!isbltin) goto jerr; }else if(LIKELY(*cp != '\0')){ if(UNLIKELY((f & a_AMV_VF_NUM) && !a_amv_var_check_num(cp, FAL0))){ n_err(_("Environment variable value not a number " "or out of range: %s\n"), avcp->avc_name); goto jerr; } if(UNLIKELY((f & a_AMV_VF_POSNUM) && !a_amv_var_check_num(cp, TRU1))){ n_err(_("Environment variable value not a number, " "negative or out of range: %s\n"), avcp->avc_name); goto jerr; } goto jnewval; }else goto jnewval; } } /* A first-time init switch is to be handled now and here */ if(UNLIKELY((f & a_AMV_VF_I3VAL) != 0)){ static struct a_amv_var_defval const **arr, *arr_base[a_AMV_VAR_I3VALS_CNT +1]; if(UNLIKELY(arr == NULL)){ arr = &arr_base[0]; arr[i = a_AMV_VAR_I3VALS_CNT] = NULL; while(i-- > 0) arr[i] = &a_amv_var_i3vals[i]; } for(i = 0; arr[i] != NULL; ++i) if(arr[i]->avdv_okey == avcp->avc_okey){ cp = (f & a_AMV_VF_BOOL) ? n_1 : arr[i]->avdv_value; /* Remove this entry, hope entire block becomes no-op asap */ do arr[i] = arr[i + 1]; while(arr[i++] != NULL); if(!(avlf & a_AMV_VLOOK_I3VAL_NONEW)) goto jnewval; if(avlf & a_AMV_VLOOK_I3VAL_NONEW_REPORT) avp = (struct a_amv_var*)-1; goto jleave; } } /* */ jdefval: if(UNLIKELY(f & a_AMV_VF_DEFVAL) != 0){ for(i = 0; i < a_AMV_VAR_DEFVALS_CNT; ++i) if(a_amv_var_defvals[i].avdv_okey == avcp->avc_okey){ cp = (f & a_AMV_VF_BOOL) ? n_1 : a_amv_var_defvals[i].avdv_value; goto jnewval; } } /* The virtual variables */ if(UNLIKELY((f & a_AMV_VF_VIRT) != 0)){ for(i = 0; i < a_AMV_VAR_VIRTS_CNT; ++i) if(a_amv_var_virts[i].avv_okey == avcp->avc_okey){ avp = n_UNCONST(a_amv_var_virts[i].avv_var); goto jleave; } /* Not reached */ } } jerr: avp = NULL; jleave: avcp->avc_var = avp; j_leave: if(UNLIKELY(!(avlf & a_AMV_VLOOK_I3VAL_NONEW)) && UNLIKELY(n_poption & n_PO_VVV) && avp != (struct a_amv_var*)-1 && avcp->avc_okey != ok_v_log_prefix){ /* I18N: Variable "name" is set to "value" */ n_err(_("*%s* is %s\n"), n_shexp_quote_cp(avcp->avc_name, FAL0), (avp == NULL ? _("not set") : ((avp->av_flags & a_AMV_VF_BOOL) ? _("boolean set") : n_shexp_quote_cp(avp->av_value, FAL0)))); } NYD2_OU; return (avp != NULL); jnewval: ASSERT(avmp != NULL); ASSERT(f == avmp->avm_flags); /* E.g., $TMPDIR may be set to non-existent, so we need to be able to catch * that and redirect to a possible default value */ if((f & a_AMV_VF_VIP) && !a_amv_var_check_vips(a_AMV_VIP_SET_PRE, avcp->avc_okey, &cp)){ #ifdef mx_HAVE_SETENV if(f & (a_AMV_VF_IMPORT | a_AMV_VF_ENV)) unsetenv(avcp->avc_name); #endif if(UNLIKELY(f & a_AMV_VF_DEFVAL) != 0){ if(avlf & a_AMV_VLOOK_BELLA_CIAO_CIAO_CIAO) n_panic(_("Cannot set *%s* to default value: %s"), n_shexp_quote_cp(avcp->avc_name, FAL0), n_shexp_quote_cp((cp == NIL ? su_empty : cp), FAL0)); avlf |= a_AMV_VLOOK_BELLA_CIAO_CIAO_CIAO; goto jdefval; } goto jerr; }else{ struct a_amv_var **avpp; uz l; l = su_cs_len(avcp->avc_name) +1; avcp->avc_var = avp = n_calloc(1, VSTRUCT_SIZEOF(struct a_amv_var, av_name) + l); avp->av_link = *(avpp = &a_amv_vars[avcp->avc_prime]); *avpp = avp; ASSERT(!avcp->avc_is_chain_variant); avp->av_flags = f; avp->av_value = a_amv_var_copy(cp); su_mem_copy(avp->av_name, avcp->avc_name, l); if(f & a_AMV_VF_ENV) a_amv_var__putenv(avcp, avp); if(f & a_AMV_VF_VIP) a_amv_var_check_vips(a_AMV_VIP_SET_POST, avcp->avc_okey, &cp); goto jleave; } } static char const * a_amv_var_vsc_global(struct a_amv_var_carrier *avcp){ char iencbuf[su_IENC_BUFFER_SIZE]; char const *rv; s32 *ep; struct a_amv_var_map const *avmp; NYD2_IN; /* Not function local, TODO but lazy evaluted for now */ if(avcp->avc_special_prop == a_AMV_VST_QM){ avmp = &a_amv_var_map[a_AMV_VAR__QM_MAP_IDX]; avcp->avc_okey = ok_v___qm; ep = &n_pstate_ex_no; }else{ avmp = &a_amv_var_map[a_AMV_VAR__EM_MAP_IDX]; avcp->avc_okey = ok_v___em; ep = &n_pstate_err_no; } /* XXX Oh heaven, we are responsible to ensure that $?/! is up-to-date * TODO we could num=1 ok_v___[qe]m, but the thing is still a string * TODO and thus conversion takes places over and over again; also * TODO for now that would have to occur before we set _that_ value * TODO so let's special treat it until we store ints as such */ switch(*ep){ case 0: rv = n_0; break; case 1: rv = n_1; break; default: rv = su_ienc(iencbuf, *ep, 10, su_IENC_MODE_SIGNED_TYPE); break; } n_PS_ROOT_BLOCK(n_var_okset(avcp->avc_okey, (up)rv)); avcp->avc_hash = avmp->avm_hash; avcp->avc_map = avmp; rv = a_amv_var_lookup(avcp, a_AMV_VLOOK_NONE) ? avcp->avc_var->av_value : NULL; NYD2_OU; return rv; } static char const * a_amv_var_vsc_multiplex(struct a_amv_var_carrier *avcp){ char iencbuf[su_IENC_BUFFER_SIZE]; s32 e; uz i; char const *rv; NYD2_IN; i = su_cs_len(rv = &avcp->avc_name[1]); /* ERR, ERRDOC, ERRNAME, plus *-NAME variants. * As well as ERRQUEUE-. */ if(rv[0] == 'E' && i >= 3 && rv[1] == 'R' && rv[2] == 'R'){ if(i == 3){ e = n_pstate_err_no; goto jeno; }else if(rv[3] == '-'){ e = su_err_from_name(&rv[4]); jeno: switch(e){ case 0: rv = n_0; break; case 1: rv = n_1; break; default: /* XXX Need to convert number to string yet */ rv = savestr(su_ienc(iencbuf, e, 10, su_IENC_MODE_SIGNED_TYPE)); break; } goto jleave; }else if(i >= 6){ if(!su_mem_cmp(&rv[3], "DOC", 3)){ rv += 6; switch(*rv){ case '\0': e = n_pstate_err_no; break; case '-': e = su_err_from_name(&rv[1]); break; default: goto jerr; } rv = su_err_doc(e); goto jleave; }else if(i >= 7 && !su_mem_cmp(&rv[3], "NAME", 4)){ rv += 7; switch(*rv){ case '\0': e = n_pstate_err_no; break; case '-': e = su_err_from_name(&rv[1]); break; default: goto jerr; } rv = su_err_name(e); goto jleave; }else if(i >= 14){ if(!su_mem_cmp(&rv[3], "QUEUE-COUNT", 11)){ if(rv[14] == '\0'){ e = 0 #ifdef mx_HAVE_ERRORS | n_pstate_err_cnt #endif ; goto jeno; } }else if(i >= 15 && !su_mem_cmp(&rv[3], "QUEUE-EXISTS", 12)){ if(rv[15] == '\0'){ e = 0 #ifdef mx_HAVE_ERRORS | (n_pstate_err_cnt != 0) #endif ; goto jeno; } } } } } jerr: rv = NULL; jleave: NYD2_OU; return rv; } static char const * a_amv_var_vsc_pospar(struct a_amv_var_carrier *avcp){ uz i, j; u16 argc; char const *rv, **argv; NYD2_IN; rv = NULL; /* If in a macro/xy.. */ if(a_amv_lopts != NULL){ boole ismacky; struct a_amv_mac_call_args *amcap; amcap = a_amv_lopts->as_amcap; argc = amcap->amca_pospar.app_count; argv = amcap->amca_pospar.app_dat; argv += amcap->amca_pospar.app_idx; /* ..in a `call'ed macro only, to be exact. Or in a_AMV_MACKY_MACK */ if(!(ismacky = (amcap->amca_amp == a_AMV_MACKY_MACK)) && (amcap->amca_ps_hook_mask || (amcap->amca_amp->am_flags & a_AMV_MF_TYPE_MASK ) == a_AMV_MF_ACCOUNT)) goto jleave; if(avcp->avc_special_cat == a_AMV_VSC_POSPAR){ if(avcp->avc_special_prop > 0){ if(argc >= avcp->avc_special_prop) rv = argv[avcp->avc_special_prop - 1]; }else if(ismacky) rv = amcap->amca_name; else rv = (a_amv_lopts->as_up != NULL ? a_amv_lopts->as_up->as_amcap->amca_name : n_empty); goto jleave; } /* MACKY_MACK doesn't know about [*@#] */ /*else*/ if(ismacky){ if(n_poption & n_PO_D_V) n_err(_("Cannot use $*/$@/$# in this context: %s\n"), n_shexp_quote_cp(avcp->avc_name, FAL0)); goto jleave; } }else{ argc = a_amv_pospar.app_count; argv = a_amv_pospar.app_dat; argv += a_amv_pospar.app_idx; if(avcp->avc_special_cat == a_AMV_VSC_POSPAR){ if(avcp->avc_special_prop > 0){ if(argc >= avcp->avc_special_prop) rv = argv[avcp->avc_special_prop - 1]; }else rv = su_program; goto jleave; } } switch(avcp->avc_special_prop){ /* XXX OPTIMIZE */ case a_AMV_VST_STAR:{ char sep; sep = *ok_vlook(ifs); if(0){ case a_AMV_VST_AT: sep = ' '; } for(i = j = 0; i < argc; ++i) j += su_cs_len(argv[i]) + 1; if(j == 0) rv = n_empty; else{ char *cp; rv = cp = n_autorec_alloc(j); for(i = j = 0; i < argc; ++i){ j = su_cs_len(argv[i]); su_mem_copy(cp, argv[i], j); cp += j; if(sep != '\0') *cp++ = sep; } if(sep != '\0') --cp; *cp = '\0'; } }break; case a_AMV_VST_NOSIGN:{ char iencbuf[su_IENC_BUFFER_SIZE]; rv = savestr(su_ienc(iencbuf, argc, 10, su_IENC_MODE_NONE)); }break; default: rv = n_empty; break; } jleave: NYD2_OU; return rv; } static boole a_amv_var_set(struct a_amv_var_carrier *avcp, char const *value, enum a_amv_var_setclr_flags avscf){ struct a_amv_var *avp; char *oval; struct a_amv_var_map const *avmp; boole rv; NYD2_IN; if(value == NULL){ rv = a_amv_var_clear(avcp, avscf); goto jleave; } if((avmp = avcp->avc_map) != NULL){ rv = FAL0; /* Validity checks */ if(UNLIKELY((avmp->avm_flags & a_AMV_VF_RDONLY) != 0 && !(n_pstate & n_PS_ROOT))){ value = N_("Variable is read-only: %s\n"); goto jeavmp; } if(UNLIKELY((avmp->avm_flags & a_AMV_VF_NOTEMPTY) && *value == '\0')){ value = N_("Variable must not be empty: %s\n"); goto jeavmp; } if(UNLIKELY((avmp->avm_flags & a_AMV_VF_NUM) && !a_amv_var_check_num(value, FAL0))){ value = N_("Variable value not a number or out of range: %s\n"); goto jeavmp; } if(UNLIKELY((avmp->avm_flags & a_AMV_VF_POSNUM) && !a_amv_var_check_num(value, TRU1))){ value = _("Variable value not a number, negative, " "or out of range: %s\n"); goto jeavmp; } if(UNLIKELY((avmp->avm_flags & a_AMV_VF_IMPORT) != 0 && !(n_psonce & n_PSO_STARTED) && !(n_pstate & n_PS_ROOT))){ value = N_("Variable cannot be set in a resource file: %s\n"); goto jeavmp; } /* Any more complicated inter-dependency? */ if(UNLIKELY((avmp->avm_flags & a_AMV_VF_VIP) != 0 && !a_amv_var_check_vips(a_AMV_VIP_SET_PRE, avcp->avc_okey, &value))){ value = N_("Assignment of variable aborted: %s\n"); jeavmp: n_err(V_(value), avcp->avc_name); goto jleave; } /* Transformations */ if(UNLIKELY(avmp->avm_flags & a_AMV_VF_LOWER)){ char c; oval = savestr(value); value = oval; for(; (c = *oval) != '\0'; ++oval) *oval = su_cs_to_lower(c); } /* Obsoletion warning */ if(UNLIKELY((avmp->avm_flags & a_AMV_VF_OBSOLETE) != 0)) n_OBSOLETE2(_("variable superseded or obsoleted"), avcp->avc_name); } /* Lookup possibly existing var. For */ rv = TRU1; a_amv_var_lookup(avcp, (a_AMV_VLOOK_I3VAL_NONEW | ((avscf & a_AMV_VSETCLR_LOCAL) ? (a_AMV_VLOOK_LOCAL | a_AMV_VLOOK_LOCAL_ONLY) : a_AMV_VLOOK_LOCAL))); avp = avcp->avc_var; /* A `local' setting is never covered by `localopts' nor frozen */ if(avscf & a_AMV_VSETCLR_LOCAL) goto jislocal; /* If this setting had been established via -S and we still have not reached * the _STARTED_CONFIG program state, silently ignore request! */ if(UNLIKELY(avp != NULL) && UNLIKELY((avp->av_flags & a_AMV_VF_EXT__FROZEN_MASK) != 0)){ if(!(n_psonce & n_PSO_STARTED_CONFIG)){ if((n_pstate & n_PS_ROOT) || (!(n_psonce & n_PSO_STARTED_GETOPT) && (n_poption & n_PO_S_FLAG_TEMPORARY))) goto joval_and_go; if(n_poption & n_PO_D_VV) n_err(_("Temporarily frozen by -S, not `set'ing: %s\n"), avcp->avc_name); goto jleave; } /* Otherwise, if -S freezing was an `unset' request, be very simple and * avoid tampering with that very special case we are not really prepared * for just one more line of code: throw the old thing away! */ if(!(avp->av_flags & a_AMV_VF_EXT_FROZEN_UNSET)) avp->av_flags &= ~a_AMV_VF_EXT__FROZEN_MASK; else{ ASSERT(avp->av_value == n_empty); ASSERT(a_amv_vars[avcp->avc_prime] == avp); a_amv_vars[avcp->avc_prime] = avp->av_link; n_free(avp); avcp->avc_var = avp = NULL; } } /* Optionally cover by `localopts' */ if(UNLIKELY(a_amv_lopts != NULL) && (avmp == NULL || !(avmp->avm_flags & a_AMV_VF_NOLOPTS))) a_amv_lopts_add(a_amv_lopts, avcp->avc_name, avcp->avc_var); jislocal: if(avp != NULL) joval_and_go: oval = avp->av_value; else{ uz l; struct a_amv_var **avpp; if(avscf & a_AMV_VSETCLR_LOCAL){ if((avpp = *a_amv_lopts->as_amcap->amca_local_vars) == NULL) avpp = *(a_amv_lopts->as_amcap->amca_local_vars = n_calloc(1, sizeof(*a_amv_lopts->as_amcap->amca_local_vars))); avpp += avcp->avc_prime; }else avpp = &a_amv_vars[avcp->avc_prime]; l = su_cs_len(avcp->avc_name) +1; avcp->avc_var = avp = n_calloc(1, VSTRUCT_SIZEOF(struct a_amv_var, av_name) + l); avp->av_link = *avpp; *avpp = avp; avp->av_flags = (((avscf & a_AMV_VSETCLR_LOCAL) ? a_AMV_VF_NOLOPTS | a_AMV_VF_EXT_LOCAL : ((avmp != NULL) ? avmp->avm_flags : 0)) | (avcp->avc_is_chain_variant ? a_AMV_VF_EXT_CHAIN : a_AMV_VF_NONE)); su_mem_copy(avp->av_name, avcp->avc_name, l); oval = n_UNCONST(n_empty); } if(avmp == NULL) avp->av_value = a_amv_var_copy(value); else{ ASSERT(!(avscf & a_AMV_VSETCLR_LOCAL)); /* Via `set' etc. the user may give even boolean options non-boolean * values, ignore that and force boolean */ if(!(avp->av_flags & a_AMV_VF_BOOL)) avp->av_value = a_amv_var_copy(value); else{ if(!(n_pstate & n_PS_ROOT) && (n_poption & n_PO_D_V) && *value != '\0') n_err(_("Ignoring value of boolean variable: %s: %s\n"), avcp->avc_name, value); avp->av_value = n_UNCONST(n_1); } } /* A `local' setting can skip all the crude special things */ if(!(avscf & a_AMV_VSETCLR_LOCAL)){ u32 f; f = avp->av_flags; if((avscf & a_AMV_VSETCLR_ENV) && !(f & a_AMV_VF_ENV)) f |= a_AMV_VF_EXT_LINKED; if(f & (a_AMV_VF_ENV | a_AMV_VF_EXT_LINKED)) rv = a_amv_var__putenv(avcp, avp); if(f & a_AMV_VF_VIP) a_amv_var_check_vips(a_AMV_VIP_SET_POST, avcp->avc_okey, &value); f &= ~a_AMV_VF_EXT__FROZEN_MASK; if(!(n_psonce & n_PSO_STARTED_GETOPT) && (n_poption & n_PO_S_FLAG_TEMPORARY) != 0) f |= a_AMV_VF_EXT_FROZEN; avp->av_flags = f; } a_amv_var_free(oval); jleave: NYD2_OU; return rv; } static boole a_amv_var__putenv(struct a_amv_var_carrier *avcp, struct a_amv_var *avp){ #ifndef mx_HAVE_SETENV char *cp; #endif boole rv; NYD2_IN; #ifdef mx_HAVE_SETENV rv = (setenv(avcp->avc_name, avp->av_value, 1) == 0); #else cp = su_cs_dup(savecatsep(avcp->avc_name, '=', avp->av_value), 0); if((rv = (putenv(cp) == 0))){ char *ocp; ocp = avp->av_env; avp->av_env = cp; cp = ocp; } if(cp != NULL) n_free(cp); #endif NYD2_OU; return rv; } static boole a_amv_var_clear(struct a_amv_var_carrier *avcp, enum a_amv_var_setclr_flags avscf){ struct a_amv_var **avpp, *avp; u32 f; struct a_amv_var_map const *avmp; boole rv; NYD2_IN; rv = FAL0; f = 0; if(LIKELY((avmp = avcp->avc_map) != NULL)){ if(UNLIKELY(((f = avmp->avm_flags) & a_AMV_VF_NODEL) != 0 && !(n_pstate & n_PS_ROOT))){ n_err(_("Variable may not be unset: %s\n"), avcp->avc_name); goto jleave; } if(UNLIKELY((f & a_AMV_VF_VIP) != 0 && !a_amv_var_check_vips(a_AMV_VIP_CLEAR, avcp->avc_okey, NULL))){ n_err(_("Clearance of variable aborted: %s\n"), avcp->avc_name); goto jleave; } } rv = TRU1; if(UNLIKELY(!a_amv_var_lookup(avcp, (((avscf & a_AMV_VSETCLR_LOCAL) ? (a_AMV_VLOOK_LOCAL | a_AMV_VLOOK_LOCAL_ONLY) : a_AMV_VLOOK_LOCAL) | a_AMV_VLOOK_I3VAL_NONEW | a_AMV_VLOOK_I3VAL_NONEW_REPORT)))){ ASSERT(avcp->avc_var == NULL); /* This may be a clearance request from the command line, via -S, and we * need to keep track of that! Unfortunately we are not prepared for * this, really, so we need to create a fake entry that is known and * handled correctly by the lowermost variable layer! * However, all this cannot happen for plain unset of `local' variables */ if(avscf & a_AMV_VSETCLR_LOCAL) goto jleave; if(UNLIKELY(!(n_psonce & n_PSO_STARTED_GETOPT)) && (n_poption & n_PO_S_FLAG_TEMPORARY)) Jfreeze:{ uz l; l = su_cs_len(avcp->avc_name) +1; avp = n_calloc(1, VSTRUCT_SIZEOF(struct a_amv_var, av_name) + l); avp->av_link = *(avpp = &a_amv_vars[avcp->avc_prime]); *avpp = avp; avp->av_value = n_UNCONST(n_empty); /* Sth. covered by _var_free()! */ ASSERT(f == (avmp != NULL ? avmp->avm_flags : 0)); avp->av_flags = f | a_AMV_VF_EXT_FROZEN | a_AMV_VF_EXT_FROZEN_UNSET; su_mem_copy(avp->av_name, avcp->avc_name, l); if((avscf & a_AMV_VSETCLR_ENV) || (f & a_AMV_VF_ENV)) a_amv_var__clearenv(avcp->avc_name, NULL); }else if(avscf & a_AMV_VSETCLR_ENV){ jforce_env: if(!(rv = a_amv_var__clearenv(avcp->avc_name, NULL))) goto jerr_env_unset; }else{ /* TODO "cannot unset undefined variable" not echoed in "ROBOT" state, * TODO should only be like that with "ignerr"! */ jerr_env_unset: if(!(n_pstate & (n_PS_ROOT | n_PS_ROBOT)) && (n_poption & n_PO_D_V)) n_err(_("Cannot unset undefined variable: %s\n"), avcp->avc_name); } goto jleave; }else if((avp = avcp->avc_var) == (struct a_amv_var*)-1){ /* Clearance request from command line, via -S? As above.. */ if(UNLIKELY(!(n_psonce & n_PSO_STARTED_GETOPT) && (n_poption & n_PO_S_FLAG_TEMPORARY) != 0)) goto Jfreeze; avcp->avc_var = NULL; if(avscf & a_AMV_VSETCLR_ENV) goto jforce_env; goto jleave; } ASSERT(avcp->avc_var != NULL); /* `local' variables bypass "frozen" checks and `localopts' coverage etc. */ if((f = avp->av_flags) & a_AMV_VF_EXT_LOCAL) goto jdefault_path; /* If this setting has been established via -S and we still have not reached * the _STARTED_CONFIG program state, silently ignore request! * XXX All this is very complicated for the tenth of a second */ /*else*/ if(UNLIKELY((f & a_AMV_VF_EXT__FROZEN_MASK) != 0)){ if(!(n_psonce & n_PSO_STARTED_CONFIG)){ if((n_pstate & n_PS_ROOT) || (!(n_psonce & n_PSO_STARTED_GETOPT) && (n_poption & n_PO_S_FLAG_TEMPORARY))){ /* Be aware this may turn a set into an unset! */ if(!(f & a_AMV_VF_EXT_FROZEN_UNSET)){ if(f & a_AMV_VF_DEFVAL) goto jdefault_path; a_amv_var_free(avp->av_value); f |= a_AMV_VF_EXT_FROZEN_UNSET; avp->av_flags = f; avp->av_value = n_UNCONST(n_empty); /* _var_free() covered */ if(f & (a_AMV_VF_ENV | a_AMV_VF_EXT_LINKED)) goto jforce_env; } goto jleave; } if(n_poption & n_PO_D_VV) n_err(_("Temporarily frozen by -S, not `unset'ting: %s\n"), avcp->avc_name); goto jleave; } f &= ~a_AMV_VF_EXT__FROZEN_MASK; avp->av_flags = f; } if(UNLIKELY(a_amv_lopts != NULL) && (avmp == NULL || !(avmp->avm_flags & a_AMV_VF_NOLOPTS))) a_amv_lopts_add(a_amv_lopts, avcp->avc_name, avcp->avc_var); jdefault_path: ASSERT(avp == avcp->avc_var); avcp->avc_var = NULL; avpp = &(((f = avp->av_flags) & a_AMV_VF_EXT_LOCAL) ? *a_amv_lopts->as_amcap->amca_local_vars : a_amv_vars )[avcp->avc_prime]; ASSERT(*avpp == avp); /* (always listhead after lookup()) */ *avpp = (*avpp)->av_link; if(f & (a_AMV_VF_ENV | a_AMV_VF_EXT_LINKED)) rv = a_amv_var__clearenv(avp->av_name, avp); a_amv_var_free(avp->av_value); n_free(avp); /* XXX Fun part, extremely simple-minded for now: if this variable has * XXX a default value, immediately reinstantiate it! TODO Heh? */ /* xxx Simply assuming we will never have default values for actual * xxx -HOST or -USER@HOST chain extensions */ if(UNLIKELY(avmp != NULL && (avmp->avm_flags & a_AMV_VF_DEFVAL) != 0)){ a_amv_var_lookup(avcp, a_AMV_VLOOK_I3VAL_NONEW); if(UNLIKELY(!(n_psonce & n_PSO_STARTED_GETOPT)) && (n_poption & n_PO_S_FLAG_TEMPORARY)) avcp->avc_var->av_flags |= a_AMV_VF_EXT_FROZEN; } jleave: NYD2_OU; return rv; } static boole a_amv_var__clearenv(char const *name, struct a_amv_var *avp){ extern char **environ; char **ecpp; boole rv; NYD2_IN; UNUSED(avp); rv = FAL0; ecpp = environ; #ifndef mx_HAVE_SETENV if(avp != NULL && avp->av_env != NULL){ for(; *ecpp != NULL; ++ecpp) if(*ecpp == avp->av_env){ do ecpp[0] = ecpp[1]; while(*ecpp++ != NULL); n_free(avp->av_env); avp->av_env = NULL; rv = TRU1; break; } }else #endif { uz l; if((l = su_cs_len(name)) > 0){ for(; *ecpp != NULL; ++ecpp) if(!strncmp(*ecpp, name, l) && (*ecpp)[l] == '='){ #ifdef mx_HAVE_SETENV unsetenv(name); #else do ecpp[0] = ecpp[1]; while(*ecpp++ != NULL); #endif rv = TRU1; break; } } } NYD2_OU; return rv; } static void a_amv_var_show_all(void){ struct n_string msg, *msgp; FILE *fp; uz no, i; struct a_amv_var *avp; char const **vacp, **cap; NYD2_IN; if((fp = mx_fs_tmp_open("setlist", (mx_FS_O_RDWR | mx_FS_O_UNLINK | mx_FS_O_REGISTER), NIL)) == NIL){ n_perr(_("`set' list: cannot create temporary file"), 0); goto jleave; } /* We need to instantiate first-time-inits and default values here, so that * they will be regular members of our _vars[] table */ for(i = a_AMV_VAR_I3VALS_CNT; i-- > 0;) n_var_oklook(a_amv_var_i3vals[i].avdv_okey); for(i = a_AMV_VAR_DEFVALS_CNT; i-- > 0;) n_var_oklook(a_amv_var_defvals[i].avdv_okey); for(no = i = 0; i < a_AMV_PRIME; ++i) for(avp = a_amv_vars[i]; avp != NULL; avp = avp->av_link) ++no; no += a_AMV_VAR_VIRTS_CNT; vacp = n_autorec_alloc(no * sizeof(*vacp)); for(cap = vacp, i = 0; i < a_AMV_PRIME; ++i) for(avp = a_amv_vars[i]; avp != NULL; avp = avp->av_link) *cap++ = avp->av_name; for(i = a_AMV_VAR_VIRTS_CNT; i-- > 0;) *cap++ = a_amv_var_virts[i].avv_var->av_name; if(no > 1) su_sort_shell_vpp(su_S(void const**,vacp), no, su_cs_toolbox.tb_compare); msgp = &msg; msgp = n_string_reserve(n_string_creat(msgp), 80); for(i = 0, cap = vacp; no != 0; ++cap, --no) i += a_amv_var_show(*cap, fp, msgp); n_string_gut(&msg); page_or_print(fp, i); mx_fs_close(fp); jleave: NYD2_OU; } static uz a_amv_var_show(char const *name, FILE *fp, struct n_string *msgp){ /* XXX a_amv_var_show(): if we iterate over all the actually set variables * XXX via a_amv_var_show_all() there is no need to call * XXX a_amv_var_revlookup() at all! Revisit this call chain */ struct a_amv_var_carrier avc; char const *quote; struct a_amv_var *avp; boole isset; uz i; NYD2_IN; msgp = n_string_trunc(msgp, 0); i = 0; a_amv_var_revlookup(&avc, name, TRU1); isset = a_amv_var_lookup(&avc, a_AMV_VLOOK_NONE); avp = avc.avc_var; if(n_poption & n_PO_D_V){ if(avc.avc_map == NULL){ msgp = n_string_push_cp(msgp, "#assembled variable with value"); i = 1; }else{ struct{ u16 flag; char msg[22]; } const tbase[] = { {a_AMV_VF_CHAIN, "variable chain"}, {a_AMV_VF_VIRT, "virtual"}, {a_AMV_VF_RDONLY, "read-only"}, {a_AMV_VF_NODEL, "nodelete"}, {a_AMV_VF_I3VAL, "initial-value"}, {a_AMV_VF_DEFVAL, "default-value"}, {a_AMV_VF_IMPORT, "import-environ-first\0"}, /* \0 fits longest */ {a_AMV_VF_ENV, "sync-environ"}, {a_AMV_VF_NOLOPTS, "no-localopts"}, {a_AMV_VF_NOTEMPTY, "notempty"}, {a_AMV_VF_NUM, "number"}, {a_AMV_VF_POSNUM, "positive-number"}, {a_AMV_VF_OBSOLETE, "obsoleted"}, }, *tp; ASSERT(!isset || ((avp->av_flags & a_AMV_VF__MASK) == (avc.avc_map->avm_flags & a_AMV_VF__MASK))); for(tp = tbase; PCMP(tp, <, &tbase[NELEM(tbase)]); ++tp) if(isset ? (avp->av_flags & tp->flag) : (avc.avc_map->avm_flags & tp->flag)){ msgp = n_string_push_c(msgp, (i++ == 0 ? '#' : ',')); msgp = n_string_push_cp(msgp, tp->msg); if(isset){ if((tp->flag == a_AMV_VF_CHAIN) && (avp->av_flags & a_AMV_VF_EXT_CHAIN)) msgp = n_string_push_cp(msgp, " (extension)"); } } } if(isset){ if(avp->av_flags & a_AMV_VF_EXT_FROZEN){ msgp = n_string_push_c(msgp, (i++ == 0 ? '#' : ',')); msgp = n_string_push_cp(msgp, "(un)?set via -S"); } } if(i > 0) msgp = n_string_push_cp(msgp, "\n "); } /* (Read-only variables are generally shown via comments..) */ if(!isset || (avp->av_flags & a_AMV_VF_RDONLY)){ msgp = n_string_push_c(msgp, n_ns[0]); if(!isset){ if(avc.avc_map != NULL && (avc.avc_map->avm_flags & a_AMV_VF_BOOL)) msgp = n_string_push_cp(msgp, "boolean; "); msgp = n_string_push_cp(msgp, "variable not set: "); msgp = n_string_push_cp(msgp, n_shexp_quote_cp(name, FAL0)); goto jleave; } } UNINIT(quote, NULL); if(!(avp->av_flags & a_AMV_VF_BOOL)){ quote = n_shexp_quote_cp(avp->av_value, TRU1); if(su_cs_cmp(quote, avp->av_value)) msgp = n_string_push_cp(msgp, "wysh "); }else if(n_poption & n_PO_D_V) msgp = n_string_push_cp(msgp, "wysh "); /* (for shell-style comment) */ if(avp->av_flags & a_AMV_VF_EXT_LINKED) msgp = n_string_push_cp(msgp, "environ "); msgp = n_string_push_cp(msgp, "set "); msgp = n_string_push_cp(msgp, name); if(!(avp->av_flags & a_AMV_VF_BOOL)){ msgp = n_string_push_c(msgp, '='); msgp = n_string_push_cp(msgp, quote); }else if(n_poption & n_PO_D_V) msgp = n_string_push_cp(msgp, " #boolean"); jleave: msgp = n_string_push_c(msgp, '\n'); fputs(n_string_cp(msgp), fp); NYD2_OU; return (i > 0 ? 2 : 1); } static boole a_amv_var_c_set(char **ap, enum a_amv_var_setclr_flags avscf){ char *cp, *cp2, *varbuf, c; uz errs; NYD2_IN; errs = 0; jouter: while((cp = *ap++) != NULL){ /* Isolate key */ cp2 = varbuf = n_autorec_alloc(su_cs_len(cp) +1); for(; (c = *cp) != '=' && c != '\0'; ++cp){ if(su_cs_is_cntrl(c) || su_cs_is_space(c)){ n_err(_("Variable name with control or space character ignored: " "%s\n"), ap[-1]); ++errs; goto jouter; } *cp2++ = c; } *cp2 = '\0'; if(c == '\0') cp = n_UNCONST(n_empty); else ++cp; if(varbuf == cp2){ n_err(_("Empty variable name ignored\n")); ++errs; }else{ struct a_amv_var_carrier avc; boole isunset; if((isunset = (varbuf[0] == 'n' && varbuf[1] == 'o'))){ if(c != '\0') n_err(_("Un`set'ting via \"no\" takes no value: %s=%s\n"), varbuf, n_shexp_quote_cp(cp, FAL0)); varbuf = &varbuf[2]; } a_amv_var_revlookup(&avc, varbuf, TRU1); if((avscf & a_AMV_VSETCLR_LOCAL) && avc.avc_map != NULL){ if(n_poption & n_PO_D_V) n_err(_("Builtin variable not overwritten by `local': %s\n"), varbuf); ++errs; }else if(isunset) errs += !a_amv_var_clear(&avc, avscf); else errs += !a_amv_var_set(&avc, cp, avscf); } } NYD2_OU; return (errs == 0); } FL int c_define(void *v){ int rv; char **args; NYD_IN; rv = 1; if((args = v)[0] == NULL){ rv = (a_amv_mac_show(a_AMV_MF_NONE) == FAL0); goto jleave; } if(args[1] == NULL || args[1][0] != '{' || args[1][1] != '\0' || args[2] != NULL){ n_err(_("Synopsis: define: {\n")); goto jleave; } rv = (a_amv_mac_def(args[0], a_AMV_MF_NONE) == FAL0); jleave: NYD_OU; return rv; } FL int c_undefine(void *v){ int rv; char **args; NYD_IN; rv = 0; args = v; do rv |= !a_amv_mac_undef(*args, a_AMV_MF_NONE); while(*++args != NULL); NYD_OU; return rv; } FL int c_call(void *vp){ int rv; NYD_IN; rv = a_amv_mac_call(vp, FAL0); NYD_OU; return rv; } FL int c_call_if(void *vp){ int rv; NYD_IN; rv = a_amv_mac_call(vp, TRU1); NYD_OU; return rv; } FL void mx_account_leave(void){ /* Note no care for *account* here */ struct a_amv_mac_call_args *amcap; struct a_amv_mac *amp; char const *cp; char *var; NYD_IN; if(a_amv_acc_curr != NIL){ /* Is there a cleanup hook? */ var = savecat("on-account-cleanup-", a_amv_acc_curr->am_name); if((cp = n_var_vlook(var, FAL0)) != NIL || (cp = ok_vlook(on_account_cleanup)) != NIL){ if((amp = a_amv_mac_lookup(cp, NIL, a_AMV_MF_NONE)) != NIL){ amcap = n_lofi_calloc(sizeof *amcap); amcap->amca_name = cp; amcap->amca_amp = amp; amcap->amca_unroller = &a_amv_acc_curr->am_lopts; amcap->amca_loflags = a_AMV_LF_SCOPE_FIXATE; amcap->amca_no_xcall = TRU1; n_pstate |= n_PS_HOOK; (void)a_amv_mac_exec(amcap); n_pstate &= ~n_PS_HOOK_MASK; }else n_err(_("*on-account-leave* hook %s does not exist\n"), n_shexp_quote_cp(cp, FAL0)); } /* `localopts'? */ if(a_amv_acc_curr->am_lopts != NIL) a_amv_lopts_unroll(&a_amv_acc_curr->am_lopts); /* For accounts this lingers */ --a_amv_acc_curr->am_refcnt; if(a_amv_acc_curr->am_flags & a_AMV_MF_DELETE) a_amv_mac_free(a_amv_acc_curr); } NYD_OU; } FL int c_account(void *v){ struct a_amv_mac_call_args *amcap; struct a_amv_mac *amp; char **args; int rv, i, oqf, nqf; NYD_IN; rv = 1; if((args = v)[0] == NULL){ rv = (a_amv_mac_show(a_AMV_MF_ACCOUNT) == FAL0); goto jleave; } if(args[1] && args[1][0] == '{' && args[1][1] == '\0'){ if(args[2] != NULL){ n_err(_("Synopsis: account: {\n")); goto jleave; } if(!su_cs_cmp_case(args[0], ACCOUNT_NULL)){ n_err(_("`account': cannot use reserved name: %s\n"), ACCOUNT_NULL); goto jleave; } rv = (a_amv_mac_def(args[0], a_AMV_MF_ACCOUNT) == FAL0); goto jleave; } if(n_pstate & n_PS_HOOK_MASK){ n_err(_("`account': cannot change account from within a hook\n")); goto jleave; } save_mbox_for_possible_quitstuff(); amp = NULL; if(su_cs_cmp_case(args[0], ACCOUNT_NULL) != 0 && (amp = a_amv_mac_lookup(args[0], NULL, a_AMV_MF_ACCOUNT)) == NULL){ n_err(_("`account': account does not exist: %s\n"), args[0]); goto jleave; } oqf = savequitflags(); /* Shutdown the active account */ if(a_amv_acc_curr != NULL) mx_account_leave(); a_amv_acc_curr = amp; /* And switch to any non-"null" account */ if(amp != NULL){ ASSERT(amp->am_lopts == NULL); amcap = n_lofi_calloc(sizeof *amcap); amcap->amca_name = amp->am_name; amcap->amca_amp = amp; amcap->amca_unroller = &->am_lopts; amcap->amca_loflags = a_AMV_LF_SCOPE_FIXATE; amcap->amca_no_xcall = TRU1; ++amp->am_refcnt; /* We may not run 0 to avoid being deleted! */ if(!a_amv_mac_exec(amcap)){ /* XXX account switch incomplete, unroll? */ n_err(_("`account': failed to switch to account: %s\n"), amp->am_name); goto jleave; } } n_PS_ROOT_BLOCK((amp != NULL ? ok_vset(account, amp->am_name) : ok_vclear(account))); /* Otherwise likely initial setfile() in a_main_rcv_mode() will pick up */ if(n_psonce & n_PSO_STARTED){ ASSERT(!(n_pstate & n_PS_HOOK_MASK)); nqf = savequitflags(); /* TODO obsolete (leave -> void -> new box!) */ restorequitflags(oqf); i = setfile("%", FEDIT_SYSBOX | FEDIT_ACCOUNT); restorequitflags(nqf); if(i < 0) goto jleave; temporary_folder_hook_check(FAL0); if(i != 0 && !ok_blook(emptystart)) /* Avoid annoying "double message" */ goto jleave; n_folder_announce(n_ANNOUNCE_CHANGE); } rv = 0; jleave: NYD_OU; return rv; } FL int c_unaccount(void *v){ int rv; char **args; NYD_IN; rv = 0; args = v; do rv |= !a_amv_mac_undef(*args, a_AMV_MF_ACCOUNT); while(*++args != NULL); NYD_OU; return rv; } FL int c_localopts(void *vp){ enum a_amv_loflags alf, alm; char const **argv; int rv; NYD_IN; rv = 1; if(a_amv_lopts == NULL){ n_err(_("Cannot use `localopts' in this context\n")); goto jleave; } if((argv = vp)[1] == NULL || su_cs_starts_with_case("scope", (++argv)[-1])) alf = alm = a_AMV_LF_SCOPE; else if(su_cs_starts_with_case("call", argv[-1])) alf = a_AMV_LF_CALL, alm = a_AMV_LF_CALL_MASK; else if(su_cs_starts_with_case("call-fixate", argv[-1])) alf = a_AMV_LF_CALL_FIXATE, alm = a_AMV_LF_CALL_MASK; else{ jesynopsis: n_err(_("Synopsis: localopts: [] \n")); goto jleave; } if(alf == a_AMV_LF_SCOPE && (a_amv_lopts->as_loflags & a_AMV_LF_SCOPE_FIXATE)){ if(n_poption & n_PO_D_V) n_err(_("Cannot turn off `localopts', setting is fixated\n")); goto jleave; } if((rv = n_boolify(*argv, UZ_MAX, FAL0)) < FAL0) goto jesynopsis; a_amv_lopts->as_loflags &= ~alm; if(rv > FAL0) a_amv_lopts->as_loflags |= alf; rv = 0; jleave: NYD_OU; return rv; } FL int c_shift(void *vp){ /* xxx move to bottom, not in macro part! */ struct a_amv_pospar *appp; u16 i; int rv; NYD_IN; rv = 1; if((vp = *(char**)vp) == NULL) i = 1; else{ s16 sib; if((su_idec_s16_cp(&sib, vp, 10, NULL ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED) ) != su_IDEC_STATE_CONSUMED || sib < 0){ n_err(_("`shift': invalid argument: %s\n"), vp); goto jleave; } i = (u16)sib; } /* If in in a macro/xy */ if(a_amv_lopts != NULL){ struct a_amv_mac const *amp; struct a_amv_mac_call_args *amcap; /* Explicitly do allow `vpospar' created things! */ amp = (amcap = a_amv_lopts->as_amcap)->amca_amp; if((amp == NULL || amcap->amca_ps_hook_mask || (amp->am_flags & a_AMV_MF_TYPE_MASK) == a_AMV_MF_ACCOUNT) && amcap->amca_pospar.app_not_heap){ n_err(_("Cannot use `shift' in `account's or hook macros etc.\n")); goto jleave; } appp = &amcap->amca_pospar; }else appp = &a_amv_pospar; if(i > appp->app_count){ n_err(_("`shift': cannot shift %hu of %hu parameters\n"), i, appp->app_count); goto jleave; }else{ appp->app_idx += i; appp->app_count -= i; rv = 0; } jleave: NYD_OU; return rv; } FL int c_return(void *vp){ /* TODO the exit status should be m_si64! */ int rv; NYD_IN; if(a_amv_lopts != NULL){ char const **argv; n_go_input_force_eof(); n_pstate_err_no = su_ERR_NONE; rv = 0; if((argv = vp)[0] != NULL){ s32 i; if((su_idec_s32_cp(&i, argv[0], 10, NULL ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED) ) == su_IDEC_STATE_CONSUMED && i >= 0) rv = (int)i; else{ n_err(_("`return': return value argument is invalid: %s\n"), argv[0]); n_pstate_err_no = su_ERR_INVAL; rv = 1; } if(argv[1] != NULL){ if((su_idec_s32_cp(&i, argv[1], 10, NULL ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED) ) == su_IDEC_STATE_CONSUMED && i >= 0) n_pstate_err_no = i; else{ n_err(_("`return': error number argument is invalid: %s\n"), argv[1]); n_pstate_err_no = su_ERR_INVAL; rv = 1; } } } }else{ n_err(_("Can only use `return' in a macro\n")); n_pstate_err_no = su_ERR_OPNOTSUPP; rv = 1; } NYD_OU; return rv; } FL void temporary_on_xy_hook_caller(char const *hname, char const *mac, boole sigs_held){ struct a_amv_mac_call_args *amcap; struct a_amv_mac *amp; NYD_IN; if(mac != NIL){ if((amp = a_amv_mac_lookup(mac, NIL, a_AMV_MF_NONE)) != NIL){ if(sigs_held) mx_sigs_all_rele(); amcap = n_lofi_calloc(sizeof *amcap); amcap->amca_name = mac; amcap->amca_amp = amp; amcap->amca_ps_hook_mask = TRU1; amcap->amca_no_xcall = TRU1; n_pstate &= ~n_PS_HOOK_MASK; n_pstate |= n_PS_HOOK; a_amv_mac_exec(amcap); if(sigs_held) mx_sigs_all_holdx(); }else n_err(_("*%s* macro does not exist: %s\n"), hname, mac); } NYD_OU; } FL boole temporary_folder_hook_check(boole nmail){ /* TODO temporary, v15: drop */ struct a_amv_mac_call_args *amcap; struct a_amv_mac *amp; uz len; char const *cp; char *var; boole rv; NYD_IN; rv = TRU1; var = n_autorec_alloc(len = su_cs_len(mailname) + sizeof("folder-hook-") -1 +1); /* First try the fully resolved path */ snprintf(var, len, "folder-hook-%s", mailname); if((cp = n_var_vlook(var, FAL0)) != NULL) goto jmac; /* If we are under *folder*, try the usual +NAME syntax, too */ if(displayname[0] == '+'){ char *x; for(x = &mailname[len]; x != mailname; --x) if(x[-1] == '/'){ snprintf(var, len, "folder-hook-+%s", x); if((cp = n_var_vlook(var, FAL0)) != NULL) goto jmac; break; } } /* Plain *folder-hook* is our last try */ if((cp = ok_vlook(folder_hook)) == NULL) goto jleave; jmac: if((amp = a_amv_mac_lookup(cp, NULL, a_AMV_MF_NONE)) == NULL){ n_err(_("Cannot call *folder-hook* for %s: macro does not exist: %s\n"), n_shexp_quote_cp(displayname, FAL0), cp); rv = FAL0; goto jleave; } amcap = n_lofi_calloc(sizeof *amcap); amcap->amca_name = cp; amcap->amca_amp = amp; n_pstate &= ~n_PS_HOOK_MASK; if(nmail){ amcap->amca_unroller = NULL; n_pstate |= n_PS_HOOK_NEWMAIL; }else{ amcap->amca_unroller = &a_amv_folder_hook_lopts; n_pstate |= n_PS_HOOK; } amcap->amca_loflags = a_AMV_LF_SCOPE_FIXATE; amcap->amca_ps_hook_mask = TRU1; amcap->amca_no_xcall = TRU1; rv = a_amv_mac_exec(amcap); n_pstate &= ~n_PS_HOOK_MASK; jleave: NYD_OU; return rv; } FL void temporary_folder_hook_unroll(void){ /* XXX intermediate hack */ NYD_IN; if(a_amv_folder_hook_lopts != NULL){ void *save = a_amv_lopts; a_amv_lopts = NULL; a_amv_lopts_unroll(&a_amv_folder_hook_lopts); ASSERT(a_amv_folder_hook_lopts == NULL); a_amv_lopts = save; } NYD_OU; } FL void temporary_compose_mode_hook_call(char const *macname, void (*hook_pre)(void *), void *hook_arg){ /* TODO compose_mode_hook_call() temporary, v15: generalize; see a_GO_SPLICE * TODO comment in go.c for the right way of doing things! */ static struct a_amv_lostack *cmh_losp; struct a_amv_mac_call_args *amcap; struct a_amv_mac *amp; NYD_IN; amp = NULL; if(macname == (char*)-1){ a_amv_mac__finalize(cmh_losp); cmh_losp = NULL; }else if(macname != NULL && (amp = a_amv_mac_lookup(macname, NULL, a_AMV_MF_NONE)) == NULL) n_err(_("Cannot call *on-compose-**: macro does not exist: %s\n"), macname); else{ amcap = n_lofi_calloc(sizeof *amcap); amcap->amca_name = (macname != NULL) ? macname : "*on-compose-splice(-shell)?*"; amcap->amca_amp = amp; amcap->amca_unroller = &a_amv_compose_lopts; amcap->amca_hook_pre = hook_pre; amcap->amca_hook_arg = hook_arg; amcap->amca_loflags = a_AMV_LF_SCOPE_FIXATE; amcap->amca_ps_hook_mask = TRU1; amcap->amca_no_xcall = TRU1; n_pstate &= ~n_PS_HOOK_MASK; n_pstate |= n_PS_HOOK; if(macname != NULL) a_amv_mac_exec(amcap); else{ cmh_losp = n_lofi_calloc(sizeof *cmh_losp); cmh_losp->as_global_saved = a_amv_lopts; cmh_losp->as_lopts = *(cmh_losp->as_amcap = amcap)->amca_unroller; cmh_losp->as_loflags = a_AMV_LF_SCOPE_FIXATE; a_amv_lopts = cmh_losp; } } NYD_OU; } FL void temporary_compose_mode_hook_unroll(void){ /* XXX intermediate hack */ NYD_IN; if(a_amv_compose_lopts != NULL){ void *save = a_amv_lopts; a_amv_lopts = NULL; a_amv_lopts_unroll(&a_amv_compose_lopts); ASSERT(a_amv_compose_lopts == NULL); a_amv_lopts = save; } NYD_OU; } #ifdef mx_HAVE_HISTORY FL boole temporary_addhist_hook(char const *ctx, boole gabby, char const *histent){ /* XXX temporary_addhist_hook(): intermediate hack */ struct a_amv_mac_call_args *amcap; s32 perrn, pexn; struct a_amv_mac *amp; char const *macname, *argv[4]; boole rv; NYD_IN; if((macname = ok_vlook(on_history_addition)) == NULL) rv = TRUM1; else if((amp = a_amv_mac_lookup(macname, NULL, a_AMV_MF_NONE)) == NULL){ n_err(_("Cannot call *on-history-addition*: macro does not exist: %s\n"), macname); rv = TRUM1; }else{ perrn = n_pstate_err_no; pexn = n_pstate_ex_no; argv[0] = ctx; argv[1] = gabby ? n_1 : n_0; argv[2] = histent; argv[3] = NULL; amcap = n_lofi_calloc(sizeof *amcap); amcap->amca_name = macname; amcap->amca_amp = amp; amcap->amca_loflags = a_AMV_LF_SCOPE_FIXATE; amcap->amca_no_xcall = amcap->amca_ignerr = TRU1; amcap->amca_pospar.app_count = 3; amcap->amca_pospar.app_not_heap = TRU1; amcap->amca_pospar.app_dat = argv; if(!a_amv_mac_exec(amcap)) rv = TRUM1; else rv = (n_pstate_ex_no == 0); n_pstate_err_no = perrn; n_pstate_ex_no = pexn; } NYD_OU; return rv; } #endif /* mx_HAVE_HISTORY */ #ifdef mx_HAVE_REGEX FL char * temporary_pospar_access_hook(char const *name, char const **argv, u32 argc, char *(*hook)(void *uservp), void *uservp){ struct a_amv_lostack los; struct a_amv_mac_call_args amca; char *rv; NYD_IN; su_mem_set(&amca, 0, sizeof amca); amca.amca_name = name; amca.amca_amp = a_AMV_MACKY_MACK; amca.amca_pospar.app_count = argc; amca.amca_pospar.app_not_heap = TRU1; amca.amca_pospar.app_dat = argv; su_mem_set(&los, 0, sizeof los); mx_sigs_all_holdx(); /* TODO DISLIKE! */ los.as_global_saved = a_amv_lopts; los.as_amcap = &amca; los.as_up = los.as_global_saved; a_amv_lopts = &los; rv = (*hook)(uservp); a_amv_lopts = los.as_global_saved; mx_sigs_all_rele(); /* TODO DISLIKE! */ NYD_OU; return rv; } #endif /* mx_HAVE_REGEX */ FL void n_var_setup_batch_mode(void){ NYD2_IN; n_pstate |= n_PS_ROBOT; /* (be silent unsetting undefined variables) */ n_poption |= n_PO_S_FLAG_TEMPORARY; ok_vset(MAIL, n_path_devnull); ok_vset(MBOX, n_path_devnull); ok_bset(emptystart); ok_bclear(errexit); ok_bclear(header); ok_vset(inbox, n_path_devnull); ok_bclear(posix); ok_bset(quiet); ok_vset(sendwait, su_empty); ok_bset(typescript_mode); n_poption &= ~n_PO_S_FLAG_TEMPORARY; n_pstate &= ~n_PS_ROBOT; NYD2_OU; } FL boole n_var_is_user_writable(char const *name){ struct a_amv_var_carrier avc; struct a_amv_var_map const *avmp; boole rv; NYD_IN; a_amv_var_revlookup(&avc, name, TRU1); if((avmp = avc.avc_map) == NULL) rv = TRU1; else rv = ((avmp->avm_flags & (a_AMV_VF_BOOL | a_AMV_VF_RDONLY)) == 0); NYD_OU; return rv; } FL char * n_var_oklook(enum okeys okey){ struct a_amv_var_carrier avc; char *rv; struct a_amv_var_map const *avmp; NYD_IN; su_mem_set(&avc, 0, sizeof avc); avc.avc_map = avmp = &a_amv_var_map[okey]; avc.avc_name = &a_amv_var_names[avmp->avm_keyoff]; avc.avc_hash = avmp->avm_hash; avc.avc_okey = okey; if(a_amv_var_lookup(&avc, a_AMV_VLOOK_NONE)) rv = avc.avc_var->av_value; else rv = NULL; NYD_OU; return rv; } FL boole n_var_okset(enum okeys okey, up val){ struct a_amv_var_carrier avc; boole ok; struct a_amv_var_map const *avmp; NYD_IN; su_mem_set(&avc, 0, sizeof avc); avc.avc_map = avmp = &a_amv_var_map[okey]; avc.avc_name = &a_amv_var_names[avmp->avm_keyoff]; avc.avc_hash = avmp->avm_hash; avc.avc_okey = okey; ok = a_amv_var_set(&avc, (val == 0x1 ? n_empty : (char const*)val), a_AMV_VSETCLR_NONE); NYD_OU; return ok; } FL boole n_var_okclear(enum okeys okey){ struct a_amv_var_carrier avc; boole rv; struct a_amv_var_map const *avmp; NYD_IN; su_mem_set(&avc, 0, sizeof avc); avc.avc_map = avmp = &a_amv_var_map[okey]; avc.avc_name = &a_amv_var_names[avmp->avm_keyoff]; avc.avc_hash = avmp->avm_hash; avc.avc_okey = okey; rv = a_amv_var_clear(&avc, a_AMV_VSETCLR_NONE); NYD_OU; return rv; } FL char const * n_var_vlook(char const *vokey, boole try_getenv){ struct a_amv_var_carrier avc; char const *rv; NYD_IN; a_amv_var_revlookup(&avc, vokey, FAL0); switch((enum a_amv_var_special_category)avc.avc_special_cat){ default: /* silence CC */ case a_AMV_VSC_NONE: rv = NULL; if(a_amv_var_lookup(&avc, a_AMV_VLOOK_LOCAL)) rv = avc.avc_var->av_value; /* Only check the environment for something that is otherwise unknown */ else if(try_getenv && avc.avc_map == NULL && !a_amv_var_revlookup_chain(&avc, vokey)) rv = getenv(vokey); break; case a_AMV_VSC_GLOBAL: rv = a_amv_var_vsc_global(&avc); break; case a_AMV_VSC_MULTIPLEX: rv = a_amv_var_vsc_multiplex(&avc); break; case a_AMV_VSC_POSPAR: case a_AMV_VSC_POSPAR_ENV: rv = a_amv_var_vsc_pospar(&avc); break; } NYD_OU; return rv; } FL boole n_var_vexplode(void const **cookie){ struct a_amv_pospar *appp; NYD_IN; appp = (a_amv_lopts != NULL) ? &a_amv_lopts->as_amcap->amca_pospar : &a_amv_pospar; *cookie = (appp->app_count > 0) ? &appp->app_dat[appp->app_idx] : NULL; NYD_OU; return (*cookie != NULL); } FL boole n_var_vset(char const *vokey, up val){ struct a_amv_var_carrier avc; boole ok; NYD_IN; a_amv_var_revlookup(&avc, vokey, TRU1); ok = a_amv_var_set(&avc, (val == 0x1 ? n_empty : (char const*)val), a_AMV_VSETCLR_NONE); NYD_OU; return ok; } FL boole n_var_vclear(char const *vokey){ struct a_amv_var_carrier avc; boole ok; NYD_IN; a_amv_var_revlookup(&avc, vokey, FAL0); ok = a_amv_var_clear(&avc, a_AMV_VSETCLR_NONE); NYD_OU; return ok; } #ifdef mx_HAVE_NET FL char * n_var_xoklook(enum okeys okey, struct mx_url const *urlp, enum okey_xlook_mode oxm){ struct a_amv_var_carrier avc; struct str const *usp; uz nlen; char *nbuf, *rv; NYD_IN; ASSERT(oxm & (OXM_PLAIN | OXM_H_P | OXM_U_H_P)); /* For simplicity: allow this case too */ if(!(oxm & (OXM_H_P | OXM_U_H_P))){ nbuf = NULL; goto jplain; } su_mem_set(&avc, 0, sizeof avc); avc.avc_name = &a_amv_var_names[(avc.avc_map = &a_amv_var_map[okey] )->avm_keyoff]; avc.avc_okey = okey; avc.avc_is_chain_variant = TRU1; usp = (oxm & OXM_U_H_P) ? &urlp->url_u_h_p : &urlp->url_h_p; nlen = su_cs_len(avc.avc_name); nbuf = n_lofi_alloc(nlen + 1 + usp->l +1); su_mem_copy(nbuf, avc.avc_name, nlen); /* One of .url_u_h_p and .url_h_p we test in here */ nbuf[nlen++] = '-'; su_mem_copy(&nbuf[nlen], usp->s, usp->l +1); avc.avc_name = nbuf; avc.avc_hash = a_AMV_NAME2HASH(avc.avc_name); if(a_amv_var_lookup(&avc, a_AMV_VLOOK_NONE)) goto jvar; /* The second */ if((oxm & (OXM_U_H_P | OXM_H_P)) == (OXM_U_H_P | OXM_H_P)){ usp = &urlp->url_h_p; su_mem_copy(&nbuf[nlen], usp->s, usp->l +1); avc.avc_name = nbuf; avc.avc_hash = a_AMV_NAME2HASH(avc.avc_name); if(a_amv_var_lookup(&avc, a_AMV_VLOOK_NONE)){ jvar: rv = avc.avc_var->av_value; goto jleave; } } jplain: /*avc.avc_is_chain_variant = FAL0;*/ rv = (oxm & OXM_PLAIN) ? n_var_oklook(okey) : NULL; jleave: if(nbuf != NULL) n_lofi_free(nbuf); NYD_OU; return rv; } #endif /* mx_HAVE_NET */ FL int c_set(void *vp){ int err; char **ap; NYD_IN; if(*(ap = vp) == NULL){ a_amv_var_show_all(); err = 0; }else{ enum a_amv_var_setclr_flags avscf; if(!(n_pstate & n_PS_ARGMOD_LOCAL)) avscf = a_AMV_VSETCLR_NONE; else{ if(a_amv_lopts == NULL){ n_err(_("`set': cannot use `local' in this context\n")); err = 1; goto jleave; } avscf = a_AMV_VSETCLR_LOCAL; } err = !a_amv_var_c_set(ap, avscf); } jleave: NYD_OU; return err; } FL int c_unset(void *vp){ struct a_amv_var_carrier avc; char **ap; int err; enum a_amv_var_setclr_flags avscf; NYD_IN; if(!(n_pstate & n_PS_ARGMOD_LOCAL)) avscf = a_AMV_VSETCLR_NONE; else{ if(a_amv_lopts == NULL){ n_err(_("`unset': cannot use `local' in this context\n")); err = 1; goto jleave; } avscf = a_AMV_VSETCLR_LOCAL; } for(err = 0, ap = vp; *ap != NULL; ++ap){ a_amv_var_revlookup(&avc, *ap, FAL0); err |= !a_amv_var_clear(&avc, avscf); } jleave: NYD_OU; return err; } FL int c_varshow(void *v){ char **ap; NYD_IN; if(*(ap = v) == NULL) v = NULL; else{ struct n_string msg, *msgp = &msg; msgp = n_string_creat(msgp); for(; *ap != NULL; ++ap) a_amv_var_show(*ap, n_stdout, msgp); n_string_gut(msgp); } NYD_OU; return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */ } FL int c_varedit(void *v){ /* TODO v15 drop */ struct a_amv_var_carrier avc; FILE *of, *nf; char *val, **argv; int err; n_sighdl_t sigint; NYD_IN; sigint = safe_signal(SIGINT, SIG_IGN); for(err = 0, argv = v; *argv != NULL; ++argv){ a_amv_var_revlookup(&avc, *argv, TRU1); if(avc.avc_map != NULL){ if(avc.avc_map->avm_flags & a_AMV_VF_BOOL){ n_err(_("`varedit': cannot edit boolean variable: %s\n"), avc.avc_name); continue; } if(avc.avc_map->avm_flags & a_AMV_VF_RDONLY){ n_err(_("`varedit': cannot edit read-only variable: %s\n"), avc.avc_name); continue; } } a_amv_var_lookup(&avc, a_AMV_VLOOK_NONE); if((of = mx_fs_tmp_open("varedit", (mx_FS_O_RDWR | mx_FS_O_UNLINK | mx_FS_O_REGISTER), NIL)) == NIL){ n_perr(_("`varedit': cannot create temporary file"), 0); err = 1; break; }else if(avc.avc_var != NULL && *(val = avc.avc_var->av_value) != '\0' && sizeof *val != fwrite(val, su_cs_len(val), sizeof *val, of)){ n_perr(_("`varedit' failed to write old value to temporary file"), 0); mx_fs_close(of); err = 1; continue; } fflush_rewind(of); nf = n_run_editor(of, (off_t)-1, 'e', FAL0, NULL,NULL, SEND_MBOX, sigint, NULL); mx_fs_close(of); if(nf != NULL){ int c; char const *varres; off_t l; l = fsize(nf); if(UCMP(64, l, >=, UZ_MAX -42)){ n_err(_("`varedit': not enough memory to store variable: %s\n"), avc.avc_name); varres = n_empty; err = 1; }else{ varres = val = n_autorec_alloc(l +1); for(; l > 0 && (c = getc(nf)) != EOF; --l) *val++ = c; *val++ = '\0'; if(l != 0){ n_err(_("`varedit': I/O while reading new value of: %s\n"), avc.avc_name); err = 1; } } if(!a_amv_var_set(&avc, varres, a_AMV_VSETCLR_NONE)) err = 1; mx_fs_close(nf); }else{ n_err(_("`varedit': cannot start $EDITOR\n")); err = 1; break; } } safe_signal(SIGINT, sigint); NYD_OU; return err; } FL int c_environ(void *v){ struct a_amv_var_carrier avc; int err; char **ap; boole islnk; NYD_IN; if((islnk = su_cs_starts_with_case("link", *(ap = v))) || su_cs_starts_with_case("unlink", *ap)){ for(err = 0; *++ap != NULL;){ a_amv_var_revlookup(&avc, *ap, TRU1); if(a_amv_var_lookup(&avc, a_AMV_VLOOK_NONE) && (islnk || (avc.avc_var->av_flags & a_AMV_VF_EXT_LINKED))){ if(!islnk){ avc.avc_var->av_flags &= ~a_AMV_VF_EXT_LINKED; continue; }else if(avc.avc_var->av_flags & (a_AMV_VF_ENV | a_AMV_VF_EXT_LINKED)){ if(n_poption & n_PO_D_V) n_err(_("`environ': link: already established: %s\n"), *ap); continue; } avc.avc_var->av_flags |= a_AMV_VF_EXT_LINKED; if(!(avc.avc_var->av_flags & a_AMV_VF_ENV)) a_amv_var__putenv(&avc, avc.avc_var); }else if(!islnk){ n_err(_("`environ': unlink: no link established: %s\n"), *ap); err = 1; }else{ char const *evp = getenv(*ap); if(evp != NULL) err |= !a_amv_var_set(&avc, evp, a_AMV_VSETCLR_ENV); else{ n_err(_("`environ': link: cannot link to non-existent: %s\n"), *ap); err = 1; } } } }else if(su_cs_starts_with_case("set", *ap)) err = !a_amv_var_c_set(++ap, a_AMV_VSETCLR_ENV); else if(su_cs_starts_with_case("unset", *ap)){ for(err = 0; *++ap != NULL;){ a_amv_var_revlookup(&avc, *ap, FAL0); if(!a_amv_var_clear(&avc, a_AMV_VSETCLR_ENV)) err = 1; } }else{ n_err(_("Synopsis: environ: ...\n")); err = 1; } NYD_OU; return err; } FL int c_vpospar(void *v){ struct n_cmd_arg *cap; uz i; struct a_amv_pospar *appp; enum{ a_NONE = 0, a_ERR = 1u<<0, a_SET = 1u<<1, a_CLEAR = 1u<<2, a_QUOTE = 1u<<3 } f; char const *varres; struct n_cmd_arg_ctx *cacp; NYD_IN; n_pstate_err_no = su_ERR_NONE; UNINIT(varres, n_empty); cacp = v; cap = cacp->cac_arg; if(su_cs_starts_with_case("set", cap->ca_arg.ca_str.s)) f = a_SET; else if(su_cs_starts_with_case("clear", cap->ca_arg.ca_str.s)) f = a_CLEAR; else if(su_cs_starts_with_case("quote", cap->ca_arg.ca_str.s)) f = a_QUOTE; else{ n_err(_("`vpospar': invalid subcommand: %s\n"), n_shexp_quote_cp(cap->ca_arg.ca_str.s, FAL0)); n_pstate_err_no = su_ERR_INVAL; f = a_ERR; goto jleave; } --cacp->cac_no; if((f & (a_CLEAR | a_QUOTE)) && cap->ca_next != NULL){ n_err(_("`vpospar': `%s': takes no argument\n"), cap->ca_arg.ca_str.s); n_pstate_err_no = su_ERR_INVAL; f = a_ERR; goto jleave; } cap = cap->ca_next; /* If in a macro, we need to overwrite the local instead of global argv */ appp = (a_amv_lopts != NULL) ? &a_amv_lopts->as_amcap->amca_pospar : &a_amv_pospar; if(f & (a_SET | a_CLEAR)){ if(cacp->cac_vput != NULL) n_err(_("`vpospar': `vput' only supported for `quote' subcommand\n")); if(!appp->app_not_heap && appp->app_maxcount > 0){ for(i = appp->app_maxcount; i-- != 0;) n_free(n_UNCONST(appp->app_dat[i])); n_free(appp->app_dat); } su_mem_set(appp, 0, sizeof *appp); if(f & a_SET){ if((i = cacp->cac_no) > a_AMV_POSPAR_MAX){ n_err(_("`vpospar': overflow: %" PRIuZ " arguments!\n"), i); n_pstate_err_no = su_ERR_OVERFLOW; f = a_ERR; goto jleave; } su_mem_set(appp, 0, sizeof *appp); if(i > 0){ appp->app_maxcount = appp->app_count = (u16)i; /* XXX Optimize: store it all in one chunk! */ ++i; i *= sizeof *appp->app_dat; appp->app_dat = n_alloc(i); for(i = 0; cap != NULL; ++i, cap = cap->ca_next){ appp->app_dat[i] = n_alloc(cap->ca_arg.ca_str.l +1); su_mem_copy(n_UNCONST(appp->app_dat[i]), cap->ca_arg.ca_str.s, cap->ca_arg.ca_str.l +1); } appp->app_dat[i] = NULL; } } }else{ if(appp->app_count == 0) varres = n_empty; else{ struct str in; struct n_string s_b, *s; char sep1, sep2; s = n_string_creat_auto(&s_b); sep1 = *ok_vlook(ifs); sep2 = *ok_vlook(ifs_ws); if(sep1 == sep2) sep2 = '\0'; if(sep1 == '\0') sep1 = ' '; for(i = 0; i < appp->app_count; ++i){ if(s->s_len){ if(!n_string_can_book(s, 2)) goto jeover; s = n_string_push_c(s, sep1); if(sep2 != '\0') s = n_string_push_c(s, sep2); } in.l = su_cs_len(in.s = n_UNCONST(appp->app_dat[i + appp->app_idx])); if(!n_string_can_book(s, in.l)){ jeover: n_err(_("`vpospar': overflow: string too long!\n")); n_pstate_err_no = su_ERR_OVERFLOW; f = a_ERR; goto jleave; } s = n_shexp_quote(s, &in, TRU1); } varres = n_string_cp(s); } if(cacp->cac_vput == NULL){ if(fprintf(n_stdout, "%s\n", varres) < 0){ n_pstate_err_no = su_err_no(); f |= a_ERR; } }else if(!n_var_vset(cacp->cac_vput, (up)varres)){ n_pstate_err_no = su_ERR_NOTSUP; f |= a_ERR; } } jleave: NYD_OU; return (f & a_ERR) ? 1 : 0; } #include "su/code-ou.h" /* s-it-mode */ s-nail-14.9.15/src/mx/attachment.c000066400000000000000000000430741352610246600166020ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Handling of attachments. * * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany. * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1980, 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. */ #undef su_FILE #define su_FILE attachment #define mx_SOURCE #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif #include #include #include #include "mx/file-streams.h" #include "mx/iconv.h" #include "mx/sigs.h" /* TODO fake */ #include "su/code-in.h" /* We use calloc() for struct attachment */ CTAV(AC_DEFAULT == 0); /* Return >=0 if file denotes a valid message number */ static int a_attachment_is_msg(char const *file); /* Fill in some basic attachment fields */ static struct attachment *a_attachment_setup_base(struct attachment *ap, char const *file); /* Setup ap to point to a message */ static struct attachment *a_attachment_setup_msg(struct attachment *ap, char const *msgcp, int msgno); /* Try to create temporary charset converted version */ #ifdef mx_HAVE_ICONV static boole a_attachment_iconv(struct attachment *ap, FILE *ifp); #endif /* */ static void a_attachment_yay(struct attachment const *ap); static int a_attachment_is_msg(char const *file){ int rv; NYD2_IN; rv = -1; if(file[0] == '#'){ uz ib; /* TODO Message numbers should be uz, and 0 may be a valid one */ if(file[2] == '\0' && file[1] == '.'){ if(dot != NULL) rv = (int)P2UZ(dot - message + 1); }else if((su_idec_uz_cp(&ib, &file[1], 10, NULL ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED) ) != su_IDEC_STATE_CONSUMED || ib == 0 || UCMP(z, ib, >, msgCount)) rv = -1; else rv = (int)ib; } NYD2_OU; return rv; } static struct attachment * a_attachment_setup_base(struct attachment *ap, char const *file){ NYD2_IN; ap->a_input_charset = ap->a_charset = NULL; ap->a_path_user = ap->a_path = ap->a_path_bname = ap->a_name = file; if((file = su_cs_rfind_c(file, '/')) != NULL) ap->a_path_bname = ap->a_name = ++file; else file = ap->a_name; ap->a_content_type = n_mimetype_classify_filename(file); ap->a_content_disposition = "attachment"; ap->a_content_description = NULL; ap->a_content_id = NULL; NYD2_OU; return ap; } static struct attachment * a_attachment_setup_msg(struct attachment *ap, char const *msgcp, int msgno){ NYD2_IN; ap->a_path_user = ap->a_path = ap->a_path_bname = ap->a_name = msgcp; ap->a_msgno = msgno; ap->a_content_type = ap->a_content_description = ap->a_content_disposition = NULL; ap->a_content_id = NULL; NYD2_OU; return ap; } #ifdef mx_HAVE_ICONV static boole a_attachment_iconv(struct attachment *ap, FILE *ifp){ struct str oul = {NULL, 0}, inl = {NULL, 0}; uz cnt, lbsize; iconv_t icp; FILE *ofp; NYD_IN; hold_sigs(); /* TODO until we have signal manager (see TODO) */ ofp = NULL; icp = n_iconv_open(ap->a_charset, ap->a_input_charset); if(icp == (iconv_t)-1){ if(su_err_no() == su_ERR_INVAL) goto jeconv; else n_perr(_("iconv_open"), 0); goto jerr; } cnt = (uz)fsize(ifp); if((ofp = mx_fs_tmp_open("atticonv", (mx_FS_O_RDWR | mx_FS_O_UNLINK | mx_FS_O_REGISTER), NIL)) == NIL){ n_perr(_("Temporary attachment data file"), 0); goto jerr; } for(lbsize = 0;;){ if(fgetline(&inl.s, &lbsize, &cnt, &inl.l, ifp, 0) == NULL){ if(!cnt) break; n_perr(_("I/O read error occurred"), 0); goto jerr; } if(n_iconv_str(icp, n_ICONV_IGN_NOREVERSE, &oul, &inl, NULL) != 0) goto jeconv; if((inl.l = fwrite(oul.s, sizeof *oul.s, oul.l, ofp)) != oul.l){ n_perr(_("I/O write error occurred"), 0); goto jerr; } } fflush_rewind(ofp); ap->a_tmpf = ofp; jleave: if(inl.s != NULL) n_free(inl.s); if(oul.s != NULL) n_free(oul.s); if(icp != (iconv_t)-1) n_iconv_close(icp); mx_fs_close(ifp); rele_sigs(); /* TODO until we have signal manager (see TODO) */ NYD_OU; return (ofp != NULL); jeconv: n_err(_("Cannot convert from %s to %s\n"), ap->a_input_charset, ap->a_charset); jerr: if(ofp != NIL) mx_fs_close(ofp); ofp = NULL; goto jleave; } #endif /* mx_HAVE_ICONV */ static void a_attachment_yay(struct attachment const *ap){ NYD2_IN; if(ap->a_msgno > 0) fprintf(n_stdout, _("Added message/rfc822 attachment for message #%u\n"), ap->a_msgno); else fprintf(n_stdout, _("Added attachment %s (%s)\n"), n_shexp_quote_cp(ap->a_name, FAL0), n_shexp_quote_cp(ap->a_path_user, FAL0)); NYD2_OU; } FL struct attachment * n_attachment_append(struct attachment *aplist, char const *file, enum n_attach_error *aerr_or_null, struct attachment **newap_or_null){ #ifdef mx_HAVE_ICONV FILE *cnvfp; #endif int msgno; char const *file_user, *incs, *oucs; struct attachment *nap, *ap; enum n_attach_error aerr; NYD_IN; #ifdef mx_HAVE_ICONV cnvfp = NULL; #endif aerr = n_ATTACH_ERR_NONE; nap = NULL; incs = oucs = NULL; if(*file == '\0'){ aerr = n_ATTACH_ERR_OTHER; goto jleave; } file_user = savestr(file); /* TODO recreate after fexpand()!?! */ if((msgno = a_attachment_is_msg(file)) < 0){ int e; char const *cp, *ncp; jrefexp: if((file = fexpand(file, FEXP_LOCAL | FEXP_NVAR)) == NULL){ aerr = n_ATTACH_ERR_OTHER; goto jleave; } #ifndef mx_HAVE_ICONV if(oucs != NULL && oucs != (char*)-1){ n_err(_("No iconv support, cannot do %s\n"), n_shexp_quote_cp(file_user, FAL0)); aerr = n_ATTACH_ERR_ICONV_NAVAIL; goto jleave; } #endif if(( #ifdef mx_HAVE_ICONV (oucs != NIL && oucs != R(char*,-1)) ? (cnvfp = mx_fs_open(file, "r")) == NIL : #endif access(file, R_OK) != 0)){ e = su_err_no(); /* It may not have worked because of a character-set specification, * so try to extract that and retry once */ if(incs == NULL && (cp = su_cs_rfind_c(file, '=')) != NULL){ uz i; char *nfp, c; nfp = savestrbuf(file, P2UZ(cp - file)); for(ncp = ++cp; (c = *cp) != '\0'; ++cp) if(!su_cs_is_alnum(c) && !su_cs_is_punct(c)) break; else if(c == '#'){ if(incs == NULL){ i = P2UZ(cp - ncp); if(i == 0 || (i == 1 && ncp[0] == '-')) incs = (char*)-1; else if((incs = n_iconv_normalize_name(savestrbuf(ncp, i)) ) == NULL){ e = su_ERR_INVAL; goto jerr_fopen; } ncp = &cp[1]; }else break; } if(c == '\0'){ char *xp; i = P2UZ(cp - ncp); if(i == 0 || (i == 1 && ncp[0] == '-')) xp = (char*)-1; else if((xp = n_iconv_normalize_name(savestrbuf(ncp, i)) ) == NULL){ e = su_ERR_INVAL; goto jerr_fopen; } if(incs == NULL) incs = xp; else oucs = xp; file = nfp; goto jrefexp; } } jerr_fopen: n_err(_("Failed to access attachment %s: %s\n"), n_shexp_quote_cp(file, FAL0), su_err_doc(e)); aerr = n_ATTACH_ERR_FILE_OPEN; goto jleave; } } nap = a_attachment_setup_base(n_autorec_calloc(1, sizeof *nap), file); nap->a_path_user = file_user; if(msgno >= 0) nap = a_attachment_setup_msg(nap, file, msgno); else{ nap->a_input_charset = (incs == NULL || incs == (char*)-1) ? savestr(ok_vlook(ttycharset)) : incs; #ifdef mx_HAVE_ICONV if(cnvfp != NULL){ nap->a_charset = oucs; if(!a_attachment_iconv(nap, cnvfp)){ nap = NULL; aerr = n_ATTACH_ERR_ICONV_FAILED; goto jleave; } nap->a_conv = AC_TMPFILE; }else #endif if(incs != NULL && oucs == NULL) nap->a_conv = AC_FIX_INCS; else nap->a_conv = AC_DEFAULT; } if(aplist != NULL){ for(ap = aplist; ap->a_flink != NULL; ap = ap->a_flink) ; ap->a_flink = nap; nap->a_blink = ap; }else aplist = nap; jleave: if(aerr_or_null != NULL) *aerr_or_null = aerr; if(newap_or_null != NULL) *newap_or_null = nap; NYD_OU; return aplist; } FL struct attachment * n_attachment_append_list(struct attachment *aplist, char const *names){ struct str shin; struct n_string shou, *shoup; NYD_IN; shoup = n_string_creat_auto(&shou); for(shin.s = n_UNCONST(names), shin.l = UZ_MAX;;){ struct attachment *nap; enum n_shexp_state shs; shs = n_shexp_parse_token((n_SHEXP_PARSE_TRUNC | n_SHEXP_PARSE_TRIM_SPACE | n_SHEXP_PARSE_LOG | n_SHEXP_PARSE_IFS_ADD_COMMA | n_SHEXP_PARSE_IGNORE_EMPTY), shoup, &shin, NULL); if(shs & n_SHEXP_STATE_ERR_MASK) break; if(shs & n_SHEXP_STATE_OUTPUT){ aplist = n_attachment_append(aplist, n_string_cp(shoup), NULL, &nap); if(nap != NULL){ if(n_psonce & n_PSO_INTERACTIVE) a_attachment_yay(nap); } } if(shs & n_SHEXP_STATE_STOP) break; } n_string_gut(shoup); NYD_OU; return aplist; } FL struct attachment * n_attachment_remove(struct attachment *aplist, struct attachment *ap){ struct attachment *bap, *fap; NYD_IN; #ifdef mx_HAVE_DEVEL for(bap = aplist; aplist != NULL && aplist != ap; aplist = aplist->a_flink) ; ASSERT(aplist != NULL); aplist = bap; #endif if(ap == aplist){ if((aplist = ap->a_flink) != NULL) aplist->a_blink = NULL; }else{ bap = ap->a_blink; fap = ap->a_flink; if(bap != NULL) bap->a_flink = fap; if(fap != NULL) fap->a_blink = bap; } if(ap->a_conv == AC_TMPFILE) mx_fs_close(ap->a_tmpf); NYD_OU; return aplist; } FL struct attachment * n_attachment_find(struct attachment *aplist, char const *name, boole *stat_or_null){ int msgno; char const *bname; boole status, sym; struct attachment *saved; NYD_IN; saved = NULL; status = FAL0; if((bname = su_cs_rfind_c(name, '/')) != NULL){ for(++bname; aplist != NULL; aplist = aplist->a_flink) if(!su_cs_cmp(name, aplist->a_path)){ status = TRU1; /* Exact match with path components: done */ goto jleave; }else if(!su_cs_cmp(bname, aplist->a_path_bname)){ if(!status){ saved = aplist; status = TRU1; }else status = TRUM1; } }else if((msgno = a_attachment_is_msg(name)) < 0){ for(sym = FAL0; aplist != NULL; aplist = aplist->a_flink){ if(!su_cs_cmp(name, aplist->a_name)){ if(!status || !sym){ saved = aplist; sym = TRU1; } }else if(!su_cs_cmp(name, aplist->a_path_bname)){ if(!status) saved = aplist; }else continue; status = status ? TRUM1 : TRU1; } }else{ for(; aplist != NULL; aplist = aplist->a_flink){ if(aplist->a_msgno > 0 && aplist->a_msgno == msgno){ status = TRU1; goto jleave; } } } if(saved != NULL) aplist = saved; jleave: if(stat_or_null != NULL) *stat_or_null = status; NYD_OU; return aplist; } FL struct attachment * n_attachment_list_edit(struct attachment *aplist, enum n_go_input_flags gif){ char prefix[32]; struct str shin; struct n_string shou, *shoup; struct attachment *naplist, *ap; u32 attno; NYD_IN; if((n_psonce & (n_PSO_INTERACTIVE | n_PSO_ATTACH_QUOTE_NOTED) ) == n_PSO_INTERACTIVE){ n_psonce |= n_PSO_ATTACH_QUOTE_NOTED; fprintf(n_stdout, _("# Only supports sh(1)ell-style quoting for file names\n")); } shoup = n_string_creat_auto(&shou); /* Modify already present ones? Append some more? */ attno = 1; for(naplist = NULL;;){ snprintf(prefix, sizeof prefix, A_("#%" PRIu32 " filename: "), attno); if(aplist != NULL){ /* TODO If we would create .a_path_user in append() after any * TODO expansion then we could avoid closing+rebuilding the temporary * TODO file if the new user input matches the original value! */ if(aplist->a_conv == AC_TMPFILE) mx_fs_close(aplist->a_tmpf); shin.s = n_shexp_quote_cp(aplist->a_path_user, FAL0); }else shin.s = n_UNCONST(n_empty); ap = NULL; if((shin.s = n_go_input_cp(gif, prefix, shin.s)) != NULL){ enum n_shexp_state shs; #ifdef mx_HAVE_UISTRINGS char const *s_save; s_save = shin.s; #endif shin.l = UZ_MAX; shs = n_shexp_parse_token((n_SHEXP_PARSE_TRUNC | n_SHEXP_PARSE_TRIM_SPACE | n_SHEXP_PARSE_LOG | n_SHEXP_PARSE_IGNORE_EMPTY), shoup, &shin, NULL); #ifdef mx_HAVE_UISTRINGS if(!(shs & n_SHEXP_STATE_STOP)) n_err(_("# May be given one argument a time only: %s\n"), n_shexp_quote_cp(s_save, FAL0)); #endif if((shs & (n_SHEXP_STATE_OUTPUT | n_SHEXP_STATE_STOP | n_SHEXP_STATE_ERR_MASK) ) != (n_SHEXP_STATE_OUTPUT | n_SHEXP_STATE_STOP)) break; naplist = n_attachment_append(naplist, n_string_cp(shoup), NULL, &ap); if(ap != NULL){ if(n_psonce & n_PSO_INTERACTIVE) a_attachment_yay(ap); ++attno; } } if(aplist != NULL){ aplist = aplist->a_flink; /* In non-interactive or batch mode an empty line ends processing */ if((n_psonce & n_PSO_INTERACTIVE) && !(n_poption & n_PO_BATCH_FLAG)) continue; } if(ap == NULL) break; } NYD_OU; return naplist; } FL sz n_attachment_list_print(struct attachment const *aplist, FILE *fp){ struct attachment const *ap; u32 attno; sz rv; NYD_IN; rv = 0; for(attno = 1, ap = aplist; ap != NULL; ++rv, ++attno, ap = ap->a_flink){ if(ap->a_msgno > 0) fprintf(fp, "#%" PRIu32 ". message/rfc822: %u\n", attno, ap->a_msgno); else{ char const *incs, *oucs; if(!su_state_has(su_STATE_REPRODUCIBLE)){ incs = ap->a_input_charset; oucs = ap->a_charset; }else incs = oucs = su_reproducible_build; fprintf(fp, "#%" PRIu32 ": %s [%s -- %s", attno, n_shexp_quote_cp(ap->a_name, FAL0), n_shexp_quote_cp(ap->a_path, FAL0), (ap->a_content_type != NULL ? ap->a_content_type : _("unclassified content"))); if(ap->a_conv == AC_TMPFILE) /* I18N: input and output character set as given */ fprintf(fp, _(", incs=%s -> oucs=%s (readily converted)"), incs, oucs); else if(ap->a_conv == AC_FIX_INCS) /* I18N: input character set as given, no conversion to apply */ fprintf(fp, _(", incs=%s (no conversion)"), incs); else if(ap->a_conv == AC_DEFAULT){ if(incs != NULL) /* I18N: input character set as given, output iterates */ fprintf(fp, _(", incs=%s -> oucs=*sendcharsets*"), incs); else if(ap->a_content_type == NULL || !su_cs_cmp_case_n(ap->a_content_type, "text/", 5)) fprintf(fp, _(", default character set handling")); } fprintf(fp, "]\n"); } } NYD_OU; return rv; } #include "su/code-ou.h" /* s-it-mode */ s-nail-14.9.15/src/mx/auxlily.c000066400000000000000000001015321352610246600161330ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Auxiliary functions that don't fit anywhere else. * * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany. * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1980, 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. */ #undef su_FILE #define su_FILE auxlily #define mx_SOURCE #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif #include #ifdef mx_HAVE_NET # ifdef mx_HAVE_GETADDRINFO # include # endif # include #endif #ifdef mx_HAVE_IDNA # if mx_HAVE_IDNA == n_IDNA_IMPL_LIBIDN2 # include # elif mx_HAVE_IDNA == n_IDNA_IMPL_LIBIDN # include # include # elif mx_HAVE_IDNA == n_IDNA_IMPL_IDNKIT # include # endif #endif #include #include #include #include #include #include "mx/child.h" #include "mx/cmd-filetype.h" #include "mx/colour.h" #include "mx/file-streams.h" #include "mx/termios.h" #include "mx/tty.h" #ifdef mx_HAVE_IDNA # include "mx/iconv.h" #endif /* TODO fake */ #include "su/code-in.h" /* */ #ifdef mx_HAVE_ERRORS CTAV(mx_ERRORS_MAX > 1); #endif /* The difference in between mx_HAVE_ERRORS and not, is size of queue only */ struct a_aux_err_node{ struct a_aux_err_node *ae_next; u32 ae_cnt; boole ae_done; u8 ae_pad[3]; uz ae_dumped_till; struct n_string ae_str; }; /* Error ring, for `errors' */ static struct a_aux_err_node *a_aux_err_head; static struct a_aux_err_node *a_aux_err_tail; /* Get our $PAGER; if env_addon is not NULL it is checked whether we know about * some environment variable that supports colour+ and set *env_addon to that, * e.g., "LESS=FRSXi" */ static char const *a_aux_pager_get(char const **env_addon); static char const * a_aux_pager_get(char const **env_addon){ char const *rv; NYD_IN; rv = ok_vlook(PAGER); if(env_addon != NIL){ *env_addon = NIL; /* Update the manual upon any changes: * *colour-pager*, $PAGER */ if(su_cs_find(rv, "less") != NIL){ if(getenv("LESS") == NIL) *env_addon = "LESS=RXi"; }else if(su_cs_find(rv, "lv") != NIL){ if(getenv("LV") == NIL) *env_addon = "LV=-c"; } } NYD_OU; return rv; } FL uz n_screensize(void){ char const *cp; uz rv; NYD2_IN; if((cp = ok_vlook(screen)) != NIL){ su_idec_uz_cp(&rv, cp, 0, NIL); if(rv == 0) rv = mx_termios_dimen.tiosd_height; }else rv = mx_termios_dimen.tiosd_height; if(rv > 2) rv -= 2; NYD2_OU; return rv; } FL FILE * mx_pager_open(void){ char const *env_add[2], *pager; FILE *rv; NYD_IN; ASSERT(n_psonce & n_PSO_INTERACTIVE); pager = a_aux_pager_get(env_add + 0); env_add[1] = NIL; if((rv = mx_fs_pipe_open(pager, "w", NIL, env_add, mx_CHILD_FD_PASS) ) == NIL) n_perr(pager, 0); NYD_OU; return rv; } FL boole mx_pager_close(FILE *fp){ boole rv; NYD_IN; rv = mx_fs_pipe_close(fp, TRU1); NYD_OU; return rv; } FL void page_or_print(FILE *fp, uz lines) { int c; char const *cp; NYD_IN; fflush_rewind(fp); if (n_go_may_yield_control() && (cp = ok_vlook(crt)) != NULL) { uz rows; if(*cp == '\0') rows = mx_termios_dimen.tiosd_height; else su_idec_uz_cp(&rows, cp, 0, NULL); /* Avoid overflow later on */ if(rows == UZ_MAX) --rows; if (rows > 0 && lines == 0) { while ((c = getc(fp)) != EOF) if (c == '\n' && ++lines >= rows) break; really_rewind(fp); } /* Take account for the follow-up prompt */ if(lines + 1 >= rows){ struct mx_child_ctx cc; char const *env_addon[2]; mx_child_ctx_setup(&cc); cc.cc_flags = mx_CHILD_RUN_WAIT_LIFE; cc.cc_fds[mx_CHILD_FD_IN] = fileno(fp); cc.cc_cmd = a_aux_pager_get(&env_addon[0]); env_addon[1] = NIL; cc.cc_env_addon = env_addon; mx_child_run(&cc); goto jleave; } } while ((c = getc(fp)) != EOF) putc(c, n_stdout); jleave: NYD_OU; } FL enum protocol which_protocol(char const *name, boole check_stat, boole try_hooks, char const **adjusted_or_null) { /* TODO This which_protocol() sickness should be URL::new()->protocol() */ char const *cp, *orig_name; enum protocol rv = PROTO_UNKNOWN; NYD_IN; if(name[0] == '%' && name[1] == ':') name += 2; orig_name = name; for (cp = name; *cp && *cp != ':'; cp++) if (!su_cs_is_alnum(*cp)) goto jfile; if(cp[0] == ':' && cp[1] == '/' && cp[2] == '/'){ if(!strncmp(name, "file", sizeof("file") -1) || !strncmp(name, "mbox", sizeof("mbox") -1)) rv = PROTO_FILE; else if(!strncmp(name, "maildir", sizeof("maildir") -1)){ #ifdef mx_HAVE_MAILDIR rv = PROTO_MAILDIR; #else n_err(_("No Maildir directory support compiled in\n")); #endif }else if(!strncmp(name, "pop3", sizeof("pop3") -1)){ #ifdef mx_HAVE_POP3 rv = PROTO_POP3; #else n_err(_("No POP3 support compiled in\n")); #endif }else if(!strncmp(name, "pop3s", sizeof("pop3s") -1)){ #if defined mx_HAVE_POP3 && defined mx_HAVE_TLS rv = PROTO_POP3; #else n_err(_("No POP3S support compiled in\n")); #endif }else if(!strncmp(name, "imap", sizeof("imap") -1)){ #ifdef mx_HAVE_IMAP rv = PROTO_IMAP; #else n_err(_("No IMAP support compiled in\n")); #endif }else if(!strncmp(name, "imaps", sizeof("imaps") -1)){ #if defined mx_HAVE_IMAP && defined mx_HAVE_TLS rv = PROTO_IMAP; #else n_err(_("No IMAPS support compiled in\n")); #endif } orig_name = &cp[3]; goto jleave; } jfile: rv = PROTO_FILE; if(check_stat || try_hooks){ struct mx_filetype ft; struct stat stb; char *np; uz i; np = n_lofi_alloc((i = su_cs_len(name)) + 4 +1); su_mem_copy(np, name, i +1); if(!stat(name, &stb)){ if(S_ISDIR(stb.st_mode) #ifdef mx_HAVE_MAILDIR && (su_mem_copy(&np[i], "/tmp", 5), !stat(np, &stb) && S_ISDIR(stb.st_mode)) && (su_mem_copy(&np[i], "/new", 5), !stat(np, &stb) && S_ISDIR(stb.st_mode)) && (su_mem_copy(&np[i], "/cur", 5), !stat(np, &stb) && S_ISDIR(stb.st_mode)) #endif ){ #ifdef mx_HAVE_MAILDIR rv = PROTO_MAILDIR; #else rv = PROTO_UNKNOWN; #endif } }else if(try_hooks && mx_filetype_trial(&ft, name)) orig_name = savecatsep(name, '.', ft.ft_ext_dat); else if((cp = ok_vlook(newfolders)) != NULL && !su_cs_cmp_case(cp, "maildir")){ #ifdef mx_HAVE_MAILDIR rv = PROTO_MAILDIR; #else n_err(_("*newfolders*: no Maildir directory support compiled in\n")); #endif } n_lofi_free(np); } jleave: if(adjusted_or_null != NULL) *adjusted_or_null = orig_name; NYD_OU; return rv; } FL char * n_c_to_hex_base16(char store[3], char c){ static char const itoa16[] = "0123456789ABCDEF"; NYD2_IN; store[2] = '\0'; store[1] = itoa16[(u8)c & 0x0F]; c = ((u8)c >> 4) & 0x0F; store[0] = itoa16[(u8)c]; NYD2_OU; return store; } FL s32 n_c_from_hex_base16(char const hex[2]){ static u8 const atoi16[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, /* 0x30-0x37 */ 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x38-0x3F */ 0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, /* 0x40-0x47 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x48-0x4f */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x50-0x57 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 0x58-0x5f */ 0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF /* 0x60-0x67 */ }; u8 i1, i2; s32 rv; NYD2_IN; if ((i1 = (u8)hex[0] - '0') >= NELEM(atoi16) || (i2 = (u8)hex[1] - '0') >= NELEM(atoi16)) goto jerr; i1 = atoi16[i1]; i2 = atoi16[i2]; if ((i1 | i2) & 0xF0u) goto jerr; rv = i1; rv <<= 4; rv += i2; jleave: NYD2_OU; return rv; jerr: rv = -1; goto jleave; } FL char const * n_getdeadletter(void){ char const *cp; boole bla; NYD_IN; bla = FAL0; jredo: cp = fexpand(ok_vlook(DEAD), FEXP_LOCAL | FEXP_NSHELL); if(cp == NULL || su_cs_len(cp) >= PATH_MAX){ if(!bla){ n_err(_("Failed to expand *DEAD*, setting default (%s): %s\n"), VAL_DEAD, n_shexp_quote_cp((cp == NULL ? n_empty : cp), FAL0)); ok_vclear(DEAD); bla = TRU1; goto jredo; }else{ cp = savecatsep(ok_vlook(TMPDIR), '/', VAL_DEAD_BASENAME); n_err(_("Cannot expand *DEAD*, using: %s\n"), cp); } } NYD_OU; return cp; } FL char * n_nodename(boole mayoverride){ static char *sys_hostname, *hostname; /* XXX free-at-exit */ struct utsname ut; char *hn; #ifdef mx_HAVE_NET # ifdef mx_HAVE_GETADDRINFO struct addrinfo hints, *res; # else struct hostent *hent; # endif #endif NYD2_IN; if(su_state_has(su_STATE_REPRODUCIBLE)) hn = n_UNCONST(su_reproducible_build); else if(mayoverride && (hn = ok_vlook(hostname)) != NULL && *hn != '\0'){ ; }else if((hn = sys_hostname) == NULL){ boole lofi; lofi = FAL0; uname(&ut); hn = ut.nodename; #ifdef mx_HAVE_NET # ifdef mx_HAVE_GETADDRINFO su_mem_set(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_flags = AI_CANONNAME; if(getaddrinfo(hn, NULL, &hints, &res) == 0){ if(res->ai_canonname != NULL){ uz l; l = su_cs_len(res->ai_canonname) +1; hn = n_lofi_alloc(l); lofi = TRU1; su_mem_copy(hn, res->ai_canonname, l); } freeaddrinfo(res); } # else hent = gethostbyname(hn); if(hent != NULL) hn = hent->h_name; # endif #endif /* mx_HAVE_NET */ /* Ensure it is non-empty! */ if(hn[0] == '\0') hn = n_UNCONST(n_LOCALHOST_DEFAULT_NAME); #ifdef mx_HAVE_IDNA /* C99 */{ struct n_string cnv; n_string_creat(&cnv); if(!n_idna_to_ascii(&cnv, hn, UZ_MAX)) n_panic(_("The system hostname is invalid, " "IDNA conversion failed: %s\n"), n_shexp_quote_cp(hn, FAL0)); sys_hostname = n_string_cp(&cnv); n_string_drop_ownership(&cnv); /*n_string_gut(&cnv);*/ } #else sys_hostname = su_cs_dup(hn, 0); #endif if(lofi) n_lofi_free(hn); hn = sys_hostname; } if(hostname != NULL && hostname != sys_hostname) n_free(hostname); hostname = su_cs_dup(hn, 0); NYD2_OU; return hostname; } #ifdef mx_HAVE_IDNA FL boole n_idna_to_ascii(struct n_string *out, char const *ibuf, uz ilen){ char *idna_utf8; boole lofi, rv; NYD_IN; if(ilen == UZ_MAX) ilen = su_cs_len(ibuf); lofi = FAL0; if((rv = (ilen == 0))) goto jleave; if(ibuf[ilen] != '\0'){ lofi = TRU1; idna_utf8 = n_lofi_alloc(ilen +1); su_mem_copy(idna_utf8, ibuf, ilen); idna_utf8[ilen] = '\0'; ibuf = idna_utf8; } ilen = 0; # ifndef mx_HAVE_ALWAYS_UNICODE_LOCALE if(n_psonce & n_PSO_UNICODE) # endif idna_utf8 = n_UNCONST(ibuf); # ifndef mx_HAVE_ALWAYS_UNICODE_LOCALE else if((idna_utf8 = n_iconv_onetime_cp(n_ICONV_NONE, "utf-8", ok_vlook(ttycharset), ibuf)) == NULL) goto jleave; # endif # if mx_HAVE_IDNA == n_IDNA_IMPL_LIBIDN2 /* C99 */{ char *idna_ascii; int f, rc; f = IDN2_NONTRANSITIONAL; jidn2_redo: if((rc = idn2_to_ascii_8z(idna_utf8, &idna_ascii, f)) == IDN2_OK){ out = n_string_assign_cp(out, idna_ascii); idn2_free(idna_ascii); rv = TRU1; ilen = out->s_len; }else if(rc == IDN2_DISALLOWED && f != IDN2_TRANSITIONAL){ f = IDN2_TRANSITIONAL; goto jidn2_redo; } } # elif mx_HAVE_IDNA == n_IDNA_IMPL_LIBIDN /* C99 */{ char *idna_ascii; if(idna_to_ascii_8z(idna_utf8, &idna_ascii, 0) == IDNA_SUCCESS){ out = n_string_assign_cp(out, idna_ascii); idn_free(idna_ascii); rv = TRU1; ilen = out->s_len; } } # elif mx_HAVE_IDNA == n_IDNA_IMPL_IDNKIT ilen = su_cs_len(idna_utf8); jredo: switch(idn_encodename( /* LOCALCONV changed meaning in v2 and is no longer available for * encoding. This makes sense, bu */ ( # ifdef IDN_UNICODECONV /* v2 */ IDN_ENCODE_APP & ~IDN_UNICODECONV # else IDN_DELIMMAP | IDN_LOCALMAP | IDN_NAMEPREP | IDN_IDNCONV | IDN_LENCHECK | IDN_ASCCHECK # endif ), idna_utf8, n_string_resize(n_string_trunc(out, 0), ilen)->s_dat, ilen)){ case idn_buffer_overflow: ilen += HOST_NAME_MAX +1; goto jredo; case idn_success: rv = TRU1; ilen = su_cs_len(out->s_dat); break; default: ilen = 0; break; } # else # error Unknown mx_HAVE_IDNA # endif jleave: if(lofi) n_lofi_free(n_UNCONST(ibuf)); out = n_string_trunc(out, ilen); NYD_OU; return rv; } #endif /* mx_HAVE_IDNA */ FL boole n_boolify(char const *inbuf, uz inlen, boole emptyrv){ boole rv; NYD2_IN; ASSERT(inlen == 0 || inbuf != NULL); if(inlen == UZ_MAX) inlen = su_cs_len(inbuf); if(inlen == 0) rv = (emptyrv >= FAL0) ? (emptyrv == FAL0 ? FAL0 : TRU1) : TRU2; else{ if((inlen == 1 && (*inbuf == '1' || *inbuf == 'y' || *inbuf == 'Y')) || !su_cs_cmp_case_n(inbuf, "true", inlen) || !su_cs_cmp_case_n(inbuf, "yes", inlen) || !su_cs_cmp_case_n(inbuf, "on", inlen)) rv = TRU1; else if((inlen == 1 && (*inbuf == '0' || *inbuf == 'n' || *inbuf == 'N')) || !su_cs_cmp_case_n(inbuf, "false", inlen) || !su_cs_cmp_case_n(inbuf, "no", inlen) || !su_cs_cmp_case_n(inbuf, "off", inlen)) rv = FAL0; else{ u64 ib; if((su_idec(&ib, inbuf, inlen, 0, 0, NULL) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)) != su_IDEC_STATE_CONSUMED) rv = TRUM1; else rv = (ib != 0); } } NYD2_OU; return rv; } FL boole n_quadify(char const *inbuf, uz inlen, char const *prompt, boole emptyrv){ boole rv; NYD2_IN; ASSERT(inlen == 0 || inbuf != NULL); if(inlen == UZ_MAX) inlen = su_cs_len(inbuf); if(inlen == 0) rv = (emptyrv >= FAL0) ? (emptyrv == FAL0 ? FAL0 : TRU1) : TRU2; else if((rv = n_boolify(inbuf, inlen, emptyrv)) < FAL0 && !su_cs_cmp_case_n(inbuf, "ask-", 4) && (rv = n_boolify(&inbuf[4], inlen - 4, emptyrv)) >= FAL0 && (n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT)) rv = mx_tty_yesorno(prompt, rv); NYD2_OU; return rv; } FL boole n_is_all_or_aster(char const *name){ boole rv; NYD2_IN; rv = ((name[0] == '*' && name[1] == '\0') || !su_cs_cmp_case(name, "all")); NYD2_OU; return rv; } FL struct n_timespec const * n_time_now(boole force_update){ /* TODO event loop update IF cmd requests! */ static struct n_timespec ts_now; NYD2_IN; if(UNLIKELY(su_state_has(su_STATE_REPRODUCIBLE))){ /* Guaranteed 32-bit posnum TODO SOURCE_DATE_EPOCH should be 64-bit! */ (void)su_idec_s64_cp(&ts_now.ts_sec, ok_vlook(SOURCE_DATE_EPOCH), 0,NULL); ts_now.ts_nsec = 0; }else if(force_update || ts_now.ts_sec == 0){ #ifdef mx_HAVE_CLOCK_GETTIME struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); ts_now.ts_sec = (s64)ts.tv_sec; ts_now.ts_nsec = (sz)ts.tv_nsec; #elif defined mx_HAVE_GETTIMEOFDAY struct timeval tv; gettimeofday(&tv, NULL); ts_now.ts_sec = (s64)tv.tv_sec; ts_now.ts_nsec = (sz)tv.tv_usec * 1000; #else ts_now.ts_sec = (s64)time(NULL); ts_now.ts_nsec = 0; #endif } /* Just in case.. */ if(UNLIKELY(ts_now.ts_sec < 0)) ts_now.ts_sec = 0; NYD2_OU; return &ts_now; } FL void time_current_update(struct time_current *tc, boole full_update){ NYD_IN; tc->tc_time = (time_t)n_time_now(TRU1)->ts_sec; if(full_update){ char *cp; struct tm *tmp; time_t t; t = tc->tc_time; jredo: if((tmp = gmtime(&t)) == NULL){ t = 0; goto jredo; } su_mem_copy(&tc->tc_gm, tmp, sizeof tc->tc_gm); if((tmp = localtime(&t)) == NULL){ t = 0; goto jredo; } su_mem_copy(&tc->tc_local, tmp, sizeof tc->tc_local); cp = su_cs_pcopy(tc->tc_ctime, n_time_ctime((s64)tc->tc_time, tmp)); *cp++ = '\n'; *cp = '\0'; ASSERT(P2UZ(++cp - tc->tc_ctime) < sizeof(tc->tc_ctime)); } NYD_OU; } FL char * n_time_ctime(s64 secsepoch, struct tm const *localtime_or_nil){/* TODO err*/ /* Problem is that secsepoch may be invalid for representation of ctime(3), * which indeed is asctime(localtime(t)); musl libc says for asctime(3): * ISO C requires us to use the above format string, * even if it will not fit in the buffer. Thus asctime_r * is _supposed_ to crash if the fields in tm are too large. * We follow this behavior and crash "gracefully" to warn * application developers that they may not be so lucky * on other implementations (e.g. stack smashing..). * So we need to do it on our own or the libc may kill us */ static char buf[32]; /* TODO static buffer (-> datetime_to_format()) */ s32 y, md, th, tm, ts; char const *wdn, *mn; struct tm const *tmp; NYD_IN; if((tmp = localtime_or_nil) == NULL){ time_t t; t = (time_t)secsepoch; jredo: if((tmp = localtime(&t)) == NULL){ /* TODO error log */ t = 0; goto jredo; } } if(UNLIKELY((y = tmp->tm_year) < 0 || y >= 9999/*S32_MAX*/ - 1900)){ y = 1970; wdn = n_weekday_names[4]; mn = n_month_names[0]; md = 1; th = tm = ts = 0; }else{ y += 1900; wdn = (tmp->tm_wday >= 0 && tmp->tm_wday <= 6) ? n_weekday_names[tmp->tm_wday] : n_qm; mn = (tmp->tm_mon >= 0 && tmp->tm_mon <= 11) ? n_month_names[tmp->tm_mon] : n_qm; if((md = tmp->tm_mday) < 1 || md > 31) md = 1; if((th = tmp->tm_hour) < 0 || th > 23) th = 0; if((tm = tmp->tm_min) < 0 || tm > 59) tm = 0; if((ts = tmp->tm_sec) < 0 || ts > 60) ts = 0; } (void)snprintf(buf, sizeof buf, "%3s %3s%3d %.2d:%.2d:%.2d %d", wdn, mn, md, th, tm, ts, y); NYD_OU; return buf; } FL uz n_msleep(uz millis, boole ignint){ uz rv; NYD2_IN; #ifdef mx_HAVE_NANOSLEEP /* C99 */{ struct timespec ts, trem; int i; ts.tv_sec = millis / 1000; ts.tv_nsec = (millis %= 1000) * 1000 * 1000; while((i = nanosleep(&ts, &trem)) != 0 && ignint) ts = trem; rv = (i == 0) ? 0 : (trem.tv_sec * 1000) + (trem.tv_nsec / (1000 * 1000)); } #elif defined mx_HAVE_SLEEP if((millis /= 1000) == 0) millis = 1; while((rv = sleep((unsigned int)millis)) != 0 && ignint) millis = rv; #else # error Configuration should have detected a function for sleeping. #endif NYD2_OU; return rv; } FL void n_err(char const *format, ...){ va_list ap; NYD2_IN; va_start(ap, format); n_verrx(FAL0, format, ap); va_end(ap); NYD2_OU; } FL void n_errx(boole allow_multiple, char const *format, ...){ va_list ap; NYD2_IN; va_start(ap, format); n_verrx(allow_multiple, format, ap); va_end(ap); NYD2_OU; } FL void n_verr(char const *format, va_list ap){ NYD2_IN; n_verrx(FAL0, format, ap); NYD2_OU; } FL void n_verrx(boole allow_multiple, char const *format, va_list ap){/*XXX sigcondom*/ mx_COLOUR( static uz c5recur; ) /* *termcap* recursion */ struct str s_b, s; struct a_aux_err_node *lenp, *enp; sz i; char const *lpref, *c5pref, *c5suff; NYD2_IN; mx_COLOUR( ++c5recur; ) lpref = NIL; c5pref = c5suff = su_empty; /* Fully expand the buffer (TODO use fmtenc) */ #undef a_X #ifdef mx_HAVE_N_VA_COPY # define a_X 128 #else # define a_X MIN(LINESIZE, 1024) #endif mx_fs_linepool_aquire(&s_b.s, &s_b.l); if(s_b.l < a_X) s_b.l = a_X; for(i = s_b.l;; s_b.l = ++i /* xxx could wrap, maybe */){ #ifdef mx_HAVE_N_VA_COPY va_list vac; n_va_copy(vac, ap); #else # define vac ap #endif s_b.s = su_MEM_REALLOC(s_b.s, s_b.l); i = vsnprintf(s_b.s, s_b.l, format, vac); #ifdef mx_HAVE_N_VA_COPY va_end(vac); #else # undef vac #endif if(i <= 0) goto jleave; if(UCMP(z, i, >=, s_b.l)){ #ifdef mx_HAVE_N_VA_COPY continue; #else i = S(int,su_cs_len(s_b.s)); #endif } break; } s = s_b; s.l = S(uz,i); /* Remove control characters but \n as we do not makeprint() XXX config */ /* C99 */{ char *ins, *curr, *max, c; for(ins = curr = s.s, max = &ins[s.l]; curr < max; ++curr) if(!su_cs_is_cntrl(c = *curr) || c == '\n') *ins++ = c; *ins = '\0'; s.l = P2UZ(ins - s.s); } /* We have the prepared error message, take it over line-by-line, possibly * completing partly prepared one first */ n_pstate |= n_PS_ERRORS_PROMPT; lpref = ok_vlook(log_prefix); #ifdef mx_HAVE_COLOUR if(c5recur == 1 && (n_psonce & n_PSO_TTYANY)){ struct str const *pref, *suff; struct mx_colour_pen *cp; if((cp = mx_colour_get_pen(mx_COLOUR_GET_FORCED, mx_COLOUR_CTX_MLE, mx_COLOUR_ID_MLE_ERROR, NIL) ) != NIL && (pref = mx_colour_pen_get_cseq(cp)) != NIL && (suff = mx_colour_get_reset_cseq(mx_COLOUR_GET_FORCED) ) != NIL){ c5pref = pref->s; c5suff = suff->s; } } #endif for(i = 0; UCMP(z, i, <, s.l);){ char c, *cp; boole fresh; lenp = enp = a_aux_err_tail; if((fresh = (enp == NIL || enp->ae_done))){ enp = su_TCALLOC(struct a_aux_err_node, 1); enp->ae_cnt = 1; n_string_creat(&enp->ae_str); if(a_aux_err_tail != NIL) a_aux_err_tail->ae_next = enp; else a_aux_err_head = enp; a_aux_err_tail = enp; } /* xxx if(!n_string_book(&enp->ae_str, s.l - i)) * xxx goto jleave;*/ /* We have completed a line? */ /* C99 */{ uz oi, j, k; oi = S(uz,i); j = s.l - oi; k = enp->ae_str.s_len; cp = S(char*,su_mem_find(&s.s[oi], '\n', j)); if(cp == NIL){ n_string_push_buf(&enp->ae_str, &s.s[oi], j); i = s.l; }else{ j = P2UZ(cp - &s.s[oi]); i += j + 1; n_string_push_buf(&enp->ae_str, &s.s[oi], j); } /* We need to write it out regardless of whether it is a complete line * or not, say (for at least `echoerrn') TODO IO errors not handled */ if(cp == NIL || allow_multiple || !(n_psonce & n_PSO_INTERACTIVE)){ enp->ae_dumped_till = enp->ae_str.s_len; fprintf(n_stderr, "%s%s%s%s%s", c5pref, (fresh ? lpref : su_empty), &n_string_cp(&enp->ae_str)[k], c5suff, (cp != NIL ? "\n" : su_empty)); fflush(n_stderr); } } if(cp == NIL) continue; enp->ae_done = TRU1; /* Check whether it is identical to the last one dumped, in which case * we throw it away and only increment the counter, as syslog would. * If not, dump it out, if not already */ c = FAL0; if(lenp != NIL){ if(lenp != enp && lenp->ae_str.s_len == enp->ae_str.s_len && !su_mem_cmp(lenp->ae_str.s_dat, enp->ae_str.s_dat, enp->ae_str.s_len)){ ++lenp->ae_cnt; c = TRU1; } /* Otherwise, if the last error has a count, say so, unless it would * soil and intermix display */ else if(lenp->ae_cnt > 1 && !allow_multiple && (n_psonce & n_PSO_INTERACTIVE)){ fprintf(n_stderr, _("%s%s-- Last message repeated %u times --%s\n"), c5pref, lpref, lenp->ae_cnt, c5suff); fflush(n_stderr); } } if(!c && !allow_multiple && (n_psonce & n_PSO_INTERACTIVE) && enp->ae_dumped_till != enp->ae_str.s_len){ fprintf(n_stderr, "%s%s%s%s\n", c5pref, ((fresh && enp->ae_dumped_till == 0) ? lpref : su_empty), &n_string_cp(&enp->ae_str)[enp->ae_dumped_till], c5suff); fflush(n_stderr); } if(c){ lenp->ae_next = NIL; a_aux_err_tail = lenp; n_string_gut(&enp->ae_str); su_FREE(enp); continue; } #ifdef mx_HAVE_ERRORS if(n_pstate_err_cnt < mx_ERRORS_MAX){ ++n_pstate_err_cnt; continue; } a_aux_err_head = (lenp = a_aux_err_head)->ae_next; #else a_aux_err_head = a_aux_err_tail = enp; #endif if(lenp != NIL){ n_string_gut(&lenp->ae_str); su_FREE(lenp); } } jleave: mx_fs_linepool_release(s_b.s, s_b.l); mx_COLOUR( --c5recur; ) NYD2_OU; } FL void n_err_sighdl(char const *format, ...){ /* TODO sigsafe; obsolete! */ va_list ap; NYD; va_start(ap, format); vfprintf(n_stderr, format, ap); va_end(ap); fflush(n_stderr); } FL void n_perr(char const *msg, int errval){ int e; char const *fmt; NYD2_IN; if(msg == NULL){ fmt = "%s%s\n"; msg = n_empty; }else fmt = "%s: %s\n"; e = (errval == 0) ? su_err_no() : errval; n_errx(FAL0, fmt, msg, su_err_doc(e)); if(errval == 0) su_err_set_no(e); NYD2_OU; } FL void n_alert(char const *format, ...){ va_list ap; NYD2_IN; n_err((a_aux_err_tail != NIL && !a_aux_err_tail->ae_done) ? _("\nAlert: ") : _("Alert: ")); va_start(ap, format); n_verrx(TRU1, format, ap); va_end(ap); n_err("\n"); NYD2_OU; } FL void n_panic(char const *format, ...){ va_list ap; NYD2_IN; if(a_aux_err_tail != NIL && !a_aux_err_tail->ae_done){ a_aux_err_tail->ae_done = TRU1; putc('\n', n_stderr); } fprintf(n_stderr, "%sPanic: ", ok_vlook(log_prefix)); va_start(ap, format); vfprintf(n_stderr, format, ap); va_end(ap); putc('\n', n_stderr); fflush(n_stderr); NYD2_OU; abort(); /* Was exit(n_EXIT_ERR); for a while, but no */ } #ifdef mx_HAVE_ERRORS FL int c_errors(void *v){ char **argv = v; struct a_aux_err_node *enp; NYD_IN; if(*argv == NULL) goto jlist; if(argv[1] != NULL) goto jerr; if(!su_cs_cmp_case(*argv, "show")) goto jlist; if(!su_cs_cmp_case(*argv, "clear")) goto jclear; jerr: fprintf(n_stderr, _("Synopsis: errors: ( or) the error ring\n")); v = NULL; jleave: NYD_OU; return (v == NULL) ? !STOP : !OKAY; /* xxx 1:bad 0:good -- do some */ jlist:{ FILE *fp; uz i; if(a_aux_err_head == NIL){ fprintf(n_stderr, _("The error ring is empty\n")); goto jleave; } if((fp = mx_fs_tmp_open("errors", (mx_FS_O_RDWR | mx_FS_O_UNLINK | mx_FS_O_REGISTER), NIL)) == NIL){ fprintf(n_stderr, _("tmpfile")); v = NIL; goto jleave; } for(i = 0, enp = a_aux_err_head; enp != NIL; enp = enp->ae_next) fprintf(fp, "%4" PRIuZ "/%-3u %s\n", ++i, enp->ae_cnt, n_string_cp(&enp->ae_str)); page_or_print(fp, 0); mx_fs_close(fp); } /* FALLTHRU */ jclear: a_aux_err_tail = NIL; n_pstate_err_cnt = 0; while((enp = a_aux_err_head) != NIL){ a_aux_err_head = enp->ae_next; n_string_gut(&enp->ae_str); su_FREE(enp); } goto jleave; } #endif /* mx_HAVE_ERRORS */ #ifdef mx_HAVE_REGEX FL char const * n_regex_err_to_doc(const regex_t *rep, int e){ char *cp; uz i; NYD2_IN; i = regerror(e, rep, NULL, 0) +1; cp = n_autorec_alloc(i); regerror(e, rep, cp, i); NYD2_OU; return cp; } #endif FL boole mx_unxy_dict(char const *cmdname, struct su_cs_dict *dp, void *vp){ char const **argv, *key; boole rv; NYD_IN; rv = TRU1; key = (argv = vp)[0]; do{ if(key[1] == '\0' && key[0] == '*'){ if(dp != NIL) su_cs_dict_clear(dp); }else if(dp == NIL || !su_cs_dict_remove(dp, key)){ n_err(_("No such `%s': %s\n"), cmdname, n_shexp_quote_cp(key, FAL0)); rv = FAL0; } }while((key = *++argv) != NIL); NYD_OU; return rv; } FL boole mx_xy_dump_dict(char const *cmdname, struct su_cs_dict *dp, struct n_strlist **result, struct n_strlist **tailpp_or_nil, struct n_strlist *(*ptf)(char const *cmdname, char const *key, void const *dat)){ struct su_cs_dict_view dv; char const **cpp, **xcpp; u32 cnt; struct n_strlist *resp, *tailp; boole rv; NYD_IN; rv = TRU1; resp = *result; if(tailpp_or_nil != NIL) tailp = *tailpp_or_nil; else if((tailp = resp) != NIL) for(;; tailp = tailp->sl_next) if(tailp->sl_next == NIL) break; if(dp == NIL || (cnt = su_cs_dict_count(dp)) == 0) goto jleave; if(n_poption & n_PO_D_V) su_cs_dict_statistics(dp); /* TODO we need LOFI/AUTOREC TALLOC version which check overflow!! * TODO these then could _really_ return NIL... */ if(U32_MAX / sizeof(*cpp) <= cnt || (cpp = S(char const**,n_autorec_alloc(sizeof(*cpp) * cnt))) == NIL) goto jleave; xcpp = cpp; su_CS_DICT_FOREACH(dp, &dv) *xcpp++ = su_cs_dict_view_key(&dv); if(cnt > 1) su_sort_shell_vpp(su_S(void const**,cpp), cnt, su_cs_toolbox.tb_compare); for(xcpp = cpp; cnt > 0; ++xcpp, --cnt){ struct n_strlist *slp; if((slp = (*ptf)(cmdname, *xcpp, su_cs_dict_lookup(dp, *xcpp))) == NIL) continue; if(resp == NIL) resp = slp; else tailp->sl_next = slp; tailp = slp; } jleave: *result = resp; if(tailpp_or_nil != NIL) *tailpp_or_nil = tailp; NYD_OU; return rv; } FL struct n_strlist * mx_xy_dump_dict_gen_ptf(char const *cmdname, char const *key, void const *dat){ /* XXX real strlist + str_to_fmt() */ char *cp; struct n_strlist *slp; uz kl, dl, cl; char const *kp, *dp; NYD2_IN; kp = n_shexp_quote_cp(key, TRU1); dp = n_shexp_quote_cp(su_S(char const*,dat), TRU1); kl = su_cs_len(kp); dl = su_cs_len(dp); cl = su_cs_len(cmdname); slp = n_STRLIST_AUTO_ALLOC(cl + 1 + kl + 1 + dl +1); slp->sl_next = NIL; cp = slp->sl_dat; su_mem_copy(cp, cmdname, cl); cp += cl; *cp++ = ' '; su_mem_copy(cp, kp, kl); cp += kl; *cp++ = ' '; su_mem_copy(cp, dp, dl); cp += dl; *cp = '\0'; slp->sl_len = P2UZ(cp - slp->sl_dat); NYD2_OU; return slp; } FL boole mx_page_or_print_strlist(char const *cmdname, struct n_strlist *slp){ uz lines; FILE *fp; boole rv; NYD_IN; rv = TRU1; if((fp = mx_fs_tmp_open(cmdname, (mx_FS_O_RDWR | mx_FS_O_UNLINK | mx_FS_O_REGISTER), NIL)) == NIL) fp = n_stdout; /* Create visual result */ for(lines = 0; slp != NIL; ++lines, slp = slp->sl_next) if(fputs(slp->sl_dat, fp) == EOF || putc('\n', fp) == EOF){ rv = FAL0; break; } if(rv && lines == 0 && fprintf(fp, _("# no %s registered\n"), cmdname) < 0) rv = FAL0; if(fp != n_stdout){ page_or_print(fp, lines); mx_fs_close(fp); } NYD_OU; return rv; } #include "su/code-ou.h" /* s-it-mode */ s-nail-14.9.15/src/mx/child.c000066400000000000000000000422061352610246600155310ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Implementation of child.h. *@ TODO . argument and environment space constraints not tested. *@ TODO . use a SU child, offer+use our own stuff for "wait status" checks. *@ TODO (requires event loop then, likely) *@ TODO . STDERR is always "passed", yet not taken care of regarding termios! *@ TODO . we would need full and true job control handling *@ TODO But at least notion of background and foreground, see termios.c! * * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #undef su_FILE #define su_FILE child #define mx_SOURCE #define mx_SOURCE_CHILD #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif #include #include #include #include "mx/file-streams.h" #include "mx/sigs.h" #include "mx/termcap.h" #include "mx/termios.h" #include "mx/child.h" #include "su/code-in.h" struct a_child_ent{ struct a_child_ent *ce_link; s32 ce_pid; /* -1: struct can be gc'd */ s32 ce_status; /* wait status */ boole ce_done; /* has terminated */ boole ce_forget; /* will not be wait()ed upon */ boole ce_tios; /* Counts against child_termios_users */ boole ce_tios_suspended; /* Suspended via TERMIOS */ u8 ce__pad[4]; }; static struct a_child_ent *a_child_head; /* Cleanup internal structures which have been rendered obsolete (childs have * terminated) in the meantime; returns list to be freed. * Note: signals including SIGCHLD need to be blocked when calling this */ static struct a_child_ent *a_child_manager_cleanup(void); /* It or NIL is returned; if ceppp is set then it will point to the linked * storage of the return value: signals need to be blocked in this case! */ SINLINE struct a_child_ent *a_child_find(s32 pid, struct a_child_ent ***ceppp_or_nil); /* Handle SIGCHLD */ static void a_child__sigchld(int signo); /* Handle job control signals */ static boole a_child__on_termios_state_change(up cookie, u32 tiossc, s32 sig); static struct a_child_ent * a_child_manager_cleanup(void){ struct a_child_ent *nlp, **nlpp, **cepp, *cep; NYD_IN; nlp = NIL; nlpp = &nlp; for(cepp = &a_child_head; *cepp != NIL;){ if((*cepp)->ce_pid == -1){ cep = *cepp; *cepp = cep->ce_link; *nlpp = cep; nlpp = &cep->ce_link; }else cepp = &(*cepp)->ce_link; } NYD_OU; return nlp; } SINLINE struct a_child_ent * a_child_find(s32 pid, struct a_child_ent ***ceppp_or_nil){ struct a_child_ent **cepp, *cep; NYD2_IN; for(cepp = &a_child_head; (cep = *cepp) != NIL; cepp = &(*cepp)->ce_link) if(cep->ce_pid == pid) break; if(ceppp_or_nil != NIL) *ceppp_or_nil = cepp; NYD2_OU; return cep; } static void a_child__sigchld(int signo){ struct a_child_ent *cep; int status; pid_t pid; UNUSED(signo); for(;;){ pid = waitpid(-1, &status, WNOHANG); if(pid <= 0){ if(pid == -1 && su_err_no() == su_ERR_INTR) continue; break; } if((cep = a_child_find(S(s32,pid), NIL)) != NIL){ cep->ce_done = TRU1; cep->ce_status = status; if(cep->ce_forget) cep->ce_pid = -1; } } } static boole a_child__on_termios_state_change(up cookie, u32 tiossc, s32 sig){/* TODO bad */ struct a_child_ent *cep; if((cep = a_child_find(S(s32,cookie), NIL)) != NIL){ if(cep->ce_done) ; else if(tiossc & mx_TERMIOS_STATE_POP){ /* TODO this is bad - we should have a reaper timer in the * TODO (yet non-existing) event loop and shut this thing down * TODO gracefully */ n_err("Reaping child process %d\n", cep->ce_pid); cep->ce_tios = FAL0; kill(cep->ce_pid, (tiossc & mx_TERMIOS_STATE_SIGNAL ? sig : SIGTERM)); /* C99 */{ uz i; for(i = 0; i < 10; ++i){ n_msleep(100, FAL0); if(cep->ce_done) break; } if(!cep->ce_done) kill(cep->ce_pid, SIGKILL); } }else if(tiossc & mx_TERMIOS_STATE_SUSPEND){ if(!cep->ce_tios_suspended){ cep->ce_tios_suspended = TRU1; if(!(tiossc & mx_TERMIOS_STATE_SIGNAL)){ int wstat; pid_t wpid; kill(cep->ce_pid, SIGTSTP); wpid = waitpid(cep->ce_pid, &wstat, WUNTRACED); UNUSED(wpid); } } }else if(tiossc & mx_TERMIOS_STATE_RESUME){ if(cep->ce_tios_suspended){ cep->ce_tios_suspended = FAL0; /* TODO Sigh. We do not handle process groups and have a bg/fg * TODO notion, so we do handle the terminal even if kids have it. * TODO Since job control sigs are sent to all processes in * TODO a process group, we race with the child. * TODO Synchronize that is impossible; for now we and termios * TODO assume au */ if(!(tiossc & mx_TERMIOS_STATE_SIGNAL)) kill(cep->ce_pid, SIGCONT); } } } return FAL0; } void mx_child_controller_setup(void){ struct sigaction nact, oact; NYD_IN; nact.sa_handler = &a_child__sigchld; sigemptyset(&nact.sa_mask); nact.sa_flags = SA_RESTART #ifdef SA_NOCLDSTOP | SA_NOCLDSTOP #endif ; if(sigaction(SIGCHLD, &nact, &oact) != 0) n_panic(_("Cannot install signal handler for child process controller")); NYD_OU; } void mx_child_ctx_setup(struct mx_child_ctx *ccp){ NYD2_IN; ASSERT(ccp); su_mem_set(ccp, 0, sizeof *ccp); ccp->cc_fds[0] = ccp->cc_fds[1] = mx_CHILD_FD_PASS; NYD2_OU; } boole mx_child_run(struct mx_child_ctx *ccp){ s32 e; NYD_IN; ASSERT(ccp); ASSERT(ccp->cc_pid == 0); ASSERT(!(ccp->cc_flags & mx_CHILD_SPAWN_CONTROL_LINGER) || (ccp->cc_flags & mx_CHILD_SPAWN_CONTROL)); e = su_ERR_NONE; if(!mx_child_fork(ccp)) e = ccp->cc_error; else if(ccp->cc_pid == 0) goto jchild; else if(ccp->cc_flags & mx_CHILD_RUN_WAIT_LIFE){ if(!mx_child_wait(ccp)) e = ccp->cc_error; if((e != su_ERR_NONE || ccp->cc_exit_status < 0) && (ok_blook(bsdcompat) || ok_blook(bsdmsgs))) n_err(_("Fatal error in process\n")); } if(e != su_ERR_NONE){ n_perr(_("child_run()"), e); su_err_set_no(ccp->cc_error = e); } NYD_OU; return (e == su_ERR_NONE); jchild:{ char *argv[128 + 4]; /* TODO magic constant, fixed size -> su_vector */ int i; if(ccp->cc_env_addon != NIL){ extern char **environ; uz ei, ei_orig, ai, ai_orig; char **env; char const **env_addon; env_addon = ccp->cc_env_addon; /* TODO note we don't check the POSIX limit: * the total space used to store the environment and the arguments to * the process is limited to {ARG_MAX} bytes */ for(ei = 0; environ[ei] != NIL; ++ei) ; ei_orig = ei; for(ai = 0; env_addon[ai] != NIL; ++ai) ; ai_orig = ai; env = n_lofi_alloc(sizeof(*env) * (ei + ai +1)); su_mem_copy(env, environ, sizeof(*env) * ei); /* Replace all those keys that yet exist */ while(ai-- > 0){ char const *ee, *kvs; uz kl; ee = env_addon[ai]; kvs = su_cs_find_c(ee, '='); ASSERT(kvs != NIL); kl = P2UZ(kvs - ee); ASSERT(kl > 0); for(ei = ei_orig; ei-- > 0;){ char const *ekvs; if((ekvs = su_cs_find_c(env[ei], '=')) != NIL && kl == P2UZ(ekvs - env[ei]) && !su_mem_cmp(ee, env[ei], kl)){ env[ei] = UNCONST(char*,ee); env_addon[ai] = NIL; break; } } } /* And append the rest */ for(ei = ei_orig, ai = ai_orig; ai-- > 0;) if(env_addon[ai] != NIL) env[ei++] = UNCONST(char*,env_addon[ai]); env[ei] = NIL; environ = env; } i = (int)getrawlist(TRU1, argv, NELEM(argv) - 4, ccp->cc_cmd, su_cs_len(ccp->cc_cmd)); if(i >= 0){ if((argv[i++] = UNCONST(char*,ccp->cc_args[0])) != NIL && (argv[i++] = UNCONST(char*,ccp->cc_args[1])) != NIL && (argv[i++] = UNCONST(char*,ccp->cc_args[2])) != NIL) argv[i] = NIL; mx_child_in_child_setup(ccp); execvp(argv[0], argv); perror(argv[0]); } _exit(n_EXIT_ERR); } } boole mx_child_fork(struct mx_child_ctx *ccp){ struct a_child_ent *nlp, *cep; NYD_IN; ASSERT(ccp); ASSERT(ccp->cc_pid == 0); ASSERT(!(ccp->cc_flags & mx_CHILD_SPAWN_CONTROL_LINGER) || (ccp->cc_flags & mx_CHILD_SPAWN_CONTROL)); ASSERT(ccp->cc_error == su_ERR_NONE); if(n_poption & n_PO_D_VV) n_err(_("Forking child%s: %s %s %s %s\n"), (ccp->cc_flags & mx_CHILD_SPAWN_CONTROL ? _(" with spawn control") : su_empty), n_shexp_quote_cp(((ccp->cc_cmd != R(char*,-1) ? (ccp->cc_cmd != NIL ? ccp->cc_cmd : _("exec handled by caller")) : _("forked concurrent \"in-image\" code"))), FAL0), (ccp->cc_args[0] != NIL ? n_shexp_quote_cp(ccp->cc_args[0], FAL0) : su_empty), (ccp->cc_args[1] != NIL ? n_shexp_quote_cp(ccp->cc_args[1], FAL0) : su_empty), (ccp->cc_args[2] != NIL ? n_shexp_quote_cp(ccp->cc_args[2], FAL0) : su_empty)); if((ccp->cc_flags & mx_CHILD_SPAWN_CONTROL) && !mx_fs_pipe_cloexec(&ccp->cc__cpipe[0])){ ccp->cc_error = su_err_no(); goto jleave; } cep = su_TCALLOC(struct a_child_ent, 1); /* Does this child take the terminal? */ if(ccp->cc_fds[0] == mx_CHILD_FD_PASS || ccp->cc_fds[1] == mx_CHILD_FD_PASS){ ccp->cc_flags |= mx__CHILD_JOBCTL; cep->ce_tios = TRU1; } /* TODO It is actally very bad to block all the signals for such a long * TODO time, especially when taking into account on what is done in here */ mx_sigs_all_hold(SIGCHLD, 0); nlp = a_child_manager_cleanup(); /* If this child takes the terminal, adjust termios now, but do not yet * install our handler */ if(cep->ce_tios) mx_termios_cmdx(mx_TERMIOS_CMD_PUSH | mx_TERMIOS_CMD_HANDS_OFF); switch((ccp->cc_pid = cep->ce_pid = fork())){ case 0: goto jkid; case -1: ccp->cc_error = su_err_no(); /* Link in cleanup list on failure */ cep->ce_link = nlp; nlp = cep; /* And shutdown our termios environment */ if(cep->ce_tios) mx_termios_cmdx(mx_TERMIOS_CMD_POP | mx_TERMIOS_CMD_HANDS_OFF); mx_sigs_all_rele(); if(ccp->cc_flags & mx_CHILD_SPAWN_CONTROL){ close(S(int,ccp->cc__cpipe[0])); close(S(int,ccp->cc__cpipe[1])); } n_perr(_("child_fork(): fork failure"), ccp->cc_error); break; default: /* In the parent, conditionally wait on the control pipe */ if(ccp->cc_flags & mx_CHILD_SPAWN_CONTROL){ char ebuf[sizeof(ccp->cc_error)]; sz r; close(S(int,ccp->cc__cpipe[1])); r = read(S(int,ccp->cc__cpipe[0]), ebuf, sizeof ebuf); close(S(int,ccp->cc__cpipe[0])); switch(r){ case 0: goto jlink_child; case sizeof(ccp->cc_error): su_mem_copy(&ccp->cc_error, ebuf, sizeof(ccp->cc_error)); break; default: ccp->cc_error = su_ERR_CHILD; break; } /* Link in cleanup list on failure */ cep->ce_link = nlp; nlp = cep; /* And shutdown our termios environment */ if(cep->ce_tios) mx_termios_cmdx(mx_TERMIOS_CMD_POP | mx_TERMIOS_CMD_HANDS_OFF); }else{ jlink_child: cep->ce_link = a_child_head; a_child_head = cep; /* Time to install our termios handler */ if(cep->ce_tios) mx_termios_on_state_change_set(&a_child__on_termios_state_change, cep->ce_pid); } /* in_child_setup() will procmask() for the child */ mx_sigs_all_rele(); break; } /* Free all stale childs */ while(nlp != NIL){ cep = nlp; nlp = nlp->ce_link; su_FREE(cep); } jleave: NYD_OU; return (ccp->cc_error == su_ERR_NONE); jkid: /* Close the unused end of the control pipe right now */ if(ccp->cc_flags & mx_CHILD_SPAWN_CONTROL) close(S(int,ccp->cc__cpipe[0])); goto jleave; } void mx_child_in_child_setup(struct mx_child_ctx *ccp){ sigset_t fset; s32 fd, i; ASSERT(ccp); ASSERT(ccp->cc_pid == 0); /* All file descriptors other than 0, 1, and 2 are supposed to be cloexec */ /* TODO WHAT IS WITH STDERR_FILENO DAMMIT? */ if((i = ((fd = S(s32,ccp->cc_fds[0])) == mx_CHILD_FD_NULL))) ccp->cc_fds[0] = fd = open(n_path_devnull, O_RDONLY); if(fd >= 0){ dup2(fd, STDIN_FILENO); if(i) close(fd); } if((i = ((fd = S(s32,ccp->cc_fds[1])) == mx_CHILD_FD_NULL))) ccp->cc_fds[1] = fd = open(n_path_devnull, O_WRONLY); if(fd >= 0){ dup2(fd, STDOUT_FILENO); if(i) close(fd); } /* Close our side of the control pipe, unless we should wait for cloexec to * do that for us */ if((ccp->cc_flags & (mx_CHILD_SPAWN_CONTROL | mx_CHILD_SPAWN_CONTROL_LINGER) ) == mx_CHILD_SPAWN_CONTROL) close(S(int,ccp->cc__cpipe[1])); if(ccp->cc_mask != NIL){ sigset_t *ssp; ssp = S(sigset_t*,ccp->cc_mask); for(i = 1; i < NSIG; ++i) if(sigismember(ssp, i)) safe_signal(i, SIG_IGN); if(!sigismember(ssp, SIGINT)) safe_signal(SIGINT, SIG_DFL); } sigemptyset(&fset); sigprocmask(SIG_SETMASK, &fset, NIL); } void mx_child_in_child_exec_failed(struct mx_child_ctx *ccp, s32 err){ ASSERT(ccp); ASSERT(ccp->cc_pid == 0); ASSERT(ccp->cc_flags & mx_CHILD_SPAWN_CONTROL_LINGER); ccp->cc_error = err; (void)write(S(int,ccp->cc__cpipe[1]), &ccp->cc_error, sizeof(ccp->cc_error)); close(S(int,ccp->cc__cpipe[1])); } s32 mx_child_signal(struct mx_child_ctx *ccp, s32 sig){ s32 rv; struct a_child_ent *cep; NYD_IN; ASSERT(ccp); ASSERT(ccp->cc_pid > 0); if((cep = a_child_find(ccp->cc_pid, NIL)) != NIL){ ASSERT(!cep->ce_forget); if(cep->ce_done) rv = -1; else if((rv = kill(S(pid_t,ccp->cc_pid), sig)) != 0) rv = su_err_no(); }else ccp->cc_pid = rv = -1; NYD_OU; return rv; } void mx_child_forget(struct mx_child_ctx *ccp){ struct a_child_ent *cep, **cepp; NYD_IN; ASSERT(ccp); ASSERT(ccp->cc_pid > 0); mx_sigs_all_hold(SIGCHLD, 0); if((cep = a_child_find(ccp->cc_pid, &cepp)) != NIL){ /* XXX ASSERT sigprocmask blocked, need debug wrapper */ ASSERT(!cep->ce_forget); ASSERT(!(ccp->cc_flags & mx__CHILD_JOBCTL)); ASSERT(!cep->ce_tios); cep->ce_forget = TRU1; if(cep->ce_done) *cepp = cep->ce_link; } mx_sigs_all_rele(); if(cep != NIL && cep->ce_done) su_FREE(cep); ccp->cc_pid = -1; NYD_OU; } boole mx_child_wait(struct mx_child_ctx *ccp){ s32 ws; boole ok; struct a_child_ent *cep, **cepp; NYD_IN; ASSERT(ccp); ASSERT(ccp->cc_pid > 0); /* TODO Unless we place childs which take the terminal in their own * TODO thing (setsid();setlogin();ioctl(fd, TIOCSCTTY)), that is * TODO CHLD:setpgid(0,0); PAREN:setpgid(CHILD,0), * TODO tcsetpgrp(STDIN_FILENO,CHILD) * TODO We need to ensure job control signals get through */ mx_sigs_all_hold(SIGCHLD, -SIGTSTP, -SIGTTIN, -SIGTTOU, 0); ok = TRU1; if((cep = a_child_find(ccp->cc_pid, &cepp)) != NIL){ /* XXX It would actually be better to sigsuspend on SIGCHLD */ while(!cep->ce_done && waitpid(S(pid_t,ccp->cc_pid), &cep->ce_status, 0) == -1){ if((ws = su_err_no()) != su_ERR_INTR){ ok = FAL0; break; } } ws = cep->ce_status; *cepp = cep->ce_link; /* This must be the one which holds that level, no? */ if(cep->ce_tios) mx_termios_cmdx(mx_TERMIOS_CMD_POP | mx_TERMIOS_CMD_HANDS_OFF); }else{ cepp = R(struct a_child_ent**,-1); ws = 0; } mx_sigs_all_rele(); if(cep != NIL) su_FREE(cep); if(ok){ ccp->cc_exit_status = WEXITSTATUS(ws); if(!WIFEXITED(ws) && ccp->cc_exit_status > 0) ccp->cc_exit_status = -ccp->cc_exit_status; }else{ cep = NIL; ccp->cc_exit_status = -255; } ccp->cc_pid = -1; NYD_OU; return (cep != NIL); } #include "su/code-ou.h" /* s-it-mode */ s-nail-14.9.15/src/mx/cmd-charsetalias.c000066400000000000000000000116401352610246600176500ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Implementation of cmd-charsetalias.h. *@ TODO Support vput, i.e.: vput charsetalias x what-this-expands-to *@ TODO _CSAL -> _CCSAL * * Copyright (c) 2017 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #undef su_FILE #define su_FILE cmd_charsetalias #define mx_SOURCE #define mx_SOURCE_CMD_CHARSETALIAS #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif #include #include #include "mx/iconv.h" #include "mx/cmd-charsetalias.h" #include "su/code-in.h" /* ..of a_csal_dp */ #define a_CSAL_FLAGS (su_CS_DICT_OWNS | su_CS_DICT_HEAD_RESORT |\ su_CS_DICT_AUTO_SHRINK | su_CS_DICT_ERR_PASS) #define a_CSAL_TRESHOLD_SHIFT 4 static struct su_cs_dict *a_csal_dp, a_csal__d; /* XXX atexit _gut() (DVL()) */ int c_charsetalias(void *vp){ struct su_cs_dict_view dv; int rv; char const **argv, *key, *dat; NYD_IN; if((key = *(argv = vp)) == NIL){ struct n_strlist *slp; slp = NIL; rv = !(mx_xy_dump_dict("charsetalias", a_csal_dp, &slp, NIL, &mx_xy_dump_dict_gen_ptf) && mx_page_or_print_strlist("charsetalias", slp)); }else if(argv[1] == NIL || (argv[2] == NIL && argv[0][0] == '-' && argv[0][1] == '\0')){ if(argv[1] != NIL) key = argv[1]; dat = key; if((key = n_iconv_normalize_name(key)) != NIL && a_csal_dp != NIL && su_cs_dict_view_find(su_cs_dict_view_setup(&dv, a_csal_dp), key)){ struct n_strlist *slp; if(argv[1] == NIL) dat = S(char const*,su_cs_dict_view_data(&dv)); else dat = mx_charsetalias_expand(key, TRU1); slp = mx_xy_dump_dict_gen_ptf("charsetalias", key, dat); rv = (fputs(slp->sl_dat, n_stdout) == EOF); rv |= (putc('\n', n_stdout) == EOF); }else{ n_err(_("No such charsetalias: %s\n"), n_shexp_quote_cp(dat, FAL0)); rv = 1; } }else{ if(a_csal_dp == NIL) a_csal_dp = su_cs_dict_set_treshold_shift( su_cs_dict_create(&a_csal__d, a_CSAL_FLAGS, &su_cs_toolbox), a_CSAL_TRESHOLD_SHIFT); for(rv = 0; key != NIL; argv += 2, key = *argv){ if((key = n_iconv_normalize_name(key)) == NIL){ n_err(_("charsetalias: invalid source charset %s\n"), n_shexp_quote_cp(*argv, FAL0)); rv = 1; continue; }else if((dat = argv[1]) == NIL){ n_err(_("Synopsis: charsetalias: \n")); rv = 1; break; }else if((dat = n_iconv_normalize_name(dat)) == NIL){ n_err(_("charsetalias: %s: invalid target charset %s\n"), n_shexp_quote_cp(argv[0], FAL0), n_shexp_quote_cp(argv[1], FAL0)); rv = 1; continue; } if(su_cs_dict_replace(a_csal_dp, key, C(char*,dat)) > 0){ n_err(_("Failed to create `charsetalias' storage: %s\n"), n_shexp_quote_cp(key, FAL0)); rv = 1; } } } NYD_OU; return rv; } int c_uncharsetalias(void *vp){ char const **argv, *cp, *key; int rv; NYD_IN; rv = 0; cp = (argv = vp)[0]; do{ if(cp[1] == '\0' && cp[0] == '*'){ if(a_csal_dp != NIL) su_cs_dict_clear(a_csal_dp); }else if((key = n_iconv_normalize_name(cp)) == NIL || a_csal_dp == NIL || !su_cs_dict_remove(a_csal_dp, key)){ n_err(_("No such `charsetalias': %s\n"), n_shexp_quote_cp(cp, FAL0)); rv = 1; } }while((cp = *++argv) != NIL); NYD_OU; return rv; } char const * mx_charsetalias_expand(char const *cp, boole is_normalized){ uz i; char const *cp_orig, *dat; NYD_IN; cp_orig = cp; if(!is_normalized) cp = n_iconv_normalize_name(cp); if(a_csal_dp != NIL) for(i = 0;; ++i){ if((dat = S(char*,su_cs_dict_lookup(a_csal_dp, cp))) == NIL) break; cp = dat; if(i == 8) /* XXX Magic (same as for `ghost' expansion) */ break; } if(cp != cp_orig) cp = savestr(cp); NYD_OU; return cp; } #include "su/code-ou.h" /* s-it-mode */ s-nail-14.9.15/src/mx/cmd-cnd.c000066400000000000000000000456211352610246600157570ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Commands: conditional constructs. * * Copyright (c) 2014 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #undef su_FILE #define su_FILE cmd_cnd #define mx_SOURCE #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif #include #include #include /* TODO fake */ #include "su/code-in.h" #define a_CCND_IF_IS_ACTIVE() (n_go_data->gdc_ifcond != su_NIL) #define a_CCND_IF_IS_SKIP() \ (a_CCND_IF_IS_ACTIVE() &&\ (((struct a_ccnd_if_node*)n_go_data->gdc_ifcond)->cin_noop ||\ !((struct a_ccnd_if_node*)n_go_data->gdc_ifcond)->cin_go)) struct a_ccnd_if_node{ struct a_ccnd_if_node *cin_outer; boole cin_error; /* Bad expression, skip entire if..endif */ boole cin_noop; /* Outer stack !cin_go, entirely no-op */ boole cin_go; /* Green light */ boole cin_else; /* In `else' clause */ u8 cin__dummy[4]; }; struct a_ccnd_if_ctx{ char const * const *cic_argv_base; char const * const *cic_argv_max; /* BUT: .cic_argv MUST be terminated! */ char const * const *cic_argv; }; /* */ static void a_ccnd_oif_error(struct a_ccnd_if_ctx const *cicp, char const *msg_or_null, char const *nearby_or_null); /* noop and (1) don't work for real, only syntax-check and * (2) non-error return is ignored */ static s8 a_ccnd_oif_test(struct a_ccnd_if_ctx *cicp, boole noop); static s8 a_ccnd_oif_group(struct a_ccnd_if_ctx *cicp, uz level, boole noop); /* Shared `if' / `elif' implementation */ static int a_ccnd_if(void *v, boole iselif); static void a_ccnd_oif_error(struct a_ccnd_if_ctx const *cicp, char const *msg_or_null, char const *nearby_or_null){ struct str s; NYD2_IN; if(msg_or_null == NULL) msg_or_null = _("invalid expression syntax"); if(nearby_or_null != NULL) n_err(_("`if' conditional: %s -- near: %s\n"), msg_or_null, nearby_or_null); else n_err(_("`if' conditional: %s\n"), msg_or_null); if((n_psonce & n_PSO_INTERACTIVE) || (n_poption & n_PO_D_V)){ str_concat_cpa(&s, cicp->cic_argv_base, (*cicp->cic_argv_base != NULL ? " " : n_empty)); n_err(_(" Expression: %s\n"), s.s); str_concat_cpa(&s, cicp->cic_argv, (*cicp->cic_argv != NULL ? " " : n_empty)); n_err(_(" Stopped at: %s\n"), s.s); } NYD2_OU; } static s8 a_ccnd_oif_test(struct a_ccnd_if_ctx *cicp, boole noop){ char const *emsg, * const *argv, *cp, *lhv, *op, *rhv; uz argc; char c; s8 rv; NYD2_IN; rv = -1; emsg = NULL; argv = cicp->cic_argv; argc = P2UZ(cicp->cic_argv_max - cicp->cic_argv); cp = argv[0]; if(UNLIKELY(argc != 1 && argc != 3 && (argc != 2 || !(n_pstate & n_PS_ARGMOD_WYSH)))){ jesyn: if(emsg != NULL) emsg = V_(emsg); #ifdef mx_HAVE_REGEX jesyn_ntr: #endif a_ccnd_oif_error(cicp, emsg, cp); goto jleave; } if(argc == 1){ switch(*cp){ case '$': /* v15compat */ if(!(n_pstate & n_PS_ARGMOD_WYSH)){ /* v15compat (!wysh): $ trigger? */ if(cp[1] == '\0') goto jesyn; /* Look up the value in question, we need it anyway */ if(*++cp == '{'){ uz i = su_cs_len(cp); if(i > 0 && cp[i - 1] == '}') cp = savestrbuf(++cp, i -= 2); else goto jesyn; } lhv = noop ? NULL : n_var_vlook(cp, TRU1); rv = (lhv != NULL); break; } /* FALLTHRU */ default: switch((rv = n_boolify(cp, UZ_MAX, TRUM1))){ case FAL0: case TRU1: break; default: emsg = N_("Expected a boolean"); goto jesyn; } break; case 'R': case 'r': rv = ((n_psonce & n_PSO_SENDMODE) == 0); break; case 'S': case 's': rv = ((n_psonce & n_PSO_SENDMODE) != 0); break; case 'T': case 't': if(!su_cs_cmp_case(cp, "true")) /* Beware! */ rv = TRU1; else rv = ((n_psonce & n_PSO_INTERACTIVE) != 0); break; } }else if(argc == 2){ ASSERT(n_pstate & n_PS_ARGMOD_WYSH); emsg = N_("unrecognized condition"); if(cp[0] != '-' || cp[2] != '\0') goto jesyn; switch((c = cp[1])){ case 'N': case 'Z': if(noop) rv = TRU1; else{ lhv = n_var_vlook(argv[1], TRU1); rv = (c == 'N') ? (lhv != su_NIL) : (lhv == su_NIL); } break; case 'n': case 'z': if(noop) rv = TRU1; else{ lhv = argv[1]; rv = (c == 'n') ? (*lhv != '\0') : (*lhv == '\0'); } break; default: goto jesyn; } }else{ enum{ a_NONE, a_MOD = 1u<<0, a_ICASE = 1u<<1, a_SATURATED = 1u<<2 } flags = a_NONE; if(n_pstate & n_PS_ARGMOD_WYSH) lhv = cp; else{ if(*cp == '$'){ /* v15compat (!wysh): $ trigger? */ if(cp[1] == '\0') goto jesyn; /* Look up the value in question, we need it anyway */ if(*++cp == '{'){ uz i = su_cs_len(cp); if(i > 0 && cp[i - 1] == '}') cp = savestrbuf(++cp, i -= 2); else goto jesyn; } lhv = noop ? NULL : n_var_vlook(cp, TRU1); }else goto jesyn; } /* Three argument comparison form required, check syntax */ emsg = N_("unrecognized condition"); op = argv[1]; if((c = op[0]) == '\0') goto jesyn; /* May be modifier */ if(c == '@'){ /* v15compat */ n_OBSOLETE2(_("if/elif: please use ? modifier suffix, " "not @ prefix: %s"), savecatsep(n_shexp_quote_cp(argv[0], FAL0), ' ', savecatsep(n_shexp_quote_cp(argv[1], FAL0), ' ', n_shexp_quote_cp(argv[2], FAL0)))); for(;;){ c = *++op; if(c == 'i') flags |= a_ICASE; else break; } if(flags == a_NONE) flags = a_ICASE; }else if((cp = su_cs_find_c(op, '?')) != su_NIL){ if(cp[1] == '\0') flags |= a_MOD; else if(su_cs_starts_with_case("case", &cp[1])) flags |= a_ICASE; else if(su_cs_starts_with_case("saturated", &cp[1])) flags |= a_SATURATED; else{ emsg = N_("invalid modifier"); goto jesyn; } op = savestrbuf(op, P2UZ(cp - op)); /* v15compat */ cp = argv[0]; } if(op[1] == '\0'){ if(c != '<' && c != '>') goto jesyn; }else if(c != '-' && op[2] != '\0') goto jesyn; else if(c == '<' || c == '>'){ if(op[1] != '=') goto jesyn; }else if(c == '=' || c == '!'){ if(op[1] != '=' && op[1] != '%' && op[1] != '@' #ifdef mx_HAVE_REGEX && op[1] != '~' #endif ) goto jesyn; }else if(c == '-'){ if(op[1] == '\0' || op[2] == '\0' || op[3] != '\0') goto jesyn; if(op[1] == 'e'){ if(op[2] != 'q') goto jesyn; }else if(op[1] == 'g' || op[1] == 'l'){ if(op[2] != 'e' && op[2] != 't') goto jesyn; }else if(op[1] == 'n'){ if(op[2] != 'e') goto jesyn; }else goto jesyn; }else goto jesyn; /* The right hand side may also be a variable, more syntax checking */ emsg = N_("invalid right hand side"); if((rhv = argv[2]) == NULL /* Can't happen */) goto jesyn; if(!(n_pstate & n_PS_ARGMOD_WYSH)){ if(*rhv == '$'){/* v15compat */ if(*++rhv == '\0') goto jesyn; else if(*rhv == '{'){ uz i = su_cs_len(rhv); if(i > 0 && rhv[i - 1] == '}') rhv = savestrbuf(++rhv, i -= 2); else{ cp = --rhv; goto jesyn; } } if(noop) rhv = NULL; else rhv = n_var_vlook(cp = rhv, TRU1); } } /* A null value is treated as the empty string */ emsg = NULL; if(lhv == NULL) lhv = n_UNCONST(su_empty); if(rhv == NULL) rhv = n_UNCONST(su_empty); #ifdef mx_HAVE_REGEX if(op[1] == '~'){ regex_t re; int s; if(flags & a_SATURATED){ emsg = N_("invalid modifier for operational mode"); goto jesyn; } if((s = regcomp(&re, rhv, REG_EXTENDED | REG_NOSUB | (flags & (a_MOD | a_ICASE) ? REG_ICASE : 0))) != 0){ emsg = savecat(_("invalid regular expression: "), n_regex_err_to_doc(NULL, s)); goto jesyn_ntr; } if(!noop) rv = (regexec(&re, lhv, 0,NULL, 0) == REG_NOMATCH) ^ (c == '='); regfree(&re); }else #endif if(noop){ ; }else if(op[1] == '%' || op[1] == '@'){ if(flags & a_SATURATED){ emsg = N_("invalid modifier for operational mode"); goto jesyn; } if(op[1] == '@') /* v15compat */ n_OBSOLETE("`if'++: \"=@\" and \"!@\" became \"=%\" and \"!%\""); rv = ((flags & (a_MOD | a_ICASE) ? su_cs_find_case(lhv, rhv) : su_cs_find(lhv, rhv)) == NULL) ^ (c == '='); }else if(c == '-'){ u32 lhvis, rhvis; s64 lhvi, rhvi; if(flags & a_ICASE){ emsg = N_("invalid modifier for operational mode"); goto jesyn; } if(*lhv == '\0') lhv = n_0; if(*rhv == '\0') rhv = n_0; rhvis = lhvis = (flags & (a_MOD | a_SATURATED) ? su_IDEC_MODE_LIMIT_NOERROR : su_IDEC_MODE_NONE) | su_IDEC_MODE_SIGNED_TYPE; lhvis = su_idec_cp(&lhvi, lhv, 0, lhvis, su_NIL); rhvis = su_idec_cp(&rhvi, rhv, 0, rhvis, su_NIL); if((lhvis & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED) ) != su_IDEC_STATE_CONSUMED || (rhvis & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED) ) != su_IDEC_STATE_CONSUMED){ /* TODO if/elif: should support $! and set ERR-OVERFLOW!! */ emsg = N_("invalid integer number"); goto jesyn; } lhvi -= rhvi; switch(op[1]){ default: case 'e': rv = (lhvi == 0); break; case 'n': rv = (lhvi != 0); break; case 'l': rv = (op[2] == 't') ? lhvi < 0 : lhvi <= 0; break; case 'g': rv = (op[2] == 't') ? lhvi > 0 : lhvi >= 0; break; } break; }else{ s32 scmp; if(flags & a_SATURATED){ emsg = N_("invalid modifier for operational mode"); goto jesyn; } scmp = (flags & (a_MOD | a_ICASE)) ? su_cs_cmp_case(lhv, rhv) : su_cs_cmp(lhv, rhv); switch(c){ default: case '=': rv = (scmp == 0); break; case '!': rv = (scmp != 0); break; case '<': rv = (op[1] == '\0') ? scmp < 0 : scmp <= 0; break; case '>': rv = (op[1] == '\0') ? scmp > 0 : scmp >= 0; break; } } } if(noop && rv < 0) rv = TRU1; jleave: NYD2_OU; return rv; } static s8 a_ccnd_oif_group(struct a_ccnd_if_ctx *cicp, uz level, boole noop){ char const *emsg, *arg0, * const *argv, * const *argv_max_save; uz i; char unary, c; enum{ a_FIRST = 1<<0, a_END_OK = 1<<1, a_NEED_LIST = 1<<2, a_CANNOT_UNARY = a_NEED_LIST, a_CANNOT_OBRACK = a_NEED_LIST, a_CANNOT_CBRACK = a_FIRST, a_CANNOT_LIST = a_FIRST, a_CANNOT_COND = a_NEED_LIST } state; s8 rv, xrv; NYD2_IN; rv = -1; state = a_FIRST; unary = '\0'; emsg = NULL; for(;;){ arg0 = *(argv = cicp->cic_argv); if(arg0 == NULL){ if(!(state & a_END_OK)){ emsg = N_("missing expression (premature end)"); goto jesyn; } if(noop && rv < 0) rv = TRU1; break; /* goto jleave; */ } switch((c = *arg0)){ case '!': if(arg0[1] != '\0') goto jneed_cond; if(state & a_CANNOT_UNARY){ emsg = N_("cannot use a unary operator here"); goto jesyn; } unary = (unary != '\0') ? '\0' : c; state &= ~(a_FIRST | a_END_OK); cicp->cic_argv = ++argv; continue; case '[': case ']': if(arg0[1] != '\0') goto jneed_cond; if(c == '['){ if(state & a_CANNOT_OBRACK){ emsg = N_("cannot open a group here"); goto jesyn; } cicp->cic_argv = ++argv; if((xrv = a_ccnd_oif_group(cicp, level + 1, noop)) < 0){ rv = xrv; goto jleave; }else if(!noop) rv = (unary != '\0') ? !xrv : xrv; unary = '\0'; state &= ~(a_FIRST | a_END_OK); state |= (level == 0 ? a_END_OK : 0) | a_NEED_LIST; continue; }else{ if(state & a_CANNOT_CBRACK){ emsg = N_("cannot use closing bracket here"); goto jesyn; } if(level == 0){ emsg = N_("no open groups to be closed here"); goto jesyn; } cicp->cic_argv = ++argv; if(noop && rv < 0) rv = TRU1; goto jleave;/* break;break; */ } case '|': case '&': if(c != arg0[1] || arg0[2] != '\0') goto jneed_cond; if(state & a_CANNOT_LIST){ emsg = N_("cannot use a AND-OR list here"); goto jesyn; } noop = ((c == '&') ^ (rv == TRU1)); state &= ~(a_FIRST | a_END_OK | a_NEED_LIST); cicp->cic_argv = ++argv; continue; default: jneed_cond: if(state & a_CANNOT_COND){ emsg = N_("cannot use a `if' condition here"); goto jesyn; } for(i = 0;; ++i){ if((arg0 = argv[i]) == NULL) break; c = *arg0; if(c == '!' && arg0[1] == '\0') break; if((c == '[' || c == ']') && arg0[1] == '\0') break; if((c == '&' || c == '|') && c == arg0[1] && arg0[2] == '\0') break; } if(i == 0){ emsg = N_("empty conditional group"); goto jesyn; } argv_max_save = cicp->cic_argv_max; cicp->cic_argv_max = argv + i; if((xrv = a_ccnd_oif_test(cicp, noop)) < 0){ rv = xrv; goto jleave; }else if(!noop) rv = (unary != '\0') ? !xrv : xrv; cicp->cic_argv_max = argv_max_save; cicp->cic_argv = (argv += i); unary = '\0'; state &= ~a_FIRST; state |= a_END_OK | a_NEED_LIST; break; } } jleave: NYD2_OU; return rv; jesyn: if(emsg == NULL) emsg = N_("invalid grouping"); a_ccnd_oif_error(cicp, V_(emsg), arg0); rv = -1; goto jleave; } static int a_ccnd_if(void *v, boole iselif){ struct a_ccnd_if_ctx cic; char const * const *argv; uz argc; s8 xrv, rv; struct a_ccnd_if_node *cinp; NYD_IN; if(!iselif){ cinp = n_alloc(sizeof *cinp); cinp->cin_outer = n_go_data->gdc_ifcond; }else{ cinp = n_go_data->gdc_ifcond; ASSERT(cinp != NULL); } cinp->cin_error = FAL0; cinp->cin_noop = a_CCND_IF_IS_SKIP(); cinp->cin_go = TRU1; cinp->cin_else = FAL0; if(!iselif) n_go_data->gdc_ifcond = cinp; if(cinp->cin_noop){ rv = 0; goto jleave; } /* For heaven's sake, support comments _today_ TODO wyshlist.. */ for(argc = 0, argv = v; argv[argc] != NULL; ++argc) if(argv[argc][0] == '#'){ char const **nav = n_autorec_alloc(sizeof(char*) * (argc + 1)); uz i; for(i = 0; i < argc; ++i) nav[i] = argv[i]; nav[i] = NULL; argv = nav; break; } cic.cic_argv_base = cic.cic_argv = argv; cic.cic_argv_max = &argv[argc]; xrv = a_ccnd_oif_group(&cic, 0, FAL0); if(xrv >= 0){ cinp->cin_go = (boole)xrv; rv = 0; }else{ cinp->cin_error = cinp->cin_noop = TRU1; rv = 1; } jleave: NYD_OU; return rv; } FL int c_if(void *v){ int rv; NYD_IN; rv = a_ccnd_if(v, FAL0); NYD_OU; return rv; } FL int c_elif(void *v){ struct a_ccnd_if_node *cinp; int rv; NYD_IN; if((cinp = n_go_data->gdc_ifcond) == NULL || cinp->cin_else){ n_err(_("`elif' without a matching `if'\n")); rv = 1; }else if(!cinp->cin_error){ cinp->cin_go = !cinp->cin_go; /* Cause right _IF_IS_SKIP() result */ rv = a_ccnd_if(v, TRU1); }else rv = 0; NYD_OU; return rv; } FL int c_else(void *v){ int rv; struct a_ccnd_if_node *cinp; NYD_IN; UNUSED(v); if((cinp = n_go_data->gdc_ifcond) == NULL || cinp->cin_else){ n_err(_("`else' without a matching `if'\n")); rv = 1; }else{ cinp->cin_else = TRU1; cinp->cin_go = !cinp->cin_go; rv = 0; } NYD_OU; return rv; } FL int c_endif(void *v){ int rv; struct a_ccnd_if_node *cinp; NYD_IN; UNUSED(v); if((cinp = n_go_data->gdc_ifcond) == NULL){ n_err(_("`endif' without a matching `if'\n")); rv = 1; }else{ n_go_data->gdc_ifcond = cinp->cin_outer; n_free(cinp); rv = 0; } NYD_OU; return rv; } FL boole n_cnd_if_is_skip(void){ boole rv; NYD2_IN; rv = a_CCND_IF_IS_SKIP(); NYD2_OU; return rv; } FL void n_cnd_if_stack_del(struct n_go_data_ctx *gdcp){ struct a_ccnd_if_node *vp, *cinp; NYD2_IN; vp = gdcp->gdc_ifcond; gdcp->gdc_ifcond = NULL; while((cinp = vp) != NULL){ vp = cinp->cin_outer; n_free(cinp); } NYD2_OU; } #include "su/code-ou.h" /* s-it-mode */ s-nail-14.9.15/src/mx/cmd-commandalias.c000066400000000000000000000077671352610246600176540ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Implementation of cmd-commandalias.h. *@ TODO Support vput, i.e.: vput commandalias x what-this-expands-to *@ TODO _CMDAL -> _CCMDAL * * Copyright (c) 2017 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #undef su_FILE #define su_FILE cmd_commandalias #define mx_SOURCE #define mx_SOURCE_CMD_COMMANDALIAS #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif #include #include #include "mx/cmd-commandalias.h" #include "su/code-in.h" /* ..of a_cmdal_dp */ #define a_CMDAL_FLAGS (su_CS_DICT_POW2_SPACED | su_CS_DICT_OWNS |\ su_CS_DICT_HEAD_RESORT | su_CS_DICT_AUTO_SHRINK | su_CS_DICT_ERR_PASS) #define a_CMDAL_TRESHOLD_SHIFT 2 static struct su_cs_dict *a_cmdal_dp, a_cmdal__d; /* XXX atexit _gut() (DVL) */ int c_commandalias(void *vp){ struct su_cs_dict_view dv; struct n_string s_b, *s; int rv; char const **argv, *key; NYD_IN; if((key = *(argv = vp)) == NIL){ struct n_strlist *slp; slp = NIL; rv = !(mx_xy_dump_dict("commandalias", a_cmdal_dp, &slp, NIL, &mx_xy_dump_dict_gen_ptf) && mx_page_or_print_strlist("commandalias", slp)); goto jleave; } /* Verify the name is a valid one, and not a command modifier */ if(*key == '\0' || *n_cmd_isolate_name(key) != '\0' || !n_cmd_is_valid_name(key)){ n_err(_("`commandalias': not a valid command name: %s\n"), n_shexp_quote_cp(key, FAL0)); rv = 1; goto jleave; } if(argv[1] == NIL){ if(a_cmdal_dp != NIL && su_cs_dict_view_find(su_cs_dict_view_setup(&dv, a_cmdal_dp), key)){ struct n_strlist *slp; slp = mx_xy_dump_dict_gen_ptf("commandalias", key, su_cs_dict_view_data(&dv)); rv = (fputs(slp->sl_dat, n_stdout) == EOF); rv |= (putc('\n', n_stdout) == EOF); }else{ n_err(_("No such commandalias: %s\n"), n_shexp_quote_cp(key, FAL0)); rv = 1; } }else{ if(a_cmdal_dp == NIL) a_cmdal_dp = su_cs_dict_set_treshold_shift( su_cs_dict_create(&a_cmdal__d, a_CMDAL_FLAGS, &su_cs_toolbox), a_CMDAL_TRESHOLD_SHIFT); s = n_string_creat_auto(&s_b); s = n_string_book(s, 500); /* xxx magic */ while(*++argv != NIL){ if(s->s_len > 0) s = n_string_push_c(s, ' '); s = n_string_push_cp(s, *argv); /* XXX with SU string, EOVERFLOW++ !*/ } if(su_cs_dict_replace(a_cmdal_dp, key, n_string_cp(s)) <= 0) rv = 0; else{ n_err(_("Failed to create `commandalias' storage: %s\n"), n_shexp_quote_cp(key, FAL0)); rv = 1; } /*n_string_gut(s);*/ } jleave: NYD_OU; return rv; } int c_uncommandalias(void *vp){ int rv; NYD_IN; rv = !mx_unxy_dict("commandalias", a_cmdal_dp, vp); NYD_OU; return rv; } char const * mx_commandalias_exists(char const *name, char const **expansion_or_nil){ char const *dat; NYD_IN; if(a_cmdal_dp == NIL || (dat = S(char*,su_cs_dict_lookup(a_cmdal_dp, name))) == NIL) name = NIL; else if(expansion_or_nil != NIL) *expansion_or_nil = dat; NYD_OU; return name; } #include "su/code-ou.h" /* s-it-mode */ s-nail-14.9.15/src/mx/cmd-csop.c000066400000000000000000000325331352610246600161550ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Implementation of cmd-csop.h. *@ TODO - better commandline parser that can dive into subcommands could *@ TODO get rid of a lot of ERR_SYNOPSIS cruft. *@ TODO - _CSOP -> _CCSOP * * Copyright (c) 2017 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #undef su_FILE #define su_FILE cmd_csop #define mx_SOURCE #define mx_SOURCE_CMD_CSOP #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif su_EMPTY_FILE() #ifdef mx_HAVE_CMD_CSOP #include #include #include #include "mx/cmd-csop.h" #include "su/code-in.h" enum a_csop_cmd{ a_CSOP_CMD_LENGTH, a_CSOP_CMD_HASH32, a_CSOP_CMD_HASH, a_CSOP_CMD_FIND, a_CSOP_CMD_IFIND, /* v15compat */ a_CSOP_CMD_SUBSTRING, a_CSOP_CMD_TRIM, a_CSOP_CMD_TRIM_FRONT, a_CSOP_CMD_TRIM_END, a_CSOP_CMD__MAX }; enum a_csop_err{ a_CSOP_ERR_NONE, a_CSOP_ERR_SYNOPSIS, a_CSOP_ERR_SUBCMD, a_CSOP_ERR_MOD_NOT_ALLOWED, a_CSOP_ERR_NUM_RANGE, a_CSOP_ERR_STR_OVERFLOW, a_CSOP_ERR_STR_NODATA, a_CSOP_ERR_STR_GENERIC }; enum {a_CSOP_ERR__MAX = a_CSOP_ERR_STR_GENERIC}; CTA(S(uz,a_CSOP_CMD__MAX | a_CSOP_ERR__MAX) <= 0x7Fu, "Bit range excess"); enum a_csop_flags{ a_CSOP_NONE, a_CSOP_ERR = 1u<<0, /* There was an error */ a_CSOP_SOFTOVERFLOW = 1u<<1, a_CSOP_ISNUM = 1u<<2, a_CSOP_ISDECIMAL = 1u<<3, /* Print only decimal result */ a_CSOP_MOD_CASE = 1u<<4, /* Case-insensitive / XY */ a_CSOP_MOD_MASK = a_CSOP_MOD_CASE, a_CSOP__FMASK = 0x1FFu, a_CSOP__FSHIFT = 9u, a_CSOP__FCMDMASK = 0xFE00u, a_CSOP__TMP = 1u<<30 }; /* .csc_cmderr=8-bit, and so a_csop_subcmd can store CMD+MOD flags in 16-bit */ CTA(((S(u32,a_CSOP_CMD__MAX | a_CSOP_ERR__MAX) << a_CSOP__FSHIFT) & ~a_CSOP__FCMDMASK) == 0, "Bit ranges overlap"); struct a_csop_ctx{ u32 csc_flags; u8 csc_cmderr; /* On input, a_vexpr_cmd, on output (maybe) a_vexpr_err */ u8 csc__pad[3]; char const **csc_argv; char const *csc_cmd_name; char const *csc_varname; /* VPUT support */ char const *csc_varres; char const *csc_arg; /* The current arg (_ERR: which caused failure) */ s64 csc_lhv; char csc_iencbuf[2+1/* BASE# prefix*/ + su_IENC_BUFFER_SIZE + 1]; }; struct a_csop_subcmd{ u16 css_mpv; char css_name[14]; }; static struct a_csop_subcmd const a_csop_subcmds[] = { #undef a_X #define a_X(C,F) (S(u16,C) << a_CSOP__FSHIFT) | F {a_X(a_CSOP_CMD_LENGTH, 0), "length"}, {a_X(a_CSOP_CMD_HASH, a_CSOP_MOD_CASE), "hash"}, {a_X(a_CSOP_CMD_HASH32, a_CSOP_MOD_CASE), "hash32"}, {a_X(a_CSOP_CMD_FIND, a_CSOP_MOD_CASE), "find"}, {a_X(a_CSOP_CMD_IFIND, 0), "ifind"}, {a_X(a_CSOP_CMD_SUBSTRING, 0), "substring"}, {a_X(a_CSOP_CMD_TRIM, 0), "trim"}, {a_X(a_CSOP_CMD_TRIM_FRONT, 0), "trim-front\0"}, {a_X(a_CSOP_CMD_TRIM_END, 0), "trim-end"} #undef a_X }; /* Entered with .vc_flags=NONE(|MOD_CASE)? */ static void a_csop_cmd(struct a_csop_ctx *cscp); static void a_csop_cmd(struct a_csop_ctx *cscp){ uz i; NYD2_IN; switch(cscp->csc_cmderr){ default: case a_CSOP_CMD_LENGTH: cscp->csc_flags |= a_CSOP_ISNUM | a_CSOP_ISDECIMAL; if(cscp->csc_argv[0] == NIL || cscp->csc_argv[1] != NIL){ cscp->csc_flags |= a_CSOP_ERR; cscp->csc_cmderr = a_CSOP_ERR_SYNOPSIS; break; } cscp->csc_arg = cscp->csc_argv[0]; i = su_cs_len(cscp->csc_arg); if(UCMP(64, i, >, S64_MAX)){ cscp->csc_flags |= a_CSOP_ERR; cscp->csc_cmderr = a_CSOP_ERR_STR_OVERFLOW; break; } cscp->csc_lhv = S(s64,i); break; case a_CSOP_CMD_HASH32: case a_CSOP_CMD_HASH: cscp->csc_flags |= a_CSOP_ISNUM | a_CSOP_ISDECIMAL; if(cscp->csc_argv[0] == NIL || cscp->csc_argv[1] != NIL){ cscp->csc_flags |= a_CSOP_ERR; cscp->csc_cmderr = a_CSOP_ERR_SYNOPSIS; break; } cscp->csc_arg = cscp->csc_argv[0]; i = ((cscp->csc_flags & a_CSOP_MOD_CASE) ? su_cs_toolbox_case.tb_hash : su_cs_toolbox.tb_hash )(cscp->csc_arg); if(cscp->csc_cmderr == a_CSOP_CMD_HASH32) i = S(u32,i & U32_MAX); cscp->csc_lhv = S(s64,i); break; case a_CSOP_CMD_IFIND: n_OBSOLETE(_("csop: ifind: simply use find?[case] instead, please")); cscp->csc_flags |= a_CSOP_MOD_CASE; /* FALLTHRU */ case a_CSOP_CMD_FIND: cscp->csc_flags |= a_CSOP_ISNUM | a_CSOP_ISDECIMAL; if(cscp->csc_argv[0] == NIL || cscp->csc_argv[1] == NIL || cscp->csc_argv[2] != NIL){ cscp->csc_flags |= a_CSOP_ERR; cscp->csc_cmderr = a_CSOP_ERR_SYNOPSIS; break; } cscp->csc_arg = cscp->csc_argv[1]; cscp->csc_varres = ((cscp->csc_flags & a_CSOP_MOD_CASE) ? su_cs_find_case : su_cs_find)(cscp->csc_argv[0], cscp->csc_arg); if(cscp->csc_varres == NIL){ cscp->csc_flags |= a_CSOP_ERR; cscp->csc_cmderr = a_CSOP_ERR_STR_NODATA; break; } i = P2UZ(cscp->csc_varres - cscp->csc_argv[0]); if(UCMP(64, i, >, S64_MAX)){ cscp->csc_flags |= a_CSOP_ERR; cscp->csc_cmderr = a_CSOP_ERR_STR_OVERFLOW; break; } cscp->csc_lhv = S(s64,i); break; case a_CSOP_CMD_SUBSTRING: if(cscp->csc_argv[0] == NIL || (cscp->csc_argv[1] != NIL && (cscp->csc_argv[2] != NIL && cscp->csc_argv[3] != NIL))){ cscp->csc_flags |= a_CSOP_ERR; cscp->csc_cmderr = a_CSOP_ERR_SYNOPSIS; break; } cscp->csc_varres = cscp->csc_arg = cscp->csc_argv[0]; i = su_cs_len(cscp->csc_arg); /* Offset */ if(cscp->csc_argv[1] == NIL || cscp->csc_argv[1][0] == '\0') cscp->csc_lhv = 0; else if((su_idec_s64_cp(&cscp->csc_lhv, cscp->csc_argv[1], 0, NIL ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED) ) != su_IDEC_STATE_CONSUMED){ cscp->csc_flags |= a_CSOP_ERR; cscp->csc_cmderr = a_CSOP_ERR_NUM_RANGE; break; }else if(cscp->csc_lhv < 0){ if(UCMP(64, i, <, -cscp->csc_lhv)){ cscp->csc_flags |= a_CSOP_ERR; cscp->csc_cmderr = a_CSOP_ERR_NUM_RANGE; goto jesubstring_off; } cscp->csc_lhv += i; } if(LIKELY(UCMP(64, i, >=, cscp->csc_lhv))){ i -= cscp->csc_lhv; cscp->csc_varres += cscp->csc_lhv; }else{ jesubstring_off: if(n_poption & n_PO_D_V) n_err(_("vexpr: substring: offset argument too large: %s\n"), n_shexp_quote_cp(cscp->csc_arg, FAL0)); cscp->csc_flags |= a_CSOP_SOFTOVERFLOW; } /* Length */ if(cscp->csc_argv[2] != NIL){ if(cscp->csc_argv[2][0] == '\0') cscp->csc_lhv = 0; else if((su_idec_s64_cp(&cscp->csc_lhv, cscp->csc_argv[2], 0, NIL ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED) ) != su_IDEC_STATE_CONSUMED){ cscp->csc_flags |= a_CSOP_ERR; cscp->csc_cmderr = a_CSOP_ERR_NUM_RANGE; break; }else if(cscp->csc_lhv < 0){ if(UCMP(64, i, <, -cscp->csc_lhv)){ goto jesubstring_len; } cscp->csc_lhv += i; } if(UCMP(64, i, >=, cscp->csc_lhv)){ if(UCMP(64, i, !=, cscp->csc_lhv)) cscp->csc_varres = savestrbuf(cscp->csc_varres, S(uz,cscp->csc_lhv)); }else{ jesubstring_len: if(n_poption & n_PO_D_V) n_err(_("vexpr: substring: length argument too large: %s\n"), n_shexp_quote_cp(cscp->csc_arg, FAL0)); cscp->csc_flags |= a_CSOP_SOFTOVERFLOW; } } break; case a_CSOP_CMD_TRIM:{ struct str trim; enum n_str_trim_flags stf; stf = n_STR_TRIM_BOTH; if(0){ case a_CSOP_CMD_TRIM_FRONT: stf = n_STR_TRIM_FRONT; }else if(0){ case a_CSOP_CMD_TRIM_END: stf = n_STR_TRIM_END; } if(cscp->csc_argv[0] == NIL || cscp->csc_argv[1] != NIL){ cscp->csc_flags |= a_CSOP_ERR; cscp->csc_cmderr = a_CSOP_ERR_SYNOPSIS; break; } cscp->csc_arg = cscp->csc_argv[0]; trim.l = su_cs_len(trim.s = UNCONST(char*,cscp->csc_arg)); (void)n_str_trim(&trim, stf); cscp->csc_varres = savestrbuf(trim.s, trim.l); }break; } NYD2_OU; } int c_csop(void *vp){ struct a_csop_ctx csc; char const *cp; u32 f; uz i, j; NYD_IN; DVL( su_mem_set(&csc, 0xAA, sizeof csc); ) csc.csc_flags = a_CSOP_ERR; csc.csc_cmderr = a_CSOP_ERR_SUBCMD; csc.csc_argv = S(char const**,vp); csc.csc_varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *csc.csc_argv++ : NIL; csc.csc_arg = csc.csc_cmd_name = *csc.csc_argv++; csc.csc_varres = su_empty; if((cp = su_cs_find_c(csc.csc_cmd_name, '?')) != NIL){ j = P2UZ(cp - csc.csc_cmd_name); if(cp[1] != '\0' && !su_cs_starts_with_case("case", &cp[1])){ n_err(_("csop: invalid modifier used: %s\n"), n_shexp_quote_cp(csc.csc_cmd_name, FAL0)); f = a_CSOP_ERR; goto jleave; } f = a_CSOP_MOD_CASE; }else{ f = a_CSOP_NONE; if(*csc.csc_cmd_name == '@'){ /* v15compat */ n_OBSOLETE2(_("csop: please use ? modifier suffix, " "not @ prefix"), n_shexp_quote_cp(csc.csc_cmd_name, FAL0)); ++csc.csc_cmd_name; f = a_CSOP_MOD_CASE; } j = su_cs_len(csc.csc_cmd_name); } for(i = 0; i < NELEM(a_csop_subcmds); ++i){ if(su_cs_starts_with_case_n(a_csop_subcmds[i].css_name, csc.csc_cmd_name, j)){ csc.csc_cmd_name = a_csop_subcmds[i].css_name; i = a_csop_subcmds[i].css_mpv; if(UNLIKELY(f & a_CSOP_MOD_MASK)){ /*u32 f2; f2 = f & a_CSOP_MOD_MASK;*/ if(UNLIKELY(!(i & a_CSOP_MOD_MASK))){ csc.csc_cmderr = a_CSOP_ERR_MOD_NOT_ALLOWED; break; /* }else if(UNLIKELY(f2 != a_CSOP_MOD_MASK && f2 != (i & a_CSOP_MOD_MASK))){ csc.csc_cmderr = a_CSOP_ERR_MOD_NOT_SUPPORTED; break; */ } } csc.csc_arg = csc.csc_cmd_name; csc.csc_flags = f; i = (i & a_CSOP__FCMDMASK) >> a_CSOP__FSHIFT; csc.csc_cmderr = S(u8,i); a_csop_cmd(&csc); break; } } f = csc.csc_flags; if(LIKELY(!(f & a_CSOP_ERR))){ n_pstate_err_no = (f & a_CSOP_SOFTOVERFLOW) ? su_ERR_OVERFLOW : su_ERR_NONE; }else switch(csc.csc_cmderr){ case a_CSOP_ERR_NONE: ASSERT(0); break; case a_CSOP_ERR_SYNOPSIS: n_err(_("Synopsis: csop: <:argument:>\n")); n_pstate_err_no = su_ERR_INVAL; goto jenum; case a_CSOP_ERR_SUBCMD: n_err(_("csop: invalid subcommand: %s\n"), n_shexp_quote_cp(csc.csc_arg, FAL0)); n_pstate_err_no = su_ERR_INVAL; goto jenum; case a_CSOP_ERR_MOD_NOT_ALLOWED: n_err(_("csop: modifiers not allowed for subcommand: %s\n"), n_shexp_quote_cp(csc.csc_arg, FAL0)); n_pstate_err_no = su_ERR_INVAL; goto jenum; case a_CSOP_ERR_NUM_RANGE: n_err(_("csop: numeric argument invalid or out of range: %s\n"), n_shexp_quote_cp(csc.csc_arg, FAL0)); n_pstate_err_no = su_ERR_RANGE; goto jenum; default: jenum: f = a_CSOP_ERR | a_CSOP_ISNUM; csc.csc_lhv = -1; break; case a_CSOP_ERR_STR_OVERFLOW: n_err(_("csop: string length or offset overflows datatype\n")); n_pstate_err_no = su_ERR_OVERFLOW; goto jestr; case a_CSOP_ERR_STR_NODATA: n_pstate_err_no = su_ERR_NODATA; /* FALLTHRU*/ case a_CSOP_ERR_STR_GENERIC: jestr: csc.csc_varres = su_empty; f = a_CSOP_ERR; break; } /* Generate the variable value content. * Anticipate in our handling below! (Avoid needless work) */ if(f & a_CSOP_ISNUM){ cp = su_ienc(csc.csc_iencbuf, csc.csc_lhv, 10, su_IENC_MODE_SIGNED_TYPE); if(cp != NIL) csc.csc_varres = cp; else{ f |= a_CSOP_ERR; csc.csc_varres = su_empty; } } if(csc.csc_varname == NIL){ /* If there was no error and we are printing a numeric result, print some * more bases for the fun of it */ if(csc.csc_varres != NIL && fprintf(n_stdout, "%s\n", csc.csc_varres) < 0){ n_pstate_err_no = su_err_no(); f |= a_CSOP_ERR; } }else if(!n_var_vset(csc.csc_varname, S(up,csc.csc_varres))){ n_pstate_err_no = su_ERR_NOTSUP; f |= a_CSOP_ERR; } jleave: NYD_OU; return (f & a_CSOP_ERR) ? 1 : 0; } #include "su/code-ou.h" #endif /* mx_HAVE_CMD_CSOP */ /* s-it-mode */ s-nail-14.9.15/src/mx/cmd-filetype.c000066400000000000000000000303531352610246600170300ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Implementation of cmd-filetype.h. *@ TODO Simply use su_string data via "LOAD\0SAVE[\0]" values, then drop all *@ TODO the toolbox and such stuff in here! *@ TODO _FT_ -> _CFT * * Copyright (c) 2017 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #undef su_FILE #define su_FILE cmd_filetype #define mx_SOURCE #define mx_SOURCE_CMD_FILETYPE #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif #include #include #include #include "mx/cmd-filetype.h" #include "su/code-in.h" /* ..of a_ft_dp */ #define a_FT_FLAGS (su_CS_DICT_OWNS | su_CS_DICT_CASE |\ su_CS_DICT_HEAD_RESORT | su_CS_DICT_AUTO_SHRINK | su_CS_DICT_ERR_PASS) #define a_FT_TRESHOLD_SHIFT 4 struct a_ft_dat{ struct str ftd_load; struct str ftd_save; }; static struct mx_filetype const a_ft_OBSOLETE_xz = { /* TODO v15 compat */ "xz", 2, "xz -cd", sizeof("xz -cd") -1, "xz -cz", sizeof("xz -cz") -1 }, a_ft_OBSOLETE_gz = { "gz", 2, "gzip -cd", sizeof("gzip -cd") -1, "gzip -cz", sizeof("gzip -cz") -1 }, a_ft_OBSOLETE_bz2 = { "bz2", 3, "bzip2 -cd", sizeof("bzip2 -cd") -1, "bzip2 -cz", sizeof("bzip2 -cz") -1 }; /* +toolbox below */ static struct su_cs_dict *a_ft_dp, a_ft__d; /* XXX atexit _gut() (DVL()) */ /* */ static void *a_ft_clone(void const *t, u32 estate); #if DVLOR(1, 0) static void a_ft_delete(void *self); #else # define a_ft_delete su_mem_free #endif static void *a_ft_assign(void *self, void const *t, u32 estate); static struct su_toolbox const a_ft_tbox = su_TOOLBOX_I9R( &a_ft_clone, &a_ft_delete, &a_ft_assign, NIL, NIL ); /* */ static struct n_strlist *a_ft_dump(char const *cmdname, char const *key, void const *dat); static void * a_ft_clone(void const *t, u32 estate){ char *cp; uz l; struct a_ft_dat *rv; struct a_ft_dat const *tp; NYD_IN; estate &= su_STATE_ERR_MASK; /* The public entry ensures this fits U32_MAX! */ tp = S(struct a_ft_dat const*,t); l = sizeof(*rv) + tp->ftd_load.l +1 + tp->ftd_save.l +1; if((rv = S(struct a_ft_dat*,su_ALLOCATE(l, 1, estate))) != NIL){ cp = S(char*,&rv[1]); su_mem_copy(rv->ftd_load.s = cp, tp->ftd_load.s, (rv->ftd_load.l = tp->ftd_load.l) +1); cp += tp->ftd_load.l +1; su_mem_copy(rv->ftd_save.s = cp, tp->ftd_save.s, (rv->ftd_save.l = tp->ftd_save.l) +1); } NYD_OU; return rv; } #if DVLOR(1, 0) static void a_ft_delete(void *self){ NYD_IN; su_FREE(self); NYD_OU; } #endif static void * a_ft_assign(void *self, void const *t, u32 estate){ void *rv; NYD_IN; if((rv = a_ft_clone(t, estate)) != NIL) su_FREE(self); NYD_OU; return rv; } static struct n_strlist * a_ft_dump(char const *cmdname, char const *key, void const *dat){ /* XXX real strlist + str_to_fmt() */ char *cp; struct n_strlist *slp; uz kl, dloadl, dsavel, cl; char const *kp, *dloadp, *dsavep; struct a_ft_dat const *ftdp; NYD2_IN; ftdp = S(struct a_ft_dat const*,dat); kp = n_shexp_quote_cp(key, TRU1); dloadp = n_shexp_quote_cp(ftdp->ftd_load.s, TRU1); dsavep = n_shexp_quote_cp(ftdp->ftd_save.s, TRU1); kl = su_cs_len(kp); dloadl = su_cs_len(dloadp); dsavel = su_cs_len(dsavep); cl = su_cs_len(cmdname); slp = n_STRLIST_AUTO_ALLOC(cl + 1 + kl + 1 + dloadl + 1 + dsavel +1); slp->sl_next = NIL; cp = slp->sl_dat; su_mem_copy(cp, cmdname, cl); cp += cl; *cp++ = ' '; su_mem_copy(cp, kp, kl); cp += kl; *cp++ = ' '; su_mem_copy(cp, dloadp, dloadl); cp += dloadl; *cp++ = ' '; su_mem_copy(cp, dsavep, dsavel); cp += dsavel; *cp = '\0'; slp->sl_len = P2UZ(cp - slp->sl_dat); NYD2_OU; return slp; } int c_filetype(void *vp){ /* TODO support auto chains: .tar.gz -> .gz + .tar */ struct a_ft_dat ftd; struct su_cs_dict_view dv; int rv; char const **argv, *key; NYD_IN; if((key = *(argv = vp)) == NIL){ struct n_strlist *slp; slp = NIL; rv = !(mx_xy_dump_dict("filetype", a_ft_dp, &slp, NIL, &a_ft_dump) && mx_page_or_print_strlist("filetype", slp)); goto jleave; } if(argv[1] == NIL){ if(a_ft_dp != NIL && su_cs_dict_view_find(su_cs_dict_view_setup(&dv, a_ft_dp), key)){ struct n_strlist *slp; slp = a_ft_dump("filetype", su_cs_dict_view_key(&dv), su_cs_dict_view_data(&dv)); rv = (fputs(slp->sl_dat, n_stdout) == EOF); rv |= (putc('\n', n_stdout) == EOF); }else{ n_err(_("No such filetype: %s\n"), n_shexp_quote_cp(key, FAL0)); rv = 1; } }else{ if(a_ft_dp == NIL) a_ft_dp = su_cs_dict_set_treshold_shift( su_cs_dict_create(&a_ft__d, a_FT_FLAGS, &a_ft_tbox), a_FT_TRESHOLD_SHIFT); rv = 0; do{ /* while(*(argv += 3) != NIL); */ uz l; if(argv[1] == NIL || argv[2] == NIL){ n_err(_("Synopsis: filetype: " " \n")); rv = 1; break; } ftd.ftd_load.l = l = su_cs_len( ftd.ftd_load.s = UNCONST(char*,argv[1])); ftd.ftd_save.l = su_cs_len(ftd.ftd_save.s = UNCONST(char*,argv[2])); if(U32_MAX -2 <= l || U32_MAX -2 - l <= ftd.ftd_save.l || U32_MAX -2 - (l += ftd.ftd_save.l) <= sizeof ftd) goto jerr; if(su_cs_dict_replace(a_ft_dp, key, &ftd) > 0){ jerr: n_err(_("Failed to create `filetype' storage: %s\n"), n_shexp_quote_cp(key, FAL0)); rv = 1; } }while((key = *(argv += 3)) != NIL); } jleave: NYD_OU; return rv; } int c_unfiletype(void *vp){ int rv; NYD_IN; rv = !mx_unxy_dict("filetype", a_ft_dp, vp); NYD_OU; return rv; } boole mx_filetype_trial(struct mx_filetype *res_or_nil, char const *file){ struct stat stb; struct su_cs_dict_view dv; struct n_string s_b, *s; u32 l; NYD_IN; s = n_string_creat_auto(&s_b); s = n_string_assign_cp(s, file); /* XXX enomem++ */ s = n_string_push_c(s, '.'); l = s->s_len; if(a_ft_dp != NIL){ su_CS_DICT_FOREACH(a_ft_dp, &dv){ s = n_string_trunc(s, l); s = n_string_push_buf(s, su_cs_dict_view_key(&dv), su_cs_dict_view_key_len(&dv)); if(!stat(n_string_cp(s), &stb) && S_ISREG(stb.st_mode)){ if(res_or_nil != NIL){ struct a_ft_dat *ftdp; ftdp = S(struct a_ft_dat*,su_cs_dict_view_data(&dv)); res_or_nil->ft_ext_dat = su_cs_dict_view_key(&dv); res_or_nil->ft_ext_len = su_cs_dict_view_key_len(&dv); res_or_nil->ft_load_dat = ftdp->ftd_load.s; res_or_nil->ft_load_len = ftdp->ftd_load.l; res_or_nil->ft_save_dat = ftdp->ftd_save.s; res_or_nil->ft_save_len = ftdp->ftd_save.l; } goto jleave; /* TODO after v15 legacy drop: break; */ } } } /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz}, * TODO but NOT supporting *file-hook-{load,save}-EXTENSION* */ s = n_string_trunc(s, l); s = n_string_push_buf(s, a_ft_OBSOLETE_xz.ft_ext_dat, a_ft_OBSOLETE_xz.ft_ext_len); if(!stat(n_string_cp(s), &stb) && S_ISREG(stb.st_mode)){ n_OBSOLETE("auto .xz support vanishs, please use `filetype' command"); if(res_or_nil != NIL) *res_or_nil = a_ft_OBSOLETE_xz; goto jleave; } s = n_string_trunc(s, l); s = n_string_push_buf(s, a_ft_OBSOLETE_gz.ft_ext_dat, a_ft_OBSOLETE_gz.ft_ext_len); if(!stat(n_string_cp(s), &stb) && S_ISREG(stb.st_mode)){ n_OBSOLETE("auto .gz support vanishs, please use `filetype' command"); if(res_or_nil != NIL) *res_or_nil = a_ft_OBSOLETE_gz; goto jleave; } s = n_string_trunc(s, l); s = n_string_push_buf(s, a_ft_OBSOLETE_bz2.ft_ext_dat, a_ft_OBSOLETE_bz2.ft_ext_len); if(!stat(n_string_cp(s), &stb) && S_ISREG(stb.st_mode)){ n_OBSOLETE("auto .bz2 support vanishs, please use `filetype' command"); if(res_or_nil != NIL) *res_or_nil = a_ft_OBSOLETE_bz2; goto jleave; } file = NIL; jleave: /* n_string_gut(s); */ NYD_OU; return (file != NIL); } boole mx_filetype_exists(struct mx_filetype *res_or_nil, char const *file){ struct su_cs_dict_view dv, *dvp; char const *lext, *ext; NYD_IN; dvp = (a_ft_dp != NIL) ? su_cs_dict_view_setup(&dv, a_ft_dp) : NIL; lext = NIL; if((ext = su_cs_rfind_c(file, '/')) != NIL) file = ++ext; for(; (ext = su_cs_find_c(file, '.')) != NIL; lext = file = ext){ ++ext; if(dvp != NIL && su_cs_dict_view_find(dvp, ext)){ lext = ext; /* return value */ if(res_or_nil != NIL){ struct a_ft_dat *ftdp; ftdp = S(struct a_ft_dat*,su_cs_dict_view_data(&dv)); res_or_nil->ft_ext_dat = su_cs_dict_view_key(&dv); res_or_nil->ft_ext_len = su_cs_dict_view_key_len(&dv); res_or_nil->ft_load_dat = ftdp->ftd_load.s; res_or_nil->ft_load_len = ftdp->ftd_load.l; res_or_nil->ft_save_dat = ftdp->ftd_save.s; res_or_nil->ft_save_len = ftdp->ftd_save.l; } goto jleave; /* TODO after v15 legacy drop: break; */ } } /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz}, * TODO as well as supporting *file-hook-{load,save}-EXTENSION* */ if(lext == NIL) goto jleave; if(!su_cs_cmp_case(lext, "xz")){ n_OBSOLETE("auto .xz support vanishs, please use `filetype' command"); if(res_or_nil != NIL) *res_or_nil = a_ft_OBSOLETE_xz; goto jleave; }else if(!su_cs_cmp_case(lext, "gz")){ n_OBSOLETE("auto .gz support vanishs, please use `filetype' command"); if(res_or_nil != NIL) *res_or_nil = a_ft_OBSOLETE_gz; goto jleave; }else if(!su_cs_cmp_case(lext, "bz2")){ n_OBSOLETE("auto .bz2 support vanishs, please use `filetype' command"); if(res_or_nil != NIL) *res_or_nil = a_ft_OBSOLETE_bz2; goto jleave; }else{ char const *cload, *csave; char *vbuf; uz l; #undef a_X1 #define a_X1 "file-hook-load-" #undef a_X2 #define a_X2 "file-hook-save-" l = su_cs_len(lext); vbuf = n_lofi_alloc(l + MAX(sizeof(a_X1), sizeof(a_X2))); su_mem_copy(vbuf, a_X1, sizeof(a_X1) -1); su_mem_copy(&vbuf[sizeof(a_X1) -1], lext, l); vbuf[sizeof(a_X1) -1 + l] = '\0'; cload = n_var_vlook(vbuf, FAL0); su_mem_copy(vbuf, a_X2, sizeof(a_X2) -1); su_mem_copy(&vbuf[sizeof(a_X2) -1], lext, l); vbuf[sizeof(a_X2) -1 + l] = '\0'; csave = n_var_vlook(vbuf, FAL0); #undef a_X2 #undef a_X1 n_lofi_free(vbuf); if((csave != NIL) | (cload != NIL)){ n_OBSOLETE("*file-hook-{load,save}-EXTENSION* will vanish, " "please use the `filetype' command"); if(((csave != NIL) ^ (cload != NIL)) == 0){ if(res_or_nil != NIL){ res_or_nil->ft_ext_dat = lext; res_or_nil->ft_ext_len = l; res_or_nil->ft_load_dat = cload; res_or_nil->ft_load_len = su_cs_len(cload); res_or_nil->ft_save_dat = csave; res_or_nil->ft_save_len = su_cs_len(csave); } goto jleave; }else n_alert(_("Incomplete *file-hook-{load,save}-EXTENSION* for: .%s"), lext); } } lext = NIL; jleave: NYD_OU; return (lext != NIL); } #include "su/code-ou.h" /* s-it-mode */ s-nail-14.9.15/src/mx/cmd-folder.c000066400000000000000000000223221352610246600164570ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Folder related user commands. * * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany. * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1980, 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. */ #undef su_FILE #define su_FILE cmd_folder #define mx_SOURCE #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif #include #include "mx/child.h" #include "mx/net-pop3.h" #include "mx/tty.h" /* TODO fake */ #include "su/code-in.h" /* c_file, c_File */ static int _c_file(void *v, enum fedit_mode fm); static int _c_file(void *v, enum fedit_mode fm) { char **argv = v; int i; NYD2_IN; if(*argv == NULL){ n_folder_announce(n_ANNOUNCE_STATUS); i = 0; goto jleave; } if (n_pstate & n_PS_HOOK_MASK) { n_err(_("Cannot change folder from within a hook\n")); i = 1; goto jleave; } save_mbox_for_possible_quitstuff(); i = setfile(*argv, fm); if (i < 0) { i = 1; goto jleave; } ASSERT(!(fm & FEDIT_NEWMAIL)); /* (Prevent implementation error) */ if (n_pstate & n_PS_SETFILE_OPENED) temporary_folder_hook_check(FAL0); if (i > 0) { /* TODO Don't report "no messages" == 1 == error when we're in, e.g., * TODO a macro: because that recursed commando loop will terminate the * TODO entire macro due to that! So either the user needs to be able * TODO to react&ignore this "error" (as in "if DOSTUFF" or "DOSTUFF; * TODO if $?", then "overriding an "error"), or we need a different * TODO return that differentiates */ i = (n_pstate & n_PS_ROBOT) ? 0 : 1; goto jleave; } if(n_pstate & n_PS_SETFILE_OPENED) n_folder_announce(n_ANNOUNCE_CHANGE); i = 0; jleave: NYD2_OU; return i; } FL int c_file(void *v) { int rv; NYD_IN; rv = _c_file(v, FEDIT_NONE); NYD_OU; return rv; } FL int c_File(void *v) { int rv; NYD_IN; rv = _c_file(v, FEDIT_RDONLY); NYD_OU; return rv; } FL int c_newmail(void *v) { int val = 1, mdot; NYD_IN; UNUSED(v); if (n_pstate & n_PS_HOOK_MASK) n_err(_("Cannot call `newmail' from within a hook\n")); #ifdef mx_HAVE_IMAP else if(mb.mb_type == MB_IMAP && !imap_newmail(1)) ; #endif else if ((val = setfile(mailname, FEDIT_NEWMAIL | ((mb.mb_perm & MB_DELE) ? 0 : FEDIT_RDONLY)) ) == 0) { mdot = getmdot(1); setdot(message + mdot - 1); } NYD_OU; return val; } FL int c_noop(void *v) { int rv = 0; NYD_IN; UNUSED(v); switch (mb.mb_type) { #ifdef mx_HAVE_POP3 case MB_POP3: mx_pop3_noop(); break; #endif #ifdef mx_HAVE_IMAP case MB_IMAP: imap_noop(); break; #endif default: break; } NYD_OU; return rv; } FL int c_remove(void *v) { char const *fmt; uz fmt_len; char **args, *name, *ename; int ec; NYD_IN; if (*(args = v) == NULL) { n_err(_("Synopsis: remove: ...\n")); ec = 1; goto jleave; } ec = 0; fmt = _("Remove %s"); fmt_len = su_cs_len(fmt); do { if ((name = fexpand(*args, FEXP_FULL)) == NULL) continue; ename = n_shexp_quote_cp(name, FAL0); if (!su_cs_cmp(name, mailname)) { n_err(_("Cannot remove current mailbox %s\n"), ename); ec |= 1; continue; } /* C99 */{ boole asw; char *vb; uz vl; vl = su_cs_len(ename) + fmt_len +1; vb = n_autorec_alloc(vl); snprintf(vb, vl, fmt, ename); asw = mx_tty_yesorno(vb, TRU1); if(!asw) continue; } switch (which_protocol(name, TRU1, FAL0, NULL)) { case PROTO_FILE: if (unlink(name) == -1) { int se = su_err_no(); if (se == su_ERR_ISDIR) { struct stat sb; if (!stat(name, &sb) && S_ISDIR(sb.st_mode)) { if (!rmdir(name)) break; se = su_err_no(); } } n_perr(name, se); ec |= 1; } break; case PROTO_POP3: n_err(_("Cannot remove POP3 mailbox %s\n"), ename); ec |= 1; break; case PROTO_MAILDIR: #ifdef mx_HAVE_MAILDIR if(maildir_remove(name) != OKAY) ec |= 1; #else n_err(_("No Maildir directory support compiled in\n")); ec |= 1; #endif break; case PROTO_IMAP: #ifdef mx_HAVE_IMAP if(imap_remove(name) != OKAY) ec |= 1; #else n_err(_("No IMAP support compiled in\n")); ec |= 1; #endif break; case PROTO_UNKNOWN: default: n_err(_("Not removed: unknown protocol: %s\n"), ename); ec |= 1; break; } } while (*++args != NULL); jleave: NYD_OU; return ec; } FL int c_rename(void *v) { char **args = v, *oldn, *newn; enum protocol oldp; int ec; NYD_IN; ec = 1; if (args[0] == NULL || args[1] == NULL || args[2] != NULL) { n_err(_("Synopsis: rename: \n")); goto jleave; } if ((oldn = fexpand(args[0], FEXP_FULL)) == NULL) goto jleave; oldp = which_protocol(oldn, TRU1, FAL0, NULL); if ((newn = fexpand(args[1], FEXP_FULL)) == NULL) goto jleave; if(oldp != which_protocol(newn, TRU1, FAL0, NULL)) { n_err(_("Can only rename folders of same type\n")); goto jleave; } if (!su_cs_cmp(oldn, mailname) || !su_cs_cmp(newn, mailname)) { n_err(_("Cannot rename current mailbox %s\n"), n_shexp_quote_cp(oldn, FAL0)); goto jleave; } ec = 0; switch (oldp) { case PROTO_FILE: if (link(oldn, newn) == -1) { switch (su_err_no()) { case su_ERR_ACCES: case su_ERR_EXIST: case su_ERR_NAMETOOLONG: case su_ERR_NOSPC: case su_ERR_XDEV: n_perr(newn, 0); break; default: n_perr(oldn, 0); break; } ec |= 1; } else if (unlink(oldn) == -1) { n_perr(oldn, 0); ec |= 1; } break; case PROTO_MAILDIR: #ifdef mx_HAVE_MAILDIR if(rename(oldn, newn) == -1){ n_perr(oldn, 0); ec |= 1; } #else n_err(_("No Maildir directory support compiled in\n")); ec |= 1; #endif break; case PROTO_POP3: n_err(_("Cannot rename POP3 mailboxes\n")); ec |= 1; break; case PROTO_IMAP: #ifdef mx_HAVE_IMAP if(imap_rename(oldn, newn) != OKAY) ec |= 1; #else n_err(_("No IMAP support compiled in\n")); ec |= 1; #endif break; case PROTO_UNKNOWN: default: n_err(_("Unknown protocol in %s and %s; not renamed\n"), n_shexp_quote_cp(oldn, FAL0), n_shexp_quote_cp(newn, FAL0)); ec |= 1; break; } jleave: NYD_OU; return ec; } FL int c_folders(void *v){ /* TODO fexpand*/ enum fexp_mode const fexp = FEXP_NSHELL #ifndef mx_HAVE_IMAP | FEXP_LOCAL #endif ; struct mx_child_ctx cc; char const *cp; char **argv; int rv; NYD_IN; rv = 1; if(*(argv = v) != NULL){ if((cp = fexpand(*argv, fexp)) == NIL) goto jleave; }else cp = n_folder_query(); #ifdef mx_HAVE_IMAP if(which_protocol(cp, FAL0, FAL0, NIL) == PROTO_IMAP) rv = imap_folders(cp, *argv == NIL); else #endif { mx_child_ctx_setup(&cc); cc.cc_flags = mx_CHILD_RUN_WAIT_LIFE; cc.cc_cmd = ok_vlook(LISTER); cc.cc_args[0] = cp; if(mx_child_run(&cc) && cc.cc_exit_status == 0) rv = 0; } jleave: NYD_OU; return rv; } #include "su/code-ou.h" /* s-it-mode */ s-nail-14.9.15/src/mx/cmd-head.c000066400000000000000000000743751352610246600161240ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Header display, search, etc., related user commands. * * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany. * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1980, 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. */ #undef su_FILE #define su_FILE cmd_head #define mx_SOURCE #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif #include #include #include #include "mx/cmd-mlist.h" #include "mx/colour.h" #include "mx/termios.h" #include "mx/ui-str.h" /* TODO fake */ #include "su/code-in.h" static int _screen; /* Print out the header of a specific message. * time_current must be up-to-date when this is called. * a_chead__hprf: handle *headline* * a_chead__subject: -1 if Subject: yet seen, otherwise n_alloc()d Subject: * a_chead__putindent: print out the indenting in threaded display * a_chead__putuc: print out a Unicode character or a substitute for it, return * 0 on error or wcwidth() (or 1) on success */ static void a_chead_print_head(uz yetprinted, uz msgno, FILE *f, boole threaded, boole subject_thread_compress); static void a_chead__hprf(uz yetprinted, char const *fmt, uz msgno, FILE *f, boole threaded, boole subject_thread_compress, char const *attrlist); static char *a_chead__subject(struct message *mp, boole threaded, boole subject_thread_compress, uz yetprinted); static int a_chead__putindent(FILE *fp, struct message *mp, int maxwidth); static uz a_chead__putuc(int u, int c, FILE *fp); static int a_chead__dispc(struct message *mp, char const *a); /* Shared `z' implementation */ static int a_chead_scroll(char const *arg, boole onlynew); /* Shared `headers' implementation */ static int _headers(int msgspec); static void a_chead_print_head(uz yetprinted, uz msgno, FILE *f, boole threaded, boole subject_thread_compress){ enum {attrlen = 14}; char attrlist[attrlen +1], *cp; char const *fmt; NYD2_IN; if((cp = ok_vlook(attrlist)) != NULL){ if(su_cs_len(cp) == attrlen){ su_mem_copy(attrlist, cp, attrlen +1); goto jattrok; } n_err(_("*attrlist* is not of the correct length, using built-in\n")); } if(ok_blook(bsdcompat) || ok_blook(bsdflags)){ char const bsdattr[attrlen +1] = "NU *HMFAT+-$~"; su_mem_copy(attrlist, bsdattr, sizeof bsdattr); }else if(ok_blook(SYSV3)){ char const bsdattr[attrlen +1] = "NU *HMFAT+-$~"; su_mem_copy(attrlist, bsdattr, sizeof bsdattr); n_OBSOLETE(_("*SYSV3*: please use *bsdcompat* or *bsdflags*, " "or set *attrlist*")); }else{ char const pattr[attrlen +1] = "NUROSPMFAT+-$~"; su_mem_copy(attrlist, pattr, sizeof pattr); } jattrok: if((fmt = ok_vlook(headline)) == NULL){ fmt = ((ok_blook(bsdcompat) || ok_blook(bsdheadline)) ? "%>%a%m %-20f %16d %4l/%-5o %i%-S" : "%>%a%m %-18f %-16d %4l/%-5o %i%-s"); } a_chead__hprf(yetprinted, fmt, msgno, f, threaded, subject_thread_compress, attrlist); NYD2_OU; } static void a_chead__hprf(uz yetprinted, char const *fmt, uz msgno, FILE *f, boole threaded, boole subject_thread_compress, char const *attrlist) { char buf[16], cbuf[8], *cp, *subjline; char const *date, *name, *fp, *color_tag; int i, n, s, wleft, subjlen; struct message *mp; mx_COLOUR( struct mx_colour_pen *cpen_new su_COMMA *cpen_cur su_COMMA *cpen_bas; ) enum { _NONE = 0, _ISDOT = 1<<0, _ISTO = 1<<1, _IFMT = 1<<2, _LOOP_MASK = (1<<4) - 1, _SFMT = 1<<4, /* It is 'S' */ /* For the simple byte-based counts in wleft and n we sometimes need * adjustments to compensate for additional bytes of UTF-8 sequences */ _PUTCB_UTF8_SHIFT = 5, _PUTCB_UTF8_MASK = 3<<5 } flags = _NONE; NYD2_IN; UNUSED(buf); if ((mp = message + msgno - 1) == dot) flags = _ISDOT; color_tag = NULL; date = n_header_textual_date_info(mp, &color_tag); /* C99 */{ boole isto; n_header_textual_sender_info(mp, &cp, NULL, NULL, NULL, &isto); name = cp; if(isto) flags |= _ISTO; } subjline = NULL; /* Detect the width of the non-format characters in *headline*; * like that we can simply use putc() in the next loop, since we have * already calculated their column widths (TODO it's sick) */ wleft = subjlen = mx_termios_dimen.tiosd_width; for (fp = fmt; *fp != '\0'; ++fp) { if (*fp == '%') { if (*++fp == '-') ++fp; else if (*fp == '+') ++fp; if (su_cs_is_digit(*fp)) { n = 0; do n = 10*n + *fp - '0'; while (++fp, su_cs_is_digit(*fp)); subjlen -= n; } if (*fp == 'i') flags |= _IFMT; if (*fp == '\0') break; } else { #ifdef mx_HAVE_WCWIDTH if (n_mb_cur_max > 1) { wchar_t wc; if ((s = mbtowc(&wc, fp, n_mb_cur_max)) == -1) n = s = 1; else if ((n = wcwidth(wc)) == -1) n = 1; } else #endif n = s = 1; subjlen -= n; wleft -= n; while (--s > 0) ++fp; } } /* Walk *headline*, producing output TODO not (really) MB safe */ #ifdef mx_HAVE_COLOUR if(mx_COLOUR_IS_ACTIVE()){ if(flags & _ISDOT) color_tag = mx_COLOUR_TAG_SUM_DOT; cpen_bas = mx_colour_pen_create(mx_COLOUR_ID_SUM_HEADER, color_tag); mx_colour_pen_put(cpen_new = cpen_cur = cpen_bas); }else cpen_new = cpen_bas = cpen_cur = NULL; #endif for (fp = fmt; *fp != '\0'; ++fp) { char c; if ((c = *fp & 0xFF) != '%') { mx_COLOUR( if(mx_COLOUR_IS_ACTIVE() && (cpen_new = cpen_bas) != cpen_cur) mx_colour_pen_put(cpen_cur = cpen_new); ); putc(c, f); continue; } flags &= _LOOP_MASK; n = 0; s = 1; if ((c = *++fp) == '-') { s = -1; ++fp; } else if (c == '+') ++fp; if (su_cs_is_digit(*fp)) { do n = 10*n + *fp - '0'; while (++fp, su_cs_is_digit(*fp)); } if ((c = *fp & 0xFF) == '\0') break; n *= s; cbuf[1] = '\0'; switch (c) { case '%': goto jputcb; case '>': case '<': if (flags & _ISDOT) { mx_COLOUR( if(mx_COLOUR_IS_ACTIVE()) cpen_new = mx_colour_pen_create(mx_COLOUR_ID_SUM_DOTMARK, color_tag); ); if((n_psonce & n_PSO_UNICODE) && !ok_blook(headline_plain)){ if (c == '>') /* 25B8;BLACK RIGHT-POINTING SMALL TRIANGLE */ cbuf[1] = (char)0x96, cbuf[2] = (char)0xB8; else /* 25C2;BLACK LEFT-POINTING SMALL TRIANGLE */ cbuf[1] = (char)0x97, cbuf[2] = (char)0x82; c = (char)0xE2; cbuf[3] = '\0'; flags |= 2 << _PUTCB_UTF8_SHIFT; } } else c = ' '; goto jputcb; case '$': #ifdef mx_HAVE_SPAM if (n == 0) n = 5; if (UCMP(32, ABS(n), >, wleft)) wleft = 0; else{ snprintf(buf, sizeof buf, "%u.%02u", (mp->m_spamscore >> 8), (mp->m_spamscore & 0xFF)); n = fprintf(f, "%*s", n, buf); wleft = (n >= 0) ? wleft - n : 0; } break; #else c = '?'; goto jputcb; #endif case 'a': c = a_chead__dispc(mp, attrlist); jputcb: #ifdef mx_HAVE_COLOUR if(mx_COLOUR_IS_ACTIVE()){ if(cpen_new == cpen_cur) cpen_new = cpen_bas; if(cpen_new != cpen_cur) mx_colour_pen_put(cpen_cur = cpen_new); } #endif if (UCMP(32, ABS(n), >, wleft)) n = (n < 0) ? -wleft : wleft; cbuf[0] = c; n = fprintf(f, "%*s", n, cbuf); if (n >= 0) { wleft -= n; if ((n = (flags & _PUTCB_UTF8_MASK)) != 0) { n >>= _PUTCB_UTF8_SHIFT; wleft += n; } } else { wleft = 0; /* TODO I/O error.. ? break? */ } #ifdef mx_HAVE_COLOUR if(mx_COLOUR_IS_ACTIVE() && (cpen_new = cpen_bas) != cpen_cur) mx_colour_pen_put(cpen_cur = cpen_new); #endif break; case 'd': if (n == 0) n = 16; if (UCMP(32, ABS(n), >, wleft)) n = (n < 0) ? -wleft : wleft; n = fprintf(f, "%*.*s", n, ABS(n), date); wleft = (n >= 0) ? wleft - n : 0; break; case 'e': if (n == 0) n = 2; if (UCMP(32, ABS(n), >, wleft)) wleft = 0; else{ n = fprintf(f, "%*u", n, (threaded == 1 ? mp->m_level : 0)); wleft = (n >= 0) ? wleft - n : 0; } break; case 'f': if (n == 0) { n = 18; if (s < 0) n = -n; } i = ABS(n); if (i > wleft) { i = wleft; n = (n < 0) ? -wleft : wleft; } if (flags & _ISTO) {/* XXX tr()! */ if(wleft <= 3){ wleft = 0; break; } i -= 3; } n = fprintf(f, "%s%s", ((flags & _ISTO) ? "To " : n_empty), colalign(name, i, n, &wleft)); if (n < 0) wleft = 0; else if (flags & _ISTO) wleft -= 3; break; case 'i': if (threaded) { #ifdef mx_HAVE_COLOUR if(mx_COLOUR_IS_ACTIVE()){ cpen_new = mx_colour_pen_create(mx_COLOUR_ID_SUM_THREAD, color_tag); if(cpen_new != cpen_cur) mx_colour_pen_put(cpen_cur = cpen_new); } #endif n = a_chead__putindent(f, mp, MIN(wleft, S(int,mx_termios_dimen.tiosd_width) - 60)); wleft = (n >= 0) ? wleft - n : 0; #ifdef mx_HAVE_COLOUR if(mx_COLOUR_IS_ACTIVE() && (cpen_new = cpen_bas) != cpen_cur) mx_colour_pen_put(cpen_cur = cpen_new); #endif } break; case 'L': /* ML status */ jmlist: /* v15compat */ switch(mx_mlist_query_mp(mp, mx_MLIST_OTHER)){ case mx_MLIST_OTHER: c = ' '; break; case mx_MLIST_KNOWN: c = 'l'; break; case mx_MLIST_SUBSCRIBED: c = 'L'; break; } goto jputcb; case 'l': if (n == 0) n = 4; if (UCMP(32, ABS(n), >, wleft)) wleft = 0; else if (mp->m_xlines) { n = fprintf(f, "%*ld", n, mp->m_xlines); wleft = (n >= 0) ? wleft - n : 0; } else { n = ABS(n); wleft -= n; while (n-- != 0) putc(' ', f); } break; case 'm': if (n == 0) { n = 3; if (threaded) for (i = msgCount; i > 999; i /= 10) ++n; } if (UCMP(32, ABS(n), >, wleft)) wleft = 0; else{ n = fprintf(f, "%*lu", n, (ul)msgno); wleft = (n >= 0) ? wleft - n : 0; } break; case 'o': if (n == 0) n = -5; if (UCMP(32, ABS(n), >, wleft)) wleft = 0; else{ n = fprintf(f, "%*lu", n, (ul)mp->m_xsize); wleft = (n >= 0) ? wleft - n : 0; } break; case 'S': flags |= _SFMT; /*FALLTHRU*/ case 's': if (n == 0) n = subjlen - 2; if (n > 0 && s < 0) n = -n; if (subjlen > wleft) subjlen = wleft; if (UCMP(32, ABS(n), >, subjlen)) n = (n < 0) ? -subjlen : subjlen; if (flags & _SFMT) n -= (n < 0) ? -2 : 2; if (n == 0) break; if (subjline == NULL) subjline = a_chead__subject(mp, (threaded && (flags & _IFMT)), subject_thread_compress, yetprinted); if (subjline == (char*)-1) { n = fprintf(f, "%*s", n, n_empty); wleft = (n >= 0) ? wleft - n : 0; } else { n = fprintf(f, ((flags & _SFMT) ? "\"%s\"" : "%s"), colalign(subjline, ABS(n), n, &wleft)); if (n < 0) wleft = 0; } break; case 'T': n_OBSOLETE("*headline*: please use %L not %T for mailing-list " "status"); goto jmlist; case 't': if (n == 0) { n = 3; if (threaded) for (i = msgCount; i > 999; i /= 10) ++n; } if (UCMP(32, ABS(n), >, wleft)) wleft = 0; else{ n = fprintf(f, "%*lu", n, (threaded ? (ul)mp->m_threadpos : (ul)msgno)); wleft = (n >= 0) ? wleft - n : 0; } break; case 'U': #ifdef mx_HAVE_IMAP if (n == 0) n = 9; if (UCMP(32, ABS(n), >, wleft)) wleft = 0; else{ n = fprintf(f, "%*" PRIu64 , n, mp->m_uid); wleft = (n >= 0) ? wleft - n : 0; } break; #else c = '0'; goto jputcb; #endif default: if (n_poption & n_PO_D_V) n_err(_("Unknown *headline* format: %%%c\n"), c); c = '?'; goto jputcb; } if (wleft <= 0) break; } mx_COLOUR( mx_colour_reset(); ) putc('\n', f); if (subjline != NULL && subjline != (char*)-1) n_free(subjline); NYD2_OU; } static char * a_chead__subject(struct message *mp, boole threaded, boole subject_thread_compress, uz yetprinted) { struct str in, out; char *rv, *ms; NYD2_IN; rv = (char*)-1; if ((ms = hfield1("subject", mp)) == NULL) goto jleave; in.l = su_cs_len(in.s = ms); mime_fromhdr(&in, &out, TD_ICONV | TD_ISPR); rv = ms = out.s; if (!threaded || !subject_thread_compress || mp->m_level == 0) goto jleave; /* In a display thread - check whether this message uses the same * Subject: as it's parent or elder neighbour, suppress printing it if * this is the case. To extend this a bit, ignore any leading Re: or * Fwd: plus follow-up WS. Ignore invisible messages along the way */ ms = n_UNCONST(subject_re_trim(n_UNCONST(ms))); for (; (mp = prev_in_thread(mp)) != NULL && yetprinted-- > 0;) { char *os; if (visible(mp) && (os = hfield1("subject", mp)) != NULL) { struct str oout; int x; in.l = su_cs_len(in.s = os); mime_fromhdr(&in, &oout, TD_ICONV | TD_ISPR); x = su_cs_cmp_case(ms, subject_re_trim(oout.s)); n_free(oout.s); if (!x) { n_free(out.s); rv = (char*)-1; } break; } } jleave: NYD2_OU; return rv; } static int a_chead__putindent(FILE *fp, struct message *mp, int maxwidth)/* XXX magics */ { struct message *mq; int *unis, indlvl, indw, i, important = MNEW | MFLAGGED; char *cs; NYD2_IN; if (mp->m_level == 0 || maxwidth == 0) { indw = 0; goto jleave; } cs = n_lofi_alloc(mp->m_level); unis = n_lofi_alloc(mp->m_level * sizeof *unis); i = mp->m_level - 1; if (mp->m_younger && UCMP(32, i + 1, ==, mp->m_younger->m_level)) { if (mp->m_parent && mp->m_parent->m_flag & important) unis[i] = mp->m_flag & important ? 0x2523 : 0x2520; else unis[i] = mp->m_flag & important ? 0x251D : 0x251C; cs[i] = '+'; } else { if (mp->m_parent && mp->m_parent->m_flag & important) unis[i] = mp->m_flag & important ? 0x2517 : 0x2516; else unis[i] = mp->m_flag & important ? 0x2515 : 0x2514; cs[i] = '\\'; } mq = mp->m_parent; for (i = mp->m_level - 2; i >= 0; --i) { if (mq) { if (UCMP(32, i, >, mq->m_level - 1)) { unis[i] = cs[i] = ' '; continue; } if (mq->m_younger) { if (mq->m_parent && (mq->m_parent->m_flag & important)) unis[i] = 0x2503; else unis[i] = 0x2502; cs[i] = '|'; } else unis[i] = cs[i] = ' '; mq = mq->m_parent; } else unis[i] = cs[i] = ' '; } --maxwidth; for (indlvl = indw = 0; (u8)indlvl < mp->m_level && indw < maxwidth; ++indlvl) { if (indw < maxwidth - 1) indw += (int)a_chead__putuc(unis[indlvl], cs[indlvl] & 0xFF, fp); else indw += (int)a_chead__putuc(0x21B8, '^', fp); } indw += a_chead__putuc(0x25B8, '>', fp); n_lofi_free(unis); n_lofi_free(cs); jleave: NYD2_OU; return indw; } static uz a_chead__putuc(int u, int c, FILE *fp){ uz rv; NYD2_IN; UNUSED(u); #ifdef mx_HAVE_NATCH_CHAR if((n_psonce & n_PSO_UNICODE) && (u & ~(wchar_t)0177) && !ok_blook(headline_plain)){ char mbb[MB_LEN_MAX]; int i, n; if((n = wctomb(mbb, u)) > 0){ rv = wcwidth(u); for(i = 0; i < n; ++i) if(putc(mbb[i] & 0377, fp) == EOF){ rv = 0; break; } }else if(n == 0) rv = (putc('\0', fp) != EOF); else rv = 0; }else #endif rv = (putc(c, fp) != EOF); NYD2_OU; return rv; } static int a_chead__dispc(struct message *mp, char const *a) { int i = ' '; NYD2_IN; if ((mp->m_flag & (MREAD | MNEW)) == MREAD) i = a[3]; if ((mp->m_flag & (MREAD | MNEW)) == (MREAD | MNEW)) i = a[2]; if (mp->m_flag & MANSWERED) i = a[8]; if (mp->m_flag & MDRAFTED) i = a[9]; if ((mp->m_flag & (MREAD | MNEW)) == MNEW) i = a[0]; if (!(mp->m_flag & (MREAD | MNEW))) i = a[1]; if (mp->m_flag & MSPAM) i = a[12]; if (mp->m_flag & MSPAMUNSURE) i = a[13]; if (mp->m_flag & MSAVED) i = a[4]; if (mp->m_flag & MPRESERVE) i = a[5]; if (mp->m_flag & (MBOX | MBOXED)) i = a[6]; if (mp->m_flag & MFLAGGED) i = a[7]; if (mb.mb_threaded == 1) { /* TODO bad, and m_collapsed is weird */ /* TODO So this does not work because of weird thread handling and * TODO intermixing view and controller except when run via -L from * TODO command line; in general these flags should go and we need * TODO specific *headline* formats which always work and indicate * TODO whether a message is in a thread, the head of a subthread etc. */ if (mp->m_collapsed > 0) i = a[11]; else if (mp->m_collapsed < 0) i = a[10]; } NYD2_OU; return i; } static int a_chead_scroll(char const *arg, boole onlynew){ sz l; boole isabs; int msgspec, size, maxs; NYD2_IN; /* TODO scroll problem: we do not know whether + and $ have already reached * TODO the last screen in threaded mode */ msgspec = onlynew ? -1 : 0; size = (int)/*TODO*/n_screensize(); if((maxs = msgCount / size) > 0 && msgCount % size == 0) --maxs; if(arg == NULL) arg = n_empty; switch(*arg){ case '\0': ++_screen; goto jfwd; case '^': if(arg[1] != '\0') goto jerr; if(_screen == 0) goto jerrbwd; _screen = 0; break; case '$': if(arg[1] != '\0') goto jerr; if(_screen == maxs) goto jerrfwd; _screen = maxs; break; case '+': if(arg[1] == '\0') ++_screen; else{ isabs = FAL0; ++arg; if(0){ case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '0': isabs = TRU1; } if((su_idec_sz_cp(&l, arg, 0, NULL ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED) ) != su_IDEC_STATE_CONSUMED) goto jerr; if(l > maxs - (isabs ? 0 : _screen)) goto jerrfwd; _screen = isabs ? (int)l : _screen + l; } jfwd: if(_screen > maxs){ jerrfwd: _screen = maxs; fprintf(n_stdout, _("On last screenful of messages\n")); } break; case '-': if(arg[1] == '\0') --_screen; else{ if((su_idec_sz_cp(&l, ++arg, 0, NULL ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED) ) != su_IDEC_STATE_CONSUMED) goto jerr; if(l > _screen) goto jerrbwd; _screen -= l; } if(_screen < 0){ jerrbwd: _screen = 0; fprintf(n_stdout, _("On first screenful of messages\n")); } if(msgspec == -1) msgspec = -2; break; default: jerr: n_err(_("Unrecognized scrolling command: %s\n"), arg); size = 1; goto jleave; } size = _headers(msgspec); jleave: NYD2_OU; return size; } static int _headers(int msgspec) /* TODO rework v15 */ { boole needdot, showlast; int g, k, mesg, size; struct message *lastmq, *mp, *mq; int volatile lastg; u32 volatile flag; enum mflag fl; NYD_IN; time_current_update(&time_current, FAL0); fl = MNEW | MFLAGGED; flag = 0; lastg = 1; lastmq = NULL; size = (int)/*TODO*/n_screensize(); if (_screen < 0) _screen = 0; #if 0 /* FIXME original code path */ k = _screen * size; #else if (msgspec <= 0) k = _screen * size; else k = msgspec; #endif if (k >= msgCount) k = msgCount - size; if (k < 0) k = 0; needdot = (msgspec <= 0) ? TRU1 : (dot != &message[msgspec - 1]); showlast = ok_blook(showlast); if (mb.mb_threaded == 0) { g = 0; mq = message; for (mp = message; PCMP(mp, <, message + msgCount); ++mp) if (visible(mp)) { if (g % size == 0) mq = mp; if (mp->m_flag & fl) { lastg = g; lastmq = mq; } if ((msgspec > 0 && PCMP(mp, ==, message + msgspec - 1)) || (msgspec == 0 && g == k) || (msgspec == -2 && g == k + size && lastmq) || (msgspec < 0 && g >= k && (mp->m_flag & fl) != 0)) break; g++; } if (lastmq && (msgspec == -2 || (msgspec == -1 && PCMP(mp, ==, message + msgCount)))) { g = lastg; mq = lastmq; } _screen = g / size; mp = mq; mesg = (int)P2UZ(mp - message); #ifdef mx_HAVE_IMAP if (mb.mb_type == MB_IMAP) imap_getheaders(mesg + 1, mesg + size); #endif mx_COLOUR( mx_colour_env_create(mx_COLOUR_CTX_SUM, n_stdout, FAL0); ) n_autorec_relax_create(); for(lastmq = NULL, mq = &message[msgCount]; mp < mq; lastmq = mp, ++mp){ ++mesg; if (!visible(mp)) continue; if (UCMP(32, flag, >=, size)) break; if(needdot){ if(showlast){ if(UCMP(32, flag, ==, size - 1) || &mp[1] == mq) goto jdot_unsort; }else if(flag == 0){ jdot_unsort: needdot = FAL0; setdot(mp); } } ++flag; a_chead_print_head(0, mesg, n_stdout, FAL0, FAL0); n_autorec_relax_unroll(); } if(needdot && ok_blook(showlast)) /* xxx will not show */ setdot(lastmq); n_autorec_relax_gut(); mx_COLOUR( mx_colour_env_gut(); ) } else { /* threaded */ g = 0; mq = threadroot; for (mp = threadroot; mp; mp = next_in_thread(mp)){ /* TODO thread handling needs rewrite, m_collapsed must go */ if (visible(mp) && (mp->m_collapsed <= 0 || PCMP(mp, ==, message + msgspec - 1))) { if (g % size == 0) mq = mp; if (mp->m_flag & fl) { lastg = g; lastmq = mq; } if ((msgspec > 0 && PCMP(mp, ==, message + msgspec - 1)) || (msgspec == 0 && g == k) || (msgspec == -2 && g == k + size && lastmq) || (msgspec < 0 && g >= k && (mp->m_flag & fl) != 0)) break; g++; } } if (lastmq && (msgspec == -2 || (msgspec == -1 && PCMP(mp, ==, message + msgCount)))) { g = lastg; mq = lastmq; } _screen = g / size; mp = mq; mx_COLOUR( mx_colour_env_create(mx_COLOUR_CTX_SUM, n_stdout, FAL0); ) n_autorec_relax_create(); for(lastmq = NULL; mp != NULL; lastmq = mp, mp = mq){ mq = next_in_thread(mp); if (visible(mp) && (mp->m_collapsed <= 0 || PCMP(mp, ==, message + msgspec - 1))) { if (UCMP(32, flag, >=, size)) break; if(needdot){ if(showlast){ if(UCMP(32, flag, ==, size - 1) || mq == NULL) goto jdot_sort; }else if(flag == 0){ jdot_sort: needdot = FAL0; setdot(mp); } } a_chead_print_head(flag, P2UZ(mp - message + 1), n_stdout, mb.mb_threaded, TRU1); ++flag; n_autorec_relax_unroll(); } } if(needdot && ok_blook(showlast)) /* xxx will not show */ setdot(lastmq); n_autorec_relax_gut(); mx_COLOUR( mx_colour_env_gut(); ) } if (flag == 0) { fprintf(n_stdout, _("No more mail.\n")); if (n_pstate & (n_PS_ROBOT | n_PS_HOOK_MASK)) flag = !flag; } NYD_OU; return !flag; } FL int c_headers(void *v) { int rv; NYD_IN; rv = print_header_group((int*)v); NYD_OU; return rv; } FL int print_header_group(int *vector) { int rv; NYD_IN; ASSERT(vector != NULL && vector != (void*)-1); rv = _headers(vector[0]); NYD_OU; return rv; } FL int c_scroll(void *v) { int rv; NYD_IN; rv = a_chead_scroll(*(char const**)v, FAL0); NYD_OU; return rv; } FL int c_Scroll(void *v) { int rv; NYD_IN; rv = a_chead_scroll(*(char const**)v, TRU1); NYD_OU; return rv; } FL int c_dotmove(void *v) { char const *args; int msgvec[2], rv; NYD_IN; if (*(args = v) == '\0' || args[1] != '\0') { jerr: n_err(_("Synopsis: dotmove: up <-> or down <+> by one message\n")); rv = 1; } else switch (args[0]) { case '-': case '+': if (msgCount == 0) { fprintf(n_stdout, _("At EOF\n")); rv = 0; } else if (n_getmsglist(n_UNCONST(/*TODO*/args), msgvec, 0, NULL) > 0) { setdot(message + msgvec[0] - 1); msgvec[1] = 0; rv = c_headers(msgvec); } else rv = 1; break; default: goto jerr; } NYD_OU; return rv; } FL int c_from(void *vp) { int *msgvec, *ip, n; char *cp; FILE * volatile obuf; NYD_IN; if(*(msgvec = vp) == 0) goto jleave; time_current_update(&time_current, FAL0); obuf = n_stdout; if (n_psonce & n_PSO_INTERACTIVE) { if ((cp = ok_vlook(crt)) != NULL) { uz ib; for (n = 0, ip = msgvec; *ip != 0; ++ip) ++n; if(*cp == '\0') ib = n_screensize(); else su_idec_uz_cp(&ib, cp, 0, NULL); if (UCMP(z, n, >, ib) && (obuf = mx_pager_open()) == NULL) obuf = n_stdout; } } /* Update dot before display so that the dotmark etc. are correct */ for (ip = msgvec; ip[1] != 0; ++ip) ; setdot(&message[(ok_blook(showlast) ? *ip : *msgvec) - 1]); mx_COLOUR( mx_colour_env_create(mx_COLOUR_CTX_SUM, obuf, (obuf != n_stdout)); ) n_autorec_relax_create(); for (n = 0, ip = msgvec; *ip != 0; ++ip) { /* TODO join into _print_head() */ a_chead_print_head((uz)n++, S(uz,*ip), obuf, mb.mb_threaded, FAL0); n_autorec_relax_unroll(); } n_autorec_relax_gut(); mx_COLOUR( mx_colour_env_gut(); ) if (obuf != n_stdout) mx_pager_close(obuf); jleave: NYD_OU; return 0; } FL void print_headers(int const *msgvec, boole only_marked, boole subject_thread_compress) { uz printed; NYD_IN; time_current_update(&time_current, FAL0); mx_COLOUR( mx_colour_env_create(mx_COLOUR_CTX_SUM, n_stdout, FAL0); ) n_autorec_relax_create(); for(printed = 0; *msgvec != 0; ++msgvec) { struct message *mp = message + *msgvec - 1; if (only_marked) { if (!(mp->m_flag & MMARK)) continue; } else if (!visible(mp)) continue; a_chead_print_head(printed++, *msgvec, n_stdout, mb.mb_threaded, subject_thread_compress); n_autorec_relax_unroll(); } n_autorec_relax_gut(); mx_COLOUR( mx_colour_env_gut(); ) NYD_OU; } #include "su/code-ou.h" /* s-it-mode */ s-nail-14.9.15/src/mx/cmd-misc.c000066400000000000000000000403361352610246600161440ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Miscellaneous user commands, like `echo', `pwd', etc. * * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany. * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1980, 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. */ #undef su_FILE #define su_FILE cmd_misc #define mx_SOURCE #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif #include #include #include #include #include "mx/child.h" #include "mx/file-streams.h" #include "mx/sigs.h" /* TODO fake */ #include "su/code-in.h" /* Expand the shell escape by expanding unescaped !'s into the last issued * command where possible */ static char const *a_cmisc_bangexp(char const *cp); /* c_n?echo(), c_n?echoerr() */ static int a_cmisc_echo(void *vp, FILE *fp, boole donl); /* c_read() */ static boole a_cmisc_read_set(char const *cp, char const *value); /* c_version() */ static su_sz a_cmisc_version_cmp(void const *s1, void const *s2); static char const * a_cmisc_bangexp(char const *cp){ static struct str last_bang; struct n_string xbang, *bang; char c; boole changed; NYD_IN; if(!ok_blook(bang)) goto jleave; changed = FAL0; for(bang = n_string_creat(&xbang); (c = *cp++) != '\0';){ if(c == '!'){ if(last_bang.l > 0) bang = n_string_push_buf(bang, last_bang.s, last_bang.l); changed = TRU1; }else{ if(c == '\\' && *cp == '!'){ ++cp; c = '!'; changed = TRU1; } bang = n_string_push_c(bang, c); } } if(last_bang.s != NULL) n_free(last_bang.s); last_bang.s = n_string_cp(bang); last_bang.l = bang->s_len; bang = n_string_drop_ownership(bang); n_string_gut(bang); cp = last_bang.s; if(changed) fprintf(n_stdout, "!%s\n", cp); jleave: NYD_OU; return cp; } static int a_cmisc_echo(void *vp, FILE *fp, boole donl){ struct n_string s_b, *s; int rv; boole doerr; char const **argv, *varname, **ap, *cp; NYD2_IN; argv = vp; varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL; s = n_string_reserve(n_string_creat_auto(&s_b), 121/* XXX */); #ifdef mx_HAVE_ERRORS doerr = (fp == n_stderr && (n_psonce & n_PSO_INTERACTIVE)); #else doerr = FAL0; #endif for(ap = argv; *ap != NULL; ++ap){ if(ap != argv) s = n_string_push_c(s, ' '); if((cp = fexpand(*ap, FEXP_NSHORTCUT | FEXP_NVAR)) == NULL) cp = *ap; s = n_string_push_cp(s, cp); } if(donl) s = n_string_push_c(s, '\n'); cp = n_string_cp(s); if(varname == NULL){ s32 e; e = su_ERR_NONE; if(doerr){ /* xxx Ensure *log-prefix* will be placed by n_err() for next msg */ if(donl) cp = n_string_cp(n_string_trunc(s, s->s_len - 1)); n_errx(TRU1, (donl ? "%s\n" : "%s"), cp); }else if(fputs(cp, fp) == EOF) e = su_err_no(); if((rv = (fflush(fp) == EOF))) e = su_err_no(); rv |= ferror(fp) ? 1 : 0; n_pstate_err_no = e; }else if(!n_var_vset(varname, (up)cp)){ n_pstate_err_no = su_ERR_NOTSUP; rv = -1; }else{ n_pstate_err_no = su_ERR_NONE; rv = (int)s->s_len; } NYD2_OU; return rv; } static boole a_cmisc_read_set(char const *cp, char const *value){ boole rv; NYD2_IN; if(!n_shexp_is_valid_varname(cp)) value = N_("not a valid variable name"); else if(!n_var_is_user_writable(cp)) value = N_("variable is read-only"); else if(!n_var_vset(cp, (up)value)) value = N_("failed to update variable value"); else{ rv = TRU1; goto jleave; } n_err("`read': %s: %s\n", V_(value), n_shexp_quote_cp(cp, FAL0)); rv = FAL0; jleave: NYD2_OU; return rv; } static su_sz a_cmisc_version_cmp(void const *s1, void const *s2){ su_sz rv; char const *cp1, *cp2; NYD2_IN; cp1 = s1; cp2 = s2; rv = su_cs_cmp(&cp1[1], &cp2[1]); NYD2_OU; return rv; } FL int c_shell(void *v){ struct mx_child_ctx cc; sigset_t mask; int rv; FILE *fp; char const **argv, *varname, *varres; NYD_IN; n_pstate_err_no = su_ERR_NONE; argv = v; varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NIL; varres = n_empty; fp = NIL; if(varname != NIL && (fp = mx_fs_tmp_open("shell", (mx_FS_O_RDWR | mx_FS_O_UNLINK | mx_FS_O_REGISTER), NIL)) == NIL){ n_pstate_err_no = su_ERR_CANCELED; rv = -1; }else{ sigemptyset(&mask); mx_child_ctx_setup(&cc); cc.cc_flags = mx_CHILD_RUN_WAIT_LIFE; cc.cc_mask = &mask; if(fp != NIL) cc.cc_fds[mx_CHILD_FD_OUT] = fileno(fp); cc.cc_cmd = ok_vlook(SHELL); cc.cc_args[0] = "-c"; cc.cc_args[1] = a_cmisc_bangexp(*argv); if(!mx_child_run(&cc) || (rv = cc.cc_exit_status) < 0){ n_pstate_err_no = cc.cc_error; rv = -1; } } if(fp != NIL){ if(rv != -1){ int c; char *x; off_t l; fflush_rewind(fp); l = fsize(fp); if(UCMP(64, l, >=, UZ_MAX -42)){ n_pstate_err_no = su_ERR_NOMEM; varres = n_empty; }else if(l > 0){ varres = x = n_autorec_alloc(l +1); for(; l > 0 && (c = getc(fp)) != EOF; --l) *x++ = c; *x++ = '\0'; if(l != 0){ n_pstate_err_no = su_err_no(); varres = n_empty; /* xxx hmmm */ } } } mx_fs_close(fp); } if(varname != NIL){ if(!n_var_vset(varname, R(up,varres))){ n_pstate_err_no = su_ERR_NOTSUP; rv = -1; } }else if(rv >= 0 && (n_psonce & n_PSO_INTERACTIVE)){ fprintf(n_stdout, "!\n"); /* Line buffered fflush(n_stdout); */ } NYD_OU; return rv; } FL int c_dosh(void *v){ struct mx_child_ctx cc; int rv; NYD_IN; UNUSED(v); mx_child_ctx_setup(&cc); cc.cc_flags = mx_CHILD_RUN_WAIT_LIFE; cc.cc_cmd = ok_vlook(SHELL); if(mx_child_run(&cc) && (rv = cc.cc_exit_status) >= 0){ putc('\n', n_stdout); /* Line buffered fflush(n_stdout); */ n_pstate_err_no = su_ERR_NONE; }else{ n_pstate_err_no = cc.cc_error; rv = -1; } NYD_OU; return rv; } FL int c_cwd(void *v){ struct n_string s_b, *s; uz l; char const *varname; NYD_IN; s = n_string_creat_auto(&s_b); varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *(char const**)v : NULL; l = PATH_MAX; for(;; l += PATH_MAX){ s = n_string_resize(n_string_trunc(s, 0), l); if(getcwd(s->s_dat, s->s_len) == NULL){ int e; e = su_err_no(); if(e == su_ERR_RANGE) continue; n_perr(_("Failed to getcwd(3)"), e); v = NULL; break; } if(varname != NULL){ if(!n_var_vset(varname, (up)s->s_dat)) v = NULL; }else{ l = su_cs_len(s->s_dat); s = n_string_trunc(s, l); if(fwrite(s->s_dat, 1, s->s_len, n_stdout) == s->s_len && putc('\n', n_stdout) == EOF) v = NULL; } break; } NYD_OU; return (v == NULL); } FL int c_chdir(void *v) { char **arglist = v; char const *cp; NYD_IN; if (*arglist == NULL) cp = ok_vlook(HOME); else if ((cp = fexpand(*arglist, FEXP_LOCAL | FEXP_NOPROTO)) == NULL) goto jleave; if (chdir(cp) == -1) { n_perr(cp, 0); cp = NULL; } jleave: NYD_OU; return (cp == NULL); } FL int c_echo(void *v){ int rv; NYD_IN; rv = a_cmisc_echo(v, n_stdout, TRU1); NYD_OU; return rv; } FL int c_echoerr(void *v){ int rv; NYD_IN; rv = a_cmisc_echo(v, n_stderr, TRU1); NYD_OU; return rv; } FL int c_echon(void *v){ int rv; NYD_IN; rv = a_cmisc_echo(v, n_stdout, FAL0); NYD_OU; return rv; } FL int c_echoerrn(void *v){ int rv; NYD_IN; rv = a_cmisc_echo(v, n_stderr, FAL0); NYD_OU; return rv; } FL int c_read(void * volatile vp){ struct n_sigman sm; struct str trim; struct n_string s_b, *s; char *linebuf; uz linesize, i; int rv; char const *ifs, **argv, *cp; NYD2_IN; s = n_string_creat_auto(&s_b); s = n_string_reserve(s, 64 -1); ifs = ok_vlook(ifs); linesize = 0; linebuf = NULL; argv = vp; n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){ case 0: break; default: n_pstate_err_no = su_ERR_INTR; rv = -1; goto jleave; } n_pstate_err_no = su_ERR_NONE; rv = n_go_input(((n_pstate & n_PS_COMPOSE_MODE ? n_GO_INPUT_CTX_COMPOSE : n_GO_INPUT_CTX_DEFAULT) | n_GO_INPUT_FORCE_STDIN | n_GO_INPUT_NL_ESC | n_GO_INPUT_PROMPT_NONE /* XXX POSIX: PS2: yes! */), NULL, &linebuf, &linesize, NULL, NULL); if(rv < 0){ if(!n_go_input_is_eof()) n_pstate_err_no = su_ERR_BADF; goto jleave; }else if(rv == 0){ if(n_go_input_is_eof()){ rv = -1; goto jleave; } }else{ trim.s = linebuf; trim.l = rv; for(; *argv != NULL; ++argv){ if(trim.l == 0 || n_str_trim_ifs(&trim, FAL0)->l == 0) break; /* The last variable gets the remaining line less trailing IFS-WS */ if(argv[1] == NULL){ jitall: s = n_string_assign_buf(s, trim.s, trim.l); trim.l = 0; }else for(cp = trim.s, i = 1;; ++cp, ++i){ if(su_cs_find_c(ifs, *cp) != NULL){ s = n_string_assign_buf(s, trim.s, i - 1); trim.s += i; trim.l -= i; break; } if(i == trim.l) goto jitall; } if(!a_cmisc_read_set(*argv, n_string_cp(s))){ n_pstate_err_no = su_ERR_NOTSUP; rv = -1; break; } } } /* Set the remains to the empty string */ for(; *argv != NULL; ++argv) if(!a_cmisc_read_set(*argv, n_empty)){ n_pstate_err_no = su_ERR_NOTSUP; rv = -1; break; } n_sigman_cleanup_ping(&sm); jleave: if(linebuf != NULL) n_free(linebuf); NYD2_OU; n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT); return rv; } FL int c_readall(void * vp){ /* TODO 64-bit retval */ struct n_sigman sm; struct n_string s_b, *s; char *linebuf; uz linesize; int rv; char const **argv; NYD2_IN; s = n_string_creat_auto(&s_b); s = n_string_reserve(s, 64 -1); linesize = 0; linebuf = NULL; argv = vp; n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){ case 0: break; default: n_pstate_err_no = su_ERR_INTR; rv = -1; goto jleave; } n_pstate_err_no = su_ERR_NONE; for(;;){ rv = n_go_input(((n_pstate & n_PS_COMPOSE_MODE ? n_GO_INPUT_CTX_COMPOSE : n_GO_INPUT_CTX_DEFAULT) | n_GO_INPUT_FORCE_STDIN | /*n_GO_INPUT_NL_ESC |*/ n_GO_INPUT_PROMPT_NONE), NULL, &linebuf, &linesize, NULL, NULL); if(rv < 0){ if(!n_go_input_is_eof()){ n_pstate_err_no = su_ERR_BADF; goto jleave; } if(s->s_len == 0) goto jleave; break; } if(n_pstate & n_PS_READLINE_NL) linebuf[rv++] = '\n'; /* Replace NUL with it */ if(UNLIKELY(rv == 0)){ /* xxx will not get*/ if(n_go_input_is_eof()){ if(s->s_len == 0){ rv = -1; goto jleave; } break; } }else if(LIKELY(UCMP(32, S32_MAX - s->s_len, >, rv))) s = n_string_push_buf(s, linebuf, rv); else{ n_pstate_err_no = su_ERR_OVERFLOW; rv = -1; goto jleave; } } if(!a_cmisc_read_set(argv[0], n_string_cp(s))){ n_pstate_err_no = su_ERR_NOTSUP; rv = -1; goto jleave; } rv = s->s_len; n_sigman_cleanup_ping(&sm); jleave: if(linebuf != NULL) n_free(linebuf); NYD2_OU; n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT); return rv; } FL struct n_string * n_version(struct n_string *s){ NYD_IN; s = n_string_push_cp(s, n_uagent); s = n_string_push_c(s, ' '); s = n_string_push_cp(s, ok_vlook(version)); s = n_string_push_c(s, ','); s = n_string_push_c(s, ' '); s = n_string_push_cp(s, ok_vlook(version_date)); s = n_string_push_c(s, ' '); s = n_string_push_c(s, '('); s = n_string_push_cp(s, _("built for ")); s = n_string_push_cp(s, ok_vlook(build_os)); s = n_string_push_c(s, ')'); s = n_string_push_c(s, '\n'); NYD_OU; return s; } FL int c_version(void *vp){ struct utsname ut; struct n_string s_b, *s; int rv; char *iop; char const *cp, **arr; uz i, lnlen, j; NYD_IN; s = n_string_creat_auto(&s_b); s = n_string_book(s, 1024); /* First two lines */ s = n_version(s); s = n_string_push_cp(s, _("Features included (+) or not (-):\n")); /* Some lines with the features. * *features* starts with dummy byte to avoid + -> *folder* expansions */ i = su_cs_len(cp = &ok_vlook(features)[1]) +1; iop = n_autorec_alloc(i); su_mem_copy(iop, cp, i); arr = n_autorec_alloc(sizeof(cp) * VAL_FEATURES_CNT); for(i = 0; (cp = su_cs_sep_c(&iop, ',', TRU1)) != NULL; ++i) arr[i] = cp; su_sort_shell_vpp(su_S(void const**,arr), i, &a_cmisc_version_cmp); for(lnlen = 0; i-- > 0;){ cp = *(arr++); j = su_cs_len(cp); if((lnlen += j + 1) > 72){ s = n_string_push_c(s, '\n'); lnlen = j + 1; } s = n_string_push_c(s, ' '); s = n_string_push_buf(s, cp, j); } s = n_string_push_c(s, '\n'); /* */ if(n_poption & n_PO_V){ s = n_string_push_cp(s, "Compile: "); s = n_string_push_cp(s, ok_vlook(build_cc)); s = n_string_push_cp(s, "\nLink: "); s = n_string_push_cp(s, ok_vlook(build_ld)); if(*(cp = ok_vlook(build_rest)) != '\0'){ s = n_string_push_cp(s, "\nRest: "); s = n_string_push_cp(s, cp); } s = n_string_push_c(s, '\n'); /* A trailing line with info of the running machine */ uname(&ut); s = n_string_push_c(s, '@'); s = n_string_push_cp(s, ut.sysname); s = n_string_push_c(s, ' '); s = n_string_push_cp(s, ut.release); s = n_string_push_c(s, ' '); s = n_string_push_cp(s, ut.version); s = n_string_push_c(s, ' '); s = n_string_push_cp(s, ut.machine); s = n_string_push_c(s, '\n'); } /* Done */ cp = n_string_cp(s); if(n_pstate & n_PS_ARGMOD_VPUT){ if(n_var_vset(*(char const**)vp, (up)cp)) rv = 0; else rv = -1; }else{ if(fputs(cp, n_stdout) != EOF) rv = 0; else{ clearerr(n_stdout); rv = 1; } } NYD_OU; return rv; } #include "su/code-ou.h" /* s-it-mode */ s-nail-14.9.15/src/mx/cmd-mlist.c000066400000000000000000000365341352610246600163460ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Implementation of cmd-mlist.h. *@ XXX Use a su_cs_set for non-regex stuff? *@ XXX use su_list for the regex stuff? *@ TODO use su_regex (and if it's a wrapper only) *@ TODO _ML -> _CML * * Copyright (c) 2014 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #undef su_FILE #define su_FILE cmd_mlist #define mx_SOURCE #define mx_SOURCE_CMD_MLIST #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif #ifdef mx_HAVE_REGEX # include #endif #include #include #include #include "mx/names.h" #include "mx/cmd-mlist.h" #include "su/code-in.h" /* ..of a_ml_dp.. */ #define a_ML_FLAGS (su_CS_DICT_POW2_SPACED | su_CS_DICT_CASE |\ su_CS_DICT_HEAD_RESORT | su_CS_DICT_AUTO_SHRINK | su_CS_DICT_ERR_PASS) #define a_ML_TRESHOLD_SHIFT 2 /* ..of a_ml_re_dp. Only for un-registration + flag change: use a high t.-s. * Usage of NILISVALO is essential for our purpose, since we use * view_set_data() to change the subscription state, but which is supposed to * only flip the lowermost bit -- see a_ml_dp docu below. * I.e., clone() -> template is char const* == regex, assign(): template is * a_ml_regex* == self with bit 1 indicating new subscription state, delete(): * a_ml_regex* == self with bit 1 indicating subscription state. * We MUST pass non-NIL to assign()! */ #ifdef mx_HAVE_REGEX # define a_ML_RE_FLAGS (su_CS_DICT_POW2_SPACED | su_CS_DICT_OWNS |\ su_CS_DICT_AUTO_SHRINK | su_CS_DICT_ERR_PASS | su_CS_DICT_NILISVALO) # define a_ML_RE_TRESHOLD_SHIFT 4 #endif #ifdef mx_HAVE_REGEX struct a_ml_regex{ struct a_ml_regex *mlr_last; struct a_ml_regex *mlr_next; regex_t mlr_regex; }; #endif /* The value of our direct match dict is in fact a boole, the regex one * uses a_ml_regex* (still with boole as first bit, as above) */ static struct su_cs_dict *a_ml_dp, a_ml__d; /* XXX atexit _gut() (DVL()) */ #ifdef mx_HAVE_REGEX static struct su_cs_dict *a_ml_re_dp, a_ml__re_d; /* XXX atexit _gut (DVL()) */ /* Regex are searched in order, subscribed first, then "default"; make this * easier by using two dedicated lists. * We will perform automatic head resorting on these lists in the hope that * this will place often used matches nearer to the head over time */ static struct a_ml_regex *a_ml_re_def, *a_ml_re_sub; /* +toolbox below */ #endif /* */ static boole a_ml_mux(boole subscribe, char const **argv); /* */ static void a_ml_unsub_all(struct su_cs_dict_view *dvp, struct su_cs_dict *dp); /* */ static struct n_strlist *a_ml_dump(char const *cmdname, char const *key, void const *dat); /* su_toolbox for a_ml_re_dp */ #ifdef mx_HAVE_REGEX static void *a_ml_re_clone(void const *t, u32 estate); static void a_ml_re_delete(void *self); static void *a_ml_re_assign(void *self, void const *t, u32 estate); static struct su_toolbox const a_ml_re_tbox = su_TOOLBOX_I9R( &a_ml_re_clone, &a_ml_re_delete, &a_ml_re_assign, NIL, NIL ); #endif static boole a_ml_mux(boole subscribe, char const **argv){ struct su_cs_dict_view dv; boole notrv; char const *cmd, *key; NYD2_IN; cmd = subscribe ? "mlsubscribe" : "mlist"; if((key = *argv) == NIL){ struct n_strlist *slp, *stailp; stailp = slp = NIL; notrv = !(mx_xy_dump_dict(cmd, a_ml_dp, &slp, &stailp, &a_ml_dump) && #ifdef mx_HAVE_REGEX mx_xy_dump_dict(cmd, a_ml_re_dp, &slp, &stailp, &a_ml_dump) && #endif mx_page_or_print_strlist(cmd, slp)); }else{ if(a_ml_dp == NIL){ a_ml_dp = su_cs_dict_set_treshold_shift( su_cs_dict_create(&a_ml__d, a_ML_FLAGS, NIL), a_ML_TRESHOLD_SHIFT); #ifdef mx_HAVE_REGEX a_ml_re_dp = su_cs_dict_set_treshold_shift( su_cs_dict_create(&a_ml__re_d, a_ML_RE_FLAGS, &a_ml_re_tbox), a_ML_RE_TRESHOLD_SHIFT); #endif } notrv = FAL0; do{ /* while((key = *++argv) != NIL); */ union {void const *cvp; void *vp; up flags;} u; /* Does this already exist, somewhere? */ if(su_cs_dict_view_find(su_cs_dict_view_setup(&dv, a_ml_dp), key) #ifdef mx_HAVE_REGEX || su_cs_dict_view_find(su_cs_dict_view_setup(&dv, a_ml_re_dp ), key) #endif ){ u.cvp = su_cs_dict_view_data(&dv); if(u.flags & TRU1){ if(subscribe) goto jelisted; u.flags &= ~TRU1; goto jset_data; }else if(!subscribe){ jelisted: n_err(_("`%s': already listed: %s\n"), cmd, n_shexp_quote_cp(key, FAL0)); notrv = TRU1; }else{ u.flags |= TRU1; jset_data: notrv |= (su_cs_dict_view_set_data(&dv, u.vp) > 0); } }else{ struct su_cs_dict *dp; /* A new entry */ ASSERT((subscribe & ~TRU1) == 0); u.flags = subscribe; #ifdef mx_HAVE_REGEX if(n_is_maybe_regex(key)){ /* XXX Since the key is char* it could reside on an address * XXX with bit 1 set, but since it is user input it came in * XXX via shell argument quoting, is thus served by our memory, * XXX and should thus be aligned properly */ ASSERT((R(up,key) & 1) == 0); u.flags |= R(up,key); dp = a_ml_re_dp; }else #endif dp = a_ml_dp; if(su_cs_dict_insert(dp, key, u.vp) > 0){ n_err(_("`%s': failed to create storage: %s\n"), n_shexp_quote_cp(key, FAL0)); notrv = 1; } } }while((key = *++argv) != NIL); } NYD2_OU; return !notrv; } static void a_ml_unsub_all(struct su_cs_dict_view *dvp, struct su_cs_dict *dp){ union {void *vp; up flags;} u; NYD2_IN; for(su_cs_dict_view_setup(dvp, dp); su_cs_dict_view_is_valid(dvp); su_cs_dict_view_next(dvp)){ u.vp = su_cs_dict_view_data(dvp); if(u.flags & TRU1){ u.flags ^= TRU1; su_cs_dict_view_set_data(dvp, u.vp); } } NYD2_OU; } static struct n_strlist * a_ml_dump(char const *cmdname, char const *key, void const *dat){ /* XXX real strlist + str_to_fmt() */ char *cp; union {void const *cvp; up flags;} u; struct n_strlist *slp; uz typel, kl, cl; char const *typep, *kp; NYD2_IN; typep = su_empty; typel = 0; slp = NIL; u.cvp = dat; if(u.flags & TRU1){ u.flags &= ~TRU1; if(!su_cs_cmp(cmdname, "mlist")) goto jleave; #ifdef mx_HAVE_REGEX if(u.cvp != NIL && (n_poption & n_PO_D_V)){ typep = " # regex(7)"; typel = sizeof(" # regex(7)") -1; } #endif }else if(!su_cs_cmp(cmdname, "mlsubscribe")) goto jleave; kp = n_shexp_quote_cp(key, TRU1); kl = su_cs_len(kp); cl = su_cs_len(cmdname); slp = n_STRLIST_AUTO_ALLOC(cl + 1 + kl + 1 + typel +1); slp->sl_next = NIL; cp = slp->sl_dat; su_mem_copy(cp, cmdname, cl); cp += cl; *cp++ = ' '; su_mem_copy(cp, kp, kl); cp += kl; if(typel > 0){ *cp++ = ' '; su_mem_copy(cp, typep, typel); cp += typel; } *cp = '\0'; slp->sl_len = P2UZ(cp - slp->sl_dat); jleave: NYD2_OU; return slp; } #ifdef mx_HAVE_REGEX static void * a_ml_re_clone(void const *t, u32 estate){ struct a_ml_regex **mlrpp; int s; char const *rep; union {void const *cvp; up flags; char const *ccp;} u; union {struct a_ml_regex *mlrp; up flags; void *vp;} rv; NYD_IN; if((rv.mlrp = su_TALLOCF(struct a_ml_regex, 1, estate)) != NIL){ u.cvp = t; u.flags &= ~TRU1; rep = u.ccp; u.cvp = t; if((s = regcomp(&rv.mlrp->mlr_regex, rep, REG_EXTENDED | REG_ICASE | REG_NOSUB)) == 0){ rv.mlrp->mlr_last = NIL; mlrpp = (u.flags & TRU1) ? &a_ml_re_sub : &a_ml_re_def; if((rv.mlrp->mlr_next = *mlrpp) != NIL) rv.mlrp->mlr_next->mlr_last = rv.mlrp; *mlrpp = rv.mlrp; rv.flags |= (u.flags & TRU1); }else{ n_err(_("`%s': invalid regular expression: %s: %s\n"), (u.flags & TRU1 ? "mlsubscribe" : "mlist"), n_shexp_quote_cp(rep, FAL0), n_regex_err_to_doc(NULL, s)); su_FREE(rv.mlrp); su_err_set_no(su_ERR_INVAL); rv.vp = NIL; } } NYD_OU; return rv.vp; } static void a_ml_re_delete(void *self){ struct a_ml_regex **lpp, *lstnp, *nxtnp; union {void *vp; up flags; struct a_ml_regex *mlrp;} u; NYD_IN; u.vp = self; if(u.flags & TRU1){ u.flags &= ~TRU1; lpp = &a_ml_re_sub; }else lpp = &a_ml_re_def; lstnp = u.mlrp->mlr_last; nxtnp = u.mlrp->mlr_next; if(u.mlrp == *lpp) *lpp = nxtnp; else lstnp->mlr_next = nxtnp; if(nxtnp != NIL) nxtnp->mlr_last = lstnp; regfree(&u.mlrp->mlr_regex); su_FREE(u.mlrp); NYD_OU; } static void * a_ml_re_assign(void *self, void const *t, u32 estate){ /* Thanks to NILISVALO we can (mis)use assignment for the sole purpose of * flipping the subscription bit and performing list relinking! */ struct a_ml_regex **lpp, *lstnp, *nxtnp; union {void *vp; void const *cvp; up flags; struct a_ml_regex *mlrp;} u; NYD_IN; UNUSED(t); UNUSED(estate); /* Out old */ u.vp = self; if(u.flags & TRU1){ u.flags &= ~TRU1; self = u.vp; lpp = &a_ml_re_sub; }else lpp = &a_ml_re_def; lstnp = u.mlrp->mlr_last; nxtnp = u.mlrp->mlr_next; if(u.mlrp == *lpp) *lpp = nxtnp; else lstnp->mlr_next = nxtnp; if(nxtnp != NIL) nxtnp->mlr_last = lstnp; /* In new */ u.cvp = t; if(u.flags & TRU1){ u.vp = self; u.flags |= TRU1; self = u.vp; u.flags ^= TRU1; lpp = &a_ml_re_sub; }else{ u.vp = self; ASSERT((u.flags & TRU1) == 0); lpp = &a_ml_re_def; } u.mlrp->mlr_last = NIL; if((u.mlrp->mlr_next = nxtnp = *lpp) != NIL) nxtnp->mlr_last = u.mlrp; *lpp = u.mlrp; NYD_OU; return self; } #endif /* mx_HAVE_REGEX */ int c_mlist(void *vp){ int rv; NYD_IN; rv = !a_ml_mux(FAL0, vp); NYD_OU; return rv; } int c_unmlist(void *vp){ char const **argv, *key; int rv; NYD_IN; rv = 0; for(argv = S(char const**,vp); (key = *argv) != NIL; ++argv){ if(key[1] == '\0' && key[0] == '*'){ if(a_ml_dp != NIL) su_cs_dict_clear(a_ml_dp); #ifdef mx_HAVE_REGEX if(a_ml_re_dp != NIL) su_cs_dict_clear(a_ml_re_dp); #endif }else if(a_ml_dp != NIL && su_cs_dict_remove(a_ml_dp, key)) ; #ifdef mx_HAVE_REGEX else if(a_ml_re_dp != NIL && su_cs_dict_remove(a_ml_re_dp, key)) ; #endif else{ n_err(_("No such `mlist': %s\n"), n_shexp_quote_cp(key, FAL0)); rv = 1; } } NYD_OU; return rv; } int c_mlsubscribe(void *vp){ int rv; NYD_IN; rv = !a_ml_mux(TRU1, vp); NYD_OU; return rv; } int c_unmlsubscribe(void *vp){ struct su_cs_dict_view dv; union {void *vp; up flags;} u; char const **argv, *key; int rv; NYD_IN; rv = 0; for(argv = S(char const**,vp); (key = *argv) != NIL; ++argv){ if(key[1] == '\0' && key[0] == '*'){ if(a_ml_dp != NIL) a_ml_unsub_all(&dv, a_ml_dp); #ifdef mx_HAVE_REGEX if(a_ml_re_dp != NIL) a_ml_unsub_all(&dv, a_ml_re_dp); #endif }else if(a_ml_dp != NIL && su_cs_dict_view_find(su_cs_dict_view_setup(&dv, a_ml_dp), key)){ goto jtest; jtest: u.vp = su_cs_dict_view_data(&dv); if(u.flags & TRU1){ u.flags ^= TRU1; su_cs_dict_view_set_data(&dv, u.vp); }else goto jenot; #ifdef mx_HAVE_REGEX }else if(a_ml_re_dp != NIL && su_cs_dict_view_find(su_cs_dict_view_setup(&dv, a_ml_re_dp), key)){ goto jtest; #endif }else{ jenot: n_err(_("No such `mlsubscribe': %s\n"), n_shexp_quote_cp(key, FAL0)); rv = 1; } } NYD_OU; return rv; } enum mx_mlist_type mx_mlist_query(char const *name, boole subscribed_only){ struct su_cs_dict_view dv; union {void *vp; void const *cvp; up flags;} u; enum mx_mlist_type rv; NYD_IN; rv = mx_MLIST_OTHER; /* Direct address match? */ if(a_ml_dp != NIL && su_cs_dict_view_find(su_cs_dict_view_setup(&dv, a_ml_dp), name)){ u.cvp = su_cs_dict_view_data(&dv); if(u.flags & TRU1) rv = mx_MLIST_SUBSCRIBED; else if(!subscribed_only) rv = mx_MLIST_KNOWN; } /* With regex support, walk subscribed and then normal list thereafter */ #ifdef mx_HAVE_REGEX else{ struct a_ml_regex **lpp, *mlrp, *lstnp, *nxtnp; lpp = &a_ml_re_sub; rv = mx_MLIST_SUBSCRIBED; jregex_redo: if((mlrp = *lpp) != NIL){ do if(regexec(&mlrp->mlr_regex, name, 0,NULL, 0) != REG_NOMATCH){ /* Relink head */ if(mlrp != *lpp){ lstnp = mlrp->mlr_last; nxtnp = mlrp->mlr_next; if((lstnp->mlr_next = nxtnp) != NIL) nxtnp->mlr_last = lstnp; mlrp->mlr_last = NIL; (mlrp->mlr_next = *lpp)->mlr_last = mlrp; *lpp = mlrp; } goto jregex_leave; }while((mlrp = mlrp->mlr_next) != NIL); } if(rv == mx_MLIST_SUBSCRIBED && !subscribed_only){ rv = mx_MLIST_KNOWN; lpp = &a_ml_re_def; goto jregex_redo; } rv = mx_MLIST_OTHER; jregex_leave:; } #endif /* mx_HAVE_REGEX */ NYD_OU; return rv; } enum mx_mlist_type mx_mlist_query_mp(struct message *mp, enum mx_mlist_type what){ /* XXX mlist_query_mp() possibly belongs to message or header instead */ struct mx_name *np; boole cc; enum mx_mlist_type rv; NYD_IN; rv = mx_MLIST_OTHER; cc = FAL0; np = lextract(hfield1("to", mp), GTO | GSKIN); jredo: for(; np != NIL; np = np->n_flink){ switch(mx_mlist_query(np->n_name, FAL0)){ case mx_MLIST_OTHER: break; case mx_MLIST_KNOWN: if(what == mx_MLIST_KNOWN || what == mx_MLIST_OTHER){ if(rv == mx_MLIST_OTHER) rv = mx_MLIST_KNOWN; if(what == mx_MLIST_KNOWN) goto jleave; } break; case mx_MLIST_SUBSCRIBED: if(what == mx_MLIST_SUBSCRIBED || what == mx_MLIST_OTHER){ if(rv != mx_MLIST_SUBSCRIBED) rv = mx_MLIST_SUBSCRIBED; goto jleave; } break; } } if(!cc){ cc = TRU1; np = lextract(hfield1("cc", mp), GCC | GSKIN); goto jredo; } jleave: NYD_OU; return rv; } #include "su/code-ou.h" /* s-it-mode */ s-nail-14.9.15/src/mx/cmd-msg.c000066400000000000000000000566021352610246600160020ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Iterating over, and over such housekeeping message user commands. * * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany. * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1980, 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. */ #undef su_FILE #define su_FILE cmd_msg #define mx_SOURCE #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif #include #include #include "mx/colour.h" #include "mx/file-streams.h" #include "mx/termios.h" /* TODO fake */ #include "su/code-in.h" /* Prepare and print "[Message: xy]:" intro */ static boole a_cmsg_show_overview(FILE *obuf, struct message *mp, int msg_no); /* Show the requested messages */ static int _type1(int *msgvec, boole doign, boole dopage, boole dopipe, boole donotdecode, char *cmd, u64 *tstats); /* Pipe the requested messages */ static int a_cmsg_pipe1(void *vp, boole doign); /* `top' / `Top' */ static int a_cmsg_top(void *vp, struct n_ignore const *itp); /* Delete the indicated messages. Set dot to some nice place afterwards */ static int delm(int *msgvec); static boole a_cmsg_show_overview(FILE *obuf, struct message *mp, int msg_no){ boole rv; char const *cpre, *csuf; NYD2_IN; cpre = csuf = n_empty; #ifdef mx_HAVE_COLOUR if(mx_COLOUR_IS_ACTIVE()){ struct mx_colour_pen *cpen; if((cpen = mx_colour_pen_create(mx_COLOUR_ID_VIEW_MSGINFO, NULL) ) != NIL){ struct str const *s; if((s = mx_colour_pen_to_str(cpen)) != NIL) cpre = s->s; if((s = mx_colour_reset_to_str()) != NIL) csuf = s->s; } } #endif /* XXX Message info uses wire format for line count */ rv = (fprintf(obuf, A_("%s[-- Message %2d -- %lu lines, %lu bytes --]:%s\n"), cpre, msg_no, (ul)mp->m_lines, (ul)mp->m_size, csuf) > 0); NYD2_OU; return rv; } static int _type1(int *msgvec, boole doign, boole dopage, boole dopipe, boole donotdecode, char *cmd, u64 *tstats) { u64 mstats[1]; int *ip; struct message *mp; char const *cp; enum sendaction action; boole volatile formfeed; FILE * volatile obuf; int volatile rv; NYD_IN; rv = 1; obuf = n_stdout; formfeed = (dopipe && ok_blook(page)); action = ((dopipe && ok_blook(piperaw)) ? SEND_MBOX : donotdecode ? SEND_SHOW : doign ? SEND_TODISP : SEND_TODISP_ALL); if(dopipe){ if((obuf = mx_fs_pipe_open(cmd, "w", ok_vlook(SHELL), NIL, -1)) == NIL){ n_perr(cmd, 0); obuf = n_stdout; } } else if ((n_psonce & n_PSO_TTYOUT) && (dopage || ((n_psonce & n_PSO_INTERACTIVE) && (cp = ok_vlook(crt)) != NULL))) { uz nlines, lib; nlines = 0; if (!dopage) { for (ip = msgvec; *ip && PCMP(ip - msgvec, <, msgCount); ++ip) { mp = message + *ip - 1; if (!(mp->m_content_info & CI_HAVE_BODY)) if (get_body(mp) != OKAY) goto jleave; nlines += mp->m_lines + 1; /* TODO BUT wire format, not display! */ } } /* >= not <: we return to the prompt */ if(dopage || nlines >= (*cp != '\0' ? (su_idec_uz_cp(&lib, cp, 0, NULL), lib) : S(uz,mx_termios_dimen.tiosd_real_height))){ if((obuf = mx_pager_open()) == NULL) obuf = n_stdout; } mx_COLOUR( if(action == SEND_TODISP || action == SEND_TODISP_ALL) mx_colour_env_create(mx_COLOUR_CTX_VIEW, obuf, obuf != n_stdout); ) } mx_COLOUR( else if(action == SEND_TODISP || action == SEND_TODISP_ALL) mx_colour_env_create(mx_COLOUR_CTX_VIEW, n_stdout, FAL0); ) rv = 0; n_autorec_relax_create(); for (ip = msgvec; *ip && PCMP(ip - msgvec, <, msgCount); ++ip) { mp = message + *ip - 1; touch(mp); setdot(mp); n_pstate |= n_PS_DID_PRINT_DOT; uncollapse1(mp, 1); if(!dopipe && ip != msgvec && fprintf(obuf, "\n") < 0){ rv = 1; break; } if(action != SEND_MBOX && !a_cmsg_show_overview(obuf, mp, *ip)){ rv = 1; break; } if(sendmp(mp, obuf, (doign ? n_IGNORE_TYPE : NULL), NULL, action, mstats ) < 0){ rv = 1; break; } n_autorec_relax_unroll(); if(formfeed){ /* TODO a nicer way to separate piped messages! */ if(putc('\f', obuf) == EOF){ rv = 1; break; } } if (tstats != NULL) tstats[0] += mstats[0]; } n_autorec_relax_gut(); mx_COLOUR( if(!dopipe && (action == SEND_TODISP || action == SEND_TODISP_ALL)) mx_colour_env_gut(); ) jleave: if (obuf != n_stdout) mx_pager_close(obuf); NYD_OU; return rv; } static int a_cmsg_pipe1(void *vp, boole doign){ u64 stats[1]; char const *cmd, *cmdq; int *msgvec, rv; struct n_cmd_arg *cap; struct n_cmd_arg_ctx *cacp; NYD2_IN; cacp = vp; cap = cacp->cac_arg; msgvec = cap->ca_arg.ca_msglist; cap = cap->ca_next; rv = 1; if((cmd = cap->ca_arg.ca_str.s)[0] == '\0' && ((cmd = ok_vlook(cmd)) == NULL || *cmd == '\0')){ n_err(_("%s: variable *cmd* not set\n"), cacp->cac_desc->cad_name); goto jleave; } cmdq = n_shexp_quote_cp(cmd, FAL0); fprintf(n_stdout, _("Pipe to: %s\n"), cmdq); stats[0] = 0; if((rv = _type1(msgvec, doign, FAL0, TRU1, FAL0, n_UNCONST(cmd), stats) ) == 0) fprintf(n_stdout, "%s %" PRIu64 " bytes\n", cmdq, stats[0]); jleave: NYD2_OU; return rv; } static int a_cmsg_top(void *vp, struct n_ignore const *itp){ struct n_string s; int *msgvec, *ip; enum{a_NONE, a_SQUEEZE = 1u<<0, a_EMPTY = 1u<<8, a_STOP = 1u<<9, a_WORKMASK = 0xFF00u} f; uz tmax, plines; FILE *iobuf, *pbuf; NYD2_IN; if((iobuf = mx_fs_tmp_open("topio", (mx_FS_O_RDWR | mx_FS_O_UNLINK | mx_FS_O_REGISTER), NIL)) == NIL){ n_perr(_("`top': I/O temporary file"), 0); vp = NIL; goto jleave; } if((pbuf = mx_fs_tmp_open("toppag", (mx_FS_O_RDWR | mx_FS_O_UNLINK | mx_FS_O_REGISTER), NIL)) == NIL){ n_perr(_("`top': temporary pager file"), 0); vp = NIL; goto jleave1; } /* TODO In v15 we should query the m_message object, and directly send only * TODO those parts, optionally over empty-line-squeeze and quote-strip * TODO filters, in which we are interested in: only text content! * TODO And: with *topsqueeze*, header/content separating empty line.. */ n_pstate &= ~n_PS_MSGLIST_DIRECT; /* TODO NO ATTACHMENTS */ plines = 0; mx_COLOUR( mx_colour_env_create(mx_COLOUR_CTX_VIEW, iobuf, FAL0); ) n_string_creat_auto(&s); /* C99 */{ sz l; if((su_idec_sz_cp(&l, ok_vlook(toplines), 0, NULL ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED) ) != su_IDEC_STATE_CONSUMED) l = 0; if(l <= 0){ tmax = n_screensize(); if(l < 0){ l = ABS(l); tmax >>= l; } }else tmax = (uz)l; } f = ok_blook(topsqueeze) ? a_SQUEEZE : a_NONE; for(ip = msgvec = vp; *ip != 0; ++ip){ struct message *mp; mp = &message[*ip - 1]; touch(mp); setdot(mp); n_pstate |= n_PS_DID_PRINT_DOT; uncollapse1(mp, 1); rewind(iobuf); if(ftruncate(fileno(iobuf), 0)){ n_perr(_("`top': ftruncate(2)"), 0); vp = NULL; break; } if(!a_cmsg_show_overview(iobuf, mp, *ip) || sendmp(mp, iobuf, itp, NULL, SEND_TODISP_ALL, NULL) < 0){ n_err(_("`top': failed to prepare message %d\n"), *ip); vp = NULL; break; } fflush_rewind(iobuf); /* TODO Skip over the _msg_overview line -- this is a hack to make * TODO colours work: colour contexts should be objects */ for(;;){ int c; if((c = getc(iobuf)) == EOF || putc(c, pbuf) == EOF){ vp = NULL; break; }else if(c == '\n') break; } if(vp == NULL) break; ++plines; /* C99 */{ uz l; n_string_trunc(&s, 0); for(l = 0, f &= ~a_WORKMASK; !(f & a_STOP);){ int c; if((c = getc(iobuf)) == EOF){ f |= a_STOP; c = '\n'; } if(c != '\n') n_string_push_c(&s, c); else if((f & a_SQUEEZE) && s.s_len == 0){ if(!(f & a_STOP) && ((f & a_EMPTY) || tmax - 1 <= l)) continue; if(putc('\n', pbuf) == EOF){ vp = NULL; break; } f |= a_EMPTY; ++l; }else{ char const *cp, *xcp; cp = n_string_cp_const(&s); /* TODO Brute simple skip part overviews; see above.. */ if(!(f & a_SQUEEZE)) c = '\1'; else if(s.s_len > 8 && (xcp = su_cs_find(cp, "[-- ")) != NULL && su_cs_find(&xcp[1], " --]") != NULL) c = '\0'; else{ char const *qcp; for(qcp = ok_vlook(quote_chars); (c = *cp) != '\0'; ++cp){ if(!su_cs_is_ascii(c)) break; if(!su_cs_is_space(c)){ if(su_cs_find_c(qcp, c) == NULL) break; c = '\0'; break; } } } if(c != '\0'){ if(fputs(n_string_cp_const(&s), pbuf) == EOF || putc('\n', pbuf) == EOF){ vp = NULL; break; } if(++l >= tmax) break; f &= ~a_EMPTY; }else f |= a_EMPTY; n_string_trunc(&s, 0); } } if(vp == NULL) break; if(l > 0) plines += l; else{ if(!(f & a_EMPTY) && putc('\n', pbuf) == EOF){ vp = NULL; break; } ++plines; } } } n_string_gut(&s); mx_COLOUR( mx_colour_env_gut(); ) fflush(pbuf); page_or_print(pbuf, plines); mx_fs_close(pbuf); jleave1: mx_fs_close(iobuf); jleave: NYD2_OU; return (vp != NULL); } static int delm(int *msgvec) { struct message *mp; int rv = -1, *ip, last; NYD_IN; last = 0; for (ip = msgvec; *ip != 0; ++ip) { mp = message + *ip - 1; touch(mp); mp->m_flag |= MDELETED | MTOUCH; mp->m_flag &= ~(MPRESERVE | MSAVED | MBOX); last = *ip; } if (last != 0) { setdot(message + last - 1); last = first(0, MDELETED); if (last != 0) { setdot(message + last - 1); rv = 0; } else { setdot(message); } } NYD_OU; return rv; } FL int c_more(void *v) { int *msgvec = v, rv; NYD_IN; rv = _type1(msgvec, TRU1, TRU1, FAL0, FAL0, NULL, NULL); NYD_OU; return rv; } FL int c_More(void *v) { int *msgvec = v, rv; NYD_IN; rv = _type1(msgvec, FAL0, TRU1, FAL0, FAL0, NULL, NULL); NYD_OU; return rv; } FL int c_type(void *v) { int *msgvec = v, rv; NYD_IN; rv = _type1(msgvec, TRU1, FAL0, FAL0, FAL0, NULL, NULL); NYD_OU; return rv; } FL int c_Type(void *v) { int *msgvec = v, rv; NYD_IN; rv = _type1(msgvec, FAL0, FAL0, FAL0, FAL0, NULL, NULL); NYD_OU; return rv; } FL int c_show(void *v) { int *msgvec = v, rv; NYD_IN; rv = _type1(msgvec, FAL0, FAL0, FAL0, TRU1, NULL, NULL); NYD_OU; return rv; } FL int c_mimeview(void *vp){ /* TODO direct addressable parts, multiple such */ struct message *mp; int rv, *msgvec; NYD_IN; if((msgvec = vp)[1] != 0){ n_err(_("`mimeview': can yet only take one message, sorry!\n"));/* TODO */ n_pstate_err_no = su_ERR_NOTSUP; rv = 1; goto jleave; } mp = &message[*msgvec - 1]; touch(mp); setdot(mp); n_pstate |= n_PS_DID_PRINT_DOT; uncollapse1(mp, 1); mx_COLOUR( mx_colour_env_create(mx_COLOUR_CTX_VIEW, n_stdout, FAL0); ) if(!a_cmsg_show_overview(n_stdout, mp, *msgvec)) n_pstate_err_no = su_ERR_IO; else if(sendmp(mp, n_stdout, n_IGNORE_TYPE, NULL, SEND_TODISP_PARTS, NULL) < 0) n_pstate_err_no = su_ERR_IO; else n_pstate_err_no = su_ERR_NONE; mx_COLOUR( mx_colour_env_gut(); ) rv = (n_pstate_err_no != su_ERR_NONE); jleave: NYD_OU; return rv; } FL int c_pipe(void *vp){ int rv; NYD_IN; rv = a_cmsg_pipe1(vp, TRU1); NYD_OU; return rv; } FL int c_Pipe(void *vp){ int rv; NYD_IN; rv = a_cmsg_pipe1(vp, FAL0); NYD_OU; return rv; } FL int c_top(void *v){ struct n_ignore *itp; int rv; NYD_IN; if(n_ignore_is_any(n_IGNORE_TOP)) itp = n_IGNORE_TOP; else{ itp = n_ignore_new(TRU1); n_ignore_insert(itp, TRU1, "from", sizeof("from") -1); n_ignore_insert(itp, TRU1, "to", sizeof("to") -1); n_ignore_insert(itp, TRU1, "cc", sizeof("cc") -1); n_ignore_insert(itp, TRU1, "subject", sizeof("subject") -1); } rv = !a_cmsg_top(v, itp); NYD_OU; return rv; } FL int c_Top(void *v){ int rv; NYD_IN; rv = !a_cmsg_top(v, n_IGNORE_TYPE); NYD_OU; return rv; } FL int c_next(void *v) { int list[2], *ip, *ip2, mdot, *msgvec = v, rv = 1; struct message *mp; NYD_IN; if (*msgvec != 0) { /* If some messages were supplied, find the first applicable one * following dot using wrap around */ mdot = (int)P2UZ(dot - message + 1); /* Find first message in supplied message list which follows dot */ for (ip = msgvec; *ip != 0; ++ip) { if ((mb.mb_threaded ? message[*ip - 1].m_threadpos > dot->m_threadpos : *ip > mdot)) break; } if (*ip == 0) ip = msgvec; ip2 = ip; do { mp = message + *ip2 - 1; if (!(mp->m_flag & MMNDEL)) { setdot(mp); goto jhitit; } if (*ip2 != 0) ++ip2; if (*ip2 == 0) ip2 = msgvec; } while (ip2 != ip); fprintf(n_stdout, _("No messages applicable\n")); goto jleave; } /* If this is the first command, select message 1. Note that this must * exist for us to get here at all */ if (!(n_pstate & n_PS_SAW_COMMAND)) { if (msgCount == 0) goto jateof; goto jhitit; } /* Just find the next good message after dot, no wraparound */ if (mb.mb_threaded == 0) { for (mp = dot + !!(n_pstate & n_PS_DID_PRINT_DOT); PCMP(mp, <, message + msgCount); ++mp) if (!(mp->m_flag & MMNORM)) break; } else { /* TODO The threading code had some bugs that caused crashes. * TODO The last thing (before the deep look) happens here, * TODO so let's not trust n_PS_DID_PRINT_DOT but check & hope it fixes */ if ((mp = dot) != NULL && (n_pstate & n_PS_DID_PRINT_DOT)) mp = next_in_thread(mp); while (mp != NULL && (mp->m_flag & MMNORM)) mp = next_in_thread(mp); } if (mp == NULL || PCMP(mp, >=, message + msgCount)) { jateof: fprintf(n_stdout, _("At EOF\n")); rv = 0; goto jleave; } setdot(mp); /* Print dot */ jhitit: list[0] = (int)P2UZ(dot - message + 1); list[1] = 0; rv = c_type(list); jleave: NYD_OU; return rv; } FL int c_pdot(void *vp){ char cbuf[su_IENC_BUFFER_SIZE], sep1, sep2; struct n_string s_b, *s; int *mlp; struct n_cmd_arg_ctx *cacp; NYD_IN; UNUSED(vp); n_pstate_err_no = su_ERR_NONE; s = n_string_creat_auto(&s_b); sep1 = *ok_vlook(ifs); sep2 = *ok_vlook(ifs_ws); if(sep1 == sep2) sep2 = '\0'; if(sep1 == '\0') sep1 = ' '; cacp = vp; for(mlp = cacp->cac_arg->ca_arg.ca_msglist; *mlp != 0; ++mlp){ if(!n_string_can_book(s, su_IENC_BUFFER_SIZE + 2u)){ n_err(_("`=': overflow: string too long!\n")); n_pstate_err_no = su_ERR_OVERFLOW; vp = NULL; goto jleave; } if(s->s_len > 0){ s = n_string_push_c(s, sep1); if(sep2 != '\0') s = n_string_push_c(s, sep2); } s = n_string_push_cp(s, su_ienc(cbuf, (u32)*mlp, 10, su_IENC_MODE_NONE)); } (void)n_string_cp(s); if(cacp->cac_vput == NULL){ if(fprintf(n_stdout, "%s\n", s->s_dat) < 0){ n_pstate_err_no = su_err_no(); vp = NULL; } }else if(!n_var_vset(cacp->cac_vput, (up)s->s_dat)){ n_pstate_err_no = su_ERR_NOTSUP; vp = NULL; } jleave: /* n_string_gut(s); */ NYD_OU; return (vp == NULL); } FL int c_messize(void *v) { int *msgvec = v, *ip, mesg; struct message *mp; NYD_IN; for (ip = msgvec; *ip != 0; ++ip) { mesg = *ip; mp = message + mesg - 1; fprintf(n_stdout, "%d: ", mesg); if (mp->m_xlines > 0) fprintf(n_stdout, "%ld", mp->m_xlines); else putc(' ', n_stdout); fprintf(n_stdout, "/%lu\n", (ul)mp->m_xsize); } NYD_OU; return 0; } FL int c_delete(void *v) { int *msgvec = v; NYD_IN; delm(msgvec); NYD_OU; return 0; } FL int c_deltype(void *v) { int list[2], rv = 0, *msgvec = v, lastdot; NYD_IN; lastdot = (int)P2UZ(dot - message + 1); if (delm(msgvec) >= 0) { list[0] = (int)P2UZ(dot - message + 1); if (list[0] > lastdot) { touch(dot); list[1] = 0; rv = c_type(list); goto jleave; } fprintf(n_stdout, _("At EOF\n")); } else fprintf(n_stdout, _("No more messages\n")); jleave: NYD_OU; return rv; } FL int c_undelete(void *v) { int *msgvec = v, *ip; struct message *mp; NYD_IN; for (ip = msgvec; *ip != 0; ++ip) { mp = &message[*ip - 1]; touch(mp); setdot(mp); if (mp->m_flag & (MDELETED | MSAVED)) mp->m_flag &= ~(MDELETED | MSAVED); else mp->m_flag &= ~MDELETED; #ifdef mx_HAVE_IMAP if (mb.mb_type == MB_IMAP || mb.mb_type == MB_CACHE) imap_undelete(mp, *ip); #endif } NYD_OU; return 0; } FL int c_stouch(void *v) { int *msgvec = v, *ip; NYD_IN; for (ip = msgvec; *ip != 0; ++ip) { setdot(message + *ip - 1); dot->m_flag |= MTOUCH; dot->m_flag &= ~MPRESERVE; n_pstate |= n_PS_DID_PRINT_DOT; } NYD_OU; return 0; } FL int c_mboxit(void *v) { int *msgvec = v, *ip; NYD_IN; if (n_pstate & n_PS_EDIT) { n_err(_("`mbox' can only be used in a system mailbox\n")); /* TODO */ goto jleave; } for (ip = msgvec; *ip != 0; ++ip) { setdot(message + *ip - 1); dot->m_flag |= MTOUCH | MBOX; dot->m_flag &= ~MPRESERVE; n_pstate |= n_PS_DID_PRINT_DOT; } jleave: NYD_OU; return 0; } FL int c_preserve(void *v) { int *msgvec = v, *ip, mesg, rv = 1; struct message *mp; NYD_IN; if (n_pstate & n_PS_EDIT) { fprintf(n_stdout, _("Cannot `preserve' in a system mailbox\n")); goto jleave; } for (ip = msgvec; *ip != 0; ++ip) { mesg = *ip; mp = message + mesg - 1; mp->m_flag |= MPRESERVE; mp->m_flag &= ~MBOX; setdot(mp); n_pstate |= n_PS_DID_PRINT_DOT; } rv = 0; jleave: NYD_OU; return rv; } FL int c_unread(void *v) { struct message *mp; int *msgvec = v, *ip; NYD_IN; for (ip = msgvec; *ip != 0; ++ip) { mp = &message[*ip - 1]; setdot(mp); dot->m_flag &= ~(MREAD | MTOUCH); dot->m_flag |= MSTATUS; #ifdef mx_HAVE_IMAP if (mb.mb_type == MB_IMAP || mb.mb_type == MB_CACHE) imap_unread(mp, *ip); /* TODO return? */ #endif n_pstate |= n_PS_DID_PRINT_DOT; } NYD_OU; return 0; } FL int c_seen(void *v) { int *msgvec = v, *ip; NYD_IN; for (ip = msgvec; *ip != 0; ++ip) { struct message *mp = message + *ip - 1; setdot(mp); touch(mp); } NYD_OU; return 0; } FL int c_flag(void *v) { struct message *m; int *msgvec = v, *ip; NYD_IN; for (ip = msgvec; *ip != 0; ++ip) { m = message + *ip - 1; setdot(m); if (!(m->m_flag & (MFLAG | MFLAGGED))) m->m_flag |= MFLAG | MFLAGGED; } NYD_OU; return 0; } FL int c_unflag(void *v) { struct message *m; int *msgvec = v, *ip; NYD_IN; for (ip = msgvec; *ip != 0; ++ip) { m = message + *ip - 1; setdot(m); if (m->m_flag & (MFLAG | MFLAGGED)) { m->m_flag &= ~(MFLAG | MFLAGGED); m->m_flag |= MUNFLAG; } } NYD_OU; return 0; } FL int c_answered(void *v) { struct message *m; int *msgvec = v, *ip; NYD_IN; for (ip = msgvec; *ip != 0; ++ip) { m = message + *ip - 1; setdot(m); if (!(m->m_flag & (MANSWER | MANSWERED))) m->m_flag |= MANSWER | MANSWERED; } NYD_OU; return 0; } FL int c_unanswered(void *v) { struct message *m; int *msgvec = v, *ip; NYD_IN; for (ip = msgvec; *ip != 0; ++ip) { m = message + *ip - 1; setdot(m); if (m->m_flag & (MANSWER | MANSWERED)) { m->m_flag &= ~(MANSWER | MANSWERED); m->m_flag |= MUNANSWER; } } NYD_OU; return 0; } FL int c_draft(void *v) { struct message *m; int *msgvec = v, *ip; NYD_IN; for (ip = msgvec; *ip != 0; ++ip) { m = message + *ip - 1; setdot(m); if (!(m->m_flag & (MDRAFT | MDRAFTED))) m->m_flag |= MDRAFT | MDRAFTED; } NYD_OU; return 0; } FL int c_undraft(void *v) { struct message *m; int *msgvec = v, *ip; NYD_IN; for (ip = msgvec; *ip != 0; ++ip) { m = message + *ip - 1; setdot(m); if (m->m_flag & (MDRAFT | MDRAFTED)) { m->m_flag &= ~(MDRAFT | MDRAFTED); m->m_flag |= MUNDRAFT; } } NYD_OU; return 0; } #include "su/code-ou.h" /* s-it-mode */ s-nail-14.9.15/src/mx/cmd-resend.c000066400000000000000000000630631352610246600164730ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ All sorts of `reply', `resend', `forward', and similar user commands. * * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany. * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1980, 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. */ #undef su_FILE #define su_FILE cmd_resend #define mx_SOURCE #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif #include #include #include "mx/cmd-charsetalias.h" #include "mx/cmd-mlist.h" #include "mx/names.h" #include "mx/url.h" /* TODO fake */ #include "su/code-in.h" /* Modify subject we reply to to begin with Re: if it does not already */ static char *a_crese_reedit(char const *subj); /* Fetch these headers, as appropriate */ static struct mx_name *a_crese_reply_to(struct message *mp); static struct mx_name *a_crese_mail_followup_to(struct message *mp); /* We honoured Reply-To: and/or Mail-Followup-To:, but *recipients-in-cc* is * set so try to keep "secondary" addressees in Cc:, if possible, */ static void a_crese_polite_rt_mft_move(struct message *mp, struct header *hp, struct mx_name *np); /* References and charset, as appropriate */ static void a_crese_make_ref_and_cs(struct message *mp, struct header *head); /* `reply' and `Lreply' workhorse */ static int a_crese_list_reply(int *msgvec, enum header_flags hf); /* Get PTF to implementation of command c (i.e., take care for *flipr*) */ static int (*a_crese_reply_or_Reply(char c))(int *, boole); /* Reply to a single message. Extract each name from the message header and * send them off to mail1() */ static int a_crese_reply(int *msgvec, boole recipient_record); /* Reply to a series of messages by simply mailing to the senders and not * messing around with the To: and Cc: lists as in normal reply */ static int a_crese_Reply(int *msgvec, boole recipient_record); /* Forward a message to a new recipient, in the sense of RFC 2822 */ static int a_crese_fwd(void *vp, boole recipient_record); /* Modify the subject we are replying to to begin with Fwd: */ static char *a_crese__fwdedit(char *subj); /* Do the real work of resending */ static int a_crese_resend1(void *v, boole add_resent); static char * a_crese_reedit(char const *subj){ char *newsubj; NYD2_IN; newsubj = NULL; if(subj != NULL && *subj != '\0'){ struct str in, out; uz i; char const *cp; in.l = su_cs_len(in.s = n_UNCONST(subj)); mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV); i = su_cs_len(cp = subject_re_trim(out.s)) +1; /* RFC mandates english "Re: " */ newsubj = n_autorec_alloc(sizeof("Re: ") -1 + i); su_mem_copy(newsubj, "Re: ", sizeof("Re: ") -1); su_mem_copy(&newsubj[sizeof("Re: ") -1], cp, i); n_free(out.s); } NYD2_OU; return newsubj; } static struct mx_name * a_crese_reply_to(struct message *mp){ char const *cp, *cp2; struct mx_name *rt, *np; enum gfield gf; NYD2_IN; gf = ok_blook(fullnames) ? GFULL | GSKIN : GSKIN; rt = NULL; if((cp = ok_vlook(reply_to_honour)) != NULL && (cp2 = hfield1("reply-to", mp)) != NULL && (rt = checkaddrs(lextract(cp2, GTO | gf), EACM_STRICT, NULL) ) != NULL){ char *lp; uz l; char const *tr; if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT)){ fprintf(n_stdout, _("Reply-To: header contains:")); for(np = rt; np != NULL; np = np->n_flink) fprintf(n_stdout, " %s", np->n_name); putc('\n', n_stdout); } tr = _("Reply-To %s%s"); l = su_cs_len(tr) + su_cs_len(rt->n_name) + 3 +1; lp = n_lofi_alloc(l); snprintf(lp, l, tr, rt->n_name, (rt->n_flink != NULL ? "..." : n_empty)); if(n_quadify(cp, UZ_MAX, lp, TRU1) <= FAL0) rt = NULL; n_lofi_free(lp); } NYD2_OU; return rt; } static struct mx_name * a_crese_mail_followup_to(struct message *mp){ char const *cp, *cp2; struct mx_name *mft, *np; enum gfield gf; NYD2_IN; gf = ok_blook(fullnames) ? GFULL | GSKIN : GSKIN; mft = NULL; if((cp = ok_vlook(followup_to_honour)) != NULL && (cp2 = hfield1("mail-followup-to", mp)) != NULL && (mft = checkaddrs(lextract(cp2, GTO | gf), EACM_STRICT, NULL) ) != NULL){ char *lp; uz l; char const *tr; if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT)){ fprintf(n_stdout, _("Mail-Followup-To: header contains:")); for(np = mft; np != NULL; np = np->n_flink) fprintf(n_stdout, " %s", np->n_name); putc('\n', n_stdout); } tr = _("Followup-To %s%s"); l = su_cs_len(tr) + su_cs_len(mft->n_name) + 3 +1; lp = n_lofi_alloc(l); snprintf(lp, l, tr, mft->n_name, (mft->n_flink != NULL ? "..." : n_empty)); if(n_quadify(cp, UZ_MAX, lp, TRU1) <= FAL0) mft = NULL; n_lofi_free(lp); } NYD2_OU; return mft; } static void a_crese_polite_rt_mft_move(struct message *mp, struct header *hp, struct mx_name *np){ boole once; NYD2_IN; UNUSED(mp); if(np == hp->h_to) hp->h_to = NULL; if(np == hp->h_cc) hp->h_cc = NULL; /* We may find that in the end To: is empty but Cc: is not, in which case we * upgrade Cc: to To: and jump back and redo the thing slightly different */ once = FAL0; jredo: while(np != NULL){ enum gfield gf; struct mx_name *nnp, **xpp, *xp; nnp = np; np = np->n_flink; if(once){ gf = GTO; xpp = &hp->h_to; }else{ gf = GCC; xpp = &hp->h_cc; } /* Try primary, then secondary */ for(xp = hp->h_mailx_orig_to; xp != NULL; xp = xp->n_flink) if(!su_cs_cmp_case(xp->n_name, nnp->n_name)) goto jlink; if(once){ gf = GCC; xpp = &hp->h_cc; } for(xp = hp->h_mailx_orig_cc; xp != NULL; xp = xp->n_flink) if(!su_cs_cmp_case(xp->n_name, nnp->n_name)) goto jlink; /* If this receiver came in only via R-T: or M-F-T:, place her/him/it in * To: due to lack of a better place */ gf = GTO; xpp = &hp->h_to; jlink: /* Link it at the end to not loose original sort order */ if((xp = *xpp) != NULL) while(xp->n_flink != NULL) xp = xp->n_flink; if((nnp->n_blink = xp) != NULL) xp->n_flink = nnp; else *xpp = nnp; nnp->n_flink = NULL; nnp->n_type = (nnp->n_type & ~GMASK) | gf; } /* If afterwards only Cc: data remains, upgrade all of it to To: */ if(hp->h_to == NULL){ np = hp->h_cc; hp->h_cc = NULL; if(!once){ hp->h_to = NULL; once = TRU1; goto jredo; }else for(hp->h_to = np; np != NULL; np = np->n_flink) np->n_type = (np->n_type & ~GMASK) | GTO; } NYD2_OU; } static void a_crese_make_ref_and_cs(struct message *mp, struct header *head) /* TODO ASAP*/ { char const *ccp; char *oldref, *oldmsgid, *newref; uz oldreflen = 0, oldmsgidlen = 0, reflen; unsigned i; struct mx_name *n; NYD2_IN; oldref = hfield1("references", mp); oldmsgid = hfield1("message-id", mp); if (oldmsgid == NULL || *oldmsgid == '\0') { head->h_ref = NULL; goto jleave; } reflen = 1; if (oldref) { oldreflen = su_cs_len(oldref); reflen += oldreflen + 2; } if (oldmsgid) { oldmsgidlen = su_cs_len(oldmsgid); reflen += oldmsgidlen; } newref = n_alloc(reflen); if (oldref != NULL) { su_mem_copy(newref, oldref, oldreflen +1); if (oldmsgid != NULL) { newref[oldreflen++] = ','; newref[oldreflen++] = ' '; su_mem_copy(newref + oldreflen, oldmsgid, oldmsgidlen +1); } } else if (oldmsgid) su_mem_copy(newref, oldmsgid, oldmsgidlen +1); n = extract(newref, GREF); n_free(newref); /* Limit number of references TODO better on parser side */ while (n->n_flink != NULL) n = n->n_flink; for (i = 1; i <= REFERENCES_MAX; ++i) { if (n->n_blink != NULL) n = n->n_blink; else break; } n->n_blink = NULL; head->h_ref = n; if (ok_blook(reply_in_same_charset) && (ccp = hfield1("content-type", mp)) != NULL){ if((head->h_charset = ccp = mime_param_get("charset", ccp)) != NULL){ ccp = mx_charsetalias_expand(ccp, FAL0); head->h_charset = ccp; } } jleave: NYD2_OU; } static int a_crese_list_reply(int *msgvec, enum header_flags hf){ struct header head; struct message *mp; char const *cp, *cp2; enum gfield gf; struct mx_name *rt, *mft, *np; NYD2_IN; n_pstate_err_no = su_ERR_NONE; gf = ok_blook(fullnames) ? GFULL | GSKIN : GSKIN; jwork_msg: n_autorec_relax_create(); mp = &message[*msgvec - 1]; touch(mp); setdot(mp); su_mem_set(&head, 0, sizeof head); head.h_flags = hf; head.h_subject = a_crese_reedit(hfield1("subject", mp)); head.h_mailx_command = (hf & HF_LIST_REPLY) ? "Lreply" : "reply"; head.h_mailx_orig_from = lextract(hfield1("from", mp), GIDENT | gf); head.h_mailx_orig_to = lextract(hfield1("to", mp), GTO | gf); head.h_mailx_orig_cc = lextract(hfield1("cc", mp), GCC | gf); head.h_mailx_orig_bcc = lextract(hfield1("bcc", mp), GBCC | gf); /* First of all check for Reply-To: then Mail-Followup-To:, because these, * if honoured, take precedence over anything else. We will join the * resulting list together if so desired. * So if we shall honour R-T: or M-F-T:, then these are our receivers! */ rt = a_crese_reply_to(mp); mft = a_crese_mail_followup_to(mp); if(rt != NULL || mft != NULL){ np = cat(rt, mft); if(mft != NULL) head.h_mft = n_namelist_dup(np, GTO | gf); /* xxx GTO: no "clone"! */ /* Optionally do not propagate a receiver that originally was in * secondary Cc: to the primary To: list */ if(ok_blook(recipients_in_cc)){ a_crese_polite_rt_mft_move(mp, &head, np); head.h_mailx_raw_cc = n_namelist_dup(head.h_cc, GCC | gf); head.h_cc = mx_alternates_remove(head.h_cc, FAL0); }else head.h_to = np; head.h_mailx_raw_to = n_namelist_dup(head.h_to, GTO | gf); head.h_to = mx_alternates_remove(head.h_to, FAL0); #ifdef mx_HAVE_DEVEL for(np = head.h_to; np != NULL; np = np->n_flink) ASSERT((np->n_type & GMASK) == GTO); for(np = head.h_cc; np != NULL; np = np->n_flink) ASSERT((np->n_type & GMASK) == GCC); #endif goto jrecipients_done; } /* Otherwise do the normal From: / To: / Cc: dance */ cp2 = n_header_senderfield_of(mp); /* Cc: */ np = NULL; if(ok_blook(recipients_in_cc) && (cp = hfield1("to", mp)) != NULL) np = lextract(cp, GCC | gf); if((cp = hfield1("cc", mp)) != NULL){ struct mx_name *x; if((x = lextract(cp, GCC | gf)) != NULL) np = cat(np, x); } if(np != NULL){ head.h_mailx_raw_cc = n_namelist_dup(np, GCC | gf); head.h_cc = mx_alternates_remove(np, FAL0); } /* To: */ np = NULL; if(cp2 != NULL) np = lextract(cp2, GTO | gf); if(!ok_blook(recipients_in_cc) && (cp = hfield1("to", mp)) != NULL){ struct mx_name *x; if((x = lextract(cp, GTO | gf)) != NULL) np = cat(np, x); } /* Delete my name from reply list, and with it, all my alternate names */ if(np != NULL){ head.h_mailx_raw_to = n_namelist_dup(np, GTO | gf); np = mx_alternates_remove(np, FAL0); /* The user may have send this to himself, don't ignore that */ if(count(np) == 0){ np = lextract(cp2, GTO | gf); head.h_mailx_raw_to = n_namelist_dup(np, GTO | gf); } } head.h_to = np; jrecipients_done: /* For list replies we want to automatically recognize the list address * given in the List-Post: header, so that we will not throw away a possible * corresponding receiver: temporarily "`mlist' the List-Post: address" */ if((hf & HF_LIST_REPLY) && (cp = hfield1("list-post", mp)) != NULL){ struct mx_name *x; if((x = n_extract_single(cp, GEXTRA | GMAILTO_URI)) == NULL || is_addr_invalid(x, EACM_STRICT)){ if(n_poption & n_PO_D_V) n_err(_("Message contains invalid List-Post: header\n")); }else{ /* A special case has been seen on e.g. ietf-announce@ietf.org: * these usually post to multiple groups, with ietf-announce@ * in List-Post:, but with Reply-To: set to ietf@ietf.org (since * -announce@ is only used for announcements, say). * So our desire is to honour this request and actively overwrite * List-Post: for our purpose; but only if its a single address. * However, to avoid ambiguities with users that place themselve in * Reply-To: and mailing lists which don't overwrite this (or only * extend this, shall such exist), only do so if reply_to exists of * a single address which points to the same domain as List-Post: */ if(rt != NULL && rt->n_flink == NULL && name_is_same_domain(x, rt)) cp = rt->n_name; /* rt is EACM_STRICT tested */ else cp = x->n_name; /* XXX mx_mlist_query_mp()?? */ if(mx_mlist_query(cp, FAL0) == mx_MLIST_OTHER) head.h_list_post = cp; } } /* In case of list replies we actively sort out any non-list recipient */ if(hf & HF_LIST_REPLY){ struct mx_name **nhpp, *nhp, *tail; cp = head.h_list_post; nhp = *(nhpp = &head.h_to); head.h_to = NULL; j_lt_redo: for(tail = NULL; nhp != NULL;){ np = nhp; nhp = nhp->n_flink; /* XXX mx_mlist_query_mp()?? */ if((cp != NULL && !su_cs_cmp_case(cp, np->n_name)) || mx_mlist_query(np->n_name, FAL0) != mx_MLIST_OTHER){ if((np->n_blink = tail) != NULL) tail->n_flink = np; else *nhpp = np; np->n_flink = NULL; tail = np; } } if(nhpp == &head.h_to){ nhp = *(nhpp = &head.h_cc); head.h_cc = NULL; goto j_lt_redo; } /* For `Lreply' only, fail immediately with DESTADDRREQ if there are no * receivers at all! */ if(head.h_to == NULL && head.h_cc == NULL){ n_err(_("No recipients specified for `Lreply'\n")); if(msgvec[1] == 0){ n_pstate_err_no = su_ERR_DESTADDRREQ; msgvec = NULL; goto jleave; } goto jskip_to_next; } } /* Move Cc: to To: as appropriate! */ if(head.h_to == NULL && (np = head.h_cc) != NULL){ head.h_cc = NULL; for(head.h_to = np; np != NULL; np = np->n_flink) np->n_type = (np->n_type & ~GMASK) | GTO; } a_crese_make_ref_and_cs(mp, &head); if(n_mail1((n_MAILSEND_HEADERS_PRINT | (hf & HF_RECIPIENT_RECORD ? n_MAILSEND_RECORD_RECIPIENT : 0)), &head, mp, NULL) != OKAY){ msgvec = NULL; goto jleave; } if(ok_blook(markanswered) && !(mp->m_flag & MANSWERED)) mp->m_flag |= MANSWER | MANSWERED; n_autorec_relax_gut(); jskip_to_next: if(*++msgvec != 0){ /* TODO message (error) ring.., less sleep */ if(n_psonce & n_PSO_INTERACTIVE){ fprintf(n_stdout, _("Waiting a second before proceeding to the next message..\n")); fflush(n_stdout); n_msleep(1000, FAL0); } goto jwork_msg; } jleave: NYD2_OU; return (msgvec == NULL); } static int (*a_crese_reply_or_Reply(char c))(int *, boole){ int (*rv)(int*, boole); NYD2_IN; rv = (ok_blook(flipr) ^ (c == 'R')) ? &a_crese_Reply : &a_crese_reply; NYD2_OU; return rv; } static int a_crese_reply(int *msgvec, boole recipient_record){ int rv; NYD2_IN; rv = a_crese_list_reply(msgvec, (recipient_record ? HF_RECIPIENT_RECORD : HF_NONE)); NYD2_OU; return rv; } static int a_crese_Reply(int *msgvec, boole recipient_record){ struct header head; struct message *mp; int *ap; enum gfield gf; NYD2_IN; su_mem_set(&head, 0, sizeof head); gf = ok_blook(fullnames) ? GFULL | GSKIN : GSKIN; for(ap = msgvec; *ap != 0; ++ap){ struct mx_name *np; mp = &message[*ap - 1]; touch(mp); setdot(mp); if((np = a_crese_reply_to(mp)) == NULL) np = lextract(n_header_senderfield_of(mp), GTO | gf); head.h_to = cat(head.h_to, np); } mp = &message[msgvec[0] - 1]; head.h_subject = hfield1("subject", mp); head.h_subject = a_crese_reedit(head.h_subject); a_crese_make_ref_and_cs(mp, &head); head.h_mailx_command = "Reply"; head.h_mailx_orig_from = lextract(hfield1("from", mp), GIDENT | gf); head.h_mailx_orig_to = lextract(hfield1("to", mp), GTO | gf); head.h_mailx_orig_cc = lextract(hfield1("cc", mp), GCC | gf); head.h_mailx_orig_bcc = lextract(hfield1("bcc", mp), GBCC | gf); if(ok_blook(recipients_in_cc)){ a_crese_polite_rt_mft_move(mp, &head, head.h_to); head.h_mailx_raw_cc = n_namelist_dup(head.h_cc, GCC | gf); head.h_cc = mx_alternates_remove(head.h_cc, FAL0); } head.h_mailx_raw_to = n_namelist_dup(head.h_to, GTO | gf); head.h_to = mx_alternates_remove(head.h_to, FAL0); if(ok_blook(quote_as_attachment)){ head.h_attach = n_autorec_calloc(1, sizeof *head.h_attach); head.h_attach->a_msgno = *msgvec; head.h_attach->a_content_description = _("Original message content"); } if(n_mail1(((recipient_record ? n_MAILSEND_RECORD_RECIPIENT : 0) | n_MAILSEND_HEADERS_PRINT), &head, mp, NULL) != OKAY){ msgvec = NULL; goto jleave; } if(ok_blook(markanswered) && !(mp->m_flag & MANSWERED)) mp->m_flag |= MANSWER | MANSWERED; jleave: NYD2_OU; return (msgvec == NULL); } static int a_crese_fwd(void *vp, boole recipient_record){ struct header head; struct message *mp; enum gfield gf; boole forward_as_attachment; int *msgvec, rv; struct n_cmd_arg *cap; struct n_cmd_arg_ctx *cacp; NYD2_IN; cacp = vp; cap = cacp->cac_arg; msgvec = cap->ca_arg.ca_msglist; cap = cap->ca_next; rv = 1; if(cap->ca_arg.ca_str.s[0] == '\0'){ if(!(n_pstate & (n_PS_HOOK_MASK | n_PS_ROBOT)) || (n_poption & n_PO_D_V)) n_err(_("No recipient specified.\n")); n_pstate_err_no = su_ERR_DESTADDRREQ; goto jleave; } forward_as_attachment = ok_blook(forward_as_attachment); gf = ok_blook(fullnames) ? GFULL | GSKIN : GSKIN; su_mem_set(&head, 0, sizeof head); head.h_to = lextract(cap->ca_arg.ca_str.s, (GTO | (ok_blook(fullnames) ? GFULL : GSKIN))); mp = &message[*msgvec - 1]; touch(mp); setdot(mp); head.h_subject = hfield1("subject", mp); head.h_subject = a_crese__fwdedit(head.h_subject); head.h_mailx_command = "forward"; head.h_mailx_raw_to = n_namelist_dup(head.h_to, GTO | gf); head.h_mailx_orig_from = lextract(hfield1("from", mp), GIDENT | gf); head.h_mailx_orig_to = lextract(hfield1("to", mp), GTO | gf); head.h_mailx_orig_cc = lextract(hfield1("cc", mp), GCC | gf); head.h_mailx_orig_bcc = lextract(hfield1("bcc", mp), GBCC | gf); if(forward_as_attachment){ head.h_attach = n_autorec_calloc(1, sizeof *head.h_attach); head.h_attach->a_msgno = *msgvec; head.h_attach->a_content_description = _("Forwarded message"); } rv = (n_mail1((n_MAILSEND_IS_FWD | (recipient_record ? n_MAILSEND_RECORD_RECIPIENT : 0) | n_MAILSEND_HEADERS_PRINT), &head, (forward_as_attachment ? NULL : mp), NULL) != OKAY); /* reverse! */ jleave: NYD2_OU; return rv; } static char * a_crese__fwdedit(char *subj){ struct str in, out; char *newsubj; NYD2_IN; newsubj = NULL; if(subj == NULL || *subj == '\0') goto jleave; in.s = subj; in.l = su_cs_len(subj); mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV); newsubj = n_autorec_alloc(out.l + 6); if(!su_cs_cmp_case_n(out.s, "Fwd: ", sizeof("Fwd: ") -1)) /* TODO EXTEND */ su_mem_copy(newsubj, out.s, out.l +1); else{ su_mem_copy(newsubj, "Fwd: ", 5); /* TODO ..a la subject_re_trim()! */ su_mem_copy(&newsubj[5], out.s, out.l +1); } n_free(out.s); jleave: NYD2_OU; return newsubj; } static int a_crese_resend1(void *vp, boole add_resent){ struct mx_url url; struct header head; struct mx_name *myto, *myrawto; boole mta_isexe; enum gfield gf; int *msgvec, rv, *ip; struct n_cmd_arg *cap; struct n_cmd_arg_ctx *cacp; NYD2_IN; cacp = vp; cap = cacp->cac_arg; msgvec = cap->ca_arg.ca_msglist; cap = cap->ca_next; rv = 1; n_pstate_err_no = su_ERR_DESTADDRREQ; if(cap->ca_arg.ca_str.s[0] == '\0'){ if(!(n_pstate & (n_PS_HOOK_MASK | n_PS_ROBOT)) || (n_poption & n_PO_D_V)) jedar: n_err(_("No recipient specified.\n")); goto jleave; } if(!(mta_isexe = mx_sendout_mta_url(&url))) goto jleave; mta_isexe = (mta_isexe != TRU1); gf = ok_blook(fullnames) ? GFULL | GSKIN : GSKIN; myrawto = nalloc(cap->ca_arg.ca_str.s, GTO | gf | GNOT_A_LIST | GNULL_OK); if(myrawto == NIL) goto jedar; su_mem_set(&head, 0, sizeof head); head.h_to = n_namelist_dup(myrawto, myrawto->n_type); /* C99 */{ s8 snderr; snderr = 0; myto = n_namelist_vaporise_head(&head, FAL0, !ok_blook(posix), (EACM_NORMAL | EACM_DOMAINCHECK | (mta_isexe ? EACM_NONE : EACM_NONAME | EACM_NONAME_OR_FAIL)), &snderr); if(snderr < 0){ n_err(_("Some addressees were classified as \"hard error\"\n")); n_pstate_err_no = su_ERR_PERM; goto jleave; } if(myto == NIL) goto jedar; } n_autorec_relax_create(); for(ip = msgvec; *ip != 0; ++ip){ struct message *mp; mp = &message[*ip - 1]; touch(mp); setdot(mp); su_mem_set(&head, 0, sizeof head); head.h_to = myto; head.h_mailx_command = "resend"; head.h_mailx_raw_to = myrawto; head.h_mailx_orig_from = lextract(hfield1("from", mp), GIDENT | gf); head.h_mailx_orig_to = lextract(hfield1("to", mp), GTO | gf); head.h_mailx_orig_cc = lextract(hfield1("cc", mp), GCC | gf); head.h_mailx_orig_bcc = lextract(hfield1("bcc", mp), GBCC | gf); if(n_resend_msg(mp, (mta_isexe ? NIL : &url), &head, add_resent ) != OKAY){ /* n_autorec_relax_gut(); XXX but is handled automatically? */ goto jleave; } n_autorec_relax_unroll(); } n_autorec_relax_gut(); n_pstate_err_no = su_ERR_NONE; rv = 0; jleave: NYD2_OU; return rv; } FL int c_reply(void *vp){ int rv; NYD_IN; rv = (*a_crese_reply_or_Reply('r'))(vp, FAL0); NYD_OU; return rv; } FL int c_replyall(void *vp){ int rv; NYD_IN; rv = a_crese_reply(vp, FAL0); NYD_OU; return rv; } FL int c_replysender(void *vp){ int rv; NYD_IN; rv = a_crese_Reply(vp, FAL0); NYD_OU; return rv; } FL int c_Reply(void *vp){ int rv; NYD_IN; rv = (*a_crese_reply_or_Reply('R'))(vp, FAL0); NYD_OU; return rv; } FL int c_Lreply(void *vp){ int rv; NYD_IN; rv = a_crese_list_reply(vp, HF_LIST_REPLY); NYD_OU; return rv; } FL int c_followup(void *vp){ int rv; NYD_IN; rv = (*a_crese_reply_or_Reply('r'))(vp, TRU1); NYD_OU; return rv; } FL int c_followupall(void *vp){ int rv; NYD_IN; rv = a_crese_reply(vp, TRU1); NYD_OU; return rv; } FL int c_followupsender(void *vp){ int rv; NYD_IN; rv = a_crese_Reply(vp, TRU1); NYD_OU; return rv; } FL int c_Followup(void *vp){ int rv; NYD_IN; rv = (*a_crese_reply_or_Reply('R'))(vp, TRU1); NYD_OU; return rv; } FL int c_forward(void *vp){ int rv; NYD_IN; rv = a_crese_fwd(vp, FAL0); NYD_OU; return rv; } FL int c_Forward(void *vp){ int rv; NYD_IN; rv = a_crese_fwd(vp, TRU1); NYD_OU; return rv; } FL int c_resend(void *vp){ int rv; NYD_IN; rv = a_crese_resend1(vp, TRU1); NYD_OU; return rv; } FL int c_Resend(void *vp){ int rv; NYD_IN; rv = a_crese_resend1(vp, FAL0); NYD_OU; return rv; } #include "su/code-ou.h" /* s-it-mode */ s-nail-14.9.15/src/mx/cmd-shortcut.c000066400000000000000000000065161352610246600170660ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Implementation of cmd-shortcut.h. *@ TODO Support vput, i.e.: vput shorcut x what-this-expands-to *@ TODO _SCUT -> _CSCUT * * Copyright (c) 2017 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #undef su_FILE #define su_FILE cmd_shortcut #define mx_SOURCE #define mx_SOURCE_CMD_SHORTCUT #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif #include #include #include "mx/cmd-shortcut.h" #include "su/code-in.h" /* ..of a_scut_dp */ #define a_SCUT_FLAGS (su_CS_DICT_OWNS | su_CS_DICT_HEAD_RESORT |\ su_CS_DICT_AUTO_SHRINK | su_CS_DICT_ERR_PASS) #define a_SCUT_TRESHOLD_SHIFT 2 static struct su_cs_dict *a_scut_dp, a_scut__d; /* XXX atexit _gut() (DVL()) */ int c_shortcut(void *vp){ struct su_cs_dict_view dv; int rv; char const **argv, *key, *dat; NYD_IN; if((key = *(argv = vp)) == NIL){ struct n_strlist *slp; slp = NIL; rv = !(mx_xy_dump_dict("shortcut", a_scut_dp, &slp, NIL, &mx_xy_dump_dict_gen_ptf) && mx_page_or_print_strlist("shortcut", slp)); }else if(argv[1] == NIL){ if(a_scut_dp != NIL && su_cs_dict_view_find(su_cs_dict_view_setup(&dv, a_scut_dp), key)){ struct n_strlist *slp; slp = mx_xy_dump_dict_gen_ptf("shortcut", key, su_cs_dict_view_data(&dv)); rv = (fputs(slp->sl_dat, n_stdout) == EOF); rv |= (putc('\n', n_stdout) == EOF); }else{ n_err(_("No such shortcut: %s\n"), n_shexp_quote_cp(key, FAL0)); rv = 1; } }else{ if(a_scut_dp == NIL) a_scut_dp = su_cs_dict_set_treshold_shift( su_cs_dict_create(&a_scut__d, a_SCUT_FLAGS, &su_cs_toolbox), a_SCUT_TRESHOLD_SHIFT); for(rv = 0; key != NIL; argv += 2, key = *argv){ if((dat = argv[1]) == NIL){ n_err(_("Synopsis: shortcut: \n")); rv = 1; break; } if(su_cs_dict_replace(a_scut_dp, key, C(char*,dat)) > 0){ n_err(_("Failed to create `shortcut' storage: %s\n"), n_shexp_quote_cp(key, FAL0)); rv = 1; } } } NYD_OU; return rv; } int c_unshortcut(void *vp){ int rv; NYD_IN; rv = !mx_unxy_dict("shortcut", a_scut_dp, vp); NYD_OU; return rv; } char const * mx_shortcut_expand(char const *cp){ NYD_IN; if(a_scut_dp != NIL) cp = S(char*,su_cs_dict_lookup(a_scut_dp, cp)); else cp = NIL; NYD_OU; return cp; } #include "su/code-ou.h" /* s-it-mode */ s-nail-14.9.15/src/mx/cmd-tab.c000066400000000000000000000746241352610246600157660ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ n_cmd_firstfit(): the table of commands + `help' and `list'. *@ And n_cmd_arg_parse(), the (new) argument list parser. TODO this is *@ TODO too stupid yet, however: it should fully support subcommands, too, so *@ TODO that, e.g., "vexpr regex" arguments can be fully prepared by the *@ TODO generic parser. But at least a bit. *@ TODO See cmd-tab.h for sort and speedup TODOs. * * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: BSD-3-Clause TODO ISC */ /* Command table and getrawlist() also: * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany. * * Copyright (c) 1980, 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. */ #undef su_FILE #define su_FILE cmd_tab #define mx_SOURCE #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif #include #include #include #include "mx/cmd-charsetalias.h" #include "mx/cmd-commandalias.h" #include "mx/cmd-csop.h" #include "mx/cmd-filetype.h" #include "mx/cmd-mlist.h" #include "mx/cmd-shortcut.h" #include "mx/cmd-vexpr.h" #include "mx/colour.h" #include "mx/cred-netrc.h" #include "mx/dig-msg.h" #include "mx/file-streams.h" #include "mx/names.h" #include "mx/sigs.h" #include "mx/termios.h" #include "mx/tty.h" #include "mx/url.h" /* TODO fake */ #include "su/code-in.h" /* Create a multiline info string about all known additional infos for lcp */ #ifdef mx_HAVE_DOCSTRINGS static char const *a_ctab_cmdinfo(struct n_cmd_desc const *cdp); #endif /* Print a list of all commands */ static int a_ctab_c_list(void *vp); static su_sz a_ctab__pcmd_cmp(void const *s1, void const *s2); /* `help' / `?' command */ static int a_ctab_c_help(void *vp); #if defined mx_HAVE_DEVEL && defined su_MEM_ALLOC_DEBUG static int a_ctab_c_memtrace(void *vp); #endif /* List of all commands; but first their n_cmd_arg_desc instances */ #include "mx/cmd-tab.h" /* $(MX_SRCDIR) */ static struct n_cmd_desc const a_ctab_ctable[] = { #include }; /* And a list of things which are special to the lexer in go.c, so that we can * provide help and list them. * This cross-file relationship is a bit unfortunate.. */ #ifdef mx_HAVE_DOCSTRINGS # define DS(S) , S #else # define DS(S) #endif static struct n_cmd_desc const a_ctab_ctable_plus[] = { { n_ns, (int(*)(void*))-1, n_CMD_ARG_TYPE_STRING, 0, 0, NULL DS(N_("Comment command: ignore remaining (continuable) line")) }, { n_hy, (int(*)(void*))-1, n_CMD_ARG_TYPE_WYSH, 0, 0, NULL DS(N_("Print out the preceding message")) } }; #undef DS #ifdef mx_HAVE_DOCSTRINGS static char const * a_ctab_cmdinfo(struct n_cmd_desc const *cdp){ struct n_string rvb, *rv; char const *cp; NYD2_IN; rv = n_string_creat_auto(&rvb); rv = n_string_reserve(rv, 80); switch(cdp->cd_caflags & n_CMD_ARG_TYPE_MASK){ case n_CMD_ARG_TYPE_MSGLIST: cp = N_("message-list"); break; case n_CMD_ARG_TYPE_NDMLIST: cp = N_("message-list (without default)"); break; case n_CMD_ARG_TYPE_STRING: case n_CMD_ARG_TYPE_RAWDAT: cp = N_("string data"); break; case n_CMD_ARG_TYPE_RAWLIST: cp = N_("old-style quoting"); break; case n_CMD_ARG_TYPE_WYRA: cp = N_("`wysh' for sh(1)ell-style quoting"); break; case n_CMD_ARG_TYPE_WYSH: cp = (cdp->cd_minargs == 0 && cdp->cd_maxargs == 0) ? N_("sh(1)ell-style quoting (takes no arguments)") : N_("sh(1)ell-style quoting"); break; default: case n_CMD_ARG_TYPE_ARG:{ u32 flags, xflags; uz i; struct n_cmd_arg_desc const *cadp; rv = n_string_push_cp(rv, _("argument tokens: ")); for(cadp = cdp->cd_cadp, i = 0; i < cadp->cad_no; ++i){ xflags = flags = cadp->cad_ent_flags[i][0]; jfakeent: if(i != 0) rv = n_string_push_c(rv, ','); if(flags & n_CMD_ARG_DESC_OPTION) rv = n_string_push_c(rv, '['); if(flags & n_CMD_ARG_DESC_GREEDY) rv = n_string_push_c(rv, ':'); switch(flags & n__CMD_ARG_DESC_TYPE_MASK){ default: case n_CMD_ARG_DESC_SHEXP: rv = n_string_push_cp(rv, _("(shell-)token")); break; case n_CMD_ARG_DESC_MSGLIST: rv = n_string_push_cp(rv, _("(shell-)msglist")); break; case n_CMD_ARG_DESC_NDMSGLIST: rv = n_string_push_cp(rv, _("(shell-)msglist (no default)")); break; case n_CMD_ARG_DESC_MSGLIST_AND_TARGET: rv = n_string_push_cp(rv, _("(shell-)msglist")); ++i; xflags = n_CMD_ARG_DESC_SHEXP; } if(flags & n_CMD_ARG_DESC_GREEDY) rv = n_string_push_c(rv, ':'); if(flags & n_CMD_ARG_DESC_OPTION) rv = n_string_push_c(rv, ']'); if(xflags != flags){ flags = xflags; goto jfakeent; } } cp = NULL; }break; } if(cp != NULL) rv = n_string_push_cp(rv, V_(cp)); /* Note: on updates, change the manual! */ if(cdp->cd_caflags & n_CMD_ARG_L) rv = n_string_push_cp(rv, _(" | `local'")); if(cdp->cd_caflags & n_CMD_ARG_V) rv = n_string_push_cp(rv, _(" | `vput'")); if(cdp->cd_caflags & n_CMD_ARG_EM) rv = n_string_push_cp(rv, _(" | *!*")); if(cdp->cd_caflags & n_CMD_ARG_A) rv = n_string_push_cp(rv, _(" | needs-box")); if(cdp->cd_caflags & (n_CMD_ARG_I | n_CMD_ARG_M | n_CMD_ARG_X)){ rv = n_string_push_cp(rv, _(" | yay:")); if(cdp->cd_caflags & n_CMD_ARG_I) rv = n_string_push_cp(rv, _(" batch/interactive")); if(cdp->cd_caflags & n_CMD_ARG_M) rv = n_string_push_cp(rv, _(" send-mode")); if(cdp->cd_caflags & n_CMD_ARG_X) rv = n_string_push_cp(rv, _(" subprocess")); } if(cdp->cd_caflags & (n_CMD_ARG_R | n_CMD_ARG_S)){ rv = n_string_push_cp(rv, _(" | nay:")); if(cdp->cd_caflags & n_CMD_ARG_R) rv = n_string_push_cp(rv, _(" compose-mode")); if(cdp->cd_caflags & n_CMD_ARG_S) rv = n_string_push_cp(rv, _(" startup")); } if(cdp->cd_caflags & n_CMD_ARG_G) rv = n_string_push_cp(rv, _(" | gabby")); cp = n_string_cp(rv); NYD2_OU; return cp; } #endif /* mx_HAVE_DOCSTRINGS */ static int a_ctab_c_list(void *vp){ FILE *fp; struct n_cmd_desc const **cdpa, *cdp, **cdpa_curr; uz i, l, scrwid; NYD_IN; i = NELEM(a_ctab_ctable) + NELEM(a_ctab_ctable_plus) +1; cdpa = n_autorec_alloc(sizeof(cdp) * i); for(i = 0; i < NELEM(a_ctab_ctable); ++i) cdpa[i] = &a_ctab_ctable[i]; for(l = 0; l < NELEM(a_ctab_ctable_plus); ++i, ++l) cdpa[i] = &a_ctab_ctable_plus[l]; cdpa[i] = NIL; if(*(void**)vp == NIL) su_sort_shell_vpp(S(void const**,cdpa), i, &a_ctab__pcmd_cmp); if((fp = mx_fs_tmp_open("list", (mx_FS_O_RDWR | mx_FS_O_UNLINK | mx_FS_O_REGISTER), NIL)) == NIL) fp = n_stdout; scrwid = mx_TERMIOS_WIDTH_OF_LISTS(); fprintf(fp, _("Commands are:\n")); l = 1; for(i = 0, cdpa_curr = cdpa; (cdp = *cdpa_curr++) != NULL;){ char const *pre, *suf; if(cdp->cd_func == NULL) pre = "[", suf = "]"; else pre = suf = n_empty; #ifdef mx_HAVE_DOCSTRINGS if(n_poption & n_PO_D_V){ fprintf(fp, "%s%s%s\n", pre, cdp->cd_name, suf); ++l; fprintf(fp, " : %s\n", V_(cdp->cd_doc)); ++l; fprintf(fp, " : %s\n", a_ctab_cmdinfo(cdp)); ++l; }else #endif { uz j; j = su_cs_len(cdp->cd_name); if(*pre != '\0') j += 2; if((i += j + 2) > scrwid){ i = j; fprintf(fp, "\n"); ++l; } fprintf(fp, (*cdpa_curr != NULL ? "%s%s%s, " : "%s%s%s\n"), pre, cdp->cd_name, suf); } } if(fp != n_stdout){ page_or_print(fp, l); mx_fs_close(fp); } NYD_OU; return 0; } static su_sz a_ctab__pcmd_cmp(void const *s1, void const *s2){ su_sz rv; struct n_cmd_desc const *cdpa1, *cdpa2; NYD2_IN; cdpa1 = s1; cdpa2 = s2; rv = su_cs_cmp(cdpa1->cd_name, cdpa2->cd_name); NYD2_OU; return rv; } static int a_ctab_c_help(void *vp){ int rv; char const *arg; NYD_IN; /* Help for a single command? */ if((arg = *(char const**)vp) != NULL){ struct n_cmd_desc const *cdp, *cdp_max; char const *alias_name, *alias_exp, *aepx; /* Aliases take precedence. * Avoid self-recursion; since a commandalias can shadow a command of * equal name allow one level of expansion to return an equal result: * "commandalias q q;commandalias x q;x" should be "x->q->q->quit" */ alias_name = NULL; while((aepx = mx_commandalias_exists(arg, &alias_exp)) != NULL && (alias_name == NULL || su_cs_cmp(alias_name, aepx))){ alias_name = aepx; fprintf(n_stdout, "%s -> ", arg); arg = alias_exp; } cdp_max = &(cdp = a_ctab_ctable)[NELEM(a_ctab_ctable)]; jredo: for(; cdp < cdp_max; ++cdp){ if(su_cs_starts_with(cdp->cd_name, arg)){ fputs(arg, n_stdout); if(su_cs_cmp(arg, cdp->cd_name)) fprintf(n_stdout, " (%s)", cdp->cd_name); }else continue; #ifdef mx_HAVE_DOCSTRINGS fprintf(n_stdout, ": %s", V_(cdp->cd_doc)); if(n_poption & n_PO_D_V) fprintf(n_stdout, "\n : %s", a_ctab_cmdinfo(cdp)); #endif putc('\n', n_stdout); rv = 0; goto jleave; } if(cdp_max == &a_ctab_ctable[NELEM(a_ctab_ctable)]){ cdp_max = &(cdp = a_ctab_ctable_plus)[NELEM(a_ctab_ctable_plus)]; goto jredo; } if(alias_name != NULL){ fprintf(n_stdout, "%s\n", n_shexp_quote_cp(arg, TRU1)); rv = 0; }else{ n_err(_("Unknown command: `%s'\n"), arg); rv = 1; } }else{ /* Very ugly, but take care for compiler supported string lengths :( */ #ifdef mx_HAVE_UISTRINGS fputs(su_program, n_stdout); fputs(_( " commands -- denotes message specification tokens, e.g.,\n" "1-5, :n, @f@Ulf or . (current, the \"dot\"), separated by *ifs*:\n"), n_stdout); fputs(_( "\n" "type type (`print') messages (honour `headerpick' etc.)\n" "Type like `type' but always show all headers\n" "next goto and type next message\n" "headers header summary ... for messages surrounding \"dot\"\n" "search ... for the given expression list (alias for `from')\n" "delete delete messages (can be `undelete'd)\n"), n_stdout); fputs(_( "\n" "save folder append messages to folder and mark as saved\n" "copy folder like `save', but do not mark them (`move' moves)\n" "write file write message contents to file (prompts for parts)\n" "Reply reply to message sender(s) only\n" "reply like `Reply', but address all recipients\n" "Lreply forced mailing list `reply' (see `mlist')\n"), n_stdout); fputs(_( "\n" "mail compose a mail for the given recipients\n" "file folder change to another mailbox\n" "File folder like `file', but open readonly\n" "quit quit and apply changes to the current mailbox\n" "xit or exit like `quit', but discard changes\n" "!shell command shell escape\n" "list [] all available commands [in search order]\n"), n_stdout); #endif /* mx_HAVE_UISTRINGS */ rv = (ferror(n_stdout) != 0); } jleave: NYD_OU; return rv; } #if defined mx_HAVE_DEVEL && defined su_MEM_ALLOC_DEBUG static int a_ctab_c_memtrace(void *vp){ int rv; u32 oopt; NYD2_IN; UNUSED(vp); /* Only for development.. */ oopt = n_poption; if(!(oopt & n_PO_V)) ok_bset(verbose); rv = (su_mem_trace() != FAL0); if(!(oopt & n_PO_V)) ok_bclear(verbose); NYD2_OU; return rv; } #endif FL char const * n_cmd_isolate_name(char const *cmd){ NYD2_IN; while(*cmd != '\0' && su_cs_find_c("\\!~|? \t0123456789&%@$^.:/-+*'\",;(`", *cmd) == NULL) ++cmd; NYD2_OU; return n_UNCONST(cmd); } FL boole n_cmd_is_valid_name(char const *cmd){ /* Mirrors things from go.c */ static char const a_prefixes[][8] = {"ignerr", "local", "wysh", "vput", "scope", "u"}; uz i; NYD2_IN; i = 0; do if(!su_cs_cmp_case(cmd, a_prefixes[i])){ cmd = NIL; break; }while(++i < NELEM(a_prefixes)); NYD2_OU; return (cmd != NIL); } FL struct n_cmd_desc const * n_cmd_firstfit(char const *cmd){ /* TODO *hashtable*! linear list search!!! */ struct n_cmd_desc const *cdp; NYD2_IN; for(cdp = a_ctab_ctable; cdp < &a_ctab_ctable[NELEM(a_ctab_ctable)]; ++cdp) if(*cmd == *cdp->cd_name && cdp->cd_func != NULL && su_cs_starts_with(cdp->cd_name, cmd)) goto jleave; cdp = NULL; jleave: NYD2_OU; return cdp; } FL struct n_cmd_desc const * n_cmd_default(void){ struct n_cmd_desc const *cdp; NYD2_IN; cdp = &a_ctab_ctable[0]; NYD2_OU; return cdp; } FL boole n_cmd_arg_parse(struct n_cmd_arg_ctx *cacp){ struct n_cmd_arg ncap, *lcap, *target_argp, **target_argpp, *cap; struct str shin_orig, shin; boole stoploop, greedyjoin; void const *cookie; uz cad_idx, parsed_args; struct n_cmd_arg_desc const *cadp; NYD_IN; ASSERT(cacp->cac_inlen == 0 || cacp->cac_indat != NULL); ASSERT(cacp->cac_desc->cad_no > 0); #ifdef mx_HAVE_DEBUG /* C99 */{ boole opt_seen = FAL0; for(cadp = cacp->cac_desc, cad_idx = 0; cad_idx < cadp->cad_no; ++cad_idx){ ASSERT(cadp->cad_ent_flags[cad_idx][0] & n__CMD_ARG_DESC_TYPE_MASK); /* TODO n_CMD_ARG_DESC_MSGLIST+ may only be used as the last entry */ ASSERT(!(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_MSGLIST) || cad_idx + 1 == cadp->cad_no); ASSERT(!(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_NDMSGLIST) || cad_idx + 1 == cadp->cad_no); ASSERT(!(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_MSGLIST_AND_TARGET) || cad_idx + 1 == cadp->cad_no); ASSERT(!opt_seen || (cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_OPTION)); if(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_OPTION) opt_seen = TRU1; ASSERT(!(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_GREEDY) || cad_idx + 1 == cadp->cad_no); /* TODO n_CMD_ARG_DESC_MSGLIST+ can only be n_CMD_ARG_DESC_GREEDY. * TODO And they may not be n_CMD_ARG_DESC_OPTION */ ASSERT(!(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_MSGLIST) || (cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_GREEDY)); ASSERT(!(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_NDMSGLIST) || (cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_GREEDY)); ASSERT(!(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_MSGLIST_AND_TARGET) || (cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_GREEDY)); ASSERT(!(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_MSGLIST) || !(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_OPTION)); ASSERT(!(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_NDMSGLIST) || !(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_OPTION)); ASSERT(!(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_MSGLIST_AND_TARGET) || !(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_OPTION)); } } #endif /* mx_HAVE_DEBUG */ n_pstate_err_no = su_ERR_NONE; shin.s = n_UNCONST(cacp->cac_indat); /* "logical" only */ shin.l = (cacp->cac_inlen == UZ_MAX ? su_cs_len(shin.s) : cacp->cac_inlen); shin_orig = shin; cacp->cac_no = 0; cacp->cac_arg = lcap = NULL; cookie = NULL; parsed_args = 0; greedyjoin = FAL0; /* TODO We need to test >= 0 in order to deal with MSGLIST arguments, as * TODO those use getmsglist() and that needs to deal with that situation. * TODO In the future that should change; see jloop_break TODO below */ for(cadp = cacp->cac_desc, cad_idx = 0; /*shin.l >= 0 &&*/ cad_idx < cadp->cad_no; ++cad_idx){ jredo: su_mem_set(&ncap, 0, sizeof ncap); ncap.ca_indat = shin.s; /* >ca_inline once we know */ su_mem_copy(&ncap.ca_ent_flags[0], &cadp->cad_ent_flags[cad_idx][0], sizeof ncap.ca_ent_flags); target_argpp = NULL; stoploop = FAL0; switch(ncap.ca_ent_flags[0] & n__CMD_ARG_DESC_TYPE_MASK){ default: case n_CMD_ARG_DESC_SHEXP:{ struct n_string shou, *shoup; enum n_shexp_state shs; if(shin.l == 0) goto jloop_break; /* xxx (required grrr) quickshot */ shoup = n_string_creat_auto(&shou); ncap.ca_arg_flags = shs = n_shexp_parse_token((ncap.ca_ent_flags[1] | n_SHEXP_PARSE_LOG | n_SHEXP_PARSE_META_SEMICOLON | n_SHEXP_PARSE_TRIM_SPACE), shoup, &shin, (ncap.ca_ent_flags[0] & n_CMD_ARG_DESC_GREEDY ? &cookie : NULL)); if((shs & n_SHEXP_STATE_META_SEMICOLON) && shin.l > 0){ ASSERT(shs & n_SHEXP_STATE_STOP); n_go_input_inject(n_GO_INPUT_INJECT_COMMIT, shin.s, shin.l); shin.l = 0; } ncap.ca_inlen = P2UZ(shin.s - ncap.ca_indat); if((shs & (n_SHEXP_STATE_OUTPUT | n_SHEXP_STATE_ERR_MASK)) == n_SHEXP_STATE_OUTPUT){ ncap.ca_arg.ca_str.s = n_string_cp(shoup); ncap.ca_arg.ca_str.l = shou.s_len; } if(shs & n_SHEXP_STATE_ERR_MASK) goto jerr; if((shs & n_SHEXP_STATE_STOP) && (ncap.ca_ent_flags[0] & (n_CMD_ARG_DESC_OPTION | n_CMD_ARG_DESC_HONOUR_STOP))){ if(!(shs & n_SHEXP_STATE_OUTPUT)){ /* We would return FAL0 for bind in "bind;echo huhu", whereas we * do not for "bind" due to the "shin.l==0 goto jloop_break;" * introductional quickshot; ensure we succeed */ if(shs & n_SHEXP_STATE_META_SEMICOLON) goto jloop_break; goto jleave; } /* Succeed if we had any arg */ stoploop = TRU1; }else if(!(shs & n_SHEXP_STATE_OUTPUT)){ ASSERT(0); goto jerr; } }break; case n_CMD_ARG_DESC_MSGLIST_AND_TARGET: target_argpp = &target_argp; /* FALLTHRU */ case n_CMD_ARG_DESC_MSGLIST: case n_CMD_ARG_DESC_NDMSGLIST: /* TODO _MSGLIST yet at end and greedy only (fast hack). * TODO And consumes too much memory */ ASSERT(shin.s[shin.l] == '\0'); if(n_getmsglist(shin.s, (ncap.ca_arg.ca_msglist = n_autorec_calloc(msgCount +1, sizeof *ncap.ca_arg.ca_msglist) ), cacp->cac_msgflag, target_argpp) < 0){ n_pstate_err_no = su_ERR_INVAL;/* XXX should come from getmsglist*/ goto jerr; } if(ncap.ca_arg.ca_msglist[0] == 0){ u32 e; switch(ncap.ca_ent_flags[0] & n__CMD_ARG_DESC_TYPE_MASK){ case n_CMD_ARG_DESC_MSGLIST_AND_TARGET: case n_CMD_ARG_DESC_MSGLIST: if((ncap.ca_arg.ca_msglist[0] = first(cacp->cac_msgflag, cacp->cac_msgmask)) == 0){ if(!(n_pstate & (n_PS_HOOK_MASK | n_PS_ROBOT)) || (n_poption & n_PO_D_V)) n_err(_("No applicable messages\n")); e = n_CMD_ARG_DESC_TO_ERRNO(ncap.ca_ent_flags[0]); if(e == 0) e = su_ERR_NODATA; n_pstate_err_no = e; goto jerr; } ncap.ca_arg.ca_msglist[1] = 0; /* TODO For the MSGLIST_AND_TARGET case an entirely empty input * TODO results in no _TARGET argument: ensure it is there! */ if(target_argpp != NULL && (cap = *target_argpp) == NULL){ cap = n_autorec_calloc(1, sizeof *cap); cap->ca_arg.ca_str.s = n_UNCONST(n_empty); *target_argpp = cap; } /* FALLTHRU */ default: break; } }else if((ncap.ca_ent_flags[0] & n_CMD_ARG_DESC_MSGLIST_NEEDS_SINGLE ) && ncap.ca_arg.ca_msglist[1] != 0){ if(!(n_pstate & (n_PS_HOOK_MASK | n_PS_ROBOT)) || (n_poption & n_PO_D_V)) n_err(_("Cannot specify multiple messages at once\n")); n_pstate_err_no = su_ERR_NOTSUP; goto jerr; } shin.l = 0; stoploop = TRU1; /* XXX Asserted to be last above! */ break; } ++parsed_args; if(greedyjoin == TRU1){ /* TODO speed this up! */ char *cp; uz i; ASSERT((ncap.ca_ent_flags[0] & n__CMD_ARG_DESC_TYPE_MASK ) != n_CMD_ARG_DESC_MSGLIST); ASSERT(lcap != NULL); ASSERT(target_argpp == NULL); i = lcap->ca_arg.ca_str.l; lcap->ca_arg.ca_str.l += 1 + ncap.ca_arg.ca_str.l; cp = n_autorec_alloc(lcap->ca_arg.ca_str.l +1); su_mem_copy(cp, lcap->ca_arg.ca_str.s, i); lcap->ca_arg.ca_str.s = cp; cp[i++] = ' '; su_mem_copy(&cp[i], ncap.ca_arg.ca_str.s, ncap.ca_arg.ca_str.l +1); }else{ cap = n_autorec_alloc(sizeof *cap); su_mem_copy(cap, &ncap, sizeof ncap); if(lcap == NULL) cacp->cac_arg = cap; else lcap->ca_next = cap; lcap = cap; ++cacp->cac_no; if(target_argpp != NULL){ lcap->ca_next = cap = *target_argpp; if(cap != NULL){ lcap = cap; ++cacp->cac_no; } } } if(stoploop) goto jleave; if((shin.l > 0 || cookie != NULL) && (ncap.ca_ent_flags[0] & n_CMD_ARG_DESC_GREEDY)){ if(!greedyjoin) greedyjoin = ((ncap.ca_ent_flags[0] & n_CMD_ARG_DESC_GREEDY_JOIN) && (ncap.ca_ent_flags[0] & n_CMD_ARG_DESC_SHEXP)) ? TRU1 : TRUM1; goto jredo; } } jloop_break: if(cad_idx < cadp->cad_no && !(cadp->cad_ent_flags[cad_idx][0] & n_CMD_ARG_DESC_OPTION)) goto jerr; lcap = (struct n_cmd_arg*)-1; jleave: NYD_OU; return (lcap != NULL); jerr: if(n_pstate_err_no == su_ERR_NONE){ n_pstate_err_no = su_ERR_INVAL; if(!(n_pstate & (n_PS_HOOK_MASK | n_PS_ROBOT)) || (n_poption & n_PO_D_V)){ uz i; for(i = 0; (i < cadp->cad_no && !(cadp->cad_ent_flags[i][0] & n_CMD_ARG_DESC_OPTION)); ++i) ; n_err(_("`%s': parsing stopped after %" PRIuZ " arguments " "(need %" PRIuZ "%s)\n" " Input: %.*s\n" " Stopped: %.*s\n"), cadp->cad_name, parsed_args, i, (i == cadp->cad_no ? n_empty : "+"), (int)shin_orig.l, shin_orig.s, (int)shin.l, shin.s); } } lcap = NULL; goto jleave; } FL void * n_cmd_arg_save_to_heap(struct n_cmd_arg_ctx const *cacp){ struct n_cmd_arg *ncap; struct n_cmd_arg_ctx *ncacp; char *buf; struct n_cmd_arg const *cap; uz len, i; NYD2_IN; /* For simplicity, save it all in once chunk, so that it can be thrown away * with a simple n_free() from whoever is concerned */ len = sizeof *cacp; for(cap = cacp->cac_arg; cap != NULL; cap = cap->ca_next){ i = cap->ca_arg.ca_str.l +1; i = Z_ALIGN(i); len += sizeof(*cap) + i; } if(cacp->cac_vput != NULL) len += su_cs_len(cacp->cac_vput) +1; ncacp = n_alloc(len); *ncacp = *cacp; buf = (char*)&ncacp[1]; for(ncap = NULL, cap = cacp->cac_arg; cap != NULL; cap = cap->ca_next){ void *vp; vp = buf; su_DBG( su_mem_set(vp, 0, sizeof *ncap); ) if(ncap == NULL) ncacp->cac_arg = vp; else ncap->ca_next = vp; ncap = vp; ncap->ca_next = NULL; ncap->ca_ent_flags[0] = cap->ca_ent_flags[0]; ncap->ca_ent_flags[1] = cap->ca_ent_flags[1]; ncap->ca_arg_flags = cap->ca_arg_flags; su_mem_copy(ncap->ca_arg.ca_str.s = (char*)&ncap[1], cap->ca_arg.ca_str.s, (ncap->ca_arg.ca_str.l = i = cap->ca_arg.ca_str.l) +1); i = Z_ALIGN(i); buf += sizeof(*ncap) + i; } if(cacp->cac_vput != NULL){ ncacp->cac_vput = buf; su_mem_copy(buf, cacp->cac_vput, su_cs_len(cacp->cac_vput) +1); }else ncacp->cac_vput = NULL; NYD2_OU; return ncacp; } FL struct n_cmd_arg_ctx * n_cmd_arg_restore_from_heap(void *vp){ struct n_cmd_arg *cap, *ncap; struct n_cmd_arg_ctx *cacp, *rv; NYD2_IN; rv = n_autorec_alloc(sizeof *rv); cacp = vp; *rv = *cacp; for(ncap = NULL, cap = cacp->cac_arg; cap != NULL; cap = cap->ca_next){ vp = n_autorec_alloc(sizeof(*ncap) + cap->ca_arg.ca_str.l +1); su_DBG( su_mem_set(vp, 0, sizeof *ncap); ) if(ncap == NULL) rv->cac_arg = vp; else ncap->ca_next = vp; ncap = vp; ncap->ca_next = NULL; ncap->ca_ent_flags[0] = cap->ca_ent_flags[0]; ncap->ca_ent_flags[1] = cap->ca_ent_flags[1]; ncap->ca_arg_flags = cap->ca_arg_flags; su_mem_copy(ncap->ca_arg.ca_str.s = (char*)&ncap[1], cap->ca_arg.ca_str.s, (ncap->ca_arg.ca_str.l = cap->ca_arg.ca_str.l) +1); } if(cacp->cac_vput != NULL) rv->cac_vput = savestr(cacp->cac_vput); NYD2_OU; return rv; } FL int getrawlist(boole wysh, char **res_dat, uz res_size, char const *line, uz linesize){ int res_no; NYD_IN; n_pstate &= ~n_PS_ARGLIST_MASK; if(res_size == 0){ res_no = -1; goto jleave; }else if(UCMP(z, res_size, >, INT_MAX)) res_size = INT_MAX; else --res_size; res_no = 0; if(!wysh){ /* And assuming result won't grow input */ char c2, c, quotec, *cp2, *linebuf; linebuf = n_lofi_alloc(linesize); for(;;){ for(; su_cs_is_blank(*line); ++line) ; if(*line == '\0') break; if(UCMP(z, res_no, >=, res_size)){ n_err(_("Too many input tokens for result storage\n")); res_no = -1; break; } cp2 = linebuf; quotec = '\0'; /* TODO v15: complete switch in order mirror known behaviour */ while((c = *line++) != '\0'){ if(quotec != '\0'){ if(c == quotec){ quotec = '\0'; continue; }else if(c == '\\'){ if((c2 = *line++) == quotec) c = c2; else --line; } }else if(c == '"' || c == '\''){ quotec = c; continue; }else if(c == '\\'){ if((c2 = *line++) != '\0') c = c2; else --line; }else if(su_cs_is_blank(c)) break; *cp2++ = c; } res_dat[res_no++] = savestrbuf(linebuf, P2UZ(cp2 - linebuf)); if(c == '\0') break; } n_lofi_free(linebuf); }else{ /* sh(1) compat mode. Prepare shell token-wise */ struct n_string store; struct str input; void const *cookie; n_string_creat_auto(&store); input.s = n_UNCONST(line); input.l = linesize; cookie = NULL; for(;;){ if(UCMP(z, res_no, >=, res_size)){ n_err(_("Too many input tokens for result storage\n")); res_no = -1; break; } /* C99 */{ enum n_shexp_state shs; shs = n_shexp_parse_token((n_SHEXP_PARSE_LOG | (cookie == NULL ? n_SHEXP_PARSE_TRIM_SPACE : 0) | /* TODO not here in old style n_SHEXP_PARSE_IFS_VAR |*/ n_SHEXP_PARSE_META_SEMICOLON), &store, &input, &cookie); if((shs & n_SHEXP_STATE_META_SEMICOLON) && input.l > 0){ ASSERT(shs & n_SHEXP_STATE_STOP); n_go_input_inject(n_GO_INPUT_INJECT_COMMIT, input.s, input.l); } if(shs & n_SHEXP_STATE_ERR_MASK){ /* Simply ignore Unicode error, just keep the normalized \[Uu] */ if((shs & n_SHEXP_STATE_ERR_MASK) != n_SHEXP_STATE_ERR_UNICODE){ res_no = -1; break; } } if(shs & n_SHEXP_STATE_OUTPUT){ res_dat[res_no++] = n_string_cp(&store); n_string_drop_ownership(&store); } if(shs & n_SHEXP_STATE_STOP) break; } } n_string_gut(&store); } if(res_no >= 0) res_dat[(uz)res_no] = NULL; jleave: NYD_OU; return res_no; } #include "su/code-ou.h" /* s-it-mode */ s-nail-14.9.15/src/mx/cmd-tab.h000066400000000000000000001110351352610246600157570ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ This is included by ./cmd-tab.c and defines the command array. *@ TODO This should be generated by a script from a template. It should *@ TODO sort the entries alphabetically, and create an array that is indexed *@ TODO by first cmd letter with modulo, the found number is used to index a *@ TODO multi-dimensional array with alphasorted entries. It should ensure *@ TODO that (exact!) POSIX abbreviations order first. *@ TODO And the data (and n_cmd_arg_desc) should be placed in special crafted *@ TODO comments in or before the function source, to be collected by script. * * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany. * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: BSD-3-Clause TODO ISC */ /* * Copyright (c) 1980, 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. */ /* Because of the largish TODO, we yet become included twice. * Not indented for that */ #ifndef n_CMD_TAB_H # define n_CMD_TAB_H #ifdef mx_HAVE_KEY_BINDINGS # define a_CTAB_CAD_BIND n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_bind) n_CMD_ARG_DESC_SUBCLASS_DEF(bind, 3, a_ctab_cad_bind){ {n_CMD_ARG_DESC_SHEXP | n_CMD_ARG_DESC_OPTION, n_SHEXP_PARSE_TRIM_IFSSPACE}, /* context */ {n_CMD_ARG_DESC_SHEXP | n_CMD_ARG_DESC_OPTION | n_CMD_ARG_DESC_HONOUR_STOP, n_SHEXP_PARSE_DRYRUN}, /* subcommand / key sequence */ {n_CMD_ARG_DESC_SHEXP | n_CMD_ARG_DESC_OPTION | n_CMD_ARG_DESC_GREEDY | n_CMD_ARG_DESC_GREEDY_JOIN | n_CMD_ARG_DESC_HONOUR_STOP, n_SHEXP_PARSE_IGNORE_EMPTY | n_SHEXP_PARSE_TRIM_IFSSPACE} /* expansion */ }n_CMD_ARG_DESC_SUBCLASS_DEF_END; #else # define a_CTAB_CAD_BIND NULL #endif n_CMD_ARG_DESC_SUBCLASS_DEF(call, 2, a_ctab_cad_call){ {n_CMD_ARG_DESC_SHEXP | n_CMD_ARG_DESC_HONOUR_STOP, n_SHEXP_PARSE_TRIM_IFSSPACE}, /* macro name */ {n_CMD_ARG_DESC_SHEXP | n_CMD_ARG_DESC_OPTION | n_CMD_ARG_DESC_GREEDY | n_CMD_ARG_DESC_HONOUR_STOP, n_SHEXP_PARSE_IFS_VAR | n_SHEXP_PARSE_TRIM_IFSSPACE} /* args */ }n_CMD_ARG_DESC_SUBCLASS_DEF_END; #ifdef mx_HAVE_TLS # define a_CTAB_CAD_CERTSAVE n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_certsave) n_CMD_ARG_DESC_SUBCLASS_DEF(certsave, 1, a_ctab_cad_certsave){ {n_CMD_ARG_DESC_MSGLIST_AND_TARGET | n_CMD_ARG_DESC_GREEDY, n_SHEXP_PARSE_TRIM_IFSSPACE} }n_CMD_ARG_DESC_SUBCLASS_DEF_END; #else # define a_CTAB_CAD_CERTSAVE NULL #endif n_CMD_ARG_DESC_SUBCLASS_DEF(copy, 1, a_ctab_cad_copy){ {n_CMD_ARG_DESC_MSGLIST_AND_TARGET | n_CMD_ARG_DESC_GREEDY, n_SHEXP_PARSE_TRIM_IFSSPACE} }n_CMD_ARG_DESC_SUBCLASS_DEF_END; n_CMD_ARG_DESC_SUBCLASS_DEF(Copy, 1, a_ctab_cad_Copy){ {n_CMD_ARG_DESC_MSGLIST | n_CMD_ARG_DESC_GREEDY, n_SHEXP_PARSE_TRIM_IFSSPACE} }n_CMD_ARG_DESC_SUBCLASS_DEF_END; n_CMD_ARG_DESC_SUBCLASS_DEF(decrypt, 1, a_ctab_cad_decrypt){ {n_CMD_ARG_DESC_MSGLIST_AND_TARGET | n_CMD_ARG_DESC_GREEDY, n_SHEXP_PARSE_TRIM_IFSSPACE} }n_CMD_ARG_DESC_SUBCLASS_DEF_END; n_CMD_ARG_DESC_SUBCLASS_DEF(Decrypt, 1, a_ctab_cad_Decrypt){ {n_CMD_ARG_DESC_MSGLIST | n_CMD_ARG_DESC_GREEDY, n_SHEXP_PARSE_TRIM_IFSSPACE} }n_CMD_ARG_DESC_SUBCLASS_DEF_END; n_CMD_ARG_DESC_SUBCLASS_DEF(digmsg, 3, a_ctab_cad_digmsg){ /* XXX 2 OR 3 */ {n_CMD_ARG_DESC_SHEXP | n_CMD_ARG_DESC_HONOUR_STOP, n_SHEXP_PARSE_TRIM_IFSSPACE}, /* subcommand (/ msgno/-) */ {n_CMD_ARG_DESC_SHEXP | n_CMD_ARG_DESC_HONOUR_STOP, n_SHEXP_PARSE_TRIM_IFSSPACE}, /* msgno/- (/ first part of user cmd) */ {n_CMD_ARG_DESC_SHEXP | n_CMD_ARG_DESC_OPTION | n_CMD_ARG_DESC_GREEDY | n_CMD_ARG_DESC_GREEDY_JOIN | n_CMD_ARG_DESC_HONOUR_STOP, n_SHEXP_PARSE_IGNORE_EMPTY | n_SHEXP_PARSE_TRIM_IFSSPACE} /* args */ }n_CMD_ARG_DESC_SUBCLASS_DEF_END; n_CMD_ARG_DESC_SUBCLASS_DEF(forward, 1, a_ctab_cad_forward){ {n_CMD_ARG_DESC_MSGLIST_AND_TARGET | n_CMD_ARG_DESC_GREEDY | n_CMD_ARG_DESC_MSGLIST_NEEDS_SINGLE, n_SHEXP_PARSE_TRIM_IFSSPACE} }n_CMD_ARG_DESC_SUBCLASS_DEF_END; n_CMD_ARG_DESC_SUBCLASS_DEF(Forward, 1, a_ctab_cad_Forward){ {n_CMD_ARG_DESC_MSGLIST | n_CMD_ARG_DESC_GREEDY | n_CMD_ARG_DESC_MSGLIST_NEEDS_SINGLE, n_SHEXP_PARSE_TRIM_IFSSPACE} }n_CMD_ARG_DESC_SUBCLASS_DEF_END; n_CMD_ARG_DESC_SUBCLASS_DEF(move, 1, a_ctab_cad_move){ {n_CMD_ARG_DESC_MSGLIST_AND_TARGET | n_CMD_ARG_DESC_GREEDY, n_SHEXP_PARSE_TRIM_IFSSPACE} }n_CMD_ARG_DESC_SUBCLASS_DEF_END; n_CMD_ARG_DESC_SUBCLASS_DEF(Move, 1, a_ctab_cad_Move){ {n_CMD_ARG_DESC_MSGLIST | n_CMD_ARG_DESC_GREEDY, n_SHEXP_PARSE_TRIM_IFSSPACE} }n_CMD_ARG_DESC_SUBCLASS_DEF_END; n_CMD_ARG_DESC_SUBCLASS_DEF(pdot, 1, a_ctab_cad_pdot){ {n_CMD_ARG_DESC_MSGLIST | n_CMD_ARG_DESC_GREEDY, n_SHEXP_PARSE_TRIM_IFSSPACE} }n_CMD_ARG_DESC_SUBCLASS_DEF_END; n_CMD_ARG_DESC_SUBCLASS_DEF(pipe, 1, a_ctab_cad_pipe){ {n_CMD_ARG_DESC_MSGLIST_AND_TARGET | n_CMD_ARG_DESC_GREEDY, n_SHEXP_PARSE_TRIM_IFSSPACE} }n_CMD_ARG_DESC_SUBCLASS_DEF_END; n_CMD_ARG_DESC_SUBCLASS_DEF(readctl, 2, a_ctab_cad_readctl){ {n_CMD_ARG_DESC_SHEXP | n_CMD_ARG_DESC_OPTION | n_CMD_ARG_DESC_HONOUR_STOP, n_SHEXP_PARSE_TRIM_IFSSPACE}, /* subcommand */ {n_CMD_ARG_DESC_SHEXP | n_CMD_ARG_DESC_OPTION | n_CMD_ARG_DESC_GREEDY | n_CMD_ARG_DESC_GREEDY_JOIN | n_CMD_ARG_DESC_HONOUR_STOP, n_SHEXP_PARSE_IGNORE_EMPTY | n_SHEXP_PARSE_TRIM_IFSSPACE} /* var names */ }n_CMD_ARG_DESC_SUBCLASS_DEF_END; n_CMD_ARG_DESC_SUBCLASS_DEF(resend, 1, a_ctab_cad_resend){ {n_CMD_ARG_DESC_MSGLIST_AND_TARGET | n_CMD_ARG_DESC_GREEDY, n_SHEXP_PARSE_TRIM_IFSSPACE} }n_CMD_ARG_DESC_SUBCLASS_DEF_END; n_CMD_ARG_DESC_SUBCLASS_DEF(Resend, 1, a_ctab_cad_Resend){ {n_CMD_ARG_DESC_MSGLIST_AND_TARGET | n_CMD_ARG_DESC_GREEDY, n_SHEXP_PARSE_TRIM_IFSSPACE} }n_CMD_ARG_DESC_SUBCLASS_DEF_END; n_CMD_ARG_DESC_SUBCLASS_DEF(save, 1, a_ctab_cad_save){ {n_CMD_ARG_DESC_MSGLIST_AND_TARGET | n_CMD_ARG_DESC_GREEDY, n_SHEXP_PARSE_TRIM_IFSSPACE} }n_CMD_ARG_DESC_SUBCLASS_DEF_END; n_CMD_ARG_DESC_SUBCLASS_DEF(Save, 1, a_ctab_cad_Save){ {n_CMD_ARG_DESC_MSGLIST | n_CMD_ARG_DESC_GREEDY, n_SHEXP_PARSE_TRIM_IFSSPACE} }n_CMD_ARG_DESC_SUBCLASS_DEF_END; #ifdef mx_HAVE_KEY_BINDINGS # define a_CTAB_CAD_UNBIND n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_unbind) n_CMD_ARG_DESC_SUBCLASS_DEF(unbind, 2, a_ctab_cad_unbind){ {n_CMD_ARG_DESC_SHEXP, n_SHEXP_PARSE_TRIM_IFSSPACE}, /* context */ /* key sequence or * */ {n_CMD_ARG_DESC_SHEXP | n_CMD_ARG_DESC_HONOUR_STOP, n_SHEXP_PARSE_DRYRUN} }n_CMD_ARG_DESC_SUBCLASS_DEF_END; #else # define a_CTAB_CAD_UNBIND NULL #endif n_CMD_ARG_DESC_SUBCLASS_DEF(vpospar, 2, a_ctab_cad_vpospar){ {n_CMD_ARG_DESC_SHEXP | n_CMD_ARG_DESC_HONOUR_STOP, n_SHEXP_PARSE_TRIM_IFSSPACE}, /* subcommand */ {n_CMD_ARG_DESC_SHEXP | n_CMD_ARG_DESC_OPTION | n_CMD_ARG_DESC_GREEDY | n_CMD_ARG_DESC_HONOUR_STOP, n_SHEXP_PARSE_IFS_VAR | n_SHEXP_PARSE_TRIM_IFSSPACE} /* args */ }n_CMD_ARG_DESC_SUBCLASS_DEF_END; n_CMD_ARG_DESC_SUBCLASS_DEF(write, 1, a_ctab_cad_write){ {n_CMD_ARG_DESC_MSGLIST_AND_TARGET | n_CMD_ARG_DESC_GREEDY, n_SHEXP_PARSE_TRIM_IFSSPACE} }n_CMD_ARG_DESC_SUBCLASS_DEF_END; #else /* ifndef n_CMD_TAB_H */ #ifdef mx_HAVE_DOCSTRINGS # define DS(S) , S #else # define DS(S) #endif #undef MAC #define MAC (n_MAXARGC - 1) /* Some shorter aliases to be able to define a command in two lines */ #define TMSGLST n_CMD_ARG_TYPE_MSGLIST #define TNDMLST n_CMD_ARG_TYPE_NDMLIST #define TRAWDAT n_CMD_ARG_TYPE_RAWDAT # define TSTRING n_CMD_ARG_TYPE_STRING #define TWYSH n_CMD_ARG_TYPE_WYSH # define TRAWLST n_CMD_ARG_TYPE_RAWLIST # define TWYRA n_CMD_ARG_TYPE_WYRA #define TARG n_CMD_ARG_TYPE_ARG #define A n_CMD_ARG_A #define F n_CMD_ARG_F #define G n_CMD_ARG_G #define H n_CMD_ARG_H #define I n_CMD_ARG_I #define L n_CMD_ARG_L #define M n_CMD_ARG_M #define O n_CMD_ARG_O #define P n_CMD_ARG_P #undef R #define R n_CMD_ARG_R #define SC n_CMD_ARG_SC #undef S #define S n_CMD_ARG_S #define T n_CMD_ARG_T #define V n_CMD_ARG_V #define W n_CMD_ARG_W #define X n_CMD_ARG_X #define EM n_CMD_ARG_EM /* Note: the first command in here may NOT expand to an unsupported one! * It is used for n_cmd_default() */ { "next", &c_next, (A | TNDMLST), 0, MMNDEL, NULL DS(N_("Goes to the next message (-list) and prints it")) }, { "alias", &c_alias, (M | TWYSH), 0, MAC, NULL DS(N_("Show all (or ), or append to ::")) }, { "print", &c_type, (A | TMSGLST), 0, MMNDEL, NULL DS(N_("Type all messages of , honouring `ignore' / `retain'")) }, { "type", &c_type, (A | TMSGLST), 0, MMNDEL, NULL DS(N_("Type all messages of , honouring `ignore' / `retain'")) }, { "Type", &c_Type, (A | TMSGLST), 0, MMNDEL, NULL DS(N_("Like `type', but bypass `ignore' / `retain'")) }, { "Print", &c_Type, (A | TMSGLST), 0, MMNDEL, NULL DS(N_("Like `print', but bypass `ignore' / `retain'")) }, { "visual", &c_visual, (A | I | S | TMSGLST), 0, MMNORM, NULL DS(N_("Edit ")) }, { "top", &c_top, (A | TMSGLST), 0, MMNDEL, NULL DS(N_("Type first *toplines* of all messages in ")) }, { "Top", &c_Top, (A | TMSGLST), 0, MMNDEL, NULL DS(N_("Like `top', but bypass `ignore' / `retain'")) }, { "touch", &c_stouch, (A | W | TMSGLST), 0, MMNDEL, NULL DS(N_("Mark for saving in *mbox*")) }, { "preserve", &c_preserve, (A | SC | W | TMSGLST), 0, MMNDEL, NULL DS(N_("Save in system mailbox instead of *MBOX*")) }, { "delete", &c_delete, (A | W | P | TMSGLST), 0, MMNDEL, NULL DS(N_("Delete ")) }, { "dp", &c_deltype, (A | W | TMSGLST), 0, MMNDEL, NULL DS(N_("Delete the current message, then type the next")) }, { "dt", &c_deltype, (A | W | TMSGLST), 0, MMNDEL, NULL DS(N_("Delete the current message, then type the next")) }, { "undelete", &c_undelete, (A | P | TMSGLST), MDELETED, MMNDEL, NULL DS(N_("Un`delete' ")) }, { "mail", &c_sendmail, (I | M | R | SC | EM | TSTRING), 0, 0, NULL DS(N_("Compose mail; recipients may be given as arguments")) }, { "Mail", &c_Sendmail, (I | M | R | SC | EM | TSTRING), 0, 0, NULL DS(N_("Like `mail', but derive filename from first recipient")) }, { "mbox", &c_mboxit, (A | W | TMSGLST), 0, MMNDEL, NULL DS(N_("Indicate that is to be stored in *MBOX*")) }, { "more", &c_more, (A | TMSGLST), 0, MMNDEL, NULL DS(N_("Invoke the pager on the given messages")) }, { "page", &c_more, (A | TMSGLST), 0, MMNDEL, NULL DS(N_("Invoke the pager on the given messages")) }, { "More", &c_More, (A | TMSGLST), 0, MMNDEL, NULL DS(N_("Invoke the pager on the given messages")) }, { "Page", &c_More, (A | TMSGLST), 0, MMNDEL, NULL DS(N_("Invoke the pager on the given messages")) }, { "unread", &c_unread, (A | TMSGLST), 0, MMNDEL, NULL DS(N_("Mark as not being read")) }, { "Unread", &c_unread, (A | TMSGLST), 0, MMNDEL, NULL DS(N_("Mark as not being read")) }, { "new", &c_unread, (A | TMSGLST), 0, MMNDEL, NULL DS(N_("Mark as not being read")) }, { "New", &c_unread, (A | TMSGLST), 0, MMNDEL, NULL DS(N_("Mark as not being read")) }, { n_em, &c_shell, (M | SC | V | X | EM | TRAWDAT), 0, 0, NULL DS(N_("Execute ")) }, { "copy", &c_copy, (A | M | TARG), 0, 0, n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_copy) DS(N_("Copy [], but do not mark them for deletion")) }, { "Copy", &c_Copy, (A | M | SC | TARG), 0, 0, n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_Copy) DS(N_("Like `copy', but derive filename from first sender")) }, { "chdir", &c_chdir, (M | TWYRA), 0, 1, NULL DS(N_("Change CWD to the specified/the login directory")) }, { "cd", &c_chdir, (M | X | TWYRA), 0, 1, NULL DS(N_("Change working directory to the specified/the login directory")) }, { "save", &c_save, (A | TARG), 0, 0, n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_save) DS(N_("Append [] to ")) }, { "Save", &c_Save, (A | SC | TARG), 0, 0, n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_Save) DS(N_("Like `save', but derive filename from first sender")) }, { "set", &c_set, (G | L | M | X | TWYRA), 0, MAC, NULL DS(N_("Print all variables, or set (a) (s)")) }, { "unalias", &c_unalias, (M | TWYSH), 1, MAC, NULL DS(N_("Un`alias' (* for all)")) }, { "write", &c_write, (A | TARG), 0, 0, n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_write) DS(N_("Write (append) [] to []")) }, { "from", &c_from, (A | TMSGLST), 0, MMNORM, NULL DS(N_("Type (matching) headers of (a search specification)")) }, { "search", &c_from, (A | TMSGLST), 0, MMNORM, NULL DS(N_("Search for , type matching headers")) }, { "file", &c_file, (M | T | TWYRA), 0, 1, NULL DS(N_("Open a new or show the current one")) }, { "followup", &c_followup, (A | I | R | SC | TMSGLST), 0, MMNDEL, NULL DS(N_("Like `reply', but derive filename from first sender")) }, { "followupall", &c_followupall, (A | I | R | SC | TMSGLST), 0, MMNDEL, NULL DS(N_("Like `reply', but derive filename from first sender")) }, { "followupsender", &c_followupsender, (A | I | R | SC | TMSGLST), 0, MMNDEL, NULL DS(N_("Like `Followup', but always reply to the sender only")) }, { "folder", &c_file, (M | T | TWYRA), 0, 1, NULL DS(N_("Open a new or show the current one")) }, { "folders", &c_folders, (M | T | TWYRA), 0, 1, NULL DS(N_("List mailboxes below the given or the global folder")) }, { "headers", &c_headers, (A | M | TMSGLST), 0, MMNDEL, NULL DS(N_("Type a page of headers (with the first of if given)")) }, { "help", &a_ctab_c_help, (G | M | X | TWYSH), 0, 1, NULL DS(N_("Show help [[Option] for the given command]]")) }, { n_qm, &a_ctab_c_help, (G | M | X | TWYSH), 0, 1, NULL DS(N_("Show help [[Option] for the given command]]")) }, { "Reply", &c_Reply, (A | I | R | SC | EM | TMSGLST), 0, MMNDEL, NULL DS(N_("Reply to originator, exclusively")) }, { "Respond", &c_Reply, (A | I | R | SC | EM | TMSGLST), 0, MMNDEL, NULL DS(N_("Reply to originator, exclusively")) }, { "Followup", &c_Followup, (A | I | R | SC | EM | TMSGLST), 0, MMNDEL, NULL DS(N_("Like `Reply', but derive filename from first sender")) }, { "reply", &c_reply, (A | I | R | SC | EM | TMSGLST), 0, MMNDEL, NULL DS(N_("Reply to originator and recipients of ")) }, { "replyall", &c_replyall, (A | I | R | SC | EM | TMSGLST), 0, MMNDEL, NULL DS(N_("Reply to originator and recipients of ")) }, { "replysender", &c_replysender, (A | I | R | SC | EM | TMSGLST), 0, MMNDEL, NULL DS(N_("Reply to originator, exclusively")) }, { "respond", &c_reply, (A | I | R | SC | EM | TMSGLST), 0, MMNDEL, NULL DS(N_("Reply to originators and recipients of ")) }, { "respondall", &c_replyall, (A | I | R | SC | EM | TMSGLST), 0, MMNDEL, NULL DS(N_("Reply to originators and recipients of ")) }, { "respondsender", &c_replysender, (A | I | R | SC | EM | TMSGLST), 0, MMNDEL, NULL DS(N_("Reply to originator, exclusively")) }, { "resend", &c_resend, (A | R | SC | EM | TARG), 0, MMNDEL, n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_resend) DS(N_("Resend to , add Resent-* header lines")) }, { "Resend", &c_Resend, (A | R | SC | EM | TARG), 0, MMNDEL, n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_Resend) DS(N_("Like `resend', but do not add Resent-* header lines")) }, { "forward", &c_forward, (A | R | SC | EM | TARG), 0, MMNDEL, n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_forward) DS(N_("Forward to
")) }, { "Forward", &c_Forward, (A | R | SC | EM | TARG), 0, MMNDEL, n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_Forward) DS(N_("Like `forward', but derive filename from
")) }, { "edit", &c_editor, (G | A | I | S | TMSGLST), 0, MMNORM, NULL DS(N_("Edit ")) }, { "pipe", &c_pipe, (A | TARG), 0, MMNDEL, n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_pipe) DS(N_("Pipe [] to [], honour `ignore' / `retain'")) }, { "|", &c_pipe, (A | TARG), 0, MMNDEL, n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_pipe) DS(N_("Pipe [] to [], honour `ignore' / `retain'")) }, { "Pipe", &c_Pipe, (A | TARG), 0, MMNDEL, n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_pipe) DS(N_("Like `pipe', but do not honour `ignore' / `retain'")) }, { "size", &c_messize, (A | TMSGLST), 0, MMNDEL, NULL DS(N_("Show size in bytes for ")) }, { "hold", &c_preserve, (A | SC | W | TMSGLST), 0, MMNDEL, NULL DS(N_("Save in system mailbox instead of *MBOX*")) }, { "if", &c_if, (G | F | M | X | TWYRA), 1, MAC, NULL DS(N_("Part of the if/elif/else/endif statement")) }, { "else", &c_else, (G | F | M | X | TWYSH), 0, 0, NULL DS(N_("Part of the if/elif/else/endif statement")) }, { "elif", &c_elif, (G | F | M | X | TWYRA), 1, MAC, NULL DS(N_("Part of the if/elif/else/endif statement")) }, { "endif", &c_endif, (G | F | M | X | TWYSH), 0, 0, NULL DS(N_("Part of the if/elif/else/endif statement")) }, { "alternates", &c_alternates, (M | V | TWYSH), 0, MAC, NULL DS(N_("Show or define alternate for the invoking user")) }, { "unalternates", &c_unalternates, (M | TWYSH), 1, MAC, NULL DS(N_("Delete alternate (* for all)")) }, { "ignore", &c_ignore, (M | TWYRA), 0, MAC, NULL DS(N_("Add to the ignored LIST, or show that list")) }, { "discard", &c_ignore, (M | TWYRA), 0, MAC, NULL DS(N_("Add to the ignored LIST, or show that list")) }, { "retain", &c_retain, (M | TWYRA), 0, MAC, NULL DS(N_("Add to retained list, or show that list")) }, { "saveignore", &c_saveignore, (O | M | TRAWLST), 0, MAC, NULL DS(N_("Obsoleted by `headerpick'")) }, { "savediscard", &c_saveignore, (O | M | TRAWLST), 0, MAC, NULL DS(N_("Obsoleted by `headerpick'")) }, { "saveretain", &c_saveretain, (O | M | TRAWLST), 0, MAC, NULL DS(N_("Obsoleted by `headerpick'")) }, { "unignore", &c_unignore, (M | TWYRA), 0, MAC, NULL DS(N_("Un`ignore' ")) }, { "unretain", &c_unretain, (M | TWYRA), 0, MAC, NULL DS(N_("Un`retain' ")) }, { "unsaveignore", &c_unsaveignore, (O | M | TRAWLST), 0, MAC, NULL DS(N_("Obsoleted by `unheaderpick'")) }, { "unsaveretain", &c_unsaveretain, (O | M | TRAWLST), 0, MAC, NULL DS(N_("Obsoleted by `unheaderpick'")) }, { "newmail", &c_newmail, (A | T | TWYSH), 0, 0, NULL DS(N_("Check for new mail in current folder")) }, { "shortcut", &c_shortcut, (M | TWYSH), 0, MAC, NULL DS(N_("Define [: plus :], or list shortcuts")) }, { "unshortcut", &c_unshortcut, (M | TWYSH), 1, MAC, NULL DS(N_("Delete (* for all)")) }, { "thread", &c_thread, (O | A | TMSGLST), 0, 0, NULL DS(N_("Obsoleted by `sort' \"thread\"")) }, { "unthread", &c_unthread, (O | A | TMSGLST), 0, 0, NULL DS(N_("Obsolete (use `unsort')")) }, { "sort", &c_sort, (A | TWYSH), 0, 1, NULL DS(N_("Change sorting to: date,from,size,spam,status,subject,thread,to"))}, { "unsort", &c_unthread, (A | TMSGLST), 0, 0, NULL DS(N_("Disable sorted or threaded mode")) }, { "flag", &c_flag, (A | M | TMSGLST), 0, 0, NULL DS(N_("(Un)Flag (for special attention)")) }, { "unflag", &c_unflag, (A | M | TMSGLST), 0, 0, NULL DS(N_("(Un)Flag (for special attention)")) }, { "answered", &c_answered, (A | M | TMSGLST), 0, 0, NULL DS(N_("Mark the given as answered")) }, { "unanswered", &c_unanswered, (A | M | TMSGLST), 0, 0, NULL DS(N_("Un`answered' ")) }, { "draft", &c_draft, (A | M | TMSGLST), 0, 0, NULL DS(N_("Mark as draft")) }, { "undraft", &c_undraft, (A | M | TMSGLST), 0, 0, NULL DS(N_("Un`draft' ")) }, { "move", &c_move, (A | M | TARG), 0, 0, n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_move) DS(N_("Like `copy', but mark messages for deletion")) }, { "Move", &c_Move, (A | M | SC | TARG), 0, 0, n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_Move) DS(N_("Like `move', but derive filename from first sender")) }, { "mv", &c_move, (O | A | M | TARG), 0, 0, n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_move) DS(N_("Like `copy', but mark messages for deletion")) }, { "Mv", &c_Move, (O | A | M | SC | TARG), 0, 0, n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_Move) DS(N_("Like `move', but derive filename from first sender")) }, { "noop", &c_noop, (A | M | TWYSH), 0, 0, NULL DS(N_("NOOP command if current `file' is accessed via network")) }, { "collapse", &c_collapse, (A | TMSGLST), 0, 0, NULL DS(N_("Collapse thread views for ")) }, { "uncollapse", &c_uncollapse, (A | TMSGLST), 0, 0, NULL DS(N_("Uncollapse if in threaded view")) }, { "verify", #ifdef mx_HAVE_XTLS &c_verify, #else NULL, #endif (A | TMSGLST), 0, 0, NULL DS(N_("Verify ")) }, { "decrypt", &c_decrypt, (A | M | TARG), 0, 0, n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_decrypt) DS(N_("Like `copy', but decrypt first, if encrypted")) }, { "Decrypt", &c_Decrypt, (A | M | SC | TARG), 0, 0, n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_Decrypt) DS(N_("Like `decrypt', but derive filename from first sender")) }, { "certsave", #ifdef mx_HAVE_TLS &c_certsave, #else NULL, #endif (A | TARG), 0, MMNDEL, a_CTAB_CAD_CERTSAVE DS(N_("Save S/MIME certificates of [] to ")) }, { "rename", &c_rename, (M | TWYRA), 0, 2, NULL DS(N_("Rename to ")) }, { "remove", &c_remove, (M | TWYRA), 0, MAC, NULL DS(N_("Remove the named folders")) }, { "show", &c_show, (A | TMSGLST), 0, MMNDEL, NULL DS(N_("Like `type', but show raw message content of ")) }, { "Show", &c_show, (A | TMSGLST), 0, MMNDEL, NULL DS(N_("Like `Type', but show raw message content of ")) }, { "mimeview", &c_mimeview, (A | I | EM | TMSGLST), 0, MMNDEL, NULL DS(N_("Show non-text parts of the given ")) }, { "seen", &c_seen, (A | M | TMSGLST), 0, MMNDEL, NULL DS(N_("Mark as seen")) }, { "Seen", &c_seen, (A | M | TMSGLST), 0, MMNDEL, NULL DS(N_("Mark as seen")) }, { "fwdignore", &c_fwdignore, (O | M | TRAWLST), 0, MAC, NULL DS(N_("Obsoleted by `headerpick'")) }, { "fwddiscard", &c_fwdignore, (O | M | TRAWLST), 0, MAC, NULL DS(N_("Obsoleted by `headerpick'")) }, { "fwdretain", &c_fwdretain, (O | M | TRAWLST), 0, MAC, NULL DS(N_("Obsoleted by `headerpick'")) }, { "unfwdignore", &c_unfwdignore, (O | M | TRAWLST), 0, MAC, NULL DS(N_("Obsoleted by `unheaderpick'")) }, { "unfwdretain", &c_unfwdretain, (O | M | TRAWLST), 0, MAC, NULL DS(N_("Obsoleted by `unheaderpick'")) }, { "mimetype", &c_mimetype, (M | TWYSH), 0, MAC, NULL DS(N_("(Load and) show all known MIME types, or define some")) }, { "unmimetype", &c_unmimetype, (M | TWYSH), 1, MAC, NULL DS(N_("Delete s (reset, * for all; former reinitializes)")) }, { "spamclear", #ifdef mx_HAVE_SPAM &c_spam_clear, #else NULL, #endif (A | M | TMSGLST), 0, MMNDEL, NULL DS(N_("Clear the spam flag for each message in ")) }, { "spamset", #ifdef mx_HAVE_SPAM &c_spam_set, #else NULL, #endif (A | M | TMSGLST), 0, MMNDEL, NULL DS(N_("Set the spam flag for each message in ")) }, { "spamforget", #ifdef mx_HAVE_SPAM &c_spam_forget, #else NULL, #endif (A | M | TMSGLST), 0, MMNDEL, NULL DS(N_("Force the spam detector to unlearn ")) }, { "spamham", #ifdef mx_HAVE_SPAM &c_spam_ham, #else NULL, #endif (A | M | TMSGLST), 0, MMNDEL, NULL DS(N_("Teach the spam detector that is ham")) }, { "spamrate", #ifdef mx_HAVE_SPAM &c_spam_rate, #else NULL, #endif (A | M | TMSGLST), 0, MMNDEL, NULL DS(N_("Rate via the spam detector")) }, { "spamspam", #ifdef mx_HAVE_SPAM &c_spam_spam, #else NULL, #endif (A | M | TMSGLST), 0, MMNDEL, NULL DS(N_("Teach the spam detector that is spam")) }, { "File", &c_File, (M | T | TWYRA), 0, 1, NULL DS(N_("Open a new mailbox readonly, or show the current mailbox")) }, { "Folder", &c_File, (M | T | TWYRA), 0, 1, NULL DS(N_("Open a new mailbox readonly, or show the current mailbox")) }, { "mlist", &c_mlist, (M | TWYSH), 0, MAC, NULL DS(N_("Show all known mailing lists or define some")) }, { "unmlist", &c_unmlist, (M | TWYSH), 1, MAC, NULL DS(N_("Un`mlist' (* for all)")) }, { "mlsubscribe", &c_mlsubscribe, (M | TWYSH), 0, MAC, NULL DS(N_("Show all mailing list subscriptions or define some")) }, { "unmlsubscribe", &c_unmlsubscribe, (M | TWYSH), 1, MAC, NULL DS(N_("Un`mlsubscribe' (* for all)"))}, { "Lreply", &c_Lreply, (A | I | R | SC | EM | TMSGLST), 0, MMNDEL, NULL DS(N_("Mailing-list reply to the given ")) }, { "dotmove", &c_dotmove, (A | TSTRING), 1, 1, NULL DS(N_("Move the dot up <-> or down <+> by one")) }, /* New-style commands without un* counterpart */ { "=", &c_pdot, (A | G | V | X | EM | TARG), 0, MMNDEL, n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_pdot) DS(N_("Show message number of [] (or the \"dot\")")) }, { "call", &c_call, (M | X | EM | TARG), 0, 0, n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_call) DS(N_("Call macro [::]")) }, { "call_if", &c_call_if, (M | X | EM | TARG), 0, 0, n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_call) DS(N_("Call macro like `call', but be silent if non-existent")) }, { "cwd", &c_cwd, (M | V | X | TWYSH), 0, 0, NULL DS(N_("Print current working directory (CWD)")) }, { "digmsg", &c_digmsg, (G | M | X | EM | TARG), 0, 0, n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_digmsg) DS(N_(" <-|msgno> | <-|msgno> : " "message dig object control"))}, { "echo", &c_echo, (G | M | V | X | EM | TWYSH), 0, MAC, NULL DS(N_("Echo arguments, and a trailing newline, to standard output")) }, { "echoerr", &c_echoerr, (G | M | V | X | EM | TWYSH), 0, MAC, NULL DS(N_("Echo arguments, and a trailing newline, to standard error")) }, { "echon", &c_echon, (G | M | V | X | EM | TWYSH), 0, MAC, NULL DS(N_("Echo arguments, without a trailing newline, to standard output")) }, { "echoerrn", &c_echoerrn, (G | M | V| X | EM | TWYSH), 0, MAC, NULL DS(N_("Echo arguments, without a trailing newline, to standard error")) }, { "environ", &c_environ, (G | M | X | TWYSH), 2, MAC, NULL DS(N_(" (an) environment (s)")) }, { "errors", #ifdef mx_HAVE_ERRORS &c_errors, #else NULL, #endif (H | I | M | TWYSH), 0, 1, NULL DS(N_("Either [] or the error message ring")) }, { "eval", &c_eval, (G | M | X | EM | TWYSH), 1, MAC, NULL DS(N_("Construct command from ::, reuse its $? and $!")) }, { "exit", &c_exit, (M | X | TWYSH), 0, 1, NULL DS(N_("Immediately return [] to the shell without saving")) }, { "history", #ifdef mx_HAVE_HISTORY &c_history, #else NULL, #endif (H | I | M | TWYSH), 0, 1, NULL DS(N_(" or select history ")) }, { "list", &a_ctab_c_list, (M | TWYSH), 0, 1, NULL DS(N_("List all commands (with argument: in prefix search order)")) }, { "localopts", &c_localopts, (H | M | X | TWYSH), 1, 2, NULL DS(N_("Localize variable modifications? [] "))}, { "netrc", #ifdef mx_HAVE_NETRC &c_netrc, #else NULL, #endif (M | TWYSH), 0, 1, NULL DS(N_("[], or the .netrc cache")) }, { "quit", &c_quit, TWYSH, 0, 1, NULL DS(N_("Exit session with [], saving messages as necessary")) }, { "read", &c_read, (G | M | X | EM | TWYSH), 1, MAC, NULL DS(N_("Read a line from standard input into (s)")) }, { "readall", &c_readall, (G | M | X | EM | TWYSH), 1, 1, NULL DS(N_("Read anything from standard input until EOF into ")) }, { "readctl", &c_readctl, (G | M | X | EM | TARG), 0, 0, n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_readctl) DS(N_("[] or read channels"))}, { "return", &c_return, (M | X | EM | TWYSH), 0, 2, NULL DS(N_("Return control [with []] from macro"))}, { "shell", &c_dosh, (I | S | EM | TWYSH), 0, 0, NULL DS(N_("Invoke an interactive shell")) }, { "shift", &c_shift, (M | X | TWYSH), 0, 1, NULL DS(N_("In a `call'ed macro, shift positional parameters")) }, { "sleep", &c_sleep, (H | M | X | EM | TWYSH), 1, 3, NULL DS(N_("Sleep for []"))}, { "source", &c_source, (M | TWYSH), 1, 1, NULL DS(N_("Read commands from ")) }, { "source_if", &c_source_if, (M | TWYSH), 1, 1, NULL DS(N_("If can be opened successfully, read commands from it")) }, { "unset", &c_unset, (G | L | M | X | TWYSH), 1, MAC, NULL DS(N_("Unset ")) }, { "varshow", &c_varshow, (G | M | X | TWYSH), 1, MAC, NULL DS(N_("Show some information about the given ")) }, { "varedit", &c_varedit, (O | G | I | M | S | TWYSH), 1, MAC, NULL DS(N_("Edit the value(s) of (an) variable(s), or create them")) }, { "vpospar", &c_vpospar, (G | M | V | X | EM | TARG), 0, 0, n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_vpospar) DS(N_("Positional parameters: , , or from ::"))}, { "version", &c_version, (H | M | V | X | TWYSH), 0, 0, NULL DS(N_("Show the version and feature set of the program")) }, { "csop", #ifdef mx_HAVE_CMD_CSOP &c_csop, #else NIL, #endif (G | M | V | X | EM | TWYSH), 2, MAC, NULL DS(N_("C-style byte string s on given ::")) }, { "vexpr", #ifdef mx_HAVE_CMD_VEXPR &c_vexpr, #else NIL, #endif (G | M | V | X | EM | TWYSH), 2, MAC, NULL DS(N_("Evaluate according to any ::")) }, { "xit"/*POSIX, first!*/, &c_exit, (M | X | TWYSH), 0, 1, NULL DS(N_("Immediately return [] to the shell without saving")) }, { "xcall", &c_xcall, (M | X | EM | TARG), 0, 0, n_CMD_ARG_DESC_SUBCLASS_CAST(&a_ctab_cad_call) DS(N_("Replace currently executing macro with macro [::]")) }, { "z", &c_scroll, (A | M | TWYSH), 0, 1, NULL DS(N_("Scroll header display as indicated by the argument (0,-,+,$)")) }, { "Z", &c_Scroll, (A | M | TWYSH), 0, 1, NULL DS(N_("Like `z', but continues to the next flagged message")) }, /* New-style commands with un* counterpart */ { "account", &c_account, (M | TWYSH), 0, MAC, NULL DS(N_("Create or select , or list all accounts")) }, { "unaccount", &c_unaccount, (M | TWYSH), 1, MAC, NULL DS(N_("Delete all given (* for all)")) }, { "bind", #ifdef mx_HAVE_KEY_BINDINGS &c_bind, #else NULL, #endif (M | TARG), 0, MAC, a_CTAB_CAD_BIND DS(N_("For [ (base)], [] " "or bind [::]"))}, { "unbind", #ifdef mx_HAVE_KEY_BINDINGS &c_unbind, #else NULL, #endif (M | TARG), 2, 2, a_CTAB_CAD_UNBIND DS(N_("Un`bind' (* for all)")) }, { "charsetalias", &c_charsetalias, (M | TWYSH), 0, MAC, NULL DS(N_("Define [: :]s, or list mappings")) }, { "uncharsetalias", &c_uncharsetalias, (M | TWYSH), 1, MAC, NULL DS(N_("Delete (* for all)")) }, { "colour", #ifdef mx_HAVE_COLOUR &c_colour, #else NULL, #endif (M | TWYSH), 1, 4, NULL DS(N_("Show colour settings of (1,8,256,all/*) or define one")) }, { "uncolour", #ifdef mx_HAVE_COLOUR &c_uncolour, #else NULL, #endif (M | TWYSH), 2, 3, NULL DS(N_("Un`colour' (* for all) []")) }, { "commandalias", &c_commandalias, (M | X | TWYSH), 0, MAC, NULL DS(N_("Print/create command [], or list all aliases")) }, { "uncommandalias", &c_uncommandalias, (M | X | TWYSH), 1, MAC, NULL DS(N_("Delete (* for all)")) }, { "ghost", &c_commandalias, (O | M | X | TWYRA), 0, MAC, NULL DS(N_("Obsoleted by `commandalias'")) }, { "unghost", &c_uncommandalias, (O | M | X | TWYRA), 1, MAC, NULL DS(N_("Obsoleted by `uncommandalias'")) }, { "define", &c_define, (M | X | TWYSH), 0, 2, NULL DS(N_("Define a or show the currently defined ones")) }, { "undefine", &c_undefine, (M | X | TWYSH), 1, MAC, NULL DS(N_("Un`define' all given (* for all)")) }, { "filetype", &c_filetype, (M | TWYSH), 0, MAC, NULL DS(N_("Create [: :] " "or list file handlers"))}, { "unfiletype", &c_unfiletype, (M | TWYSH), 1, MAC, NULL DS(N_("Delete file handler for [::] (* for all)")) }, { "headerpick", &c_headerpick, (M | TWYSH), 0, MAC, NULL DS(N_("Header selection: [ [ []]]"))}, { "unheaderpick", &c_unheaderpick, (M | TWYSH), 3, MAC, NULL DS(N_("Header deselection: "))}, { "tls", #ifdef mx_HAVE_TLS &c_tls, #else NULL, #endif (G | V | EM | TWYSH), 1, MAC, NULL DS(N_("TLS information and management: [<:argument:>]")) }, /* Codec commands */ { "addrcodec", &c_addrcodec, (G | M | V | X | EM | TRAWDAT), 0, 0, NULL DS(N_("Email address <[+[+[+]]]e[ncode]|d[ecode]|s[kin]> "))}, { "shcodec", &c_shcodec, (G | M | V | X | EM | TRAWDAT), 0, 0, NULL DS(N_("Shell quoting: <[+]e[ncode]|d[ecode]> ")) }, { "urlcodec", &c_urlcodec, (G | M | V | X | EM | TRAWDAT), 0, 0, NULL DS(N_("URL percent <[path]e[ncode]|[path]d[ecode]> ")) }, #ifdef mx_HAVE_DEVEL # ifdef su_MEM_ALLOC_DEBUG { "memtrace", &a_ctab_c_memtrace, (I | M | TWYSH), 0, 0, NULL DS(N_("Trace current memory usage afap")) }, # endif #endif /* mx_HAVE_DEVEL */ /* Obsolete stuff */ #ifdef mx_HAVE_IMAP { "imap", &c_imap_imap, (A | TSTRING), 0, MAC, NULL DS(N_("Send command strings directly to the IMAP server")) }, { "connect", &c_connect, (A | TSTRING), 0, 0, NULL DS(N_("If disconnected, connect to IMAP mailbox")) }, { "disconnect", &c_disconnect, (A | TNDMLST), 0, 0, NULL DS(N_("If connected, disconnect from IMAP mailbox")) }, { "cache", &c_cache, (A | TMSGLST), 0, 0, NULL DS(N_("Read specified into the IMAP cache")) }, { "imapcodec", &c_imapcodec, (G | M | V | X | TRAWDAT), 0, 0, NULL DS(N_("IMAP mailbox name ")) } #endif #undef DS #undef MAC #undef TMSGLST #undef TNDMLST #undef TRAWDAT # undef TSTRING #undef TWYSH # undef TRAWLST # undef TWYRA #undef TARG #undef A #undef F #undef G #undef H #undef I #undef L #undef M #undef O #undef P #undef R #define R su_R #undef SC #undef S #define S su_S #undef T #undef V #undef W #undef X #undef EM #endif /* n_CMD_TAB_H */ /* s-it-mode */ s-nail-14.9.15/src/mx/cmd-vexpr.c000066400000000000000000000730061352610246600163550ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Implementation of cmd-vexpr.h. *@ TODO - better commandline parser that can dive into subcommands could *@ TODO get rid of a lot of ERR_SYNOPSIS cruft. *@ TODO - use su_regex (and if it's a wrapper only) *@ TODO - use su_path_info instead of stat(2) *@ TODO - yet needs OPT_CMD_CSOP for compat byte string operation call-out *@ TODO - _VEXPR -> _CVEXPR * * Copyright (c) 2017 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #undef su_FILE #define su_FILE cmd_vexpr #define mx_SOURCE #define mx_SOURCE_CMD_VEXPR #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif su_EMPTY_FILE() #ifdef mx_HAVE_CMD_VEXPR #include /* TODO su_path_info */ #include /* TODO su_path_info */ #include /* TODO su_path_info */ #ifdef mx_HAVE_REGEX # include /* TODO su_regex */ #endif #include #include #include /* v15compat: csop.h */ #include "mx/cmd-csop.h" #include "mx/random.h" #include "mx/ui-str.h" #include "mx/cmd-vexpr.h" #include "su/code-in.h" enum a_vexpr_cmd{ a_VEXPR_CMD_NUM__MIN, a_VEXPR_CMD_NUM_EQUAL = a_VEXPR_CMD_NUM__MIN, a_VEXPR_CMD_NUM_NOT, a_VEXPR_CMD_NUM_PLUS, a_VEXPR_CMD_NUM_MINUS, a_VEXPR_CMD_NUM_MUL, a_VEXPR_CMD_NUM_DIV, a_VEXPR_CMD_NUM_MOD, a_VEXPR_CMD_NUM_OR, a_VEXPR_CMD_NUM_AND, a_VEXPR_CMD_NUM_XOR, a_VEXPR_CMD_NUM_LSHIFT, a_VEXPR_CMD_NUM_RSHIFT, a_VEXPR_CMD_NUM_URSHIFT, a_VEXPR_CMD_NUM_PBASE, a_VEXPR_CMD_NUM__MAX, a_VEXPR_CMD_AGN__MIN = a_VEXPR_CMD_NUM__MAX, a_VEXPR_CMD_AGN_FILE_EXPAND = a_VEXPR_CMD_AGN__MIN, a_VEXPR_CMD_AGN_FILE_STAT, a_VEXPR_CMD_AGN_FILE_LSTAT, a_VEXPR_CMD_AGN_RANDOM, a_VEXPR_CMD_AGN__MAX, a_VEXPR_CMD_STR__MIN = a_VEXPR_CMD_AGN__MAX, a_VEXPR_CMD_STR_MAKEPRINT = a_VEXPR_CMD_STR__MIN, #ifdef mx_HAVE_REGEX a_VEXPR_CMD_STR_REGEX, a_VEXPR_CMD_STR_IREGEX, /* v15compat */ #endif a_VEXPR_CMD_STR__MAX, /* v15compat: vexpr byte operations -> csop */ a_VEXPR_CMD_BYTE__MIN = a_VEXPR_CMD_STR__MAX, a_VEXPR_CMD_BYTE_LENGTH = a_VEXPR_CMD_BYTE__MIN, a_VEXPR_CMD_BYTE_HASH32, a_VEXPR_CMD_BYTE_HASH, a_VEXPR_CMD_BYTE_FIND, a_VEXPR_CMD_BYTE_IFIND, a_VEXPR_CMD_BYTE_SUBSTRING, a_VEXPR_CMD_BYTE_TRIM, a_VEXPR_CMD_BYTE_TRIM_FRONT, a_VEXPR_CMD_BYTE_TRIM_END, a_VEXPR_CMD_BYTE__MAX, a_VEXPR_CMD__MAX }; enum a_vexpr_err{ a_VEXPR_ERR_NONE, a_VEXPR_ERR_SYNOPSIS, a_VEXPR_ERR_SUBCMD, a_VEXPR_ERR_MOD_NOT_ALLOWED, a_VEXPR_ERR_MOD_NOT_SUPPORTED, a_VEXPR_ERR_NUM_RANGE, a_VEXPR_ERR_NUM_OVERFLOW, a_VEXPR_ERR_STR_NUM_RANGE, a_VEXPR_ERR_STR_OVERFLOW, a_VEXPR_ERR_STR_NODATA, a_VEXPR_ERR_STR_GENERIC }; enum {a_VEXPR_ERR__MAX = a_VEXPR_ERR_STR_GENERIC}; CTA(S(uz,a_VEXPR_CMD__MAX | a_VEXPR_ERR__MAX) <= 0x7Fu, "Bit range excess"); enum a_vexpr_flags{ a_VEXPR_NONE, a_VEXPR_ERR = 1u<<0, /* There was an error */ a_VEXPR_MOD_SATURATED = 1u<<1, /* Saturated / case-insensitive / XY */ a_VEXPR_MOD_CASE = 1u<<2, /* Saturated / case-insensitive / XY */ a_VEXPR_MOD_MASK = a_VEXPR_MOD_SATURATED | a_VEXPR_MOD_CASE, a_VEXPR_ISNUM = 1u<<3, a_VEXPR_ISDECIMAL = 1u<<4, /* Print only decimal result */ a_VEXPR_UNSIGNED_OP = 1u<<5, /* Force unsigned interpretation */ a_VEXPR_SOFTOVERFLOW = 1u<<6, a_VEXPR_PBASE = 1u<<7, /* Print additional number base */ a_VEXPR_PBASE_FORCE_UNSIGNED = 1u<<8, /* We saw an u prefix for it */ a_VEXPR__FMASK = 0x1FFu, a_VEXPR__FSHIFT = 9u, a_VEXPR__FCMDMASK = 0xFE00u, a_VEXPR__TMP = 1u<<30 }; /* .vc_cmderr=8-bit, and so a_vexpr_subcmd can store CMD+MOD flags in 16-bit */ CTA(((S(u32,a_VEXPR_CMD__MAX | a_VEXPR_ERR__MAX) << a_VEXPR__FSHIFT) & ~a_VEXPR__FCMDMASK) == 0, "Bit ranges overlap"); struct a_vexpr_ctx{ u32 vc_flags; u8 vc_cmderr; /* On input, a_vexpr_cmd, on output (maybe) a_vexpr_err */ u8 vc_pbase; u8 vc__pad[2]; char const **vc_argv; char const *vc_cmd_name; char const *vc_varname; /* VPUT support */ char const *vc_varres; char const *vc_arg; /* The current arg (_ERR: which caused failure) */ s64 vc_lhv; s64 vc_rhv; char vc_iencbuf[2+1/* BASE# prefix*/ + su_IENC_BUFFER_SIZE + 1]; }; struct a_vexpr_subcmd{ u16 vs_mpv; char vs_name[14]; }; static struct a_vexpr_subcmd const a_vexpr_subcmds[] = { #undef a_X #define a_X(C,F) (S(u16,C) << a_VEXPR__FSHIFT) | F {a_X(a_VEXPR_CMD_NUM_EQUAL, a_VEXPR_MOD_SATURATED), "="}, {a_X(a_VEXPR_CMD_NUM_NOT, a_VEXPR_MOD_SATURATED), "~"}, {a_X(a_VEXPR_CMD_NUM_PLUS, a_VEXPR_MOD_SATURATED), "+"}, {a_X(a_VEXPR_CMD_NUM_MINUS, a_VEXPR_MOD_SATURATED), "-"}, {a_X(a_VEXPR_CMD_NUM_MUL, a_VEXPR_MOD_SATURATED), "*"}, {a_X(a_VEXPR_CMD_NUM_DIV, a_VEXPR_MOD_SATURATED), "/"}, {a_X(a_VEXPR_CMD_NUM_MOD, a_VEXPR_MOD_SATURATED), "%"}, {a_X(a_VEXPR_CMD_NUM_OR, a_VEXPR_MOD_SATURATED), "|"}, {a_X(a_VEXPR_CMD_NUM_AND, a_VEXPR_MOD_SATURATED), "&"}, {a_X(a_VEXPR_CMD_NUM_XOR, a_VEXPR_MOD_SATURATED), "^"}, {a_X(a_VEXPR_CMD_NUM_LSHIFT, a_VEXPR_MOD_SATURATED), "<<"}, {a_X(a_VEXPR_CMD_NUM_RSHIFT, a_VEXPR_MOD_SATURATED), ">>"}, {a_X(a_VEXPR_CMD_NUM_URSHIFT, a_VEXPR_MOD_SATURATED), ">>>"}, {a_X(a_VEXPR_CMD_NUM_PBASE, a_VEXPR_MOD_SATURATED), "pbase\0"}, {a_X(a_VEXPR_CMD_AGN_FILE_EXPAND, 0), "file-expand\0"}, {a_X(a_VEXPR_CMD_AGN_FILE_STAT, 0), "file-stat"}, {a_X(a_VEXPR_CMD_AGN_FILE_LSTAT, 0), "file-lstat"}, {a_X(a_VEXPR_CMD_AGN_RANDOM, 0), "random"}, {a_X(a_VEXPR_CMD_STR_MAKEPRINT, 0), "makeprint"}, #ifdef mx_HAVE_REGEX {a_X(a_VEXPR_CMD_STR_REGEX, a_VEXPR_MOD_CASE), "regex"}, {a_X(a_VEXPR_CMD_STR_IREGEX, 0), "iregex"}, /* v15compat*/ #endif /* v15compat: vexpr byte operations -> csop */ {a_X(a_VEXPR_CMD_BYTE_LENGTH, 0), "length"}, {a_X(a_VEXPR_CMD_BYTE_HASH, 0), "hash"}, {a_X(a_VEXPR_CMD_BYTE_HASH32, 0), "hash32"}, {a_X(a_VEXPR_CMD_BYTE_FIND, a_VEXPR_MOD_CASE), "find"}, {a_X(a_VEXPR_CMD_BYTE_IFIND, 0), "ifind"}, /* v15compat */ {a_X(a_VEXPR_CMD_BYTE_SUBSTRING, 0), "substring"}, {a_X(a_VEXPR_CMD_BYTE_TRIM, 0), "trim"}, {a_X(a_VEXPR_CMD_BYTE_TRIM_FRONT, 0), "trim-front\0"}, {a_X(a_VEXPR_CMD_BYTE_TRIM_END, 0), "trim-end"}, #undef a_X }; /* Entered with .vc_flags=NONE(|MOD)? */ static void a_vexpr_numeric(struct a_vexpr_ctx *vcp); static void a_vexpr_agnostic(struct a_vexpr_ctx *vcp); static void a_vexpr_string(struct a_vexpr_ctx *vcp); #ifdef mx_HAVE_REGEX static char *a_vexpr__regex_replace(void *uservp); #endif static void a_vexpr_numeric(struct a_vexpr_ctx *vcp){ u8 cmd; u32 idecs, idecm; s64 lhv, rhv; char const *cp; u32 f; NYD2_IN; lhv = rhv = 0; f = vcp->vc_flags; f |= a_VEXPR_ISNUM; if((cp = vcp->vc_argv[0]) == NIL){ f |= a_VEXPR_ERR; vcp->vc_cmderr = a_VEXPR_ERR_SYNOPSIS; goto jleave; } jlhv_redo: if(*cp == '\0') lhv = 0; else{ vcp->vc_arg = cp; idecm = (((*cp == 'u' || *cp == 'U') ? (f |= a_VEXPR_PBASE_FORCE_UNSIGNED, ++cp, su_IDEC_MODE_NONE) : ((*cp == 's' || *cp == 'S') ? (++cp, su_IDEC_MODE_SIGNED_TYPE) : su_IDEC_MODE_SIGNED_TYPE | su_IDEC_MODE_POW2BASE_UNSIGNED) ) | su_IDEC_MODE_BASE0_NUMBER_SIGN_RESCAN); if(((idecs = su_idec_cp(&lhv, cp, 0, idecm, NIL) ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED) ) != su_IDEC_STATE_CONSUMED){ if(!(idecs & su_IDEC_STATE_EOVERFLOW) || !(f & a_VEXPR_MOD_SATURATED)){ f |= a_VEXPR_ERR; vcp->vc_cmderr = a_VEXPR_ERR_NUM_RANGE; }else f |= a_VEXPR_SOFTOVERFLOW; goto jleave; } } switch((cmd = S(u8,vcp->vc_cmderr))){ /* break==goto jleave */ case a_VEXPR_CMD_NUM_NOT: lhv = ~lhv; /* FALLTHRU */ default: case a_VEXPR_CMD_NUM_EQUAL: if(vcp->vc_argv[1] != NIL){ f |= a_VEXPR_ERR; vcp->vc_cmderr = a_VEXPR_ERR_SYNOPSIS; } break; case a_VEXPR_CMD_NUM_PLUS: if(vcp->vc_argv[1] == NIL){ lhv = +lhv; break; } goto jbinop; case a_VEXPR_CMD_NUM_MINUS: if(vcp->vc_argv[1] == NIL){ lhv = -lhv; break; } goto jbinop; case a_VEXPR_CMD_NUM_MUL: case a_VEXPR_CMD_NUM_DIV: case a_VEXPR_CMD_NUM_MOD: case a_VEXPR_CMD_NUM_OR: case a_VEXPR_CMD_NUM_AND: case a_VEXPR_CMD_NUM_XOR: case a_VEXPR_CMD_NUM_LSHIFT: case a_VEXPR_CMD_NUM_RSHIFT: case a_VEXPR_CMD_NUM_URSHIFT: jbinop: if((cp = vcp->vc_argv[1]) == NIL || (vcp->vc_arg = cp, vcp->vc_argv[2] != NIL)){ f |= a_VEXPR_ERR; vcp->vc_cmderr = a_VEXPR_ERR_SYNOPSIS; break; } if(*cp == '\0') rhv = 0; else{ idecm = (((*cp == 'u' || *cp == 'U') ? (++cp, su_IDEC_MODE_NONE) : ((*cp == 's' || *cp == 'S') ? (++cp, su_IDEC_MODE_SIGNED_TYPE) : su_IDEC_MODE_SIGNED_TYPE | su_IDEC_MODE_POW2BASE_UNSIGNED)) | su_IDEC_MODE_BASE0_NUMBER_SIGN_RESCAN); if(((idecs = su_idec_cp(&rhv, cp, 0, idecm, NIL) ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED) ) != su_IDEC_STATE_CONSUMED){ if(!(idecs & su_IDEC_STATE_EOVERFLOW) || !(f & a_VEXPR_MOD_SATURATED)){ f |= a_VEXPR_ERR; vcp->vc_cmderr = a_VEXPR_ERR_NUM_RANGE; }else f |= a_VEXPR_SOFTOVERFLOW; break; } } jbinop_again: switch(cmd){ default: case a_VEXPR_CMD_NUM_PLUS: if(rhv < 0){ if(rhv != S64_MIN){ rhv = -rhv; cmd = a_VEXPR_CMD_NUM_MINUS; goto jbinop_again; }else if(lhv < 0) goto jenum_plusminus; else if(lhv == 0){ lhv = rhv; break; } }else if(S64_MAX - rhv < lhv) goto jenum_plusminus; lhv += rhv; break; case a_VEXPR_CMD_NUM_MINUS: if(rhv < 0){ if(rhv != S64_MIN){ rhv = -rhv; cmd = a_VEXPR_CMD_NUM_PLUS; goto jbinop_again; }else if(lhv > 0) goto jenum_plusminus; else if(lhv == 0){ lhv = rhv; break; } }else if(S64_MIN + rhv > lhv){ jenum_plusminus: if(!(f & a_VEXPR_MOD_SATURATED)){ f |= a_VEXPR_ERR; vcp->vc_cmderr = a_VEXPR_ERR_NUM_OVERFLOW; break; } f |= a_VEXPR_SOFTOVERFLOW; lhv = (lhv < 0 || cmd == a_VEXPR_CMD_NUM_MINUS) ? S64_MIN : S64_MAX; break; } lhv -= rhv; break; case a_VEXPR_CMD_NUM_MUL: /* Will the result be positive? */ if((lhv < 0) == (rhv < 0)){ if(lhv > 0){ lhv = -lhv; rhv = -rhv; } if(rhv != 0 && lhv != 0 && S64_MAX / rhv > lhv){ if(!(f & a_VEXPR_MOD_SATURATED)){ f |= a_VEXPR_ERR; vcp->vc_cmderr = a_VEXPR_ERR_NUM_OVERFLOW; break; } f |= a_VEXPR_SOFTOVERFLOW; lhv = S64_MAX; }else lhv *= rhv; }else{ if(rhv > 0){ if(lhv != 0 && S64_MIN / lhv < rhv){ if(!(f & a_VEXPR_MOD_SATURATED)){ f |= a_VEXPR_ERR; vcp->vc_cmderr = a_VEXPR_ERR_NUM_OVERFLOW; break; } f |= a_VEXPR_SOFTOVERFLOW; lhv = S64_MIN; }else lhv *= rhv; }else{ if(rhv != 0 && lhv != 0 && S64_MIN / rhv < lhv){ if(!(f & a_VEXPR_MOD_SATURATED)){ f |= a_VEXPR_ERR; vcp->vc_cmderr = a_VEXPR_ERR_NUM_OVERFLOW; } f |= a_VEXPR_SOFTOVERFLOW; lhv = S64_MIN; }else lhv *= rhv; } } break; case a_VEXPR_CMD_NUM_DIV: if(rhv == 0){ if(!(f & a_VEXPR_MOD_SATURATED)){ f |= a_VEXPR_ERR; vcp->vc_cmderr = a_VEXPR_ERR_NUM_RANGE; break; } f |= a_VEXPR_SOFTOVERFLOW; lhv = S64_MAX; }else lhv /= rhv; break; case a_VEXPR_CMD_NUM_MOD: if(rhv == 0){ if(!(f & a_VEXPR_MOD_SATURATED)){ f |= a_VEXPR_ERR; vcp->vc_cmderr = a_VEXPR_ERR_NUM_RANGE; break; } f |= a_VEXPR_SOFTOVERFLOW; lhv = S64_MAX; }else lhv %= rhv; break; case a_VEXPR_CMD_NUM_OR: lhv |= rhv; break; case a_VEXPR_CMD_NUM_AND: lhv &= rhv; break; case a_VEXPR_CMD_NUM_XOR: lhv ^= rhv; break; case a_VEXPR_CMD_NUM_LSHIFT: case a_VEXPR_CMD_NUM_RSHIFT: case a_VEXPR_CMD_NUM_URSHIFT: if(rhv <= 63) /* xxx 63? */ cmd = S(u8,rhv); else if(!(f & a_VEXPR_MOD_SATURATED)){ f |= a_VEXPR_ERR; vcp->vc_cmderr = a_VEXPR_ERR_NUM_OVERFLOW; break; }else cmd = 63; if(cmd == a_VEXPR_CMD_NUM_LSHIFT) lhv <<= cmd; else if(cmd == a_VEXPR_CMD_NUM_RSHIFT) lhv >>= cmd; else lhv = S(u64,lhv) >> cmd; break; } break; case a_VEXPR_CMD_NUM_PBASE: /* Have been here already? */ if(f & a_VEXPR_PBASE) break; if((cp = vcp->vc_argv[1]) == NIL || vcp->vc_argv[2] != NIL){ f |= a_VEXPR_ERR; vcp->vc_cmderr = a_VEXPR_ERR_SYNOPSIS; break; } if(lhv < 2 || lhv > 36){ f |= a_VEXPR_ERR; vcp->vc_cmderr = a_VEXPR_ERR_NUM_RANGE; break; } f |= a_VEXPR_PBASE; vcp->vc_pbase = S(u8,lhv); goto jlhv_redo; } jleave: vcp->vc_flags = f; vcp->vc_lhv = lhv; vcp->vc_rhv = rhv; NYD2_OU; } static void a_vexpr_agnostic(struct a_vexpr_ctx *vcp){ NYD2_IN; switch(vcp->vc_cmderr){ default: case a_VEXPR_CMD_AGN_FILE_EXPAND: if(vcp->vc_argv[0] == NIL || vcp->vc_argv[1] != NIL){ vcp->vc_flags |= a_VEXPR_ERR; vcp->vc_cmderr = a_VEXPR_ERR_SYNOPSIS; break; } vcp->vc_arg = vcp->vc_argv[0]; if((vcp->vc_varres = fexpand(vcp->vc_arg, FEXP_NVAR | FEXP_NOPROTO) ) == NIL){ vcp->vc_flags |= a_VEXPR_ERR; vcp->vc_cmderr = a_VEXPR_ERR_STR_NODATA; } break; case a_VEXPR_CMD_AGN_FILE_LSTAT: vcp->vc_flags |= a_VEXPR_MOD_MASK; /* FALLTHRU */ case a_VEXPR_CMD_AGN_FILE_STAT:{ struct stat st; struct n_string s_b, *s; char c; if(vcp->vc_argv[0] == NIL || vcp->vc_argv[1] != NIL){ vcp->vc_flags |= a_VEXPR_ERR; vcp->vc_cmderr = a_VEXPR_ERR_SYNOPSIS; break; } vcp->vc_arg = vcp->vc_argv[0]; if((vcp->vc_varres = fexpand(vcp->vc_arg, FEXP_NVAR | FEXP_NOPROTO) ) == NIL){ vcp->vc_flags |= a_VEXPR_ERR; vcp->vc_cmderr = a_VEXPR_ERR_STR_NODATA; break; } if(((vcp->vc_flags & a_VEXPR_MOD_MASK) ? lstat : stat )(vcp->vc_varres, &st) != 0){ vcp->vc_flags |= a_VEXPR_ERR; vcp->vc_cmderr = a_VEXPR_ERR_STR_NODATA; break; } s = n_string_book(n_string_creat_auto(&s_b), 250); s = n_string_push_cp(s, "st_file="); s = n_string_push_cp(s, n_shexp_quote_cp(vcp->vc_varres, FAL0)); s = n_string_push_c(s, ' '); s = n_string_push_cp(s, "st_type="); if(S_ISDIR(st.st_mode)) c = '/'; else if(S_ISLNK(st.st_mode)) c = '@'; #ifdef S_ISBLK else if(S_ISBLK(st.st_mode)) c = '#'; #endif #ifdef S_ISCHR else if(S_ISCHR(st.st_mode)) c = '%'; #endif #ifdef S_ISFIFO else if(S_ISFIFO(st.st_mode)) c = '|'; #endif #ifdef S_ISSOCK else if(S_ISSOCK(st.st_mode)) c = '='; #endif else c = '.'; s = n_string_push_c(s, c); s = n_string_push_c(s, ' '); s = n_string_push_cp(s, "st_nlink="); s = n_string_push_cp(s, su_ienc_s64(vcp->vc_iencbuf, st.st_nlink, 10)); s = n_string_push_c(s, ' '); s = n_string_push_cp(s, "st_size="); s = n_string_push_cp(s, su_ienc_u64(vcp->vc_iencbuf, st.st_size, 10)); s = n_string_push_c(s, ' '); s = n_string_push_cp(s, "st_mode="); s = n_string_push_cp(s, su_ienc_s32(vcp->vc_iencbuf, st.st_mode & 07777, 8)); s = n_string_push_c(s, ' '); s = n_string_push_cp(s, "st_uid="); s = n_string_push_cp(s, su_ienc_s64(vcp->vc_iencbuf, st.st_uid, 10)); s = n_string_push_c(s, ' '); s = n_string_push_cp(s, "st_gid="); s = n_string_push_cp(s, su_ienc_s64(vcp->vc_iencbuf, st.st_gid, 10)); s = n_string_push_c(s, ' '); vcp->vc_varres = n_string_cp(s); /* n_string_gut(n_string_drop_ownership(s)); */ }break; case a_VEXPR_CMD_AGN_RANDOM: if(vcp->vc_argv[0] == NIL || vcp->vc_argv[1] != NIL){ vcp->vc_flags |= a_VEXPR_ERR; vcp->vc_cmderr = a_VEXPR_ERR_SYNOPSIS; break; } vcp->vc_arg = vcp->vc_argv[0]; if((su_idec_s64_cp(&vcp->vc_lhv, vcp->vc_argv[0], 0, NIL ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED) ) != su_IDEC_STATE_CONSUMED || vcp->vc_lhv < 0 || vcp->vc_lhv > PATH_MAX){ vcp->vc_flags |= a_VEXPR_ERR; vcp->vc_cmderr = a_VEXPR_ERR_STR_NUM_RANGE; break; } if(vcp->vc_lhv == 0) vcp->vc_lhv = NAME_MAX; vcp->vc_varres = mx_random_create_cp(S(uz,vcp->vc_lhv), NIL); break; } NYD2_OU; } static void a_vexpr_string(struct a_vexpr_ctx *vcp){ NYD2_IN; switch(vcp->vc_cmderr){ default: case a_VEXPR_CMD_STR_MAKEPRINT:{ struct str sin, sout; if(vcp->vc_argv[0] == NIL || vcp->vc_argv[1] != NIL){ vcp->vc_flags |= a_VEXPR_ERR; vcp->vc_cmderr = a_VEXPR_ERR_SYNOPSIS; break; } vcp->vc_arg = vcp->vc_argv[0]; /* XXX using su_cs_len for `vexpr makeprint' is wrong for UTF-16 */ sin.l = su_cs_len(sin.s = UNCONST(char*,vcp->vc_arg)); makeprint(&sin, &sout); vcp->vc_varres = savestrbuf(sout.s, sout.l); n_free(sout.s); }break; /* TODO `vexpr': (wide) string length, find, etc!! */ #ifdef mx_HAVE_REGEX case a_VEXPR_CMD_STR_IREGEX: n_OBSOLETE(_("vexpr: iregex: simply use regex?[case] instead, please")); vcp->vc_flags |= a_VEXPR_MOD_CASE; /* FALLTHRU */ case a_VEXPR_CMD_STR_REGEX:{ regmatch_t rema[1 + mx_VEXPR_REGEX_MAX]; regex_t re; int reflrv; vcp->vc_flags |= a_VEXPR_ISNUM | a_VEXPR_ISDECIMAL; if(vcp->vc_argv[0] == NIL || vcp->vc_argv[1] == NIL || (vcp->vc_argv[2] != NIL && vcp->vc_argv[3] != NIL)){ vcp->vc_flags |= a_VEXPR_ERR; vcp->vc_cmderr = a_VEXPR_ERR_SYNOPSIS; break; } vcp->vc_arg = vcp->vc_argv[1]; reflrv = REG_EXTENDED; if(vcp->vc_flags & a_VEXPR_MOD_CASE) reflrv |= REG_ICASE; if((reflrv = regcomp(&re, vcp->vc_arg, reflrv))){ n_err(_("vexpr: invalid regular expression: %s: %s\n"), n_shexp_quote_cp(vcp->vc_arg, FAL0), n_regex_err_to_doc(NIL, reflrv)); vcp->vc_flags |= a_VEXPR_ERR; vcp->vc_cmderr = a_VEXPR_ERR_STR_GENERIC; n_pstate_err_no = su_ERR_INVAL; break; } reflrv = regexec(&re, vcp->vc_argv[0], NELEM(rema), rema, 0); regfree(&re); if(reflrv == REG_NOMATCH){ vcp->vc_flags |= a_VEXPR_ERR; vcp->vc_cmderr = a_VEXPR_ERR_STR_NODATA; break; } /* Search only? Else replace, which is a bit */ if(vcp->vc_argv[2] == NIL){ if(UCMP(64, rema[0].rm_so, >, S64_MAX)){ vcp->vc_flags |= a_VEXPR_ERR; vcp->vc_cmderr = a_VEXPR_ERR_STR_OVERFLOW; break; } vcp->vc_lhv = S(s64,rema[0].rm_so); }else{ /* TODO We yet need to have a hook into generic pospar handling * TODO instead of having a shexp_parse carrier which takes some * TODO pospars directly */ char const *name, **argv, **ccpp; uz i, argc; name = savestrbuf(&vcp->vc_argv[0][rema[0].rm_so], rema[0].rm_eo - rema[0].rm_so); for(argc = i = 1; i < NELEM(rema); ++i) if(rema[i].rm_so != -1) argc = i; argv = su_LOFI_TALLOC(char const*,argc +1); for(ccpp = argv, i = 1; i <= argc; ++ccpp, ++i) if(rema[i].rm_so != -1) *ccpp = savestrbuf(&vcp->vc_argv[0][rema[i].rm_so], rema[i].rm_eo - rema[i].rm_so); else *ccpp = su_empty; *ccpp = NIL; /* Logical unconst */ vcp->vc_varres = temporary_pospar_access_hook(name, argv, argc, &a_vexpr__regex_replace, UNCONST(char*,vcp->vc_argv[2])); su_LOFI_FREE(argv); if(vcp->vc_varres == NIL){ vcp->vc_flags |= a_VEXPR_ERR; vcp->vc_cmderr = a_VEXPR_ERR_STR_NODATA; break; } vcp->vc_flags ^= (a_VEXPR_ISNUM | a_VEXPR_ISDECIMAL); } }break; #endif /* mx_HAVE_REGEX */ } NYD2_OU; } #ifdef mx_HAVE_REGEX static char * a_vexpr__regex_replace(void *uservp){ struct str templ; struct n_string s_b; char *rv; enum n_shexp_state shs; NYD2_IN; templ.s = S(char*,uservp); templ.l = UZ_MAX; shs = n_shexp_parse_token((n_SHEXP_PARSE_LOG | n_SHEXP_PARSE_IGNORE_EMPTY | n_SHEXP_PARSE_QUOTE_AUTO_FIXED | n_SHEXP_PARSE_QUOTE_AUTO_DSQ), n_string_creat_auto(&s_b), &templ, NIL); if((shs & (n_SHEXP_STATE_ERR_MASK | n_SHEXP_STATE_STOP) ) == n_SHEXP_STATE_STOP){ rv = n_string_cp(&s_b); n_string_drop_ownership(&s_b); }else rv = NIL; NYD2_OU; return rv; } #endif /* mx_HAVE_REGEX */ int c_vexpr(void *vp){ /* TODO POSIX expr(1) comp. exit status */ struct a_vexpr_ctx vc; char const *cp; u32 f; uz i, j; NYD_IN; /*DVL(*/ su_mem_set(&vc, 0xAA, sizeof vc); /*)*/ vc.vc_flags = a_VEXPR_ERR | a_VEXPR_ISNUM; vc.vc_cmderr = a_VEXPR_ERR_SUBCMD; vc.vc_argv = S(char const**,vp); vc.vc_varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *vc.vc_argv++ : NIL; vc.vc_varres = su_empty; vc.vc_arg = vc.vc_cmd_name = *vc.vc_argv++; if((cp = su_cs_find_c(vc.vc_cmd_name, '?')) != NIL){ j = P2UZ(cp - vc.vc_cmd_name); if(cp[1] == '\0') f = a_VEXPR_MOD_MASK; else if(su_cs_starts_with_case("case", &cp[1])) f = a_VEXPR_MOD_CASE; else if(su_cs_starts_with_case("saturated", &cp[1])) f = a_VEXPR_MOD_SATURATED; else{ n_err(_("vexpr: invalid modifier: %s\n"), n_shexp_quote_cp(vc.vc_cmd_name, FAL0)); f = a_VEXPR_ERR; goto jleave; } }else{ f = a_VEXPR_NONE; if(*vc.vc_cmd_name == '@'){ /* v15compat */ n_OBSOLETE2(_("vexpr: please use ? modifier suffix, " "not @ prefix"), n_shexp_quote_cp(vc.vc_cmd_name, FAL0)); ++vc.vc_cmd_name; f = a_VEXPR_MOD_MASK; } j = su_cs_len(vc.vc_cmd_name); } for(i = 0; i < NELEM(a_vexpr_subcmds); ++i){ if(su_cs_starts_with_case_n(a_vexpr_subcmds[i].vs_name, vc.vc_cmd_name, j)){ vc.vc_cmd_name = a_vexpr_subcmds[i].vs_name; i = a_vexpr_subcmds[i].vs_mpv; if(UNLIKELY(f & a_VEXPR_MOD_MASK)){ u32 f2; f2 = f & a_VEXPR_MOD_MASK; if(UNLIKELY(!(i & a_VEXPR_MOD_MASK))){ vc.vc_cmderr = a_VEXPR_ERR_MOD_NOT_ALLOWED; break; }else if(UNLIKELY(f2 != a_VEXPR_MOD_MASK && f2 != (i & a_VEXPR_MOD_MASK))){ vc.vc_cmderr = a_VEXPR_ERR_MOD_NOT_SUPPORTED; break; } } vc.vc_arg = vc.vc_cmd_name; vc.vc_flags = f; i = (i & a_VEXPR__FCMDMASK) >> a_VEXPR__FSHIFT; if((vc.vc_cmderr = S(u8,i)) < a_VEXPR_CMD_NUM__MAX) a_vexpr_numeric(&vc); else if(i < a_VEXPR_CMD_AGN__MAX) a_vexpr_agnostic(&vc); else if(i < a_VEXPR_CMD_STR__MAX) a_vexpr_string(&vc); else /*if(i < a_VEXPR_CMD_BYTE__MAX)*/{ n_OBSOLETE2(_("vexpr: C-style string operations are now " "handled via `csop' command (sorry)"), vc.vc_cmd_name); f = (c_csop(vp) == 0) ? 0 : a_VEXPR_ERR; goto jleave; } break; } } f = vc.vc_flags; if(LIKELY(!(f & a_VEXPR_ERR))){ n_pstate_err_no = (f & a_VEXPR_SOFTOVERFLOW) ? su_ERR_OVERFLOW : su_ERR_NONE; }else switch(vc.vc_cmderr){ case a_VEXPR_ERR_NONE: ASSERT(0); break; case a_VEXPR_ERR_SYNOPSIS: n_err(_("Synopsis: vexpr: <:argument:>\n")); n_pstate_err_no = su_ERR_INVAL; goto jenum; case a_VEXPR_ERR_SUBCMD: n_err(_("vexpr: invalid subcommand: %s\n"), n_shexp_quote_cp(vc.vc_arg, FAL0)); n_pstate_err_no = su_ERR_INVAL; goto jenum; case a_VEXPR_ERR_MOD_NOT_ALLOWED: n_err(_("vexpr: modifiers not allowed for subcommand: %s\n"), n_shexp_quote_cp(vc.vc_arg, FAL0)); n_pstate_err_no = su_ERR_INVAL; goto jenum; case a_VEXPR_ERR_MOD_NOT_SUPPORTED: n_err(_("vexpr: given modifier not supported for subcommand: %s\n"), n_shexp_quote_cp(vc.vc_arg, FAL0)); n_pstate_err_no = su_ERR_INVAL; goto jenum; case a_VEXPR_ERR_NUM_RANGE: n_err(_("vexpr: numeric argument invalid or out of range: %s\n"), n_shexp_quote_cp(vc.vc_arg, FAL0)); n_pstate_err_no = su_ERR_RANGE; goto jenum; case a_VEXPR_ERR_NUM_OVERFLOW: n_err(_("vexpr: expression overflows datatype: %" PRId64 " %s %" PRId64 "\n"), vc.vc_lhv, vc.vc_cmd_name, vc.vc_rhv); n_pstate_err_no = su_ERR_OVERFLOW; goto jenum; default: jenum: f = a_VEXPR_ERR | a_VEXPR_ISNUM | a_VEXPR_ISDECIMAL; vc.vc_lhv = -1; break; case a_VEXPR_ERR_STR_NUM_RANGE: n_err(_("vexpr: numeric argument invalid or out of range: %s\n"), n_shexp_quote_cp(vc.vc_arg, FAL0)); n_pstate_err_no = su_ERR_RANGE; goto jestr; case a_VEXPR_ERR_STR_OVERFLOW: n_err(_("vexpr: string length or offset overflows datatype\n")); n_pstate_err_no = su_ERR_OVERFLOW; goto jestr; case a_VEXPR_ERR_STR_NODATA: n_pstate_err_no = su_ERR_NODATA; /* FALLTHRU*/ case a_VEXPR_ERR_STR_GENERIC: jestr: vc.vc_varres = su_empty; f = a_VEXPR_ERR; break; } /* Generate the variable value content for numerics. * Anticipate in our handling below! (Avoid needless work) */ if((f & a_VEXPR_ISNUM) && ((f & (a_VEXPR_ISDECIMAL | a_VEXPR_PBASE)) || vc.vc_varname != NIL)){ cp = su_ienc(vc.vc_iencbuf, vc.vc_lhv, ((!(f & a_VEXPR_ERR) && (f & a_VEXPR_PBASE)) ? vc.vc_pbase : 10), (((f & (a_VEXPR_PBASE | a_VEXPR_PBASE_FORCE_UNSIGNED)) == (a_VEXPR_PBASE | a_VEXPR_PBASE_FORCE_UNSIGNED)) ? su_IENC_MODE_NONE : su_IENC_MODE_SIGNED_TYPE)); if(cp != NIL) vc.vc_varres = cp; else{ f |= a_VEXPR_ERR; vc.vc_varres = su_empty; } } if(vc.vc_varname == NIL){ /* If there was no error and we are printing a numeric result, print some * more bases for the fun of it */ if((f & (a_VEXPR_ERR | a_VEXPR_ISNUM | a_VEXPR_ISDECIMAL) ) == a_VEXPR_ISNUM){ char binabuf[64 + 64 / 8 +1]; for(j = 1, i = 0; i < 64; ++i){ binabuf[63 + 64 / 8 -j - i] = (vc.vc_lhv & (S(u64,1) << i)) ? '1' : '0'; if((i & 7) == 7 && i != 63){ ++j; binabuf[63 + 64 / 8 -j - i] = ' '; } } binabuf[64 + 64 / 8 -1] = '\0'; if(fprintf(n_stdout, "0b %s\n0%" PRIo64 " | 0x%" PRIX64 " | %" PRId64 "\n", binabuf, vc.vc_lhv, vc.vc_lhv, vc.vc_lhv) < 0 || ((f & a_VEXPR_PBASE) && fprintf(n_stdout, "%s\n", vc.vc_varres) < 0)){ n_pstate_err_no = su_err_no(); f |= a_VEXPR_ERR; } }else if(vc.vc_varres != NIL && fprintf(n_stdout, "%s\n", vc.vc_varres) < 0){ n_pstate_err_no = su_err_no(); f |= a_VEXPR_ERR; } }else if(!n_var_vset(vc.vc_varname, S(up,vc.vc_varres))){ n_pstate_err_no = su_ERR_NOTSUP; f |= a_VEXPR_ERR; } jleave: NYD_OU; return (f & a_VEXPR_ERR) ? 1 : 0; } #include "su/code-ou.h" #endif /* mx_HAVE_CMD_VEXPR */ /* s-it-mode */ s-nail-14.9.15/src/mx/cmd-write.c000066400000000000000000000232521352610246600163410ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ User commands which save, copy, write (parts of) messages. * * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany. * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1980, 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. */ #undef su_FILE #define su_FILE cmd_write #define mx_SOURCE #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif #include #include #include "mx/file-locks.h" #include "mx/file-streams.h" #include "mx/names.h" /* TODO fake */ #include "su/code-in.h" /* Save/copy the indicated messages at the end of the passed file name. * If mark is true, mark the message "saved" */ static int a_cwrite_save1(void *vp, struct n_ignore const *itp, int convert, boole domark, boole domove); static int a_cwrite_save1(void *vp, struct n_ignore const *itp, int convert, boole domark, boole domove) { u64 mstats[1], tstats[2]; enum mx_fs_open_state fs; struct message *mp; char *file, *cp, *cq; FILE *obuf; char const *shell, *disp; boole success; int last, *msgvec, *ip; struct n_cmd_arg *cap; struct n_cmd_arg_ctx *cacp; NYD2_IN; cacp = vp; cap = cacp->cac_arg; last = 0; msgvec = cap->ca_arg.ca_msglist; success = FAL0; shell = NULL; file = NULL; if(!(cap->ca_ent_flags[0] & n_CMD_ARG_DESC_MSGLIST_AND_TARGET)){ struct mx_name *np; if((cp = n_header_senderfield_of(message + *msgvec - 1)) == NULL || (np = lextract(cp, GTO | GSKIN)) == NULL){ n_err(_("Cannot determine message sender to %s.\n"), cacp->cac_desc->cad_name); goto jleave; } cp = np->n_name; for (cq = cp; *cq != '\0' && *cq != '@'; cq++) ; *cq = '\0'; if (ok_blook(outfolder)) { uz i; i = su_cs_len(cp) +1; file = n_autorec_alloc(i + 1); file[0] = '+'; su_mem_copy(file + 1, cp, i); } else file = cp; }else{ cap = cap->ca_next; if((file = cap->ca_arg.ca_str.s)[0] == '\0') file = fexpand("&", FEXP_NVAR); while(su_cs_is_space(*file)) ++file; if (*file == '|') { ++file; shell = ok_vlook(SHELL); /* Pipe target is special TODO hacked in later, normalize flow! */ if((obuf = mx_fs_pipe_open(file, "w", shell, NIL, -1)) == NIL){ int esave; n_perr(file, esave = su_err_no()); su_err_set_no(esave); goto jleave; } disp = A_("[Piped]"); fs = mx_FS_OPEN_STATE_NONE; goto jsend; } } if ((file = fexpand(file, FEXP_FULL)) == NULL) goto jleave; /* TODO all this should be URL and Mailbox-"VFS" based, and then finally * TODO end up as Mailbox()->append(). Unless SEND_TOFILE, of course. * TODO However, URL parse because that file:// prefix check is a HACK! */ if(convert == SEND_TOFILE && !su_cs_starts_with(file, "file://")) file = savecat("file://", file); if((obuf = mx_fs_open_any(file, "a+", &fs)) == NIL){ n_perr(file, 0); goto jleave; } ASSERT((fs & n_PROTO_MASK) == n_PROTO_IMAP || (fs & n_PROTO_MASK) == n_PROTO_FILE || (fs & n_PROTO_MASK) == n_PROTO_MAILDIR); #if defined mx_HAVE_POP3 && defined mx_HAVE_IMAP if(mb.mb_type == MB_POP3 && (fs & n_PROTO_MASK) == n_PROTO_IMAP){ mx_fs_close(obuf); n_err(_("Direct copy from POP3 to IMAP not supported before v15\n")); goto jleave; } #endif disp = (fs & mx_FS_OPEN_STATE_EXISTS) ? A_("[Appended]") : A_("[New file]"); if((fs & n_PROTO_MASK) != n_PROTO_IMAP){ /* TODO RETURN check, but be aware of protocols: v15: Mailbox->lock()! * TODO BETTER yet: should be returned in lock state already! */ mx_file_lock(fileno(obuf), mx_FILE_LOCK_TYPE_WRITE, 0,0, UZ_MAX); if(fs & mx_FS_OPEN_STATE_EXISTS){ int xerr; if((xerr = n_folder_mbox_prepare_append(obuf, NULL)) != su_ERR_NONE){ n_perr(file, xerr); goto jleave; } } } jsend: success = TRU1; tstats[0] = tstats[1] = 0; #ifdef mx_HAVE_IMAP imap_created_mailbox = 0; #endif n_autorec_relax_create(); for (ip = msgvec; *ip != 0; ++ip) { mp = &message[*ip - 1]; #ifdef mx_HAVE_IMAP if((fs & n_PROTO_MASK) == n_PROTO_IMAP && !n_ignore_is_any(n_IGNORE_SAVE) && imap_thisaccount(file)){ if(imap_copy(mp, P2UZ(mp - message + 1), file) == STOP){ success = FAL0; goto jferr; } mstats[0] = mp->m_xsize; }else #endif { if(sendmp(mp, obuf, itp, NIL, convert, mstats) < 0){ success = FAL0; goto jferr; } /* TODO v15compat: solely Mailbox()->append() related, and today * TODO can mess with the content of a message (in that if a msg * TODO ends with two \n, that is ok'd as MBOX separator! */ if(convert == SEND_MBOX) n_folder_mbox_prepare_append(obuf, NIL); } n_autorec_relax_unroll(); touch(mp); if (domark) mp->m_flag |= MSAVED; if (domove) { mp->m_flag |= MDELETED | MSAVED; last = *ip; } tstats[0] += mstats[0]; tstats[1] += mp->m_lines;/* TODO won't work, need target! v15!! */ } n_autorec_relax_gut(); fflush(obuf); if (ferror(obuf)) { jferr: n_perr(file, 0); if (!success) srelax_rele(); success = FAL0; } if(shell != NIL){ if(!mx_fs_pipe_close(obuf, TRU1)) success = FAL0; }else if(!mx_fs_close(obuf)) success = FAL0; if (success) { #ifdef mx_HAVE_IMAP if((fs & n_PROTO_MASK) == n_PROTO_IMAP){ if(disconnected(file)) disp = A_("[Queued]"); else if(imap_created_mailbox) disp = A_("[New file]"); else disp = A_("[Appended]"); } #endif fprintf(n_stdout, "%s %s %" /*PRIu64 "/%"*/ PRIu64 " bytes\n", n_shexp_quote_cp(file, FAL0), disp, /*tstats[1], TODO v15: lines written */ tstats[0]); } else if (domark) { for (ip = msgvec; *ip != 0; ++ip) { mp = message + *ip - 1; mp->m_flag &= ~MSAVED; } } else if (domove) { for (ip = msgvec; *ip != 0; ++ip) { mp = message + *ip - 1; mp->m_flag &= ~(MSAVED | MDELETED); } } if (domove && last && success) { setdot(message + last - 1); last = first(0, MDELETED); setdot(message + (last != 0 ? last - 1 : 0)); } jleave: NYD2_OU; return (success == FAL0); } FL int c_save(void *vp){ int rv; NYD_IN; rv = a_cwrite_save1(vp, n_IGNORE_SAVE, SEND_MBOX, TRU1, FAL0); NYD_OU; return rv; } FL int c_Save(void *vp){ int rv; NYD_IN; rv = a_cwrite_save1(vp, n_IGNORE_SAVE, SEND_MBOX, TRU1, FAL0); NYD_OU; return rv; } FL int c_copy(void *vp){ int rv; NYD_IN; rv = a_cwrite_save1(vp, n_IGNORE_SAVE, SEND_MBOX, FAL0, FAL0); NYD_OU; return rv; } FL int c_Copy(void *vp){ int rv; NYD_IN; rv = a_cwrite_save1(vp, n_IGNORE_SAVE, SEND_MBOX, FAL0, FAL0); NYD_OU; return rv; } FL int c_move(void *vp){ int rv; NYD_IN; rv = a_cwrite_save1(vp, n_IGNORE_SAVE, SEND_MBOX, FAL0, TRU1); NYD_OU; return rv; } FL int c_Move(void *vp){ int rv; NYD_IN; rv = a_cwrite_save1(vp, n_IGNORE_SAVE, SEND_MBOX, FAL0, TRU1); NYD_OU; return rv; } FL int c_decrypt(void *vp){ int rv; NYD_IN; rv = a_cwrite_save1(vp, n_IGNORE_SAVE, SEND_DECRYPT, FAL0, FAL0); NYD_OU; return rv; } FL int c_Decrypt(void *vp){ int rv; NYD_IN; rv = a_cwrite_save1(vp, n_IGNORE_SAVE, SEND_DECRYPT, FAL0, FAL0); NYD_OU; return rv; } FL int c_write(void *vp){ int rv; struct n_cmd_arg *cap; struct n_cmd_arg_ctx *cacp; NYD_IN; if((cap = (cacp = vp)->cac_arg->ca_next)->ca_arg.ca_str.s[0] == '\0') cap->ca_arg.ca_str.s = savestrbuf(n_path_devnull, cap->ca_arg.ca_str.l = su_cs_len(n_path_devnull)); rv = a_cwrite_save1(vp, n_IGNORE_ALL, SEND_TOFILE, FAL0, FAL0); NYD_OU; return rv; } #include "su/code-ou.h" /* s-it-mode */ s-nail-14.9.15/src/mx/collect.c000066400000000000000000001770551352610246600161060ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Collect input from standard input, handling ~ escapes. *@ TODO This needs a complete rewrite, with carriers, etc. * * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany. * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1980, 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. */ #undef su_FILE #define su_FILE collect #define mx_SOURCE #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif #include #include #include #include "mx/child.h" #include "mx/dig-msg.h" #include "mx/file-streams.h" #include "mx/filter-quote.h" #include "mx/names.h" #include "mx/sigs.h" #include "mx/tty.h" #include "mx/ui-str.h" /* TODO fake */ #include "su/code-in.h" struct a_coll_fmt_ctx{ /* xxx This is temporary until v15 has objects */ char const *cfc_fmt; FILE *cfc_fp; struct message *cfc_mp; char *cfc_cumul; char *cfc_addr; char *cfc_real; char *cfc_full; char *cfc_date; char const *cfc_msgid; /* Or NULL */ }; struct a_coll_ocs_arg{ n_sighdl_t coa_opipe; n_sighdl_t coa_oint; FILE *coa_stdin; /* The descriptor (pipe(2)+fs_fd_open()) we read from */ FILE *coa_stdout; /* The pipe_open() through which we write to the hook */ sz coa_pipe[2]; /* ..backing .coa_stdin */ s8 *coa_senderr; /* Set to 1 on failure */ char coa_cmd[VFIELD_SIZE(0)]; }; /* The following hookiness with global variables is so that on receipt of an * interrupt signal, the partial message can be salted away on *DEAD* */ static n_sighdl_t _coll_saveint; /* Previous SIGINT value */ static n_sighdl_t _coll_savehup; /* Previous SIGHUP value */ static FILE *_coll_fp; /* File for saving away */ static int volatile _coll_hadintr; /* Have seen one SIGINT so far */ static sigjmp_buf _coll_jmp; /* To get back to work */ static sigjmp_buf _coll_abort; /* To end collection with error */ static char const *a_coll_ocs__macname; /* *on-compose-splice* */ /* Handle `~:', `~_' and some hooks; hp may be NULL */ static void _execute_command(struct header *hp, char const *linebuf, uz linesize); /* Return errno */ static s32 a_coll_include_file(char const *name, boole indent, boole writestat); /* Execute cmd and insert its standard output into fp, return errno */ static s32 a_coll_insert_cmd(FILE *fp, char const *cmd); /* ~p command */ static void print_collf(FILE *collf, struct header *hp); /* Write a file, ex-like if f set */ static s32 a_coll_write(char const *name, FILE *fp, int f); /* *message-inject-head* */ static boole a_coll_message_inject_head(FILE *fp); /* With bells and whistles */ static boole a_coll_quote_message(FILE *fp, struct message *mp, boole isfwd); /* *{forward,quote}-inject-{head,tail}*. * fmt may be NULL or the empty string, in which case no output is produced */ static boole a_coll__fmt_inj(struct a_coll_fmt_ctx const *cfcp); /* Parse off the message header from fp and store relevant fields in hp, * replace _coll_fp with a shiny new version without any header. * Takes care for closing of fp and _coll_fp as necessary */ static boole a_coll_makeheader(FILE *fp, struct header *hp, s8 *checkaddr_err, boole do_delayed_due_t); /* Edit the message being collected on fp. * If c=='|' pipecmd must be set and is passed through to n_run_editor(). * On successful return, make the edit file the new temp file; return errno */ static s32 a_coll_edit(int c, struct header *hp, char const *pipecmd); /* Pipe the message through the command. Old message is on stdin of command, * new message collected from stdout. Shell must return 0 to accept new msg */ static s32 a_coll_pipe(char const *cmd); /* Interpolate the named messages into the current message, possibly doing * indent stuff. The flag argument is one of the command escapes: [mMfFuU]. * Return errno */ static s32 a_coll_forward(char const *ms, FILE *fp, int f); /* On interrupt, come here to save the partial message in ~/dead.letter. * Then jump out of the collection loop */ static void _collint(int s); static void collhup(int s); /* ~[AaIi], *message-inject-**: put value, expand \[nt] if *posix* */ static boole a_coll_putesc(char const *s, boole addnl, FILE *stream); /* *on-compose-splice* driver and *on-compose-splice(-shell)?* finalizer */ static int a_coll_ocs__mac(void); static void a_coll_ocs__finalize(void *vp); static void _execute_command(struct header *hp, char const *linebuf, uz linesize){ /* The problem arises if there are rfc822 message attachments and the * user uses `~:' to change the current file. TODO Unfortunately we * TODO cannot simply keep a pointer to, or increment a reference count * TODO of the current `file' (mailbox that is) object, because the * TODO codebase doesn't deal with that at all; so, until some far * TODO later time, copy the name of the path, and warn the user if it * TODO changed; we COULD use the AC_TMPFILE attachment type, i.e., * TODO copy the message attachments over to temporary files, but that * TODO would require more changes so that the user still can recognize * TODO in `~@' etc. that its a rfc822 message attachment; see below */ struct n_sigman sm; struct attachment *ap; char * volatile mnbuf; NYD_IN; UNUSED(linesize); mnbuf = NULL; n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_HUP | n_SIGMAN_INT | n_SIGMAN_QUIT){ case 0: break; default: n_pstate_err_no = su_ERR_INTR; n_pstate_ex_no = 1; goto jleave; } /* If the above todo is worked, remove or outsource to attachment.c! */ if(hp != NULL && (ap = hp->h_attach) != NULL) do if(ap->a_msgno){ mnbuf = su_cs_dup(mailname, 0); break; } while((ap = ap->a_flink) != NULL); n_go_command(n_GO_INPUT_CTX_COMPOSE, linebuf); n_sigman_cleanup_ping(&sm); jleave: if(mnbuf != NULL){ if(su_cs_cmp(mnbuf, mailname)) n_err(_("Mailbox changed: it is likely that existing " "rfc822 attachments became invalid!\n")); n_free(mnbuf); } NYD_OU; n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT); } static s32 a_coll_include_file(char const *name, boole indent, boole writestat){ FILE *fbuf; char const *heredb, *indb; uz linesize, heredl, indl, cnt, linelen; char *linebuf; s64 lc, cc; s32 rv; NYD_IN; rv = su_ERR_NONE; lc = cc = 0; mx_fs_linepool_aquire(&linebuf, &linesize); heredb = NULL; heredl = 0; /* The -M case is special */ if(name == (char*)-1){ fbuf = n_stdin; name = n_hy; }else if(name[0] == '-' && (name[1] == '\0' || su_cs_is_space(name[1]))){ fbuf = n_stdin; if(name[1] == '\0'){ if(!(n_psonce & n_PSO_INTERACTIVE)){ n_err(_("~< -: HERE-delimiter required in non-interactive mode\n")); rv = su_ERR_INVAL; goto jleave; } }else{ for(heredb = &name[2]; *heredb != '\0' && su_cs_is_space(*heredb); ++heredb) ; if((heredl = su_cs_len(heredb)) == 0){ jdelim_empty: n_err(_("~< - HERE-delimiter: delimiter must not be empty\n")); rv = su_ERR_INVAL; goto jleave; } if(*heredb == '\''){ for(indb = ++heredb; *indb != '\0' && *indb != '\''; ++indb) ; if(*indb == '\0'){ n_err(_("~< - HERE-delimiter: missing trailing quote\n")); rv = su_ERR_INVAL; goto jleave; }else if(indb[1] != '\0'){ n_err(_("~< - HERE-delimiter: trailing characters after " "quote\n")); rv = su_ERR_INVAL; goto jleave; } if((heredl = P2UZ(indb - heredb)) == 0) goto jdelim_empty; heredb = savestrbuf(heredb, heredl); } } name = n_hy; }else if((fbuf = mx_fs_open(name, "r")) == NIL){ n_perr(name, rv = su_err_no()); goto jleave; } indl = indent ? su_cs_len(indb = ok_vlook(indentprefix)) : 0; if(fbuf != n_stdin) cnt = fsize(fbuf); while(fgetline(&linebuf, &linesize, (fbuf == n_stdin ? NULL : &cnt), &linelen, fbuf, 0) != NULL){ if(heredl > 0 && heredl == linelen - 1 && !su_mem_cmp(heredb, linebuf, heredl)){ heredb = NULL; break; } if(indl > 0){ if(fwrite(indb, sizeof *indb, indl, _coll_fp) != indl){ rv = su_err_no(); goto jleave; } cc += indl; } if(fwrite(linebuf, sizeof *linebuf, linelen, _coll_fp) != linelen){ rv = su_err_no(); goto jleave; } cc += linelen; ++lc; } if(fflush(_coll_fp)){ rv = su_err_no(); goto jleave; } if(heredb != NULL) rv = su_ERR_NOTOBACCO; jleave: mx_fs_linepool_release(linebuf, linesize); if(fbuf != NIL){ if(fbuf != n_stdin) mx_fs_close(fbuf); else if(heredl > 0) clearerr(n_stdin); } if(writestat) fprintf(n_stdout, "%s%s %" PRId64 "/%" PRId64 "\n", n_shexp_quote_cp(name, FAL0), (rv ? " " n_ERROR : n_empty), lc, cc); NYD_OU; return rv; } static s32 a_coll_insert_cmd(FILE *fp, char const *cmd){ FILE *ibuf; s64 lc, cc; s32 rv; NYD_IN; rv = su_ERR_NONE; lc = cc = 0; if((ibuf = mx_fs_pipe_open(cmd, "r", ok_vlook(SHELL), NIL, -1)) != NIL){ int c; while((c = getc(ibuf)) != EOF){ /* XXX bytewise, yuck! */ if(putc(c, fp) == EOF){ rv = su_err_no(); break; } ++cc; if(c == '\n') ++lc; } if(!feof(ibuf) || ferror(ibuf)){ if(rv == su_ERR_NONE) rv = su_ERR_IO; } if(!mx_fs_pipe_close(ibuf, TRU1)){ if(rv == su_ERR_NONE) rv = su_ERR_IO; } }else n_perr(cmd, rv = su_err_no()); fprintf(n_stdout, "CMD%s %" PRId64 "/%" PRId64 "\n", (rv == su_ERR_NONE ? n_empty : " " n_ERROR), lc, cc); NYD_OU; return rv; } static void print_collf(FILE *cf, struct header *hp) { char *lbuf; FILE *obuf; uz cnt, linesize, linelen; NYD_IN; fflush_rewind(cf); cnt = S(uz,fsize(cf)); if((obuf = mx_fs_tmp_open("collfp", (mx_FS_O_RDWR | mx_FS_O_UNLINK | mx_FS_O_REGISTER), NIL)) == NIL){ n_perr(_("Can't create temporary file for `~p' command"), 0); goto jleave; } mx_sigs_all_holdx(); fprintf(obuf, _("-------\nMessage contains:\n")); /* XXX112 */ n_puthead(TRU1, hp, obuf, (GIDENT | GTO | GSUBJECT | GCC | GBCC | GBCC_IS_FCC | GNL | GFILES | GCOMMA), SEND_TODISP, CONV_NONE, NULL, NULL); lbuf = NULL; linesize = 0; while(fgetline(&lbuf, &linesize, &cnt, &linelen, cf, 1)) prout(lbuf, linelen, obuf); if(lbuf != NULL) n_free(lbuf); if(hp->h_attach != NULL){ fputs(_("-------\nAttachments:\n"), obuf); n_attachment_list_print(hp->h_attach, obuf); } mx_sigs_all_rele(); page_or_print(obuf, 0); mx_fs_close(obuf); jleave: NYD_OU; } static s32 a_coll_write(char const *name, FILE *fp, int f) { FILE *of; int c; s64 lc, cc; s32 rv; NYD_IN; rv = su_ERR_NONE; if(f) { fprintf(n_stdout, "%s ", n_shexp_quote_cp(name, FAL0)); fflush(n_stdout); } if((of = mx_fs_open(name, "a")) == NIL){ n_perr(name, rv = su_err_no()); goto jerr; } lc = cc = 0; while ((c = getc(fp)) != EOF) { ++cc; if (c == '\n') ++lc; if (putc(c, of) == EOF) { n_perr(name, rv = su_err_no()); goto jerr; } } fprintf(n_stdout, "%" PRId64 "/%" PRId64 "\n", lc, cc); jleave: if(of != NIL) mx_fs_close(of); fflush(n_stdout); NYD_OU; return rv; jerr: putc('-', n_stdout); putc('\n', n_stdout); goto jleave; } static boole a_coll_message_inject_head(FILE *fp){ boole rv; char const *cp, *cp_obsolete; NYD2_IN; cp_obsolete = ok_vlook(NAIL_HEAD); if(cp_obsolete != NULL) n_OBSOLETE(_("please use *message-inject-head*, not *NAIL_HEAD*")); if(((cp = ok_vlook(message_inject_head)) != NULL || (cp = cp_obsolete) != NULL) && !a_coll_putesc(cp, TRU1, fp)) rv = FAL0; else rv = TRU1; NYD2_OU; return rv; } static boole a_coll_quote_message(FILE *fp, struct message *mp, boole isfwd){ struct a_coll_fmt_ctx cfc; char const *cp; struct n_ignore const *quoteitp; enum sendaction action; boole rv; NYD_IN; rv = FAL0; if(isfwd || (cp = ok_vlook(quote)) != NULL){ quoteitp = n_IGNORE_ALL; action = SEND_QUOTE; if(isfwd){ char const *cp_v15compat; if((cp_v15compat = ok_vlook(fwdheading)) != NULL) n_OBSOLETE(_("please use *forward-inject-head* instead of " "*fwdheading*")); if((cp = ok_vlook(forward_inject_head)) == NULL && (cp = cp_v15compat) == NULL) cp = n_FORWARD_INJECT_HEAD; /* v15compat: make auto-defval */ quoteitp = n_IGNORE_FWD; }else{ if(!su_cs_cmp(cp, "noheading")){ cp = NULL; }else if(!su_cs_cmp(cp, "headers")){ quoteitp = n_IGNORE_TYPE; cp = NULL; }else if(!su_cs_cmp(cp, "allheaders")){ quoteitp = NULL; action = SEND_QUOTE_ALL; cp = NULL; }else if((cp = ok_vlook(quote_inject_head)) == NULL) cp = n_QUOTE_INJECT_HEAD; /* v15compat: make auto-defval */ } /* We we pass through our formatter? */ if((cfc.cfc_fmt = cp) != NULL){ /* TODO In v15 [-textual_-]sender_info() should only create a list * TODO of matching header objects, and the formatter should simply * TODO iterate over this list and call OBJ->to_ui_str(FLAGS) or so. * TODO For now fully initialize this thing once (grrrr!!) */ cfc.cfc_fp = fp; cfc.cfc_mp = mp; n_header_textual_sender_info(cfc.cfc_mp = mp, &cfc.cfc_cumul, &cfc.cfc_addr, &cfc.cfc_real, &cfc.cfc_full, NULL); cfc.cfc_date = n_header_textual_date_info(mp, NULL); /* C99 */{ struct mx_name *np; char const *msgid; if((msgid = hfield1("message-id", mp)) != NULL && (np = lextract(msgid, GREF)) != NULL) msgid = np->n_name; else msgid = NULL; cfc.cfc_msgid = msgid; } if(!a_coll__fmt_inj(&cfc) || fflush(fp)) goto jleave; } if(sendmp(mp, fp, quoteitp, (isfwd ? NULL : ok_vlook(indentprefix)), action, NULL) < 0) goto jleave; if(isfwd){ if((cp = ok_vlook(forward_inject_tail)) == NULL) cp = n_FORWARD_INJECT_TAIL; }else if(cp != NULL && (cp = ok_vlook(quote_inject_tail)) == NULL) cp = n_QUOTE_INJECT_TAIL; if((cfc.cfc_fmt = cp) != NULL && (!a_coll__fmt_inj(&cfc) || fflush(fp))) goto jleave; } rv = TRU1; jleave: NYD_OU; return rv; } static boole a_coll__fmt_inj(struct a_coll_fmt_ctx const *cfcp){ struct quoteflt qf; struct n_string s_b, *s; char c; char const *fmt; NYD_IN; if((fmt = cfcp->cfc_fmt) == NULL || *fmt == '\0') goto jleave; s = n_string_book(n_string_creat_auto(&s_b), 127); while((c = *fmt++) != '\0'){ if(c != '%' || (c = *fmt++) == '%'){ jwrite_char: s = n_string_push_c(s, c); }else switch(c){ case 'a': s = n_string_push_cp(s, cfcp->cfc_addr); break; case 'd': s = n_string_push_cp(s, cfcp->cfc_date); break; case 'f': s = n_string_push_cp(s, cfcp->cfc_full); break; case 'i': if(cfcp->cfc_msgid != NULL) s = n_string_push_cp(s, cfcp->cfc_msgid); break; case 'n': s = n_string_push_cp(s, cfcp->cfc_cumul); break; case 'r': s = n_string_push_cp(s, cfcp->cfc_real); break; case '\0': --fmt; c = '%'; goto jwrite_char; default: n_err(_("*{forward,quote}-inject-{head,tail}*: " "unknown format: %c (in: %s)\n"), c, n_shexp_quote_cp(cfcp->cfc_fmt, FAL0)); goto jwrite_char; } } quoteflt_init(&qf, NULL, FAL0); quoteflt_reset(&qf, cfcp->cfc_fp); if(quoteflt_push(&qf, s->s_dat, s->s_len) < 0 || quoteflt_flush(&qf) < 0) cfcp = NULL; quoteflt_destroy(&qf); /*n_string_gut(s);*/ jleave: NYD_OU; return (cfcp != NULL); } static boole a_coll_makeheader(FILE *fp, struct header *hp, s8 *checkaddr_err, boole do_delayed_due_t) { FILE *nf; int c; boole rv; NYD_IN; rv = FAL0; if((nf = mx_fs_tmp_open("colhead", (mx_FS_O_RDWR | mx_FS_O_UNLINK | mx_FS_O_REGISTER), NIL)) == NIL){ n_perr(_("temporary mail edit file"), 0); goto jleave; } n_header_extract(((do_delayed_due_t ? n_HEADER_EXTRACT_FULL | n_HEADER_EXTRACT_PREFILL_RECEIVERS : n_HEADER_EXTRACT_EXTENDED) | n_HEADER_EXTRACT_IGNORE_SHELL_COMMENTS), fp, hp, checkaddr_err); if (checkaddr_err != NULL && *checkaddr_err != 0) goto jleave; /* In template mode some things have been delayed until the template has * been read */ if(do_delayed_due_t){ char const *cp; if((cp = ok_vlook(on_compose_enter)) != NULL){ setup_from_and_sender(hp); temporary_compose_mode_hook_call(cp, &n_temporary_compose_hook_varset, hp); } if(!a_coll_message_inject_head(nf)) goto jleave; } while ((c = getc(fp)) != EOF) /* XXX bytewise, yuck! */ putc(c, nf); if(fp != _coll_fp) mx_fs_close(_coll_fp); mx_fs_close(fp); _coll_fp = nf; nf = NIL; if (check_from_and_sender(hp->h_from, hp->h_sender) == NULL) goto jleave; rv = TRU1; jleave: if(nf != NIL) mx_fs_close(nf); NYD_OU; return rv; } static s32 a_coll_edit(int c, struct header *hp, char const *pipecmd) /* TODO errret */ { struct n_sigman sm; FILE *nf; n_sighdl_t volatile sigint; struct mx_name *saved_in_reply_to; boole saved_filrec; s32 volatile rv; NYD_IN; rv = su_ERR_NONE; saved_filrec = ok_blook(add_file_recipients); n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){ case 0: sigint = safe_signal(SIGINT, SIG_IGN); break; default: sigint = SIG_ERR; rv = su_ERR_INTR; goto jleave; } if(!saved_filrec) ok_bset(add_file_recipients); saved_in_reply_to = NULL; if(hp != NULL){ struct mx_name *np; if((np = hp->h_in_reply_to) == NULL) hp->h_in_reply_to = np = n_header_setup_in_reply_to(hp); if(np != NULL) saved_in_reply_to = ndup(np, np->n_type); } rewind(_coll_fp); nf = n_run_editor(_coll_fp, (off_t)-1, c, FAL0, hp, NULL, SEND_MBOX, sigint, pipecmd); if(nf != NULL){ if(hp != NULL){ /* Overtaking of nf->_coll_fp is done by a_coll_makeheader()! */ if(!a_coll_makeheader(nf, hp, NULL, FAL0)) rv = su_ERR_INVAL; /* Break the thread if In-Reply-To: has been modified */ if(hp->h_in_reply_to == NULL || (saved_in_reply_to != NULL && su_cs_cmp_case(hp->h_in_reply_to->n_fullname, saved_in_reply_to->n_fullname))){ hp->h_ref = NULL; /* Create a thread of only the replied-to message if it is - */ if(hp->h_in_reply_to != NULL && !su_cs_cmp(hp->h_in_reply_to->n_fullname, n_hy)) hp->h_in_reply_to = hp->h_ref = saved_in_reply_to; } }else{ fseek(nf, 0L, SEEK_END); mx_fs_close(_coll_fp); _coll_fp = nf; } }else rv = su_ERR_CHILD; n_sigman_cleanup_ping(&sm); jleave: if(!saved_filrec) ok_bclear(add_file_recipients); if(sigint != SIG_ERR) safe_signal(SIGINT, sigint); NYD_OU; n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT); return rv; } static s32 a_coll_pipe(char const *cmd) { FILE *nf; n_sighdl_t sigint; s32 rv; NYD_IN; rv = su_ERR_NONE; sigint = safe_signal(SIGINT, SIG_IGN); if((nf = mx_fs_tmp_open("colpipe", (mx_FS_O_RDWR | mx_FS_O_UNLINK | mx_FS_O_REGISTER), NIL)) == NIL){ jperr: n_perr(_("temporary mail edit file"), rv = su_err_no()); goto jout; } /* stdin = current message. stdout = new message */ if(fflush(_coll_fp) == EOF) goto jperr; rewind(_coll_fp); /* C99 */{ struct mx_child_ctx cc; mx_child_ctx_setup(&cc); cc.cc_flags = mx_CHILD_RUN_WAIT_LIFE; cc.cc_fds[mx_CHILD_FD_IN] = fileno(_coll_fp); cc.cc_fds[mx_CHILD_FD_OUT] = fileno(nf); cc.cc_cmd = ok_vlook(SHELL); cc.cc_args[0] = "-c"; cc.cc_args[1] = cmd; if(!mx_child_run(&cc) || cc.cc_exit_status != 0){ mx_fs_close(nf); rv = su_ERR_CHILD; goto jout; } } if (fsize(nf) == 0) { n_err(_("No bytes from %s !?\n"), n_shexp_quote_cp(cmd, FAL0)); mx_fs_close(nf); rv = su_ERR_NODATA; goto jout; } /* Take new files */ fseek(nf, 0L, SEEK_END); mx_fs_close(_coll_fp); _coll_fp = nf; jout: safe_signal(SIGINT, sigint); NYD_OU; return rv; } static s32 a_coll_forward(char const *ms, FILE *fp, int f) { int *msgvec, rv = 0; struct n_ignore const *itp; char const *tabst; enum sendaction action; NYD_IN; if ((rv = n_getmsglist(ms, n_msgvec, 0, NULL)) < 0) { rv = su_ERR_NOENT; /* XXX not really, should be handled there! */ goto jleave; } if (rv == 0) { *n_msgvec = first(0, MMNORM); if (*n_msgvec == 0) { n_err(_("No appropriate messages\n")); rv = su_ERR_NOENT; goto jleave; } rv = 1; } msgvec = n_autorec_calloc(rv +1, sizeof *msgvec); while(rv-- > 0) msgvec[rv] = n_msgvec[rv]; rv = 0; if (f == 'f' || f == 'F' || f == 'u') tabst = NULL; else tabst = ok_vlook(indentprefix); if (f == 'u' || f == 'U') itp = n_IGNORE_ALL; else itp = su_cs_is_upper(f) ? NULL : n_IGNORE_TYPE; action = (su_cs_is_upper(f) && f != 'U') ? SEND_QUOTE_ALL : SEND_QUOTE; fprintf(n_stdout, A_("Interpolating:")); srelax_hold(); for(; *msgvec != 0; ++msgvec){ struct message *mp; mp = &message[*msgvec - 1]; touch(mp); fprintf(n_stdout, " %d", *msgvec); fflush(n_stdout); if(f == 'Q'){ if(!a_coll_quote_message(fp, mp, FAL0)){ rv = su_ERR_IO; break; } }else if(sendmp(mp, fp, itp, tabst, action, NULL) < 0){ n_perr(_("forward: temporary mail file"), 0); rv = su_ERR_IO; break; } srelax(); } srelax_rele(); fprintf(n_stdout, "\n"); jleave: NYD_OU; return rv; } static void _collint(int s) { NYD; /* Signal handler */ /* the control flow is subtle, because we can be called from ~q */ if (_coll_hadintr == 0) { if (ok_blook(ignore)) { fputs("@\n", n_stdout); fflush(n_stdout); clearerr(n_stdin); } else _coll_hadintr = 1; siglongjmp(_coll_jmp, 1); } n_exit_status |= n_EXIT_SEND_ERROR; if (s != 0) savedeadletter(_coll_fp, TRU1); /* Aborting message, no need to fflush() .. */ siglongjmp(_coll_abort, 1); } static void collhup(int s) { NYD; /* Signal handler */ UNUSED(s); savedeadletter(_coll_fp, TRU1); /* Let's pretend nobody else wants to clean up, a true statement at * this time */ exit(n_EXIT_ERR); } static boole a_coll_putesc(char const *s, boole addnl, FILE *stream){ char c1, c2; boole isposix; NYD2_IN; isposix = ok_blook(posix); while((c1 = *s++) != '\0'){ if(c1 == '\\' && ((c2 = *s) == 't' || c2 == 'n')){ if(!isposix){ isposix = TRU1; /* TODO v15 OBSOLETE! */ n_err(_("Compose mode warning: expanding \\t or \\n in variable " "without *posix*!\n" " Support remains only for ~A,~a,~I,~i in *posix* mode!\n" " Please use \"wysh set X=y..\" instead\n")); } ++s; c1 = (c2 == 't') ? '\t' : '\n'; } if(putc(c1, stream) == EOF) goto jleave; } if(addnl && putc('\n', stream) == EOF) goto jleave; jleave: NYD2_OU; return (c1 == '\0'); } static int a_coll_ocs__mac(void){ /* Executes in a fork(2)ed child TODO if remains, global MASKs for those! */ setvbuf(n_stdin, NULL, _IOLBF, 0); setvbuf(n_stdout, NULL, _IOLBF, 0); n_psonce &= ~(n_PSO_INTERACTIVE | n_PSO_TTYANY); n_pstate |= n_PS_COMPOSE_FORKHOOK; n_readctl_read_overlay = NULL; /* TODO need OnForkEvent! See c_readctl() */ mx_dig_msg_read_overlay = NIL; /* TODO need OnForkEvent! See c_digmsg() */ if(n_poption & n_PO_D_VV){ char buf[128]; snprintf(buf, sizeof buf, "[%d]%s", getpid(), ok_vlook(log_prefix)); ok_vset(log_prefix, buf); } /* TODO If that uses `!' it will effectively SIG_IGN SIGINT, ...and such */ temporary_compose_mode_hook_call(a_coll_ocs__macname, NULL, NULL); return 0; } static void a_coll_ocs__finalize(void *vp){ /* Note we use this for destruction upon setup errors, thus */ n_sighdl_t opipe; n_sighdl_t oint; struct a_coll_ocs_arg **coapp, *coap; NYD2_IN; temporary_compose_mode_hook_call((char*)-1, NULL, NULL); coap = *(coapp = vp); *coapp = (struct a_coll_ocs_arg*)-1; if(coap->coa_stdin != NIL) mx_fs_close(coap->coa_stdin); else if(coap->coa_pipe[0] != -1) close(S(int,coap->coa_pipe[0])); if(coap->coa_stdout != NIL && !mx_fs_pipe_close(coap->coa_stdout, TRU1)) *coap->coa_senderr = 1; if(coap->coa_pipe[1] != -1) close(S(int,coap->coa_pipe[1])); opipe = coap->coa_opipe; oint = coap->coa_oint; n_lofi_free(coap); mx_sigs_all_holdx(); safe_signal(SIGPIPE, opipe); safe_signal(SIGINT, oint); mx_sigs_all_rele(); NYD2_OU; } FL void n_temporary_compose_hook_varset(void *arg){ /* TODO v15: drop */ struct header *hp; char const *val; NYD2_IN; hp = arg; if((val = hp->h_subject) == NULL) val = n_empty; ok_vset(mailx_subject, val); if((val = detract(hp->h_from, GNAMEONLY)) == NULL) val = n_empty; ok_vset(mailx_from, val); if((val = detract(hp->h_sender, GNAMEONLY)) == NULL) val = n_empty; ok_vset(mailx_sender, val); if((val = detract(hp->h_to, GNAMEONLY)) == NULL) val = n_empty; ok_vset(mailx_to, val); if((val = detract(hp->h_cc, GNAMEONLY)) == NULL) val = n_empty; ok_vset(mailx_cc, val); if((val = detract(hp->h_bcc, GNAMEONLY)) == NULL) val = n_empty; ok_vset(mailx_bcc, val); if((val = hp->h_mailx_command) == NULL) val = n_empty; ok_vset(mailx_command, val); if((val = detract(hp->h_mailx_raw_to, GNAMEONLY)) == NULL) val = n_empty; ok_vset(mailx_raw_to, val); if((val = detract(hp->h_mailx_raw_cc, GNAMEONLY)) == NULL) val = n_empty; ok_vset(mailx_raw_cc, val); if((val = detract(hp->h_mailx_raw_bcc, GNAMEONLY)) == NULL) val = n_empty; ok_vset(mailx_raw_bcc, val); if((val = detract(hp->h_mailx_orig_from, GNAMEONLY)) == NULL) val = n_empty; ok_vset(mailx_orig_from, val); if((val = detract(hp->h_mailx_orig_to, GNAMEONLY)) == NULL) val = n_empty; ok_vset(mailx_orig_to, val); if((val = detract(hp->h_mailx_orig_cc, GNAMEONLY)) == NULL) val = n_empty; ok_vset(mailx_orig_cc, val); if((val = detract(hp->h_mailx_orig_bcc, GNAMEONLY)) == NULL) val = n_empty; ok_vset(mailx_orig_bcc, val); NYD2_OU; } FL FILE * n_collect(enum n_mailsend_flags msf, struct header *hp, struct message *mp, char const *quotefile, s8 *checkaddr_err) { enum{ a_NONE, a_ERREXIT = 1u<<0, a_IGNERR = 1u<<1, #define a_HARDERR() ((flags & (a_ERREXIT | a_IGNERR)) == a_ERREXIT) a_COAP_NOSIGTERM = 1u<<8, a_NEED_INJECT_RESTART = 1u<<16, a_CAN_DELAY_INJECT = 1u<<17, a_EVER_LEFT_INPUT_LOOPS = 1u<<18, a_ROUND_MASK = a_NEED_INJECT_RESTART | a_CAN_DELAY_INJECT | a_EVER_LEFT_INPUT_LOOPS }; struct mx_dig_msg_ctx dmc; struct n_string s_b, * volatile s; struct a_coll_ocs_arg *coap; int c; int volatile gfield, eofcnt, getfields; char volatile escape; char *linebuf; char const *cp, *cp_base, * volatile coapm, * volatile ifs_saved; uz i, linesize; long cnt; sigset_t oset, nset; u32 volatile flags; FILE * volatile sigfp; NYD_IN; mx_DIG_MSG_COMPOSE_CREATE(&dmc, hp); _coll_fp = NULL; sigfp = NULL; flags = a_CAN_DELAY_INJECT; eofcnt = 0; ifs_saved = coapm = NULL; coap = NULL; s = NULL; mx_fs_linepool_aquire(&linebuf, &linesize); /* Start catching signals from here, but we still die on interrupts * until we're in the main loop */ sigfillset(&nset); sigprocmask(SIG_BLOCK, &nset, &oset); if ((_coll_saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN) safe_signal(SIGINT, &_collint); if ((_coll_savehup = safe_signal(SIGHUP, SIG_IGN)) != SIG_IGN) safe_signal(SIGHUP, collhup); if (sigsetjmp(_coll_abort, 1)) goto jerr; if (sigsetjmp(_coll_jmp, 1)) goto jerr; n_pstate |= n_PS_COMPOSE_MODE; sigprocmask(SIG_SETMASK, &oset, (sigset_t*)NULL); if((_coll_fp = mx_fs_tmp_open("collect", (mx_FS_O_RDWR | mx_FS_O_UNLINK | mx_FS_O_REGISTER), NIL)) == NIL){ n_perr(_("collect: temporary mail file"), 0); goto jerr; } /* If we are going to prompt for a subject, refrain from printing a newline * after the headers (since some people mind) */ getfields = 0; if(!(n_poption & n_PO_t_FLAG)){ gfield = GTO | GSUBJECT | GCC | GBCC | GNL; if(ok_blook(fullnames)) gfield |= GCOMMA; if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT)){ if(hp->h_subject == NIL && ok_blook(asksub)/* *ask* auto warped! */) gfield &= ~(GSUBJECT | GNL), getfields |= GSUBJECT; if(hp->h_to == NULL) gfield &= ~(GTO | GNL), getfields |= GTO; if(!ok_blook(bsdcompat) && !ok_blook(askatend)){ if(ok_blook(askbcc)) gfield &= ~(GBCC | GNL), getfields |= GBCC; if(ok_blook(askcc)) gfield &= ~(GCC | GNL), getfields |= GCC; } } }else{ UNINIT(gfield, 0); } _coll_hadintr = 0; if (!sigsetjmp(_coll_jmp, 1)) { /* Ask for some headers first, as necessary */ if(getfields) grab_headers(n_GO_INPUT_CTX_COMPOSE, hp, getfields, 1); /* Execute compose-enter; delayed for -t mode */ if(!(n_poption & n_PO_t_FLAG) && (cp = ok_vlook(on_compose_enter)) != NULL){ setup_from_and_sender(hp); temporary_compose_mode_hook_call(cp, &n_temporary_compose_hook_varset, hp); } /* TODO Mm: nope since it may require turning this into a multipart one */ if(!(n_poption & (n_PO_Mm_FLAG | n_PO_t_FLAG))){ if(!a_coll_message_inject_head(_coll_fp)) goto jerr; /* Quote an original message */ if(mp != NULL && !a_coll_quote_message(_coll_fp, mp, ((msf & n_MAILSEND_IS_FWD) != 0))) goto jerr; } if (quotefile != NULL) { if((n_pstate_err_no = a_coll_include_file(quotefile, FAL0, FAL0) ) != su_ERR_NONE) goto jerr; } if(n_psonce & n_PSO_INTERACTIVE){ if(!(n_pstate & n_PS_ROBOT)){ s = n_string_creat_auto(&s_b); s = n_string_reserve(s, 80); } if(!(n_poption & n_PO_Mm_FLAG) && !(n_pstate & n_PS_ROBOT)){ /* Print what we have sofar also on the terminal (if useful) */ if(n_go_input_have_injections()){ flags |= a_NEED_INJECT_RESTART; flags &= ~a_CAN_DELAY_INJECT; }else{ jinject_restart: if((cp = ok_vlook(editalong)) == NIL){ if(msf & n_MAILSEND_HEADERS_PRINT) n_puthead(TRU1, hp, n_stdout, gfield, SEND_TODISP, CONV_NONE, NIL, NIL); rewind(_coll_fp); while((c = getc(_coll_fp)) != EOF) /* XXX bytewise, yuck! */ putc(c, n_stdout); if(fseek(_coll_fp, 0, SEEK_END)) goto jerr; fflush(n_stdout); }else{ if(a_coll_edit(((*cp == 'v') ? 'v' : 'e'), hp, NIL ) != su_ERR_NONE) goto jerr; /* Print msg mandated by the Mail Reference Manual */ jcont: if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT) && !(flags & a_NEED_INJECT_RESTART)) fputs(_("(continue)\n"), n_stdout); fflush(n_stdout); } } } } } else { /* Come here for printing the after-signal message. Duplicate messages * won't be printed because the write is aborted if we get a SIGTTOU */ if(_coll_hadintr && (n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT)) n_err(_("\n(Interrupt -- one more to kill letter)\n")); } /* If not under shell hook control */ if(coap == NIL){ /* We're done with -M or -m TODO because: we are too stupid yet, above */ if(n_poption & n_PO_Mm_FLAG) goto jout; /* No command escapes, interrupts not expected? */ if(!(n_psonce & n_PSO_INTERACTIVE) && !(n_poption & (n_PO_t_FLAG | n_PO_TILDE_FLAG))){ /* Need to go over n_go_input() to handle injections nonetheless */ enum n_go_input_flags gif; ASSERT(!(flags & a_NEED_INJECT_RESTART)); for(gif = n_GO_INPUT_CTX_COMPOSE | n_GO_INPUT_DELAY_INJECTIONS;;){ cnt = n_go_input(gif, n_empty, &linebuf, &linesize, NULL, NULL); if(cnt < 0){ if(!n_go_input_is_eof()) goto jerr; if(n_go_input_have_injections()){ gif &= ~n_GO_INPUT_DELAY_INJECTIONS; continue; } break; } i = S(uz,cnt); if(i != fwrite(linebuf, sizeof *linebuf, i, _coll_fp)) goto jerr; /* TODO n_PS_READLINE_NL is a hack to ensure that _in_all_- * TODO _code_paths_ a file without trailing NL isn't modified * TODO to contain one; the "saw-newline" needs to be part of an * TODO I/O input machinery object */ if(n_pstate & n_PS_READLINE_NL){ if(putc('\n', _coll_fp) == EOF) goto jerr; } } goto jout; } escape = *ok_vlook(escape); } /* The "interactive" collect loop */ flags &= a_ROUND_MASK; if(ok_blook(errexit)) flags |= a_ERREXIT; for(;;){ enum {a_HIST_NONE, a_HIST_ADD = 1u<<0, a_HIST_GABBY = 1u<<1} hist; /* C99 */{ enum n_go_input_flags gif; boole histadd; /* TODO optimize: no need to evaluate that anew for each loop tick! */ histadd = (s != NIL); gif = n_GO_INPUT_CTX_COMPOSE; if(flags & a_CAN_DELAY_INJECT) gif |= n_GO_INPUT_DELAY_INJECTIONS; if((n_poption & n_PO_t_FLAG) && !(n_psonce & n_PSO_t_FLAG_DONE)){ ASSERT(!(flags & a_NEED_INJECT_RESTART)); }else{ if(n_psonce & n_PSO_INTERACTIVE){ gif |= n_GO_INPUT_NL_ESC; if(UNLIKELY((flags & a_NEED_INJECT_RESTART) && !n_go_input_have_injections())){ flags &= ~a_NEED_INJECT_RESTART; goto jinject_restart; } }else{ ASSERT(!(flags & a_NEED_INJECT_RESTART)); if(n_poption & n_PO_TILDE_FLAG) gif |= n_GO_INPUT_NL_ESC; } } cnt = n_go_input(gif, n_empty, &linebuf, &linesize, NULL, &histadd); hist = histadd ? a_HIST_ADD | a_HIST_GABBY : a_HIST_NONE; } if(cnt < 0){ /* TODO n_go_input_is_eof()! Could be error!! */ if(coap != NIL) break; if((n_poption & n_PO_t_FLAG) && !(n_psonce & n_PSO_t_FLAG_DONE)){ fflush_rewind(_coll_fp); n_psonce |= n_PSO_t_FLAG_DONE; flags &= ~a_CAN_DELAY_INJECT; if(!a_coll_makeheader(_coll_fp, hp, checkaddr_err, TRU1)) goto jerr; continue; } if((flags & a_CAN_DELAY_INJECT) && n_go_input_have_injections()){ flags &= ~a_CAN_DELAY_INJECT; continue; } if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT) && ok_blook(ignoreeof) && ++eofcnt < 4){ fprintf(n_stdout, _("*ignoreeof* set, use `~.' to terminate letter\n")); n_go_input_clearerr(); continue; } break; } _coll_hadintr = 0; cp = linebuf; if(cnt == 0) goto jputnl; else if(coap == NULL){ if(!(n_psonce & n_PSO_INTERACTIVE) && !(n_poption & n_PO_TILDE_FLAG)) goto jputline; else if(cp[0] == '.'){ if(cnt == 1 && (ok_blook(dot) || (ok_blook(posix) && ok_blook(ignoreeof)))) break; } } if(cp[0] != escape){ jputline: if(fwrite(cp, sizeof *cp, cnt, _coll_fp) != (uz)cnt) goto jerr; /* TODO n_PS_READLINE_NL is a terrible hack to ensure that _in_all_- * TODO _code_paths_ a file without trailing newline isn't modified * TODO to contain one; the "saw-newline" needs to be part of an * TODO I/O input machinery object */ jputnl: if(n_pstate & n_PS_READLINE_NL){ if(putc('\n', _coll_fp) == EOF) goto jerr; } continue; } c = *(cp_base = ++cp); if(--cnt == 0) goto jearg; /* Avoid history entry? */ while(su_cs_is_space(c)){ hist = a_HIST_NONE; c = *(cp_base = ++cp); if(--cnt == 0) goto jearg; } /* It may just be an escaped escaped character, do that quick */ if(c == escape) goto jputline; /* Avoid hard *errexit*? */ flags &= ~a_IGNERR; if(c == '-'){ flags ^= a_IGNERR; c = *++cp; if(--cnt == 0) goto jearg; } /* Trim input, also to gain a somewhat normalized history entry */ ++cp; if(--cnt > 0){ struct str x; x.s = n_UNCONST(cp); x.l = (uz)cnt; n_str_trim_ifs(&x, TRU1); x.s[x.l] = '\0'; cp = x.s; cnt = (int)/*XXX*/x.l; } if(hist != a_HIST_NONE){ s = n_string_assign_c(s, escape); if(flags & a_IGNERR) s = n_string_push_c(s, '-'); s = n_string_push_c(s, c); if(cnt > 0){ s = n_string_push_c(s, ' '); s = n_string_push_buf(s, cp, cnt); } } /* Switch over all command escapes */ switch(c){ default: if(1){ char buf[sizeof(su_UTF8_REPLACER)]; if(su_cs_is_ascii(c)) buf[0] = c, buf[1] = '\0'; else if(n_psonce & n_PSO_UNICODE) su_mem_copy(buf, su_utf8_replacer, sizeof su_utf8_replacer); else buf[0] = '?', buf[1] = '\0'; n_err(_("Unknown command escape: `%c%s'\n"), escape, buf); }else jearg: n_err(_("Invalid command escape usage: %s\n"), n_shexp_quote_cp(linebuf, FAL0)); if(a_HARDERR()) goto jerr; n_pstate_err_no = su_ERR_INVAL; n_pstate_ex_no = 1; continue; case '!': /* Shell escape, send the balance of line to sh -c */ if(cnt == 0 || coap != NULL) goto jearg; else{ char const *argv[2]; argv[0] = cp; argv[1] = NULL; n_pstate_ex_no = c_shell(argv); /* TODO history norm.; errexit? */ } goto jhistcont; case '.': /* Simulate end of file on input */ if(cnt != 0 || coap != NULL) goto jearg; goto jout; /* TODO does not enter history, thus */ case ':': case '_': /* Escape to command mode, but be nice! *//* TODO command expansion * TODO should be handled here so that we have unique history! */ if(cnt == 0) goto jearg; _execute_command(hp, cp, cnt); if(ok_blook(errexit)) flags |= a_ERREXIT; else flags &= ~a_ERREXIT; if(n_pstate_ex_no != 0 && a_HARDERR()) goto jerr; if(coap == NULL) escape = *ok_vlook(escape); hist &= ~a_HIST_GABBY; break; /* case '<': <> 'd' */ case '?': #ifdef mx_HAVE_UISTRINGS fputs(_( "COMMAND ESCAPES (to be placed after a newline) excerpt:\n" "~. Commit and send message\n" "~: Execute an internal command\n" "~< Insert (\"~\": insert shell command)\n" "~@ [] Edit [Add] attachments (file[=in-charset[#out-charset]], #no)\n" "~c Add users to Cc: list (`~b': to Bcc:)\n" "~e, ~v Edit message via $EDITOR / $VISUAL\n" ), n_stdout); fputs(_( "~F Read in with headers, do not *indentprefix* lines\n" "~f Like `~F', but honour `headerpick' configuration\n" "~H Edit From:, Reply-To: and Sender:\n" "~h Prompt for Subject:, To:, Cc: and Bcc:\n" "~i Insert a value and a newline (`~I': insert value)\n" "~M Read in with headers, *indentprefix* (`~m': use `headerpick')\n" "~p Show current message compose buffer\n" "~Q Read in using normal *quote* algorithm\n" ), n_stdout); fputs(_( "~r Insert (`~R': *indentprefix* lines)\n" " may also be <- [HERE-DELIMITER]>\n" "~s Set Subject:\n" "~t Add users to To: list\n" "~u Read in without headers (`~U': *indentprefix* lines)\n" "~w Write message onto file\n" "~x Abort composition, discard message (`~q': save in $DEAD)\n" "~| Pipe message through shell filter (`~||': with headers)\n" ), n_stdout); #endif /* mx_HAVE_UISTRINGS */ if(cnt != 0) goto jearg; n_pstate_err_no = su_ERR_NONE; n_pstate_ex_no = 0; break; case '@':{ struct attachment *aplist; /* Edit the attachment list */ aplist = hp->h_attach; hp->h_attach = NULL; if(cnt != 0) hp->h_attach = n_attachment_append_list(aplist, cp); else hp->h_attach = n_attachment_list_edit(aplist, n_GO_INPUT_CTX_COMPOSE); n_pstate_err_no = su_ERR_NONE; /* XXX ~@ does NOT handle $!/$?! */ n_pstate_ex_no = 0; /* XXX */ }break; case '^': if(!mx_dig_msg_circumflex(&dmc, n_stdout, cp)){ if(ferror(_coll_fp)) goto jerr; goto jearg; } n_pstate_err_no = su_ERR_NONE; /* XXX */ n_pstate_ex_no = 0; /* XXX */ hist &= ~a_HIST_GABBY; break; /* case '_': <> ':' */ case '|': /* Pipe message through command. Collect output as new message */ if(cnt == 0) goto jearg; /* Is this request to do a "stream equivalent" to 'e' and 'v'? */ if(*cp == '|'){ ++cp; goto jev_go; } if((n_pstate_err_no = a_coll_pipe(cp)) == su_ERR_NONE) n_pstate_ex_no = 0; else if(ferror(_coll_fp)) goto jerr; else if(a_HARDERR()) goto jerr; else n_pstate_ex_no = 1; hist &= ~a_HIST_GABBY; goto jhistcont; case 'A': case 'a': /* Insert the contents of a sign variable */ if(cnt != 0) goto jearg; cp = (c == 'a') ? ok_vlook(sign) : ok_vlook(Sign); goto jIi_putesc; case 'b': /* Add stuff to blind carbon copies list TODO join 'c' */ if(cnt == 0) goto jearg; else{ struct mx_name *np; s8 soe; soe = 0; if((np = checkaddrs(lextract(cp, GBCC | GFULL), EACM_NORMAL, &soe) ) != NULL) hp->h_bcc = cat(hp->h_bcc, np); if(soe == 0){ n_pstate_err_no = su_ERR_NONE; n_pstate_ex_no = 0; }else{ n_pstate_ex_no = 1; n_pstate_err_no = (soe < 0) ? su_ERR_PERM : su_ERR_INVAL; } } hist &= ~a_HIST_GABBY; break; case 'c': /* Add to the CC list TODO join 'b' */ if(cnt == 0) goto jearg; else{ struct mx_name *np; s8 soe; soe = 0; if((np = checkaddrs(lextract(cp, GCC | GFULL), EACM_NORMAL, &soe) ) != NULL) hp->h_cc = cat(hp->h_cc, np); if(soe == 0){ n_pstate_err_no = su_ERR_NONE; n_pstate_ex_no = 0; }else{ n_pstate_ex_no = 1; n_pstate_err_no = (soe < 0) ? su_ERR_PERM : su_ERR_INVAL; } } hist &= ~a_HIST_GABBY; break; case 'd': if(cnt != 0) goto jearg; cp = n_getdeadletter(); if(0){ case '<': case 'R': case 'r': /* Invoke a file: Search for the file name, then open it and copy * the contents to _coll_fp */ if(cnt == 0){ n_err(_("Interpolate what file?\n")); if(a_HARDERR()) goto jerr; n_pstate_err_no = su_ERR_NOENT; n_pstate_ex_no = 1; break; } if(*cp == '!' && c == '<'){ /* TODO hist. normalization */ if((n_pstate_err_no = a_coll_insert_cmd(_coll_fp, ++cp) ) != su_ERR_NONE){ if(ferror(_coll_fp)) goto jerr; if(a_HARDERR()) goto jerr; n_pstate_ex_no = 1; break; } goto jhistcont; } /* Note this also expands things like * !:vput vexpr delim random 0 * !< - $delim */ if((cp = fexpand(cp, FEXP_LOCAL | FEXP_NOPROTO | FEXP_NSHELL) ) == NULL){ if(a_HARDERR()) goto jerr; n_pstate_err_no = su_ERR_INVAL; n_pstate_ex_no = 1; break; } } /* XXX race, and why not test everywhere, then? */ if(n_is_dir(cp, FAL0)){ n_err(_("%s: is a directory\n"), n_shexp_quote_cp(cp, FAL0)); if(a_HARDERR()) goto jerr; n_pstate_err_no = su_ERR_ISDIR; n_pstate_ex_no = 1; break; } if((n_pstate_err_no = a_coll_include_file(cp, (c == 'R'), TRU1) ) != su_ERR_NONE){ if(ferror(_coll_fp)) goto jerr; if(a_HARDERR()) goto jerr; n_pstate_ex_no = 1; break; } n_pstate_err_no = su_ERR_NONE; /* XXX */ n_pstate_ex_no = 0; /* XXX */ break; case 'e': case 'v': /* Edit the current message. 'e' -> use EDITOR, 'v' -> use VISUAL */ if(cnt != 0 || coap != NULL) goto jearg; jev_go: if((n_pstate_err_no = a_coll_edit(c, ((c == '|' || ok_blook(editheaders)) ? hp : NULL), cp) ) == su_ERR_NONE) n_pstate_ex_no = 0; else if(ferror(_coll_fp)) goto jerr; else if(a_HARDERR()) goto jerr; else n_pstate_ex_no = 1; goto jhistcont; case 'F': case 'f': case 'M': case 'm': case 'Q': case 'U': case 'u': /* Interpolate the named messages, if we are in receiving mail mode. * Does the standard list processing garbage. If ~f is given, we * don't shift over */ if((n_pstate_err_no = a_coll_forward(cp, _coll_fp, c)) == su_ERR_NONE) n_pstate_ex_no = 0; else if(ferror(_coll_fp)) goto jerr; else if(a_HARDERR()) goto jerr; else n_pstate_ex_no = 1; break; case 'H': /* Grab extra headers */ if(cnt != 0) goto jearg; do grab_headers(n_GO_INPUT_CTX_COMPOSE, hp, GEXTRA, 0); while(check_from_and_sender(hp->h_from, hp->h_sender) == NULL); n_pstate_err_no = su_ERR_NONE; /* XXX */ n_pstate_ex_no = 0; /* XXX */ break; case 'h': /* Grab a bunch of headers */ if(cnt != 0) goto jearg; do grab_headers(n_GO_INPUT_CTX_COMPOSE, hp, (GTO | GSUBJECT | GCC | GBCC), (ok_blook(bsdcompat) && ok_blook(bsdorder))); while(hp->h_to == NULL); n_pstate_err_no = su_ERR_NONE; /* XXX */ n_pstate_ex_no = 0; /* XXX */ break; case 'I': case 'i': /* Insert a variable into the file */ if(cnt == 0) goto jearg; cp = n_var_vlook(cp, TRU1); jIi_putesc: if(cp == NULL || *cp == '\0') break; if(!a_coll_putesc(cp, (c != 'I'), _coll_fp)) goto jerr; if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT) && (!a_coll_putesc(cp, (c != 'I'), n_stdout) || fflush(n_stdout) == EOF)) goto jerr; n_pstate_err_no = su_ERR_NONE; /* XXX */ n_pstate_ex_no = 0; /* XXX */ break; /* case 'M': <> 'F' */ /* case 'm': <> 'f' */ case 'p': /* Print current state of the message without altering anything */ if(cnt != 0) goto jearg; print_collf(_coll_fp, hp); /* XXX pstate_err_no ++ */ if(ferror(_coll_fp)) goto jerr; n_pstate_err_no = su_ERR_NONE; /* XXX */ n_pstate_ex_no = 0; /* XXX */ break; /* case 'Q': <> 'F' */ case 'q': case 'x': /* Force a quit, act like an interrupt had happened */ if(cnt != 0) goto jearg; /* If we are running a splice hook, assume it quits on its own now, * otherwise we (no true out-of-band IPC to signal this state, XXX sic) * have to SIGTERM it in order to stop this wild beast */ flags |= a_COAP_NOSIGTERM; ++_coll_hadintr; _collint((c == 'x') ? 0 : SIGINT); exit(n_EXIT_ERR); /*NOTREACHED*/ /* case 'R': <> 'd' */ /* case 'r': <> 'd' */ case 's': /* Set the Subject list */ if(cnt == 0) goto jearg; /* Subject:; take care for Debian #419840 and strip any \r and \n */ if(su_cs_first_of(hp->h_subject = savestr(cp), "\n\r") != su_UZ_MAX){ char *xp; n_err(_("-s: normalizing away invalid ASCII NL / CR bytes\n")); for(xp = hp->h_subject; *xp != '\0'; ++xp) if(*xp == '\n' || *xp == '\r') *xp = ' '; n_pstate_err_no = su_ERR_INVAL; n_pstate_ex_no = 1; }else{ n_pstate_err_no = su_ERR_NONE; n_pstate_ex_no = 0; } break; case 't': /* Add to the To: list TODO join 'b', 'c' */ if(cnt == 0) goto jearg; else{ struct mx_name *np; s8 soe; soe = 0; if((np = checkaddrs(lextract(cp, GTO | GFULL), EACM_NORMAL, &soe) ) != NULL) hp->h_to = cat(hp->h_to, np); if(soe == 0){ n_pstate_err_no = su_ERR_NONE; n_pstate_ex_no = 0; }else{ n_pstate_ex_no = 1; n_pstate_err_no = (soe < 0) ? su_ERR_PERM : su_ERR_INVAL; } } hist &= ~a_HIST_GABBY; break; /* case 'U': <> 'F' */ /* case 'u': <> 'f' */ /* case 'v': <> 'e' */ case 'w': /* Write the message on a file */ if(cnt == 0) goto jearg; if((cp = fexpand(cp, FEXP_LOCAL | FEXP_NOPROTO)) == NULL){ n_err(_("Write what file!?\n")); if(a_HARDERR()) goto jerr; n_pstate_err_no = su_ERR_INVAL; n_pstate_ex_no = 1; break; } rewind(_coll_fp); if((n_pstate_err_no = a_coll_write(cp, _coll_fp, 1)) == su_ERR_NONE) n_pstate_ex_no = 0; else if(ferror(_coll_fp)) goto jerr; else if(a_HARDERR()) goto jerr; else n_pstate_ex_no = 1; break; /* case 'x': <> 'q' */ } /* Finally place an entry in history as applicable */ if(0){ jhistcont: c = '\1'; }else c = '\0'; if(hist & a_HIST_ADD){ /* Do not add *escape* to the history in order to allow history search * to be handled generically in the MLE regardless of actual *escape* * settings etc. */ mx_tty_addhist(&n_string_cp(s)[1], (n_GO_INPUT_CTX_COMPOSE | (hist & a_HIST_GABBY ? n_GO_INPUT_HIST_GABBY : n_GO_INPUT_NONE))); } if(c != '\0') goto jcont; } jout: flags |= a_EVER_LEFT_INPUT_LOOPS; /* Do we have *on-compose-splice-shell*, or *on-compose-splice*? * TODO Usual f...ed up state of signals and terminal etc. */ if(coap == NULL && (cp = ok_vlook(on_compose_splice_shell)) != NULL) Jocs:{ union {int (*ptf)(void); char const *sh;} u; char const *cmd; /* Reset *escape* and more to their defaults. On change update manual! */ if(ifs_saved == NULL) ifs_saved = savestr(ok_vlook(ifs)); ok_vclear(ifs); escape = n_ESCAPE[0]; flags &= ~a_CAN_DELAY_INJECT; if(coapm != NULL){ /* XXX Due pipe_open() fflush(NIL) in PTF mode */ /*if(!n_real_seek(_coll_fp, 0, SEEK_END)) * goto jerr;*/ u.ptf = &a_coll_ocs__mac; cmd = (char*)-1; a_coll_ocs__macname = cp = coapm; }else{ u.sh = ok_vlook(SHELL); cmd = cp; } i = su_cs_len(cp) +1; coap = n_lofi_alloc(VSTRUCT_SIZEOF(struct a_coll_ocs_arg, coa_cmd ) + i); coap->coa_pipe[0] = coap->coa_pipe[1] = -1; coap->coa_stdin = coap->coa_stdout = NIL; coap->coa_senderr = checkaddr_err; su_mem_copy(coap->coa_cmd, cp, i); mx_sigs_all_holdx(); coap->coa_opipe = safe_signal(SIGPIPE, SIG_IGN); coap->coa_oint = safe_signal(SIGINT, SIG_IGN); mx_sigs_all_rele(); if(mx_fs_pipe_cloexec(coap->coa_pipe) && (coap->coa_stdin = mx_fs_fd_open(coap->coa_pipe[0], "r", FAL0) ) != NIL && (coap->coa_stdout = mx_fs_pipe_open(cmd, "W", u.sh, NIL, coap->coa_pipe[1])) != NIL){ close(S(int,coap->coa_pipe[1])); coap->coa_pipe[1] = -1; temporary_compose_mode_hook_call(NULL, NULL, NULL); n_go_splice_hack(coap->coa_cmd, coap->coa_stdin, coap->coa_stdout, (n_psonce & ~(n_PSO_INTERACTIVE | n_PSO_TTYANY)), &a_coll_ocs__finalize, &coap); /* Hook version protocol for ~^: update manual upon change! */ fputs(mx_DIG_MSG_PLUMBING_VERSION "\n", n_stdout/*coap->coa_stdout*/); goto jcont; } c = su_err_no(); a_coll_ocs__finalize(coap); n_perr(_("Cannot invoke *on-compose-splice(-shell)?*"), c); goto jerr; } if(*checkaddr_err != 0){ *checkaddr_err = 0; goto jerr; } if(coapm == NIL && (coapm = ok_vlook(on_compose_splice)) != NIL) goto Jocs; if(coap != NIL && ifs_saved != NIL){ ok_vset(ifs, ifs_saved); ifs_saved = NIL; } /* * Note: the order of the following steps is documented for `~.'. * Adjust the manual on change!! */ /* Final chance to edit headers, if not already above; and *asksend* */ if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT)){ if(ok_blook(bsdcompat) || ok_blook(askatend)){ gfield = GNONE; if(ok_blook(askcc)) gfield |= GCC; if(ok_blook(askbcc)) gfield |= GBCC; if(gfield != GNONE) grab_headers(n_GO_INPUT_CTX_COMPOSE, hp, gfield, 1); } if(ok_blook(askattach)) hp->h_attach = n_attachment_list_edit(hp->h_attach, n_GO_INPUT_CTX_COMPOSE); if(ok_blook(asksend)){ boole b; ifs_saved = coapm = NULL; coap = NULL; fprintf(n_stdout, _("-------\nEnvelope contains:\n")); /* XXX112 */ if(!n_puthead(TRU1, hp, n_stdout, GIDENT | GREF_IRT | GSUBJECT | GTO | GCC | GBCC | GBCC_IS_FCC | GCOMMA, SEND_TODISP, CONV_NONE, NULL, NULL)) goto jerr; jreasksend: if(n_go_input(n_GO_INPUT_CTX_COMPOSE | n_GO_INPUT_NL_ESC, _("Send this message [yes/no, empty: recompose]? "), &linebuf, &linesize, NULL, NULL) < 0){ if(!n_go_input_is_eof()) goto jerr; cp = n_1; } if((b = n_boolify(linebuf, UZ_MAX, TRUM1)) < FAL0) goto jreasksend; if(b == TRU2) goto jcont; if(!b) goto jerr; } } /* Execute compose-leave */ if((cp = ok_vlook(on_compose_leave)) != NULL){ setup_from_and_sender(hp); temporary_compose_mode_hook_call(cp, &n_temporary_compose_hook_varset, hp); } /* Add automatic receivers */ if ((cp = ok_vlook(autocc)) != NULL && *cp != '\0') hp->h_cc = cat(hp->h_cc, checkaddrs(lextract(cp, GCC | (ok_blook(fullnames) ? GFULL | GSKIN : GSKIN)), EACM_NORMAL, checkaddr_err)); if ((cp = ok_vlook(autobcc)) != NULL && *cp != '\0') hp->h_bcc = cat(hp->h_bcc, checkaddrs(lextract(cp, GBCC | (ok_blook(fullnames) ? GFULL | GSKIN : GSKIN)), EACM_NORMAL, checkaddr_err)); if (*checkaddr_err != 0) goto jerr; /* TODO Cannot do since it may require turning this into a multipart one */ if(n_poption & n_PO_Mm_FLAG) goto jskiptails; /* Place signature? */ if((cp = ok_vlook(signature)) != NULL && *cp != '\0'){ /* TODO OBSOLETE */ char const *cpq; n_OBSOLETE(_("please use *on-compose-{leave,splice}* and/or " "*message-inject-tail*, not *signature*")); if((cpq = fexpand(cp, FEXP_LOCAL | FEXP_NOPROTO)) == NULL){ n_err(_("*signature* expands to invalid file: %s\n"), n_shexp_quote_cp(cp, FAL0)); goto jerr; } cpq = n_shexp_quote_cp(cp = cpq, FAL0); if((sigfp = mx_fs_open(cp, "r")) == NIL){ n_err(_("Can't open *signature* %s: %s\n"), cpq, su_err_doc(su_err_no())); goto jerr; } if(linebuf == NULL) linebuf = n_alloc(linesize = LINESIZE); c = '\0'; while((i = fread(linebuf, sizeof *linebuf, linesize, UNVOLATILE(FILE*,sigfp))) > 0){ c = linebuf[i - 1]; if(i != fwrite(linebuf, sizeof *linebuf, i, _coll_fp)) goto jerr; } /* C99 */{ FILE *x = UNVOLATILE(FILE*,sigfp); int e = su_err_no(), ise = ferror(x); sigfp = NULL; mx_fs_close(x); if(ise){ n_err(_("Errors while reading *signature* %s: %s\n"), cpq, su_err_doc(e)); goto jerr; } } if(c != '\0' && c != '\n') putc('\n', _coll_fp); } { char const *cp_obsolete = ok_vlook(NAIL_TAIL); if(cp_obsolete != NULL) n_OBSOLETE(_("please use *message-inject-tail*, not *NAIL_TAIL*")); if((cp = ok_vlook(message_inject_tail)) != NULL || (cp = cp_obsolete) != NULL){ if(!a_coll_putesc(cp, TRU1, _coll_fp)) goto jerr; if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT) && (!a_coll_putesc(cp, TRU1, n_stdout) || fflush(n_stdout) == EOF)) goto jerr; } } jskiptails: if(fflush(_coll_fp)) goto jerr; rewind(_coll_fp); if(mp != NULL && ok_blook(quote_as_attachment)){ struct attachment *ap; ap = n_autorec_calloc(1, sizeof *ap); if((ap->a_flink = hp->h_attach) != NULL) hp->h_attach->a_blink = ap; hp->h_attach = ap; ap->a_msgno = (int)P2UZ(mp - message + 1); ap->a_content_description = _("Original message content"); } jleave: sigprocmask(SIG_BLOCK, &nset, NULL); mx_fs_linepool_release(linebuf, linesize); mx_DIG_MSG_COMPOSE_GUT(&dmc); n_pstate &= ~n_PS_COMPOSE_MODE; safe_signal(SIGINT, _coll_saveint); safe_signal(SIGHUP, _coll_savehup); sigprocmask(SIG_SETMASK, &oset, NULL); NYD_OU; return _coll_fp; jerr: mx_sigs_all_holdx(); if(coap != NULL && coap != (struct a_coll_ocs_arg*)-1){ if(!(flags & a_COAP_NOSIGTERM)) mx_fs_pipe_signal(coap->coa_stdout, SIGTERM); n_go_splice_hack_remove_after_jump(); coap = NULL; } if(ifs_saved != NULL){ ok_vset(ifs, ifs_saved); ifs_saved = NULL; } if(sigfp != NIL){ mx_fs_close(UNVOLATILE(FILE*,sigfp)); sigfp = NULL; } if(_coll_fp != NIL){ mx_fs_close(_coll_fp); _coll_fp = NIL; } mx_sigs_all_rele(); ASSERT(checkaddr_err != NULL); /* TODO We don't save in $DEAD upon error because msg not readily composed? * TODO But this no good, it should go ZOMBIE / DRAFT / POSTPONED or what! */ if(*checkaddr_err != 0){ if(*checkaddr_err == 111) n_err(_("Compose mode splice hook failure\n")); else n_err(_("Some addressees were classified as \"hard error\"\n")); }else if(_coll_hadintr == 0){ *checkaddr_err = TRU1; /* TODO ugly: "sendout_error" now.. */ n_err(_("Failed to prepare composed message\n")); } goto jleave; #undef a_HARDERR } #include "su/code-ou.h" /* s-it-mode */ s-nail-14.9.15/src/mx/colour.c000066400000000000000000000745051352610246600157600ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Implementation of colour.h. *@ TODO mx_colour_env should be objects, mx_COLOUR_IS_ACTIVE() should take *@ TODO such an object! We still should work together with n_go_data, *@ TODO but only for cleanup purposes. No stack at all, that is to say! *@ TODO (Note we yet use autorec memory, so with JUMPS this needs care!) * * Copyright (c) 2014 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #undef su_FILE #define su_FILE colour #define mx_SOURCE #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif su_EMPTY_FILE() #ifdef mx_HAVE_COLOUR #include #include #include #include "mx/sigs.h" #include "mx/termcap.h" /* TODO fake */ #include "mx/colour.h" #include "su/code-in.h" /* Not needed publically, but extends a public set */ #define mx_COLOUR_TAG_ERR ((char*)-1) #define a_COLOUR_TAG_IS_SPECIAL(P) (P2UZ(P) >= P2UZ(-3)) enum a_colour_type{ a_COLOUR_T_256, a_COLOUR_T_8, a_COLOUR_T_1, a_COLOUR_T_NONE, /* EQ largest real colour + 1! */ a_COLOUR_T_UNKNOWN /* Initial value: real one queried before 1st use */ }; enum a_colour_tag_type{ a_COLOUR_TT_NONE, a_COLOUR_TT_DOT = 1u<<0, /* "dot" */ a_COLOUR_TT_OLDER = 1u<<1, /* "older" */ a_COLOUR_TT_HEADERS = 1u<<2, /* Comma-separated list of headers allowed */ a_COLOUR_TT_SUM = a_COLOUR_TT_DOT | a_COLOUR_TT_OLDER, a_COLOUR_TT_VIEW = a_COLOUR_TT_HEADERS }; struct a_colour_type_map{ u8 ctm_type; /* a_colour_type */ char ctm_name[7]; }; struct a_colour_map_id{ u8 cmi_ctx; /* enum mx_colour_ctx */ u8 cmi_id; /* enum mx_colour_id */ u8 cmi_tt; /* enum a_colour_tag_type */ char const cmi_name[13]; }; CTA(mx__COLOUR_IDS <= U8_MAX, "Enumeration exceeds storage datatype"); struct mx_colour_pen{ struct str cp_dat; /* Pre-prepared ISO 6429 escape sequence */ }; struct a_colour_map /* : public mx_colour_pen */{ struct mx_colour_pen cm_pen; /* Points into .cm_buf */ struct a_colour_map *cm_next; char const *cm_tag; /* Colour tag or NULL for default (last) */ struct a_colour_map_id const *cm_cmi; #ifdef mx_HAVE_REGEX regex_t *cm_regex; #endif u32 cm_refcnt; /* Beware of reference drops in recursions */ u32 cm_user_off; /* User input offset in .cm_buf */ char cm_buf[VFIELD_SIZE(0)]; }; struct a_colour_g{ boole cg_is_init; u8 cg_type; /* a_colour_type */ u8 __cg_pad[6]; struct mx_colour_pen cg_reset; /* The reset sequence */ struct a_colour_map *cg_maps[a_COLOUR_T_NONE][mx__COLOUR_CTX_MAX1][mx__COLOUR_IDS]; char cg__reset_buf[Z_ALIGN_SMALL(sizeof("\033[0m"))]; }; /* C99: use [INDEX]={} */ /* */ CTA(a_COLOUR_T_256 == 0, "Unexpected value of constant"); CTA(a_COLOUR_T_8 == 1, "Unexpected value of constant"); CTA(a_COLOUR_T_1 == 2, "Unexpected value of constant"); static char const a_colour_types[][8] = {"256", "iso", "mono"}; static struct a_colour_type_map const a_colour_type_maps[] = { {a_COLOUR_T_256, "256"}, {a_COLOUR_T_8, "8"}, {a_COLOUR_T_8, "iso"}, {a_COLOUR_T_8, "ansi"}, {a_COLOUR_T_1, "1"}, {a_COLOUR_T_1, "mono"} }; CTAV(mx_COLOUR_CTX_SUM == 0); CTAV(mx_COLOUR_CTX_VIEW == 1); CTAV(mx_COLOUR_CTX_MLE == 2); static char const a_colour_ctx_prefixes[mx__COLOUR_CTX_MAX1][8] = { "sum-", "view-", "mle-" }; static struct a_colour_map_id const a_colour_map_ids[mx__COLOUR_CTX_MAX1][mx__COLOUR_IDS] = {{ {mx_COLOUR_CTX_SUM, mx_COLOUR_ID_SUM_DOTMARK, a_COLOUR_TT_SUM, "dotmark"}, {mx_COLOUR_CTX_SUM, mx_COLOUR_ID_SUM_HEADER, a_COLOUR_TT_SUM, "header"}, {mx_COLOUR_CTX_SUM, mx_COLOUR_ID_SUM_THREAD, a_COLOUR_TT_SUM, "thread"}, }, { {mx_COLOUR_CTX_VIEW, mx_COLOUR_ID_VIEW_FROM_, a_COLOUR_TT_NONE, "from_"}, {mx_COLOUR_CTX_VIEW, mx_COLOUR_ID_VIEW_HEADER, a_COLOUR_TT_VIEW, "header"}, {mx_COLOUR_CTX_VIEW, mx_COLOUR_ID_VIEW_MSGINFO, a_COLOUR_TT_NONE, "msginfo"}, {mx_COLOUR_CTX_VIEW, mx_COLOUR_ID_VIEW_PARTINFO, a_COLOUR_TT_NONE, "partinfo"}, }, { {mx_COLOUR_CTX_MLE, mx_COLOUR_ID_MLE_POSITION, a_COLOUR_TT_NONE, "position"}, {mx_COLOUR_CTX_MLE, mx_COLOUR_ID_MLE_PROMPT, a_COLOUR_TT_NONE, "prompt"}, {mx_COLOUR_CTX_MLE, mx_COLOUR_ID_MLE_ERROR, a_COLOUR_TT_NONE, "error"}, }}; #define a_COLOUR_MAP_SHOW_FIELDWIDTH \ (int)(sizeof("view-")-1 + sizeof("partinfo")-1) static struct a_colour_g a_colour_g; /* */ static void a_colour_init(void); static boole a_colour_termcap_init(void); /* May we work with colour at the very moment? */ SINLINE boole a_colour_ok_to_go(u32 get_flags); /* Find the type or -1 */ static enum a_colour_type a_colour_type_find(char const *name); /* `(un)?colour' implementations */ static boole a_colour_mux(char **argv); static boole a_colour_unmux(char **argv); static boole a_colour__show(enum a_colour_type ct); /* (regexpp may be NULL) */ static char const *a_colour__tag_identify(struct a_colour_map_id const *cmip, char const *ctag, void **regexpp); /* Try to find a mapping identity for user given slotname */ static struct a_colour_map_id const *a_colour_map_id_find( char const *slotname); /* Find an existing mapping for the given combination */ static struct a_colour_map *a_colour_map_find(enum mx_colour_ctx cctx, enum mx_colour_id cid, char const *ctag); /* In-/Decrement reference counter, destroy if counts gets zero */ #define a_colour_map_ref(SELF) do{ ++(SELF)->cm_refcnt; }while(0) static void a_colour_map_unref(struct a_colour_map *self); /* Create an ISO 6429 (ECMA-48/ANSI) terminal control escape sequence from user * input spec, store it or on error message in *store */ static boole a_colour_iso6429(enum a_colour_type ct, char **store, char const *spec); static void a_colour_init(void){ NYD2_IN; a_colour_g.cg_is_init = TRU1; su_mem_copy(a_colour_g.cg_reset.cp_dat.s = a_colour_g.cg__reset_buf, "\033[0m", a_colour_g.cg_reset.cp_dat.l = sizeof("\033[0m") -1); /* (calloc) */ a_colour_g.cg_type = a_COLOUR_T_UNKNOWN; NYD2_OU; } static boole a_colour_termcap_init(void){ boole rv; NYD2_IN; rv = FAL0; if(n_psonce & n_PSO_STARTED){ struct mx_termcap_value tv; if(!mx_termcap_query(mx_TERMCAP_QUERY_colors, &tv)) a_colour_g.cg_type = a_COLOUR_T_NONE; else{ rv = TRU1; switch(tv.tv_data.tvd_numeric){ case 256: a_colour_g.cg_type = a_COLOUR_T_256; break; case 8: a_colour_g.cg_type = a_COLOUR_T_8; break; case 1: a_colour_g.cg_type = a_COLOUR_T_1; break; default: if(n_poption & n_PO_D_V) n_err(_("Ignoring unsupported termcap entry for Co(lors)\n")); /* FALLTHRU */ case 0: a_colour_g.cg_type = a_COLOUR_T_NONE; rv = FAL0; break; } } } NYD2_OU; return rv; } SINLINE boole a_colour_ok_to_go(u32 get_flags){ u32 po; boole rv; NYD2_IN; rv = FAL0; po = (n_poption & n_PO_V_MASK);/* TODO *colour-disable* */ n_poption &= ~n_PO_V_MASK; /* TODO log too loud - need "no log" bit!! */ /* xxx Entire preamble could later be a shared function */ if(!(n_psonce & n_PSO_TTYANY) || !(n_psonce & n_PSO_STARTED) || ok_blook(colour_disable) || (!(get_flags & mx_COLOUR_GET_FORCED) && !mx_COLOUR_IS_ACTIVE()) || ((get_flags & mx_COLOUR_PAGER_USED) && !ok_blook(colour_pager))) goto jleave; if(UNLIKELY(!a_colour_g.cg_is_init)) a_colour_init(); if(UNLIKELY(a_colour_g.cg_type == a_COLOUR_T_NONE) || !a_colour_termcap_init()) goto jleave; rv = TRU1; jleave: n_poption |= po; NYD2_OU; return rv; } static enum a_colour_type a_colour_type_find(char const *name){ struct a_colour_type_map const *ctmp; enum a_colour_type rv; NYD2_IN; ctmp = a_colour_type_maps; do if(!su_cs_cmp_case(ctmp->ctm_name, name)){ rv = ctmp->ctm_type; goto jleave; }while(PCMP(++ctmp, !=, a_colour_type_maps + NELEM(a_colour_type_maps))); rv = (enum a_colour_type)-1; jleave: NYD2_OU; return rv; } static boole a_colour_mux(char **argv){ void *regexp; char const *mapname, *ctag; struct a_colour_map **cmap, *blcmp, *lcmp, *cmp; struct a_colour_map_id const *cmip; boole rv; enum a_colour_type ct; NYD2_IN; if((ct = a_colour_type_find(*argv++)) == (enum a_colour_type)-1 && (*argv != NULL || !n_is_all_or_aster(argv[-1]))){ n_err(_("`colour': invalid colour type %s\n"), n_shexp_quote_cp(argv[-1], FAL0)); rv = FAL0; goto jleave; } if(!a_colour_g.cg_is_init) a_colour_init(); if(*argv == NULL){ rv = a_colour__show(ct); goto jleave; } rv = FAL0; regexp = NULL; if((cmip = a_colour_map_id_find(mapname = argv[0])) == NULL){ n_err(_("`colour': non-existing mapping: %s\n"), n_shexp_quote_cp(mapname, FAL0)); goto jleave; } if(argv[1] == NULL){ n_err(_("`colour': %s: missing attribute argument\n"), n_shexp_quote_cp(mapname, FAL0)); goto jleave; } /* Check whether preconditions are at all allowed, verify them as far as * possible as necessary. For shell_quote() simplicity let's just ignore an * empty precondition */ if((ctag = argv[2]) != NULL && *ctag != '\0'){ char const *xtag; if(cmip->cmi_tt == a_COLOUR_TT_NONE){ n_err(_("`colour': %s does not support preconditions\n"), n_shexp_quote_cp(mapname, FAL0)); goto jleave; }else if((xtag = a_colour__tag_identify(cmip, ctag, ®exp)) == mx_COLOUR_TAG_ERR){ /* I18N: ..of colour mapping */ n_err(_("`colour': %s: invalid precondition: %s\n"), n_shexp_quote_cp(mapname, FAL0), n_shexp_quote_cp(ctag, FAL0)); goto jleave; } ctag = xtag; } /* At this time we have all the information to be able to query whether such * a mapping is yet established. If so, destroy it */ for(blcmp = lcmp = NULL, cmp = *(cmap = &a_colour_g.cg_maps[ct][cmip->cmi_ctx][cmip->cmi_id]); cmp != NULL; blcmp = lcmp, lcmp = cmp, cmp = cmp->cm_next){ char const *xctag = cmp->cm_tag; if(xctag == ctag || (ctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(ctag) && xctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(xctag) && !su_cs_cmp(xctag, ctag))){ if(lcmp == NULL) *cmap = cmp->cm_next; else lcmp->cm_next = cmp->cm_next; a_colour_map_unref(cmp); break; } } /* Create mapping */ /* C99 */{ uz tl, usrl, cl; char *bp, *cp; if(!a_colour_iso6429(ct, &cp, argv[1])){ /* I18N: colour command: mapping: error message: user argument */ n_err(_("`colour': %s: %s: %s\n"), n_shexp_quote_cp(mapname, FAL0), cp, n_shexp_quote_cp(argv[1], FAL0)); goto jleave; } tl = (ctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(ctag)) ? su_cs_len(ctag) : 0; cmp = n_alloc(VSTRUCT_SIZEOF(struct a_colour_map, cm_buf) + tl +1 + (usrl = su_cs_len(argv[1])) +1 + (cl = su_cs_len(cp)) +1); /* .cm_buf stuff */ cmp->cm_pen.cp_dat.s = bp = cmp->cm_buf; cmp->cm_pen.cp_dat.l = cl; su_mem_copy(bp, cp, ++cl); bp += cl; cmp->cm_user_off = (u32)P2UZ(bp - cmp->cm_buf); su_mem_copy(bp, argv[1], ++usrl); bp += usrl; if(tl > 0){ cmp->cm_tag = bp; su_mem_copy(bp, ctag, ++tl); /*bp += tl;*/ }else cmp->cm_tag = ctag; /* Non-buf stuff; default mapping */ if(lcmp != NULL){ /* Default mappings must be last */ if(ctag == NULL){ while(lcmp->cm_next != NULL) lcmp = lcmp->cm_next; }else if(lcmp->cm_next == NULL && lcmp->cm_tag == NULL){ if((lcmp = blcmp) == NULL) goto jlinkhead; } cmp->cm_next = lcmp->cm_next; lcmp->cm_next = cmp; }else{ jlinkhead: cmp->cm_next = *cmap; *cmap = cmp; } cmp->cm_cmi = cmip; #ifdef mx_HAVE_REGEX cmp->cm_regex = regexp; #endif cmp->cm_refcnt = 0; a_colour_map_ref(cmp); } rv = TRU1; jleave: NYD2_OU; return rv; } static boole a_colour_unmux(char **argv){ char const *mapname, *ctag, *xtag; struct a_colour_map **cmap, *lcmp, *cmp; struct a_colour_map_id const *cmip; enum a_colour_type ct; boole aster, rv; NYD2_IN; rv = TRU1; aster = FAL0; if((ct = a_colour_type_find(*argv++)) == (enum a_colour_type)-1){ if(!n_is_all_or_aster(argv[-1])){ n_err(_("`uncolour': invalid colour type %s\n"), n_shexp_quote_cp(argv[-1], FAL0)); rv = FAL0; goto j_leave; } aster = TRU1; ct = 0; } mapname = argv[0]; ctag = argv[1]; if(!a_colour_g.cg_is_init) goto jemap; /* Delete anything? */ jredo: if(ctag == NULL && mapname[0] == '*' && mapname[1] == '\0'){ uz i1, i2; struct a_colour_map *tmp; for(i1 = 0; i1 < mx__COLOUR_CTX_MAX1; ++i1) for(i2 = 0; i2 < mx__COLOUR_IDS; ++i2) for(cmp = *(cmap = &a_colour_g.cg_maps[ct][i1][i2]), *cmap = NULL; cmp != NULL;){ tmp = cmp; cmp = cmp->cm_next; a_colour_map_unref(tmp); } }else{ if((cmip = a_colour_map_id_find(mapname)) == NULL){ rv = FAL0; jemap: /* I18N: colour cmd, mapping and precondition (option in quotes) */ n_err(_("`uncolour': non-existing mapping: %s%s%s\n"), n_shexp_quote_cp(mapname, FAL0), (ctag == NULL ? n_empty : " "), (ctag == NULL ? n_empty : n_shexp_quote_cp(ctag, FAL0))); goto jleave; } if((xtag = ctag) != NULL){ if(cmip->cmi_tt == a_COLOUR_TT_NONE){ n_err(_("`uncolour': %s does not support preconditions\n"), n_shexp_quote_cp(mapname, FAL0)); rv = FAL0; goto jleave; }else if((xtag = a_colour__tag_identify(cmip, ctag, NULL)) == mx_COLOUR_TAG_ERR){ n_err(_("`uncolour': %s: invalid precondition: %s\n"), n_shexp_quote_cp(mapname, FAL0), n_shexp_quote_cp(ctag, FAL0)); rv = FAL0; goto jleave; } /* (Improve user experience) */ if(xtag != NULL && !a_COLOUR_TAG_IS_SPECIAL(xtag)) ctag = xtag; } lcmp = NULL; cmp = *(cmap = &a_colour_g.cg_maps[ct][cmip->cmi_ctx][cmip->cmi_id]); for(;;){ char const *xctag; if(cmp == NULL){ rv = FAL0; goto jemap; } if((xctag = cmp->cm_tag) == ctag) break; if(ctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(ctag) && xctag != NULL && !a_COLOUR_TAG_IS_SPECIAL(xctag) && !su_cs_cmp(xctag, ctag)) break; lcmp = cmp; cmp = cmp->cm_next; } if(lcmp == NULL) *cmap = cmp->cm_next; else lcmp->cm_next = cmp->cm_next; a_colour_map_unref(cmp); } jleave: if(aster && ++ct != a_COLOUR_T_NONE) goto jredo; j_leave: NYD2_OU; return rv; } static boole a_colour__show(enum a_colour_type ct){ struct a_colour_map *cmp; uz i1, i2; boole rv; NYD2_IN; /* Show all possible types? */ if((rv = (ct == (enum a_colour_type)-1 ? TRU1 : FAL0))) ct = 0; jredo: for(i1 = 0; i1 < mx__COLOUR_CTX_MAX1; ++i1) for(i2 = 0; i2 < mx__COLOUR_IDS; ++i2){ if((cmp = a_colour_g.cg_maps[ct][i1][i2]) == NULL) continue; for(; cmp != NULL; cmp = cmp->cm_next){ char const *tag; if((tag = cmp->cm_tag) == NULL) tag = n_empty; else if(tag == mx_COLOUR_TAG_SUM_DOT) tag = "dot"; else if(tag == mx_COLOUR_TAG_SUM_OLDER) tag = "older"; fprintf(n_stdout, "colour %s %-*s %s %s\n", a_colour_types[ct], a_COLOUR_MAP_SHOW_FIELDWIDTH, savecat(a_colour_ctx_prefixes[i1], a_colour_map_ids[i1][i2].cmi_name), (char const*)cmp->cm_buf + cmp->cm_user_off, n_shexp_quote_cp(tag, TRU1)); } } if(rv && ++ct != a_COLOUR_T_NONE) goto jredo; rv = TRU1; NYD2_OU; return rv; } static char const * a_colour__tag_identify(struct a_colour_map_id const *cmip, char const *ctag, void **regexpp){ NYD2_IN; UNUSED(regexpp); if((cmip->cmi_tt & a_COLOUR_TT_DOT) && !su_cs_cmp_case(ctag, "dot")) ctag = mx_COLOUR_TAG_SUM_DOT; else if((cmip->cmi_tt & a_COLOUR_TT_OLDER) && !su_cs_cmp_case(ctag, "older")) ctag = mx_COLOUR_TAG_SUM_OLDER; else if(cmip->cmi_tt & a_COLOUR_TT_HEADERS){ char *cp, c; uz i; /* Can this be a valid list of headers? However, with regular expressions * simply use the input as such if it appears to be a regex */ #ifdef mx_HAVE_REGEX if(n_is_maybe_regex(ctag)){ int s; if(regexpp != NULL && (s = regcomp(*regexpp = n_alloc(sizeof(regex_t)), ctag, REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){ n_err(_("`colour': invalid regular expression: %s: %s\n"), n_shexp_quote_cp(ctag, FAL0), n_regex_err_to_doc(NULL, s)); n_free(*regexpp); goto jetag; } }else #endif { /* Normalize to lowercase and strip any whitespace before use */ i = su_cs_len(ctag); cp = n_autorec_alloc(i +1); for(i = 0; (c = *ctag++) != '\0';){ boole isblspc = su_cs_is_space(c); if(!isblspc && !su_cs_is_alnum(c) && c != '-' && c != ',') goto jetag; /* Since we compare header names as they come from the message this * lowercasing is however redundant: we need to casecmp() them */ if(!isblspc) cp[i++] = su_cs_to_lower(c); } cp[i] = '\0'; ctag = cp; } }else jetag: ctag = mx_COLOUR_TAG_ERR; NYD2_OU; return ctag; } static struct a_colour_map_id const * a_colour_map_id_find(char const *cp){ uz i; struct a_colour_map_id const (*cmip)[mx__COLOUR_IDS], *rv; NYD2_IN; rv = NULL; for(i = 0;; ++i){ if(i == mx__COLOUR_IDS) goto jleave; else{ uz j = su_cs_len(a_colour_ctx_prefixes[i]); if(!su_cs_cmp_case_n(cp, a_colour_ctx_prefixes[i], j)){ cp += j; break; } } } cmip = &a_colour_map_ids[i]; for(i = 0;; ++i){ if(i == mx__COLOUR_IDS || (rv = &(*cmip)[i])->cmi_name[0] == '\0'){ rv = NULL; break; } if(!su_cs_cmp_case(cp, rv->cmi_name)) break; } jleave: NYD2_OU; return rv; } static struct a_colour_map * a_colour_map_find(enum mx_colour_ctx cctx, enum mx_colour_id cid, char const *ctag){ struct a_colour_map *cmp; NYD2_IN; cmp = a_colour_g.cg_maps[a_colour_g.cg_type][cctx][cid]; for(; cmp != NULL; cmp = cmp->cm_next){ char const *xtag = cmp->cm_tag; if(xtag == ctag) break; if(xtag == NULL) break; if(ctag == NULL || a_COLOUR_TAG_IS_SPECIAL(ctag)) continue; #ifdef mx_HAVE_REGEX if(cmp->cm_regex != NULL){ if(regexec(cmp->cm_regex, ctag, 0,NULL, 0) != REG_NOMATCH) break; }else #endif if(cmp->cm_cmi->cmi_tt & a_COLOUR_TT_HEADERS){ char *hlist = savestr(xtag), *cp; while((cp = su_cs_sep_c(&hlist, ',', TRU1)) != NULL){ if(!su_cs_cmp_case(cp, ctag)) break; } if(cp != NULL) break; } } NYD2_OU; return cmp; } static void a_colour_map_unref(struct a_colour_map *self){ NYD2_IN; if(--self->cm_refcnt == 0){ #ifdef mx_HAVE_REGEX if(self->cm_regex != NULL){ regfree(self->cm_regex); n_free(self->cm_regex); } #endif n_free(self); } NYD2_OU; } static boole a_colour_iso6429(enum a_colour_type ct, char **store, char const *spec){ struct isodesc{ char id_name[15]; char id_modc; } const fta[] = { {"bold", '1'}, {"underline", '4'}, {"reverse", '7'} }, ca[] = { {"black", '0'}, {"red", '1'}, {"green", '2'}, {"brown", '3'}, {"blue", '4'}, {"magenta", '5'}, {"cyan", '6'}, {"white", '7'} }, *idp; char *xspec, *cp, fg[3], cfg[2 + 2*sizeof("255")]; u8 ftno_base, ftno; boole rv; NYD_IN; rv = FAL0; /* 0/1 indicate usage, thereafter possibly 256 color sequences */ cfg[0] = cfg[1] = 0; /* Since we use autorec_alloc(), reuse the su_cs_sep_c() buffer also for the * return value, ensure we have enough room for that */ /* C99 */{ uz i = su_cs_len(spec) +1; xspec = n_autorec_alloc(MAX(i, sizeof("\033[1;4;7;38;5;255;48;5;255m"))); su_mem_copy(xspec, spec, i); spec = xspec; } /* Iterate over the colour spec */ ftno = 0; while((cp = su_cs_sep_c(&xspec, ',', TRU1)) != NULL){ char *y, *x = su_cs_find_c(cp, '='); if(x == NULL){ jbail: *store = n_UNCONST(_("invalid attribute list")); goto jleave; } *x++ = '\0'; if(!su_cs_cmp_case(cp, "ft")){ if(!su_cs_cmp_case(x, "inverse")){ n_OBSOLETE(_("please use reverse for ft= fonts, not inverse")); x = n_UNCONST("reverse"); } for(idp = fta;; ++idp) if(idp == fta + NELEM(fta)){ *store = n_UNCONST(_("invalid font attribute")); goto jleave; }else if(!su_cs_cmp_case(x, idp->id_name)){ if(ftno < NELEM(fg)) fg[ftno++] = idp->id_modc; else{ *store = n_UNCONST(_("too many font attributes")); goto jleave; } break; } }else if(!su_cs_cmp_case(cp, "fg")){ y = cfg + 0; goto jiter_colour; }else if(!su_cs_cmp_case(cp, "bg")){ y = cfg + 1; jiter_colour: if(ct == a_COLOUR_T_1){ *store = n_UNCONST(_("colours are not allowed")); goto jleave; } /* Maybe 256 color spec */ if(su_cs_is_digit(x[0])){ u8 xv; if(ct == a_COLOUR_T_8){ *store = n_UNCONST(_("invalid colour for 8-colour mode")); goto jleave; } if((su_idec_u8_cp(&xv, x, 10, NULL ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED) ) != su_IDEC_STATE_CONSUMED){ *store = n_UNCONST(_("invalid 256-colour specification")); goto jleave; } y[0] = 5; su_mem_copy((y == &cfg[0] ? y + 2 : y + 1 + sizeof("255")), x, (x[1] == '\0' ? 2 : (x[2] == '\0' ? 3 : 4))); }else for(idp = ca;; ++idp) if(idp == ca + NELEM(ca)){ *store = n_UNCONST(_("invalid colour attribute")); goto jleave; }else if(!su_cs_cmp_case(x, idp->id_name)){ y[0] = 1; y[2] = idp->id_modc; break; } }else goto jbail; } /* Restore our autorec_alloc() buffer, create return value */ xspec = n_UNCONST(spec); if(ftno > 0 || cfg[0] || cfg[1]){ /* TODO unite/share colour setters */ xspec[0] = '\033'; xspec[1] = '['; xspec += 2; for(ftno_base = ftno; ftno > 0;){ if(ftno-- != ftno_base) *xspec++ = ';'; *xspec++ = fg[ftno]; } if(cfg[0]){ if(ftno_base > 0) *xspec++ = ';'; xspec[0] = '3'; if(cfg[0] == 1){ xspec[1] = cfg[2]; xspec += 2; }else{ su_mem_copy(xspec + 1, "8;5;", 4); xspec += 5; for(ftno = 2; cfg[ftno] != '\0'; ++ftno) *xspec++ = cfg[ftno]; } } if(cfg[1]){ if(ftno_base > 0 || cfg[0]) *xspec++ = ';'; xspec[0] = '4'; if(cfg[1] == 1){ xspec[1] = cfg[3]; xspec += 2; }else{ su_mem_copy(xspec + 1, "8;5;", 4); xspec += 5; for(ftno = 2 + sizeof("255"); cfg[ftno] != '\0'; ++ftno) *xspec++ = cfg[ftno]; } } *xspec++ = 'm'; } *xspec = '\0'; *store = n_UNCONST(spec); rv = TRU1; jleave: NYD_OU; return rv; } int c_colour(void *v){ int rv; NYD_IN; rv = !a_colour_mux(v); NYD_OU; return rv; } int c_uncolour(void *v){ int rv; NYD_IN; rv = !a_colour_unmux(v); NYD_OU; return rv; } void mx_colour_stack_del(struct n_go_data_ctx *gdcp){ struct mx_colour_env *vp, *cep; NYD_IN; vp = gdcp->gdc_colour; gdcp->gdc_colour = NULL; gdcp->gdc_colour_active = FAL0; while((cep = vp) != NULL){ vp = cep->ce_last; if(cep->ce_current != NULL && cep->ce_outfp == n_stdout){ n_sighdl_t hdl; hdl = n_signal(SIGPIPE, SIG_IGN); fwrite(a_colour_g.cg_reset.cp_dat.s, a_colour_g.cg_reset.cp_dat.l, 1, cep->ce_outfp); fflush(cep->ce_outfp); n_signal(SIGPIPE, hdl); } } NYD_OU; } void mx_colour_env_create(enum mx_colour_ctx cctx, FILE *fp, boole pager_used){ struct mx_colour_env *cep; NYD_IN; if(!(n_psonce & n_PSO_TTYANY)) goto jleave; if(!a_colour_g.cg_is_init) a_colour_init(); /* TODO reset the outer level? Iff ce_outfp==fp? */ cep = n_autorec_alloc(sizeof *cep); cep->ce_last = n_go_data->gdc_colour; cep->ce_enabled = FAL0; cep->ce_ctx = cctx; cep->ce_ispipe = pager_used; cep->ce_outfp = fp; cep->ce_current = NULL; n_go_data->gdc_colour_active = FAL0; n_go_data->gdc_colour = cep; if(ok_blook(colour_disable) || (pager_used && !ok_blook(colour_pager))) goto jleave; if(a_colour_g.cg_type == a_COLOUR_T_NONE || !a_colour_termcap_init()) goto jleave; n_go_data->gdc_colour_active = cep->ce_enabled = TRU1; jleave: NYD_OU; } void mx_colour_env_gut(void){ struct mx_colour_env *cep; NYD_IN; if(!(n_psonce & n_PSO_INTERACTIVE)) goto jleave; /* TODO v15: Could happen because of jump, causing _stack_del().. */ if((cep = n_go_data->gdc_colour) == NULL) goto jleave; n_go_data->gdc_colour_active = ((n_go_data->gdc_colour = cep->ce_last ) != NULL && cep->ce_last->ce_enabled); if(cep->ce_current != NULL){ n_sighdl_t hdl; hdl = n_signal(SIGPIPE, SIG_IGN); fwrite(a_colour_g.cg_reset.cp_dat.s, a_colour_g.cg_reset.cp_dat.l, 1, cep->ce_outfp); n_signal(SIGPIPE, hdl); } jleave: NYD_OU; } void mx_colour_put(enum mx_colour_id cid, char const *ctag){ NYD_IN; if(mx_COLOUR_IS_ACTIVE()){ struct mx_colour_env *cep; cep = n_go_data->gdc_colour; if(cep->ce_current != NULL) fwrite(a_colour_g.cg_reset.cp_dat.s, a_colour_g.cg_reset.cp_dat.l, 1, cep->ce_outfp); if((cep->ce_current = a_colour_map_find(cep->ce_ctx, cid, ctag)) != NULL) fwrite(cep->ce_current->cm_pen.cp_dat.s, cep->ce_current->cm_pen.cp_dat.l, 1, cep->ce_outfp); } NYD_OU; } void mx_colour_reset(void){ NYD_IN; if(mx_COLOUR_IS_ACTIVE()){ struct mx_colour_env *cep; cep = n_go_data->gdc_colour; if(cep->ce_current != NULL){ cep->ce_current = NULL; fwrite(a_colour_g.cg_reset.cp_dat.s, a_colour_g.cg_reset.cp_dat.l, 1, cep->ce_outfp); } } NYD_OU; } struct str const * mx_colour_reset_to_str(void){ struct str *rv; NYD_IN; if(mx_COLOUR_IS_ACTIVE()) rv = &a_colour_g.cg_reset.cp_dat; else rv = NULL; NYD_OU; return rv; } struct mx_colour_pen * mx_colour_pen_create(enum mx_colour_id cid, char const *ctag){ struct a_colour_map *cmp; struct mx_colour_pen *rv; NYD_IN; if(mx_COLOUR_IS_ACTIVE() && (cmp = a_colour_map_find(n_go_data->gdc_colour->ce_ctx, cid, ctag) ) != NULL){ union {void *vp; char *cp; struct mx_colour_pen *cpp;} u; u.vp = cmp; rv = u.cpp; }else rv = NULL; NYD_OU; return rv; } void mx_colour_pen_put(struct mx_colour_pen *self){ NYD_IN; if(mx_COLOUR_IS_ACTIVE()){ union {void *vp; char *cp; struct a_colour_map *cmp;} u; struct mx_colour_env *cep; cep = n_go_data->gdc_colour; u.vp = self; if(u.cmp != cep->ce_current){ if(cep->ce_current != NULL) fwrite(a_colour_g.cg_reset.cp_dat.s, a_colour_g.cg_reset.cp_dat.l, 1, cep->ce_outfp); if(u.cmp != NULL) fwrite(self->cp_dat.s, self->cp_dat.l, 1, cep->ce_outfp); cep->ce_current = u.cmp; } } NYD_OU; } struct str const * mx_colour_pen_to_str(struct mx_colour_pen *self){ struct str *rv; NYD_IN; if(mx_COLOUR_IS_ACTIVE() && self != NULL) rv = &self->cp_dat; else rv = NULL; NYD_OU; return rv; } struct str const * mx_colour_get_reset_cseq(u32 get_flags){ struct str const *rv; NYD_IN; rv = a_colour_ok_to_go(get_flags) ? &a_colour_g.cg_reset.cp_dat : NIL; NYD_OU; return rv; } struct mx_colour_pen * mx_colour_get_pen(u32 get_flags, enum mx_colour_ctx cctx, enum mx_colour_id cid, char const *ctag){ struct a_colour_map *cmp; struct mx_colour_pen *rv; NYD_IN; rv = NIL; if(a_colour_ok_to_go(get_flags) && (cmp = a_colour_map_find(cctx, cid, ctag)) != NIL){ union {void *v; struct mx_colour_pen *cp;} p; p.v = cmp; rv = p.cp; } NYD_OU; return rv; } struct str const * mx_colour_pen_get_cseq(struct mx_colour_pen const *self){ struct str const *rv; NYD2_IN; rv = (self != NIL) ? &self->cp_dat : 0; NYD2_OU; return rv; } #include "su/code-ou.h" #endif /* mx_HAVE_COLOUR */ /* s-it-mode */ s-nail-14.9.15/src/mx/cred-auth.c000066400000000000000000000303721352610246600163230ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Implementation of cred-auth.h. * * Copyright (c) 2014 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #undef su_FILE #define su_FILE cred_auth #define mx_SOURCE #define mx_SOURCE_CRED_AUTH #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif su_EMPTY_FILE() #ifdef mx_HAVE_NET #include #include #include "mx/cred-netrc.h" #include "mx/tty.h" #include "mx/url.h" #include "mx/cred-auth.h" #include "su/code-in.h" /* temporary (we'll have file://..) */ static char *a_credauth_last_at_before_slash(char const *cp); static char * a_credauth_last_at_before_slash(char const *cp){ char const *xcp; char c; NYD2_IN; for(xcp = cp; (c = *xcp) != '\0'; ++xcp) if(c == '/') break; while(xcp > cp && *--xcp != '@') ; if(*xcp != '@') xcp = NIL; NYD2_OU; return UNCONST(char*,xcp); } boole mx_cred_auth_lookup(struct mx_cred_ctx *ccp, struct mx_url *urlp){ enum{ a_NONE, a_WANT_PASS = 1u<<0, a_REQ_PASS = 1u<<1, a_WANT_USER = 1u<<2, a_REQ_USER = 1u<<3, a_NEED_TLS = 1u<<4 }; char *s; char const *pstr, *authdef; u16 authmask; enum okeys authokey; u32 ware; NYD_IN; su_mem_set(ccp, 0, sizeof *ccp); ccp->cc_user = urlp->url_user; ASSERT(urlp->url_user.s != NIL); ware = a_NONE; switch((ccp->cc_cproto = urlp->url_cproto)){ case CPROTO_CCRED: authokey = R(enum okeys,-1); authmask = mx_CRED_AUTHTYPE_PLAIN; authdef = "plain"; pstr = "ccred"; break; default: case CPROTO_SMTP: authokey = ok_v_smtp_auth; authmask = mx_CRED_AUTHTYPE_NONE | mx_CRED_AUTHTYPE_PLAIN | mx_CRED_AUTHTYPE_LOGIN | mx_CRED_AUTHTYPE_OAUTHBEARER | mx_CRED_AUTHTYPE_EXTERNAL | mx_CRED_AUTHTYPE_EXTERNANON | mx_CRED_AUTHTYPE_CRAM_MD5 | mx_CRED_AUTHTYPE_GSSAPI; authdef = "plain"; pstr = "smtp"; break; case CPROTO_POP3: authokey = ok_v_pop3_auth; authmask = mx_CRED_AUTHTYPE_PLAIN | mx_CRED_AUTHTYPE_OAUTHBEARER | mx_CRED_AUTHTYPE_EXTERNAL | mx_CRED_AUTHTYPE_EXTERNANON | mx_CRED_AUTHTYPE_GSSAPI; authdef = "plain"; pstr = "pop3"; break; #ifdef mx_HAVE_IMAP case CPROTO_IMAP: pstr = "imap"; authokey = ok_v_imap_auth; authmask = mx_CRED_AUTHTYPE_LOGIN | mx_CRED_AUTHTYPE_OAUTHBEARER | mx_CRED_AUTHTYPE_EXTERNAL | mx_CRED_AUTHTYPE_EXTERNANON | mx_CRED_AUTHTYPE_CRAM_MD5 | mx_CRED_AUTHTYPE_GSSAPI; authdef = "login"; break; #endif } /* Authentication type XXX table driven iter */ if(authokey == R(enum okeys,-1) || (s = xok_VLOOK(authokey, urlp, OXM_ALL)) == NIL) s = UNCONST(char*,authdef); if(!su_cs_cmp_case(s, "none")){ ccp->cc_auth = "NONE"; ccp->cc_authtype = mx_CRED_AUTHTYPE_NONE; /*ware = a_NONE;*/ }else if(!su_cs_cmp_case(s, "plain")){ ccp->cc_auth = "PLAIN"; ccp->cc_authtype = mx_CRED_AUTHTYPE_PLAIN; ware = a_REQ_PASS | a_REQ_USER; }else if(!su_cs_cmp_case(s, "login")){ ccp->cc_auth = "LOGIN"; ccp->cc_authtype = mx_CRED_AUTHTYPE_LOGIN; ware = a_REQ_PASS | a_REQ_USER; }else if(!su_cs_cmp_case(s, "oauthbearer")){ ccp->cc_auth = "OAUTHBEARER"; ccp->cc_authtype = mx_CRED_AUTHTYPE_OAUTHBEARER; ccp->cc_needs_tls = TRU1; ware = a_REQ_PASS | a_REQ_USER; }else if(!su_cs_cmp_case(s, "external")){ ccp->cc_auth = "EXTERNAL"; ccp->cc_authtype = mx_CRED_AUTHTYPE_EXTERNAL; ware = a_REQ_USER; ware |= a_NEED_TLS; }else if(!su_cs_cmp_case(s, "externanon")){ ccp->cc_auth = "EXTERNAL"; ccp->cc_authtype = mx_CRED_AUTHTYPE_EXTERNANON; ware = a_NEED_TLS; }else if(!su_cs_cmp_case(s, "cram-md5")){ ccp->cc_auth = "CRAM-MD5"; ccp->cc_authtype = mx_CRED_AUTHTYPE_CRAM_MD5; ware = a_REQ_PASS | a_REQ_USER; }else if(!su_cs_cmp_case(s, "gssapi")){ ccp->cc_auth = "GSS-API"; ccp->cc_authtype = mx_CRED_AUTHTYPE_GSSAPI; ware = a_REQ_USER; } /* no else */ /* Verify method */ if(!(ccp->cc_authtype & authmask)){ n_err(_("Unsupported %s authentication method: %s\n"), pstr, s); ccp = NIL; goto jleave; } #ifndef mx_HAVE_MD5 if(ccp->cc_authtype == mx_CRED_AUTHTYPE_CRAM_MD5){ n_err(_("No CRAM-MD5 support compiled in\n")); ccp = NIL; goto jleave; } #endif #ifndef mx_HAVE_GSSAPI if(ccp->cc_authtype == mx_CRED_AUTHTYPE_GSSAPI){ n_err(_("No GSS-API support compiled in\n")); ccp = NIL; goto jleave; } #endif if((ware & a_NEED_TLS) #ifdef mx_HAVE_TLS && !(urlp->url_flags & (mx_URL_TLS_REQUIRED | mx_URL_TLS_OPTIONAL)) #endif ){ n_err(_("TLS transport is required for authentication %s: %s\n"), ccp->cc_auth, urlp->url_p_u_h_p); ccp = NIL; goto jleave; } /* Password */ ccp->cc_pass = urlp->url_pass; if(ccp->cc_pass.s != NIL) goto jleave; if((s = xok_vlook(password, urlp, OXM_ALL)) != NIL) goto js2pass; #ifdef mx_HAVE_NETRC if(xok_blook(netrc_lookup, urlp, OXM_ALL) && mx_netrc_lookup(urlp, TRU1)){ ccp->cc_pass = urlp->url_pass; goto jleave; } #endif if(ware & a_REQ_PASS){ if((s = mx_tty_getpass(savecat(urlp->url_u_h.s, _(" requires a password: ")))) != NIL) js2pass: ccp->cc_pass.l = su_cs_len(ccp->cc_pass.s = savestr(s)); else{ n_err(_("A password is necessary for %s authentication\n"), pstr); ccp = NIL; } } jleave: if(ccp != NIL && (n_poption & n_PO_D_VV)) n_err(_("Credentials: host %s, user %s, pass %s\n"), urlp->url_h_p.s, n_shexp_quote_cp(ccp->cc_user.s, FAL0), n_shexp_quote_cp((ccp->cc_pass.s != NIL ? ccp->cc_pass.s : su_empty), FAL0)); NYD_OU; return (ccp != NIL); } boole mx_cred_auth_lookup_old(struct mx_cred_ctx *ccp, enum cproto cproto, char const *addr) { char const *pname, *pxstr, *authdef, *s; uz pxlen, addrlen, i; char *vbuf; u8 authmask; enum {NONE=0, WANT_PASS=1<<0, REQ_PASS=1<<1, WANT_USER=1<<2, REQ_USER=1<<3} ware = NONE; boole addr_is_nuser = FAL0; /* XXX v15.0 legacy! v15_compat */ NYD_IN; n_OBSOLETE(_("Use of old-style credentials, which will vanish in v15!\n" " Please read the manual section " "\"On URL syntax and credential lookup\"")); su_mem_set(ccp, 0, sizeof *ccp); switch (cproto) { default: case CPROTO_SMTP: pname = "SMTP"; pxstr = "smtp-auth"; pxlen = sizeof("smtp-auth") -1; authmask = mx_CRED_AUTHTYPE_NONE | mx_CRED_AUTHTYPE_PLAIN | mx_CRED_AUTHTYPE_LOGIN | mx_CRED_AUTHTYPE_CRAM_MD5 | mx_CRED_AUTHTYPE_GSSAPI; authdef = "none"; addr_is_nuser = TRU1; break; case CPROTO_POP3: pname = "POP3"; pxstr = "pop3-auth"; pxlen = sizeof("pop3-auth") -1; authmask = mx_CRED_AUTHTYPE_PLAIN; authdef = "plain"; break; #ifdef mx_HAVE_IMAP case CPROTO_IMAP: pname = "IMAP"; pxstr = "imap-auth"; pxlen = sizeof("imap-auth") -1; authmask = mx_CRED_AUTHTYPE_LOGIN | mx_CRED_AUTHTYPE_CRAM_MD5 | mx_CRED_AUTHTYPE_GSSAPI; authdef = "login"; break; #endif } ccp->cc_cproto = cproto; addrlen = su_cs_len(addr); vbuf = n_lofi_alloc(pxlen + addrlen + sizeof("-password-")-1 +1); su_mem_copy(vbuf, pxstr, pxlen); /* Authentication type */ vbuf[pxlen] = '-'; su_mem_copy(vbuf + pxlen + 1, addr, addrlen +1); if ((s = n_var_vlook(vbuf, FAL0)) == NULL) { vbuf[pxlen] = '\0'; if ((s = n_var_vlook(vbuf, FAL0)) == NULL) s = n_UNCONST(authdef); } if (!su_cs_cmp_case(s, "none")) { ccp->cc_auth = "NONE"; ccp->cc_authtype = mx_CRED_AUTHTYPE_NONE; /*ware = NONE;*/ } else if (!su_cs_cmp_case(s, "plain")) { ccp->cc_auth = "PLAIN"; ccp->cc_authtype = mx_CRED_AUTHTYPE_PLAIN; ware = REQ_PASS | REQ_USER; } else if (!su_cs_cmp_case(s, "login")) { ccp->cc_auth = "LOGIN"; ccp->cc_authtype = mx_CRED_AUTHTYPE_LOGIN; ware = REQ_PASS | REQ_USER; } else if (!su_cs_cmp_case(s, "cram-md5")) { ccp->cc_auth = "CRAM-MD5"; ccp->cc_authtype = mx_CRED_AUTHTYPE_CRAM_MD5; ware = REQ_PASS | REQ_USER; } else if (!su_cs_cmp_case(s, "gssapi")) { ccp->cc_auth = "GSS-API"; ccp->cc_authtype = mx_CRED_AUTHTYPE_GSSAPI; ware = REQ_USER; } /* no else */ /* Verify method */ if (!(ccp->cc_authtype & authmask)) { n_err(_("Unsupported %s authentication method: %s\n"), pname, s); ccp = NULL; goto jleave; } # ifndef mx_HAVE_MD5 if (ccp->cc_authtype == mx_CRED_AUTHTYPE_CRAM_MD5) { n_err(_("No CRAM-MD5 support compiled in\n")); ccp = NULL; goto jleave; } # endif # ifndef mx_HAVE_GSSAPI if (ccp->cc_authtype == mx_CRED_AUTHTYPE_GSSAPI) { n_err(_("No GSS-API support compiled in\n")); ccp = NULL; goto jleave; } # endif /* User name */ if (!(ware & (WANT_USER | REQ_USER))) goto jpass; if(!addr_is_nuser){ if((s = a_credauth_last_at_before_slash(addr)) != NIL){ char *cp; cp = savestrbuf(addr, P2UZ(s - addr)); if((ccp->cc_user.s = mx_url_xdec(cp)) == NIL){ n_err(_("String is not properly URL percent encoded: %s\n"), cp); ccp = NULL; goto jleave; } ccp->cc_user.l = su_cs_len(ccp->cc_user.s); } else if (ware & REQ_USER) goto jgetuser; goto jpass; } su_mem_copy(vbuf + pxlen, "-user-", i = sizeof("-user-") -1); i += pxlen; su_mem_copy(vbuf + i, addr, addrlen +1); if ((s = n_var_vlook(vbuf, FAL0)) == NULL) { vbuf[--i] = '\0'; if ((s = n_var_vlook(vbuf, FAL0)) == NULL && (ware & REQ_USER)) { if((s = mx_tty_getuser(NIL)) == NIL){ jgetuser: /* TODO v15.0: today we simply bail, but we should call getuser(). * TODO even better: introduce "PROTO-user" and "PROTO-pass" and * TODO check that first, then! change control flow, grow vbuf */ n_err(_("A user is necessary for %s authentication\n"), pname); ccp = NULL; goto jleave; } } } ccp->cc_user.l = su_cs_len(ccp->cc_user.s = savestr(s)); /* Password */ jpass: if (!(ware & (WANT_PASS | REQ_PASS))) goto jleave; if (!addr_is_nuser) { su_mem_copy(vbuf, "password-", i = sizeof("password-") -1); } else { su_mem_copy(vbuf + pxlen, "-password-", i = sizeof("-password-") -1); i += pxlen; } su_mem_copy(vbuf + i, addr, addrlen +1); if ((s = n_var_vlook(vbuf, FAL0)) == NULL) { vbuf[--i] = '\0'; if ((!addr_is_nuser || (s = n_var_vlook(vbuf, FAL0)) == NULL) && (ware & REQ_PASS)){ if((s = mx_tty_getpass(savecat(pname, _(" requires a password: "))) ) == NIL){ n_err(_("A password is necessary for %s authentication\n"), pname); ccp = NIL; goto jleave; } } } if (s != NULL) ccp->cc_pass.l = su_cs_len(ccp->cc_pass.s = savestr(s)); jleave: n_lofi_free(vbuf); if (ccp != NULL && (n_poption & n_PO_D_VV)) n_err(_("Credentials: host %s, user %s, pass %s\n"), addr, (ccp->cc_user.s != NULL ? ccp->cc_user.s : n_empty), (ccp->cc_pass.s != NULL ? ccp->cc_pass.s : n_empty)); NYD_OU; return (ccp != NULL); } #include "su/code-ou.h" #endif /* mx_HAVE_NET */ /* s-it-mode */ s-nail-14.9.15/src/mx/cred-md5.c000066400000000000000000000356401352610246600160520ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Implementation of cred-md5.h. * * Copyright (c) 2014 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ /* MD5.c - header file for MD5C.C from RFC 1321 is */ /* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All rights reserved. License to copy and use this software is granted provided that it is identified as the "RSA Data Security, Inc. MD5 Message-Digest Algorithm" in all material mentioning or referencing this software or this function. License is also granted to make and use derivative works provided that such works are identified as "derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm" in all material mentioning or referencing the derived work. RSA Data Security, Inc. makes no representations concerning either the merchantability of this software or the suitability of this software for any particular purpose. It is provided "as is" without express or implied warranty of any kind. These notices must be retained in any copies of any part of this documentation and/or software. */ #undef su_FILE #define su_FILE cred_md5 #define mx_SOURCE #define mx_SOURCE_CRED_MD5 #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif su_EMPTY_FILE() #ifdef mx_HAVE_MD5 #include #include #include "mx/cred-md5.h" #include "su/code-in.h" #ifndef mx_HAVE_XTLS_MD5 /* RFC 1321, MD5.C: */ #define UINT4B_MAX 0xFFFFFFFFul /* Constants for MD5Transform routine. */ #define S11 7 #define S12 12 #define S13 17 #define S14 22 #define S21 5 #define S22 9 #define S23 14 #define S24 20 #define S31 4 #define S32 11 #define S33 16 #define S34 23 #define S41 6 #define S42 10 #define S43 15 #define S44 21 static unsigned char PADDING[64] = { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; /* #define F(x,y,z) (((x) & (y)) | ((~(x)) & (z))) #define G(x,y,z) (((x) & (z)) | ((y) & (~(z)))) */ /* As pointed out by Wei Dai , the above can be * simplified to the code below. Wei attributes these optimizations * to Peter Gutmann's SHS code, and he attributes it to Rich Schroeppel. */ #define F(b,c,d) ((((c) ^ (d)) & (b)) ^ (d)) #define G(b,c,d) ((((b) ^ (c)) & (d)) ^ (c)) #define H(b,c,d) ((b) ^ (c) ^ (d)) #define I(b,c,d) (((~(d) & UINT4B_MAX) | (b)) ^ (c)) /* ROTATE_LEFT rotates x left n bits. */ #define ROTATE_LEFT(x, n) ((((x) << (n)) & UINT4B_MAX) | ((x) >> (32 - (n)))) /* * FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. * Rotation is separate from addition to prevent recomputation. */ #define FF(a, b, c, d, x, s, ac) { \ (a) = ((a) + F(b, c, d) + (x) + ((ac) & UINT4B_MAX)) & UINT4B_MAX; \ (a) = ROTATE_LEFT((a), (s)); \ (a) = ((a) + (b)) & UINT4B_MAX; \ } #define GG(a, b, c, d, x, s, ac) { \ (a) = ((a) + G(b, c, d) + (x) + ((ac) & UINT4B_MAX)) & UINT4B_MAX; \ (a) = ROTATE_LEFT((a), (s)); \ (a) = ((a) + (b)) & UINT4B_MAX; \ } #define HH(a, b, c, d, x, s, ac) { \ (a) = ((a) + H(b, c, d) + (x) + ((ac) & UINT4B_MAX)) & UINT4B_MAX; \ (a) = ROTATE_LEFT((a), (s)); \ (a) = ((a) + (b)) & UINT4B_MAX; \ } #define II(a, b, c, d, x, s, ac) { \ (a) = ((a) + I(b, c, d) + (x) + ((ac) & UINT4B_MAX)) & UINT4B_MAX; \ (a) = ROTATE_LEFT((a), (s)); \ (a) = ((a) + (b)) & UINT4B_MAX; \ } static void Encode(unsigned char *outp, mx_md5_type *inp, unsigned int len); static void Decode(mx_md5_type *outp, unsigned char *inp, unsigned int len); static void MD5Transform(mx_md5_type state[], unsigned char block[]); /* * Encodes input (md5_type) into output (unsigned char). Assumes len is * a multiple of 4. */ static void Encode(unsigned char *outp, mx_md5_type *inp, unsigned int len) { unsigned int i, j; for (i = 0, j = 0; j < len; i++, j += 4) { outp[j] = inp[i] & 0xff; outp[j+1] = (inp[i] >> 8) & 0xff; outp[j+2] = (inp[i] >> 16) & 0xff; outp[j+3] = (inp[i] >> 24) & 0xff; } } /* * Decodes input (unsigned char) into output (md5_type). Assumes len is * a multiple of 4. */ static void Decode(mx_md5_type *outp, unsigned char *inp, unsigned int len) { unsigned int i, j; for (i = 0, j = 0; j < len; i++, j += 4) outp[i] = ((mx_md5_type)inp[j] | (mx_md5_type)inp[j+1] << 8 | (mx_md5_type)inp[j+2] << 16 | (mx_md5_type)inp[j+3] << 24) & UINT4B_MAX; } /* MD5 basic transformation. Transforms state based on block. */ static void MD5Transform(mx_md5_type state[4], unsigned char block[64]) { mx_md5_type a = state[0], b = state[1], c = state[2], d = state[3], x[16]; Decode(x, block, 64); /* Round 1 */ FF(a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ FF(d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ FF(c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ FF(b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ FF(a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ FF(d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ FF(c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ FF(b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ FF(a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ FF(d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ FF(c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ FF(b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ FF(a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ FF(d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ FF(c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ FF(b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ /* Round 2 */ GG(a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ GG(d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ GG(c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ GG(b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ GG(a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ GG(d, a, b, c, x[10], S22, 0x2441453); /* 22 */ GG(c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ GG(b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ GG(a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ GG(d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ GG(c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ GG(b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ GG(a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ GG(d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ GG(c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ GG(b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ /* Round 3 */ HH(a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ HH(d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ HH(c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ HH(b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ HH(a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ HH(d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ HH(c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ HH(b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ HH(a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ HH(d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ HH(c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ HH(b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ HH(a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ HH(d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ HH(c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ HH(b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ /* Round 4 */ II(a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ II(d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ II(c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ II(b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ II(a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ II(d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ II(c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ II(b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ II(a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ II(d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ II(c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ II(b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ II(a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ II(d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ II(c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ II(b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ state[0] = (state[0] + a) & UINT4B_MAX; state[1] = (state[1] + b) & UINT4B_MAX; state[2] = (state[2] + c) & UINT4B_MAX; state[3] = (state[3] + d) & UINT4B_MAX; /* * Zeroize sensitive information. */ (*su_mem_set_volatile)(x, 0, sizeof x); } /* * MD5 initialization. Begins an MD5 operation, writing a new context. */ void mx_md5_init(mx_md5_t *context) { context->count[0] = context->count[1] = 0; /* * Load magic initialization constants. */ context->state[0] = 0x67452301; context->state[1] = 0xefcdab89; context->state[2] = 0x98badcfe; context->state[3] = 0x10325476; } /* * MD5 block update operation. Continues an MD5 message-digest * operation, processing another message block, and updating the * context. */ void mx_md5_update(mx_md5_t *context, unsigned char *input, unsigned int inputLen) { unsigned int i, idx, partLen; /* Compute number of bytes mod 64 */ idx = context->count[0]>>3 & 0x3F; /* Update number of bits */ if ((context->count[0] = (context->count[0] + (inputLen<<3)) & UINT4B_MAX) < ((inputLen << 3) & UINT4B_MAX)) context->count[1] = (context->count[1] + 1) & UINT4B_MAX; context->count[1] = (context->count[1] + (inputLen >> 29)) & UINT4B_MAX; partLen = 64 - idx; /* * Transform as many times as possible. */ if (inputLen >= partLen) { su_mem_copy(&context->buffer[idx], input, partLen); MD5Transform(context->state, context->buffer); for (i = partLen; i + 63 < inputLen; i += 64) MD5Transform(context->state, &input[i]); idx = 0; } else i = 0; /* Buffer remaining input */ su_mem_copy(&context->buffer[idx], &input[i], inputLen-i); } /* * MD5 finalization. Ends an MD5 message-digest operation, writing the * the message digest and zeroizing the context. */ void mx_md5_final(unsigned char digest[mx_MD5_DIGEST_SIZE], mx_md5_t *context) { unsigned char bits[8]; unsigned int idx, padLen; /* Save number of bits */ Encode(bits, context->count, 8); /* * Pad out to 56 mod 64. */ idx = context->count[0]>>3 & 0x3f; padLen = idx < 56 ? 56 - idx : 120 - idx; mx_md5_update(context, PADDING, padLen); /* Append length (before padding) */ mx_md5_update(context, bits, 8); /* Store state in digest */ Encode(digest, context->state, 16); /* * Zeroize sensitive information. */ (*su_mem_set_volatile)(context, 0, sizeof *context); } # undef UINT4B_MAX # undef S11 # undef S12 # undef S13 # undef S14 # undef S21 # undef S22 # undef S23 # undef S24 # undef S31 # undef S32 # undef S33 # undef S34 # undef S41 # undef S42 # undef S43 # undef S44 # undef F # undef G # undef H # undef I # undef ROTATE_LEFT # undef FF # undef GG # undef HH # undef II #endif /* mx_HAVE_XTLS_MD5 */ char * mx_md5_tohex(char hex[mx_MD5_TOHEX_SIZE], void const *vp){ uz i, j; char const *cp; NYD_IN; cp = vp; for(i = 0; i < mx_MD5_TOHEX_SIZE / 2; ++i){ j = i << 1; #define a_HEX(n) ((n) > 9 ? (n) - 10 + 'a' : (n) + '0') hex[j] = a_HEX((cp[i] & 0xF0) >> 4); hex[++j] = a_HEX(cp[i] & 0x0F); #undef a_HEX } NYD_OU; return hex; } char * mx_md5_cram_string(struct str const *user, struct str const *pass, char const *b64){ struct str in, out; char digest[16], *cp; NYD_IN; out.s = NIL; if(user->l >= UZ_MAX - 1 - mx_MD5_TOHEX_SIZE - 1) goto jleave; if(pass->l >= INT_MAX) goto jleave; in.s = UNCONST(char*,b64); in.l = su_cs_len(in.s); if(!b64_decode(&out, &in)) goto jleave; if(out.l >= INT_MAX){ n_free(out.s); out.s = NIL; goto jleave; } mx_md5_hmac(S(uc*,out.s), out.l, S(uc*,pass->s), pass->l, digest); n_free(out.s); cp = mx_md5_tohex(n_autorec_alloc(mx_MD5_TOHEX_SIZE +1), digest); in.l = user->l + mx_MD5_TOHEX_SIZE +1; in.s = n_lofi_alloc(user->l + 1 + mx_MD5_TOHEX_SIZE +1); su_mem_copy(in.s, user->s, user->l); in.s[user->l] = ' '; su_mem_copy(&in.s[user->l + 1], cp, mx_MD5_TOHEX_SIZE); if(b64_encode(&out, &in, B64_SALLOC | B64_CRLF) == NIL) out.s = NIL; n_lofi_free(in.s); jleave: NYD_OU; return out.s; } void mx_md5_hmac(unsigned char *text, int text_len, unsigned char *key, int key_len, void *digest){ /* * This code is taken from * * Network Working Group H. Krawczyk * Request for Comments: 2104 IBM * Category: Informational M. Bellare * UCSD * R. Canetti * IBM * February 1997 * * * HMAC: Keyed-Hashing for Message Authentication */ mx_md5_t context; unsigned char k_ipad[65]; /* inner padding - key XORd with ipad */ unsigned char k_opad[65]; /* outer padding - key XORd with opad */ unsigned char tk[16]; int i; NYD_IN; /* if key is longer than 64 bytes reset it to key=MD5(key) */ if(key_len > 64){ mx_md5_t tctx; mx_md5_init(&tctx); mx_md5_update(&tctx, key, key_len); mx_md5_final(tk, &tctx); key = tk; key_len = 16; } /* the HMAC_MD5 transform looks like: * * MD5(K XOR opad, MD5(K XOR ipad, text)) * * where K is an n byte key * ipad is the byte 0x36 repeated 64 times * opad is the byte 0x5c repeated 64 times * and text is the data being protected */ /* start out by storing key in pads */ su_mem_set(k_ipad, 0, sizeof k_ipad); su_mem_set(k_opad, 0, sizeof k_opad); su_mem_copy(k_ipad, key, key_len); su_mem_copy(k_opad, key, key_len); /* XOR key with ipad and opad values */ for(i=0; i<64; i++){ k_ipad[i] ^= 0x36; k_opad[i] ^= 0x5c; } /* perform inner MD5 */ mx_md5_init(&context); /* init context for 1st pass */ mx_md5_update(&context, k_ipad, 64); /* start with inner pad */ mx_md5_update(&context, text, text_len); /* then text of datagram */ mx_md5_final(digest, &context); /* finish up 1st pass */ /* perform outer MD5 */ mx_md5_init(&context); /* init context for 2nd pass */ mx_md5_update(&context, k_opad, 64); /* start with outer pad */ mx_md5_update(&context, digest, 16); /* then results of 1st hash */ mx_md5_final(digest, &context); /* finish up 2nd pass */ NYD_OU; } #include "su/code-ou.h" #endif /* mx_HAVE_MD5 */ /* s-it-mode */ s-nail-14.9.15/src/mx/cred-netrc.c000066400000000000000000000370001352610246600164700ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Implementation of cred-netrc.h. *@ .netrc parser quite loosely based upon NetBSD usr.bin/ftp/ *@ $NetBSD: ruserpass.c,v 1.33 2007/04/17 05:52:04 lukem Exp $ * * Copyright (c) 2014 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #undef su_FILE #define su_FILE cred_netrc #define mx_SOURCE #define mx_SOURCE_CRED_NETRC #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif su_EMPTY_FILE() #ifdef mx_HAVE_NETRC #include #include #include "mx/child.h" #include "mx/file-streams.h" #include "mx/sigs.h" #include "mx/url.h" #include "mx/cred-netrc.h" #include "su/code-in.h" /* NetBSD usr.bin/ftp/ruserpass.c uses 100 bytes for that, we need four * concurrently (dummy, host, user, pass), so make it a KB */ #define a_NETRC_TOKEN_MAXLEN (1024 / 4) enum a_netrc_token{ a_NETRC_ERROR = -1, a_NETRC_NONE = 0, a_NETRC_DEFAULT, a_NETRC_LOGIN, a_NETRC_PASSWORD, a_NETRC_ACCOUNT, a_NETRC_MACDEF, a_NETRC_MACHINE, a_NETRC_INPUT }; struct a_netrc_node{ struct a_netrc_node *nrc_next; struct a_netrc_node *nrc_result; /* In match phase, former possible one */ u32 nrc_mlen; /* Length of machine name */ u32 nrc_ulen; /* Length of user name */ u32 nrc_plen; /* Length of password */ char nrc_dat[VFIELD_SIZE(sizeof(u32))]; }; #define a_NETRC_NODE_ERR R(struct a_netrc_node*,-1) static struct a_netrc_node *a_netrc_cache; /* Initialize .netrc cache */ static void a_netrc_init(void); static enum a_netrc_token a_netrc__token(FILE *fi, char buffer[a_NETRC_TOKEN_MAXLEN], boole *nl_last); /* 0=no match; 1=exact match; -1=wildcard match */ static int a_netrc_match_host(struct a_netrc_node const *nrc, struct mx_url const *urlp); /* */ static boole a_netrc_find_user(struct mx_url *urlp, struct a_netrc_node const *nrc); static boole a_netrc_find_pass(struct mx_url *urlp, boole user_match, struct a_netrc_node const *nrc); static void a_netrc_init(void){ char buffer[a_NETRC_TOKEN_MAXLEN], host[a_NETRC_TOKEN_MAXLEN], user[a_NETRC_TOKEN_MAXLEN], pass[a_NETRC_TOKEN_MAXLEN], *netrc_load; struct stat sb; FILE * volatile fi; enum a_netrc_token t; boole volatile ispipe; boole seen_default, nl_last; struct a_netrc_node * volatile ntail, * volatile nhead, * volatile nrc; NYD_IN; UNINIT(ntail, NIL); nhead = NIL; nrc = a_NETRC_NODE_ERR; ispipe = FAL0; fi = NIL; mx_sigs_all_holdx(); /* todo */ if((netrc_load = ok_vlook(netrc_pipe)) != NIL){ ispipe = TRU1; if((fi = mx_fs_pipe_open(netrc_load, "r", ok_vlook(SHELL), NIL, mx_CHILD_FD_NULL)) == NIL){ n_perr(netrc_load, 0); goto j_leave; } }else{ if((netrc_load = fexpand(ok_vlook(NETRC), FEXP_LOCAL | FEXP_NOPROTO) ) == NIL) goto j_leave; if((fi = mx_fs_open(netrc_load, "r")) == NIL){ n_err(_("Cannot open %s\n"), n_shexp_quote_cp(netrc_load, FAL0)); goto j_leave; } /* Be simple and apply rigid (permission) check(s) */ if(fstat(fileno(fi), &sb) == -1 || !S_ISREG(sb.st_mode) || (sb.st_mode & (S_IRWXG | S_IRWXO))){ n_err(_("Not a regular file, or accessible by non-user: %s\n"), n_shexp_quote_cp(netrc_load, FAL0)); goto jleave; } } seen_default = FAL0; nl_last = TRU1; switch((t = a_netrc__token(fi, buffer, &nl_last))){ case a_NETRC_NONE: break; default: /* Doesn't happen (but on error?), keep CC happy */ case a_NETRC_DEFAULT: jdef: /* We ignore the default entry (require an exact host match), and we also * ignore anything after such an entry (faulty syntax) */ seen_default = TRU1; /* FALLTHRU */ case a_NETRC_MACHINE: jm_h: /* Normalize HOST to lowercase */ *host = '\0'; if (!seen_default && (t = a_netrc__token(fi, host, &nl_last)) != a_NETRC_INPUT) goto jerr; else{ char *cp; for(cp = host; *cp != '\0'; ++cp) *cp = su_cs_to_lower(*cp); } *user = *pass = '\0'; while((t = a_netrc__token(fi, buffer, &nl_last)) != a_NETRC_NONE && t != a_NETRC_MACHINE && t != a_NETRC_DEFAULT){ switch(t){ case a_NETRC_LOGIN: if((t = a_netrc__token(fi, user, &nl_last)) != a_NETRC_INPUT) goto jerr; break; case a_NETRC_PASSWORD: if((t = a_netrc__token(fi, pass, &nl_last)) != a_NETRC_INPUT) goto jerr; break; case a_NETRC_ACCOUNT: if((t = a_netrc__token(fi, buffer, &nl_last)) != a_NETRC_INPUT) goto jerr; break; case a_NETRC_MACDEF: if((t = a_netrc__token(fi, buffer, &nl_last)) != a_NETRC_INPUT) goto jerr; else{ int i, c; for(i = 0; (c = getc(fi)) != EOF;) if(c == '\n'){ /* xxx */ /* Don't care about comments here, since we parse until * we've seen two successive newline characters */ if(i) break; i = 1; }else i = 0; } break; default: case a_NETRC_ERROR: goto jerr; } } if(!seen_default && (*user != '\0' || *pass != '\0')){ struct a_netrc_node *nx; uz hl, usrl, pl; hl = su_cs_len(host); usrl = su_cs_len(user); pl = su_cs_len(pass); nx = su_ALLOC(VSTRUCT_SIZEOF(struct a_netrc_node, nrc_dat) + hl +1 + usrl +1 + pl +1); if(nhead != NIL) ntail->nrc_next = nx; else nhead = nx; ntail = nx; nx->nrc_next = NIL; nx->nrc_mlen = hl; nx->nrc_ulen = usrl; nx->nrc_plen = pl; su_mem_copy(nx->nrc_dat, host, ++hl); su_mem_copy(&nx->nrc_dat[hl], user, ++usrl); su_mem_copy(&nx->nrc_dat[hl + usrl], pass, ++pl); } if(t == a_NETRC_MACHINE) goto jm_h; if(t == a_NETRC_DEFAULT) goto jdef; ASSERT(t == a_NETRC_NONE); break; case a_NETRC_ERROR: jerr: if(n_poption & n_PO_D_V) n_err(_("Errors occurred while parsing %s\n"), n_shexp_quote_cp(netrc_load, FAL0)); ASSERT(nrc == a_NETRC_NODE_ERR); goto jleave; } if(nhead != NIL) nrc = nhead; jleave: if(fi != NIL){ if(ispipe) mx_fs_pipe_close(fi, TRU1); else mx_fs_close(fi); } if(nrc == a_NETRC_NODE_ERR) while(nhead != NIL){ ntail = nhead; nhead = nhead->nrc_next; su_FREE(ntail); } j_leave: a_netrc_cache = nrc; mx_sigs_all_rele(); NYD_OU; } static enum a_netrc_token a_netrc__token(FILE *fi, char buffer[a_NETRC_TOKEN_MAXLEN], boole *nl_last){ int c; char *cp; enum a_netrc_token rv; NYD2_IN; rv = a_NETRC_NONE; for(;;){ boole seen_nl; c = EOF; if(feof(fi) || ferror(fi)) goto jleave; for(seen_nl = *nl_last; (c = getc(fi)) != EOF && su_cs_is_white(c);) seen_nl |= (c == '\n'); if(c == EOF) goto jleave; /* fetchmail and derived parsers support comments */ if((*nl_last = seen_nl) && c == '#'){ while((c = getc(fi)) != EOF && c != '\n') ; continue; } break; } cp = buffer; /* Is it a quoted token? At least IBM syntax also supports ' quotes */ if(c == '"' || c == '\''){ int quotec; quotec = c; /* Not requiring the closing QM is (Net)BSD syntax */ while((c = getc(fi)) != EOF && c != quotec){ /* Reverse solidus escaping the next character is (Net)BSD syntax */ if(c == '\\') if((c = getc(fi)) == EOF) break; *cp++ = c; if(PCMP(cp, ==, buffer + a_NETRC_TOKEN_MAXLEN)){ rv = a_NETRC_ERROR; goto jleave; } } }else{ *cp++ = c; while((c = getc(fi)) != EOF && !su_cs_is_white(c)){ /* Rverse solidus escaping the next character is (Net)BSD syntax */ if(c == '\\' && (c = getc(fi)) == EOF) break; *cp++ = c; if(PCMP(cp, ==, buffer + a_NETRC_TOKEN_MAXLEN)){ rv = a_NETRC_ERROR; goto jleave; } } *nl_last = (c == '\n'); } *cp = '\0'; /* XXX Table-based keyword checking */ if(*buffer == '\0') do {/*rv = a_NETRC_NONE*/} while(0); else if(!su_cs_cmp(buffer, "default")) rv = a_NETRC_DEFAULT; else if(!su_cs_cmp(buffer, "login")) rv = a_NETRC_LOGIN; else if(!su_cs_cmp(buffer, "password") || !su_cs_cmp(buffer, "passwd")) rv = a_NETRC_PASSWORD; else if(!su_cs_cmp(buffer, "account")) rv = a_NETRC_ACCOUNT; else if(!su_cs_cmp(buffer, "macdef")) rv = a_NETRC_MACDEF; else if(!su_cs_cmp(buffer, "machine")) rv = a_NETRC_MACHINE; else rv = a_NETRC_INPUT; jleave: if(c == EOF && !feof(fi)) rv = a_NETRC_ERROR; NYD2_OU; return rv; } static int a_netrc_match_host(struct a_netrc_node const *nrc, struct mx_url const *urlp){ char const *d2, *d1; uz l2, l1; int rv = 0; NYD2_IN; /* Find a matching machine -- entries are all lowercase normalized */ if(nrc->nrc_mlen == urlp->url_host.l){ if(LIKELY(!su_mem_cmp(nrc->nrc_dat, urlp->url_host.s, urlp->url_host.l))) rv = 1; goto jleave; } /* Cannot be an exact match, but maybe the .netrc machine starts with * a "*." glob, which we recognize as an extension, meaning "skip * a single subdomain, then match the rest" */ d1 = nrc->nrc_dat + 2; l1 = nrc->nrc_mlen; if(l1 <= 2 || d1[-1] != '.' || d1[-2] != '*') goto jleave; l1 -= 2; /* Brute skipping over one subdomain, no RFC 1035 or RFC 1122 checks; * in fact this even succeeds for ".host.com", but - why care, here? */ d2 = urlp->url_host.s; l2 = urlp->url_host.l; while(l2 > 0){ --l2; if(*d2++ == '.') break; } if(l2 == l1 && !su_mem_cmp(d1, d2, l1)) /* This matches, but we won't use it directly but watch out for an * exact match first! */ rv = -1; jleave: NYD2_OU; return rv; } static boole a_netrc_find_user(struct mx_url *urlp, struct a_netrc_node const *nrc){ NYD2_IN; for(; nrc != NIL; nrc = nrc->nrc_result) if(nrc->nrc_ulen > 0){ /* Fake it was part of URL otherwise XXX */ urlp->url_flags |= mx_URL_HAD_USER; /* That buffer will be duplicated by url_parse() in this case! */ urlp->url_user.s = n_UNCONST(nrc->nrc_dat + nrc->nrc_mlen +1); urlp->url_user.l = nrc->nrc_ulen; break; } NYD2_OU; return (nrc != NIL); } static boole a_netrc_find_pass(struct mx_url *urlp, boole user_match, struct a_netrc_node const *nrc){ NYD2_IN; for(; nrc != NIL; nrc = nrc->nrc_result){ boole um; um = (nrc->nrc_ulen == urlp->url_user.l && !su_mem_cmp(nrc->nrc_dat + nrc->nrc_mlen +1, urlp->url_user.s, urlp->url_user.l)); if(user_match){ if(!um) continue; }else if(!um && nrc->nrc_ulen > 0) continue; if(nrc->nrc_plen == 0) continue; /* We are responsible for duplicating this buffer! */ urlp->url_pass.s = savestrbuf(nrc->nrc_dat + nrc->nrc_mlen +1 + nrc->nrc_ulen + 1, (urlp->url_pass.l = nrc->nrc_plen)); break; } NYD2_OU; return (nrc != NIL); } int c_netrc(void *vp){ struct a_netrc_node *nrc; boole load_only; char **argv; NYD_IN; argv = vp; load_only = FAL0; if(*argv == NIL) goto jlist; if(argv[1] != NIL) goto jerr; if(!su_cs_cmp_case(*argv, "show")) goto jlist; load_only = TRU1; if(!su_cs_cmp_case(*argv, "load")) goto jlist; if(!su_cs_cmp_case(*argv, "clear")) goto jclear; jerr: n_err(_("Synopsis: netrc: ( or) the .netrc cache\n")); vp = NIL; jleave: NYD_OU; return (vp == NIL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */ jlist:{ FILE *fp; uz l; if(a_netrc_cache == NIL) a_netrc_init(); if(a_netrc_cache == a_NETRC_NODE_ERR){ n_err(_("Interpolate what file?\n")); vp = NIL; goto jleave; } if(load_only) goto jleave; if((fp = mx_fs_tmp_open("netrc", (mx_FS_O_RDWR | mx_FS_O_UNLINK | mx_FS_O_REGISTER), NIL)) == NIL){ n_perr(_("tmpfile"), 0); vp = NIL; goto jleave; } for(l = 0, nrc = a_netrc_cache; nrc != NIL; ++l, nrc = nrc->nrc_next){ fprintf(fp, _("machine %s "), nrc->nrc_dat); /* XXX quote? */ if(nrc->nrc_ulen > 0) fprintf(fp, _("login \"%s\" "), string_quote(nrc->nrc_dat + nrc->nrc_mlen +1)); if(nrc->nrc_plen > 0) fprintf(fp, _("password \"%s\"\n"), string_quote(nrc->nrc_dat + nrc->nrc_mlen +1 + nrc->nrc_ulen +1)); else putc('\n', fp); } page_or_print(fp, l); mx_fs_close(fp); } goto jleave; jclear: if(a_netrc_cache == a_NETRC_NODE_ERR) a_netrc_cache = NIL; while((nrc = a_netrc_cache) != NIL){ a_netrc_cache = nrc->nrc_next; su_FREE(nrc); } goto jleave; } boole mx_netrc_lookup(struct mx_url *urlp, boole only_pass){ struct a_netrc_node *nrc, *wild, *exact; boole rv; NYD_IN; rv = FAL0; ASSERT_NYD(!only_pass || urlp->url_user.s != NIL); ASSERT_NYD(only_pass || urlp->url_user.s == NIL); if(a_netrc_cache == NIL) a_netrc_init(); if(a_netrc_cache == a_NETRC_NODE_ERR) goto jleave; wild = exact = NIL; for(nrc = a_netrc_cache; nrc != NIL; nrc = nrc->nrc_next) switch(a_netrc_match_host(nrc, urlp)){ case 1: nrc->nrc_result = exact; exact = nrc; continue; case -1: nrc->nrc_result = wild; wild = nrc; /* FALLTHRU */ case 0: continue; } if(!only_pass && urlp->url_user.s == NIL){ /* Must be an unambiguous entry of its kind */ if(exact != NIL && exact->nrc_result != NIL) goto jleave; if(a_netrc_find_user(urlp, exact)) goto j_user; if(wild != NIL && wild->nrc_result != NIL) goto jleave; if(!a_netrc_find_user(urlp, wild)) goto jleave; j_user:; } if(a_netrc_find_pass(urlp, TRU1, exact) || a_netrc_find_pass(urlp, TRU1, wild) || /* Do not try to find a password without exact user match unless we * have been called during credential lookup, aka the second time */ !only_pass || a_netrc_find_pass(urlp, FAL0, exact) || a_netrc_find_pass(urlp, FAL0, wild)) rv = TRU1; jleave: NYD_OU; return rv; } #include "su/code-ou.h" #endif /* mx_HAVE_NETRC */ /* s-it-mode */ s-nail-14.9.15/src/mx/dig-msg.c000066400000000000000000001151761352610246600160040ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Dig message objects. TODO Very very restricted (especially non-compose) *@ Protocol change: adjust mx-config.h:mx_DIG_MSG_PLUMBING_VERSION + `~^' man. *@ TODO - a_dmsg_cmd() should generate string lists, not perform real I/O. *@ TODO I.e., drop FILE* arg, generate stringlist; up to callers... *@ TODO - With our own I/O there should then be a StringListDevice as the *@ TODO owner and I/O overlay provider: NO temporary file (sic)! *@ XXX - Multiple objects per message could be possible (a_dmsg_find()), *@ XXX except in compose mode * * Copyright (c) 2016 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #undef su_FILE #define su_FILE dig_msg #define mx_SOURCE #define mx_SOURCE_DIG_MSG #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif #include #include #include #include "mx/file-streams.h" #include "mx/names.h" #include "mx/dig-msg.h" #include "su/code-in.h" struct mx_dig_msg_ctx *mx_dig_msg_read_overlay; /* XXX HACK */ struct mx_dig_msg_ctx *mx_dig_msg_compose_ctx; /* Or NIL XXX HACK*/ /* Try to convert cp into an unsigned number that corresponds to an existing * message number (or ERR_INVAL), search for an existing object (ERR_EXIST if * oexcl and exists; ERR_NOENT if not oexcl and does not exist). * On oexcl success *dmcp will be n_alloc()ated with .dmc_msgno and .dmc_mp * etc. set; but not linked into mb.mb_digmsg and .dmc_fp not created etc. */ static s32 a_dmsg_find(char const *cp, struct mx_dig_msg_ctx **dmcpp, boole oexcl); /* Subcommand drivers */ static boole a_dmsg_cmd(FILE *fp, struct mx_dig_msg_ctx *dmcp, char const *cmd, uz cmdl, char const *cp); static boole a_dmsg__header(FILE *fp, struct mx_dig_msg_ctx *dmcp, char *cmda[3]); static boole a_dmsg__attach(FILE *fp, struct mx_dig_msg_ctx *dmcp, char *cmda[3]); static s32 a_dmsg_find(char const *cp, struct mx_dig_msg_ctx **dmcpp, boole oexcl){ struct mx_dig_msg_ctx *dmcp; s32 rv; u32 msgno; NYD2_IN; if(cp[0] == '-' && cp[1] == '\0'){ if((dmcp = mx_dig_msg_compose_ctx) != NULL){ *dmcpp = dmcp; if(dmcp->dmc_flags & mx_DIG_MSG_COMPOSE_DIGGED) rv = oexcl ? su_ERR_EXIST : su_ERR_NONE; else rv = oexcl ? su_ERR_NONE : su_ERR_NOENT; }else rv = su_ERR_INVAL; goto jleave; } if((su_idec_u32_cp(&msgno, cp, 0, NULL ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED) ) != su_IDEC_STATE_CONSUMED || msgno == 0 || UCMP(z, msgno, >, msgCount)){ rv = su_ERR_INVAL; goto jleave; } for(dmcp = mb.mb_digmsg; dmcp != NULL; dmcp = dmcp->dmc_next) if(dmcp->dmc_msgno == msgno){ *dmcpp = dmcp; rv = oexcl ? su_ERR_EXIST : su_ERR_NONE; goto jleave; } if(!oexcl){ rv = su_ERR_NOENT; goto jleave; } *dmcpp = dmcp = n_calloc(1, Z_ALIGN(sizeof *dmcp) + sizeof(struct header)); dmcp->dmc_mp = &message[msgno - 1]; dmcp->dmc_flags = mx_DIG_MSG_OWN_MEMBAG | ((TRU1/*TODO*/ || !(mb.mb_perm & MB_DELE)) ? mx_DIG_MSG_RDONLY : mx_DIG_MSG_NONE); dmcp->dmc_msgno = msgno; dmcp->dmc_hp = (struct header*)P2UZ(&dmcp[1]); dmcp->dmc_membag = su_mem_bag_create(&dmcp->dmc__membag_buf[0], 0); /* Rest done by caller */ rv = su_ERR_NONE; jleave: NYD2_OU; return rv; } static boole a_dmsg_cmd(FILE *fp, struct mx_dig_msg_ctx *dmcp, char const *cmd, uz cmdl, char const *cp){ char *cmda[3]; boole rv; NYD2_IN; /* C99 */{ uz i; /* TODO trim+strlist_split(_ifs?)() */ for(i = 0; i < NELEM(cmda); ++i){ while(su_cs_is_blank(*cp)) ++cp; if(*cp == '\0') cmda[i] = NULL; else{ char const *xp; if(i < NELEM(cmda) - 1) for(xp = cp++; *cp != '\0' && !su_cs_is_blank(*cp); ++cp) ; else{ /* Last slot takes all the rest of the line, less trailing WS */ for(xp = cp++; *cp != '\0'; ++cp) ; while(su_cs_is_blank(cp[-1])) --cp; } cmda[i] = savestrbuf(xp, P2UZ(cp - xp)); } } } if(su_cs_starts_with_case_n("header", cmd, cmdl)) rv = a_dmsg__header(fp, dmcp, cmda); else if(su_cs_starts_with_case_n("attachment", cmd, cmdl)){ if(!(dmcp->dmc_flags & mx_DIG_MSG_COMPOSE)) /* TODO attachment support */ rv = (fprintf(fp, "505 `digmsg attachment' only in compose mode (yet)\n") > 0); else rv = a_dmsg__attach(fp, dmcp, cmda); }else if(su_cs_starts_with_case_n("version", cmd, cmdl)){ if(cmda[0] != NULL) goto jecmd; rv = (fputs("210 " mx_DIG_MSG_PLUMBING_VERSION "\n", fp) != EOF); }else if((cmdl == 1 && cmd[0] == '?') || su_cs_starts_with_case_n("help", cmd, cmdl)){ if(cmda[0] != NULL) goto jecmd; rv = (fputs("211 Omnia vincit Amor et nos cedamos Amori\n", fp) != EOF && #ifdef mx_HAVE_UISTRINGS fputs(_( "attachment:\n" " attribute name (212; 501)\n" " attribute-at position\n" " attribute-set name key value (210; 505/501)\n" " attribute-set-at position key value\n" " insert file[=input-charset[#output-charset]] " "(210; 501/505/506)" " insert #message-number\n" " list (212; 501)\n" " remove name (210; 501/506)\n" " remove-at position (210; 501/505)\n"), fp) != EOF && fputs(_( "header\n" " insert field content (210; 501/505/506)\n" " list [field] (210; [501]);\n" " remove field (210; 501/505)\n" " remove-at field position (210; 501/505)\n" " show field (211/212; 501)\n" "help (211)\n" "version (210)\n"), fp) != EOF && #endif putc('\n', fp) != EOF); }else{ jecmd: fputs("500\n", fp); rv = FAL0; } fflush(fp); NYD2_OU; return rv; } static boole a_dmsg__header(FILE *fp, struct mx_dig_msg_ctx *dmcp, char *cmda[3]){ uz i; struct n_header_field *hfp; struct mx_name *np, **npp; char const *cp; struct header *hp; NYD2_IN; hp = dmcp->dmc_hp; /* Strip the optional colon from header names */ if((cp = cmda[1]) != su_NIL){ for(i = 0; cp[i] != '\0'; ++i) ; if(i > 0 && cp[i - 1] == ':'){ --i; cmda[1][i] = '\0'; } } if((cp = cmda[0]) == NULL){ ASSERT(cmda[1] == NULL); cp = n_empty; /* xxx not NULL anyway */ goto jdefault; } if(su_cs_starts_with_case("insert", cp)){ /* TODO LOGIC BELONGS head.c * TODO That is: Header::factory(string) -> object (blahblah). * TODO I.e., as long as we don't have regular RFC compliant parsers * TODO which differentiate in between structured and unstructured * TODO header fields etc., a little workaround */ struct mx_name *xnp; s8 aerr; char const *mod_suff; enum expand_addr_check_mode eacm; enum gfield ntype; boole mult_ok; if(cmda[1] == NULL || cmda[2] == NULL) goto jecmd; if(dmcp->dmc_flags & mx_DIG_MSG_RDONLY) goto j505r; /* Strip [\r\n] which would render a body invalid XXX all controls? */ /* C99 */{ char c; for(cp = cmda[2]; (c = *cp) != '\0'; ++cp) if(c == '\n' || c == '\r') *su_UNCONST(char*,cp) = ' '; } if(!su_cs_cmp_case(cmda[1], cp = "Subject")){ if(cmda[2][0] != '\0'){ if(hp->h_subject != NULL) hp->h_subject = savecatsep(hp->h_subject, ' ', cmda[2]); else hp->h_subject = su_UNCONST(char*,cmda[2]); if(fprintf(fp, "210 %s 1\n", cp) < 0) cp = NULL; goto jleave; }else goto j501cp; } mult_ok = TRU1; ntype = GEXTRA | GFULL | GFULLEXTRA; eacm = EACM_STRICT; mod_suff = su_NIL; if(!su_cs_cmp_case(cmda[1], cp = "From")){ npp = &hp->h_from; jins: aerr = 0; /* todo As said above, this should be table driven etc., but.. */ if(ntype & GBCC_IS_FCC){ np = nalloc_fcc(cmda[2]); if(is_addr_invalid(np, eacm)) goto jins_505; }else{ if((np = (mult_ok > FAL0 ? lextract : n_extract_single)(cmda[2], ntype | GNULL_OK)) == NULL) goto j501cp; if((np = checkaddrs(np, eacm, &aerr), aerr != 0)){ jins_505: if(fprintf(fp, "505 %s\n", cp) < 0) cp = NULL; goto jleave; } } /* Go to the end of the list, track whether it contains any * non-deleted entries */ i = 0; if((xnp = *npp) != NULL) for(;; xnp = xnp->n_flink){ if(!(xnp->n_type & GDEL)) ++i; if(xnp->n_flink == NULL) break; } if(!mult_ok && (i != 0 || np->n_flink != NULL)){ if(fprintf(fp, "506 %s\n", cp) < 0) cp = NULL; }else{ if(xnp == NULL) *npp = np; else xnp->n_flink = np; np->n_blink = xnp; if(fprintf(fp, "210 %s %" PRIuZ "\n", cp, ++i) < 0) cp = NULL; } goto jleave; } #undef a_X #define a_X(F,H,INS) \ if(!su_cs_cmp_case(cmda[1], cp = F)) {npp = &hp->H; INS; goto jins;} if((cp = su_cs_find_c(cmda[1], '?')) != su_NIL){ mod_suff = cp; cmda[1][P2UZ(cp - cmda[1])] = '\0'; if(*++cp != '\0' && !su_cs_starts_with_case("single", cp)){ cp = mod_suff; goto j501cp; } mult_ok = TRUM1; } /* Just like with ~t,~c,~b, immediately test *expandaddr* compliance */ a_X("To", h_to, ntype = GTO|GFULL su_COMMA eacm = EACM_NORMAL); a_X("Cc", h_cc, ntype = GCC|GFULL su_COMMA eacm = EACM_NORMAL); a_X("Bcc", h_bcc, ntype = GBCC|GFULL su_COMMA eacm = EACM_NORMAL); if((cp = mod_suff) != su_NIL) goto j501cp; /* Not | EAF_FILE, depend on *expandaddr*! */ a_X("Fcc", h_fcc, ntype = GBCC|GBCC_IS_FCC su_COMMA eacm = EACM_NORMAL); a_X("Sender", h_sender, mult_ok = FAL0); a_X("Reply-To", h_reply_to, eacm = EACM_NONAME); a_X("Mail-Followup-To", h_mft, eacm = EACM_NONAME); a_X("Message-ID", h_message_id, mult_ok = FAL0 su_COMMA ntype = GREF su_COMMA eacm = EACM_NONAME); a_X("References", h_ref, ntype = GREF su_COMMA eacm = EACM_NONAME); a_X("In-Reply-To", h_in_reply_to, ntype = GREF su_COMMA eacm = EACM_NONAME); #undef a_X if((cp = n_header_is_known(cmda[1], UZ_MAX)) != NULL) goto j505r; /* Free-form header fields */ /* C99 */{ uz nl, bl; struct n_header_field **hfpp; for(cp = cmda[1]; *cp != '\0'; ++cp) if(!fieldnamechar(*cp)){ cp = cmda[1]; goto j501cp; } for(i = 0, hfpp = &hp->h_user_headers; *hfpp != NULL; ++i) hfpp = &(*hfpp)->hf_next; nl = su_cs_len(cp = cmda[1]) +1; bl = su_cs_len(cmda[2]) +1; *hfpp = hfp = n_autorec_alloc(VSTRUCT_SIZEOF(struct n_header_field, hf_dat) + nl + bl); hfp->hf_next = NULL; hfp->hf_nl = nl - 1; hfp->hf_bl = bl - 1; su_mem_copy(&hfp->hf_dat[0], cp, nl); su_mem_copy(&hfp->hf_dat[nl], cmda[2], bl); if(fprintf(fp, "210 %s %" PRIuZ "\n", &hfp->hf_dat[0], ++i) < 0) cp = NULL; } }else if(su_cs_starts_with_case("list", cp)){ jdefault: if(cmda[1] == NULL){ fputs("210", fp); if(hp->h_subject != NULL) fputs(" Subject", fp); if(hp->h_from != NULL) fputs(" From", fp); if(hp->h_sender != NULL) fputs(" Sender", fp); if(hp->h_to != NULL) fputs(" To", fp); if(hp->h_cc != NULL) fputs(" Cc", fp); if(hp->h_bcc != NULL) fputs(" Bcc", fp); if(hp->h_fcc != NULL) fputs(" Fcc", fp); if(hp->h_reply_to != NULL) fputs(" Reply-To", fp); if(hp->h_mft != NULL) fputs(" Mail-Followup-To", fp); if(hp->h_message_id != NULL) fputs(" Message-ID", fp); if(hp->h_ref != NULL) fputs(" References", fp); if(hp->h_in_reply_to != NULL) fputs(" In-Reply-To", fp); if(hp->h_mailx_command != NULL) fputs(" Mailx-Command", fp); if(hp->h_mailx_raw_to != NULL) fputs(" Mailx-Raw-To", fp); if(hp->h_mailx_raw_cc != NULL) fputs(" Mailx-Raw-Cc", fp); if(hp->h_mailx_raw_bcc != NULL) fputs(" Mailx-Raw-Bcc", fp); if(hp->h_mailx_orig_from != NULL) fputs(" Mailx-Orig-From", fp); if(hp->h_mailx_orig_to != NULL) fputs(" Mailx-Orig-To", fp); if(hp->h_mailx_orig_cc != NULL) fputs(" Mailx-Orig-Cc", fp); if(hp->h_mailx_orig_bcc != NULL) fputs(" Mailx-Orig-Bcc", fp); /* Print only one instance of each free-form header */ for(hfp = hp->h_user_headers; hfp != NULL; hfp = hfp->hf_next){ struct n_header_field *hfpx; for(hfpx = hp->h_user_headers;; hfpx = hfpx->hf_next) if(hfpx == hfp){ putc(' ', fp); fputs(&hfp->hf_dat[0], fp); break; }else if(!su_cs_cmp_case(&hfpx->hf_dat[0], &hfp->hf_dat[0])) break; } if(putc('\n', fp) == EOF) cp = NULL; goto jleave; } if(cmda[2] != NULL) goto jecmd; if(!su_cs_cmp_case(cmda[1], cp = "Subject")){ np = (hp->h_subject != NULL) ? (struct mx_name*)-1 : NULL; goto jlist; } if(!su_cs_cmp_case(cmda[1], cp = "From")){ np = hp->h_from; jlist: fprintf(fp, "%s %s\n", (np == NULL ? "501" : "210"), cp); goto jleave; } #undef a_X #define a_X(F,H) \ if(!su_cs_cmp_case(cmda[1], cp = F)) {np = hp->H; goto jlist;} a_X("Sender", h_sender); a_X("To", h_to); a_X("Cc", h_cc); a_X("Bcc", h_bcc); a_X("Fcc", h_fcc); a_X("Reply-To", h_reply_to); a_X("Mail-Followup-To", h_mft); a_X("Message-ID", h_message_id); a_X("References", h_ref); a_X("In-Reply-To", h_in_reply_to); a_X("Mailx-Raw-To", h_mailx_raw_to); a_X("Mailx-Raw-Cc", h_mailx_raw_cc); a_X("Mailx-Raw-Bcc", h_mailx_raw_bcc); a_X("Mailx-Orig-From", h_mailx_orig_from); a_X("Mailx-Orig-To", h_mailx_orig_to); a_X("Mailx-Orig-Cc", h_mailx_orig_cc); a_X("Mailx-Orig-Bcc", h_mailx_orig_bcc); #undef a_X if(!su_cs_cmp_case(cmda[1], cp = "Mailx-Command")){ np = (hp->h_mailx_command != NULL) ? (struct mx_name*)-1 : NULL; goto jlist; } /* Free-form header fields */ for(cp = cmda[1]; *cp != '\0'; ++cp) if(!fieldnamechar(*cp)){ cp = cmda[1]; goto j501cp; } cp = cmda[1]; for(hfp = hp->h_user_headers;; hfp = hfp->hf_next){ if(hfp == NULL) goto j501cp; else if(!su_cs_cmp_case(cp, &hfp->hf_dat[0])){ if(fprintf(fp, "210 %s\n", &hfp->hf_dat[0]) < 0) cp = NULL; break; } } }else if(su_cs_starts_with_case("remove", cp)){ if(cmda[1] == NULL || cmda[2] != NULL) goto jecmd; if(dmcp->dmc_flags & mx_DIG_MSG_RDONLY) goto j505r; if(!su_cs_cmp_case(cmda[1], cp = "Subject")){ if(hp->h_subject != NULL){ hp->h_subject = NULL; if(fprintf(fp, "210 %s\n", cp) < 0) cp = NULL; goto jleave; }else goto j501cp; } if(!su_cs_cmp_case(cmda[1], cp = "From")){ npp = &hp->h_from; jrem: if(*npp != NULL){ *npp = NULL; if(fprintf(fp, "210 %s\n", cp) < 0) cp = NULL; goto jleave; }else goto j501cp; } #undef a_X #define a_X(F,H) \ if(!su_cs_cmp_case(cmda[1], cp = F)) {npp = &hp->H; goto jrem;} a_X("Sender", h_sender); a_X("To", h_to); a_X("Cc", h_cc); a_X("Bcc", h_bcc); a_X("Fcc", h_fcc); a_X("Reply-To", h_reply_to); a_X("Mail-Followup-To", h_mft); a_X("Message-ID", h_message_id); a_X("References", h_ref); a_X("In-Reply-To", h_in_reply_to); #undef a_X if((cp = n_header_is_known(cmda[1], UZ_MAX)) != NULL) goto j505r; /* Free-form header fields (note j501cp may print non-normalized name) */ /* C99 */{ struct n_header_field **hfpp; boole any; for(cp = cmda[1]; *cp != '\0'; ++cp) if(!fieldnamechar(*cp)){ cp = cmda[1]; goto j501cp; } cp = cmda[1]; for(any = FAL0, hfpp = &hp->h_user_headers; (hfp = *hfpp) != NULL;){ if(!su_cs_cmp_case(cp, &hfp->hf_dat[0])){ *hfpp = hfp->hf_next; if(!any){ if(fprintf(fp, "210 %s\n", &hfp->hf_dat[0]) < 0){ cp = NULL; goto jleave; } } any = TRU1; }else hfpp = &hfp->hf_next; } if(!any) goto j501cp; } }else if(su_cs_starts_with_case("remove-at", cp)){ if(cmda[1] == NULL || cmda[2] == NULL) goto jecmd; if(dmcp->dmc_flags & mx_DIG_MSG_RDONLY) goto j505r; if((su_idec_uz_cp(&i, cmda[2], 0, NULL ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED) ) != su_IDEC_STATE_CONSUMED || i == 0){ if(fprintf(fp, "505 invalid position: %s\n", cmda[2]) < 0) cp = NULL; goto jleave; } if(!su_cs_cmp_case(cmda[1], cp = "Subject")){ if(hp->h_subject != NULL && i == 1){ hp->h_subject = NULL; if(fprintf(fp, "210 %s 1\n", cp) < 0) cp = NULL; goto jleave; }else goto j501cp; } if(!su_cs_cmp_case(cmda[1], cp = "From")){ npp = &hp->h_from; jremat: if((np = *npp) == NULL) goto j501cp; while(--i != 0 && np != NULL) np = np->n_flink; if(np == NULL) goto j501cp; if(np->n_blink != NULL) np->n_blink->n_flink = np->n_flink; else *npp = np->n_flink; if(np->n_flink != NULL) np->n_flink->n_blink = np->n_blink; if(fprintf(fp, "210 %s\n", cp) < 0) cp = NULL; goto jleave; } #undef a_X #define a_X(F,H) \ if(!su_cs_cmp_case(cmda[1], cp = F)) {npp = &hp->H; goto jremat;} a_X("Sender", h_sender); a_X("To", h_to); a_X("Cc", h_cc); a_X("Bcc", h_bcc); a_X("Fcc", h_fcc); a_X("Reply-To", h_reply_to); a_X("Mail-Followup-To", h_mft); a_X("Message-ID", h_message_id); a_X("References", h_ref); a_X("In-Reply-To", h_in_reply_to); #undef a_X if((cp = n_header_is_known(cmda[1], UZ_MAX)) != NULL) goto j505r; /* Free-form header fields */ /* C99 */{ struct n_header_field **hfpp; for(cp = cmda[1]; *cp != '\0'; ++cp) if(!fieldnamechar(*cp)){ cp = cmda[1]; goto j501cp; } cp = cmda[1]; for(hfpp = &hp->h_user_headers; (hfp = *hfpp) != NULL;){ if(--i == 0){ *hfpp = hfp->hf_next; if(fprintf(fp, "210 %s %" PRIuZ "\n", &hfp->hf_dat[0], i) < 0){ cp = NULL; goto jleave; } break; }else hfpp = &hfp->hf_next; } if(hfp == NULL) goto j501cp; } }else if(su_cs_starts_with_case("show", cp)){ if(cmda[1] == NULL || cmda[2] != NULL) goto jecmd; if(!su_cs_cmp_case(cmda[1], cp = "Subject")){ if(hp->h_subject == NULL) goto j501cp; if(fprintf(fp, "212 %s\n%s\n\n", cp, hp->h_subject) < 0) cp = NULL; goto jleave; } if(!su_cs_cmp_case(cmda[1], cp = "From")){ np = hp->h_from; jshow: if(np != NULL){ fprintf(fp, "211 %s\n", cp); do if(!(np->n_type & GDEL)){ switch(np->n_flags & mx_NAME_ADDRSPEC_ISMASK){ case mx_NAME_ADDRSPEC_ISFILE: cp = n_hy; break; case mx_NAME_ADDRSPEC_ISPIPE: cp = "|"; break; case mx_NAME_ADDRSPEC_ISNAME: cp = n_ns; break; default: cp = np->n_name; break; } fprintf(fp, "%s %s\n", cp, np->n_fullname); }while((np = np->n_flink) != NULL); if(putc('\n', fp) == EOF) cp = NULL; goto jleave; }else goto j501cp; } #undef a_X #define a_X(F,H) \ if(!su_cs_cmp_case(cmda[1], cp = F)) {np = hp->H; goto jshow;} a_X("Sender", h_sender); a_X("To", h_to); a_X("Cc", h_cc); a_X("Bcc", h_bcc); a_X("Fcc", h_fcc); a_X("Reply-To", h_reply_to); a_X("Mail-Followup-To", h_mft); a_X("Message-ID", h_message_id); a_X("References", h_ref); a_X("In-Reply-To", h_in_reply_to); a_X("Mailx-Raw-To", h_mailx_raw_to); a_X("Mailx-Raw-Cc", h_mailx_raw_cc); a_X("Mailx-Raw-Bcc", h_mailx_raw_bcc); a_X("Mailx-Orig-From", h_mailx_orig_from); a_X("Mailx-Orig-To", h_mailx_orig_to); a_X("Mailx-Orig-Cc", h_mailx_orig_cc); a_X("Mailx-Orig-Bcc", h_mailx_orig_bcc); #undef a_X if(!su_cs_cmp_case(cmda[1], cp = "Mailx-Command")){ if(hp->h_mailx_command == NULL) goto j501cp; if(fprintf(fp, "212 %s\n%s\n\n", cp, hp->h_mailx_command) < 0) cp = NULL; goto jleave; } /* Free-form header fields */ /* C99 */{ boole any; for(cp = cmda[1]; *cp != '\0'; ++cp) if(!fieldnamechar(*cp)){ cp = cmda[1]; goto j501cp; } cp = cmda[1]; for(any = FAL0, hfp = hp->h_user_headers; hfp != NULL; hfp = hfp->hf_next){ if(!su_cs_cmp_case(cp, &hfp->hf_dat[0])){ if(!any) fprintf(fp, "212 %s\n", &hfp->hf_dat[0]); any = TRU1; fprintf(fp, "%s\n", &hfp->hf_dat[hfp->hf_nl +1]); } } if(any){ if(putc('\n', fp) == EOF) cp = NULL; }else goto j501cp; } }else goto jecmd; jleave: NYD2_OU; return (cp != NULL); jecmd: fputs("500\n", fp); cp = NULL; goto jleave; j505r: if(fprintf(fp, "505 read-only: %s\n", cp) < 0) cp = NULL; goto jleave; j501cp: if(fprintf(fp, "501 %s\n", cp) < 0) cp = NULL; goto jleave; } static boole a_dmsg__attach(FILE *fp, struct mx_dig_msg_ctx *dmcp, char *cmda[3]){ boole status; struct attachment *ap; char const *cp; struct header *hp; NYD2_IN; hp = dmcp->dmc_hp; if((cp = cmda[0]) == NULL){ cp = n_empty; /* xxx not NULL anyway */ goto jdefault; } if(su_cs_starts_with_case("attribute", cp)){ if(cmda[1] == NULL || cmda[2] != NULL) goto jecmd; if((ap = n_attachment_find(hp->h_attach, cmda[1], NULL)) != NULL){ jatt_att: fprintf(fp, "212 %s\n", cmda[1]); if(ap->a_msgno > 0){ if(fprintf(fp, "message-number %d\n\n", ap->a_msgno) < 0) cp = NULL; }else{ fprintf(fp, "creation-name %s\nopen-path %s\nfilename %s\n", ap->a_path_user, ap->a_path, ap->a_name); if(ap->a_content_description != NULL) fprintf(fp, "content-description %s\n", ap->a_content_description); if(ap->a_content_id != NULL) fprintf(fp, "content-id %s\n", ap->a_content_id->n_name); if(ap->a_content_type != NULL) fprintf(fp, "content-type %s\n", ap->a_content_type); if(ap->a_content_disposition != NULL) fprintf(fp, "content-disposition %s\n", ap->a_content_disposition); if(putc('\n', fp) == EOF) cp = NULL; } }else{ if(fputs("501\n", fp) == EOF) cp = NULL; } }else if(su_cs_starts_with_case("attribute-at", cp)){ uz i; if(cmda[1] == NULL || cmda[2] != NULL) goto jecmd; if((su_idec_uz_cp(&i, cmda[1], 0, NULL ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED) ) != su_IDEC_STATE_CONSUMED || i == 0){ if(fprintf(fp, "505 invalid position: %s\n", cmda[1]) < 0) cp = NULL; }else{ for(ap = hp->h_attach; ap != NULL && --i != 0; ap = ap->a_flink) ; if(ap != NULL) goto jatt_att; else{ if(fputs("501\n", fp) == EOF) cp = NULL; } } }else if(su_cs_starts_with_case("attribute-set", cp)){ if(cmda[1] == NULL || cmda[2] == NULL) goto jecmd; if(dmcp->dmc_flags & mx_DIG_MSG_RDONLY) goto j505r; if((ap = n_attachment_find(hp->h_attach, cmda[1], NULL)) != NULL){ jatt_attset: if(ap->a_msgno > 0){ if(fprintf(fp, "505 RFC822 message attachment: %s\n", cmda[1]) < 0) cp = NULL; }else{ char c, *keyw; cp = keyw = cmda[2]; while((c = *cp) != '\0' && !su_cs_is_blank(c)) ++cp; *UNCONST(char*,cp++) = '\0'; if(c != '\0'){ while((c = *cp) != '\0' && su_cs_is_blank(c)) ++cp; if(c != '\0'){ char *xp; /* Strip [\r\n] which would render a parameter invalid XXX * XXX all controls? */ for(xp = su_UNCONST(char*,cp); (c = *xp) != '\0'; ++xp) if(c == '\n' || c == '\r') *xp = ' '; c = *cp; } } if(!su_cs_cmp_case(keyw, "filename")) ap->a_name = (c == '\0') ? ap->a_path_bname : cp; else if(!su_cs_cmp_case(keyw, "content-description")) ap->a_content_description = (c == '\0') ? NULL : cp; else if(!su_cs_cmp_case(keyw, "content-id")){ ap->a_content_id = NULL; if(c != '\0'){ struct mx_name *np; /* XXX lextract->extract_single() */ np = checkaddrs(lextract(cp, GREF), /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME, NULL); if(np != NULL && np->n_flink == NULL) ap->a_content_id = np; else cp = NULL; } }else if(!su_cs_cmp_case(keyw, "content-type")) ap->a_content_type = (c == '\0') ? NULL : cp; else if(!su_cs_cmp_case(keyw, "content-disposition")) ap->a_content_disposition = (c == '\0') ? NULL : cp; else cp = NULL; if(cp != NULL){ uz i; for(i = 0; ap != NULL; ++i, ap = ap->a_blink) ; if(fprintf(fp, "210 %" PRIuZ "\n", i) < 0) cp = NULL; }else{ if(fputs("505\n", fp) == EOF) cp = NULL; } } }else{ if(fputs("501\n", fp) == EOF) cp = NULL; } }else if(su_cs_starts_with_case("attribute-set-at", cp)){ uz i; if(cmda[1] == NULL || cmda[2] == NULL) goto jecmd; if(dmcp->dmc_flags & mx_DIG_MSG_RDONLY) goto j505r; if((su_idec_uz_cp(&i, cmda[1], 0, NULL ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED) ) != su_IDEC_STATE_CONSUMED || i == 0){ if(fprintf(fp, "505 invalid position: %s\n", cmda[1]) < 0) cp = NULL; }else{ for(ap = hp->h_attach; ap != NULL && --i != 0; ap = ap->a_flink) ; if(ap != NULL) goto jatt_attset; else{ if(fputs("501\n", fp) == EOF) cp = NULL; } } }else if(su_cs_starts_with_case("insert", cp)){ enum n_attach_error aerr; if(cmda[1] == NULL || cmda[2] != NULL) goto jecmd; if(dmcp->dmc_flags & mx_DIG_MSG_RDONLY) goto j505r; hp->h_attach = n_attachment_append(hp->h_attach, cmda[1], &aerr, &ap); switch(aerr){ case n_ATTACH_ERR_FILE_OPEN: cp = "505\n"; goto jatt_ins; case n_ATTACH_ERR_ICONV_FAILED: cp = "506\n"; goto jatt_ins; case n_ATTACH_ERR_ICONV_NAVAIL: case n_ATTACH_ERR_OTHER: default: cp = "501\n"; jatt_ins: if(fprintf(fp, "%s %s\n", cp, cmda[1]) < 0) cp = NULL; break; case n_ATTACH_ERR_NONE:{ uz i; for(i = 0; ap != NULL; ++i, ap = ap->a_blink) ; if(fprintf(fp, "210 %" PRIuZ "\n", i) < 0) cp = NULL; }break; } goto jleave; }else if(su_cs_starts_with_case("list", cp)){ jdefault: if(cmda[1] != NULL) goto jecmd; if((ap = hp->h_attach) != NULL){ fputs("212\n", fp); do fprintf(fp, "%s\n", ap->a_path_user); while((ap = ap->a_flink) != NULL); if(putc('\n', fp) == EOF) cp = NULL; }else{ if(fputs("501\n", fp) == EOF) cp = NULL; } }else if(su_cs_starts_with_case("remove", cp)){ if(cmda[1] == NULL || cmda[2] != NULL) goto jecmd; if(dmcp->dmc_flags & mx_DIG_MSG_RDONLY) goto j505r; if((ap = n_attachment_find(hp->h_attach, cmda[1], &status)) != NULL){ if(status == TRUM1){ if(fputs("506\n", fp) == EOF) cp = NULL; }else{ hp->h_attach = n_attachment_remove(hp->h_attach, ap); if(fprintf(fp, "210 %s\n", cmda[1]) < 0) cp = NULL; } }else{ if(fputs("501\n", fp) == EOF) cp = NULL; } }else if(su_cs_starts_with_case("remove-at", cp)){ uz i; if(cmda[1] == NULL || cmda[2] != NULL) goto jecmd; if(dmcp->dmc_flags & mx_DIG_MSG_RDONLY) goto j505r; if((su_idec_uz_cp(&i, cmda[1], 0, NULL ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED) ) != su_IDEC_STATE_CONSUMED || i == 0){ if(fprintf(fp, "505 invalid position: %s\n", cmda[1]) < 0) cp = NULL; }else{ for(ap = hp->h_attach; ap != NULL && --i != 0; ap = ap->a_flink) ; if(ap != NULL){ hp->h_attach = n_attachment_remove(hp->h_attach, ap); if(fprintf(fp, "210 %s\n", cmda[1]) < 0) cp = NULL; }else{ if(fputs("501\n", fp) == EOF) cp = NULL; } } }else goto jecmd; jleave: NYD2_OU; return (cp != NULL); jecmd: (void)fputs("500\n", fp); cp = NULL; goto jleave; j505r: if(fprintf(fp, "505 read-only: %s\n", cp) < 0) cp = NULL; goto jleave; } void mx_dig_msg_on_mailbox_close(struct mailbox *mbp){ /* XXX HACK <- event! */ struct mx_dig_msg_ctx *dmcp; NYD_IN; while((dmcp = mbp->mb_digmsg) != NULL){ mbp->mb_digmsg = dmcp->dmc_next; if(dmcp->dmc_flags & mx_DIG_MSG_FCLOSE) fclose(dmcp->dmc_fp); if(dmcp->dmc_flags & mx_DIG_MSG_OWN_MEMBAG) su_mem_bag_gut(dmcp->dmc_membag); n_free(dmcp); } NYD_OU; } int c_digmsg(void *vp){ char const *cp, *emsg; struct mx_dig_msg_ctx *dmcp; struct n_cmd_arg *cap; struct n_cmd_arg_ctx *cacp; NYD_IN; n_pstate_err_no = su_ERR_NONE; cacp = vp; cap = cacp->cac_arg; if(su_cs_starts_with_case("create", cp = cap->ca_arg.ca_str.s)){ if(cacp->cac_no < 2 || cacp->cac_no > 3) goto jesynopsis; cap = cap->ca_next; /* Request to use STDOUT? */ if(cacp->cac_no == 3){ cp = cap->ca_next->ca_arg.ca_str.s; if(*cp != '-' || cp[1] != '\0'){ emsg = N_("`digmsg': create: invalid I/O channel: %s\n"); goto jeinval_quote; } } /* First of all, our context object */ switch(a_dmsg_find(cp = cap->ca_arg.ca_str.s, &dmcp, TRU1)){ case su_ERR_INVAL: emsg = N_("`digmsg': create: message number invalid: %s\n"); goto jeinval_quote; case su_ERR_EXIST: emsg = N_("`digmsg': create: message object already exists: %s\n"); goto jeinval_quote; default: break; } if(dmcp->dmc_flags & mx_DIG_MSG_COMPOSE) dmcp->dmc_flags = mx_DIG_MSG_COMPOSE | mx_DIG_MSG_COMPOSE_DIGGED; else{ FILE *fp; if((fp = setinput(&mb, dmcp->dmc_mp, NEED_HEADER)) == NULL){ /* XXX Should have paniced before.. */ n_free(dmcp); emsg = N_("`digmsg': create: mailbox I/O error for message: %s\n"); goto jeinval_quote; } su_mem_bag_push(n_go_data->gdc_membag, dmcp->dmc_membag); /* XXX n_header_extract error!! */ n_header_extract((n_HEADER_EXTRACT_FULL | n_HEADER_EXTRACT_PREFILL_RECEIVERS | n_HEADER_EXTRACT_IGNORE_FROM_), fp, dmcp->dmc_hp, NULL); su_mem_bag_pop(n_go_data->gdc_membag, dmcp->dmc_membag); } if(cacp->cac_no == 3) dmcp->dmc_fp = n_stdout; /* For compose mode simply use FS_O_REGISTER, the number of dangling * deleted files with open descriptors until next fs_close_all() * should be very small; if this paradigm is changed * DIG_MSG_COMPOSE_GUT() needs to be adjusted */ else if((dmcp->dmc_fp = mx_fs_tmp_open("digmsg", (mx_FS_O_RDWR | mx_FS_O_UNLINK | (dmcp->dmc_flags & mx_DIG_MSG_COMPOSE ? mx_FS_O_REGISTER : 0)), NIL)) != NIL) dmcp->dmc_flags |= mx_DIG_MSG_HAVE_FP | (dmcp->dmc_flags & mx_DIG_MSG_COMPOSE ? 0 : mx_DIG_MSG_FCLOSE); else{ n_err(_("`digmsg': create: cannot create temporary file: %s\n"), su_err_doc(n_pstate_err_no = su_err_no())); vp = NULL; goto jeremove; } if(!(dmcp->dmc_flags & mx_DIG_MSG_COMPOSE)){ dmcp->dmc_last = NULL; if((dmcp->dmc_next = mb.mb_digmsg) != NULL) dmcp->dmc_next->dmc_last = dmcp; mb.mb_digmsg = dmcp; } }else if(su_cs_starts_with_case("remove", cp)){ if(cacp->cac_no != 2) goto jesynopsis; cap = cap->ca_next; switch(a_dmsg_find(cp = cap->ca_arg.ca_str.s, &dmcp, FAL0)){ case su_ERR_INVAL: emsg = N_("`digmsg': remove: message number invalid: %s\n"); goto jeinval_quote; default: if(!(dmcp->dmc_flags & mx_DIG_MSG_COMPOSE) || (dmcp->dmc_flags & mx_DIG_MSG_COMPOSE_DIGGED)) break; /* FALLTHRU */ case su_ERR_NOENT: emsg = N_("`digmsg': remove: no such message object: %s\n"); goto jeinval_quote; } if(!(dmcp->dmc_flags & mx_DIG_MSG_COMPOSE)){ if(dmcp->dmc_last != NULL) dmcp->dmc_last->dmc_next = dmcp->dmc_next; else{ ASSERT(dmcp == mb.mb_digmsg); mb.mb_digmsg = dmcp->dmc_next; } if(dmcp->dmc_next != NULL) dmcp->dmc_next->dmc_last = dmcp->dmc_last; } if(dmcp->dmc_flags & mx_DIG_MSG_FCLOSE) fclose(dmcp->dmc_fp); jeremove: if(dmcp->dmc_flags & mx_DIG_MSG_OWN_MEMBAG) su_mem_bag_gut(dmcp->dmc_membag); if(dmcp->dmc_flags & mx_DIG_MSG_COMPOSE) dmcp->dmc_flags = mx_DIG_MSG_COMPOSE; else n_free(dmcp); }else{ switch(a_dmsg_find(cp, &dmcp, FAL0)){ case su_ERR_INVAL: emsg = N_("`digmsg': message number invalid: %s\n"); goto jeinval_quote; case su_ERR_NOENT: emsg = N_("`digmsg': no such message object: %s\n"); goto jeinval_quote; default: break; } cap = cap->ca_next; if(dmcp->dmc_flags & mx_DIG_MSG_HAVE_FP){ rewind(dmcp->dmc_fp); ftruncate(fileno(dmcp->dmc_fp), 0); } su_mem_bag_push(n_go_data->gdc_membag, dmcp->dmc_membag); /* C99 */{ struct str cmds_b, *cmdsp; cp = n_empty; if(cap == NULL){ /* XXX cmd_arg_parse is stupid */ cmdsp = &cmds_b; cmdsp->s = su_UNCONST(char*,cp); cmdsp->l = 0; }else{ cmdsp = &cap->ca_arg.ca_str; if((cap = cap->ca_next) != NULL) cp = cap->ca_arg.ca_str.s; } if(!a_dmsg_cmd(dmcp->dmc_fp, dmcp, cmdsp->s, cmdsp->l, cp)) vp = NULL; } su_mem_bag_pop(n_go_data->gdc_membag, dmcp->dmc_membag); if(dmcp->dmc_flags & mx_DIG_MSG_HAVE_FP){ rewind(dmcp->dmc_fp); mx_dig_msg_read_overlay = dmcp; } } jleave: NYD_OU; return (vp == NULL); jesynopsis: n_err(_("Synopsis: digmsg: <-|msgno> [<:argument:>]\n")); goto jeinval; jeinval_quote: emsg = V_(emsg); n_err(emsg, n_shexp_quote_cp(cp, FAL0)); jeinval: n_pstate_err_no = su_ERR_INVAL; vp = NULL; goto jleave; } boole mx_dig_msg_circumflex(struct mx_dig_msg_ctx *dmcp, FILE *fp, char const *cmd){ boole rv; char c; char const *cp, *cmd_top; NYD_IN; cp = cmd; while(su_cs_is_blank(*cp)) ++cp; cmd = cp; for(cmd_top = cp; (c = *cp) != '\0'; cmd_top = ++cp) if(su_cs_is_blank(c)) break; rv = a_dmsg_cmd(fp, dmcp, cmd, P2UZ(cmd_top - cmd), cp); NYD_OU; return rv; } #include "su/code-ou.h" /* s-it-mode */ s-nail-14.9.15/src/mx/edit.c000066400000000000000000000171351352610246600153760ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Perform message editing functions. * * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany. * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 1980, 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. */ #undef su_FILE #define su_FILE edit #define mx_SOURCE #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif #include "mx/child.h" #include "mx/file-streams.h" #include "mx/sigs.h" #include "mx/tty.h" /* TODO fake */ #include "su/code-in.h" /* Edit a message by writing the message into a funnily-named file (which * should not exist) and forking an editor on it */ static int edit1(int *msgvec, int viored); static int edit1(int *msgvec, int viored) { int c, i; FILE *fp = NULL; struct message *mp; off_t size; boole wb, lastnl; NYD_IN; wb = ok_blook(writebackedited); /* Deal with each message to be edited... */ for (i = 0; msgvec[i] != 0 && i < msgCount; ++i) { n_sighdl_t sigint; if(i > 0){ char prompt[64]; snprintf(prompt, sizeof prompt, _("Edit message %d"), msgvec[i]); if(!mx_tty_yesorno(prompt, TRU1)) continue; } mp = message + msgvec[i] - 1; setdot(mp); n_pstate |= n_PS_DID_PRINT_DOT; touch(mp); sigint = safe_signal(SIGINT, SIG_IGN); --mp->m_size; /* Strip final NL.. TODO MAILVFS->MESSAGE->length() */ fp = n_run_editor(fp, -1/*mp->m_size TODO */, viored, ((mb.mb_perm & MB_EDIT) == 0 || !wb), NULL, mp, (wb ? SEND_MBOX : SEND_TODISP_ALL), sigint, NULL); ++mp->m_size; /* And readd it TODO */ if (fp != NULL) { fseek(mb.mb_otf, 0L, SEEK_END); size = ftell(mb.mb_otf); mp->m_block = mailx_blockof(size); mp->m_offset = mailx_offsetof(size); mp->m_lines = 0; mp->m_flag |= MODIFY; rewind(fp); lastnl = 0; size = 0; while ((c = getc(fp)) != EOF) { if ((lastnl = (c == '\n'))) ++mp->m_lines; if (putc(c, mb.mb_otf) == EOF) break; ++size; } if (!lastnl && putc('\n', mb.mb_otf) != EOF) ++size; if (putc('\n', mb.mb_otf) != EOF) ++size; mp->m_size = (uz)size; if (ferror(mb.mb_otf)) n_perr(_("/tmp"), 0); mx_fs_close(fp); } safe_signal(SIGINT, sigint); } NYD_OU; return 0; } FL int c_editor(void *v) { int *msgvec = v, rv; NYD_IN; rv = edit1(msgvec, 'e'); NYD_OU; return rv; } FL int c_visual(void *v) { int *msgvec = v, rv; NYD_IN; rv = edit1(msgvec, 'v'); NYD_OU; return rv; } FL FILE * n_run_editor(FILE *fp, off_t size, int viored, boole readonly,/* TODO condom */ struct header *hp, struct message *mp, enum sendaction action, n_sighdl_t oldint, char const *pipecmd) { struct stat statb; struct mx_child_ctx cc; sigset_t cset; int t; time_t modtime; off_t modsize; struct mx_fs_tmp_ctx *fstcp; FILE *nf, *nf_pipetmp, *nf_tmp; NYD_IN; nf = nf_pipetmp = NIL; modtime = 0, modsize = 0; if((nf_tmp = mx_fs_tmp_open("edbase", ((viored == '|' ? mx_FS_O_RDWR : mx_FS_O_WRONLY) | mx_FS_O_REGISTER | mx_FS_O_REGISTER_UNLINK), &fstcp)) == NIL){ jetempo: n_perr(_("creation of temporary mail edit file"), 0); goto jleave; } if(hp != NULL){ ASSERT(mp == NULL); if(!n_header_put4compose(nf_tmp, hp)) goto jleave; } if(mp != NULL){ ASSERT(hp == NULL); if(sendmp(mp, nf_tmp, NULL, NULL, action, NULL) < 0){ n_err(_("Failed to prepare editable message\n")); goto jleave; } }else{ if(size >= 0){ while(--size >= 0 && (t = getc(fp)) != EOF) if(putc(t, nf_tmp) == EOF) break; }else{ while((t = getc(fp)) != EOF) if(putc(t, nf_tmp) == EOF) break; } } fflush(nf_tmp); if((t = (fp != NULL && ferror(fp))) == 0 && (t = ferror(nf_tmp)) == 0){ if(viored != '|'){ if(!fstat(fileno(nf_tmp), &statb)) modtime = statb.st_mtime, modsize = statb.st_size; if(readonly) t = (fchmod(fileno(nf_tmp), S_IRUSR) != 0); } } if(t != 0){ n_perr(fstcp->fstc_filename, 0); goto jleave; } mx_child_ctx_setup(&cc); cc.cc_flags = mx_CHILD_RUN_WAIT_LIFE; if(viored == '|'){ ASSERT(pipecmd != NIL); if((nf_pipetmp = mx_fs_tmp_open("edpipe", (mx_FS_O_WRONLY | mx_FS_O_REGISTER | mx_FS_O_REGISTER_UNLINK), &fstcp)) == NIL) goto jetempo; really_rewind(nf = nf_tmp); nf_tmp = nf_pipetmp; nf_pipetmp = nf; nf = NIL; cc.cc_fds[mx_CHILD_FD_IN] = fileno(nf_pipetmp); cc.cc_fds[mx_CHILD_FD_OUT] = fileno(nf_tmp); cc.cc_cmd = ok_vlook(SHELL); cc.cc_args[0] = "-c"; cc.cc_args[1] = pipecmd; }else{ cc.cc_cmd = (viored == 'e') ? ok_vlook(EDITOR) : ok_vlook(VISUAL); if(oldint != SIG_IGN){ sigemptyset(&cset); cc.cc_mask = &cset; } cc.cc_args[0] = fstcp->fstc_filename; } if(!mx_child_run(&cc) || cc.cc_exit_status != 0) goto jleave; /* If in read only mode or file unchanged, just remove the editor temporary * and return. Otherwise switch to new file */ if(viored != '|'){ if(readonly) goto jleave; if(stat(fstcp->fstc_filename, &statb) == -1){ n_perr(fstcp->fstc_filename, 0); goto jleave; } if(modtime == statb.st_mtime && modsize == statb.st_size) goto jleave; } if((nf = mx_fs_open(fstcp->fstc_filename, "r+")) == NIL) n_perr(fstcp->fstc_filename, 0); jleave: if(nf_pipetmp != NIL) mx_fs_close(nf_pipetmp); if(nf_tmp != NIL && !mx_fs_close(nf_tmp)){ n_perr(_("closing of temporary mail edit file"), 0); if(nf != NIL) mx_fs_close(nf); nf = NIL; } NYD_OU; return nf; } #include "su/code-ou.h" /* s-it-mode */ s-nail-14.9.15/src/mx/file-dotlock.h000066400000000000000000000140261352610246600170260ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Creation of an exclusive "dotlock" file. This is (potentially) shared *@ in between n_dotlock() and the privilege-separated "dotlocker".. *@ (Which is why it doesn't use NYD or other utilities.) *@ The code assumes it has been chdir(2)d into the target directory and *@ that SIGPIPE is ignored (we react upon ERR_PIPE). *@ It furtherly assumes that it can create a file name that is at least one *@ byte longer than the dotlock file's name! * * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (c) 1996 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. * * 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. */ /* Jump in */ static enum mx_file_dotlock_state a_file_lock_dotlock_create( struct mx_file_dotlock_info *fdip); /* Create a unique file. O_EXCL does not really work over NFS so we follow * the following trick (inspired by S.R. van den Berg): * - make a mostly unique filename and try to create it * - link the unique filename to our target * - get the link count of the target * - unlink the mostly unique filename * - if the link count was 2, then we are ok; else we've failed */ static enum mx_file_dotlock_state a_file_lock_dotlock__create_excl( struct mx_file_dotlock_info *fdip, char const *lname); static enum mx_file_dotlock_state a_file_lock_dotlock_create(struct mx_file_dotlock_info *fdip){ /* Use PATH_MAX not NAME_MAX to catch those "we proclaim the minimum value" * problems (SunOS), since the pathconf(3) value came too late! */ char lname[PATH_MAX +1]; sigset_t nset, oset; uz tries; sz w; enum mx_file_dotlock_state rv, xrv; /* The callee ensured this does not end up as plain .fdi_lock_name. * However, when called via dotlock-ps, this may not be true in malicious * cases, so add another check, then */ snprintf(lname, sizeof lname, "%s%s%s", fdip->fdi_lock_name, fdip->fdi_randstr, fdip->fdi_hostname); #ifdef mx_SOURCE_PS_DOTLOCK_MAIN if(!strcmp(lname, fdip->fdi_lock_name)){ rv = mx_FILE_DOTLOCK_STATE_FISHY | mx_FILE_DOTLOCK_STATE_ABANDON; goto jleave; } #endif sigfillset(&nset); for(tries = 0;; ++tries){ sigprocmask(SIG_BLOCK, &nset, &oset); rv = a_file_lock_dotlock__create_excl(fdip, lname); sigprocmask(SIG_SETMASK, &oset, NULL); if(rv == mx_FILE_DOTLOCK_STATE_NONE || (rv & mx_FILE_DOTLOCK_STATE_ABANDON)) break; if(fdip->fdi_pollmsecs == 0 || tries >= mx_DOTLOCK_TRIES){ rv |= mx_FILE_DOTLOCK_STATE_ABANDON; break; } xrv = mx_FILE_DOTLOCK_STATE_PING; w = write(STDOUT_FILENO, &xrv, sizeof xrv); if(w == -1 && su_err_no() == su_ERR_PIPE){ rv = mx_FILE_DOTLOCK_STATE_DUNNO | mx_FILE_DOTLOCK_STATE_ABANDON; break; } n_msleep(fdip->fdi_pollmsecs, FAL0); } #ifdef mx_SOURCE_PS_DOTLOCK_MAIN jleave: #endif return rv; } static enum mx_file_dotlock_state a_file_lock_dotlock__create_excl(struct mx_file_dotlock_info *fdip, char const *lname){ struct stat stb; int fd, e; uz tries; enum mx_file_dotlock_state rv; rv = mx_FILE_DOTLOCK_STATE_NONE; /* We try to create the unique filename */ for(tries = 0;; ++tries){ fd = open(lname, #ifdef O_SYNC (O_WRONLY | O_CREAT | O_EXCL | O_SYNC), #else (O_WRONLY | O_CREAT | O_EXCL), #endif S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH); if(fd != -1){ #ifdef mx_SOURCE_PS_DOTLOCK_MAIN if(fdip->fdi_stb != NULL && fchown(fd, fdip->fdi_stb->st_uid, fdip->fdi_stb->st_gid)){ s32 x; x = su_err_no(); close(fd); su_err_set_no(x); goto jbados; } #endif close(fd); break; }else if((e = su_err_no()) != su_ERR_EXIST){ rv = ((e == su_ERR_ROFS) ? mx_FILE_DOTLOCK_STATE_ROFS | mx_FILE_DOTLOCK_STATE_ABANDON : mx_FILE_DOTLOCK_STATE_NOPERM); goto jleave; }else if(tries >= mx_DOTLOCK_TRIES){ rv = mx_FILE_DOTLOCK_STATE_EXIST; goto jleave; } } /* We link the name to the fname */ if(link(lname, fdip->fdi_lock_name) == -1) goto jbados; /* Note that we stat our own exclusively created name, not the * destination, since the destination can be affected by others */ if(stat(lname, &stb) == -1) goto jbados; unlink(lname); /* If the number of links was two (one for the unique file and one for * the lock), we've won the race */ if(stb.st_nlink != 2) rv = mx_FILE_DOTLOCK_STATE_EXIST; jleave: return rv; jbados: rv = ((su_err_no() == su_ERR_EXIST) ? mx_FILE_DOTLOCK_STATE_EXIST : mx_FILE_DOTLOCK_STATE_NOPERM | mx_FILE_DOTLOCK_STATE_ABANDON); unlink(lname); goto jleave; } /* s-it-mode */ s-nail-14.9.15/src/mx/file-locks.c000066400000000000000000000411471352610246600165010ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Implementation of file-locks.h. * * Copyright (c) 2015 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #undef su_FILE #define su_FILE file_locks #define mx_SOURCE #define mx_SOURCE_FILE_LOCKS #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif #include #ifdef mx_HAVE_DOTLOCK # include #endif #include "mx/file-streams.h" #include "mx/random.h" #include "mx/sigs.h" #include "mx/file-locks.h" /* TODO fake */ #include "su/code-in.h" /* XXX Our pipe_open() main() takes void, temporary global data store */ #ifdef mx_HAVE_DOTLOCK static enum mx_file_lock_type a_filo_flt; static int a_filo_fd; struct mx_file_dotlock_info *a_filo_fdip; #endif /* Workhorse */ static boole a_filo_lock(int fd, enum mx_file_lock_type flt, off_t off, off_t len); /* main() of fork(2)ed dot file locker */ #ifdef mx_HAVE_DOTLOCK static int a_filo_main(void); #endif #ifdef mx_HAVE_DOTLOCK # include "mx/file-dotlock.h" /* $(MX_SRCDIR) */ #endif static boole a_filo_lock(int fd, enum mx_file_lock_type flt, off_t off, off_t len){ struct flock flp; boole rv; NYD2_IN; su_mem_set(&flp, 0, sizeof flp); switch(flt){ default: case mx_FILE_LOCK_TYPE_READ: rv = F_RDLCK; break; case mx_FILE_LOCK_TYPE_WRITE: rv = F_WRLCK; break; } flp.l_type = rv; flp.l_start = off; flp.l_whence = SEEK_SET; flp.l_len = len; if(!(rv = (fcntl(fd, F_SETLK, &flp) != -1))) switch(su_err_no()){ case su_ERR_BADF: case su_ERR_INVAL: rv = TRUM1; break; } NYD2_OU; return rv; } #ifdef mx_HAVE_DOTLOCK static int a_filo_main(void){ /* Use PATH_MAX not NAME_MAX to catch those "we proclaim the minimum value" * problems (SunOS), since the pathconf(3) value comes too late! */ char name[PATH_MAX +1]; struct mx_file_dotlock_info fdi; struct stat stb, fdstb; enum mx_file_dotlock_state fdls; char const *cp; int fd; enum mx_file_lock_type flt; NYD_IN; /* Ignore SIGPIPE, we will see ERR_PIPE and "fall through" */ safe_signal(SIGPIPE, SIG_IGN); /* Get the arguments "passed to us" */ flt = a_filo_flt; fd = a_filo_fd; UNUSED(fd); fdi = *a_filo_fdip; /* chdir(2)? */ jislink: fdls = mx_FILE_DOTLOCK_STATE_CANT_CHDIR | mx_FILE_DOTLOCK_STATE_ABANDON; if((cp = su_cs_rfind_c(fdi.fdi_file_name, '/')) != NIL){ char const *fname = cp + 1; while(PCMP(cp - 1, >, fdi.fdi_file_name) && cp[-1] == '/') --cp; cp = savestrbuf(fdi.fdi_file_name, P2UZ(cp - fdi.fdi_file_name)); if(chdir(cp)) goto jmsg; fdi.fdi_file_name = fname; } /* So we are here, but then again the file can be a symbolic link! * This is however only true if we do not have realpath(3) available since * that will have resolved the path already otherwise; nonetheless, let * readlink(2) be a precondition for dotlocking and keep this code */ if(lstat(cp = fdi.fdi_file_name, &stb) == -1) goto jmsg; if(S_ISLNK(stb.st_mode)){ /* Use n_autorec_alloc() and hope we stay in built-in buffer.. */ char *x; uz i; sz sr; for(x = NIL, i = PATH_MAX;; i += PATH_MAX){ x = n_autorec_alloc(i +1); sr = readlink(cp, x, i); if(sr <= 0){ fdls = mx_FILE_DOTLOCK_STATE_FISHY | mx_FILE_DOTLOCK_STATE_ABANDON; goto jmsg; } if(UCMP(z, sr, <, i)){ x[sr] = '\0'; break; } } fdi.fdi_file_name = x; goto jislink; } fdls = mx_FILE_DOTLOCK_STATE_FISHY | mx_FILE_DOTLOCK_STATE_ABANDON; /* Bail out if the file has changed its identity in the meanwhile */ if(fstat(fd, &fdstb) == -1 || fdstb.st_dev != stb.st_dev || fdstb.st_ino != stb.st_ino || fdstb.st_uid != stb.st_uid || fdstb.st_gid != stb.st_gid || fdstb.st_mode != stb.st_mode) goto jmsg; /* Be aware, even if the error is false! Note the shared code in * file-dotlock.h *requires* that it is possible to create a filename * at least one byte longer than di_lock_name! */ do/* while(0) breaker */{ # ifdef mx_HAVE_PATHCONF long pc; # endif int i; i = snprintf(name, sizeof name, "%s.lock", fdi.fdi_file_name); if(i < 0 || UCMP(32, i, >=, sizeof name)){ jenametool: fdls = mx_FILE_DOTLOCK_STATE_NAMETOOLONG | mx_FILE_DOTLOCK_STATE_ABANDON; goto jmsg; } /* fd is a file, not portable to use for _PC_NAME_MAX */ # ifdef mx_HAVE_PATHCONF su_err_set_no(su_ERR_NONE); if((pc = pathconf(".", _PC_NAME_MAX)) == -1){ /* su_err_no() unchanged: no limit */ if(su_err_no() == su_ERR_NONE) break; # endif if(UCMP(z, NAME_MAX - 1, <, i)) goto jenametool; # ifdef mx_HAVE_PATHCONF }else if(pc - 1 >= i) break; else goto jenametool; # endif }while(0); fdi.fdi_lock_name = name; /* We are in the directory of the mailbox for which we have to create * a dotlock file for. Any symbolic links have been resolved. * We do not know whether we have realpath(3) available,and manually * resolving the path is due especially given that we support the special * "%:" syntax to warp any file into a "system mailbox"; there may also be * multiple system mailbox directories... * So what we do is that we fstat(2) the mailbox and check its UID and * GID against that of our own process: if any of those mismatch we must * either assume a directory we are not allowed to write in, or that we run * via -u/$USER/%USER as someone else, in which case we favour our * privilege-separated dotlock process */ ASSERT(cp != NIL); /* Ugly: avoid a useless var and reuse that one */ if(access(".", W_OK)){ /* This may however also indicate a read-only filesystem, which is not * really an error from our point of view since the mailbox will degrade * to a readonly one for which no dotlock is needed, then, and errors * may arise only due to actions which require box modifications */ if(su_err_no() == su_ERR_ROFS){ fdls = mx_FILE_DOTLOCK_STATE_ROFS | mx_FILE_DOTLOCK_STATE_ABANDON; goto jmsg; } cp = NIL; } if(cp == NIL || stb.st_uid != n_user_id || stb.st_gid != n_group_id){ char itoabuf[64]; char const *args[13]; snprintf(itoabuf, sizeof itoabuf, "%" PRIuZ, fdi.fdi_pollmsecs); args[ 0] = VAL_PS_DOTLOCK; args[ 1] = (flt == mx_FILE_LOCK_TYPE_READ) ? "rdotlock" : "wdotlock"; args[ 2] = "mailbox"; args[ 3] = fdi.fdi_file_name; args[ 4] = "name"; args[ 5] = fdi.fdi_lock_name; args[ 6] = "hostname"; args[ 7] = fdi.fdi_hostname; args[ 8] = "randstr"; args[ 9] = fdi.fdi_randstr; args[10] = "pollmsecs"; args[11] = itoabuf; args[12] = NIL; execv(VAL_LIBEXECDIR "/" VAL_UAGENT "-dotlock", n_UNCONST(args)); fdls = mx_FILE_DOTLOCK_STATE_NOEXEC; write(STDOUT_FILENO, &fdls, sizeof fdls); /* But fall through and try it with normal privileges! */ } /* So let's try and call it ourselfs! Note we do not block signals just * like our privsep child does, the user will anyway be able to remove his * file again, and if we are in -u/$USER mode then we are allowed to access * the user's box: shall we leave behind a stale dotlock then at least we * start a friendly human conversation. Since we cannot handle SIGKILL and * SIGSTOP malicious things could happen whatever we do */ safe_signal(SIGHUP, SIG_IGN); safe_signal(SIGINT, SIG_IGN); safe_signal(SIGQUIT, SIG_IGN); safe_signal(SIGTERM, SIG_IGN); NYD; fdls = a_file_lock_dotlock_create(&fdi); NYD; /* Finally: notify our parent about the actual lock state.. */ jmsg: write(STDOUT_FILENO, &fdls, sizeof fdls); close(STDOUT_FILENO); /* ..then eventually wait until we shall remove the lock again, which will * be notified via the read returning */ if(fdls == mx_FILE_DOTLOCK_STATE_NONE){ read(STDIN_FILENO, &fdls, sizeof fdls); unlink(name); } NYD_OU; return n_EXIT_OK; } #endif /* mx_HAVE_DOTLOCK */ boole mx_file_lock(int fd, enum mx_file_lock_type flt, off_t off, off_t len, uz pollmsecs){ uz tries; boole didmsg, rv; NYD_IN; if(pollmsecs == UZ_MAX) pollmsecs = mx_FILE_LOCK_MILLIS; UNINIT(rv, 0); for(didmsg = FAL0, tries = 0; tries <= mx_FILE_LOCK_TRIES; ++tries){ rv = a_filo_lock(fd, flt, off, len); if(rv == TRUM1){ rv = FAL0; break; } if(rv || pollmsecs == 0) break; else{ if(!didmsg){ n_err(_("Failed to create a file lock, waiting %lu milliseconds "), pollmsecs); didmsg = TRU1; }else n_err("."); n_msleep(pollmsecs, FAL0); } } if(didmsg) n_err(" %s\n", (rv ? _("ok") : _("failure"))); NYD_OU; return rv; } FILE * mx_file_dotlock(char const *fname, int fd, enum mx_file_lock_type flt, off_t off, off_t len, uz pollmsecs){ #undef a_DOMSG #define a_DOMSG() \ do if(!didmsg){\ didmsg = TRUM1;\ n_err(dmsg, dmsg_name);\ }while(0) #ifdef mx_HAVE_DOTLOCK sz cpipe[2]; struct mx_file_dotlock_info fdi; enum mx_file_dotlock_state fdls; char const *emsg; #endif char const *dmsg, *dmsg_name; int serr; union {uz tries; int (*ptf)(void); char const *sh; sz r;} u; boole flocked, didmsg; FILE *rv; NYD_IN; if(pollmsecs == UZ_MAX) pollmsecs = mx_FILE_LOCK_MILLIS; rv = NIL; didmsg = FAL0; UNINIT(serr, 0); #ifdef mx_HAVE_DOTLOCK emsg = NIL; #endif dmsg = _("Creating file (dot) lock for %s "); dmsg_name = n_shexp_quote_cp(fname, FAL0); if(n_poption & n_PO_D_VV) a_DOMSG(); flocked = FAL0; for(u.tries = 0; !mx_file_lock(fd, flt, off, len, 0);) switch((serr = su_err_no())){ case su_ERR_ACCES: case su_ERR_AGAIN: case su_ERR_NOLCK: if(pollmsecs > 0 && ++u.tries < mx_FILE_LOCK_TRIES){ a_DOMSG(); n_err("."); n_msleep(pollmsecs, FAL0); continue; } /* FALLTHRU */ default: goto jleave; } flocked = TRU1; #ifndef mx_HAVE_DOTLOCK jleave: if(didmsg == TRUM1) n_err("\n"); if(flocked) rv = (FILE*)-1; else su_err_set_no(serr); NYD_OU; return rv; #else if(ok_blook(dotlock_disable)){ rv = R(FILE*,-1); goto jleave; } /* Create control-pipe for our dot file locker process, which will remove * the lock and terminate once the pipe is closed, for whatever reason */ if(!mx_fs_pipe_cloexec(cpipe)){ serr = su_err_no(); emsg = N_(" Cannot create dotlock file control pipe\n"); goto jemsg; } /* And the locker process itself; it will be a (rather cheap) thread only * unless the lock has to be placed in the system spool and we have our * privilege-separated dotlock program available, in which case that will be * executed and do "it" */ fdi.fdi_file_name = fname; fdi.fdi_pollmsecs = pollmsecs; /* Initialize some more stuff; query the two strings in the parent in order * to cache the result of the former and anyway minimalize child page-ins. * Especially uname(3) may hang for multiple seconds when it is called the * first time! */ fdi.fdi_hostname = n_nodename(FAL0); fdi.fdi_randstr = mx_random_create_cp(16, NIL); a_filo_flt = flt; a_filo_fd = fd; a_filo_fdip = &fdi; u.ptf = &a_filo_main; rv = mx_fs_pipe_open(R(char*,-1), "W", u.sh, NIL, cpipe[1]); serr = su_err_no(); close(S(int,cpipe[1])); if(rv == NIL){ close(S(int,cpipe[0])); emsg = N_(" Cannot create file lock process\n"); goto jemsg; } /* Let's check whether we were able to create the dotlock file */ for(;;){ u.r = read(S(int,cpipe[0]), &fdls, sizeof fdls); if(UCMP(z, u.r, !=, sizeof fdls)){ serr = (u.r != -1) ? su_ERR_AGAIN : su_err_no(); fdls = mx_FILE_DOTLOCK_STATE_DUNNO | mx_FILE_DOTLOCK_STATE_ABANDON; }else serr = su_ERR_NONE; if(fdls == mx_FILE_DOTLOCK_STATE_NONE || (fdls & mx_FILE_DOTLOCK_STATE_ABANDON)) close(S(int,cpipe[0])); switch(fdls & ~mx_FILE_DOTLOCK_STATE_ABANDON){ case mx_FILE_DOTLOCK_STATE_NONE: goto jleave; case mx_FILE_DOTLOCK_STATE_CANT_CHDIR: if(n_poption & n_PO_D_V) emsg = N_(" Cannot change directory, please check permissions\n"); serr = su_ERR_ACCES; break; case mx_FILE_DOTLOCK_STATE_NAMETOOLONG: emsg = N_("Resulting dotlock filename would be too long\n"); serr = su_ERR_ACCES; break; case mx_FILE_DOTLOCK_STATE_ROFS: ASSERT(fdls & mx_FILE_DOTLOCK_STATE_ABANDON); if(n_poption & n_PO_D_V) emsg = N_(" Read-only filesystem, not creating lock file\n"); serr = su_ERR_ROFS; break; case mx_FILE_DOTLOCK_STATE_NOPERM: if((n_psonce & n_PSO_INTERACTIVE) || (n_poption & n_PO_D_V)) emsg = N_(" Cannot create a dotlock file, " "please check permissions\n" " (Or set *dotlock-disable*, then try again)\n"); serr = su_ERR_ACCES; break; case mx_FILE_DOTLOCK_STATE_NOEXEC: if((n_psonce & (n_PSO_INTERACTIVE | n_PSO_PS_DOTLOCK_NOTED) ) == n_PSO_INTERACTIVE || (n_poption & n_PO_D_V)){ n_psonce |= n_PSO_PS_DOTLOCK_NOTED; emsg = N_(" Cannot find privilege-separated dotlock program\n"); } serr = su_ERR_NOENT; break; case mx_FILE_DOTLOCK_STATE_PRIVFAILED: emsg = N_(" Privilege-separated dotlock program cannot change " "privileges\n"); serr = su_ERR_PERM; break; case mx_FILE_DOTLOCK_STATE_EXIST: emsg = N_(" It seems there is a stale dotlock file?\n" " Please remove the lock file manually, then retry\n"); serr = su_ERR_EXIST; break; case mx_FILE_DOTLOCK_STATE_FISHY: emsg = N_(" Fishy! Is someone trying to \"steal\" foreign files?\n" " Please check the mailbox file etc. manually, then retry\n"); serr = su_ERR_AGAIN; /* ? Hack to ignore *dotlock-ignore-error* xxx */ break; default: case mx_FILE_DOTLOCK_STATE_DUNNO: emsg = N_(" Unspecified dotlock file control process error.\n" " Like broken I/O pipe; this one is unlikely to happen\n"); if(serr != su_ERR_AGAIN) serr = su_ERR_INVAL; break; case mx_FILE_DOTLOCK_STATE_PING: a_DOMSG(); n_err("."); continue; } if(emsg != NIL){ a_DOMSG(); n_err(_(". failed\n%s%s"), V_(emsg), ((fdls & mx_FILE_DOTLOCK_STATE_ABANDON) ? su_empty : _("Trying different policy "))); didmsg = TRU1; emsg = NIL; } if(fdls & mx_FILE_DOTLOCK_STATE_ABANDON){ mx_fs_pipe_close(rv, FAL0); rv = NIL; break; } } jleave: if(didmsg == TRUM1) n_err(". %s\n", (rv != NIL ? _("ok") : _("failed"))); if(rv == NIL){ if(flocked){ if(serr == su_ERR_ROFS) rv = R(FILE*,-1); else if(serr != su_ERR_AGAIN && serr != su_ERR_EXIST && ok_blook(dotlock_ignore_error)){ n_OBSOLETE(_("*dotlock-ignore-error*: please use " "*dotlock-disable* instead")); if(n_poption & n_PO_D_V) n_err(_(" *dotlock-ignore-error* set: continuing\n")); rv = R(FILE*,-1); }else goto jserr; }else jserr: su_err_set_no(serr); } NYD_OU; return rv; jemsg: a_DOMSG(); n_err("\n"); n_err(V_(emsg)); didmsg = TRU1; goto jleave; #endif /* mx_HAVE_DOTLOCK */ #undef a_DOMSG } #include "su/code-ou.h" /* s-it-mode */ s-nail-14.9.15/src/mx/file-streams.c000066400000000000000000000757151352610246600170540ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ Implementation of file-streams.h. *@ TODO tmp_release() should be removed: tmp_open() should take an optional *@ TODO vector of NIL terminated {char const *mode;sz fd_result;} structs, *@ TODO and create all desired copies; drop HOLDSIGS, then, too! * * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #undef su_FILE #define su_FILE file_streams #define mx_SOURCE #define mx_SOURCE_FILE_STREAMS #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif #include #include #include #include "mx/child.h" #include "mx/cmd-filetype.h" #include "mx/random.h" #include "mx/sigs.h" #include "mx/termcap.h" #include "mx/file-streams.h" #include "su/code-in.h" #ifdef O_CLOEXEC # define a_FS__O_CLOEXEC O_CLOEXEC #else # define a_FS__O_CLOEXEC 0 #endif enum{ a_FS_PIPE_READ = 0, a_FS_PIPE_WRITE = 1 }; enum a_fs_ent_flags{ a_FS_EF_RAW, a_FS_EF_IMAP = 1u<<1, a_FS_EF_MAILDIR = 1u<<2, a_FS_EF_HOOK = 1u<<3, a_FS_EF_PIPE = 1u<<4, a_FS_EF_MASK = (1u<<7) - 1, /* TODO a_FS_EF_UNLINK: should be in a separate process so that unlinking * TODO the temporary "garbage" is "safe"(r than it is like that) */ a_FS_EF_UNLINK = 1u<<8, /* mx_fs_tmp_release() callable? */ a_FS_EF_HOLDSIGS = 1u<<9, a_FS_EF_FD_PASS_NEEDS_WAIT = 1u<<10 }; /* This struct is what backs struct mx_fs_tmp_ctx! */ struct a_fs_ent{ char *fse_realfile; struct a_fs_ent *fse_link; u32 fse_flags; int fse_omode; long fse_offset; /* TODO SU I/O 64-bit */ FILE *fse_fp; char *fse_save_cmd; struct mx_child_ctx fse_cc; }; struct a_fs_lpool_ent{ struct a_fs_lpool_ent *fsle_last; char *fsle_dat; uz fsle_size; }; static struct a_fs_ent *a_fs_fp_head; struct a_fs_lpool_ent *a_fs_lpool_free; struct a_fs_lpool_ent *a_fs_lpool_used; /* Scan file open mode, and turn it to flags. If & was prepended, set *mode * to NIL to indicate O_REGISTER shall not be set */ static boole a_fs_scan_mode(char const **mode, int *omode); /* */ static struct a_fs_ent *a_fs_register_file(FILE *fp, int omode, struct mx_child_ctx *ccp, u32 flags, char const *realfile, long offset, char const *save_cmd); static boole a_fs_unregister_file(FILE *fp); /* */ static boole a_fs_file_load(uz flags, int infd, int outfd, char const *load_cmd); static boole a_fs_file_save(struct a_fs_ent *fpp); static boole a_fs_scan_mode(char const **mode, int *omode){ static struct{ char const mode[4]; int omode; }const maps[] = { {"r", O_RDONLY}, {"w", O_WRONLY | O_CREAT | mx_O_NOXY_BITS | O_TRUNC}, {"wx", O_WRONLY | O_CREAT | O_EXCL}, {"a", O_WRONLY | O_APPEND | O_CREAT | mx_O_NOXY_BITS}, {"a+", O_RDWR | O_APPEND | O_CREAT | mx_O_NOXY_BITS}, {"r+", O_RDWR}, {"w+", O_RDWR | O_CREAT | mx_O_NOXY_BITS | O_TRUNC}, {"W+", O_RDWR | O_CREAT | O_EXCL} }; boole rv; uz i; char const *xmode; NYD2_IN; if((xmode = *mode)[0] == '&'){ *mode = NIL; ++xmode; } for(i = 0; i < NELEM(maps); ++i) if(!su_cs_cmp(maps[i].mode, xmode)){ *omode = maps[i].omode; rv = TRU1; goto jleave; } su_DBG( n_alert(_("Internal error: bad stdio open mode %s"), xmode); ) su_NDBG( su_err_set_no(su_ERR_INVAL); ) *omode = 0; /* (silence CC) */ rv = FAL0; jleave: NYD2_OU; return rv; } static struct a_fs_ent * a_fs_register_file(FILE *fp, int omode, struct mx_child_ctx *ccp, u32 flags, char const *realfile, long offset, char const *save_cmd){ struct a_fs_ent *fsep; NYD_IN; ASSERT(!(flags & a_FS_EF_UNLINK) || realfile != NIL); fsep = su_TCALLOC(struct a_fs_ent, 1); if(realfile != NIL) fsep->fse_realfile = su_cs_dup(realfile, 0); fsep->fse_link = a_fs_fp_head; fsep->fse_flags = flags; fsep->fse_omode = omode; fsep->fse_offset = offset; fsep->fse_fp = fp; if(save_cmd != NIL) fsep->fse_save_cmd = su_cs_dup(save_cmd, 0); if(ccp != NIL) fsep->fse_cc = *ccp; a_fs_fp_head = fsep; NYD_OU; return fsep; } static boole a_fs_unregister_file(FILE *fp){ struct a_fs_ent **fsepp, *fsep; boole rv; NYD_IN; rv = TRU1; for(fsepp = &a_fs_fp_head;; fsepp = &fsep->fse_link){ if(UNLIKELY((fsep = *fsepp) == NIL)){ su_DBGOR(n_panic, n_alert)(_("Invalid file pointer")); rv = FAL0; break; }else if(fsep->fse_fp != fp) continue; switch(fsep->fse_flags & a_FS_EF_MASK){ case a_FS_EF_RAW: case a_FS_EF_PIPE: break; default: rv = a_fs_file_save(fsep); break; } if((fsep->fse_flags & a_FS_EF_UNLINK) && unlink(fsep->fse_realfile)) rv = FAL0; *fsepp = fsep->fse_link; if(fsep->fse_realfile != NIL) su_FREE(fsep->fse_realfile); if(fsep->fse_save_cmd != NIL) su_FREE(fsep->fse_save_cmd); su_FREE(fsep); break; } NYD_OU; return rv; } static boole a_fs_file_load(uz flags, int infd, int outfd, char const *load_cmd){ struct mx_child_ctx cc; boole rv; NYD2_IN; mx_child_ctx_setup(&cc); cc.cc_flags = mx_CHILD_RUN_WAIT_LIFE; cc.cc_fds[mx_CHILD_FD_IN] = infd; cc.cc_fds[mx_CHILD_FD_OUT] = outfd; switch(flags & a_FS_EF_MASK){ case a_FS_EF_IMAP: case a_FS_EF_MAILDIR: rv = TRU1; break; case a_FS_EF_HOOK: cc.cc_cmd = ok_vlook(SHELL); cc.cc_args[0] = "-c"; cc.cc_args[1] = load_cmd; if(0){ /* FALLTHRU */ default: cc.cc_cmd = mx_FS_FILETYPE_CAT_PROG; } rv = (mx_child_run(&cc) && cc.cc_exit_status == 0); break; } NYD2_OU; return rv; } static boole a_fs_file_save(struct a_fs_ent *fsep){ struct mx_child_ctx cc; boole rv; NYD_IN; if(fsep->fse_omode == O_RDONLY){ rv = TRU1; goto jleave; } rv = FAL0; fflush(fsep->fse_fp); clearerr(fsep->fse_fp); /* Ensure the I/O library does not optimize the fseek(3) away! */ if(!n_real_seek(fsep->fse_fp, fsep->fse_offset, SEEK_SET)){ s32 err; err = su_err_no(); n_err(_("Fatal: cannot restore file position and save %s: %s\n"), n_shexp_quote_cp(fsep->fse_realfile, FAL0), su_err_doc(err)); goto jleave; } #ifdef mx_HAVE_MAILDIR if((fsep->fse_flags & a_FS_EF_MASK) == a_FS_EF_MAILDIR){ rv = maildir_append(fsep->fse_realfile, fsep->fse_fp, fsep->fse_offset); goto jleave; } #endif #ifdef mx_HAVE_IMAP if((fsep->fse_flags & a_FS_EF_MASK) == a_FS_EF_IMAP){ rv = imap_append(fsep->fse_realfile, fsep->fse_fp, fsep->fse_offset); goto jleave; } #endif mx_child_ctx_setup(&cc); cc.cc_flags = mx_CHILD_RUN_WAIT_LIFE; cc.cc_fds[mx_CHILD_FD_IN] = fileno(fsep->fse_fp); if((cc.cc_fds[mx_CHILD_FD_OUT] = open(fsep->fse_realfile, ((fsep->fse_omode | O_CREAT | (fsep->fse_omode & O_APPEND ? 0 : O_TRUNC) | mx_O_NOXY_BITS ) & ~O_EXCL), 0666)) == -1){ s32 err; err = su_err_no(); n_err(_("Fatal: cannot create %s: %s\n"), n_shexp_quote_cp(fsep->fse_realfile, FAL0), su_err_doc(err)); goto jleave; } switch(fsep->fse_flags & a_FS_EF_MASK){ case a_FS_EF_HOOK: if(n_poption & n_PO_D_V) n_err(_("Using `filetype' handler %s to save %s\n"), n_shexp_quote_cp(fsep->fse_save_cmd, FAL0), n_shexp_quote_cp(fsep->fse_realfile, FAL0)); cc.cc_cmd = ok_vlook(SHELL); cc.cc_args[0] = "-c"; cc.cc_args[1] = fsep->fse_save_cmd; break; default: cc.cc_cmd = mx_FS_FILETYPE_CAT_PROG; break; } rv = (mx_child_run(&cc) && cc.cc_exit_status == 0); close(cc.cc_fds[mx_CHILD_FD_OUT]); /* XXX no error handling */ jleave: NYD_OU; return rv; } FILE * mx_fs_open(char const *file, char const *oflags){ int osflags, fd; char const *moflags; FILE *fp; NYD_IN; fp = NIL; moflags = oflags; if(!a_fs_scan_mode(&moflags, &osflags)) goto jleave; osflags |= a_FS__O_CLOEXEC; if(moflags == NIL) ++oflags; if((fd = open(file, osflags, 0666)) == -1) goto jleave; mx_FS_FD_CLOEXEC_SET(fd); if((fp = fdopen(fd, oflags)) != NIL && moflags != NIL) a_fs_register_file(fp, osflags, 0, a_FS_EF_RAW, NIL, 0L, NIL); jleave: NYD_OU; return fp; } FILE * mx_fs_open_any(char const *file, char const *oflags, /* TODO take flags */ enum mx_fs_open_state *fs_or_nil){ /* TODO as bits, return state */ /* TODO Support file locking upon open time */ long offset; enum protocol p; enum mx_fs_oflags rof; uz flags; int osflags, omode, infd; char const *cload, *csave; enum mx_fs_open_state fs; s32 err; FILE *rv; NYD_IN; rv = NIL; err = su_ERR_NONE; fs = mx_FS_OPEN_STATE_NONE; cload = csave = NIL; /* !O_REGISTER is disallowed */ if(!a_fs_scan_mode(&oflags, &osflags) || oflags == NIL){ err = su_ERR_INVAL; goto jleave; } flags = 0; rof = mx_FS_O_RDWR | mx_FS_O_UNLINK; if(osflags & O_APPEND) rof |= mx_FS_O_APPEND; omode = (osflags == O_RDONLY) ? R_OK : R_OK | W_OK; /* We don't want to find mbox.bz2 when doing "copy * mbox", but only for * "file mbox", so don't try hooks when writing */ p = which_protocol(csave = file, TRU1, ((omode & W_OK) == 0), &file); fs = S(enum mx_fs_open_state,p); switch(p){ default: goto jleave; case n_PROTO_IMAP: #ifdef mx_HAVE_IMAP file = csave; flags |= a_FS_EF_IMAP; osflags = O_RDWR | O_APPEND | O_CREAT | mx_O_NOXY_BITS; infd = -1; break; #else err = su_ERR_OPNOTSUPP; goto jleave; #endif case n_PROTO_MAILDIR: #ifdef mx_HAVE_MAILDIR if(fs_or_nil != NIL && !access(file, F_OK)) fs |= mx_FS_OPEN_STATE_EXISTS; flags |= a_FS_EF_MAILDIR; osflags = O_RDWR | O_APPEND | O_CREAT | mx_O_NOXY_BITS; infd = -1; break; #else err = su_ERR_OPNOTSUPP; goto jleave; #endif case n_PROTO_FILE:{ struct mx_filetype ft; if(!(osflags & O_EXCL) && fs_or_nil != NIL && !access(file, F_OK)) fs |= mx_FS_OPEN_STATE_EXISTS; if(mx_filetype_exists(&ft, file)){ flags |= a_FS_EF_HOOK; cload = ft.ft_load_dat; csave = ft.ft_save_dat; /* Cause truncation for compressor/hook output files */ osflags &= ~O_APPEND; rof &= ~mx_FS_O_APPEND; if((infd = open(file, (omode & W_OK ? O_RDWR : O_RDONLY))) != -1){ fs |= mx_FS_OPEN_STATE_EXISTS; if(n_poption & n_PO_D_V) n_err(_("Using `filetype' handler %s to load %s\n"), n_shexp_quote_cp(cload, FAL0), n_shexp_quote_cp(file, FAL0)); }else{ err = su_err_no(); if(!(osflags & O_CREAT) || err != su_ERR_NOENT) goto jleave; } }else{ /*flags |= a_FS_EF_RAW;*/ rv = mx_fs_open(file, oflags); if((osflags & O_EXCL) && rv == NIL) fs |= mx_FS_OPEN_STATE_EXISTS; goto jleave; } }break; } /* Note rv is not yet register_file()d, fclose() it in error path! */ if((rv = mx_fs_tmp_open("fopenany", rof, NIL)) == NIL){ n_perr(_("tmpfile"), err = su_err_no()); goto Jerr; } if(flags & (a_FS_EF_IMAP | a_FS_EF_MAILDIR)) ; else if(infd >= 0){ if(!a_fs_file_load(flags, infd, fileno(rv), cload)) Jerr:{ err = su_err_no(); if(rv != NIL) mx_fs_close(rv); rv = NIL; if(infd >= 0) close(infd); goto jleave; } }else{ if((infd = creat(file, 0666)) == -1){ err = su_err_no(); fclose(rv); rv = NIL; goto jleave; } } if(infd >= 0) close(infd); fflush(rv); if(!(osflags & O_APPEND)) rewind(rv); if((offset = ftell(rv)) == -1){ err = su_err_no(); mx_fs_close(rv); rv = NIL; goto jleave; } a_fs_register_file(rv, osflags, 0, flags, file, offset, csave); jleave: if(fs_or_nil != NIL) *fs_or_nil = fs; if(rv == NIL && err != su_ERR_NONE) su_err_set_no(err); NYD_OU; return rv; } FILE * mx_fs_tmp_open(char const *namehint, u32 oflags, struct mx_fs_tmp_ctx **fstcp_or_nil){ /* The 8 is arbitrary but leaves room for a six character suffix (the * POSIX minimum path length is 14, though we don't check that XXX). * 8 should be more than sufficient given that we use base64url encoding * for our random string */ enum {a_RANDCHARS = 8u}; char *cp_base, *cp; uz maxname, xlen, i; char const *tmpdir; int osoflags, fd, e; boole relesigs; FILE *fp; NYD_IN; ASSERT(namehint != NIL); ASSERT((oflags & mx_FS_O_WRONLY) || (oflags & mx_FS_O_RDWR)); ASSERT(!(oflags & mx_FS_O_RDONLY)); ASSERT(!(oflags & mx_FS_O_REGISTER_UNLINK) || (oflags & mx_FS_O_REGISTER)); ASSERT(!(oflags & mx_FS_O_HOLDSIGS) || !(oflags & mx_FS_O_UNLINK)); ASSERT(fstcp_or_nil == NIL || ((oflags & mx_FS_O_REGISTER) || (oflags & mx_FS_O_HOLDSIGS) || !(oflags & mx_FS_O_UNLINK))); if(fstcp_or_nil != NIL) *fstcp_or_nil = NIL; fp = NIL; relesigs = FAL0; e = 0; tmpdir = ok_vlook(TMPDIR); maxname = NAME_MAX; #ifdef mx_HAVE_PATHCONF /* C99 */{ long pc; if((pc = pathconf(tmpdir, _PC_NAME_MAX)) != -1){ maxname = S(uz,pc); if(maxname <= a_RANDCHARS + 1){ su_err_set_no(su_ERR_NAMETOOLONG); goto jleave; } } } #endif if((oflags & mx_FS_O_SUFFIX) && *namehint != '\0'){ if((xlen = su_cs_len(namehint)) > maxname - a_RANDCHARS){ su_err_set_no(su_ERR_NAMETOOLONG); goto jleave; } }else xlen = 0; /* Prepare the template string once, then iterate over the random range. * But first ensure we can report the name in !O_REGISTER cases ("hack") */ i = su_cs_len(tmpdir) + 1 + maxname +1; if(!(oflags & mx_FS_O_REGISTER) && fstcp_or_nil != NIL){ union {struct a_fs_ent *fse; void *v; struct mx_fs_tmp_ctx *fstc;} p; p.v = n_autorec_alloc(sizeof(*p.fse) + i); su_mem_set(p.fse, 0, sizeof(*p.fse)); p.fse->fse_realfile = R(char*,&p.fse[1]); if(oflags & mx_FS_O_HOLDSIGS) /* Enable tmp_release() nonetheless? */ p.fse->fse_flags = a_FS_EF_HOLDSIGS; *fstcp_or_nil = p.fstc; } cp_base = cp = n_lofi_alloc(i); cp = su_cs_pcopy(cp, tmpdir); *cp++ = '/'; /* C99 */{ char *x; x = su_cs_pcopy(cp, VAL_UAGENT); *x++ = '-'; if(!(oflags & mx_FS_O_SUFFIX)) x = su_cs_pcopy(x, namehint); i = P2UZ(x - cp); if(i > maxname - xlen - a_RANDCHARS){ uz j; j = maxname - xlen - a_RANDCHARS; x -= i - j; } if((oflags & mx_FS_O_SUFFIX) && xlen > 0) su_mem_copy(x + a_RANDCHARS, namehint, xlen); x[xlen + a_RANDCHARS] = '\0'; cp = x; } osoflags = O_CREAT | O_EXCL | a_FS__O_CLOEXEC; osoflags |= (oflags & mx_FS_O_WRONLY) ? O_WRONLY : O_RDWR; if(oflags & mx_FS_O_APPEND) osoflags |= O_APPEND; for(relesigs = TRU1, i = 0;; ++i){ su_mem_copy(cp, mx_random_create_cp(a_RANDCHARS, NIL), a_RANDCHARS); mx_sigs_all_holdx(); if((fd = open(cp_base, osoflags, 0600)) != -1){ mx_FS_FD_CLOEXEC_SET(fd); break; } if(i >= mx_FS_TMP_OPEN_TRIES){ e = su_err_no(); goto jfree; } mx_sigs_all_rele(); } /* C99 */{ char const *osflags; osflags = (oflags & mx_FS_O_RDWR ? "w+" : "w"); if((fp = fdopen(fd, osflags)) != NIL){ if(oflags & mx_FS_O_REGISTER){ struct a_fs_ent *fsep; int osflagbits; a_fs_scan_mode(&osflags, &osflagbits); /* TODO osoflags&xy ?!!? */ fsep = a_fs_register_file(fp, osflagbits | a_FS__O_CLOEXEC, 0, (a_FS_EF_RAW | (oflags & mx_FS_O_REGISTER_UNLINK ? a_FS_EF_UNLINK : 0) | (oflags & mx_FS_O_HOLDSIGS ? a_FS_EF_HOLDSIGS : 0)), cp_base, 0L, NIL); if(fstcp_or_nil != NIL){ union {void *v; struct mx_fs_tmp_ctx *fstc;} p; p.v = fsep; *fstcp_or_nil = p.fstc; } }else if(fstcp_or_nil != NIL){ /* Otherwise copy filename buffer into autorec storage */ cp = UNCONST(char*,(*fstcp_or_nil)->fstc_filename); su_cs_pcopy(cp, cp_base); } } } if(fp == NIL || (oflags & mx_FS_O_UNLINK)){ e = su_err_no(); unlink(cp_base); goto jfree; }else if(fp != NIL){ /* We will succeed and keep the file around for further usage, likely * another stream will be opened for pure reading purposes (this is true * at the time of this writing. A restrictive umask(2) settings may have * turned the path inaccessible, so ensure it may be read at least! * TODO once ok_vlook() can return an integer, look up *umask* first! */ (void)fchmod(fd, S_IWUSR | S_IRUSR); } n_lofi_free(cp_base); jleave: if(relesigs && (fp == NIL || !(oflags & mx_FS_O_HOLDSIGS))) mx_sigs_all_rele(); if(fp == NIL){ su_err_set_no(e); if(fstcp_or_nil != NIL) *fstcp_or_nil = NIL; } NYD_OU; return fp; jfree: if((cp = cp_base) != NIL) n_lofi_free(cp); goto jleave; } void mx_fs_tmp_release(struct mx_fs_tmp_ctx *fstcp){ union {void *vp; struct a_fs_ent *fsep;} u; NYD_IN; ASSERT(fstcp != NIL); u.vp = fstcp; ASSERT(u.fsep->fse_flags & a_FS_EF_HOLDSIGS); DBG( if(u.fsep->fse_flags & a_FS_EF_UNLINK) n_alert("tmp_release(): REGISTER_UNLINK set!"); ) unlink(u.fsep->fse_realfile); u.fsep->fse_flags &= ~(a_FS_EF_HOLDSIGS | a_FS_EF_UNLINK); mx_sigs_all_rele(); NYD_OU; } FILE * mx_fs_fd_open(sz fd, char const *oflags, boole nocloexec){ FILE *fp; int osflags; NYD_IN; a_fs_scan_mode(&oflags, &osflags); if(!nocloexec) osflags |= a_FS__O_CLOEXEC; /* Ensured by caller as documented! */ if((fp = fdopen(S(int,fd), oflags)) != NIL && oflags != NIL) a_fs_register_file(fp, osflags, 0, a_FS_EF_RAW, NIL, 0L, NIL); NYD_OU; return fp; } void mx_fs_fd_cloexec_set(sz fd){ s32 ifd; NYD2_IN; ifd = S(s32,fd); /*if((a__fl = fcntl(ifd, F_GETFD)) != -1 && !(ifd & FD_CLOEXEC))*/ (void)fcntl(ifd, F_SETFD, FD_CLOEXEC); NYD2_OU; } boole mx_fs_close(FILE *fp){ boole rv; NYD_IN; rv = (a_fs_unregister_file(fp) && fclose(fp) == 0); NYD_OU; return rv; } boole mx_fs_pipe_cloexec(sz fd[2]){ int xfd[2]; boole rv; NYD_IN; rv = FAL0; #ifdef mx_HAVE_PIPE2 if(pipe2(xfd, O_CLOEXEC) != -1){ fd[0] = xfd[0]; fd[1] = xfd[1]; rv = TRU1; } #else if(pipe(xfd) != -1){ fd[0] = xfd[0]; fd[1] = xfd[1]; mx_fs_fd_cloexec_set(fd[0]); mx_fs_fd_cloexec_set(fd[1]); rv = TRU1; } #endif NYD_OU; return rv; } FILE * mx_fs_pipe_open(char const *cmd, char const *mode, char const *sh, char const **env_addon, int newfd1){ struct mx_child_ctx cc; sz p[2], myside, hisside, fd0, fd1; sigset_t nset; char mod[2]; FILE *rv; NYD_IN; rv = NIL; mod[0] = '0', mod[1] = '\0'; if(!mx_fs_pipe_cloexec(p)) goto jleave; if(*mode == 'r'){ myside = p[a_FS_PIPE_READ]; fd0 = mx_CHILD_FD_PASS; hisside = fd1 = p[a_FS_PIPE_WRITE]; mod[0] = *mode; }else if(*mode == 'W'){ myside = p[a_FS_PIPE_WRITE]; hisside = fd0 = p[a_FS_PIPE_READ]; fd1 = newfd1; mod[0] = 'w'; }else{ myside = p[a_FS_PIPE_WRITE]; hisside = fd0 = p[a_FS_PIPE_READ]; fd1 = mx_CHILD_FD_PASS; mod[0] = 'w'; } sigemptyset(&nset); mx_child_ctx_setup(&cc); /* Be simple and let's just synchronize completely on that */ cc.cc_flags = ((cc.cc_cmd = cmd) == R(char*,-1)) ? mx_CHILD_SPAWN_CONTROL : mx_CHILD_SPAWN_CONTROL | mx_CHILD_SPAWN_CONTROL_LINGER; cc.cc_mask = &nset; cc.cc_fds[mx_CHILD_FD_IN] = fd0; cc.cc_fds[mx_CHILD_FD_OUT] = fd1; cc.cc_env_addon = env_addon; /* Is this a special in-process fork setup? */ if(cmd == R(char*,-1)){ if(!mx_child_fork(&cc)) n_perr(_("fork"), 0); else if(cc.cc_pid == 0){ union {char const *ccp; int (*ptf)(void); int es;} u; mx_child_in_child_setup(&cc); close(S(int,p[a_FS_PIPE_READ])); close(S(int,p[a_FS_PIPE_WRITE])); /* TODO close all other open FDs except stds and reset memory */ /* Standard I/O drives me insane! All we need is a sync operation * that causes n_stdin to forget about any read buffer it may have. * We cannot use fflush(3), this works with Musl and Solaris, but not * with GlibC. (For at least pipes.) We cannot use fdreopen(), * because this function does not exist! Luckily (!!!) we only use * n_stdin not stdin in our child, otherwise all bets were off! * TODO (Unless we would fiddle around with FILE* directly: * TODO #ifdef __GLIBC__ * TODO n_stdin->_IO_read_ptr = n_stdin->_IO_read_end; * TODO #elif *BSD* * TODO n_stdin->_r = 0; * TODO #elif su_OS_SOLARIS || su_OS_SUNOS * TODO n_stdin->_cnt = 0; * TODO #endif * TODO ) which should have additional config test for sure! */ n_stdin = fdopen(STDIN_FILENO, "r"); /*n_stdout = fdopen(STDOUT_FILENO, "w");*/ /*n_stderr = fdopen(STDERR_FILENO, "w");*/ u.ccp = sh; u.es = (*u.ptf)(); /*fflush(NULL);*/ for(;;) _exit(u.es); } }else{ if(sh != NIL){ cc.cc_cmd = sh; cc.cc_args[0] = "-c"; cc.cc_args[1] = cmd; } mx_child_run(&cc); } if(cc.cc_pid < 0){ close(S(int,p[a_FS_PIPE_READ])); close(S(int,p[a_FS_PIPE_WRITE])); goto jleave; } close(S(int,hisside)); if((rv = fdopen(myside, mod)) != NIL) a_fs_register_file(rv, 0, &cc, ((fd0 != mx_CHILD_FD_PASS && fd1 != mx_CHILD_FD_PASS) ? a_FS_EF_PIPE : a_FS_EF_PIPE | a_FS_EF_FD_PASS_NEEDS_WAIT), NIL, 0L, NIL); else close(myside); jleave: NYD_OU; return rv; } s32 mx_fs_pipe_signal(FILE *fp, s32 sig){ s32 rv; struct a_fs_ent *fsep; NYD_IN; for(fsep = a_fs_fp_head;; fsep = fsep->fse_link) if(fsep == NIL){ rv = -1; break; }else if(fsep->fse_fp == fp){ rv = mx_child_signal(&fsep->fse_cc, sig); break; } NYD_OU; return rv; } boole mx_fs_pipe_close(FILE *ptr, boole dowait){ n_sighdl_t opipe; struct mx_child_ctx cc; boole rv; struct a_fs_ent *fsep; NYD_IN; for(fsep = a_fs_fp_head;; fsep = fsep->fse_link) if(UNLIKELY(fsep == NIL)){ DBG( n_alert(_("pipe_close: invalid file pointer")); ) rv = FAL0; goto jleave; }else if(fsep->fse_fp == ptr) break; cc = fsep->fse_cc; ASSERT(!(fsep->fse_flags & a_FS_EF_FD_PASS_NEEDS_WAIT) || dowait); a_fs_unregister_file(ptr); opipe = safe_signal(SIGPIPE, SIG_IGN); /* TODO only because we jump stuff */ fclose(ptr); safe_signal(SIGPIPE, opipe); if(dowait) rv = (mx_child_wait(&cc) && cc.cc_exit_status == 0); else{ mx_child_forget(&cc); rv = TRU1; } jleave: NYD_OU; return rv; } void mx_fs_close_all(void){ NYD_IN; while(a_fs_fp_head != NIL) if((a_fs_fp_head->fse_flags & a_FS_EF_MASK) == a_FS_EF_PIPE) mx_fs_pipe_close(a_fs_fp_head->fse_fp, TRU1); else mx_fs_close(a_fs_fp_head->fse_fp); NYD_OU; } void mx_fs_linepool_aquire(char **dpp, uz *dsp){ struct a_fs_lpool_ent *lpep; NYD2_IN; if((lpep = a_fs_lpool_free) != NIL) a_fs_lpool_free = lpep->fsle_last; else lpep = su_TCALLOC(struct a_fs_lpool_ent, 1); lpep->fsle_last = a_fs_lpool_used; a_fs_lpool_used = lpep; *dpp = lpep->fsle_dat; lpep->fsle_dat = NIL; *dsp = lpep->fsle_size; NYD2_OU; } void mx_fs_linepool_release(char *dp, uz ds){ struct a_fs_lpool_ent *lpep; NYD2_IN; ASSERT(a_fs_lpool_used != NIL); lpep = a_fs_lpool_used; a_fs_lpool_used = lpep->fsle_last; lpep->fsle_last = a_fs_lpool_free; a_fs_lpool_free = lpep; lpep->fsle_dat = dp; lpep->fsle_size = ds; NYD2_OU; } void mx_fs_linepool_cleanup(void){ struct a_fs_lpool_ent *lpep, *tmp; NYD2_IN; lpep = a_fs_lpool_used; a_fs_lpool_used = NIL; jredo: while((tmp = lpep) != NIL){ lpep = lpep->fsle_last; if(tmp->fsle_dat != NIL) su_FREE(tmp->fsle_dat); su_FREE(tmp); } if((lpep = a_fs_lpool_free) != NIL){ a_fs_lpool_free = NIL; goto jredo; } NYD2_OU; } /* TODO The rest below is old-style */ /* line is a buffer with the result of fgets(). Returns the first newline or * the last character read */ static uz _length_of_line(char const *line, uz linesize); /* Read a line, one character at a time */ static char * _fgetline_byone(char **line, uz *linesize, uz *llen, FILE *fp, int appendnl, uz n su_DBG_LOC_ARGS_DECL); static uz _length_of_line(char const *line, uz linesize) { uz i; NYD2_IN; /* Last character is always '\0' and was added by fgets() */ for (--linesize, i = 0; i < linesize; i++) if (line[i] == '\n') break; i = (i < linesize) ? i + 1 : linesize; NYD2_OU; return i; } static char * _fgetline_byone(char **line, uz *linesize, uz *llen, FILE *fp, int appendnl, uz n su_DBG_LOC_ARGS_DECL) { char *rv; int c; NYD2_IN; ASSERT(*linesize == 0 || *line != NULL); n_pstate &= ~n_PS_READLINE_NL; /* Always leave room for NETNL, not only \n */ for (rv = *line;;) { if (*linesize <= LINESIZE || n >= *linesize - 128) { *linesize += ((rv == NULL) ? LINESIZE + n + 2 : 256); *line = rv = su_MEM_REALLOC_LOCOR(rv, *linesize, su_DBG_LOC_ARGS_ORUSE); } c = getc(fp); if (c != EOF) { rv[n++] = c; rv[n] = '\0'; if (c == '\n') { n_pstate |= n_PS_READLINE_NL; break; } } else { if (n > 0) { if (appendnl) { rv[n++] = '\n'; rv[n] = '\0'; } break; } else { rv = NULL; goto jleave; } } } if (llen) *llen = n; jleave: NYD2_OU; return rv; } FL char * (fgetline)(char **line, uz *linesize, uz *cnt, uz *llen, FILE *fp, int appendnl su_DBG_LOC_ARGS_DECL) { uz i_llen, size; char *rv; NYD2_IN; if (cnt == NULL) { /* Without count, we can't determine where the chars returned by fgets() * end if there's no newline. We have to read one character by one */ rv = _fgetline_byone(line, linesize, llen, fp, appendnl, 0 su_DBG_LOC_ARGS_USE); goto jleave; } n_pstate &= ~n_PS_READLINE_NL; if ((rv = *line) == NULL || *linesize < LINESIZE) *line = rv = su_MEM_REALLOC_LOCOR(rv, *linesize = LINESIZE, su_DBG_LOC_ARGS_ORUSE); size = (*linesize <= *cnt) ? *linesize : *cnt + 1; if (size <= 1 || fgets(rv, size, fp) == NULL) { /* Leave llen untouched; it is used to determine whether the last line * was \n-terminated in some callers */ rv = NULL; goto jleave; } i_llen = _length_of_line(rv, size); *cnt -= i_llen; while (rv[i_llen - 1] != '\n') { *line = rv = su_MEM_REALLOC_LOCOR(rv, *linesize += 256, su_DBG_LOC_ARGS_ORUSE); size = *linesize - i_llen; size = (size <= *cnt) ? size : *cnt + 1; if (size <= 1 || fgets(rv + i_llen, size, fp) == NULL) { if (appendnl) { rv[i_llen++] = '\n'; rv[i_llen] = '\0'; } break; } size = _length_of_line(rv + i_llen, size); i_llen += size; *cnt -= size; } /* Always leave room for NETNL, not only \n */ if(appendnl && *linesize - i_llen < 3) *line = rv = su_MEM_REALLOC_LOCOR(rv, *linesize += 256, su_DBG_LOC_ARGS_ORUSE); if (llen) *llen = i_llen; jleave: NYD2_OU; return rv; } FL int (readline_restart)(FILE *ibuf, char **linebuf, uz *linesize, uz n su_DBG_LOC_ARGS_DECL) { /* TODO readline_restart(): always *appends* LF just to strip it again; * TODO should be configurable just as for fgetline(); ..or whatever.. * TODO intwrap */ int rv = -1; long size; NYD2_IN; clearerr(ibuf); /* Interrupts will cause trouble if we are inside a stdio call. As this is * only relevant if input is from tty, bypass it by read(), then */ if ((n_psonce & n_PSO_TTYIN) && fileno(ibuf) == 0) { ASSERT(*linesize == 0 || *linebuf != NULL); n_pstate &= ~n_PS_READLINE_NL; for (;;) { if (*linesize <= LINESIZE || n >= *linesize - 128) { *linesize += ((*linebuf == NULL) ? LINESIZE + n + 1 : 256); *linebuf = su_MEM_REALLOC_LOCOR(*linebuf, *linesize, su_DBG_LOC_ARGS_ORUSE); } jagain: size = read(0, *linebuf + n, *linesize - n - 1); if (size > 0) { n += size; (*linebuf)[n] = '\0'; if ((*linebuf)[n - 1] == '\n') { n_pstate |= n_PS_READLINE_NL; break; } } else { if (size < 0 && su_err_no() == su_ERR_INTR) goto jagain; /* TODO eh. what is this? that now supposed to be a line?!? */ if (n > 0) { if ((*linebuf)[n - 1] != '\n') { (*linebuf)[n++] = '\n'; (*linebuf)[n] = '\0'; } else n_pstate |= n_PS_READLINE_NL; break; } else goto jleave; } } } else { /* Not reading from standard input or standard input not a terminal. We * read one char at a time as it is the only way to get lines with * embedded NUL characters in standard stdio */ if (_fgetline_byone(linebuf, linesize, &n, ibuf, 1, n su_DBG_LOC_ARGS_USE) == NULL) goto jleave; } if (n > 0 && (*linebuf)[n - 1] == '\n') (*linebuf)[--n] = '\0'; rv = (int)n; jleave: NYD2_OU; return rv; } FL off_t fsize(FILE *iob) { struct stat sbuf; off_t rv; NYD_IN; rv = (fstat(fileno(iob), &sbuf) == -1) ? 0 : sbuf.st_size; NYD_OU; return rv; } #include "su/code-ou.h" /* s-it-mode */ s-nail-14.9.15/src/mx/filter-html.c000066400000000000000000001210651352610246600166760ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ HTML tagsoup filter TODO rewrite wchar_t based (require mx_HAVE_C90AMEND1) *@ TODO . Numeric &#NO; entities should be treated by struct a_flthtml_ent *@ TODO . Binary sort/search ENTITY table *@ TODO . Yes, we COULD support CSS based quoting when we'd check type="quote" *@ TODO (nonstandard) and watch out for style="gmail_quote" (or so, VERY *@ TODO nonstandard) and tracking a stack of such elements (to be popped *@ TODO once the closing element is seen). Then, after writing a newline, *@ TODO place sizeof(stack) ">"s first. But aren't these HTML mails rude? *@ TODO Interlocking and non-well-formed data will break us down * * Copyright (c) 2015 - 2019 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * 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. */ #undef su_FILE #define su_FILE filter_html #define mx_SOURCE #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif su_EMPTY_FILE() #ifdef mx_HAVE_FILTER_HTML_TAGSOUP #ifdef mx_HAVE_C90AMEND1 # include # include #endif #include #include #include #include #include "mx/sigs.h" #include "mx/termios.h" #include "mx/filter-html.h" #include "su/code-in.h" enum a_flthtml_limits{ a_FLTHTML_MINLEN = 10, /* Minimum line length (cannot really be smaller) */ a_FLTHTML_BRKSUB = 8 /* Start considering line break MAX - BRKSUB */ }; enum a_flthtml_flags{ a_FLTHTML_BQUOTE_MASK = 0xFFFFu, a_FLTHTML_UTF8 = 1u<<16, /* Data is in UTF-8 */ a_FLTHTML_ERROR = 1u<<17, /* Hard error, bail as soon as possible */ a_FLTHTML_NOPUT = 1u<<18, /* (In a tag,) Don't generate output */ a_FLTHTML_IGN = 1u<<19, /* Ignore mode on */ a_FLTHTML_ANY = 1u<<20, /* Yet seen just any output */ a_FLTHTML_PRE = 1u<<21, /* In
formatted mode */
   a_FLTHTML_ENT = 1u<<22, /* Currently parsing an entity */
   a_FLTHTML_BLANK = 1u<<23, /* Whitespace last */
   a_FLTHTML_HREF = 1u<<24, /* External  was the last href seen */

   a_FLTHTML_NL_1 = 1u<<25, /* One \n seen */
   a_FLTHTML_NL_2 = 2u<<25, /* We have produced an all empty line */
   a_FLTHTML_NL_MASK = a_FLTHTML_NL_1 | a_FLTHTML_NL_2
};

enum a_flthtml_special_actions{
   a_FLTHTML_SA_NEEDSEP = -1, /* Need an empty line (paragraph separator) */
   a_FLTHTML_SA_NEEDNL = -2, /* Need a new line start (table row) */
   a_FLTHTML_SA_IGN = -3, /* Things like , ";' couldn't
       * TODO fool it!   We really want this mode also for FLTHTML_NOPUT to be
       * TODO able to *gracefully* detect the tag-closing '>', but then if
       * TODO that is a single mechanism we should have made it! */
      if(f & a_FLTHTML_IGN){
         uz i;
         struct mx_flthtml_tag const *hftp;

         hftp = self->fh_ign_tag;

         if(c == '<'){
            hot = TRU1;
jcp_reset:
            cp = self->fh_bdat;
         }else if(c == '>'){
            if(hot){
               if((i = P2UZ(cp - self->fh_bdat)) > 1 &&
                     --i == hftp->fht_len &&
                     !su_cs_cmp_case_n(self->fh_bdat + 1, hftp->fht_tag, i))
                  self->fh_flags = (f &= ~(a_FLTHTML_IGN | a_FLTHTML_NOPUT));
               hot = FAL0;
               goto jcp_reset;
            }
         }else if(hot){
            *cp++ = c;
            i = P2UZ(cp - self->fh_bdat);
            if((i == 1 && c != '/') || --i > hftp->fht_len){
               hot = FAL0;
               goto jcp_reset;
            }
         }
      }else switch(c){
      case '<':
         /* People are using & without &ing it, ditto <; be aware */
         if(f & (a_FLTHTML_NOPUT | a_FLTHTML_ENT)){
            f &= ~a_FLTHTML_ENT;
            /* Special case " from */
      i = su_cs_len(ln) + su_cs_len(hn) + 1 +1;
      rv = cp = n_autorec_alloc(i);
      su_cs_pcopy(su_cs_pcopy(su_cs_pcopy(cp, ln), n_at), hn);
   }
   goto jleave;
}

FL char const *
myorigin(struct header *hp) /* TODO */
{
   char const *rv = NULL, *ccp;
   struct mx_name *np;
   NYD_IN;

   if((ccp = myaddrs(hp)) != NULL &&
         (np = lextract(ccp, GEXTRA | GFULL)) != NULL){
      if(np->n_flink == NULL)
         rv = ccp;
      /* Verified upon variable set time */
      else if((ccp = ok_vlook(sender)) != NULL)
         rv = ccp;
      /* TODO why not else rv = n_poption_arg_r; ?? */
   }
   NYD_OU;
   return rv;
}

FL boole
is_head(char const *linebuf, uz linelen, boole check_rfc4155)
{
   char date[n_FROM_DATEBUF];
   boole rv;
   NYD2_IN;

   if ((rv = (linelen >= 5 && !su_mem_cmp(linebuf, "From ", 5))) &&
         check_rfc4155 &&
         (a_header_extract_date_from_from_(linebuf, linelen, date) <= 0 ||
          !a_header_is_date(date)))
      rv = TRUM1;
   NYD2_OU;
   return rv;
}

FL boole
n_header_put4compose(FILE *fp, struct header *hp){
   boole rv;
   int t;
   NYD_IN;

   t = GTO | GSUBJECT | GCC | GBCC | GBCC_IS_FCC | GREF_IRT | GNL | GCOMMA;
   if((hp->h_from != NULL || myaddrs(hp) != NULL) ||
         (hp->h_sender != NULL || ok_vlook(sender) != NULL) ||
         (hp->h_reply_to != NULL || ok_vlook(reply_to) != NULL) ||
            ok_vlook(replyto) != NULL /* v15compat, OBSOLETE */ ||
         hp->h_list_post != NULL || (hp->h_flags & HF_LIST_REPLY))
      t |= GIDENT;

   rv = n_puthead(TRUM1, hp, fp, t, SEND_TODISP, CONV_NONE, NULL, NULL);
   NYD_OU;
   return rv;
}

FL void
n_header_extract(enum n_header_extract_flags hef, FILE *fp, struct header *hp,
   s8 *checkaddr_err_or_null)
{
   struct str suffix;
   struct n_header_field **hftail;
   struct header nh, *hq = &nh;
   char *linebuf, *colon;
   uz linesize, seenfields = 0;
   int c;
   long lc;
   off_t firstoff;
   char const *val, *cp;
   NYD_IN;

   mx_fs_linepool_aquire(&linebuf, &linesize);
   if(linebuf != NIL)
      linebuf[0] = '\0';

   su_mem_set(hq, 0, sizeof *hq);
   if(hef & n_HEADER_EXTRACT_PREFILL_RECEIVERS){
      hq->h_to = hp->h_to;
      hq->h_cc = hp->h_cc;
      hq->h_bcc = hp->h_bcc;
   }
   hftail = &hq->h_user_headers;

   if((firstoff = ftell(fp)) == -1)
      goto jeseek;
   for (lc = 0; readline_restart(fp, &linebuf, &linesize, 0) > 0; ++lc)
      ;
   c = fseek(fp, firstoff, SEEK_SET);
   clearerr(fp);
   if(c != 0){
jeseek:
      if(checkaddr_err_or_null != NULL)
         *checkaddr_err_or_null = -1;
      n_err("I/O error while parsing headers, operation aborted\n");
      goto jleave;
   }

   /* TODO yippieia, cat(check(lextract)) :-) */
   while ((lc = a_gethfield(hef, fp, &linebuf, &linesize, lc, &colon)) >= 0) {
      struct mx_name *np;

      /* We explicitly allow EAF_NAME for some addressees since aliases are not
       * yet expanded when we parse these! */
      if ((val = n_header_get_field(linebuf, "to", &suffix)) != NULL) {
         ++seenfields;
         if(suffix.s != su_NIL && suffix.l > 0 &&
               !su_cs_starts_with_case_n("single", suffix.s, suffix.l))
            goto jebadhead;
         hq->h_to = cat(hq->h_to, checkaddrs(lextract(val, GTO | GFULL |
               (suffix.s != su_NIL ? GNOT_A_LIST : GNONE)),
               EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err_or_null));
      } else if ((val = n_header_get_field(linebuf, "cc", &suffix)) != NULL) {
         ++seenfields;
         if(suffix.s != su_NIL && suffix.l > 0 &&
               !su_cs_starts_with_case_n("single", suffix.s, suffix.l))
            goto jebadhead;
         hq->h_cc = cat(hq->h_cc, checkaddrs(lextract(val, GCC | GFULL |
               (suffix.s != su_NIL ? GNOT_A_LIST : GNONE)),
               EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err_or_null));
      } else if ((val = n_header_get_field(linebuf, "bcc", &suffix)) != NULL) {
         ++seenfields;
         if(suffix.s != su_NIL && suffix.l > 0 &&
               !su_cs_starts_with_case_n("single", suffix.s, suffix.l))
            goto jebadhead;
         hq->h_bcc = cat(hq->h_bcc, checkaddrs(lextract(val, GBCC | GFULL |
               (suffix.s != su_NIL ? GNOT_A_LIST : GNONE)),
               EACM_NORMAL | EAF_NAME | EAF_MAYKEEP, checkaddr_err_or_null));
      } else if ((val = n_header_get_field(linebuf, "fcc", NULL)) != NULL) {
         if(hef & n_HEADER_EXTRACT__MODE_MASK){
            ++seenfields;
            hq->h_fcc = cat(hq->h_fcc, nalloc_fcc(val));
         }else
            goto jebadhead;
      } else if ((val = n_header_get_field(linebuf, "from", NULL)) != NULL) {
         if(hef & n_HEADER_EXTRACT_FULL){
            ++seenfields;
            hq->h_from = cat(hq->h_from,
                  checkaddrs(lextract(val, GEXTRA | GFULL | GFULLEXTRA),
                     EACM_STRICT, NULL));
         }
      }else if((val = n_header_get_field(linebuf, "reply-to", NULL)) != NULL){
         ++seenfields;
         hq->h_reply_to = cat(hq->h_reply_to,
               checkaddrs(lextract(val, GEXTRA | GFULL), EACM_STRICT, NULL));
      }else if((val = n_header_get_field(linebuf, "sender", NULL)) != NULL){
         if(hef & n_HEADER_EXTRACT_FULL){
            ++seenfields;
            hq->h_sender = checkaddrs(n_extract_single(val,
                  GEXTRA | GFULL | GFULLEXTRA), EACM_STRICT, NULL);
         } else
            goto jebadhead;
      }else if((val = n_header_get_field(linebuf, "subject", NULL)) != NULL){
         ++seenfields;
         for (cp = val; su_cs_is_blank(*cp); ++cp)
            ;
         hq->h_subject = (hq->h_subject != NULL)
               ? save2str(hq->h_subject, cp) : savestr(cp);
      }
      /* The remaining are mostly hacked in and thus TODO -- at least in
       * TODO respect to their content checking */
      else if((val = n_header_get_field(linebuf, "message-id", NULL)) != NULL){
         if(hef & n_HEADER_EXTRACT__MODE_MASK){
            np = checkaddrs(lextract(val, GREF | GNOT_A_LIST),
                  /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
                  NULL);
            if (np == NULL || np->n_flink != NULL)
               goto jebadhead;
            ++seenfields;
            hq->h_message_id = np;
         }else
            goto jebadhead;
      }else if((val = n_header_get_field(linebuf, "in-reply-to", NULL)
            ) != NULL){
         if(hef & n_HEADER_EXTRACT__MODE_MASK){
            np = checkaddrs(lextract(val, GREF),
                  /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
                  NULL);
            ++seenfields;
            hq->h_in_reply_to = np;
         }else
            goto jebadhead;
      }else if((val = n_header_get_field(linebuf, "references", NULL)
            ) != NULL){
         if(hef & n_HEADER_EXTRACT__MODE_MASK){
            ++seenfields;
            /* TODO Limit number of references TODO better on parser side */
            hq->h_ref = cat(hq->h_ref, checkaddrs(extract(val, GREF),
                  /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG | EACM_NONAME,
                  NULL));
         }else
            goto jebadhead;
      }
      /* and that is very hairy */
      else if((val = n_header_get_field(linebuf, "mail-followup-to", NULL)
               ) != NULL){
         if(hef & n_HEADER_EXTRACT__MODE_MASK){
            ++seenfields;
            hq->h_mft = cat(hq->h_mft, checkaddrs(lextract(val,
                  GEXTRA | GFULL),
                  /*EACM_STRICT | TODO '/' valid! | EACM_NOLOG | */EACM_NONAME,
                  checkaddr_err_or_null));
         }else
            goto jebadhead;
      }
      /* A free-form header; a_gethfield() did some verification already.. */
      else{
         struct n_header_field *hfp;
         u32 nl, bl;
         char const *nstart;

         for(nstart = cp = linebuf;; ++cp)
            if(!fieldnamechar(*cp))
               break;
         nl = (u32)P2UZ(cp - nstart);

         while(su_cs_is_blank(*cp))
            ++cp;
         if(*cp++ != ':'){
jebadhead:
            n_err(_("Ignoring header field: %s\n"), linebuf);
            continue;
         }
         while(su_cs_is_blank(*cp))
            ++cp;
         bl = (u32)su_cs_len(cp) +1;

         ++seenfields;
         *hftail =
         hfp = n_autorec_alloc(VSTRUCT_SIZEOF(struct n_header_field,
               hf_dat) + nl +1 + bl);
            hftail = &hfp->hf_next;
         hfp->hf_next = NULL;
         hfp->hf_nl = nl;
         hfp->hf_bl = bl - 1;
         su_mem_copy(hfp->hf_dat, nstart, nl);
            hfp->hf_dat[nl++] = '\0';
            su_mem_copy(hfp->hf_dat + nl, cp, bl);
      }
   }

   /* In case the blank line after the header has been edited out.  Otherwise,
    * fetch the header separator */
   if (linebuf != NULL) {
      if (linebuf[0] != '\0') {
         for (cp = linebuf; *(++cp) != '\0';)
            ;
         fseek(fp, (long)-P2UZ(1 + cp - linebuf), SEEK_CUR);
      } else {
         if ((c = getc(fp)) != '\n' && c != EOF)
            ungetc(c, fp);
      }
   }

   if (seenfields > 0 &&
         (checkaddr_err_or_null == NULL || *checkaddr_err_or_null == 0)) {
      hp->h_to = hq->h_to;
      hp->h_cc = hq->h_cc;
      hp->h_bcc = hq->h_bcc;
      hp->h_from = hq->h_from;
      hp->h_reply_to = hq->h_reply_to;
      hp->h_sender = hq->h_sender;
      if(hq->h_subject != NULL ||
            (hef & n_HEADER_EXTRACT__MODE_MASK) != n_HEADER_EXTRACT_FULL)
         hp->h_subject = hq->h_subject;
      hp->h_user_headers = hq->h_user_headers;

      if(hef & n_HEADER_EXTRACT__MODE_MASK){
         hp->h_fcc = hq->h_fcc;
         if(hef & n_HEADER_EXTRACT_FULL)
            hp->h_ref = hq->h_ref;
         hp->h_message_id = hq->h_message_id;
         hp->h_in_reply_to = hq->h_in_reply_to;
         /* TODO For now the user cannot force "throwing away of M-F-T:" by
          * TODO simply deleting the header; it is possible to adjust the
          * TODO content, but is still mangled further on: very bad */
         if(hq->h_mft != NULL)
            hp->h_mft = hq->h_mft;

         /* And perform additional validity checks so that we don't bail later
          * on TODO this is good and the place where this should occur,
          * TODO unfortunately a lot of other places do again and blabla */
         if(hp->h_from == NULL)
            hp->h_from = n_poption_arg_r;
         else if((hef & n_HEADER_EXTRACT_FULL) &&
               hp->h_from->n_flink != NULL && hp->h_sender == NULL)
            hp->h_sender = n_extract_single(ok_vlook(sender),
                  GEXTRA | GFULL | GFULLEXTRA);
      }
   } else
      n_err(_("Restoring deleted header lines\n"));

jleave:
   mx_fs_linepool_release(linebuf, linesize);
   NYD_OU;
}

FL char *
hfield_mult(char const *field, struct message *mp, int mult)
{
   FILE *ibuf;
   struct str hfs;
   long lc;
   uz linesize;
   char *linebuf, *colon;
   char const *hfield;
   NYD_IN;

   mx_fs_linepool_aquire(&linebuf, &linesize);

   /* There are (spam) messages which have header bytes which are many KB when
    * joined, so resize a single heap storage until we are done if we shall
    * collect a field that may have multiple bodies; only otherwise use the
    * string dope directly */
   su_mem_set(&hfs, 0, sizeof hfs);

   if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
      goto jleave;
   if ((lc = mp->m_lines - 1) < 0)
      goto jleave;

   if ((mp->m_flag & MNOFROM) == 0 &&
         readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
      goto jleave;
   while (lc > 0) {
      if ((lc = a_gethfield(n_HEADER_EXTRACT_NONE, ibuf, &linebuf, &linesize,
            lc, &colon)) < 0)
         break;
      if ((hfield = n_header_get_field(linebuf, field, NULL)) != NULL &&
            *hfield != '\0') {
         if (mult)
            n_str_add_buf(&hfs, hfield, su_cs_len(hfield));
         else {
            hfs.s = savestr(hfield);
            break;
         }
      }
   }

jleave:
   mx_fs_linepool_release(linebuf, linesize);
   if (mult && hfs.s != NULL) {
      colon = savestrbuf(hfs.s, hfs.l);
      n_free(hfs.s);
      hfs.s = colon;
   }
   NYD_OU;
   return hfs.s;
}

FL char const *
n_header_get_field(char const *linebuf, char const *field,
   struct str *suffix_or_nil)
{
   char const *rv = NULL;
   NYD2_IN;

   if(suffix_or_nil != su_NIL)
      suffix_or_nil->s = su_NIL;

   while (su_cs_to_lower(*linebuf) == su_cs_to_lower(*field)) {
      ++linebuf;
      ++field;
   }

   if(*field != '\0')
      goto jleave;

   if(suffix_or_nil != su_NIL && *linebuf == '?'){
      char c;
      uz i;

      for(i = 0; (c = *linebuf) != '\0'; ++linebuf, ++i)
         if(su_cs_is_blank(c) || c == ':')
            break;
      if(i > 0){
         suffix_or_nil->l = --i;
         suffix_or_nil->s = UNCONST(char*,&linebuf[-i]);
      }
   }

   while(su_cs_is_blank(*linebuf))
      ++linebuf;
   if(*linebuf++ != ':')
      goto jleave;
   while(su_cs_is_blank(*linebuf)) /* TODO header parser.. strip trailing WS */
      ++linebuf;

   rv = linebuf;
jleave:
   NYD2_OU;
   return rv;
}

FL char const *
skip_comment(char const *cp)
{
   uz nesting;
   NYD_IN;

   for (nesting = 1; nesting > 0 && *cp; ++cp) {
      switch (*cp) {
      case '\\':
         if (cp[1])
            ++cp;
         break;
      case '(':
         ++nesting;
         break;
      case ')':
         --nesting;
         break;
      }
   }
   NYD_OU;
   return cp;
}

FL char const *
routeaddr(char const *name)
{
   char const *np, *rp = NULL;
   NYD_IN;

   for (np = name; *np; np++) {
      switch (*np) {
      case '(':
         np = skip_comment(np + 1) - 1;
         break;
      case '"':
         while (*np) {
            if (*++np == '"')
               break;
            if (*np == '\\' && np[1])
               np++;
         }
         break;
      case '<':
         rp = np;
         break;
      case '>':
         goto jleave;
      }
   }
   rp = NULL;
jleave:
   NYD_OU;
   return rp;
}

FL enum expand_addr_flags
expandaddr_to_eaf(void){ /* TODO should happen at var assignment time */
   struct eafdesc{
      char eafd_name[15];
      boole eafd_is_target;
      u32 eafd_andoff;
      u32 eafd_or;
   } const eafa[] = {
      {"restrict", FAL0, EAF_TARGET_MASK, EAF_RESTRICT | EAF_RESTRICT_TARGETS},
      {"fail", FAL0, EAF_NONE, EAF_FAIL},
      {"failinvaddr\0", FAL0, EAF_NONE, EAF_FAILINVADDR | EAF_ADDR},
      {"domaincheck\0", FAL0, EAF_NONE, EAF_DOMAINCHECK | EAF_ADDR},
      {"namehostex\0", FAL0, EAF_NONE, EAF_NAMEHOSTEX},
      {"shquote", FAL0, EAF_NONE, EAF_SHEXP_PARSE},
      {"all", TRU1, EAF_NONE, EAF_TARGET_MASK},
         {"fcc", TRU1, EAF_NONE, EAF_FCC}, /* Fcc: only */
         {"file", TRU1, EAF_NONE, EAF_FILE | EAF_FCC}, /* Fcc: + other addr */
         {"pipe", TRU1, EAF_NONE, EAF_PIPE}, /* TODO No Pcc: yet! */
         {"name", TRU1, EAF_NONE, EAF_NAME},
         {"addr", TRU1, EAF_NONE, EAF_ADDR}
   }, *eafp;

   char *buf;
   enum expand_addr_flags rv;
   char const *cp;
   NYD2_IN;

   if((cp = ok_vlook(expandaddr)) == NULL)
      rv = EAF_RESTRICT_TARGETS;
   else if(*cp == '\0')
      rv = EAF_TARGET_MASK;
   else{
      rv = EAF_TARGET_MASK;

      for(buf = savestr(cp); (cp = su_cs_sep_c(&buf, ',', TRU1)) != NULL;){
         boole minus;

         if((minus = (*cp == '-')) || (*cp == '+' ? (minus = TRUM1) : FAL0))
            ++cp;

         for(eafp = eafa;; ++eafp) {
            if(eafp == &eafa[NELEM(eafa)]){
               if(n_poption & n_PO_D_V)
                  n_err(_("Unknown *expandaddr* value: %s\n"), cp);
               break;
            }else if(!su_cs_cmp_case(cp, eafp->eafd_name)){
               if(minus){
                  if(eafp->eafd_is_target){
                     if(minus != TRU1)
                        goto jandor;
                     else
                        rv &= ~eafp->eafd_or;
                  }else if(n_poption & n_PO_D_V)
                     n_err(_("- or + prefix invalid for *expandaddr* value: "
                        "%s\n"), --cp);
               }else{
jandor:
                  rv &= ~eafp->eafd_andoff;
                  rv |= eafp->eafd_or;
               }
               break;
            }else if(!su_cs_cmp_case(cp, "noalias")){ /* TODO v15 OBSOLETE */
               n_OBSOLETE(_("*expandaddr*: noalias is henceforth -name"));
               rv &= ~EAF_NAME;
               break;
            }
         }
      }

      if((rv & EAF_RESTRICT) && ((n_psonce & n_PSO_INTERACTIVE) ||
            (n_poption & n_PO_TILDE_FLAG)))
         rv |= EAF_TARGET_MASK;
      else if(n_poption & n_PO_D_V){
         if(!(rv & EAF_TARGET_MASK))
            n_err(_("*expandaddr* does not allow any addressees\n"));
         else if((rv & EAF_FAIL) && (rv & EAF_TARGET_MASK) == EAF_TARGET_MASK)
            n_err(_("*expandaddr* with fail, but no restrictions to apply\n"));
      }
   }
   NYD2_OU;
   return rv;
}

FL s8
is_addr_invalid(struct mx_name *np, enum expand_addr_check_mode eacm){
   /* TODO This is called much too often!  Message->DOMTree->modify->..
    * TODO I.e., [verify once before compose-mode], verify once after
    * TODO compose-mode, store result in object */
   char cbuf[sizeof "'\\U12340'"];
   char const *cs;
   int f;
   s8 rv;
   enum expand_addr_flags eaf;
   NYD_IN;

   eaf = expandaddr_to_eaf();
   f = np->n_flags;

   if((rv = ((f & mx_NAME_ADDRSPEC_INVALID) != 0))){
      if(eaf & EAF_FAILINVADDR)
         rv = -rv;

      if(!(eacm & EACM_NOLOG) && !(f & mx_NAME_ADDRSPEC_ERR_EMPTY)){
         u32 c;
         boole ok8bit;
         char const *fmt;

         fmt = "'\\x%02X'";
         ok8bit = TRU1;

         if(f & mx_NAME_ADDRSPEC_ERR_IDNA) {
            cs = _("Invalid domain name: %s, character %s\n");
            fmt = "'\\U%04X'";
            ok8bit = FAL0;
         }else if(f & mx_NAME_ADDRSPEC_ERR_ATSEQ)
            cs = _("%s contains invalid %s sequence\n");
         else if(f & mx_NAME_ADDRSPEC_ERR_NAME)
            cs = _("%s is an invalid alias name\n");
         else
            cs = _("%s contains invalid byte %s\n");

         c = mx_name_flags_get_err_wc(f);
         snprintf(cbuf, sizeof cbuf,
            (ok8bit && c >= 040 && c <= 0177 ? "'%c'" : fmt), c);
         goto jprint;
      }
      goto jleave;
   }

   /* *expandaddr* stuff */
   if(!(rv = ((eacm & EACM_MODE_MASK) != EACM_NONE)))
      goto jleave;

   /* This header does not allow such targets at all (XXX >RFC 5322 parser) */
   if((eacm & EACM_STRICT) && (f & mx_NAME_ADDRSPEC_ISFILEORPIPE)){
      if(eaf & EAF_FAIL)
         rv = -rv;
      cs = _("%s%s: file or pipe addressees not allowed here\n");
      goto j0print;
   }

   eaf |= (eacm & EAF_TARGET_MASK);
   if(eacm & EACM_NONAME)
      eaf &= ~EAF_NAME;
   if(eaf & EAF_FAIL)
      rv = -rv;

   switch(f & mx_NAME_ADDRSPEC_ISMASK){
   case mx_NAME_ADDRSPEC_ISFILE:
      if((eaf & EAF_FILE) || ((eaf & EAF_FCC) && (np->n_type & GBCC_IS_FCC)))
         goto jgood;
      cs = _("%s%s: *expandaddr* does not allow file target\n");
      break;
   case mx_NAME_ADDRSPEC_ISPIPE:
      if(eaf & EAF_PIPE)
         goto jgood;
      cs = _("%s%s: *expandaddr* does not allow command pipe target\n");
      break;
   case mx_NAME_ADDRSPEC_ISNAME:
      if((eaf & EAF_NAMEHOSTEX) &&
            (!su_cs_cmp(np->n_name, ok_vlook(LOGNAME)) ||
               getpwnam(np->n_name) != NIL)){
         np->n_flags ^= mx_NAME_ADDRSPEC_ISADDR | mx_NAME_ADDRSPEC_ISNAME;
         np->n_name = np->n_fullname = savecatsep(np->n_name, '@',
               n_nodename(TRU1));
         goto jisaddr;
      }

      if(eaf & EAF_NAME)
         goto jgood;
      if(!(eaf & EAF_FAIL) && (eacm & EACM_NONAME_OR_FAIL)){
         rv = -rv;
         cs = _("%s%s: user name (MTA alias) targets are not allowed\n");
      }else
         cs = _("%s%s: *expandaddr* does not allow user name target\n");
      break;
   default:
   case mx_NAME_ADDRSPEC_ISADDR:
jisaddr:
      if(!(eaf & EAF_ADDR)){
         cs = _("%s%s: *expandaddr* does not allow mail address target\n");
         break;
      }
      if(!(eacm & EACM_DOMAINCHECK) || !(eaf & EAF_DOMAINCHECK))
         goto jgood;
      else{
         char const *doms;

         ASSERT(np->n_flags & mx_NAME_SKINNED);
         /* XXX We had this info before, and threw it away.. */
         doms = su_cs_rfind_c(np->n_name, '@');
         ASSERT(doms != NULL);
         ++doms;

         if(!su_cs_cmp_case("localhost", doms))
            goto jgood;
         if(!su_cs_cmp_case(n_nodename(TRU1), doms))
            goto jgood;

         if((cs = ok_vlook(expandaddr_domaincheck)) != NULL){
            char *cpb, *cp;

            cpb = savestr(cs);
            while((cp = su_cs_sep_c(&cpb, ',', TRU1)) != NULL)
               if(!su_cs_cmp_case(cp, doms))
                  goto jgood;
         }
      }
      cs = _("%s%s: *expandaddr*: not \"domaincheck\" whitelisted\n");
      break;
   }

j0print:
   cbuf[0] = '\0';
   if(!(eacm & EACM_NOLOG))
jprint:
      n_err(cs, n_shexp_quote_cp(np->n_name, TRU1), cbuf);
   goto jleave;
jgood:
   rv = FAL0;
jleave:
   NYD_OU;
   return rv;
}

FL char *
skin(char const *name)
{
   struct n_addrguts ag;
   char *rv;
   NYD_IN;

   if(name != NULL){
      /*name =*/ n_addrspec_with_guts(&ag, name, GSKIN);
      rv = ag.ag_skinned;
      if(!(ag.ag_n_flags & mx_NAME_NAME_SALLOC))
         rv = savestrbuf(rv, ag.ag_slen);
   }else
      rv = NULL;
   NYD_OU;
   return rv;
}

/* TODO addrspec_with_guts: RFC 5322
 * TODO addrspec_with_guts: trim whitespace ETC. ETC. ETC. BAD BAD BAD!!! */
FL char const *
n_addrspec_with_guts(struct n_addrguts *agp, char const *name, u32 gfield){
   char const *cp;
   char *cp2, *bufend, *nbuf, c;
   enum{
      a_NONE,
      a_DOSKIN = 1u<<0,
      a_NOLIST = 1u<<1,
      a_QUENCO = 1u<<2,

      a_GOTLT = 1u<<3,
      a_GOTADDR = 1u<<4,
      a_GOTSPACE = 1u<<5,
      a_LASTSP = 1u<<6,
      a_IDX0 = 1u<<7
   } flags;
   NYD_IN;

jredo_uri:
   su_mem_set(agp, 0, sizeof *agp);

   if((agp->ag_input = name) == NULL || (agp->ag_ilen = su_cs_len(name)) == 0){
      agp->ag_skinned = n_UNCONST(n_empty); /* ok: mx_NAME_SALLOC is not set */
      agp->ag_slen = 0;
      agp->ag_n_flags = mx_name_flags_set_err(agp->ag_n_flags,
            mx_NAME_ADDRSPEC_ERR_EMPTY, '\0');
      goto jleave;
   }

   flags = a_NONE;
   if(gfield & (GFULL | GSKIN | GREF))
      flags |= a_DOSKIN;
   if(gfield & GNOT_A_LIST)
      flags |= a_NOLIST;
   if(gfield & GQUOTE_ENCLOSED_OK){
      gfield ^= GQUOTE_ENCLOSED_OK;
      flags |= a_QUENCO;
   }

   if(!(flags & a_DOSKIN)){
      su_ASSERT(!(gfield & GMAILTO_URI));
      /*agp->ag_iaddr_start = 0;*/
      agp->ag_iaddr_aend = agp->ag_ilen;
      agp->ag_skinned = n_UNCONST(name); /* (mx_NAME_SALLOC not set) */
      agp->ag_slen = agp->ag_ilen;
      agp->ag_n_flags = mx_NAME_SKINNED;
      goto jcheck;
   }

   if(gfield & GMAILTO_URI){
      su_ASSERT(gfield & GNOT_A_LIST);
      if(*(cp = name) == '<' && cp[agp->ag_ilen - 1] == '>'){
         name = mx_url_mailto_to_address(savestrbuf(++cp, agp->ag_ilen - 2));
         gfield &= ~GMAILTO_URI;
         goto jredo_uri;
      }
   }

   /* We will skin that thing */
   nbuf = n_lofi_alloc(agp->ag_ilen +1);
   /*agp->ag_iaddr_start = 0;*/
   cp2 = bufend = nbuf;

   /* TODO This is complete crap and should use a token parser.
    * TODO It can be fooled and is too stupid to find an email address in
    * TODO something valid unless it contains <>.  oh my */
   for(cp = name++; (c = *cp++) != '\0';){
      switch (c) {
      case '(':
         cp = skip_comment(cp);
         flags &= ~a_LASTSP;
         break;
      case '"':
         /* Start of a "quoted-string".  Copy it in its entirety */
         /* XXX RFC: quotes are "semantically invisible"
          * XXX But it was explicitly added (Changelog.Heirloom,
          * XXX [9.23] released 11/15/00, "Do not remove quotes
          * XXX when skinning names"?  No more info.. */
         *cp2++ = c;
         ASSERT(!(flags & a_IDX0));
         if((flags & a_QUENCO) && cp == name)
            flags |= a_IDX0;
         while ((c = *cp) != '\0') { /* TODO improve */
            ++cp;
            if (c == '"') {
               *cp2++ = c;
               /* Special case: if allowed so and anything is placed in quotes
                * then throw away the quotes and start all over again */
               if((flags & a_IDX0) && *cp == '\0'){
                  name = savestrbuf(name, P2UZ(--cp - name));
                  goto jredo_uri;
               }
               break;
            }
            if (c != '\\')
               *cp2++ = c;
            else if ((c = *cp) != '\0') {
               *cp2++ = c;
               ++cp;
            }
         }
         flags &= ~(a_LASTSP | a_IDX0);
         break;
      case ' ':
      case '\t':
         if((flags & (a_GOTADDR | a_GOTSPACE)) == a_GOTADDR){
            flags |= a_GOTSPACE;
            agp->ag_iaddr_aend = P2UZ(cp - name);
         }
         if (cp[0] == 'a' && cp[1] == 't' && su_cs_is_blank(cp[2]))
            cp += 3, *cp2++ = '@';
         else if (cp[0] == '@' && su_cs_is_blank(cp[1]))
            cp += 2, *cp2++ = '@';
         else
            flags |= a_LASTSP;
         break;
      case '<':
         agp->ag_iaddr_start = P2UZ(cp - (name - 1));
         cp2 = bufend;
         flags &= ~(a_GOTSPACE | a_LASTSP);
         flags |= a_GOTLT | a_GOTADDR;
         break;
      case '>':
         if(flags & a_GOTLT){
            /* (_addrspec_check() verifies these later!) */
            flags &= ~(a_GOTLT | a_LASTSP);
            agp->ag_iaddr_aend = P2UZ(cp - name);

            /* Skip over the entire remaining field */
            while((c = *cp) != '\0'){
               if(c == ',' && !(flags & a_NOLIST))
                  break;
               ++cp;
               if (c == '(')
                  cp = skip_comment(cp);
               else if (c == '"')
                  while ((c = *cp) != '\0') {
                     ++cp;
                     if (c == '"')
                        break;
                     if (c == '\\' && *cp != '\0')
                        ++cp;
                  }
            }
            break;
         }
         /* FALLTHRU */
      default:
         if(flags & a_LASTSP){
            flags &= ~a_LASTSP;
            if(flags & a_GOTADDR)
               *cp2++ = ' ';
         }
         *cp2++ = c;
         /* This character is forbidden here, but it may nonetheless be
          * present: ensure we turn this into something valid!  (E.g., if the
          * next character would be a "..) */
         if(c == '\\' && *cp != '\0')
            *cp2++ = *cp++;
         if(c == ',' && !(flags & a_NOLIST)){
            if(!(flags & a_GOTLT)){
               *cp2++ = ' ';
               for(; su_cs_is_blank(*cp); ++cp)
                  ;
               flags &= ~a_LASTSP;
               bufend = cp2;
            }
         }else if(!(flags & a_GOTADDR)){
            flags |= a_GOTADDR;
            agp->ag_iaddr_start = P2UZ(cp - name);
         }
      }
   }
   agp->ag_slen = P2UZ(cp2 - nbuf);

   if (agp->ag_iaddr_aend == 0)
      agp->ag_iaddr_aend = agp->ag_ilen;
   /* Misses > */
   else if (agp->ag_iaddr_aend < agp->ag_iaddr_start) {
      cp2 = n_autorec_alloc(agp->ag_ilen + 1 +1);
      su_mem_copy(cp2, agp->ag_input, agp->ag_ilen);
      agp->ag_iaddr_aend = agp->ag_ilen;
      cp2[agp->ag_ilen++] = '>';
      cp2[agp->ag_ilen] = '\0';
      agp->ag_input = cp2;
   }

   agp->ag_skinned = savestrbuf(nbuf, agp->ag_slen);
   n_lofi_free(nbuf);
   agp->ag_n_flags = mx_NAME_NAME_SALLOC | mx_NAME_SKINNED;
jcheck:
   if(a_header_addrspec_check(agp, ((flags & a_DOSKIN) != 0),
         ((flags & GNOT_A_LIST) != 0)) <= FAL0)
      name = NULL;
   else
      name = agp->ag_input;
jleave:
   NYD_OU;
   return name;
}

FL int
c_addrcodec(void *vp){
   struct str trims;
   struct n_string s_b, *s;
   uz alen;
   int mode;
   char const **argv, *varname, *act, *cp;
   NYD_IN;

   s = n_string_creat_auto(&s_b);
   argv = vp;
   varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;

   act = *argv;
   for(cp = act; *cp != '\0' && !su_cs_is_space(*cp); ++cp)
      ;
   mode = 0;
   if(*act == '+')
      mode = 1, ++act;
   if(*act == '+')
      mode = 2, ++act;
   if(*act == '+')
      mode = 3, ++act;
   if(act >= cp)
      goto jesynopsis;
   alen = P2UZ(cp - act);
   if(*cp != '\0')
      ++cp;

   trims.l = su_cs_len(trims.s = n_UNCONST(cp));
   cp = savestrbuf(n_str_trim(&trims, n_STR_TRIM_BOTH)->s, trims.l);
   if(trims.l <= UZ_MAX / 4)
         trims.l <<= 1;
   s = n_string_reserve(s, trims.l);

   n_pstate_err_no = su_ERR_NONE;

   if(su_cs_starts_with_case_n("encode", act, alen)){
      /* This function cannot be a simple nalloc() wrapper even later on, since
       * we may need to turn any ", () or \ into quoted-pairs */
      struct mx_name *np;
      char c;

      while((c = *cp++) != '\0'){
         if(((c == '(' || c == ')') && mode < 1) || (c == '"' && mode < 2) ||
               (c == '\\' && mode < 3))
            s = n_string_push_c(s, '\\');
         s = n_string_push_c(s, c);
      }

      if((np = n_extract_single(cp = n_string_cp(s), GTO | GFULL)) != NULL)
         cp = np->n_fullname;
      else{
         n_pstate_err_no = su_ERR_INVAL;
         vp = NULL;
      }
   }else if(mode == 0){
      if(su_cs_starts_with_case_n("decode", act, alen)){
         char c;

         while((c = *cp++) != '\0'){
            switch(c){
            case '(':
               s = n_string_push_c(s, '(');
               act = skip_comment(cp);
               if(--act > cp)
                  s = n_string_push_buf(s, cp, P2UZ(act - cp));
               s = n_string_push_c(s, ')');
               cp = ++act;
               break;
            case '"':
               while(*cp != '\0'){
                  if((c = *cp++) == '"')
                     break;
                  if(c == '\\' && (c = *cp) != '\0')
                     ++cp;
                  s = n_string_push_c(s, c);
               }
               break;
            default:
               if(c == '\\' && (c = *cp++) == '\0')
                  break;
               s = n_string_push_c(s, c);
               break;
            }
         }
         cp = n_string_cp(s);
      }else if(su_cs_starts_with_case_n("skin", act, alen) ||
            (mode = 1, su_cs_starts_with_case_n("skinlist", act, alen))){
         struct mx_name *np;

         if((np = n_extract_single(cp, GTO | GFULL)) != NULL){
            cp = np->n_name;

            if(mode == 1 && mx_mlist_query(cp, FAL0) != mx_MLIST_OTHER)
               n_pstate_err_no = su_ERR_EXIST;
         }else{
            n_pstate_err_no = su_ERR_INVAL;
            vp = NULL;
         }
      }else
         goto jesynopsis;
   }else
      goto jesynopsis;

   if(varname == NULL){
      if(fprintf(n_stdout, "%s\n", cp) < 0){
         n_pstate_err_no = su_err_no();
         vp = NULL;
      }
   }else if(!n_var_vset(varname, (up)cp)){
      n_pstate_err_no = su_ERR_NOTSUP;
      vp = NULL;
   }

jleave:
   NYD_OU;
   return (vp != NULL ? 0 : 1);
jesynopsis:
   n_err(_("Synopsis: addrcodec: <[+[+[+]]]e[ncode]|d[ecode]|s[kin]> "
      "\n"));
   n_pstate_err_no = su_ERR_INVAL;
   vp = NULL;
   goto jleave;
}

FL char *
realname(char const *name)
{
   char const *cp, *cq, *cstart = NULL, *cend = NULL;
   char *rname, *rp;
   struct str in, out;
   int quoted, good, nogood;
   NYD_IN;

   if ((cp = n_UNCONST(name)) == NULL)
      goto jleave;
   for (; *cp != '\0'; ++cp) {
      switch (*cp) {
      case '(':
         if (cstart != NULL) {
            /* More than one comment in address, doesn't make sense to display
             * it without context.  Return the entire field */
            cp = mime_fromaddr(name);
            goto jleave;
         }
         cstart = cp++;
         cp = skip_comment(cp);
         cend = cp--;
         if (cend <= cstart)
            cend = cstart = NULL;
         break;
      case '"':
         while (*cp) {
            if (*++cp == '"')
               break;
            if (*cp == '\\' && cp[1])
               ++cp;
         }
         break;
      case '<':
         if (cp > name) {
            cstart = name;
            cend = cp;
         }
         break;
      case ',':
         /* More than one address. Just use the first one */
         goto jbrk;
      }
   }

jbrk:
   if (cstart == NULL) {
      if (*name == '<') {
         /* If name contains only a route-addr, the surrounding angle brackets
          * don't serve any useful purpose when displaying, so remove */
         cp = prstr(skin(name));
      } else
         cp = mime_fromaddr(name);
      goto jleave;
   }

   /* Strip quotes. Note that quotes that appear within a MIME encoded word are
    * not stripped. The idea is to strip only syntactical relevant things (but
    * this is not necessarily the most sensible way in practice) */
   rp = rname = n_lofi_alloc(P2UZ(cend - cstart +1));
   quoted = 0;
   for (cp = cstart; cp < cend; ++cp) {
      if (*cp == '(' && !quoted) {
         cq = skip_comment(++cp);
         if (PCMP(--cq, >, cend))
            cq = cend;
         while (cp < cq) {
            if (*cp == '\\' && PCMP(cp + 1, <, cq))
               ++cp;
            *rp++ = *cp++;
         }
      } else if (*cp == '\\' && PCMP(cp + 1, <, cend))
         *rp++ = *++cp;
      else if (*cp == '"') {
         quoted = !quoted;
         continue;
      } else
         *rp++ = *cp;
   }
   *rp = '\0';
   in.s = rname;
   in.l = rp - rname;
   mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
   n_lofi_free(rname);
   rname = savestr(out.s);
   n_free(out.s);

   while (su_cs_is_blank(*rname))
      ++rname;
   for (rp = rname; *rp != '\0'; ++rp)
      ;
   while (PCMP(--rp, >=, rname) && su_cs_is_blank(*rp))
      *rp = '\0';
   if (rp == rname) {
      cp = mime_fromaddr(name);
      goto jleave;
   }

   /* mime_fromhdr() has converted all nonprintable characters to question
    * marks now. These and blanks are considered uninteresting; if the
    * displayed part of the real name contains more than 25% of them, it is
    * probably better to display the plain email address instead */
   good = 0;
   nogood = 0;
   for (rp = rname; *rp != '\0' && PCMP(rp, <, rname + 20); ++rp)
      if (*rp == '?' || su_cs_is_blank(*rp))
         ++nogood;
      else
         ++good;
   cp = (good * 3 < nogood) ? prstr(skin(name)) : rname;
jleave:
   NYD_OU;
   return n_UNCONST(cp);
}

FL char *
n_header_senderfield_of(struct message *mp){
   char *cp;
   NYD_IN;

   if((cp = hfield1("from", mp)) != NULL && *cp != '\0')
      ;
   else if((cp = hfield1("sender", mp)) != NULL && *cp != '\0')
      ;
   else{
      char *namebuf, *cp2, *linebuf;
      uz namesize, linesize;
      FILE *ibuf;
      int f1st = 1;

      mx_fs_linepool_aquire(&linebuf, &linesize);

      /* And fallback only works for MBOX */
      namebuf = n_alloc(namesize = 1);
      namebuf[0] = 0;
      if (mp->m_flag & MNOFROM)
         goto jout;
      if ((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
         goto jout;
      if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
         goto jout;

jnewname:
      if (namesize <= linesize)
         namebuf = n_realloc(namebuf, namesize = linesize +1);
      for (cp = linebuf; *cp != '\0' && *cp != ' '; ++cp)
         ;
      for (; su_cs_is_blank(*cp); ++cp)
         ;
      for (cp2 = namebuf + su_cs_len(namebuf);
            *cp && !su_cs_is_blank(*cp) &&
            PCMP(cp2, <, namebuf + namesize -1);)
         *cp2++ = *cp++;
      *cp2 = '\0';

      if (readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
         goto jout;
      if ((cp = su_cs_find_c(linebuf, 'F')) == NULL)
         goto jout;
      if (strncmp(cp, "From", 4))
         goto jout;
      if (namesize <= linesize)
         namebuf = n_realloc(namebuf, namesize = linesize + 1);

      /* UUCP from 976 (we do not support anyway!) */
      while ((cp = su_cs_find_c(cp, 'r')) != NULL) {
         if (!strncmp(cp, "remote", 6)) {
            if ((cp = su_cs_find_c(cp, 'f')) == NULL)
               break;
            if (strncmp(cp, "from", 4) != 0)
               break;
            if ((cp = su_cs_find_c(cp, ' ')) == NULL)
               break;
            cp++;
            if (f1st) {
               strncpy(namebuf, cp, namesize);
               f1st = 0;
            } else {
               cp2 = su_cs_rfind_c(namebuf, '!') + 1;
               strncpy(cp2, cp, P2UZ(namebuf + namesize - cp2));
            }
            namebuf[namesize - 2] = '!';
            namebuf[namesize - 1] = '\0';
            goto jnewname;
         }
         cp++;
      }
jout:
      if (*namebuf != '\0' || ((cp = hfield1("return-path", mp))) == NULL ||
            *cp == '\0')
         cp = savestr(namebuf);

      mx_fs_linepool_release(linebuf, linesize);
      n_free(namebuf);
   }

   NYD_OU;
   return cp;
}

FL char const *
subject_re_trim(char const *s){
   struct{
      u8 len;
      char  dat[7];
   }const *pp, ignored[] = { /* Update *reply-strings* manual upon change! */
      {3, "re:"},
      {3, "aw:"}, {5, "antw:"}, /* de */
      {3, "wg:"}, /* Seen too often in the wild */
      {0, ""}
   };

   boole any;
   char *re_st, *re_st_x;
   char const *orig_s;
   uz re_l;
   NYD_IN;

   any = FAL0;
   orig_s = s;
   re_st = NULL;
   UNINIT(re_l, 0);

   if((re_st_x = ok_vlook(reply_strings)) != NULL &&
         (re_l = su_cs_len(re_st_x)) > 0){
      re_st = n_lofi_alloc(++re_l * 2);
      su_mem_copy(re_st, re_st_x, re_l);
   }

jouter:
   while(*s != '\0'){
      while(su_cs_is_space(*s))
         ++s;

      for(pp = ignored; pp->len > 0; ++pp)
         if(su_cs_starts_with_case(s, pp->dat)){
            s += pp->len;
            any = TRU1;
            goto jouter;
         }

      if(re_st != NULL){
         char *cp;

         su_mem_copy(re_st_x = &re_st[re_l], re_st, re_l);
         while((cp = su_cs_sep_c(&re_st_x, ',', TRU1)) != NULL)
            if(su_cs_starts_with_case(s, cp)){
               s += su_cs_len(cp);
               any = TRU1;
               goto jouter;
            }
      }
      break;
   }

   if(re_st != NULL)
      n_lofi_free(re_st);
   NYD_OU;
   return any ? s : orig_s;
}

FL int
msgidcmp(char const *s1, char const *s2)
{
   int q1 = 0, q2 = 0, c1, c2;
   NYD_IN;

   while(*s1 == '<')
      ++s1;
   while(*s2 == '<')
      ++s2;

   do {
      c1 = msgidnextc(&s1, &q1);
      c2 = msgidnextc(&s2, &q2);
      if (c1 != c2)
         break;
   } while (c1 && c2);
   NYD_OU;
   return c1 - c2;
}

FL char const *
fakefrom(struct message *mp)
{
   char const *name;
   NYD_IN;

   if (((name = skin(hfield1("return-path", mp))) == NULL || *name == '\0' ) &&
         ((name = skin(hfield1("from", mp))) == NULL || *name == '\0'))
      /* XXX MAILER-DAEMON is what an old MBOX manual page says.
       * RFC 4155 however requires a RFC 5322 (2822) conforming
       * "addr-spec", but we simply can't provide that */
      name = "MAILER-DAEMON";
   NYD_OU;
   return name;
}

#if defined mx_HAVE_IMAP_SEARCH || defined mx_HAVE_IMAP
FL time_t
unixtime(char const *fromline)
{
   char const *fp, *xp;
   time_t t, t2;
   s32 i, year, month, day, hour, minute, second, tzdiff;
   struct tm *tmptr;
   NYD2_IN;

   for (fp = fromline; *fp != '\0' && *fp != '\n'; ++fp)
      ;
   fp -= 24;
   if (P2UZ(fp - fromline) < 7)
      goto jinvalid;
   if (fp[3] != ' ')
      goto jinvalid;
   for (i = 0;;) {
      if (!strncmp(fp + 4, n_month_names[i], 3))
         break;
      if (n_month_names[++i][0] == '\0')
         goto jinvalid;
   }
   month = i + 1;
   if (fp[7] != ' ')
      goto jinvalid;
   su_idec_s32_cp(&day, &fp[8], 10, &xp);
   if (*xp != ' ' || xp != fp + 10)
      goto jinvalid;
   su_idec_s32_cp(&hour, &fp[11], 10, &xp);
   if (*xp != ':' || xp != fp + 13)
      goto jinvalid;
   su_idec_s32_cp(&minute, &fp[14], 10, &xp);
   if (*xp != ':' || xp != fp + 16)
      goto jinvalid;
   su_idec_s32_cp(&second, &fp[17], 10, &xp);
   if (*xp != ' ' || xp != fp + 19)
      goto jinvalid;
   su_idec_s32_cp(&year, &fp[20], 10, &xp);
   if (xp != fp + 24)
      goto jinvalid;
   if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
      goto jinvalid;
   if((t2 = mktime(gmtime(&t))) == (time_t)-1)
      goto jinvalid;
   tzdiff = t - t2;
   if((tmptr = localtime(&t)) == NULL)
      goto jinvalid;
   if (tmptr->tm_isdst > 0)
      tzdiff += 3600; /* TODO simply adding an hour for ISDST is .. buuh */
   t -= tzdiff;
jleave:
   NYD2_OU;
   return t;
jinvalid:
   t = n_time_epoch();
   goto jleave;
}
#endif /* mx_HAVE_IMAP_SEARCH || mx_HAVE_IMAP */

FL time_t
rfctime(char const *date) /* TODO su_idec_ return tests */
{
   char const *cp, *x;
   time_t t;
   s32 i, year, month, day, hour, minute, second;
   NYD2_IN;

   cp = date;

   if ((cp = nexttoken(cp)) == NULL)
      goto jinvalid;
   if (su_cs_is_alpha(cp[0]) && su_cs_is_alpha(cp[1]) &&
         su_cs_is_alpha(cp[2]) && cp[3] == ',') {
      if ((cp = nexttoken(&cp[4])) == NULL)
         goto jinvalid;
   }
   su_idec_s32_cp(&day, cp, 10, &x);
   if ((cp = nexttoken(x)) == NULL)
      goto jinvalid;
   for (i = 0;;) {
      if (!strncmp(cp, n_month_names[i], 3))
         break;
      if (n_month_names[++i][0] == '\0')
         goto jinvalid;
   }
   month = i + 1;
   if ((cp = nexttoken(&cp[3])) == NULL)
      goto jinvalid;
   /* RFC 5322, 4.3:
    *  Where a two or three digit year occurs in a date, the year is to be
    *  interpreted as follows: If a two digit year is encountered whose
    *  value is between 00 and 49, the year is interpreted by adding 2000,
    *  ending up with a value between 2000 and 2049.  If a two digit year
    *  is encountered with a value between 50 and 99, or any three digit
    *  year is encountered, the year is interpreted by adding 1900 */
   su_idec_s32_cp(&year, cp, 10, &x);
   i = (int)P2UZ(x - cp);
   if (i == 2 && year >= 0 && year <= 49)
      year += 2000;
   else if (i == 3 || (i == 2 && year >= 50 && year <= 99))
      year += 1900;
   if ((cp = nexttoken(x)) == NULL)
      goto jinvalid;
   su_idec_s32_cp(&hour, cp, 10, &x);
   if (*x != ':')
      goto jinvalid;
   cp = &x[1];
   su_idec_s32_cp(&minute, cp, 10, &x);
   if (*x == ':') {
      cp = &x[1];
      su_idec_s32_cp(&second, cp, 10, &x);
   } else
      second = 0;

   if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
      goto jinvalid;
   if ((cp = nexttoken(x)) != NULL) {
      char buf[3];
      int sign = 1;

      switch (*cp) {
      case '+':
         sign = -1;
         /* FALLTHRU */
      case '-':
         ++cp;
         break;
      }
      if (su_cs_is_digit(cp[0]) && su_cs_is_digit(cp[1]) &&
            su_cs_is_digit(cp[2]) && su_cs_is_digit(cp[3])) {
         s64 tadj;

         buf[2] = '\0';
         buf[0] = cp[0];
         buf[1] = cp[1];
         su_idec_s32_cp(&i, buf, 10, NULL);
         tadj = (s64)i * 3600; /* XXX */
         buf[0] = cp[2];
         buf[1] = cp[3];
         su_idec_s32_cp(&i, buf, 10, NULL);
         tadj += (s64)i * 60; /* XXX */
         if (sign < 0)
            tadj = -tadj;
         t += (time_t)tadj;
      }
      /* TODO WE DO NOT YET PARSE (OBSOLETE) ZONE NAMES
       * TODO once again, Christos Zoulas and NetBSD Mail have done
       * TODO a really good job already, but using strptime(3), which
       * TODO is not portable.  Nonetheless, WE must improve, not
       * TODO at last because we simply ignore obsolete timezones!!
       * TODO See RFC 5322, 4.3! */
   }
jleave:
   NYD2_OU;
   return t;
jinvalid:
   t = 0;
   goto jleave;
}

FL time_t
combinetime(int year, int month, int day, int hour, int minute, int second){
   uz const jdn_epoch = 2440588;
   boole const y2038p = (sizeof(time_t) == 4);

   uz jdn;
   time_t t;
   NYD2_IN;

   if(UCMP(32, second, >/*XXX leap=*/, n_DATE_SECSMIN) ||
         UCMP(32, minute, >=, n_DATE_MINSHOUR) ||
         UCMP(32, hour, >=, n_DATE_HOURSDAY) ||
         day < 1 || day > 31 ||
         month < 1 || month > 12 ||
         year < 1970)
      goto jerr;

   if(year >= 1970 + ((y2038p ? S32_MAX : S64_MAX) /
         (n_DATE_SECSDAY * n_DATE_DAYSYEAR))){
      /* Be a coward regarding Y2038, many people (mostly myself, that is) do
       * test by stepping second-wise around the flip.  Don't care otherwise */
      if(!y2038p)
         goto jerr;
      if(year > 2038 || month > 1 || day > 19 ||
            hour > 3 || minute > 14 || second > 7)
         goto jerr;
   }

   t = second;
   t += minute * n_DATE_SECSMIN;
   t += hour * n_DATE_SECSHOUR;

   jdn = a_header_gregorian_to_jdn(year, month, day);
   jdn -= jdn_epoch;
   t += (time_t)jdn * n_DATE_SECSDAY;
jleave:
   NYD2_OU;
   return t;
jerr:
   t = (time_t)-1;
   goto jleave;
}

FL void
substdate(struct message *m)
{
   /* The Date: of faked From_ lines is traditionally the date the message was
    * written to the mail file. Try to determine this using RFC message header
    * fields, or fall back to current time */
   char const *cp;
   NYD_IN;

   m->m_time = 0;
   if ((cp = hfield1("received", m)) != NULL) {
      while ((cp = nexttoken(cp)) != NULL && *cp != ';') {
         do
            ++cp;
         while (su_cs_is_alnum(*cp));
      }
      if (cp && *++cp)
         m->m_time = rfctime(cp);
   }
   if (m->m_time == 0 || m->m_time > time_current.tc_time) {
      if ((cp = hfield1("date", m)) != NULL)
         m->m_time = rfctime(cp);
   }
   if (m->m_time == 0 || m->m_time > time_current.tc_time)
      m->m_time = time_current.tc_time;
   NYD_OU;
}

FL char *
n_header_textual_date_info(struct message *mp, char const **color_tag_or_null){
   struct tm tmlocal;
   char *rv;
   char const *fmt, *cp;
   time_t t;
   NYD_IN;
   UNUSED(color_tag_or_null);

   t = mp->m_time;
   fmt = ok_vlook(datefield);

jredo:
   if(fmt != NULL){
      u8 i;

      cp = hfield1("date", mp);/* TODO use m_date field! */
      if(cp == NULL){
         fmt = NULL;
         goto jredo;
      }

      t = rfctime(cp);
      rv = n_time_ctime(t, NULL);
      cp = ok_vlook(datefield_markout_older);
      i = (*fmt != '\0');
      if(cp != NULL)
         i |= (*cp != '\0') ? 2 | 4 : 2; /* XXX no magics */

      /* May we strftime(3)? */
      if(i & (1 | 4)){
         /* This localtime(3) should not fail since rfctime(3).. but .. */
         struct tm *tmp;
         time_t t2;

         /* TODO the datetime stuff is horror: mails should be parsed into
          * TODO an object tree, and date: etc. have a datetime object, which
          * TODO verifies upon parse time; then ALL occurrences of datetime are
          * TODO valid all through the program; and: to_wire, to_user! */
         t2 = t;
jredo_localtime:
         if((tmp = localtime(&t2)) == NULL){
            t2 = 0;
            goto jredo_localtime;
         }
         su_mem_copy(&tmlocal, tmp, sizeof *tmp);
      }

      if((i & 2) &&
            (UCMP(64, t, >, time_current.tc_time + n_DATE_SECSDAY) ||
#define _6M ((n_DATE_DAYSYEAR / 2) * n_DATE_SECSDAY)
            UCMP(64, t + _6M, <, time_current.tc_time))){
#undef _6M
         if((fmt = (i & 4) ? cp : NULL) == NULL){
            char *x;
            LCTA(n_FROM_DATEBUF >= 4 + 7 + 1 + 4, "buffer too small");

            x = n_autorec_alloc(n_FROM_DATEBUF);
            su_mem_set(x, ' ', 4 + 7 + 1 + 4);
            su_mem_copy(&x[4], &rv[4], 7);
            x[4 + 7] = ' ';
            su_mem_copy(&x[4 + 7 + 1], &rv[20], 4);
            x[4 + 7 + 1 + 4] = '\0';
            rv = x;
         }
         mx_COLOUR(
            if(color_tag_or_null != NULL)
               *color_tag_or_null = mx_COLOUR_TAG_SUM_OLDER;
         )
      }else if((i & 1) == 0)
         fmt = NULL;

      if(fmt != NULL){
         uz j;

         for(j = n_FROM_DATEBUF;; j <<= 1){
            i = strftime(rv = n_autorec_alloc(j), j, fmt, &tmlocal);
            if(i != 0)
               break;
            if(j > 128){
               n_err(_("Ignoring this date format: %s\n"),
                  n_shexp_quote_cp(fmt, FAL0));
               su_cs_pcopy_n(rv, n_time_ctime(t, NULL), j);
            }
         }
      }
   }else if(t == (time_t)0 && !(mp->m_flag & MNOFROM)){
      /* TODO eliminate this path, query the FROM_ date in setptr(),
       * TODO all other codepaths do so by themselves ALREADY ?????
       * TODO ASSERT(mp->m_time != 0);, then
       * TODO ALSO changes behaviour of datefield_markout_older */
      a_header_parse_from_(mp, rv = n_autorec_alloc(n_FROM_DATEBUF));
   }else
      rv = savestr(n_time_ctime(t, NULL));
   NYD_OU;
   return rv;
}

FL struct mx_name *
n_header_textual_sender_info(struct message *mp, char **cumulation_or_null,
      char **addr_or_null, char **name_real_or_null, char **name_full_or_null,
      boole *is_to_or_null){
   struct n_string s_b1, s_b2, *sp1, *sp2;
   struct mx_name *np, *np2;
   boole isto, b;
   char *cp;
   NYD_IN;

   cp = n_header_senderfield_of(mp);
   isto = FAL0;

   if((np = lextract(cp, GFULL | GSKIN)) != NULL){
      if(is_to_or_null != NULL && ok_blook(showto) &&
            np->n_flink == NULL && mx_name_is_mine(np->n_name)){
         if((cp = hfield1("to", mp)) != NULL &&
               (np2 = lextract(cp, GFULL | GSKIN)) != NULL){
            np = np2;
            isto = TRU1;
         }
      }

      if(((b = ok_blook(showname)) && cumulation_or_null != NULL) ||
            name_real_or_null != NULL || name_full_or_null != NULL){
         uz i;

         for(i = 0, np2 = np; np2 != NULL; np2 = np2->n_flink)
            i += su_cs_len(np2->n_fullname) +3;

         sp1 = n_string_book(n_string_creat_auto(&s_b1), i);
         sp2 = (name_full_or_null == NULL) ? NULL
               : n_string_book(n_string_creat_auto(&s_b2), i);

         for(np2 = np; np2 != NULL; np2 = np2->n_flink){
            if(sp1->s_len > 0){
               sp1 = n_string_push_c(sp1, ',');
               sp1 = n_string_push_c(sp1, ' ');
               if(sp2 != NULL){
                  sp2 = n_string_push_c(sp2, ',');
                  sp2 = n_string_push_c(sp2, ' ');
               }
            }

            if((cp = realname(np2->n_fullname)) == NULL)
               cp = np2->n_name;
            sp1 = n_string_push_cp(sp1, cp);
            if(sp2 != NULL)
               sp2 = n_string_push_cp(sp2, np2->n_fullname);
         }

         n_string_cp(sp1);
         if(b && cumulation_or_null != NULL)
            *cumulation_or_null = sp1->s_dat;
         if(name_real_or_null != NULL)
            *name_real_or_null = sp1->s_dat;
         if(name_full_or_null != NULL)
            *name_full_or_null = n_string_cp(sp2);

         /* n_string_gut(n_string_drop_ownership(sp2)); */
         /* n_string_gut(n_string_drop_ownership(sp1)); */
      }

      if((b = (!b && cumulation_or_null != NULL)) || addr_or_null != NULL){
         cp = detract(np, GCOMMA | GNAMEONLY);
         if(b)
            *cumulation_or_null = cp;
         if(addr_or_null != NULL)
            *addr_or_null = cp;
      }
   }else if(cumulation_or_null != NULL || addr_or_null != NULL ||
         name_real_or_null != NULL || name_full_or_null != NULL){
      cp = savestr(n_empty);

      if(cumulation_or_null != NULL)
         *cumulation_or_null = cp;
      if(addr_or_null != NULL)
         *addr_or_null = cp;
      if(name_real_or_null != NULL)
         *name_real_or_null = cp;
      if(name_full_or_null != NULL)
         *name_full_or_null = cp;
   }

   if(is_to_or_null != NULL)
      *is_to_or_null = isto;
   NYD_OU;
   return np;
}

FL void
setup_from_and_sender(struct header *hp)
{
   char const *addr;
   struct mx_name *np;
   NYD_IN;

   /* If -t parsed or composed From: then take it.  With -t we otherwise
    * want -r to be honoured in favour of *from* in order to have
    * a behaviour that is compatible with what users would expect from e.g.
    * postfix(1) */
   if ((np = hp->h_from) != NULL ||
         ((n_poption & n_PO_t_FLAG) && (np = n_poption_arg_r) != NULL)) {
      ;
   } else if ((addr = myaddrs(hp)) != NULL)
      np = lextract(addr, GEXTRA | GFULL | GFULLEXTRA);
   hp->h_from = np;

   if ((np = hp->h_sender) != NULL) {
      ;
   } else if ((addr = ok_vlook(sender)) != NULL)
      np = n_extract_single(addr, GEXTRA | GFULL | GFULLEXTRA);
   hp->h_sender = np;

   NYD_OU;
}

FL struct mx_name const *
check_from_and_sender(struct mx_name const *fromfield,
   struct mx_name const *senderfield)
{
   struct mx_name const *rv = NULL;
   NYD_IN;

   if (senderfield != NULL) {
      if (senderfield->n_flink != NULL) {
         n_err(_("The Sender: field may contain only one address\n"));
         goto jleave;
      }
      rv = senderfield;
   }

   if (fromfield != NULL) {
      if (fromfield->n_flink != NULL && senderfield == NULL) {
         n_err(_("A Sender: is required when there are multiple "
            "addresses in From:\n"));
         goto jleave;
      }
      if (rv == NULL)
         rv = fromfield;
   }

   if (rv == NULL)
      rv = (struct mx_name*)0x1;
jleave:
   NYD_OU;
   return rv;
}

#ifdef mx_HAVE_XTLS
FL char *
getsender(struct message *mp)
{
   char *cp;
   struct mx_name *np;
   NYD_IN;

   if ((cp = hfield1("from", mp)) == NULL ||
         (np = lextract(cp, GEXTRA | GSKIN)) == NULL)
      cp = NULL;
   else
      cp = (np->n_flink != NULL) ? skin(hfield1("sender", mp)) : np->n_name;
   NYD_OU;
   return cp;
}
#endif

FL struct mx_name *
n_header_setup_in_reply_to(struct header *hp){
   struct mx_name *np;
   NYD_IN;

   np = NULL;

   if(hp != NULL)
      if((np = hp->h_in_reply_to) == NULL && (np = hp->h_ref) != NULL)
         while(np->n_flink != NULL)
            np = np->n_flink;
   NYD_OU;
   return np;
}

FL int
grab_headers(enum n_go_input_flags gif, struct header *hp, enum gfield gflags,
      int subjfirst)
{
   /* TODO grab_headers: again, check counts etc. against RFC;
    * TODO (now assumes check_from_and_sender() is called afterwards ++ */
   int errs;
   int volatile comma;
   NYD_IN;

   errs = 0;
   comma = (ok_blook(bsdcompat) || ok_blook(bsdmsgs)) ? 0 : GCOMMA;

   if (gflags & GTO)
      hp->h_to = grab_names(gif, "To: ", hp->h_to, comma, GTO | GFULL);
   if (subjfirst && (gflags & GSUBJECT))
      hp->h_subject = n_go_input_cp(gif, "Subject: ", hp->h_subject);
   if (gflags & GCC)
      hp->h_cc = grab_names(gif, "Cc: ", hp->h_cc, comma, GCC | GFULL);
   if (gflags & GBCC)
      hp->h_bcc = grab_names(gif, "Bcc: ", hp->h_bcc, comma, GBCC | GFULL);

   if (gflags & GEXTRA) {
      if (hp->h_from == NULL)
         hp->h_from = lextract(myaddrs(hp), GEXTRA | GFULL | GFULLEXTRA);
      hp->h_from = grab_names(gif, "From: ", hp->h_from, comma,
            GEXTRA | GFULL | GFULLEXTRA);
      if (hp->h_reply_to == NULL) {
         struct mx_name *v15compat;

         if((v15compat = lextract(ok_vlook(replyto), GEXTRA | GFULL)) != NULL)
            n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
         hp->h_reply_to = lextract(ok_vlook(reply_to), GEXTRA | GFULL);
         if(hp->h_reply_to == NULL) /* v15 */
            hp->h_reply_to = v15compat;
      }
      hp->h_reply_to = grab_names(gif, "Reply-To: ", hp->h_reply_to, comma,
            GEXTRA | GFULL);
      if (hp->h_sender == NULL)
         hp->h_sender = n_extract_single(ok_vlook(sender), GEXTRA | GFULL);
      hp->h_sender = grab_names(gif, "Sender: ", hp->h_sender, comma,
            GEXTRA | GFULL | GNOT_A_LIST);
   }

   if (!subjfirst && (gflags & GSUBJECT))
      hp->h_subject = n_go_input_cp(gif, "Subject: ", hp->h_subject);

   NYD_OU;
   return errs;
}

FL boole
n_header_match(struct message *mp, struct search_expr const *sep){
   struct str fiter, in, out;
   char const *field;
   long lc;
   FILE *ibuf;
   uz linesize;
   char *linebuf, *colon;
   enum {a_NONE, a_ALL, a_ITER, a_RE} match;
   boole rv;
   NYD_IN;

   rv = FAL0;
   match = a_NONE;
   mx_fs_linepool_aquire(&linebuf, &linesize);
   UNINIT(fiter.l, 0);
   UNINIT(fiter.s, NIL);

   if((ibuf = setinput(&mb, mp, NEED_HEADER)) == NULL)
      goto jleave;
   if((lc = mp->m_lines - 1) < 0)
      goto jleave;

   if((mp->m_flag & MNOFROM) == 0 &&
         readline_restart(ibuf, &linebuf, &linesize, 0) < 0)
      goto jleave;

   /* */
   if((field = sep->ss_field) != NULL){
      if(!su_cs_cmp_case(field, "header") ||
            (field[0] == '<' && field[1] == '\0'))
         match = a_ALL;
      else{
         fiter.s = n_lofi_alloc((fiter.l = su_cs_len(field)) +1);
         match = a_ITER;
      }
#ifdef mx_HAVE_REGEX
   }else if(sep->ss_fieldre != NULL){
      match = a_RE;
#endif
   }else
      match = a_ALL;

   /* Iterate over all the headers */
   while(lc > 0){
      struct mx_name *np;

      if((lc = a_gethfield(n_HEADER_EXTRACT_NONE, ibuf, &linebuf, &linesize,
            lc, &colon)) <= 0)
         break;

      /* Is this a header we are interested in? */
      if(match == a_ITER){
         char *itercp;

         su_mem_copy(itercp = fiter.s, sep->ss_field, fiter.l +1);
         while((field = su_cs_sep_c(&itercp, ',', TRU1)) != NULL){
            /* It may be an abbreviation */
            char const x[][8] = {"from", "to", "cc", "bcc", "subject"};
            uz i;
            char c1;

            if(field[0] != '\0' && field[1] == '\0'){
               c1 = su_cs_to_lower(field[0]);
               for(i = 0; i < NELEM(x); ++i){
                  if(c1 == x[i][0]){
                     field = x[i];
                     break;
                  }
               }
            }

            if(!su_cs_cmp_case_n(field, linebuf, P2UZ(colon - linebuf)))
               break;
         }
         if(field == NULL)
            continue;
#ifdef mx_HAVE_REGEX
      }else if(match == a_RE){
         char *cp;
         uz i;

         i = P2UZ(colon - linebuf);
         cp = n_lofi_alloc(i +1);
         su_mem_copy(cp, linebuf, i);
         cp[i] = '\0';
         i = (regexec(sep->ss_fieldre, cp, 0,NULL, 0) != REG_NOMATCH);
         n_lofi_free(cp);
         if(!i)
            continue;
#endif
      }

      /* It could be a plain existence test */
      if(sep->ss_field_exists){
         rv = TRU1;
         break;
      }

      /* Need to check the body */
      while(su_cs_is_blank(*++colon))
         ;
      in.s = colon;

      /* Shall we split into address list and match as/the addresses only?
       * TODO at some later time we should ignore and log efforts to search
       * TODO a skinned address list if we know the header has none such */
      if(sep->ss_skin){
         if((np = lextract(in.s, GSKIN)) == NULL)
            continue;
         out.s = np->n_name;
      }else{
         np = NULL;
         in.l = su_cs_len(in.s);
         mime_fromhdr(&in, &out, TD_ICONV);
      }

jnext_name:
#ifdef mx_HAVE_REGEX
      if(sep->ss_bodyre != NULL)
         rv = (regexec(sep->ss_bodyre, out.s, 0,NULL, 0) != REG_NOMATCH);
      else
#endif
         rv = substr(out.s, sep->ss_body);

      if(np == NULL)
         n_free(out.s);
      if(rv)
         break;
      if(np != NULL && (np = np->n_flink) != NULL){
         out.s = np->n_name;
         goto jnext_name;
      }
   }

jleave:
   if(match == a_ITER)
      n_lofi_free(fiter.s);
   mx_fs_linepool_release(linebuf, linesize);
   NYD_OU;
   return rv;
}

FL char const *
n_header_is_known(char const *name, uz len){
   static char const * const names[] = {
      "Bcc", "Cc", "From",
      "In-Reply-To", "Mail-Followup-To",
      "Message-ID", "References", "Reply-To",
      "Sender", "Subject", "To",
      /* More known, here and there */
      "Fcc",
      /* Mailx internal temporaries */
      "Mailx-Command",
      "Mailx-Orig-Bcc", "Mailx-Orig-Cc", "Mailx-Orig-From", "Mailx-Orig-To",
      "Mailx-Raw-Bcc", "Mailx-Raw-Cc", "Mailx-Raw-To",
      NULL
   };
   char const * const *rv;
   NYD_IN;

   if(len == UZ_MAX)
      len = su_cs_len(name);

   for(rv = names; *rv != NULL; ++rv)
      if(!su_cs_cmp_case_n(*rv, name, len))
         break;
   NYD_OU;
   return *rv;
}

FL boole
n_header_add_custom(struct n_header_field **hflp, char const *dat,
      boole heap){
   uz i;
   u32 nl, bl;
   char const *cp;
   struct n_header_field *hfp;
   NYD_IN;

   hfp = NULL;

   /* For (-C) convenience, allow leading WS */
   while(su_cs_is_blank(*dat))
      ++dat;

   /* Isolate the header field from the body */
   for(cp = dat;; ++cp){
      if(fieldnamechar(*cp))
         continue;
      if(*cp == '\0'){
         if(cp == dat)
            goto jename;
      }else if(*cp != ':' && !su_cs_is_blank(*cp)){
jename:
         cp = N_("Invalid custom header (not \"field: body\"): %s\n");
         goto jerr;
      }
      break;
   }
   nl = (u32)P2UZ(cp - dat);
   if(nl == 0)
      goto jename;

   /* Verify the custom header does not use standard/managed field name */
   if(n_header_is_known(dat, nl) != NULL){
      cp = N_("Custom headers cannot use standard header names: %s\n");
      goto jerr;
   }

   /* Skip on over to the body */
   while(su_cs_is_blank(*cp))
      ++cp;
   if(*cp++ != ':')
      goto jename;
   while(su_cs_is_blank(*cp))
      ++cp;
   bl = (u32)su_cs_len(cp);
   for(i = bl++; i-- != 0;)
      if(su_cs_is_cntrl(cp[i])){
         cp = N_("Invalid custom header: contains control characters: %s\n");
         goto jerr;
      }

   i = VSTRUCT_SIZEOF(struct n_header_field, hf_dat) + nl +1 + bl;
   *hflp = hfp = heap ? n_alloc(i) : n_autorec_alloc(i);
   hfp->hf_next = NULL;
   hfp->hf_nl = nl;
   hfp->hf_bl = bl - 1;
   su_mem_copy(hfp->hf_dat, dat, nl);
      hfp->hf_dat[nl++] = '\0';
      su_mem_copy(hfp->hf_dat + nl, cp, bl);
jleave:
   NYD_OU;
   return (hfp != NULL);

jerr:
   n_err(V_(cp), n_shexp_quote_cp(dat, FAL0));
   goto jleave;
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/mx/iconv.c000066400000000000000000000220571352610246600155660ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ Implementation of iconv.h.
 *
 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: BSD-3-Clause
 */
/*
 * Copyright (c) 1980, 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.
 */
#undef su_FILE
#define su_FILE iconv
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

#include 
#include 
#include 

#include "mx/iconv.h"
/* TODO fake */
#include "su/code-in.h"

#ifdef mx_HAVE_ICONV
s32 n_iconv_err_no; /* TODO HACK: part of CTX to not get lost */
iconv_t iconvd;
#endif

char *
n_iconv_normalize_name(char const *cset){
   char *cp, c, *tcp, tc;
   boole any;
   NYD2_IN;

   /* We need to strip //SUFFIXes off, we want to normalize to all lowercase,
    * and we perform some slight content testing, too */
   for(any = FAL0, cp = n_UNCONST(cset); (c = *cp) != '\0'; ++cp){
      if(!su_cs_is_alnum(c) && !su_cs_is_punct(c)){
         n_err(_("Invalid character set name %s\n"),
            n_shexp_quote_cp(cset, FAL0));
         cset = NULL;
         goto jleave;
      }else if(c == '/')
         break;
      else if(su_cs_is_upper(c))
         any = TRU1;
   }

   if(any || c != '\0'){
      cp = savestrbuf(cset, P2UZ(cp - cset));
      for(tcp = cp; (tc = *tcp) != '\0'; ++tcp)
         *tcp = su_cs_to_lower(tc);

      if(c != '\0' && (n_poption & n_PO_D_V))
         n_err(_("Stripped off character set suffix: %s -> %s\n"),
            n_shexp_quote_cp(cset, FAL0), n_shexp_quote_cp(cp, FAL0));

      cset = cp;
   }
jleave:
   NYD2_OU;
   return n_UNCONST(cset);
}

boole
n_iconv_name_is_ascii(char const *cset){ /* TODO ctext/su */
   /* In reversed MIME preference order */
   static char const * const names[] = {"csASCII", "cp367", "IBM367", "us",
         "ISO646-US", "ISO_646.irv:1991", "ANSI_X3.4-1986", "iso-ir-6",
         "ANSI_X3.4-1968", "ASCII", "US-ASCII"};
   boole rv;
   char const * const *npp;
   NYD2_IN;

   npp = &names[NELEM(names)];
   do if((rv = !su_cs_cmp_case(cset, *--npp)))
      break;
   while((rv = (npp != &names[0])));
   NYD2_OU;
   return rv;
}

#ifdef mx_HAVE_ICONV
iconv_t
n_iconv_open(char const *tocode, char const *fromcode){
   iconv_t id;
   NYD_IN;

   if((!su_cs_cmp_case(fromcode, "unknown-8bit") ||
            !su_cs_cmp_case(fromcode, "binary")) &&
         (fromcode = ok_vlook(charset_unknown_8bit)) == NULL)
      fromcode = ok_vlook(CHARSET_8BIT_OKEY);

   id = iconv_open(tocode, fromcode);

   /* If the encoding names are equal at this point, they are just not
    * understood by iconv(), and we cannot sensibly use it in any way.  We do
    * not perform this as an optimization above since iconv() can otherwise be
    * used to check the validity of the input even with identical encoding
    * names */
   if (id == (iconv_t)-1 && !su_cs_cmp_case(tocode, fromcode))
      su_err_set_no(su_ERR_NONE);
   NYD_OU;
   return id;
}

void
n_iconv_close(iconv_t cd){
   NYD_IN;
   iconv_close(cd);
   if(cd == iconvd)
      iconvd = (iconv_t)-1;
   NYD_OU;
}

void
n_iconv_reset(iconv_t cd){
   NYD_IN;
   iconv(cd, NULL, NULL, NULL, NULL);
   NYD_OU;
}

/* (2012-09-24: export and use it exclusively to isolate prototype problems
 * (*inb* is 'char const **' except in POSIX) in a single place.
 * GNU libiconv even allows for configuration time const/non-const..
 * In the end it's an ugly guess, but we can't do better since make(1) doesn't
 * support compiler invocations which bail on error, so no -Werror */
/* Citrus project? */
# if defined _ICONV_H_ && defined __ICONV_F_HIDE_INVALID
  /* DragonFly 3.2.1 is special TODO newer DragonFly too, but different */
#  if su_OS_DRAGONFLY
#   define __INBCAST(S) (char ** __restrict__)n_UNCONST(S)
#  else
#   define __INBCAST(S) (char const **)n_UNCONST(S)
#  endif
# elif su_OS_SUNOS || su_OS_SOLARIS
#  define __INBCAST(S) (char const ** __restrict__)n_UNCONST(S)
# endif
# ifndef __INBCAST
#  define __INBCAST(S)  (char **)n_UNCONST(S)
# endif

int
n_iconv_buf(iconv_t cd, enum n_iconv_flags icf,
   char const **inb, uz *inbleft, char **outb, uz *outbleft){
   int err;
   NYD2_IN;

   if((icf & n_ICONV_UNIREPL) && !(n_psonce & n_PSO_UNICODE))
      icf &= ~n_ICONV_UNIREPL;

   for(;;){
      uz i;

      if((i = iconv(cd, __INBCAST(inb), inbleft, outb, outbleft)) == 0)
         break;
      if(i != (uz)-1){
         if(!(icf & n_ICONV_IGN_NOREVERSE)){
            err = su_ERR_NOENT;
            goto jleave;
         }
         break;
      }

      if((err = su_err_no()) == su_ERR_2BIG)
         goto jleave;

      if(!(icf & n_ICONV_IGN_ILSEQ) || err != su_ERR_ILSEQ)
         goto jleave;
      if(*inbleft > 0){
         ++(*inb);
         --(*inbleft);
         if(icf & n_ICONV_UNIREPL){
            if(*outbleft >= sizeof(su_utf8_replacer) -1){
               su_mem_copy(*outb, su_utf8_replacer,
                  sizeof(su_utf8_replacer) -1);
               *outb += sizeof(su_utf8_replacer) -1;
               *outbleft -= sizeof(su_utf8_replacer) -1;
               continue;
            }
         }else if(*outbleft > 0){
            *(*outb)++ = '?';
            --*outbleft;
            continue;
         }
         err = su_ERR_2BIG;
         goto jleave;
      }else if(*outbleft > 0){
         **outb = '\0';
         goto jleave;
      }
   }
   err = 0;
jleave:
   n_iconv_err_no = err;
   NYD2_OU;
   return err;
}
# undef __INBCAST

int
n_iconv_str(iconv_t cd, enum n_iconv_flags icf,
      struct str *out, struct str const *in, struct str *in_rest_or_null){
   struct n_string s_b, *s;
   char const *ib;
   int err;
   uz il;
   NYD2_IN;

   il = in->l;
   if(!n_string_get_can_book(il) || !n_string_get_can_book(out->l)){
      err = su_ERR_INVAL;
      goto j_leave;
   }
   ib = in->s;

   s = n_string_creat(&s_b);
   s = n_string_take_ownership(s, out->s, out->l, 0);

   for(;;){
      char *ob_base, *ob;
      uz ol, nol;

      if((nol = ol = s->s_len) < il)
         nol = il;
      ASSERT(sizeof(s->s_len) == sizeof(u32));
      if(nol < 128)
         nol += 32;
      else{
         u64 xnol;

         xnol = (u64)(nol << 1) - (nol >> 4);
         if(!n_string_can_book(s, xnol)){
            xnol = ol + 64;
            if(!n_string_can_book(s, xnol)){
               err = su_ERR_INVAL;
               goto jleave;
            }
         }
         nol = (uz)xnol;
      }
      s = n_string_resize(s, nol);

      ob = ob_base = &s->s_dat[ol];
      nol -= ol;
      err = n_iconv_buf(cd, icf, &ib, &il, &ob, &nol);

      s = n_string_trunc(s, ol + P2UZ(ob - ob_base));
      if(err == 0 || err != su_ERR_2BIG)
         break;
   }

   if(in_rest_or_null != NULL){
      in_rest_or_null->s = n_UNCONST(ib);
      in_rest_or_null->l = il;
   }

jleave:
   out->s = n_string_cp(s);
   out->l = s->s_len;
   s = n_string_drop_ownership(s);
   /* n_string_gut(s)*/
j_leave:
   NYD2_OU;
   return err;
}

char *
n_iconv_onetime_cp(enum n_iconv_flags icf,
      char const *tocode, char const *fromcode, char const *input){
   struct str out, in;
   iconv_t icd;
   char *rv;
   NYD2_IN;

   rv = NULL;
   if(tocode == NULL)
      tocode = ok_vlook(ttycharset);
   if(fromcode == NULL)
      fromcode = "utf-8";

   if((icd = iconv_open(tocode, fromcode)) == (iconv_t)-1)
      goto jleave;

   in.l = su_cs_len(in.s = n_UNCONST(input)); /* logical */
   out.s = NULL, out.l = 0;
   if(!n_iconv_str(icd, icf, &out, &in, NULL))
      rv = savestrbuf(out.s, out.l);
   if(out.s != NULL)
      n_free(out.s);

   iconv_close(icd);
jleave:
   NYD2_OU;
   return rv;
}
#endif /* mx_HAVE_ICONV */

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/mx/ignore.c000066400000000000000000000503311352610246600157270ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ `headerpick', `retain' and `ignore', and `un..' variants.
 *
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE ignore
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

#include 
#include 
#include 

#include "mx/termios.h"

/* TODO fake */
#include "su/code-in.h"

struct a_ignore_type{
   u32 it_count;     /* Entries in .it_ht (and .it_re) */
   boole it_all;       /* _All_ fields ought to be _type_ (ignore/retain) */
   u8 it__dummy[3];
   struct a_ignore_field{
      struct a_ignore_field *if_next;
      char if_field[VFIELD_SIZE(0)]; /* Header field */
   } *it_ht[3]; /* TODO make hashmap dynamic */
#ifdef mx_HAVE_REGEX
   struct a_ignore_re{
      struct a_ignore_re *ir_next;
      regex_t ir_regex;
      char ir_input[VFIELD_SIZE(0)]; /* Regex input text (for showing it) */
   } *it_re, *it_re_tail;
#endif
};

struct n_ignore{
   struct a_ignore_type i_retain;
   struct a_ignore_type i_ignore;
   boole i_auto;       /* In auto-reclaimed, not heap memory */
   boole i_bltin;      /* Is a built-in n_IGNORE* type */
   u8 i_ibm_idx;     /* If .i_bltin: a_ignore_bltin_map[] idx */
   u8 i__dummy[5];
};

struct a_ignore_bltin_map{
   struct n_ignore *ibm_ip;
   char const ibm_name[8];
};

static struct a_ignore_bltin_map const a_ignore_bltin_map[] = {
   {n_IGNORE_TYPE, "type\0"},
   {n_IGNORE_SAVE, "save\0"},
   {n_IGNORE_FWD, "forward\0"},
   {n_IGNORE_TOP, "top\0"},

   {n_IGNORE_TYPE, "print\0"},
   {n_IGNORE_FWD, "fwd\0"}
};
#ifdef mx_HAVE_DEVEL /* Avoid gcc warn cascade "n_ignore is defined locally" */
CTAV(-n__IGNORE_TYPE - n__IGNORE_ADJUST == 0);
CTAV(-n__IGNORE_SAVE - n__IGNORE_ADJUST == 1);
CTAV(-n__IGNORE_FWD - n__IGNORE_ADJUST == 2);
CTAV(-n__IGNORE_TOP - n__IGNORE_ADJUST == 3);
CTAV(n__IGNORE_MAX == 3);
#endif

static struct n_ignore *a_ignore_bltin[n__IGNORE_MAX + 1];
/* Almost everyone uses `ignore'/`retain', put _TYPE in BSS */
static struct n_ignore a_ignore_type;

/* Return real self, which is xself unless that is one of the built-in specials,
 * in which case NULL is returned if nonexistent and docreate is false.
 * The other statics assume self has been resolved (unless noted) */
static struct n_ignore *a_ignore_resolve_self(struct n_ignore *xself,
                           boole docreate);

/* Lookup whether a mapping is contained: TRU1=retained, TRUM1=ignored.
 * If retain is _not_ TRUM1 then only the retained/ignored slot is inspected,
 * and regular expressions are not executed but instead their .ir_input is
 * text-compared against len bytes of dat.
 * Note it doesn't handle the .it_all "all fields" condition */
static boole a_ignore_lookup(struct n_ignore const *self, boole retain,
               char const *dat, uz len);

/* Delete all retain( else ignor)ed members */
static void a_ignore_del_allof(struct n_ignore *ip, boole retain);

/* Try to map a string to one of the built-in types */
static struct a_ignore_bltin_map const *a_ignore_resolve_bltin(char const *cp);

/* Logic behind `headerpick T T' (a.k.a. `retain'+) */
static boole a_ignore_addcmd_mux(struct n_ignore *ip, char const **list,
               boole retain);

static void a_ignore__show(struct n_ignore const *ip, boole retain);

/* Logic behind `unheaderpick T T' (a.k.a. `unretain'+) */
static boole a_ignore_delcmd_mux(struct n_ignore *ip, char const **list,
               boole retain);

static boole a_ignore__delone(struct n_ignore *ip, boole retain,
               char const *field);

static struct n_ignore *
a_ignore_resolve_self(struct n_ignore *xself, boole docreate){
   up suip;
   struct n_ignore *self;
   NYD2_IN;

   self = xself;
   suip = -(up)self - n__IGNORE_ADJUST;

   if(suip <= n__IGNORE_MAX){
      if((self = a_ignore_bltin[suip]) == NULL && docreate){
         if(xself == n_IGNORE_TYPE){
            self = &a_ignore_type;
            /* LIB: su_mem_set(self, 0, sizeof *self);*/
         }else
            self = n_ignore_new(FAL0);
         self->i_bltin = TRU1;
         self->i_ibm_idx = (u8)suip;
         a_ignore_bltin[suip] = self;
      }
   }
   NYD2_OU;
   return self;
}

static boole
a_ignore_lookup(struct n_ignore const *self, boole retain,
      char const *dat, uz len){
   boole rv;
#ifdef mx_HAVE_REGEX
   struct a_ignore_re *irp;
#endif
   struct a_ignore_field *ifp;
   u32 hi;
   NYD2_IN;

   if(len == UZ_MAX)
      len = su_cs_len(dat);
   hi = su_cs_hash_case_cbuf(dat, len) % NELEM(self->i_retain.it_ht);

   /* Again: doesn't handle .it_all conditions! */
   /* (Inner functions would be nice, again) */
   if(retain && self->i_retain.it_count > 0){
      rv = TRU1;
      for(ifp = self->i_retain.it_ht[hi]; ifp != NULL; ifp = ifp->if_next)
         if(!su_cs_cmp_case_n(ifp->if_field, dat, len))
            goto jleave;
#ifdef mx_HAVE_REGEX
      if(dat[len - 1] != '\0')
         dat = savestrbuf(dat, len);
      for(irp = self->i_retain.it_re; irp != NULL; irp = irp->ir_next)
         if((retain == TRUM1
               ? (regexec(&irp->ir_regex, dat, 0,NULL, 0) != REG_NOMATCH)
               : !strncmp(irp->ir_input, dat, len)))
            goto jleave;
#endif
      rv = (retain == TRUM1) ? TRUM1 : FAL0;
   }else if((retain == TRUM1 || !retain) && self->i_ignore.it_count > 0){
      rv = TRUM1;
      for(ifp = self->i_ignore.it_ht[hi]; ifp != NULL; ifp = ifp->if_next)
         if(!su_cs_cmp_case_n(ifp->if_field, dat, len))
            goto jleave;
#ifdef mx_HAVE_REGEX
      if(dat[len - 1] != '\0')
         dat = savestrbuf(dat, len);
      for(irp = self->i_ignore.it_re; irp != NULL; irp = irp->ir_next)
         if((retain == TRUM1
               ? (regexec(&irp->ir_regex, dat, 0,NULL, 0) != REG_NOMATCH)
               : !strncmp(irp->ir_input, dat, len)))
            goto jleave;
#endif
      rv = (retain == TRUM1) ? TRU1 : FAL0;
   }else
      rv = FAL0;
jleave:
   NYD2_OU;
   return rv;
}

static void
a_ignore_del_allof(struct n_ignore *ip, boole retain){
#ifdef mx_HAVE_REGEX
   struct a_ignore_re *irp;
#endif
   struct a_ignore_field *ifp;
   struct a_ignore_type *itp;
   NYD2_IN;

   itp = retain ? &ip->i_retain : &ip->i_ignore;

   if(!ip->i_auto){
      uz i;

      for(i = 0; i < NELEM(itp->it_ht); ++i)
         for(ifp = itp->it_ht[i]; ifp != NULL;){
            struct a_ignore_field *x;

            x = ifp;
            ifp = ifp->if_next;
            n_free(x);
         }
   }

#ifdef mx_HAVE_REGEX
   for(irp = itp->it_re; irp != NULL;){
      struct a_ignore_re *x;

      x = irp;
      irp = irp->ir_next;
      regfree(&x->ir_regex);
      if(!ip->i_auto)
         n_free(x);
   }
#endif

   su_mem_set(itp, 0, sizeof *itp);
   NYD2_OU;
}

static struct a_ignore_bltin_map const *
a_ignore_resolve_bltin(char const *cp){
   struct a_ignore_bltin_map const *ibmp;
   NYD2_IN;

   for(ibmp = &a_ignore_bltin_map[0];;)
      if(!su_cs_cmp_case(cp, ibmp->ibm_name))
         break;
      else if(++ibmp == &a_ignore_bltin_map[NELEM(a_ignore_bltin_map)]){
         ibmp = NULL;
         break;
      }
   NYD2_OU;
   return ibmp;
}

static boole
a_ignore_addcmd_mux(struct n_ignore *ip, char const **list, boole retain){
   char const **ap;
   boole rv;
   NYD2_IN;

   ip = a_ignore_resolve_self(ip, rv = (*list != NULL));

   if(!rv){
      if(ip != NULL && ip->i_bltin)
         a_ignore__show(ip, retain);
      rv = TRU1;
   }else{
      for(ap = list; *ap != 0; ++ap)
         switch(n_ignore_insert_cp(ip, retain, *ap)){
         case FAL0:
            n_err(_("Invalid field name cannot be %s: %s\n"),
               (retain ? _("retained") : _("ignored")), *ap);
            rv = FAL0;
            break;
         case TRUM1:
            if(n_poption & n_PO_D_V)
               n_err(_("Field already %s: %s\n"),
                  (retain ? _("retained") : _("ignored")), *ap);
            /* FALLTHRU */
         case TRU1:
            break;
         }
   }
   NYD2_OU;
   return rv;
}

static void
a_ignore__show(struct n_ignore const *ip, boole retain){
#ifdef mx_HAVE_REGEX
   struct a_ignore_re *irp;
#endif
   struct a_ignore_field *ifp;
   uz i, sw;
   char const **ap, **ring;
   struct a_ignore_type const *itp;
   NYD2_IN;

   itp = retain ? &ip->i_retain : &ip->i_ignore;

   do{
      char const *pre, *attr;

      if(itp->it_all)
         pre = n_empty, attr = n_star;
      else if(itp->it_count == 0)
         pre = n_ns, attr = _("currently covers no fields");
      else
         break;
      fprintf(n_stdout, _("%sheaderpick %s %s %s\n"),
         pre, a_ignore_bltin_map[ip->i_ibm_idx].ibm_name,
         (retain ? "retain" : "ignore"), attr);
      goto jleave;
   }while(0);

   ring = n_autorec_alloc((itp->it_count +1) * sizeof *ring);
   for(ap = ring, i = 0; i < NELEM(itp->it_ht); ++i)
      for(ifp = itp->it_ht[i]; ifp != NULL; ifp = ifp->if_next)
         *ap++ = ifp->if_field;
   *ap = NULL;

   su_sort_shell_vpp(su_S(void const**,ring), P2UZ(ap - ring),
      su_cs_toolbox_case.tb_compare);

   i = fprintf(n_stdout, "headerpick %s %s",
      a_ignore_bltin_map[ip->i_ibm_idx].ibm_name,
      (retain ? "retain" : "ignore"));
   sw = mx_termios_dimen.tiosd_width;

   for(ap = ring; *ap != NULL; ++ap){
      /* These fields are all ASCII, no visual width needed */
      uz len;

      len = su_cs_len(*ap) + 1;
      if(UCMP(z, len, >=, sw - i)){
         fputs(" \\\n ", n_stdout);
         i = 1;
      }
      i += len;
      putc(' ', n_stdout);
      fputs(*ap, n_stdout);
   }

   /* Regular expression in FIFO order */
#ifdef mx_HAVE_REGEX
   for(irp = itp->it_re; irp != NULL; irp = irp->ir_next){
      uz len;
      char const *cp;

      cp = n_shexp_quote_cp(irp->ir_input, FAL0);
      len = su_cs_len(cp) + 1;
      if(UCMP(z, len, >=, sw - i)){
         fputs(" \\\n ", n_stdout);
         i = 1;
      }
      i += len;
      putc(' ', n_stdout);
      fputs(cp, n_stdout);
   }
#endif

   putc('\n', n_stdout);
jleave:
   fflush(n_stdout);
   NYD2_OU;
}

static boole
a_ignore_delcmd_mux(struct n_ignore *ip, char const **list, boole retain){
   char const *cp;
   struct a_ignore_type *itp;
   boole rv;
   NYD2_IN;

   ip = a_ignore_resolve_self(ip, rv = (*list != NULL));
   itp = retain ? &ip->i_retain : &ip->i_ignore;

   if(itp->it_count == 0 && !itp->it_all)
      n_err(_("No fields currently being %s\n"),
         (retain ? _("retained") : _("ignored")));
   else
      while((cp = *list++) != NULL)
         if(cp[0] == '*' && cp[1] == '\0')
            a_ignore_del_allof(ip, retain);
         else if(!a_ignore__delone(ip, retain, cp)){
            n_err(_("Field not %s: %s\n"),
               (retain ? _("retained") : _("ignored")), cp);
            rv = FAL0;
         }
   NYD2_OU;
   return rv;
}

static boole
a_ignore__delone(struct n_ignore *ip, boole retain, char const *field){
   struct a_ignore_type *itp;
   NYD_IN;

   itp = retain ? &ip->i_retain : &ip->i_ignore;

#ifdef mx_HAVE_REGEX
   if(n_is_maybe_regex(field)){
      struct a_ignore_re **lirp, *irp;

      for(irp = *(lirp = &itp->it_re); irp != NULL;
            lirp = &irp->ir_next, irp = irp->ir_next)
         if(!su_cs_cmp(field, irp->ir_input)){
            *lirp = irp->ir_next;
            if(irp == itp->it_re_tail)
               itp->it_re_tail = irp->ir_next;

            regfree(&irp->ir_regex);
            if(!ip->i_auto)
               n_free(irp);
            --itp->it_count;
            goto jleave;
         }
   }else
#endif /* mx_HAVE_REGEX */
   {
      struct a_ignore_field **ifpp, *ifp;
      u32 hi;

      hi = su_cs_hash_case_cbuf(field, UZ_MAX) % NELEM(itp->it_ht);

      for(ifp = *(ifpp = &itp->it_ht[hi]); ifp != NULL;
            ifpp = &ifp->if_next, ifp = ifp->if_next)
         if(!su_cs_cmp_case(ifp->if_field, field)){
            *ifpp = ifp->if_next;
            if(!ip->i_auto)
               n_free(ifp);
            --itp->it_count;
           goto jleave;
         }
   }

   ip = NULL;
jleave:
   NYD_OU;
   return (ip != NULL);
}

FL int
c_headerpick(void *vp){
   boole retain;
   struct a_ignore_bltin_map const *ibmp;
   char const **argv;
   int rv;
   NYD_IN;

   rv = 1;
   argv = vp;

   /* Without arguments, show all settings of all contexts */
   if(*argv == NULL){
      rv = 0;
      for(ibmp = &a_ignore_bltin_map[0];
            ibmp <= &a_ignore_bltin_map[n__IGNORE_MAX]; ++ibmp){
         rv |= !a_ignore_addcmd_mux(ibmp->ibm_ip, argv, TRU1);
         rv |= !a_ignore_addcmd_mux(ibmp->ibm_ip, argv, FAL0);
      }
      goto jleave;
   }

   if((ibmp = a_ignore_resolve_bltin(*argv)) == NULL){
      n_err(_("`headerpick': invalid context: %s\n"), *argv);
      goto jleave;
   }
   ++argv;

   /* With only , show all settings of it */
   if(*argv == NULL){
      rv = 0;
      rv |= !a_ignore_addcmd_mux(ibmp->ibm_ip, argv, TRU1);
      rv |= !a_ignore_addcmd_mux(ibmp->ibm_ip, argv, FAL0);
      goto jleave;
   }

   if(su_cs_starts_with_case("retain", *argv))
      retain = TRU1;
   else if(su_cs_starts_with_case("ignore", *argv))
      retain = FAL0;
   else{
      n_err(_("`headerpick': invalid type (retain, ignore): %s\n"), *argv);
      goto jleave;
   }
   ++argv;

   /* With only  and , show its settings */
   if(*argv == NULL){
      rv = !a_ignore_addcmd_mux(ibmp->ibm_ip, argv, retain);
      goto jleave;
   }

   rv = !a_ignore_addcmd_mux(ibmp->ibm_ip, argv, retain);
jleave:
   NYD_OU;
   return rv;
}

FL int
c_unheaderpick(void *vp){
   boole retain;
   struct a_ignore_bltin_map const *ibmp;
   char const **argv;
   int rv;
   NYD_IN;

   rv = 1;
   argv = vp;

   if((ibmp = a_ignore_resolve_bltin(*argv)) == NULL){
      n_err(_("`unheaderpick': invalid context: %s\n"), *argv);
      goto jleave;
   }
   ++argv;

   if(su_cs_starts_with_case("retain", *argv))
      retain = TRU1;
   else if(su_cs_starts_with_case("ignore", *argv))
      retain = FAL0;
   else{
      n_err(_("`unheaderpick': invalid type (retain, ignore): %s\n"), *argv);
      goto jleave;
   }
   ++argv;

   rv = !a_ignore_delcmd_mux(ibmp->ibm_ip, argv, retain);
jleave:
   NYD_OU;
   return rv;
}

FL int
c_retain(void *vp){
   int rv;
   NYD_IN;

   rv = !a_ignore_addcmd_mux(n_IGNORE_TYPE, vp, TRU1);
   NYD_OU;
   return rv;
}

FL int
c_ignore(void *vp){
   int rv;
   NYD_IN;

   rv = !a_ignore_addcmd_mux(n_IGNORE_TYPE, vp, FAL0);
   NYD_OU;
   return rv;
}

FL int
c_unretain(void *vp){
   int rv;
   NYD_IN;

   rv = !a_ignore_delcmd_mux(n_IGNORE_TYPE, vp, TRU1);
   NYD_OU;
   return rv;
}

FL int
c_unignore(void *vp){
   int rv;
   NYD_IN;

   rv = !a_ignore_delcmd_mux(n_IGNORE_TYPE, vp, FAL0);
   NYD_OU;
   return rv;
}

FL int
c_saveretain(void *v){ /* TODO v15 drop */
   int rv;
   NYD_IN;

   rv = !a_ignore_addcmd_mux(n_IGNORE_SAVE, v, TRU1);
   NYD_OU;
   return rv;
}

FL int
c_saveignore(void *v){ /* TODO v15 drop */
   int rv;
   NYD_IN;

   rv = !a_ignore_addcmd_mux(n_IGNORE_SAVE, v, FAL0);
   NYD_OU;
   return rv;
}

FL int
c_unsaveretain(void *v){ /* TODO v15 drop */
   int rv;
   NYD_IN;

   rv = !a_ignore_delcmd_mux(n_IGNORE_SAVE, v, TRU1);
   NYD_OU;
   return rv;
}

FL int
c_unsaveignore(void *v){ /* TODO v15 drop */
   int rv;
   NYD_IN;

   rv = !a_ignore_delcmd_mux(n_IGNORE_SAVE, v, FAL0);
   NYD_OU;
   return rv;
}

FL int
c_fwdretain(void *v){ /* TODO v15 drop */
   int rv;
   NYD_IN;

   rv = !a_ignore_addcmd_mux(n_IGNORE_FWD, v, TRU1);
   NYD_OU;
   return rv;
}

FL int
c_fwdignore(void *v){ /* TODO v15 drop */
   int rv;
   NYD_IN;

   rv = !a_ignore_addcmd_mux(n_IGNORE_FWD, v, FAL0);
   NYD_OU;
   return rv;
}

FL int
c_unfwdretain(void *v){ /* TODO v15 drop */
   int rv;
   NYD_IN;

   rv = !a_ignore_delcmd_mux(n_IGNORE_FWD, v, TRU1);
   NYD_OU;
   return rv;
}

FL int
c_unfwdignore(void *v){ /* TODO v15 drop */
   int rv;
   NYD_IN;

   rv = !a_ignore_delcmd_mux(n_IGNORE_FWD, v, FAL0);
   NYD_OU;
   return rv;
}

FL struct n_ignore *
n_ignore_new(boole isauto){
   struct n_ignore *self;
   NYD_IN;

   self = isauto ? n_autorec_calloc(1, sizeof *self) : n_calloc(1,sizeof *self);
   self->i_auto = isauto;
   NYD_OU;
   return self;
}

FL void
n_ignore_del(struct n_ignore *self){
   NYD_IN;
   a_ignore_del_allof(self, TRU1);
   a_ignore_del_allof(self, FAL0);
   if(!self->i_auto)
      n_free(self);
   NYD_OU;
}

FL boole
n_ignore_is_any(struct n_ignore const *self){
   boole rv;
   NYD_IN;

   self = a_ignore_resolve_self(n_UNCONST(self), FAL0);
   rv = (self != NULL &&
         (self->i_retain.it_count != 0 || self->i_retain.it_all ||
          self->i_ignore.it_count != 0 || self->i_ignore.it_all));
   NYD_OU;
   return rv;
}

FL boole
n_ignore_insert(struct n_ignore *self, boole retain,
      char const *dat, uz len){
#ifdef mx_HAVE_REGEX
   struct a_ignore_re *irp;
   boole isre;
#endif
   struct a_ignore_field *ifp;
   struct a_ignore_type *itp;
   boole rv;
   NYD_IN;

   retain = !!retain; /* Make it true bool, TRUM1 has special _lookup meaning */
   rv = FAL0;
   self = a_ignore_resolve_self(self, TRU1);

   if(len == UZ_MAX)
      len = su_cs_len(dat);

   /* Request to ignore or retain _anything_?  That is special-treated */
   if(len == 1 && dat[0] == '*'){
      itp = retain ? &self->i_retain : &self->i_ignore;
      if(itp->it_all)
         rv = TRUM1;
      else{
         itp->it_all = TRU1;
         a_ignore_del_allof(self, retain);
         rv = TRU1;
      }
      goto jleave;
   }

   /* Check for regular expression or valid fieldname */
#ifdef mx_HAVE_REGEX
   if(!(isre = n_is_maybe_regex_buf(dat, len)))
#endif
   {
      char c;
      uz i;

      for(i = 0; i < len; ++i){
         c = dat[i];
         if(!fieldnamechar(c))
            goto jleave;
      }
   }

   rv = TRUM1;
   if(a_ignore_lookup(self, retain, dat, len) == (retain ? TRU1 : TRUM1))
      goto jleave;

   itp = retain ? &self->i_retain : &self->i_ignore;

   if(itp->it_count == U32_MAX){
      n_err(_("Header selection size limit reached, cannot insert: %.*s\n"),
         (int)MIN(len, S32_MAX), dat);
      rv = FAL0;
      goto jleave;
   }

   rv = TRU1;
#ifdef mx_HAVE_REGEX
   if(isre){
      struct a_ignore_re *x;
      int s;
      uz i;

      i = VSTRUCT_SIZEOF(struct a_ignore_re, ir_input) + ++len;
      irp = self->i_auto ? n_autorec_alloc(i) : n_alloc(i);
      su_mem_copy(irp->ir_input, dat, --len);
      irp->ir_input[len] = '\0';

      if((s = regcomp(&irp->ir_regex, irp->ir_input,
            REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
         n_err(_("Invalid regular expression: %s: %s\n"),
            n_shexp_quote_cp(irp->ir_input, FAL0),
            n_regex_err_to_doc(NULL, s));
         if(!self->i_auto)
            n_free(irp);
         rv = FAL0;
         goto jleave;
      }

      irp->ir_next = NULL;
      if((x = itp->it_re_tail) != NULL)
         x->ir_next = irp;
      else
         itp->it_re = irp;
      itp->it_re_tail = irp;
   }else
#endif /* mx_HAVE_REGEX */
   {
      u32 hi;
      uz i;

      i = VSTRUCT_SIZEOF(struct a_ignore_field, if_field) + len + 1;
      ifp = self->i_auto ? n_autorec_alloc(i) : n_alloc(i);
      su_mem_copy(ifp->if_field, dat, len);
      ifp->if_field[len] = '\0';
      hi = su_cs_hash_case_cbuf(dat, len) % NELEM(itp->it_ht);
      ifp->if_next = itp->it_ht[hi];
      itp->it_ht[hi] = ifp;
   }
   ++itp->it_count;
jleave:
   NYD_OU;
   return rv;
}

FL boole
n_ignore_lookup(struct n_ignore const *self, char const *dat, uz len){
   boole rv;
   NYD_IN;

   if(self == n_IGNORE_ALL)
      rv = TRUM1;
   else if(len == 0 ||
         (self = a_ignore_resolve_self(n_UNCONST(self), FAL0)) == NULL)
      rv = FAL0;
   else if(self->i_retain.it_all)
      rv = TRU1;
   else if(self->i_retain.it_count == 0 && self->i_ignore.it_all)
      rv = TRUM1;
   else
      rv = a_ignore_lookup(self, TRUM1, dat, len);
   NYD_OU;
   return rv;
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/mx/imap-search.c000066400000000000000000000543231352610246600166420ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ Client-side implementation of the IMAP SEARCH command. This is used
 *@ for folders not located on IMAP servers, or for IMAP servers that do
 *@ not implement the SEARCH command.
 *
 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: BSD-4-Clause
 */
/*
 * Copyright (c) 2004
 * Gunnar Ritter.  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 Gunnar Ritter
 *    and his contributors.
 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER 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 GUNNAR RITTER 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.
 */
#undef su_FILE
#define su_FILE imap_search
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

su_EMPTY_FILE()
#ifdef mx_HAVE_IMAP_SEARCH

#include 
#include 
#include 

#include "mx/file-streams.h"
#include "mx/names.h"

/* TODO fake */
#include "su/code-in.h"

enum itoken {
   ITBAD, ITEOD, ITBOL, ITEOL, ITAND, ITSET, ITALL, ITANSWERED,
   ITBCC, ITBEFORE, ITBODY,
   ITCC,
   ITDELETED, ITDRAFT,
   ITFLAGGED, ITFROM,
   ITHEADER,
   ITKEYWORD,
   ITLARGER,
   ITNEW, ITNOT,
   ITOLD, ITON, ITOR,
   ITRECENT,
   ITSEEN, ITSENTBEFORE, ITSENTON, ITSENTSINCE, ITSINCE, ITSMALLER,
      ITSUBJECT,
   ITTEXT, ITTO,
   ITUID, ITUNANSWERED, ITUNDELETED, ITUNDRAFT, ITUNFLAGGED, ITUNKEYWORD,
      ITUNSEEN
};

struct itlex {
   char const     *s_string;
   enum itoken    s_token;
};

struct itnode {
   enum itoken    n_token;
   uz          n_n;
   void           *n_v;
   void           *n_w;
   struct itnode  *n_x;
   struct itnode  *n_y;
};

static struct itlex const  _it_strings[] = {
   { "ALL",          ITALL },
   { "ANSWERED",     ITANSWERED },
   { "BCC",          ITBCC },
   { "BEFORE",       ITBEFORE },
   { "BODY",         ITBODY },
   { "CC",           ITCC },
   { "DELETED",      ITDELETED },
   { "DRAFT",        ITDRAFT },
   { "FLAGGED",      ITFLAGGED },
   { "FROM",         ITFROM },
   { "HEADER",       ITHEADER },
   { "KEYWORD",      ITKEYWORD },
   { "LARGER",       ITLARGER },
   { "NEW",          ITNEW },
   { "NOT",          ITNOT },
   { "OLD",          ITOLD },
   { "ON",           ITON },
   { "OR",           ITOR },
   { "RECENT",       ITRECENT },
   { "SEEN",         ITSEEN },
   { "SENTBEFORE",   ITSENTBEFORE },
   { "SENTON",       ITSENTON },
   { "SENTSINCE",    ITSENTSINCE },
   { "SINCE",        ITSINCE },
   { "SMALLER",      ITSMALLER },
   { "SUBJECT",      ITSUBJECT },
   { "TEXT",         ITTEXT },
   { "TO",           ITTO },
   { "UID",          ITUID },
   { "UNANSWERED",   ITUNANSWERED },
   { "UNDELETED",    ITUNDELETED },
   { "UNDRAFT",      ITUNDRAFT },
   { "UNFLAGGED",    ITUNFLAGGED },
   { "UNKEYWORD",    ITUNKEYWORD },
   { "UNSEEN",       ITUNSEEN },
   { NULL,           ITBAD }
};

static struct itnode    *_it_tree;
static char             *_it_begin;
static enum itoken      _it_token;
static uz            _it_number;
static void             *_it_args[2];
static uz           _it_need_headers;

static enum okay     itparse(char const *spec, char const **xp, int sub);
static enum okay     itscan(char const *spec, char const **xp);
static enum okay     itsplit(char const *spec, char const **xp);
static enum okay     itstring(void **tp, char const *spec, char const **xp);
static int           itexecute(struct mailbox *mp, struct message *m,
                        uz c, struct itnode *n);

static time_t        _imap_read_date(char const *cp);
static char *        _imap_quotestr(char const *s);
static char *        _imap_unquotestr(char const *s);

static boole        matchfield(struct message *m, char const *field,
                        void const *what);
static int           matchenvelope(struct message *m, char const *field,
                        void const *what);
static char *        mkenvelope(struct mx_name *np);
static char const *  around(char const *cp);

static enum okay
itparse(char const *spec, char const **xp, int sub)
{
   int level = 0;
   struct itnode n, *z, *ittree;
   enum okay rv;
   NYD_IN;

   _it_tree = NULL;
   while ((rv = itscan(spec, xp)) == OKAY && _it_token != ITBAD &&
         _it_token != ITEOD) {
      ittree = _it_tree;
      su_mem_set(&n, 0, sizeof n);
      spec = *xp;
      switch (_it_token) {
      case ITBOL:
         ++level;
         continue;
      case ITEOL:
         if (--level == 0)
            goto jleave;
         if (level < 0) {
            if (sub > 0) {
               --(*xp);
               goto jleave;
            }
            n_err(_("Excess in )\n"));
            rv = STOP;
            goto jleave;
         }
         continue;
      case ITNOT:
         /*  */
         n.n_token = ITNOT;
         if ((rv = itparse(spec, xp, sub + 1)) == STOP)
            goto jleave;
         spec = *xp;
         if ((n.n_x = _it_tree) == NULL) {
            n_err(_("Criterion for NOT missing: >>> %s <<<\n"), around(*xp));
            rv = STOP;
            goto jleave;
         }
         _it_token = ITNOT;
         break;
      case ITOR:
         /*   */
         n.n_token = ITOR;
         if ((rv = itparse(spec, xp, sub + 1)) == STOP)
            goto jleave;
         if ((n.n_x = _it_tree) == NULL) {
            n_err(_("First criterion for OR missing: >>> %s <<<\n"),
               around(*xp));
            rv = STOP;
            goto jleave;
         }
         spec = *xp;
         if ((rv = itparse(spec, xp, sub + 1)) == STOP)
            goto jleave;
         spec = *xp;
         if ((n.n_y = _it_tree) == NULL) {
            n_err(_("Second criterion for OR missing: >>> %s <<<\n"),
               around(*xp));
            rv = STOP;
            goto jleave;
         }
         break;
      default:
         n.n_token = _it_token;
         n.n_n = _it_number;
         n.n_v = _it_args[0];
         n.n_w = _it_args[1];
      }

      _it_tree = ittree;
      if (_it_tree == NULL) {
         _it_tree = n_autorec_alloc(sizeof *_it_tree);
         *_it_tree = n;
      } else {
         z = _it_tree;
         _it_tree = n_autorec_alloc(sizeof *_it_tree);
         _it_tree->n_token = ITAND;
         _it_tree->n_x = z;
         _it_tree->n_y = n_autorec_alloc(sizeof *_it_tree->n_y);
         *_it_tree->n_y = n;
      }
      if (sub && level == 0)
         break;
   }
jleave:
   NYD_OU;
   return rv;
}

static enum okay
itscan(char const *spec, char const **xp)
{
   int i, n;
   enum okay rv = OKAY;
   NYD_IN;

   while (su_cs_is_space(*spec))
      ++spec;
   if (*spec == '(') {
      *xp = &spec[1];
      _it_token = ITBOL;
      goto jleave;
   }
   if (*spec == ')') {
      *xp = &spec[1];
      _it_token = ITEOL;
      goto jleave;
   }
   while (su_cs_is_space(*spec))
      ++spec;
   if (*spec == '\0') {
      _it_token = ITEOD;
      goto jleave;
   }

#define __GO(C) ((C) != '\0' && (C) != '(' && (C) != ')' && !su_cs_is_space(C))
   for (i = 0; _it_strings[i].s_string != NULL; ++i) {
      n = su_cs_len(_it_strings[i].s_string);
      if (!su_cs_cmp_case_n(spec, _it_strings[i].s_string, n) &&
            !__GO(spec[n])) {
         _it_token = _it_strings[i].s_token;
         spec += n;
         while (su_cs_is_space(*spec))
            ++spec;
         rv = itsplit(spec, xp);
         goto jleave;
      }
   }
   if (su_cs_is_digit(*spec)) {
      su_idec_uz_cp(&_it_number, spec, 10, xp);
      if (!__GO(**xp)) {
         _it_token = ITSET;
         goto jleave;
      }
   }

   n_err(_("Bad SEARCH criterion: "));
   for (i = 0; __GO(spec[i]); ++i)
      ;
   n_err(_("%.*s: >>> %s <<<\n"), i, spec, around(*xp));
#undef __GO

   _it_token = ITBAD;
   rv = STOP;
jleave:
   NYD_OU;
   return rv;
}

static enum okay
itsplit(char const *spec, char const **xp)
{
   char const *cp;
   time_t t;
   enum okay rv;
   NYD_IN;

   switch (_it_token) {
   case ITBCC:
   case ITBODY:
   case ITCC:
   case ITFROM:
   case ITSUBJECT:
   case ITTEXT:
   case ITTO:
      /*  */
      ++_it_need_headers;
      rv = itstring(_it_args, spec, xp);
      break;
   case ITSENTBEFORE:
   case ITSENTON:
   case ITSENTSINCE:
      ++_it_need_headers;
      /*FALLTHRU*/
   case ITBEFORE:
   case ITON:
   case ITSINCE:
      /*  */
      if ((rv = itstring(_it_args, spec, xp)) != OKAY)
         break;
      if ((t = _imap_read_date(_it_args[0])) == (time_t)-1) {
         n_err(_("Invalid date %s: >>> %s <<<\n"),
            (char*)_it_args[0], around(*xp));
         rv = STOP;
         break;
      }
      _it_number = t;
      rv = OKAY;
      break;
   case ITHEADER:
      /*   */
      ++_it_need_headers;
      if ((rv = itstring(_it_args, spec, xp)) != OKAY)
         break;
      spec = *xp;
      if ((rv = itstring(_it_args + 1, spec, xp)) != OKAY)
         break;
      break;
   case ITKEYWORD:
   case ITUNKEYWORD:
      /*  */ /* TODO use table->flag map search instead */
      if ((rv = itstring(_it_args, spec, xp)) != OKAY)
         break;
      if (!su_cs_cmp_case(_it_args[0], "\\Seen"))
         _it_number = MREAD;
      else if (!su_cs_cmp_case(_it_args[0], "\\Deleted"))
         _it_number = MDELETED;
      else if (!su_cs_cmp_case(_it_args[0], "\\Recent"))
         _it_number = MNEW;
      else if (!su_cs_cmp_case(_it_args[0], "\\Flagged"))
         _it_number = MFLAGGED;
      else if (!su_cs_cmp_case(_it_args[0], "\\Answered"))
         _it_number = MANSWERED;
      else if (!su_cs_cmp_case(_it_args[0], "\\Draft"))
         _it_number = MDRAFT;
      else
         _it_number = 0;
      break;
   case ITLARGER:
   case ITSMALLER:
      /*  */
      if ((rv = itstring(_it_args, spec, xp)) != OKAY)
         break;
      else{
         su_idec_uz_cp(&_it_number, _it_args[0], 10, &cp);
      }
      if (su_cs_is_space(*cp) || *cp == '\0')
         break;
      n_err(_("Invalid size: >>> %s <<<\n"), around(*xp));
      rv = STOP;
      break;
   case ITUID:
      /*  */
      n_err(_("Searching for UIDs is not supported: >>> %s <<<\n"),
         around(*xp));
      rv = STOP;
      break;
   default:
      *xp = spec;
      rv = OKAY;
      break;
   }
   NYD_OU;
   return rv;
}

static enum okay
itstring(void **tp, char const *spec, char const **xp) /* XXX lesser derefs */
{
   int inquote = 0;
   char *ap;
   enum okay rv = STOP;
   NYD_IN;

   while (su_cs_is_space(*spec))
      ++spec;
   if (*spec == '\0' || *spec == '(' || *spec == ')') {
      n_err(_("Missing string argument: >>> %s <<<\n"),
         around(&(*xp)[spec - *xp]));
      goto jleave;
   }
   ap = *tp = n_autorec_alloc(su_cs_len(spec) +1);
   *xp = spec;
    do {
      if (inquote && **xp == '\\')
         *ap++ = *(*xp)++;
      else if (**xp == '"')
         inquote = !inquote;
      else if (!inquote &&
            (su_cs_is_space(**xp) || **xp == '(' || **xp == ')')) {
         *ap++ = '\0';
         break;
      }
      *ap++ = **xp;
   } while (*(*xp)++);

   *tp = _imap_unquotestr(*tp);
   rv = OKAY;
jleave:
   NYD_OU;
   return rv;
}

static int
itexecute(struct mailbox *mp, struct message *m, uz c, struct itnode *n)
{
   struct search_expr se;
   char *cp;
   FILE *ibuf;
   int rv;
   NYD_IN;

   if (n == NULL) {
      n_err(_("Internal error: Empty node in SEARCH tree\n"));
      rv = 0;
      goto jleave;
   }

   switch (n->n_token) {
   case ITBEFORE:
   case ITON:
   case ITSINCE:
      if (m->m_time == 0 && !(m->m_flag & MNOFROM) &&
            (ibuf = setinput(mp, m, NEED_HEADER)) != NULL) {
         char *line;
         uz linesize;

         mx_fs_linepool_aquire(&line, &linesize);
         if (readline_restart(ibuf, &line, &linesize, 0) > 0)
            m->m_time = unixtime(line);
         mx_fs_linepool_release(line, linesize);
      }
      break;
   case ITSENTBEFORE:
   case ITSENTON:
   case ITSENTSINCE:
      if (m->m_date == 0)
         if ((cp = hfield1("date", m)) != NULL)
            m->m_date = rfctime(cp);
      break;
   default:
      break;
   }

   switch (n->n_token) {
   default:
      n_err(_("Internal SEARCH error: Lost token %d\n"), n->n_token);
      rv = 0;
      break;
   case ITAND:
      rv = itexecute(mp, m, c, n->n_x) & itexecute(mp, m, c, n->n_y);
      break;
   case ITSET:
      rv = (c == n->n_n);
      break;
   case ITALL:
      rv = 1;
      break;
   case ITANSWERED:
      rv = ((m->m_flag & MANSWERED) != 0);
      break;
   case ITBCC:
      rv = matchenvelope(m, "bcc", n->n_v);
      break;
   case ITBEFORE:
      rv = UCMP(z, m->m_time, <, n->n_n);
      break;
   case ITBODY:
      su_mem_set(&se, 0, sizeof se);
      se.ss_field = "body";
      se.ss_body = n->n_v;
      rv = message_match(m, &se, FAL0);
      break;
   case ITCC:
      rv = matchenvelope(m, "cc", n->n_v);
      break;
   case ITDELETED:
      rv = ((m->m_flag & MDELETED) != 0);
      break;
   case ITDRAFT:
      rv = ((m->m_flag & MDRAFTED) != 0);
      break;
   case ITFLAGGED:
      rv = ((m->m_flag & MFLAGGED) != 0);
      break;
   case ITFROM:
      rv = matchenvelope(m, "from", n->n_v);
      break;
   case ITHEADER:
      rv = matchfield(m, n->n_v, n->n_w);
      break;
   case ITKEYWORD:
      rv = ((m->m_flag & n->n_n) != 0);
      break;
   case ITLARGER:
      rv = (m->m_xsize > n->n_n);
      break;
   case ITNEW:
      rv = ((m->m_flag & (MNEW | MREAD)) == MNEW);
      break;
   case ITNOT:
      rv = !itexecute(mp, m, c, n->n_x);
      break;
   case ITOLD:
      rv = !(m->m_flag & MNEW);
      break;
   case ITON:
      rv = (UCMP(z, m->m_time, >=, n->n_n) &&
            UCMP(z, m->m_time, <, n->n_n + 86400));
      break;
   case ITOR:
      rv = itexecute(mp, m, c, n->n_x) | itexecute(mp, m, c, n->n_y);
      break;
   case ITRECENT:
      rv = ((m->m_flag & MNEW) != 0);
      break;
   case ITSEEN:
      rv = ((m->m_flag & MREAD) != 0);
      break;
   case ITSENTBEFORE:
      rv = UCMP(z, m->m_date, <, n->n_n);
      break;
   case ITSENTON:
      rv = (UCMP(z, m->m_date, >=, n->n_n) &&
            UCMP(z, m->m_date, <, n->n_n + 86400));
      break;
   case ITSENTSINCE:
      rv = UCMP(z, m->m_date, >=, n->n_n);
      break;
   case ITSINCE:
      rv = UCMP(z, m->m_time, >=, n->n_n);
      break;
   case ITSMALLER:
      rv = UCMP(z, m->m_xsize, <, n->n_n);
      break;
   case ITSUBJECT:
      rv = matchfield(m, "subject", n->n_v);
      break;
   case ITTEXT:
      su_mem_set(&se, 0, sizeof se);
      se.ss_field = "text";
      se.ss_body = n->n_v;
      rv = message_match(m, &se, TRU1);
      break;
   case ITTO:
      rv = matchenvelope(m, "to", n->n_v);
      break;
   case ITUNANSWERED:
      rv = !(m->m_flag & MANSWERED);
      break;
   case ITUNDELETED:
      rv = !(m->m_flag & MDELETED);
      break;
   case ITUNDRAFT:
      rv = !(m->m_flag & MDRAFTED);
      break;
   case ITUNFLAGGED:
      rv = !(m->m_flag & MFLAGGED);
      break;
   case ITUNKEYWORD:
      rv = !(m->m_flag & n->n_n);
      break;
   case ITUNSEEN:
      rv = !(m->m_flag & MREAD);
      break;
   }
jleave:
   NYD_OU;
   return rv;
}

static time_t
_imap_read_date(char const *cp)
{
   time_t t, t2;
   s32 year, month, day, i, tzdiff;
   struct tm *tmptr;
   char const *xp, *yp;
   NYD_IN;

   if (*cp == '"')
      ++cp;
   su_idec_s32_cp(&day, cp, 10, &xp);
   if (day <= 0 || day > 31 || *xp++ != '-')
      goto jerr;

   for (i = 0;;) {
      if (!su_cs_cmp_case_n(xp, n_month_names[i], 3))
         break;
      if (n_month_names[++i][0] == '\0')
         goto jerr;
   }
   month = i + 1;
   if (xp[3] != '-')
      goto jerr;
   su_idec_s32_cp(&year, &xp[4], 10, &yp);
   if (year < 1970 || year > 2037 || PCMP(yp, !=, xp + 8))
      goto jerr;
   if (yp[0] != '\0' && (yp[1] != '"' || yp[2] != '\0'))
      goto jerr;
   if ((t = combinetime(year, month, day, 0, 0, 0)) == (time_t)-1)
      goto jleave/*jerr*/;
   if((t2 = mktime(gmtime(&t))) == (time_t)-1)
      goto jerr;
   tzdiff = t - t2;
   if((tmptr = localtime(&t)) == NULL)
      goto jerr;
   if (tmptr->tm_isdst > 0)
      tzdiff += 3600;
   t -= tzdiff;
jleave:
   NYD_OU;
   return t;
jerr:
   t = (time_t)-1;
   goto jleave;
}

static char *
_imap_quotestr(char const *s)
{
   char *n, *np;
   NYD2_IN;

   np = n = n_autorec_alloc(2 * su_cs_len(s) + 3);
   *np++ = '"';
   while (*s) {
      if (*s == '"' || *s == '\\')
         *np++ = '\\';
      *np++ = *s++;
   }
   *np++ = '"';
   *np = '\0';
   NYD2_OU;
   return n;
}

static char *
_imap_unquotestr(char const *s)
{
   char *n, *np;
   NYD2_IN;

   if (*s != '"') {
      n = savestr(s);
      goto jleave;
   }

   np = n = n_autorec_alloc(su_cs_len(s) + 1);
   while (*++s) {
      if (*s == '\\')
         s++;
      else if (*s == '"')
         break;
      *np++ = *s;
   }
   *np = '\0';
jleave:
   NYD2_OU;
   return n;
}

static boole
matchfield(struct message *m, char const *field, void const *what)
{
   struct str in, out;
   boole rv = FAL0;
   NYD_IN;

   if ((in.s = hfieldX(field, m)) == NULL)
      goto jleave;

   in.l = su_cs_len(in.s);
   mime_fromhdr(&in, &out, TD_ICONV);
   rv = substr(out.s, what);
   n_free(out.s);
jleave:
   NYD_OU;
   return rv;
}

static int
matchenvelope(struct message *m, char const *field, void const *what)
{
   struct mx_name *np;
   char *cp;
   int rv = 0;
   NYD_IN;

   if ((cp = hfieldX(field, m)) == NULL)
      goto jleave;

   for (np = lextract(cp, GFULL); np != NULL; np = np->n_flink) {
      if (!substr(np->n_name, what) && !substr(mkenvelope(np), what))
         continue;
      rv = 1;
      break;
   }

jleave:
   NYD_OU;
   return rv;
}

static char *
mkenvelope(struct mx_name *np)
{
   uz epsize;
   char *ep, *realnam = NULL, /**sourceaddr = NULL,*/ *localpart,
      *domainpart, *cp, *rp, *xp, *ip;
   struct str in, out;
   int level = 0;
   boole hadphrase = FAL0;
   NYD_IN;

   in.s = np->n_fullname;
   in.l = su_cs_len(in.s);
   mime_fromhdr(&in, &out, TD_ICONV);
   rp = ip = n_lofi_alloc(su_cs_len(out.s) + 1);
   for (cp = out.s; *cp; cp++) {
      switch (*cp) {
      case '"':
         while (*cp) {
            if (*++cp == '"')
               break;
            if (cp[0] == '\\' && cp[1] != '\0')
               ++cp;
            *rp++ = *cp;
         }
         break;
      case '<':
         while (cp > out.s && su_cs_is_blank(cp[-1]))
            --cp;
         rp = ip;
         xp = out.s;
         if (PCMP(xp, <, cp - 1) && *xp == '"' && cp[-1] == '"') {
            ++xp;
            --cp;
         }
         while (xp < cp)
            *rp++ = *xp++;
         hadphrase = TRU1;
         goto jdone;
      case '(':
         if (level++)
            goto jdfl;
         if (!hadphrase)
            rp = ip;
         hadphrase = TRU1;
         break;
      case ')':
         if (--level)
            goto jdfl;
         break;
      case '\\':
         if (level && cp[1] != '\0')
            cp++;
         goto jdfl;
      default:
jdfl:
         *rp++ = *cp;
      }
   }
jdone:
   *rp = '\0';
   if (hadphrase)
      realnam = ip;
   n_free(out.s);
   localpart = savestr(np->n_name);
   if ((cp = su_cs_rfind_c(localpart, '@')) != NULL) {
      *cp = '\0';
      domainpart = cp + 1;
   }else
      domainpart = NULL;

   ep = n_autorec_alloc(epsize = su_cs_len(np->n_fullname) * 2 + 40);
   snprintf(ep, epsize, "(%s %s %s %s)",
      realnam ? _imap_quotestr(realnam) : "NIL",
      /*sourceaddr ? _imap_quotestr(sourceaddr) :*/ "NIL",
      _imap_quotestr(localpart),
      domainpart ? _imap_quotestr(domainpart) : "NIL");
   n_lofi_free(ip);
   NYD_OU;
   return ep;
}

#define SURROUNDING 16
static char const *
around(char const *cp)
{
   static char ab[2 * SURROUNDING +1];

   uz i;
   NYD_IN;

   for (i = 0; i < SURROUNDING && cp > _it_begin; ++i)
      --cp;
   for (i = 0; i < sizeof(ab) -1; ++i)
      ab[i] = *cp++;
   ab[i] = '\0';
   NYD_OU;
   return ab;
}

FL sz
imap_search(char const *spec, int f)
{
   static char *lastspec;

   char const *xp;
   uz i;
   sz rv;
   NYD_IN;

   if (su_cs_cmp(spec, "()")) {
      if (lastspec != NULL)
         n_free(lastspec);
      i = su_cs_len(spec);
      lastspec = su_cs_dup_cbuf(spec, i, 0);
   } else if (lastspec == NULL) {
      n_err(_("No last SEARCH criteria available\n"));
      rv = -1;
      goto jleave;
   }
   spec =
   _it_begin = lastspec;

   _it_need_headers = FAL0;
#ifdef mx_HAVE_IMAP
   if ((rv = imap_search1(spec, f) == OKAY))
      goto jleave;
#endif
   if (itparse(spec, &xp, 0) == STOP){
      rv = -1;
      goto jleave;
   }

   rv = 0;

   if (_it_tree == NULL)
      goto jleave;

#ifdef mx_HAVE_IMAP
   if (mb.mb_type == MB_IMAP && _it_need_headers)
      imap_getheaders(1, msgCount);
#endif
   srelax_hold();
   for (i = 0; UCMP(z, i, <, msgCount); ++i) {
      if (message[i].m_flag & MHIDDEN)
         continue;
      if (f == MDELETED || !(message[i].m_flag & MDELETED)) {
         uz j = (int)(i + 1);
         if (itexecute(&mb, message + i, j, _it_tree)){
            mark((int)j, f);
            ++rv;
         }
         srelax();
      }
   }
   srelax_rele();
jleave:
   NYD_OU;
   return rv;
}

#include "su/code-ou.h"
#endif /* mx_HAVE_IMAP_SEARCH */
/* s-it-mode */
s-nail-14.9.15/src/mx/maildir.c000066400000000000000000000757631352610246600161050ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ Maildir folder support. FIXME rewrite - why do we chdir(2)??
 *@ FIXME Simply truncating paths isn't really it.
 *
 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: BSD-4-Clause
 */
/*
 * Copyright (c) 2004
 * Gunnar Ritter.  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 Gunnar Ritter
 *    and his contributors.
 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER 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 GUNNAR RITTER 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.
 */
#undef su_FILE
#define su_FILE maildir
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

su_EMPTY_FILE()
#ifdef mx_HAVE_MAILDIR

#include 

#include 
#include 
#include 
#include 

#include "mx/file-streams.h"
#include "mx/sigs.h"

/* TODO fake */
#include "su/code-in.h"

/* a_maildir_tbl should be a hash-indexed array of trees! */
static struct message **a_maildir_tbl, **a_maildir_tbl_top;
static u32 a_maildir_tbl_prime, a_maildir_tbl_maxdist;
static sigjmp_buf    _maildir_jmp;

static void             __maildircatch(int s);
static void             __maildircatch_hold(int s);

/* Do some cleanup in the tmp/ subdir */
static void             _cleantmp(void);

static int a_maildir_setfile1(char const *name, enum fedit_mode fm,
      int omsgCount);

static int a_maildir_cmp(void const *a, void const *b);

static int              _maildir_subdir(char const *name, char const *sub,
                           enum fedit_mode fm);

static void             _maildir_append(char const *name, char const *sub,
                           char const *fn);

static boole a_maildir_readin(char const *name, struct message *mp);

static void             maildir_update(void);

static void             _maildir_move(struct n_timespec const *tsp,
                           struct message *m);

static char *           mkname(struct n_timespec const *tsp, enum mflag f,
                           char const *pref);

static enum okay        maildir_append1(struct n_timespec const *tsp,
                           char const *name, FILE *fp, off_t off1,
                           long size, enum mflag flag);

static enum okay        trycreate(char const *name);

static enum okay        mkmaildir(char const *name);

static struct message * mdlook(char const *name, struct message *data);

static void             mktable(void);

static enum okay        subdir_remove(char const *name, char const *sub);

static void
__maildircatch(int s)
{
   NYD; /* Signal handler */
   siglongjmp(_maildir_jmp, s);
}

static void
__maildircatch_hold(int s)
{
   NYD; /* Signal handler */
   UNUSED(s);
   /* TODO no STDIO in signal handler, no _() tr's -- pre-translate interrupt
    * TODO globally; */
   n_err_sighdl(_("\nImportant operation in progress: "
      "interrupt again to forcefully abort\n"));
   safe_signal(SIGINT, &__maildircatch);
}

static void
_cleantmp(void)
{
   struct stat st;
   struct n_string s_b, *s;
   s64 now;
   DIR *dirp;
   struct dirent *dp;
   NYD_IN;

   if ((dirp = opendir("tmp")) == NULL)
      goto jleave;

   now = n_time_now(FAL0)->ts_sec - 36*3600;
   s = n_string_creat_auto(&s_b);

   while ((dp = readdir(dirp)) != NULL) {
      if (dp->d_name[0] == '.')
         continue;

      s = n_string_trunc(s, 0);
      s = n_string_push_buf(s, "tmp/", sizeof("tmp/") -1);
      s = n_string_push_cp(s, dp->d_name);
      if (stat(n_string_cp(s), &st) == -1)
         continue;
      if (st.st_atime <= now)
         unlink(s->s_dat);
   }
   closedir(dirp);
jleave:
   NYD_OU;
}

static int
a_maildir_setfile1(char const *name, enum fedit_mode fm, int omsgCount)
{
   int i;
   NYD_IN;

   if (!(fm & FEDIT_NEWMAIL))
      _cleantmp();

   mb.mb_perm = ((n_poption & n_PO_R_FLAG) || (fm & FEDIT_RDONLY))
         ? 0 : MB_DELE;
   if ((i = _maildir_subdir(name, "cur", fm)) != 0)
      goto jleave;
   if ((i = _maildir_subdir(name, "new", fm)) != 0)
      goto jleave;
   _maildir_append(name, NULL, NULL);

   n_autorec_relax_create();
   for(i = ((fm & FEDIT_NEWMAIL) ? omsgCount : 0); i < msgCount; ++i){
      if(!a_maildir_readin(name, &message[i])){
         i = -1;
         break;
      }
      n_autorec_relax_unroll();
   }
   n_autorec_relax_gut();
   if(i < 0)
      goto jleave;

   if (fm & FEDIT_NEWMAIL) {
      if (msgCount > omsgCount)
         qsort(&message[omsgCount], msgCount - omsgCount, sizeof *message,
            &a_maildir_cmp);
   } else if (msgCount)
      qsort(message, msgCount, sizeof *message, &a_maildir_cmp);
   i = msgCount;
jleave:
   NYD_OU;
   return i;
}

static int
a_maildir_cmp(void const *xa, void const *xb){
   char const *cpa, *cpa_pid, *cpb, *cpb_pid;
   union {struct message const *mp; char const *cp;} a, b;
   s64 at, bt;
   int rv;
   NYD2_IN;

   a.mp = xa;
   b.mp = xb;

   /* We could have parsed the time somewhen in the past, do a quick shot */
   at = (s64)a.mp->m_time;
   bt = (s64)b.mp->m_time;
   if(at != 0 && bt != 0 && (at -= bt) != 0)
      goto jret;

   /* Otherwise we need to parse the name */
   a.cp = &a.mp->m_maildir_file[4];
   b.cp = &b.mp->m_maildir_file[4];

   /* Interpret time stored in name, and use it for comparison */
   if(((su_idec_s64_cp(&at, a.cp, 10, &cpa)
            ) & su_IDEC_STATE_EMASK) != su_IDEC_STATE_EBASE || *cpa != '.' ||
         a.cp == cpa)
      goto jm1; /* Fishy */
   if(((su_idec_s64_cp(&bt, b.cp, 10, &cpb)
            ) & su_IDEC_STATE_EMASK) != su_IDEC_STATE_EBASE || *cpb != '.' ||
         b.cp == cpb)
      goto j1; /* Fishy */

   if((at -= bt) != 0)
      goto jret;

   /* If the seconds part does not work, go deeper.
    * We use de-facto standard "maildir — E-mail directory" from the Courier
    * mail server, also used by, e.g., Dovecot: sec.MusecPpid.hostname:2,flags.
    * However, a different name convention exists which uses
    * sec.pid_counter.hostname:2,flags.
    * First go for usec/counter, then pid */

   /* A: exact "standard"? */
   cpa_pid = NULL;
   a.cp = ++cpa;
   if((rv = *a.cp) == 'M')
      ;
   /* Known compat? */
   else if(su_cs_is_digit(rv)){
      cpa_pid = a.cp++;
      while((rv = *a.cp) != '\0' && rv != '_')
         ++a.cp;
      if(rv == '\0')
         goto jm1; /* Fishy */
   }
   /* This is compatible to what dovecot does, it surely does not do so
    * for nothing, but i have no idea, but am too stupid to ask */
   else for(;; rv = *++a.cp){
      if(rv == 'M')
         break;
      if(rv == '\0' || rv == '.' || rv == n_MAILDIR_SEPARATOR)
         goto jm1; /* Fishy */
   }
   ++a.cp;
   if(((su_idec_s64_cp(&at, a.cp, 10, &cpa)
            ) & su_IDEC_STATE_EMASK) != su_IDEC_STATE_EBASE)
      goto jm1; /* Fishy */

   /* B: as above */
   cpb_pid = NULL;
   b.cp = ++cpb;
   if((rv = *b.cp) == 'M')
      ;
   else if(su_cs_is_digit(rv)){
      cpb_pid = b.cp++;
      while((rv = *b.cp) != '\0' && rv != '_')
         ++b.cp;
      if(rv == '\0')
         goto j1;
   }else for(;; rv = *++b.cp){
      if(rv == 'M')
         break;
      if(rv == '\0' || rv == '.' || rv == n_MAILDIR_SEPARATOR)
         goto jm1;
   }
   ++b.cp;
   if(((su_idec_s64_cp(&bt, b.cp, 10, &cpb)
            ) & su_IDEC_STATE_EMASK) != su_IDEC_STATE_EBASE)
      goto j1;

   if((at -= bt) != 0)
      goto jret;

   /* So this gets hairy: sort by PID, then hostname */
   if(cpa_pid != NULL){
      a.cp = cpa_pid;
      xa = cpa;
   }else{
      a.cp = cpa;
      if(*a.cp++ != 'P')
         goto jm1; /* Fishy */
   }
   if(((su_idec_s64_cp(&at, a.cp, 10, &cpa)
            ) & su_IDEC_STATE_EMASK) != su_IDEC_STATE_EBASE)
      goto jm1; /* Fishy */

   if(cpb_pid != NULL){
      b.cp = cpb_pid;
      xb = cpb;
   }else{
      b.cp = cpb;
      if(*b.cp++ != 'P')
         goto j1; /* Fishy */
   }
   if(((su_idec_s64_cp(&bt, b.cp, 10, &cpb)
            ) & su_IDEC_STATE_EMASK) != su_IDEC_STATE_EBASE)
      goto jm1; /* Fishy */

   if((at -= bt) != 0)
      goto jret;

   /* Hostname */
   a.cp = (cpa_pid != NULL) ? xa : cpa;
   b.cp = (cpb_pid != NULL) ? xb : cpb;
   for(;; ++a.cp, ++b.cp){
      char ac, bc;

      ac = *a.cp;
      at = (ac != '\0' && ac != n_MAILDIR_SEPARATOR);
      bc = *b.cp;
      bt = (bc != '\0' && bc != n_MAILDIR_SEPARATOR);
      if((at -= bt) != 0)
         break;
      at = ac;
      if((at -= bc) != 0)
         break;
      if(ac == '\0')
         break;
   }

jret:
   rv = (at == 0 ? 0 : (at < 0 ? -1 : 1));
jleave:
   NYD2_OU;
   return rv;
jm1:
   rv = -1;
   goto jleave;
j1:
   rv = 1;
   goto jleave;
}

static int
_maildir_subdir(char const *name, char const *sub, enum fedit_mode fm)
{
   DIR *dirp;
   struct dirent *dp;
   int rv;
   NYD_IN;

   if ((dirp = opendir(sub)) == NULL) {
      n_err(_("Cannot open directory %s\n"),
         n_shexp_quote_cp(savecatsep(name, '/', sub), FAL0));
      rv = -1;
      goto jleave;
   }
   if (access(sub, W_OK) == -1)
      mb.mb_perm = 0;
   while ((dp = readdir(dirp)) != NULL) {
      if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' ||
            (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
         continue;
      if (dp->d_name[0] == '.')
         continue;
      if (!(fm & FEDIT_NEWMAIL) || mdlook(dp->d_name, NULL) == NULL)
         _maildir_append(name, sub, dp->d_name);
   }
   closedir(dirp);
   rv = 0;
jleave:
   NYD_OU;
   return rv;
}

static void
_maildir_append(char const *name, char const *sub, char const *fn)
{
   struct message *m;
   time_t t = 0;
   enum mflag f = MUSED | MNOFROM | MNEWEST;
   char const *cp, *xp;
   NYD_IN;
   UNUSED(name);

   if (fn != NULL && sub != NULL) {
      if (!su_cs_cmp(sub, "new"))
         f |= MNEW;

      /* C99 */{
         s64 tib;

         (void)/*TODO*/su_idec_s64_cp(&tib, fn, 10, &xp);
         t = (time_t)tib;
      }

      if ((cp = su_cs_rfind_c(xp, ',')) != NULL && PCMP(cp, >, xp + 2) &&
            cp[-1] == '2' && cp[-2] == n_MAILDIR_SEPARATOR) {
         while (*++cp != '\0') {
            switch (*cp) {
            case 'F':
               f |= MFLAGGED;
               break;
            case 'R':
               f |= MANSWERED;
               break;
            case 'S':
               f |= MREAD;
               break;
            case 'T':
               f |= MDELETED;
               break;
            case 'D':
               f |= MDRAFT;
               break;
            }
         }
      }
   }

   /* Ensure room (and a NULLified last entry) */
   ++msgCount;
   message_append(NULL);
   --msgCount;

   if (fn == NULL || sub == NULL)
      goto jleave;

   m = &message[msgCount++];
   /* C99 */{
      char *tmp;
      uz i, j;

      i = su_cs_len(fn) +1;
      j = su_cs_len(sub);
      m->m_maildir_file = tmp = n_alloc(j + 1 + i);
      su_mem_copy(tmp, sub, j);
      tmp[j++] = '/';
      su_mem_copy(&tmp[j], fn, i);
   }
   m->m_time = t;
   m->m_flag = f;
   m->m_maildir_hash = su_cs_hash(fn);
jleave:
   NYD_OU;
   return;
}

static boole
a_maildir_readin(char const *name, struct message *mp){
   long size, lines;
   boole b;
   char *buf;
   uz cnt, buflen, bufsize;
   off_t offset;
   FILE *fp;
   char const *emsg;
   NYD_IN;

   if((fp = mx_fs_open(mp->m_maildir_file, "r")) == NIL){
      emsg = _("Cannot read %s for message %lu\n");
      goto jerr;
   }

   offset = ftell(mb.mb_otf);
   cnt = fsize(fp);

   mx_fs_linepool_aquire(&buf, &bufsize);
   buflen = 0;
   b = FAL0;
   size = lines = 0;
   while(fgetline(&buf, &bufsize, &cnt, &buflen, fp, 1) != NIL){
      /* Since we simply copy over data without doing any transfer
       * encoding reclassification/adjustment we *have* to perform
       * RFC 4155 compliant From_ quoting here */
      if(b && is_head(buf, buflen, FAL0)){
         putc('>', mb.mb_otf);
         ++size;
      }
      size += fwrite(buf, 1, buflen, mb.mb_otf);
      b = (*buf == '\n');
      ++lines;
   }
   mx_fs_linepool_release(buf, bufsize);

   if(!b){
      /* TODO we need \n\n for mbox format.
       * TODO That is to say we do it wrong here in order to get it right
       * TODO when send.c stuff or with MBOX handling, even though THIS
       * TODO line is solely a property of the MBOX database format! */
      putc('\n', mb.mb_otf);
      ++lines;
      ++size;
   }
   b = (fflush(mb.mb_otf) != EOF);

   mx_fs_close(fp);

   if(!b){
      emsg = _("I/O error reading %s for message %lu\n");
      goto jleave;
   }

   mp->m_size = mp->m_xsize = size;
   mp->m_lines = mp->m_xlines = lines;
   mp->m_block = mailx_blockof(offset);
   mp->m_offset = mailx_offsetof(offset);
   substdate(mp);
jleave:
   NYD_OU;
   return b;

jerr:
   n_err(emsg,
      n_shexp_quote_cp(savecatsep(name, '/', mp->m_maildir_file), FAL0),
      S(ul,P2UZ(mp - message + 1)));
   b = FAL0;
   goto jleave;
}

static void
maildir_update(void)
{
   struct message *m;
   struct n_timespec const *tsp;
   int dodel, c, gotcha = 0, held = 0, modflags = 0;
   NYD_IN;

   if (mb.mb_perm == 0)
      goto jfree;

   if (!(n_pstate & n_PS_EDIT)) {
      holdbits();
      for (m = message, c = 0; PCMP(m, <, message + msgCount); ++m) {
         if (m->m_flag & MBOX)
            c++;
      }
      if (c > 0)
         if (makembox() == STOP)
            goto jbypass;
   }

   tsp = n_time_now(TRU1); /* TODO FAL0, eventloop update! */

   n_autorec_relax_create();
   for (m = message, gotcha = 0, held = 0; PCMP(m, <, message + msgCount);
         ++m) {
      if (n_pstate & n_PS_EDIT)
         dodel = m->m_flag & MDELETED;
      else
         dodel = !((m->m_flag & MPRESERVE) || !(m->m_flag & MTOUCH));
      if (dodel) {
         if (unlink(m->m_maildir_file) < 0)
            n_err(_("Cannot delete file %s for message %lu\n"),
               n_shexp_quote_cp(savecatsep(mailname, '/', m->m_maildir_file),
                  FAL0), (ul)P2UZ(m - message + 1));
         else
            ++gotcha;
      } else {
         if ((m->m_flag & (MREAD | MSTATUS)) == (MREAD | MSTATUS) ||
               (m->m_flag & (MNEW | MBOXED | MSAVED | MSTATUS | MFLAG |
               MUNFLAG | MANSWER | MUNANSWER | MDRAFT | MUNDRAFT))) {
            _maildir_move(tsp, m);
            n_autorec_relax_unroll();
            ++modflags;
         }
         ++held;
      }
   }
   n_autorec_relax_gut();

jbypass:
   if ((gotcha || modflags) && (n_pstate & n_PS_EDIT)) {
      fprintf(n_stdout, "%s %s\n",
         n_shexp_quote_cp(displayname, FAL0),
         ((ok_blook(bsdcompat) || ok_blook(bsdmsgs))
          ? _("complete") : _("updated.")));
   } else if (held && !(n_pstate & n_PS_EDIT) && mb.mb_perm != 0) {
      if (held == 1)
         fprintf(n_stdout, _("Held 1 message in %s\n"), displayname);
      else
         fprintf(n_stdout, _("Held %d messages in %s\n"), held, displayname);
   }
   fflush(n_stdout);
jfree:
   for (m = message; PCMP(m, <, message + msgCount); ++m)
      n_free(n_UNCONST(m->m_maildir_file));
   NYD_OU;
}

static void
_maildir_move(struct n_timespec const *tsp, struct message *m)
{
   char *fn, *newfn;
   NYD_IN;

   fn = mkname(tsp, m->m_flag, m->m_maildir_file + 4);
   newfn = savecat("cur/", fn);
   if (!su_cs_cmp(m->m_maildir_file, newfn))
      goto jleave;
   if (link(m->m_maildir_file, newfn) == -1) {
      n_err(_("Cannot link %s to %s: message %lu not touched\n"),
         n_shexp_quote_cp(savecatsep(mailname, '/', m->m_maildir_file), FAL0),
         n_shexp_quote_cp(savecatsep(mailname, '/', newfn), FAL0),
         (ul)P2UZ(m - message + 1));
      goto jleave;
   }
   if (unlink(m->m_maildir_file) == -1)
      n_err(_("Cannot unlink %s\n"),
         n_shexp_quote_cp(savecatsep(mailname, '/', m->m_maildir_file), FAL0));
jleave:
   NYD_OU;
}

static char *
mkname(struct n_timespec const *tsp, enum mflag f, char const *pref)
{
   static char *node;
   static struct n_timespec ts;

   char *cp;
   int size, n, i;
   NYD_IN;

   if (pref == NULL) {
      s64 s;

      if(n_pid == 0)
         n_pid = getpid();

      if (node == NULL) {
         cp = n_nodename(FAL0);
         n = size = 0;
         do {
            if (UCMP(32, n, <, size + 8))
               node = n_realloc(node, size += 20);
            switch (*cp) {
            case '/':
               node[n++] = '\\', node[n++] = '0',
               node[n++] = '5', node[n++] = '7';
               break;
            case ':':
               node[n++] = '\\', node[n++] = '0',
               node[n++] = '7', node[n++] = '2';
               break;
            default:
               node[n++] = *cp;
            }
         } while (*cp++ != '\0');
      }

      /* Problem: Courier spec uses microseconds, not nanoseconds */
      if((s = tsp->ts_sec) > ts.ts_sec){
         ts.ts_sec = s;
         ts.ts_nsec = tsp->ts_nsec / (n_DATE_NANOSSEC / n_DATE_MICROSSEC);
      }else{
         s = tsp->ts_nsec / (n_DATE_NANOSSEC / n_DATE_MICROSSEC);
         if(s <= ts.ts_nsec)
            s = ts.ts_nsec + 1;
         if(s < n_DATE_MICROSSEC)
            ts.ts_nsec = s;
         else{
            ++ts.ts_sec;
            ts.ts_nsec = 0;
         }
      }

      /* Create a name according to Courier spec */
      size = 60 + su_cs_len(node);
      cp = n_autorec_alloc(size);
      n = snprintf(cp, size, "%" PRId64 ".M%" PRIdZ "P%ld.%s:2,",
            ts.ts_sec, ts.ts_nsec, (long)n_pid, node);
   } else {
      size = (n = su_cs_len(pref)) + 13;
      cp = n_autorec_alloc(size);
      su_mem_copy(cp, pref, n +1);
      for (i = n; i > 3; --i)
         if (cp[i - 1] == ',' && cp[i - 2] == '2' &&
               cp[i - 3] == n_MAILDIR_SEPARATOR) {
            n = i;
            break;
         }
      if (i <= 3) {
         su_mem_copy(cp + n, ":2,", 3 +1);
         n += 3;
      }
   }
   if (n < size - 7) {
      if (f & MDRAFTED)
         cp[n++] = 'D';
      if (f & MFLAGGED)
         cp[n++] = 'F';
      if (f & MANSWERED)
         cp[n++] = 'R';
      if (f & MREAD)
         cp[n++] = 'S';
      if (f & MDELETED)
         cp[n++] = 'T';
      cp[n] = '\0';
   }
   NYD_OU;
   return cp;
}

static enum okay
maildir_append1(struct n_timespec const *tsp, char const *name, FILE *fp,
   off_t off1, long size, enum mflag flag)
{
   char buf[4096], *fn, *tfn, *nfn;
   struct stat st;
   FILE *op;
   uz nlen, flen, n;
   enum okay rv = STOP;
   NYD_IN;

   nlen = su_cs_len(name);

   /* Create a unique temporary file */
   for (nfn = (char*)0xA /* XXX no magic */;; n_msleep(500, FAL0)) {
      flen = su_cs_len(fn = mkname(tsp, flag, NULL));
      tfn = n_autorec_alloc(n = nlen + flen + 6);
      snprintf(tfn, n, "%s/tmp/%s", name, fn);

      /* Use "wx" for O_EXCL XXX stat(2) rather redundant; coverity:TOCTOU */
      if((!stat(tfn, &st) || su_err_no() == su_ERR_NOENT) &&
            (op = mx_fs_open(tfn, "wx")) != NIL)
         break;

      nfn = (char*)(P2UZ(nfn) - 1);
      if (nfn == NULL) {
         n_err(_("Can't create an unique file name in %s\n"),
            n_shexp_quote_cp(savecat(name, "/tmp"), FAL0));
         goto jleave;
      }
   }

   if (fseek(fp, off1, SEEK_SET) == -1)
      goto jtmperr;
   while (size > 0) {
      uz z = UCMP(z, size, >, sizeof buf) ? sizeof buf : S(uz,size);

      if (z != (n = fread(buf, 1, z, fp)) || n != fwrite(buf, 1, n, op)) {
jtmperr:
         n_err(_("Error writing to %s\n"), n_shexp_quote_cp(tfn, FAL0));
         mx_fs_close(op);
         goto jerr;
      }
      size -= n;
   }
   mx_fs_close(op);

   nfn = n_autorec_alloc(n = nlen + flen + 6);
   snprintf(nfn, n, "%s/new/%s", name, fn);
   if (link(tfn, nfn) == -1) {
      n_err(_("Cannot link %s to %s\n"), n_shexp_quote_cp(tfn, FAL0),
         n_shexp_quote_cp(nfn, FAL0));
      goto jerr;
   }
   rv = OKAY;
jerr:
   if (unlink(tfn) == -1)
      n_err(_("Cannot unlink %s\n"), n_shexp_quote_cp(tfn, FAL0));
jleave:
   NYD_OU;
   return rv;
}

static enum okay
trycreate(char const *name)
{
   struct stat st;
   enum okay rv = STOP;
   NYD_IN;

   if (!stat(name, &st)) {
      if (!S_ISDIR(st.st_mode)) {
         n_err(_("%s is not a directory\n"), n_shexp_quote_cp(name, FAL0));
         goto jleave;
      }
   } else if (!n_path_mkdir(name)) {
      n_err(_("Cannot create directory %s\n"), n_shexp_quote_cp(name, FAL0));
      goto jleave;
   }
   rv = OKAY;
jleave:
   NYD_OU;
   return rv;
}

static enum okay
mkmaildir(char const *name) /* TODO proper cleanup on error; use path[] loop */
{
   char *np;
   uz i;
   enum okay rv = STOP;
   NYD_IN;

   if (trycreate(name) == OKAY) {
      np = n_lofi_alloc((i = su_cs_len(name)) + 4 +1);
      su_mem_copy(np, name, i);
      su_mem_copy(&np[i], "/tmp", 4 +1);
      if (trycreate(np) == OKAY) {
         su_mem_copy(&np[i], "/new", 4);
         if (trycreate(np) == OKAY) {
            su_mem_copy(&np[i], "/cur", 4);
            rv = trycreate(np);
         }
      }
      n_lofi_free(np);
   }
   NYD_OU;
   return rv;
}

static struct message *
mdlook(char const *name, struct message *data)
{
   struct message **mpp, *mp;
   u32 h, i;
   NYD_IN;

   if(data != NULL)
      i = data->m_maildir_hash;
   else
      i = su_cs_hash(name);
   h = i;
   mpp = &a_maildir_tbl[i %= a_maildir_tbl_prime];

   for(i = 0;;){
      if((mp = *mpp) == NULL){
         if(UNLIKELY(data != NULL)){
            *mpp = mp = data;
            if(i > a_maildir_tbl_maxdist)
               a_maildir_tbl_maxdist = i;
         }
         break;
      }else if(mp->m_maildir_hash == h &&
            !su_cs_cmp(&mp->m_maildir_file[4], name))
         break;

      if(UNLIKELY(mpp++ == a_maildir_tbl_top))
         mpp = a_maildir_tbl;
      if(++i > a_maildir_tbl_maxdist && UNLIKELY(data == NULL)){
         mp = NULL;
         break;
      }
   }
   NYD_OU;
   return mp;
}

static void
mktable(void)
{
   struct message *mp;
   uz i;
   NYD_IN;

   i = a_maildir_tbl_prime = msgCount;
   i <<= 1;
   do
      a_maildir_tbl_prime = su_prime_lookup_next(a_maildir_tbl_prime);
   while(a_maildir_tbl_prime < i);
   a_maildir_tbl = n_calloc(a_maildir_tbl_prime, sizeof *a_maildir_tbl);
   a_maildir_tbl_top = &a_maildir_tbl[a_maildir_tbl_prime - 1];
   a_maildir_tbl_maxdist = 0;
   for(mp = message, i = msgCount; i-- != 0; ++mp)
      mdlook(&mp->m_maildir_file[4], mp);
   NYD_OU;
}

static enum okay
subdir_remove(char const *name, char const *sub)
{
   char *path;
   int pathsize, pathend, namelen, sublen, n;
   DIR *dirp;
   struct dirent *dp;
   enum okay rv = STOP;
   NYD_IN;

   namelen = su_cs_len(name);
   sublen = su_cs_len(sub);
   path = n_alloc(pathsize = namelen + sublen + 30 +1);
   su_mem_copy(path, name, namelen);
   path[namelen] = '/';
   su_mem_copy(path + namelen + 1, sub, sublen);
   path[namelen + sublen + 1] = '/';
   path[pathend = namelen + sublen + 2] = '\0';

   if ((dirp = opendir(path)) == NULL) {
      n_perr(path, 0);
      goto jleave;
   }
   while ((dp = readdir(dirp)) != NULL) {
      if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' ||
            (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
         continue;
      if (dp->d_name[0] == '.')
         continue;
      n = su_cs_len(dp->d_name);
      if (UCMP(32, pathend + n +1, >, pathsize))
         path = n_realloc(path, pathsize = pathend + n + 30);
      su_mem_copy(path + pathend, dp->d_name, n +1);
      if (unlink(path) == -1) {
         n_perr(path, 0);
         closedir(dirp);
         goto jleave;
      }
   }
   closedir(dirp);

   path[pathend] = '\0';
   if (rmdir(path) == -1) {
      n_perr(path, 0);
      goto jleave;
   }
   rv = OKAY;
jleave:
   n_free(path);
   NYD_OU;
   return rv;
}

FL int
maildir_setfile(char const *who, char const * volatile name,
   enum fedit_mode fm)
{
   n_sighdl_t volatile saveint;
   struct cw cw;
   char const *emsg;
   int omsgCount;
   int volatile i = -1;
   NYD_IN;

   omsgCount = msgCount;
   if (cwget(&cw) == STOP) {
      n_alert(_("Cannot open current directory"));
      goto jleave;
   }

   if (!(fm & FEDIT_NEWMAIL) && !quit(FAL0))
      goto jleave;

   saveint = safe_signal(SIGINT, SIG_IGN);

   if (!(fm & FEDIT_NEWMAIL)) {
      if (fm & FEDIT_SYSBOX)
         n_pstate &= ~n_PS_EDIT;
      else
         n_pstate |= n_PS_EDIT;
      if (mb.mb_itf) {
         fclose(mb.mb_itf);
         mb.mb_itf = NULL;
      }
      if (mb.mb_otf) {
         fclose(mb.mb_otf);
         mb.mb_otf = NULL;
      }
      initbox(name);
      mb.mb_type = MB_MAILDIR;
   }

   if(!n_is_dir(name, FAL0)){
      emsg = N_("Not a maildir: %s\n");
      goto jerr;
   }else if(chdir(name) < 0){
      emsg = N_("Cannot enter maildir://%s\n");
jerr:
      n_err(V_(emsg), n_shexp_quote_cp(name, FAL0));
      mb.mb_type = MB_VOID;
      *mailname = '\0';
      msgCount = 0;
      cwrelse(&cw);
      safe_signal(SIGINT, saveint);
      goto jleave;
   }

   a_maildir_tbl = NULL;
   if (sigsetjmp(_maildir_jmp, 1) == 0) {
      if (fm & FEDIT_NEWMAIL)
         mktable();
      if (saveint != SIG_IGN)
         safe_signal(SIGINT, &__maildircatch);
      if(a_maildir_setfile1(name, fm, omsgCount) < 0){
         if((fm & FEDIT_NEWMAIL) && a_maildir_tbl != NIL)
            n_free(a_maildir_tbl);
         emsg = N_("Cannot setup maildir://%s\n");
         goto jerr;
      }
   }
   if ((fm & FEDIT_NEWMAIL) && a_maildir_tbl != NULL)
      n_free(a_maildir_tbl);

   safe_signal(SIGINT, saveint);

   if (cwret(&cw) == STOP)
      n_panic(_("Cannot change back to current directory"));
   cwrelse(&cw);

   setmsize(msgCount);
   if ((fm & FEDIT_NEWMAIL) && mb.mb_sorted && msgCount > omsgCount) {
      mb.mb_threaded = 0;
      c_sort((void*)-1);
   }

   if (!(fm & FEDIT_NEWMAIL)) {
      n_pstate &= ~n_PS_SAW_COMMAND;
      n_pstate |= n_PS_SETFILE_OPENED;
   }

   if ((n_poption & n_PO_EXISTONLY) && !(n_poption & n_PO_HEADERLIST)) {
      i = (msgCount == 0);
      goto jleave;
   }

   if (!(fm & FEDIT_NEWMAIL) && (fm & FEDIT_SYSBOX) && msgCount == 0) {
      if (mb.mb_type == MB_MAILDIR /* XXX ?? */ && !ok_blook(emptystart))
         n_err(_("No mail for %s at %s\n"), who, n_shexp_quote_cp(name, FAL0));
      i = 1;
      goto jleave;
   }

   if ((fm & FEDIT_NEWMAIL) && msgCount > omsgCount)
      newmailinfo(omsgCount);
   i = 0;
jleave:
   NYD_OU;
   return i;
}

FL boole
maildir_quit(boole hold_sigs_on)
{
   n_sighdl_t saveint;
   struct cw cw;
   boole rv;
   NYD_IN;

   if(hold_sigs_on)
      rele_sigs();

   rv = FAL0;

   if (cwget(&cw) == STOP) {
      n_alert(_("Cannot open current directory"));
      goto jleave;
   }

   saveint = safe_signal(SIGINT, SIG_IGN);

   if (chdir(mailname) == -1) {
      n_err(_("Cannot change directory to %s\n"),
         n_shexp_quote_cp(mailname, FAL0));
      cwrelse(&cw);
      safe_signal(SIGINT, saveint);
      goto jleave;
   }

   if (sigsetjmp(_maildir_jmp, 1) == 0) {
      if (saveint != SIG_IGN)
         safe_signal(SIGINT, &__maildircatch_hold);
      maildir_update();
   }

   safe_signal(SIGINT, saveint);

   if (cwret(&cw) == STOP)
      n_panic(_("Cannot change back to current directory"));
   cwrelse(&cw);
   rv = TRU1;
jleave:
   if(hold_sigs_on)
      hold_sigs();
   NYD_OU;
   return rv;
}

FL enum okay
maildir_append(char const *name, FILE *fp, long offset)
{
   struct n_timespec const *tsp;
   char *buf, *bp, *lp;
   uz bufsize, buflen, cnt;
   off_t off1 = -1, offs;
   long size;
   int flag;
   enum {_NONE = 0, _INHEAD = 1<<0, _NLSEP = 1<<1} state;
   enum okay rv;
   NYD_IN;

   if ((rv = mkmaildir(name)) != OKAY)
      goto jleave;

   mx_fs_linepool_aquire(&buf, &bufsize);
   buflen = 0;
   cnt = fsize(fp);
   offs = offset /* BSD will move due to O_APPEND! ftell(fp) */;
   size = 0;
   tsp = n_time_now(TRU1); /* TODO -> eventloop */

   n_autorec_relax_create();
   for (flag = MNEW, state = _NLSEP;;) {
      bp = fgetline(&buf, &bufsize, &cnt, &buflen, fp, 1);

      if (bp == NULL ||
            ((state & (_INHEAD | _NLSEP)) == _NLSEP &&
             is_head(buf, buflen, FAL0))) {
         if (off1 != (off_t)-1) {
            if ((rv = maildir_append1(tsp, name, fp, off1, size, flag)) == STOP)
               goto jfree;
            n_autorec_relax_unroll();
            if (fseek(fp, offs + buflen, SEEK_SET) == -1) {
               rv = STOP;
               goto jfree;
            }
         }
         off1 = offs + buflen;
         size = 0;
         state = _INHEAD;
         flag = MNEW;

         if (bp == NULL)
            break;
      } else
         size += buflen;
      offs += buflen;

      state &= ~_NLSEP;
      if (buf[0] == '\n') {
         state &= ~_INHEAD;
         state |= _NLSEP;
      } else if (state & _INHEAD) {
         if (!su_cs_cmp_case_n(buf, "status", 6)) {
            lp = buf + 6;
            while (su_cs_is_white(*lp))
               ++lp;
            if (*lp == ':')
               while (*++lp != '\0')
                  switch (*lp) {
                  case 'R':
                     flag |= MREAD;
                     break;
                  case 'O':
                     flag &= ~MNEW;
                     break;
                  }
         } else if (!su_cs_cmp_case_n(buf, "x-status", 8)) {
            lp = buf + 8;
            while (su_cs_is_white(*lp))
               ++lp;
            if (*lp == ':') {
               while (*++lp != '\0')
                  switch (*lp) {
                  case 'F':
                     flag |= MFLAGGED;
                     break;
                  case 'A':
                     flag |= MANSWERED;
                     break;
                  case 'T':
                     flag |= MDRAFTED;
                     break;
                  }
            }
         }
      }
   }

   ASSERT(rv == OKAY);
jfree:
   n_autorec_relax_gut();
   mx_fs_linepool_release(buf, bufsize);
jleave:
   NYD_OU;
   return rv;
}

FL enum okay
maildir_remove(char const *name)
{
   enum okay rv = STOP;
   NYD_IN;

   if (subdir_remove(name, "tmp") == STOP ||
         subdir_remove(name, "new") == STOP ||
         subdir_remove(name, "cur") == STOP)
      goto jleave;
   if (rmdir(name) == -1) {
      n_perr(name, 0);
      goto jleave;
   }
   rv = OKAY;
jleave:
   NYD_OU;
   return rv;
}

#include "su/code-ou.h"
#endif /* mx_HAVE_MAILDIR */
/* s-it-mode */
s-nail-14.9.15/src/mx/main.c000066400000000000000000001205411352610246600153710ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ Startup and initialization.
 *@ This file is also used to materialize externals.
 *
 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: BSD-3-Clause TODO ISC
 */
/*
 * Copyright (c) 1980, 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.
 */
#undef su_FILE
#define su_FILE main
#define mx_SOURCE
#define mx_SOURCE_MASTER

#include "mx/nail.h"

#include 

#include 
#include 
#include 

#include "mx/iconv.h"
#include "mx/names.h"
#include "mx/sigs.h"
#include "mx/termcap.h"
#include "mx/termios.h"
#include "mx/tty.h"
#include "mx/ui-str.h"

/* TODO fake */
#include "su/code-in.h"

struct a_arg{
   struct a_arg *aa_next;
   char const *aa_file;
};

/* (extern, but not with amalgamation, so define here) */
VL char const n_weekday_names[7 + 1][4] = {
   "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", ""
};
VL char const n_month_names[12 + 1][4] = {
   "Jan", "Feb", "Mar", "Apr", "May", "Jun",
   "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ""
};
VL char const n_uagent[sizeof VAL_UAGENT] = VAL_UAGENT;
#ifdef mx_HAVE_UISTRINGS
VL char const n_error[sizeof n_ERROR] = N_(n_ERROR);
#endif
VL char const n_path_devnull[sizeof n_PATH_DEVNULL] = n_PATH_DEVNULL;
VL char const n_0[2] = "0";
VL char const n_1[2] = "1";
VL char const n_m1[3] = "-1";
VL char const n_em[2] = "!";
VL char const n_ns[2] = "#";
VL char const n_star[2] = "*";
VL char const n_hy[2] = "-";
VL char const n_qm[2] = "?";
VL char const n_at[2] = "@";

/* Perform basic startup initialization */
static void a_main_startup(void);

/* Grow a char** */
static uz a_main_grow_cpp(char const ***cpp, uz newsize, uz oldcnt);

/* Setup some variables which we require to be valid / verified */
static void a_main_setup_vars(void);

/* Ok, we are reading mail.  Decide whether we are editing a mailbox or reading
 * the system mailbox, and open up the right stuff */
static int a_main_rcv_mode(boole had_A_arg, char const *folder,
            char const *Larg, char const **Yargs, uz Yargs_cnt);

/* Interrupt printing of the headers */
static void a_main_hdrstop(int signo);

/* */
static void a_main_usage(FILE *fp);
static boole a_main_dump_doc(up cookie, boole has_arg, char const *sopt,
      char const *lopt, char const *doc);

static void
a_main_startup(void){
   struct passwd *pwuid;
   char *cp;
   NYD2_IN;

   n_stdin = stdin;
   n_stdout = stdout;
   n_stderr = stderr;
   dflpipe = SIG_DFL;

   if((cp = su_cs_rfind_c(su_program, '/')) != NULL)
      su_program = ++cp;
   /* XXX Due to n_err() mess the su_log config only applies to EMERG yet! */
   su_state_set(su_STATE_LOG_SHOW_LEVEL | su_STATE_LOG_SHOW_PID
         /* XXX | su_STATE_ERR_NOMEM | su_STATE_ERR_OVERFLOW */
   );
   su_log_set_level(n_LOG_LEVEL); /* XXX _EMERG is 0.. */

   /* Set up a reasonable environment */

   /* TODO This is wrong: interactive is STDIN/STDERR for a POSIX sh(1).
    * TODO For now we get this wrong, all over the place, as this software
    * TODO has always been developed with stdout as an output channel.
    * TODO Start doing it right for at least explicit terminal-related things,
    * TODO but v15 should use ONLY this, also for terminal input! */
   if(isatty(STDIN_FILENO)){
      n_psonce |= n_PSO_TTYIN;
#ifdef mx_HAVE_MLE
      if((mx_tty_fp = fdopen(fileno(n_stdin), "w+")) != NIL)
         setvbuf(mx_tty_fp, NULL, _IOLBF, 0);
#endif
   }

   if(isatty(STDOUT_FILENO))
      n_psonce |= n_PSO_TTYOUT;
   /* STDOUT is always line buffered from our point of view */
   setvbuf(n_stdout, NULL, _IOLBF, 0);

   /* Assume we are interactive, then.
    * This state will become unset later for n_PO_QUICKRUN_MASK! */
   if((n_psonce & n_PSO_TTYANY) == n_PSO_TTYANY)
      n_psonce |= n_PSO_INTERACTIVE;

   if(mx_tty_fp == NIL)
      mx_tty_fp = (n_psonce & n_PSO_TTYIN) ? n_stdin : n_stdout;

   if(isatty(STDERR_FILENO))
      n_psonce |= n_PSO_TTYERR;

   /* Now that the basic I/O is accessible, initialize our main machinery,
    * input, loop, child, termios, whatever */
   n_go_init();

   if(n_psonce & n_PSO_INTERACTIVE)
      safe_signal(SIGPIPE, dflpipe = SIG_IGN);

#if su_DVLOR(1, 0)
   safe_signal(SIGABRT, &mx__nyd_oncrash);
# ifdef SIGBUS
   safe_signal(SIGBUS, &mx__nyd_oncrash);
# endif
   safe_signal(SIGFPE, &mx__nyd_oncrash);
   safe_signal(SIGILL, &mx__nyd_oncrash);
   safe_signal(SIGSEGV, &mx__nyd_oncrash);
#endif

   /*  --  >8  --  8<  --  */

   n_locale_init();

#ifdef mx_HAVE_ICONV
   iconvd = (iconv_t)-1;
#endif

   /*
    * Ensure some variables get loaded and/or verified, I. (pre-getopt)
    */

   /* Detect, verify and fixate our invoking user (environment) */
   n_group_id = getgid();
   if((pwuid = getpwuid(n_user_id = getuid())) == NULL)
      n_panic(_("Cannot associate a name with uid %lu"), (ul)n_user_id);
   else{
      char const *ep;
      boole doenv;

      if(!(doenv = (ep = ok_vlook(LOGNAME)) == NULL) &&
            (doenv = (su_cs_cmp(pwuid->pw_name, ep) != 0)))
         n_err(_("Warning: $LOGNAME (%s) not identical to user (%s)!\n"),
            ep, pwuid->pw_name);
      if(doenv){
         n_pstate |= n_PS_ROOT;
         ok_vset(LOGNAME, pwuid->pw_name);
         n_pstate &= ~n_PS_ROOT;
      }

      /* BSD compat */
      if((ep = ok_vlook(USER)) != NULL && su_cs_cmp(pwuid->pw_name, ep)){
         n_err(_("Warning: $USER (%s) not identical to user (%s)!\n"),
            ep, pwuid->pw_name);
         n_pstate |= n_PS_ROOT;
         ok_vset(USER, pwuid->pw_name);
         n_pstate &= ~n_PS_ROOT;
      }

      /* XXX myfullname = pw->pw_gecos[OPTIONAL!] -> GUT THAT; TODO pw_shell */
   }

   /* This is not automated just as $TMPDIR is for the initial setting, since
    * we have the pwuid at hand and can simply use it!  See accmacvar.c! */
   if(n_user_id == 0 || (cp = ok_vlook(HOME)) == NULL){
      cp = pwuid->pw_dir;
      n_pstate |= n_PS_ROOT;
      ok_vset(HOME, cp);
      n_pstate &= ~n_PS_ROOT;
   }

   (void)ok_blook(POSIXLY_CORRECT);
   NYD2_OU;
}

static uz
a_main_grow_cpp(char const ***cpp, uz newsize, uz oldcnt){
   /* Just use auto-reclaimed storage, it will be preserved */
   char const **newcpp;
   NYD2_IN;

   newcpp = n_autorec_alloc(sizeof(char*) * (newsize + 1));

   if(oldcnt > 0)
      su_mem_copy(newcpp, *cpp, oldcnt * sizeof(char*));
   *cpp = newcpp;
   NYD2_OU;
   return newsize;
}

static void
a_main_setup_vars(void){
   char const *cp;
   NYD2_IN;

   /*
    * Ensure some variables get loaded and/or verified, II. (post getopt).
    */

   /* Do not honour TMPDIR if root */
   if(n_user_id == 0)
      ok_vset(TMPDIR, NULL);
   else
      (void)ok_vlook(TMPDIR);

   /* Are we in a reproducible-builds.org environment?
    * That special mode bends some settings (again) */
   if(ok_vlook(SOURCE_DATE_EPOCH) != NULL){
      su_state_set(su_STATE_REPRODUCIBLE);
      su_program = su_reproducible_build;
      n_pstate |= n_PS_ROOT;
      ok_vset(LOGNAME, su_reproducible_build);
      /* Do not care about USER at all in this special mode! */
      n_pstate &= ~n_PS_ROOT;
      cp = savecat(su_reproducible_build, ": ");
      ok_vset(log_prefix, cp);
   }


   /* Finally set our terminal dimension */
   mx_termios_controller_setup(mx_TERMIOS_SETUP_TERMSIZE);

   NYD2_OU;
}

static sigjmp_buf a_main__hdrjmp; /* XXX */

static int
a_main_rcv_mode(boole had_A_arg, char const *folder, char const *Larg,
      char const **Yargs, uz Yargs_cnt){
   /* XXX a_main_rcv_mode(): use argument carrier */
   n_sighdl_t prevint;
   int i;
   NYD_IN;

   i = had_A_arg ? FEDIT_ACCOUNT : FEDIT_NONE;
   if(n_poption & n_PO_QUICKRUN_MASK)
      i |= FEDIT_RDONLY;

   if(folder == NULL){
      folder = "%";
      if(had_A_arg)
         i |= FEDIT_SYSBOX;
   }
#ifdef mx_HAVE_IMAP
   else if(*folder == '@'){
      /* This must be treated specially to make possible invocation like
       * -A imap -f @mailbox */
      char const *cp;

      cp = n_folder_query();
      if(which_protocol(cp, FAL0, FAL0, NULL) == PROTO_IMAP)
         su_cs_pcopy_n(mailname, cp, sizeof mailname);
   }
#endif

   i = setfile(folder, i);
   if(i < 0){
      n_exit_status = n_EXIT_ERR; /* error already reported */
      goto jquit;
   }
   temporary_folder_hook_check(FAL0);
   if(n_poption & n_PO_QUICKRUN_MASK){
      n_exit_status = i;
      if(i == n_EXIT_OK && (!(n_poption & n_PO_EXISTONLY) ||
            (n_poption & n_PO_HEADERLIST)))
         print_header_summary(Larg);
      goto jquit;
   }

   if(i > 0 && !ok_blook(emptystart)){
      n_exit_status = n_EXIT_ERR;
      goto jleave;
   }

   if(sigsetjmp(a_main__hdrjmp, 1) == 0){
      if((prevint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
         safe_signal(SIGINT, &a_main_hdrstop);
      if(!ok_blook(quiet))
         fprintf(n_stdout, _("%s version %s.  Type `?' for help\n"),
            n_uagent,
            (su_state_has(su_STATE_REPRODUCIBLE)
               ? su_reproducible_build : ok_vlook(version)));
      n_folder_announce(n_ANNOUNCE_MAIN_CALL | n_ANNOUNCE_CHANGE);
      safe_signal(SIGINT, prevint);
   }

   /* Enter the command loop */
   if(n_psonce & n_PSO_INTERACTIVE)
      mx_tty_init();
   /* "load()" more commands given on command line */
   if(Yargs_cnt > 0 && !n_go_XYargs(TRU1, Yargs, Yargs_cnt))
      n_exit_status = n_EXIT_ERR;
   else
      n_go_main_loop();
   if(n_psonce & n_PSO_INTERACTIVE)
      mx_tty_destroy((n_psonce & n_PSO_XIT) != 0);

   if(!(n_psonce & n_PSO_XIT)){
      if(mb.mb_type == MB_FILE || mb.mb_type == MB_MAILDIR){
         safe_signal(SIGHUP, SIG_IGN);
         safe_signal(SIGINT, SIG_IGN);
         safe_signal(SIGQUIT, SIG_IGN);
      }
jquit:
      save_mbox_for_possible_quitstuff();
      quit(FAL0);
   }
jleave:
   NYD_OU;
   return n_exit_status;
}

static void
a_main_hdrstop(int signo){
   NYD; /* Signal handler */
   UNUSED(signo);

   fflush(n_stdout);
   n_err_sighdl(_("\nInterrupt\n"));
   siglongjmp(a_main__hdrjmp, 1);
}

static void
a_main_usage(FILE *fp){
   /* Stay in VAL_HEIGHT lines; On buf length change: verify visual output! */
   char buf[7];
   uz i;
   NYD2_IN;

   i = su_cs_len(su_program);
   i = MIN(i, sizeof(buf) -1);
   if(i > 0)
      su_mem_set(buf, ' ', i);
   buf[i] = '\0';

   fprintf(fp, _("%s (%s %s): send and receive Internet mail\n"),
      su_program, n_uagent, ok_vlook(version));
   if(fp != n_stderr)
      putc('\n', fp);

   fprintf(fp, _(
      "Send-only mode: send mail \"to-addr\"(ess) receiver(s):\n"
      "  %s [-DdEFinv~#] [-: spec] [-A account] [:-C \"field: body\":]\n"
      "  %s [:-a attachment:] [:-b bcc-addr:] [:-c cc-addr:]\n"
      "  %s [-M type | -m file | -q file | -t] [-r from-addr] "
         "[:-S var[=value]:]\n"
      "  %s [-s subject] [-T \"arget: addr\"] [:-X/Y cmd:] [-.] :to-addr:\n"),
      su_program, buf, buf, buf);
   if(fp != n_stderr)
      putc('\n', fp);

   fprintf(fp, _(
      "\"Receive\" mode, starting on [-u user], primary *inbox* or [$MAIL]:\n"
      "  %s [-DdEeHiNnRv~#] [-: spec] [-A account] [:-C \"field: body\":]\n"
      "  %s [-L spec] [-r from-addr] [:-S var[=value]:] [-u user] "
         "[:-X/Y cmd:]\n"),
      su_program, buf);
   if(fp != n_stderr)
      putc('\n', fp);

   fprintf(fp, _(
      "\"Receive\" mode, starting on -f (secondary $MBOX or [file]):\n"
      "  %s [-DdEeHiNnRv~#] [-: spec] [-A account] [:-C \"field: body\":] -f\n"
      "  %s [-L spec] [-r from-addr] [:-S var[=value]:] [:-X/Y cmd:] "
         "[file]\n"),
      su_program, buf);
   if(fp != n_stderr)
      putc('\n', fp);

   /* (ISO C89 string length) */
   fprintf(fp, _(
         ". -d sandbox, -:/ no .rc files, -. end options and force send-mode\n"
         ". -a attachment[=input-charset[#output-charset]]\n"
         ". -[bcrT], to-addr: ex@am.ple or '(Lovely) Ex '\n"
         ". -[Mmqt]: special input data (-t: template message on stdin)\n"
         ". -e only mail check, -H header summary; "
            "both: message specification via -L\n"
         ". -S (un)sets variable, -X/-Y execute commands pre/post startup, "
            "-#: batch mode\n"));
   fprintf(fp, _(
         ". Features via \"$ %s -Xversion -Xx\"; there is --long-help\n"
         ". Bugs/Contact via "
            "\"$ %s -Sexpandaddr=shquote '\\$contact-mail'\"\n"),
         su_program, su_program);
   NYD2_OU;
}

static boole
a_main_dump_doc(up cookie, boole has_arg, char const *sopt, char const *lopt,
      char const *doc){
   char const *x1, *x2;
   NYD2_IN;

   if(has_arg)
      /* I18N: describing arguments to command line options */
      x1 = (sopt[0] != '\0' ? _(" ARG, ") : sopt), x2 = _("=ARG");
   else
      /* I18N: separating command line options */
      x1 = (sopt[0] != '\0' ? _(", ") : sopt), x2 = su_empty;
   /* I18N: short option, "[ ARG], " separator, long option [=ARG], doc */
   fprintf(S(FILE*,cookie), _("%s%s%s%s: %s\n"), sopt, x1, lopt, x2, doc);
   NYD2_OU;
   return TRU1;
}

int
main(int argc, char *argv[]){
   /* TODO Once v15 control flow/carrier rewrite took place main() should
    * TODO be rewritten and option parsing++ should be outsourced.
    * TODO Like so we can get rid of some stack locals etc.
    * TODO Furthermore: the locals should be in a carrier, and once there
    * TODO is the memory pool+page cache, that should go in LOFI memory,
    * TODO and there should be two pools: one which is fixated() and remains,
    * TODO and one with throw away data (-X, -Y args, temporary allocs, e.g.,
    * TODO redo -S like so, etc.) */
   /* Keep in SYNC: ./nail.1:"SYNOPSIS, main() */
   static char const a_sopts[] =
         "::A:a:Bb:C:c:DdEeFfHhiL:M:m:NnO:q:Rr:S:s:T:tu:VvX:Y:~#.";
   static char const * const a_lopts[] = {
      "resource-files:;:;" N_("control loading of resource files"),
      "account:;A;" N_("execute an `account command'"),
         "attach:;a;" N_("attach a file to message to be sent"),
      "bcc:;b;" N_("add blind carbon copy recipient"),
      "custom-header:;C;" N_("create custom header (\"header-field: body\")"),
         "cc:;c;" N_("add carbon copy recipient"),
      "disconnected;D;" N_("identical to -Sdisconnected"),
         "debug;d;" N_("identical to -Sdebug"),
      "discard-empty-messages;E;" N_("identical to -Sskipemptybody"),
         "check-and-exit;e;" N_("note mail presence (of -L) via exit status"),
      "file;f;" N_("open secondary mailbox (or \"file\" last on command line"),
      "header-summary;H;" N_("is to be displayed (for given file) only"),
         "help;h;" N_("short help"),
      "header-search:;L;" N_("like -H (or -e) for the given \"spec\" only"),
      "no-header-summary;N;" N_("identical to -Snoheader"),
      "quote-file:;q;" N_("initialize body of message to be sent with a file"),
      "read-only;R;" N_("any mailbox file will be opened read-only"),
         "from-address:;r;" N_("set source address used by MTAs (+ -Sfrom)"),
      "set:;S;" N_("set one of the INTERNAL VARIABLES (unset via \"noARG\")"),
         "subject:;s;" N_("specify subject of message to be sent"),
      "target:;T;" N_("add receiver(s) \"header-field: address\" as via -t"),
      "template;t;" N_("message to be sent is read from standard input"),
      "inbox-of:;u;" N_("initially open primary mailbox of the given user"),
      "version;V;" N_("print version (more so with \"[-v] -Xversion -Xx\")"),
         "verbose;v;" N_("equals -Sverbose (multiply for more verbosity)"),
      "startup-cmd:;X;" N_("to be executed before normal operation"),
      "cmd:;Y;" N_("to be executed under normal operation (is \"input\")"),
      "enable-cmd-escapes;~;" N_("even in non-interactive compose mode"),
      "batch-mode;#;" N_("more confined non-interactive setup"),
      "end-options;.;" N_("force the end of options, and send mode"),
      "long-help;\201;" N_("this listing"),
      NULL
   };
   struct su_avopt avo;
   int i;
   char *cp;
   uz Xargs_size, Xargs_cnt, Yargs_size, Yargs_cnt, smopts_size;
   char const *Aarg, *emsg, *folder, *Larg, *okey, *qf,
      *subject, *uarg, **Xargs, **Yargs;
   struct attachment *attach;
   struct mx_name *to, *cc, *bcc;
   struct a_arg *a_head, *a_curr;
   enum{
      a_RF_NONE = 0,
      a_RF_SET = 1<<0,
      a_RF_SYSTEM = 1<<1,
      a_RF_USER = 1<<2,
      a_RF_ALL = a_RF_SYSTEM | a_RF_USER
   } resfiles;
   NYD_IN;

   a_head = NULL;
   UNINIT(a_curr, NULL);
   to = cc = bcc = NULL;
   attach = NULL;
   Aarg = emsg = folder = Larg = okey = qf = subject = uarg = NULL;
   Xargs = Yargs = NULL;
   Xargs_size = Xargs_cnt = Yargs_size = Yargs_cnt = smopts_size = 0;
   resfiles = a_RF_ALL;

   /*
    * Start our lengthy setup, finalize by setting n_PSO_STARTED
    */

   su_program = argv[0];
   a_main_startup();

   /* Command line parsing.
    * XXX We could parse silently to grasp the actual mode (send, receive
    * XXX with/out -f, then use an according option array.  This would ease
    * XXX the interdependency checking necessities! */
   su_avopt_setup(&avo, --argc, su_C(char const*const*,++argv),
      a_sopts, a_lopts);
   while((i = su_avopt_parse(&avo)) != su_AVOPT_STATE_DONE){
      switch(i){
      case 'A':
         /* Execute an account command later on */
         Aarg = avo.avo_current_arg;
         break;
      case 'a':{
         /* Add an attachment */
         struct a_arg *nap;

         n_psonce |= n_PSO_SENDMODE;
         nap = n_autorec_alloc(sizeof(struct a_arg));
         if(a_head == NULL)
            a_head = nap;
         else
            a_curr->aa_next = nap;
         nap->aa_next = NULL;
         nap->aa_file = avo.avo_current_arg;
         a_curr = nap;
         }break;
      case 'B':
         n_OBSOLETE(_("-B is obsolete, please use -# as necessary"));
         break;
      case 'b':
         /* Add (a) blind carbon copy recipient (list) */
         n_psonce |= n_PSO_SENDMODE;
         bcc = cat(bcc, lextract(avo.avo_current_arg,
               GBCC | GFULL | GNOT_A_LIST | GSHEXP_PARSE_HACK));
         break;
      case 'C':{
         /* Create custom header (at list tail) */
         struct n_header_field **hflpp;

         if(*(hflpp = &n_poption_arg_C) != NIL){
            while((*hflpp)->hf_next != NIL)
               hflpp = &(*hflpp)->hf_next;
            hflpp = &(*hflpp)->hf_next;
         }
         if(!n_header_add_custom(hflpp, avo.avo_current_arg, FAL0)){
            emsg = N_("Invalid custom header data with -C");
            goto jusage;
         }
         }break;
      case 'c':
         /* Add (a) carbon copy recipient (list) */
         n_psonce |= n_PSO_SENDMODE;
         cc = cat(cc, lextract(avo.avo_current_arg,
               GCC | GFULL | GNOT_A_LIST | GSHEXP_PARSE_HACK));
         break;
      case 'D':
#ifdef mx_HAVE_IMAP
         ok_bset(disconnected);
#endif
         break;
      case 'd':
         ok_bset(debug);
         break;
      case 'E':
         ok_bset(skipemptybody);
         break;
      case 'e':
         /* Check if mail (matching -L) exists in given box, exit status */
         n_poption |= n_PO_EXISTONLY;
         n_psonce &= ~n_PSO_INTERACTIVE;
         break;
      case 'F':
         /* Save msg in file named after local part of first recipient */
         n_poption |= n_PO_F_FLAG;
         n_psonce |= n_PSO_SENDMODE;
         break;
      case 'f':
         /* User is specifying file to "edit" with Mail, as opposed to reading
          * system mailbox.  If no argument is given, we read his mbox file.
          * Check for remaining arguments later */
         folder = "&";
         break;
      case 'H':
         /* Display summary of headers, exit */
         n_poption |= n_PO_HEADERSONLY;
         n_psonce &= ~n_PSO_INTERACTIVE;
         break;
      case 'h':
      case (char)(su_u8)'\201':
         a_main_usage(n_stdout);
         if(i != 'h'){
            fprintf(n_stdout, "\nLong options:\n");
            (void)su_avopt_dump_doc(&avo, &a_main_dump_doc,
               su_S(su_up,n_stdout));
         }
         goto jleave;
      case 'i':
         /* Ignore interrupts */
         ok_bset(ignore);
         break;
      case 'L':
         /* Display summary of headers which match given spec, exit.
          * In conjunction with -e, only test the given spec for existence */
         n_poption |= n_PO_HEADERLIST;
         n_psonce &= ~n_PSO_INTERACTIVE;
         Larg = avo.avo_current_arg;
         if(*Larg == '"' || *Larg == '\''){ /* TODO list.c:listspec_check() */
            uz j;

            j = su_cs_len(++Larg);
            if(j > 0){
               cp = savestrbuf(Larg, --j);
               Larg = cp;
            }
         }
         break;
      case 'M':
         /* Flag message body (standard input) with given MIME type */
         if(qf != NULL && (!(n_poption & n_PO_Mm_FLAG) || qf != (char*)-1))
            goto jeMmq;
         n_poption_arg_Mm = avo.avo_current_arg;
         qf = (char*)-1;
         if(0){
            /* FALLTHRU*/
      case 'm':
            /* Flag the given file with MIME type and use as message body */
            if(qf != NULL && (!(n_poption & n_PO_Mm_FLAG) || qf == (char*)-1))
               goto jeMmq;
            qf = avo.avo_current_arg;
         }
         n_poption |= n_PO_Mm_FLAG;
         n_psonce |= n_PSO_SENDMODE;
         break;
      case 'N':
         /* Avoid initial header printing */
         ok_bclear(header);
         break;
      case 'n':
         /* Don't source "unspecified system start-up file" */
         if(resfiles & a_RF_SET){
            emsg = N_("-n cannot be used in conjunction with -:");
            goto jusage;
         }
         resfiles = a_RF_USER;
         break;
      case 'O':
         /* Additional options to pass-through to MTA TODO v15-compat legacy */
         if(n_smopts_cnt == smopts_size)
            smopts_size = a_main_grow_cpp(&n_smopts, smopts_size + 8,
                  n_smopts_cnt);
         n_smopts[n_smopts_cnt++] = avo.avo_current_arg;
         break;
      case 'q':
         /* "Quote" file: use as message body (-t without headers etc.) */
         /* XXX Traditional.  Add -Q to initialize as *quote*d content? */
         if(qf != NULL && (n_poption & n_PO_Mm_FLAG)){
jeMmq:
            emsg = N_("Only one of -M, -m or -q may be given");
            goto jusage;
         }
         n_psonce |= n_PSO_SENDMODE;
         /* Allow, we have to special check validity of -q- later on! */
         qf = (avo.avo_current_arg[0] == '-' && avo.avo_current_arg[1] == '\0')
               ? (char*)-1 : avo.avo_current_arg;
         break;
      case 'R':
         /* Open folders read-only */
         n_poption |= n_PO_R_FLAG;
         break;
      case 'r':
         /* Set From address. */
         n_poption |= n_PO_r_FLAG;
         if(avo.avo_current_arg[0] == '\0')
            break;
         else{
            struct mx_name *fa;

            fa = nalloc(avo.avo_current_arg, GSKIN | GFULL | GFULLEXTRA |
                  GNOT_A_LIST | GNULL_OK | GSHEXP_PARSE_HACK);
            if(fa == NULL || is_addr_invalid(fa, EACM_STRICT | EACM_NOLOG)){
               emsg = N_("Invalid address argument with -r");
               goto jusage;
            }
            n_poption_arg_r = fa;
            /* TODO -r options is set in n_smopts, but may
             * TODO be overwritten by setting from= in
             * TODO an interactive session!
             * TODO Maybe disable setting of from?
             * TODO Warn user?  Update manual!! */
            avo.avo_current_arg = savecat("from=", fa->n_fullname);
         }
         /* FALLTHRU */
      case 'S':
         {  struct str sin;
            struct n_string s_b, *s;
            char const *a[2];
            boole b;

            if(i != 'S' || ok_vlook(v15_compat) == su_NIL){
               okey = a[0] = avo.avo_current_arg;
               s = NIL;
            }else{
               enum n_shexp_state shs;

               n_autorec_relax_create();
               s = n_string_creat_auto(&s_b);
               sin.s = n_UNCONST(avo.avo_current_arg);
               sin.l = UZ_MAX;
               shs = n_shexp_parse_token((n_SHEXP_PARSE_LOG |
                     n_SHEXP_PARSE_IGNORE_EMPTY |
                     n_SHEXP_PARSE_QUOTE_AUTO_FIXED |
                     n_SHEXP_PARSE_QUOTE_AUTO_DSQ), s, &sin, NULL);
               if((shs & n_SHEXP_STATE_ERR_MASK) ||
                     !(shs & n_SHEXP_STATE_STOP)){
                  n_autorec_relax_gut();
                  goto je_S;
               }
               okey = a[0] = n_string_cp_const(s);
            }

            a[1] = NIL;
            n_poption |= n_PO_S_FLAG_TEMPORARY;
            n_pstate |= n_PS_ROBOT;
            b = (c_set(a) == 0);
            n_pstate &= ~n_PS_ROBOT;
            n_poption &= ~n_PO_S_FLAG_TEMPORARY;

            if(s != NIL)
               n_autorec_relax_gut();
            if(!b && (ok_blook(errexit) || ok_blook(posix))){
je_S:
               emsg = N_("-S failed to set variable");
               goto jusage;
            }
         }
         break;
      case 's':
         /* Subject:; take care for Debian #419840 and strip any \r and \n */
         if(su_cs_first_of(subject = avo.avo_current_arg, "\n\r"
               ) != su_UZ_MAX){
            n_err(_("-s: normalizing away invalid ASCII NL / CR bytes\n"));
            for(subject = cp = savestr(avo.avo_current_arg); *cp != '\0'; ++cp)
               if(*cp == '\n' || *cp == '\r')
                  *cp = ' ';
         }
         n_psonce |= n_PSO_SENDMODE;
         break;

      case 'T':{
         /* Target mode: `digmsg header insert' from command line */
         struct str suffix;
         struct mx_name **npp, *np;
         enum gfield gf;
         char const *a;

         if((a = n_header_get_field(avo.avo_current_arg, "to", &suffix)
               ) != su_NIL){
            gf = GTO | GSHEXP_PARSE_HACK | GFULL | GNULL_OK;
            npp = &to;
         }else if((a = n_header_get_field(avo.avo_current_arg, "cc", &suffix)
               ) != su_NIL){
            gf = GCC | GSHEXP_PARSE_HACK | GFULL | GNULL_OK;
            npp = &cc;
         }else if((a = n_header_get_field(avo.avo_current_arg, "bcc", &suffix)
               ) != su_NIL){
            gf = GBCC | GSHEXP_PARSE_HACK | GFULL | GNULL_OK;
            npp = &cc;
         }else if((a = n_header_get_field(avo.avo_current_arg, "fcc", su_NIL)
               ) != su_NIL){
            gf = GBCC_IS_FCC;
            npp = &bcc;
         }else{
            ASSERT(suffix.s == su_NIL);
jeTuse:
            emsg = N_("-T: only supports to,cc,bcc (with ?single modifier) "
                  "and fcc");
            goto jusage;
         }

         if(suffix.s != su_NIL){
            if(suffix.l > 0 &&
                  !su_cs_starts_with_case_n("single", suffix.s, suffix.l))
               goto jeTuse;
            gf |= GNOT_A_LIST;
         }

         if(!(gf & GBCC_IS_FCC))
            np = lextract(a, gf);
         else
            np = nalloc_fcc(a);
         if(np == su_NIL){
            emsg = N_("-T: invalid receiver (address)");
            goto jusage;
         }
         *npp = cat(*npp, np);
         }break;

      case 't':
         /* Use the given message as send template */
         n_poption |= n_PO_t_FLAG;
         n_psonce |= n_PSO_SENDMODE;
         break;
      case 'u':
         /* Open primary mailbox of the given user */
         uarg = savecat("%", avo.avo_current_arg);
         break;
      case 'V':{
         struct n_string s;

         fputs(n_string_cp_const(n_version(
            n_string_book(n_string_creat_auto(&s), 120))), n_stdout);
         n_exit_status = n_EXIT_OK;
         }goto jleave;
      case 'v':
         /* Be verbose */
         ok_bset(verbose);
         break;
      case 'X':
         /* Add to list of commands to exec before entering normal operation */
         if(Xargs_cnt == Xargs_size)
            Xargs_size = a_main_grow_cpp(&Xargs, Xargs_size + 8, Xargs_cnt);
         Xargs[Xargs_cnt++] = avo.avo_current_arg;
         break;
      case 'Y':
         /* Add to list of commands to exec after entering normal operation */
         if(Yargs_cnt == Yargs_size)
            Yargs_size = a_main_grow_cpp(&Yargs, Yargs_size + 8, Yargs_cnt);
         Yargs[Yargs_cnt++] = avo.avo_current_arg;
         break;
      case ':':
         /* Control which resource files shall be loaded */
         if(!(resfiles & (a_RF_SET | a_RF_SYSTEM))){
            emsg = N_("-n cannot be used in conjunction with -:");
            goto jusage;
         }
         resfiles = a_RF_SET;
         while((i = *avo.avo_current_arg++) != '\0')
            switch(i){
            case 'S': case 's': resfiles |= a_RF_SYSTEM; break;
            case 'U': case 'u': resfiles |= a_RF_USER; break;
            case '-': case '/': resfiles &= ~a_RF_ALL; break;
            default:
               emsg = N_("Invalid argument of -:");
               goto jusage;
            }
         break;
      case '~':
         /* Enable command escapes even in non-interactive mode */
         n_poption |= n_PO_TILDE_FLAG;
         break;
      case '#':
         /* Work in batch mode, even if non-interactive */
         if(!(n_psonce & n_PSO_INTERACTIVE))
            setvbuf(n_stdin, NULL, _IOLBF, 0);
         n_poption |= n_PO_TILDE_FLAG | n_PO_BATCH_FLAG;
         folder = n_path_devnull;
         n_var_setup_batch_mode();
         break;
      case '.':
         /* Enforce send mode */
         n_psonce |= n_PSO_SENDMODE;
         goto jgetopt_done;
      case su_AVOPT_STATE_ERR_ARG:
         emsg = su_avopt_fmt_err_arg;
         if(0){
            /* FALLTHRU */
      case su_AVOPT_STATE_ERR_OPT:
            emsg = su_avopt_fmt_err_opt;
         }
         n_err(emsg, avo.avo_current_err_opt);
         if(0){
jusage:
            if(emsg != NULL)
               n_err("%s\n", V_(emsg));
         }
         a_main_usage(n_stderr);
         n_exit_status = n_EXIT_USE;
         goto jleave;
      }
   }
jgetopt_done:
   ;

   /* The normal arguments may be followed by MTA arguments after a "--";
    * however, -f may take off an argument, too, and before that.
    * Since MTA arguments after "--" require *expandargv*, delay parsing off
    * those options until after the resource files are loaded... */
   argc = avo.avo_argc;
   argv = su_C(char**,avo.avo_argv);
   if((cp = argv[i = 0]) == NULL)
      ;
   else if(cp[0] == '-' && cp[1] == '-' && cp[2] == '\0')
      ++i;
   /* n_PO_BATCH_FLAG sets to /dev/null, but -f can still be used and sets & */
   else if(folder != NULL && /*folder[0] == '&' &&*/ folder[1] == '\0'){
      folder = cp;
      if((cp = argv[++i]) != NULL){
         if(cp[0] != '-' || cp[1] != '-' || cp[2] != '\0'){
            emsg = N_("More than one file given with -f");
            goto jusage;
         }
         ++i;
      }
   }else{
      n_psonce |= n_PSO_SENDMODE;
      for(;;){
         to = cat(to, lextract(cp, GTO | GFULL | GNOT_A_LIST |
               GSHEXP_PARSE_HACK));
         if((cp = argv[++i]) == NULL)
            break;
         if(cp[0] == '-' && cp[1] == '-' && cp[2] == '\0'){
            ++i;
            break;
         }
      }
   }
   argc = i;

   /* ...BUT, since we use n_autorec_alloc() for the MTA n_smopts storage we
    * need to allocate the space for them before we fixate that storage! */
   while(argv[i] != NULL)
      ++i;
   if(n_smopts_cnt + i > smopts_size)
      su_DBG(smopts_size =)
      a_main_grow_cpp(&n_smopts, n_smopts_cnt + i + 1, n_smopts_cnt);

   /* Check for inconsistent arguments, fix some temporaries */
   if(n_psonce & n_PSO_SENDMODE){
      /* XXX This is only because BATCH_FLAG sets *folder*=/dev/null
       * XXX in order to function.  Ideally that would not be needed */
      if(folder != NULL && !(n_poption & n_PO_BATCH_FLAG)){
         emsg = N_("Cannot give -f and people to send to.");
         goto jusage;
      }
      if(uarg != NULL){
         emsg = N_("The -u option cannot be used in send mode");
         goto jusage;
      }
      if(!(n_poption & n_PO_t_FLAG) && to == NULL){
         emsg = N_("Send options without primary recipient specified.");
         goto jusage;
      }
      if((n_poption & n_PO_t_FLAG) && qf != NULL){
         emsg = N_("The -M, -m, -q and -t options are mutual exclusive.");
         goto jusage;
      }
      if(n_poption & (n_PO_EXISTONLY | n_PO_HEADERSONLY | n_PO_HEADERLIST)){
         emsg = N_("The -e, -H and -L options cannot be used in send mode.");
         goto jusage;
      }
      if(n_poption & n_PO_R_FLAG){
         emsg = N_("The -R option is meaningless in send mode.");
         goto jusage;
      }

      if(n_psonce & n_PSO_INTERACTIVE){
         if(qf == (char*)-1){
            if(!(n_poption & n_PO_Mm_FLAG))
               emsg = N_("-q can't use standard input when interactive.\n");
            goto jusage;
         }
      }
   }else{
      if(uarg != NULL && folder != NULL){
         emsg = N_("The options -u and -f (and -#) are mutually exclusive");
         goto jusage;
      }
      if((n_poption & (n_PO_EXISTONLY | n_PO_HEADERSONLY)) ==
            (n_PO_EXISTONLY | n_PO_HEADERSONLY)){
         emsg = N_("The options -e and -H are mutual exclusive");
         goto jusage;
      }
      if((n_poption & (n_PO_HEADERSONLY | n_PO_HEADERLIST) /* TODO OBSOLETE */
            ) == (n_PO_HEADERSONLY | n_PO_HEADERLIST))
         n_OBSOLETE(_("please use \"-e -L xy\" instead of \"-H -L xy\""));

      if(uarg != NULL)
         folder = uarg;
   }

   /*
    * We have reached our second program state, the command line options have
    * been worked and verified a bit, we are likely to go, perform more setup
    */
   n_psonce |= n_PSO_STARTED_GETOPT;

   a_main_setup_vars();

   /* Create memory pool snapshot; Memory is auto-reclaimed from now on */
   su_mem_bag_fixate(n_go_data->gdc_membag);

   /* load() any resource files */
   if(resfiles & a_RF_ALL){
      /* *expand() returns a savestr(), but load() only uses the file name
       * for fopen(), so it is safe to do this */
      if(resfiles & a_RF_SYSTEM){
         boole nload;

         if((nload = ok_blook(NAIL_NO_SYSTEM_RC)))
            n_OBSOLETE(_("Please use $MAILX_NO_SYSTEM_RC instead of "
               "$NAIL_NO_SYSTEM_RC"));
         if(!nload && !ok_blook(MAILX_NO_SYSTEM_RC) &&
               !n_go_load(ok_vlook(system_mailrc)))
            goto jleave;
      }

      if((resfiles & a_RF_USER) &&
            !n_go_load(fexpand(ok_vlook(MAILRC), FEXP_LOCAL | FEXP_NOPROTO)))
         goto jleave;

      if((cp = ok_vlook(NAIL_EXTRA_RC)) != NULL)
         n_OBSOLETE(_("Please use *mailx-extra-rc*, not *NAIL_EXTRA_RC*"));
      if((cp != NULL || (cp = ok_vlook(mailx_extra_rc)) != NULL) &&
            !n_go_load(fexpand(cp, FEXP_LOCAL | FEXP_NOPROTO)))
         goto jleave;
   }

   /* Cause possible umask(2) to be applied, now that any setting is
    * established, and before we change accounts, evaluate commands etc. */
   (void)ok_vlook(umask);

   /* Additional options to pass-through to MTA, and allowed to do so? */
   i = argc;
   if((cp = ok_vlook(expandargv)) != NULL){
      boole isfail, isrestrict;

      isfail = !su_cs_cmp_case(cp, "fail");
      isrestrict = (!isfail && !su_cs_cmp_case(cp, "restrict"));

      if((n_poption & n_PO_D_V) && !isfail && !isrestrict && *cp != '\0')
         n_err(_("Unknown *expandargv* value: %s\n"), cp);

      if((cp = argv[i]) != NULL){
         if(isfail || (isrestrict && (!(n_poption & n_PO_TILDE_FLAG) ||
                  !(n_psonce & n_PSO_INTERACTIVE)))){
je_expandargv:
            n_err(_("*expandargv* doesn't allow MTA arguments; consider "
               "using *mta-arguments*\n"));
            n_exit_status = n_EXIT_USE | n_EXIT_SEND_ERROR;
            goto jleave;
         }
         do{
            ASSERT(n_smopts_cnt + 1 <= smopts_size);
            n_smopts[n_smopts_cnt++] = cp;
         }while((cp = argv[++i]) != NULL);
      }
   }else if(argv[i] != NULL)
      goto je_expandargv;

   /* We had to wait until the resource files are loaded and any command line
    * setting has been restored, but get the termcap up and going before we
    * switch account or running commands */
#ifdef mx_HAVE_TCAP
   if(n_psonce & n_PSO_TTYANY)
      mx_termcap_init();
#endif

   /* Now we can set the account */
   if(Aarg != NULL){
      char const *a[2];

      a[0] = Aarg;
      a[1] = NULL;
      if(c_account(a) && (!(n_psonce & n_PSO_INTERACTIVE) ||
            ok_blook(errexit) || ok_blook(posix))){
         n_exit_status = n_EXIT_USE | n_EXIT_SEND_ERROR;
         goto jleave;
      }
   }

   /*
    * Almost setup, only -X commands are missing!
    */
   n_psonce |= n_PSO_STARTED_CONFIG;

   /* "load()" commands given on command line */
   if(Xargs_cnt > 0 && !n_go_XYargs(FAL0, Xargs, Xargs_cnt))
      goto jleave_full;

   /* Final tests */
   if(n_poption & n_PO_Mm_FLAG){
      if(qf == (char*)-1){
         if(!n_mimetype_check_mtname(n_poption_arg_Mm)){
            n_err(_("Could not find `mimetype' for -M argument: %s\n"),
               n_poption_arg_Mm);
            n_exit_status = n_EXIT_ERR;
            goto jleave_full;
         }
      }else if(/* XXX only to satisfy Coverity! */qf != NULL &&
            (n_poption_arg_Mm = n_mimetype_classify_filename(qf)) == NULL){
         n_err(_("Could not `mimetype'-classify -m argument: %s\n"),
            n_shexp_quote_cp(qf, FAL0));
         n_exit_status = n_EXIT_ERR;
         goto jleave_full;
      }else if(!su_cs_cmp_case(n_poption_arg_Mm, "text/plain")) /* TODO magic*/
         n_poption_arg_Mm = NULL;
   }

   /*
    * We're finally completely setup and ready to go!
    */
   n_psonce |= n_PSO_STARTED;

   /* TODO v15compat */
   if((n_poption & n_PO_D_V) && ok_vlook(v15_compat) == NIL)
      n_err("Warning -- v15-compat=yes will be default in v14.10.0!\n");

   if(!(n_psonce & n_PSO_SENDMODE))
      n_exit_status = a_main_rcv_mode((Aarg != NULL), folder, Larg,
            Yargs, Yargs_cnt);
   else{
      /* Now that full mailx(1)-style file expansion is possible handle the
       * attachments which we had delayed due to this.
       * This may use savestr(), but since we won't enter the command loop we
       * don't need to care about that */
      for(; a_head != NULL; a_head = a_head->aa_next){
         enum n_attach_error aerr;

         attach = n_attachment_append(attach, a_head->aa_file, &aerr, NULL);
         if(aerr != n_ATTACH_ERR_NONE){
            n_exit_status = n_EXIT_ERR;
            goto jleave_full;
         }
      }

      if(n_psonce & n_PSO_INTERACTIVE)
         mx_tty_init();
      /* "load()" more commands given on command line */
      if(Yargs_cnt > 0 && !n_go_XYargs(TRU1, Yargs, Yargs_cnt))
         n_exit_status = n_EXIT_ERR;
      else
         n_mail((((n_psonce & n_PSO_INTERACTIVE
                  ) ? n_MAILSEND_HEADERS_PRINT : 0) |
               (n_poption & n_PO_F_FLAG ? n_MAILSEND_RECORD_RECIPIENT : 0)),
            to, cc, bcc, subject, attach, qf);
      if(n_psonce & n_PSO_INTERACTIVE)
         mx_tty_destroy((n_psonce & n_PSO_XIT) != 0);
   }

jleave_full:
   i = n_exit_status;

   n_psonce &= ~n_PSO_EXIT_MASK;
   mx_account_leave();

   n_psonce &= ~n_PSO_EXIT_MASK;
   temporary_on_xy_hook_caller("on-program-exit", ok_vlook(on_program_exit),
      FAL0);

   n_exit_status = i;

jleave:
#ifdef su_HAVE_DEBUG
   su_mem_bag_gut(n_go_data->gdc_membag); /* Was init in go_init() */
   su_mem_set_conf(su_MEM_CONF_LINGER_FREE_RELEASE, 0);
#endif
   NYD_OU;
   return n_exit_status;
}

#include "su/code-ou.h"

/* Source the others in that case! */
#ifdef mx_HAVE_AMALGAMATION
# include 
#endif

/* s-it-mode */
s-nail-14.9.15/src/mx/message.c000066400000000000000000001346071352610246600161010ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ Message, message array, n_getmsglist(), and related operations.
 *
 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: BSD-3-Clause
 */
/*
 * Copyright (c) 1980, 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.
 */
#undef su_FILE
#define su_FILE message
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

#include 
#include 

#include "mx/cmd-mlist.h"
#include "mx/file-streams.h"
#include "mx/names.h"
#include "mx/net-pop3.h"
#include "mx/termios.h"

/* TODO fake */
#include "su/code-in.h"

/* Token values returned by the scanner used for argument lists.
 * Also, sizes of scanner-related things */
enum a_msg_token{
   a_MSG_T_EOL,      /* End of the command line */
   a_MSG_T_NUMBER,   /* Message number */
   a_MSG_T_MINUS,    /* - */
   a_MSG_T_STRING,   /* A string (possibly containing -) */
   a_MSG_T_DOT,      /* . */
   a_MSG_T_UP,       /* ^ */
   a_MSG_T_DOLLAR,   /* $ */
   a_MSG_T_ASTER,    /* * */
   a_MSG_T_OPEN,     /* ( */
   a_MSG_T_CLOSE,    /* ) */
   a_MSG_T_PLUS,     /* + */
   a_MSG_T_COMMA,    /* , */
   a_MSG_T_SEMI,     /* ; */
   a_MSG_T_BACK,     /* ` */
   a_MSG_T_ERROR     /* Lexical error */
};

enum a_msg_idfield{
   a_MSG_ID_REFERENCES,
   a_MSG_ID_IN_REPLY_TO
};

enum a_msg_state{
   a_MSG_S_NEW = 1u<<0,
   a_MSG_S_OLD = 1u<<1,
   a_MSG_S_UNREAD = 1u<<2,
   a_MSG_S_DELETED = 1u<<3,
   a_MSG_S_READ = 1u<<4,
   a_MSG_S_FLAG = 1u<<5,
   a_MSG_S_ANSWERED = 1u<<6,
   a_MSG_S_DRAFT = 1u<<7,
   a_MSG_S_SPAM = 1u<<8,
   a_MSG_S_SPAMUNSURE = 1u<<9,
   a_MSG_S_MLIST = 1u<<10,
   a_MSG_S_MLSUBSCRIBE = 1u<<11
};

struct a_msg_coltab{
   char mco_char; /* What to find past : */
   u8 mco__dummy[3];
   int mco_bit;   /* Associated modifier bit */
   int mco_mask;  /* m_status bits to mask */
   int mco_equal; /* ... must equal this */
};

struct a_msg_lex{
   char ml_char;
   u8 ml_token;
};

struct a_msg_speclex{
   char *msl_str;             /* If parsed a string */
   int msl_no;                /* If parsed a number TODO sz! */
   char msl__smallstrbuf[4];
   /* We directly adjust pointer in .ca_arg.ca_str.s, do not adjust .l */
   struct n_cmd_arg *msl_cap;
   char const *msl_input_orig;
};

static struct a_msg_coltab const a_msg_coltabs[] = {
   {'n', {0,}, a_MSG_S_NEW, MNEW, MNEW},
   {'o', {0,}, a_MSG_S_OLD, MNEW, 0},
   {'u', {0,}, a_MSG_S_UNREAD, MREAD, 0},
   {'d', {0,}, a_MSG_S_DELETED, MDELETED, MDELETED},
   {'r', {0,}, a_MSG_S_READ, MREAD, MREAD},
   {'f', {0,}, a_MSG_S_FLAG, MFLAGGED, MFLAGGED},
   {'a', {0,}, a_MSG_S_ANSWERED, MANSWERED, MANSWERED},
   {'t', {0,}, a_MSG_S_DRAFT, MDRAFTED, MDRAFTED},
   {'s', {0,}, a_MSG_S_SPAM, MSPAM, MSPAM},
   {'S', {0,}, a_MSG_S_SPAMUNSURE, MSPAMUNSURE, MSPAMUNSURE},
   /* These have no per-message flags, but must be evaluated */
   {'l', {0,}, a_MSG_S_MLIST, 0, 0},
   {'L', {0,}, a_MSG_S_MLSUBSCRIBE, 0, 0},
};

static struct a_msg_lex const a_msg_singles[] = {
   {'$', a_MSG_T_DOLLAR},
   {'.', a_MSG_T_DOT},
   {'^', a_MSG_T_UP},
   {'*', a_MSG_T_ASTER},
   {'-', a_MSG_T_MINUS},
   {'+', a_MSG_T_PLUS},
   {'(', a_MSG_T_OPEN},
   {')', a_MSG_T_CLOSE},
   {',', a_MSG_T_COMMA},
   {';', a_MSG_T_SEMI},
   {'`', a_MSG_T_BACK}
};

/* Slots in ::message */
static uz a_msg_mem_space;

/* Mark entire threads */
static boole a_msg_threadflag;

/* :d on its way HACK TODO */
static boole a_msg_list_saw_d, a_msg_list_last_saw_d;

/* Lazy load message header fields */
static enum okay a_msg_get_header(struct message *mp);

/* Append, taking care of resizes TODO vector */
static char **a_msg_add_to_nmadat(char ***nmadat, uz *nmasize,
               char **np, char *string);

/* Mark all messages that the user wanted from the command line in the message
 * structure.  Return 0 on success, -1 on error */
static int a_msg_markall(char const *orig, struct n_cmd_arg *cap, int f);

/* Turn the character after a colon modifier into a bit value */
static int a_msg_evalcol(int col);

/* Check the passed message number for legality and proper flags.  Unless f is
 * MDELETED the message has to be undeleted */
static boole a_msg_check(int mno, int f);

/* Scan out a single lexical item and return its token number, updating *mslp */
static int a_msg_scan(struct a_msg_speclex *mslp);

/* See if the passed name sent the passed message */
static boole a_msg_match_sender(struct message *mp, char const *str,
               boole allnet);

/* Check whether the given message-id or references match */
static boole a_msg_match_mid(struct message *mp, char const *id,
               enum a_msg_idfield idfield);

/* See if the given string matches.
 * For the purpose of the scan, we ignore case differences.
 * This is the engine behind the "/" search */
static boole a_msg_match_dash(struct message *mp, char const *str);

/* See if the given search expression matches.
 * For the purpose of the scan, we ignore case differences.
 * This is the engine behind the "@[..@].." search */
static boole a_msg_match_at(struct message *mp, struct search_expr *sep);

/* Unmark the named message */
static void a_msg_unmark(int mesg);

/* Return the message number corresponding to the passed meta character */
static int a_msg_metamess(int meta, int f);

/* Helper for mark(): self valid, threading enabled */
static void a_msg__threadmark(struct message *self, int f);

static enum okay
a_msg_get_header(struct message *mp){
   enum okay rv;
   NYD2_IN;
   UNUSED(mp);

   switch(mb.mb_type){
   case MB_FILE:
   case MB_MAILDIR:
      rv = OKAY;
      break;
#ifdef mx_HAVE_POP3
   case MB_POP3:
      rv = mx_pop3_header(mp);
      break;
#endif
#ifdef mx_HAVE_IMAP
   case MB_IMAP:
   case MB_CACHE:
      rv = imap_header(mp);
      break;
#endif
   case MB_VOID:
   default:
      rv = STOP;
      break;
   }
   NYD2_OU;
   return rv;
}

static char **
a_msg_add_to_nmadat(char ***nmadat, uz *nmasize, /* TODO Vector */
      char **np, char *string){
   uz idx, i;
   NYD2_IN;

   if((idx = P2UZ(np - *nmadat)) >= *nmasize){
      char **narr;

      i = *nmasize << 1;
      *nmasize = i;
      narr = n_autorec_alloc(i * sizeof *np);
      su_mem_copy(narr, *nmadat, i >>= 1);
      *nmadat = narr;
      np = &narr[idx];
   }
   *np++ = string;
   NYD2_OU;
   return np;
}

static int
a_msg_markall(char const *orig, struct n_cmd_arg *cap, int f){
   struct a_msg_speclex msl;
   enum a_msg_idfield idfield;
   uz j, nmasize;
   char const *id;
   char **nmadat_lofi, **nmadat, **np, **nq, *cp;
   struct message *mp, *mx;
   int i, valdot, beg, colmod, tok, colresult;
   enum{
      a_NONE = 0,
      a_ALLNET = 1u<<0,    /* (CTA()d to be == TRU1 */
      a_ALLOC = 1u<<1,     /* Have allocated something */
      a_THREADED = 1u<<2,
      a_ERROR = 1u<<3,
      a_ANY = 1u<<4,       /* Have marked just ANY */
      a_RANGE = 1u<<5,     /* Seen dash, await close */
      a_ASTER = 1u<<8,
      a_TOPEN = 1u<<9,     /* ( used (and didn't match) */
      a_TBACK = 1u<<10,    /* ` used (and didn't match) */
#ifdef mx_HAVE_IMAP
      a_HAVE_IMAP_HEADERS = 1u<<14,
#endif
      a_LOG = 1u<<29,      /* Log errors */
      a_TMP = 1u<<30
   } flags;
   NYD_IN;
   LCTA((u32)a_ALLNET == (u32)TRU1,
      "Constant is converted to boole via AND, thus");

   /* Update message array: clear MMARK but remember its former state for ` */
   for(i = msgCount; i-- > 0;){
      enum mflag mf;

      mf = (mp = &message[i])->m_flag;
      if(mf & MMARK)
         mf |= MOLDMARK;
      else
         mf &= ~MOLDMARK;
      mf &= ~MMARK;
      mp->m_flag = mf;
   }

   su_mem_set(&msl, 0, sizeof msl);
   msl.msl_cap = cap;
   msl.msl_input_orig = orig;

   np = nmadat =
   nmadat_lofi = n_lofi_alloc((nmasize = 64) * sizeof *np); /* TODO vector */
   UNINIT(beg, 0);
   UNINIT(idfield, a_MSG_ID_REFERENCES);
   a_msg_threadflag = FAL0;
   valdot = (int)P2UZ(dot - message + 1);
   colmod = 0;
   id = NULL;
   flags = a_ALLOC | (mb.mb_threaded ? a_THREADED : 0) |
         ((!(n_pstate & n_PS_HOOK_MASK) || (n_poption & n_PO_D_V))
            ? a_LOG : 0);

   while((tok = a_msg_scan(&msl)) != a_MSG_T_EOL){
      if((a_msg_threadflag = (tok < 0)))
         tok &= INT_MAX;

      switch(tok){
      case a_MSG_T_NUMBER:
         n_pstate |= n_PS_MSGLIST_GABBY;
jnumber:
         if(!a_msg_check(msl.msl_no, f))
            goto jerr;

         if(flags & a_RANGE){
            flags ^= a_RANGE;

            if(!(flags & a_THREADED)){
               if(beg < msl.msl_no)
                  i = beg;
               else{
                  i = msl.msl_no;
                  msl.msl_no = beg;
               }

               for(; i <= msl.msl_no; ++i){
                  mp = &message[i - 1];
                  if(!(mp->m_flag & MHIDDEN) &&
                         (f == MDELETED || !(mp->m_flag & MDELETED))){
                     mark(i, f);
                     flags |= a_ANY;
                  }
               }
            }else{
               /* TODO threaded ranges are a mess */
               enum{
                  a_T_NONE,
                  a_T_HOT = 1u<<0,
                  a_T_DIR_PREV = 1u<<1
               } tf;
               int i_base;

               if(beg < msl.msl_no)
                  i = beg;
               else{
                  i = msl.msl_no;
                  msl.msl_no = beg;
               }

               i_base = i;
               tf = a_T_NONE;
jnumber__thr:
               for(;;){
                  mp = &message[i - 1];
                  if(!(mp->m_flag & MHIDDEN) &&
                         (f == MDELETED || !(mp->m_flag & MDELETED))){
                     if(tf & a_T_HOT){
                        mark(i, f);
                        flags |= a_ANY;
                     }
                  }

                  /* We may have reached the endpoint.  If we were still
                   * detecting the direction to search for it, restart.
                   * Otherwise finished */
                  if(i == msl.msl_no){ /* XXX */
                     if(!(tf & a_T_HOT)){
                        tf |= a_T_HOT;
                        i = i_base;
                        goto jnumber__thr;
                     }
                     break;
                  }

                  mx = (tf & a_T_DIR_PREV) ? prev_in_thread(mp)
                        : next_in_thread(mp);
                  if(mx == NULL){
                     /* We anyway have failed to reach the endpoint in this
                      * direction; if we already switched that, report error */
                     if(!(tf & a_T_DIR_PREV)){
                        tf |= a_T_DIR_PREV;
                        i = i_base;
                        goto jnumber__thr;
                     }
                     id = N_("Range crosses multiple threads\n");
                     goto jerrmsg;
                  }
                  i = (int)P2UZ(mx - message + 1);
               }
            }

            beg = 0;
         }else{
            /* Could be an inclusive range? */
            if(msl.msl_cap != NULL &&
                  msl.msl_cap->ca_arg.ca_str.s[0] == '-'){
               if(*++msl.msl_cap->ca_arg.ca_str.s == '\0')
                  msl.msl_cap = msl.msl_cap->ca_next;
               beg = msl.msl_no;
               flags |= a_RANGE;
            }else{
               mark(msl.msl_no, f);
               flags |= a_ANY;
            }
         }
         break;
      case a_MSG_T_PLUS:
         n_pstate &= ~n_PS_MSGLIST_DIRECT;
         n_pstate |= n_PS_MSGLIST_GABBY;
         i = valdot;
         do{
            if(flags & a_THREADED){
               mx = next_in_thread(&message[i - 1]);
               i = mx ? (int)P2UZ(mx - message + 1) : msgCount + 1;
            }else
               ++i;
            if(i > msgCount){
               id = N_("Referencing beyond last message\n");
               goto jerrmsg;
            }
         }while(message[i - 1].m_flag == MHIDDEN ||
            (message[i - 1].m_flag & MDELETED) != (unsigned)f);
         msl.msl_no = i;
         goto jnumber;
      case a_MSG_T_MINUS:
         n_pstate &= ~n_PS_MSGLIST_DIRECT;
         n_pstate |= n_PS_MSGLIST_GABBY;
         i = valdot;
         do{
            if(flags & a_THREADED){
               mx = prev_in_thread(&message[i - 1]);
               i = mx ? (int)P2UZ(mx - message + 1) : 0;
            }else
               --i;
            if(i <= 0){
               id = N_("Referencing before first message\n");
               goto jerrmsg;
            }
         }while(message[i - 1].m_flag == MHIDDEN ||
            (message[i - 1].m_flag & MDELETED) != (unsigned)f);
         msl.msl_no = i;
         goto jnumber;
      case a_MSG_T_STRING:
         n_pstate &= ~n_PS_MSGLIST_DIRECT;
         if(flags & a_RANGE)
            goto jebadrange;

         /* This may be a colon modifier */
         if((cp = msl.msl_str)[0] != ':')
            np = a_msg_add_to_nmadat(&nmadat, &nmasize, np,
                  savestr(msl.msl_str));
         else{
            while(*++cp != '\0'){
               colresult = a_msg_evalcol(*cp);
               if(colresult == 0){
                  if(flags & a_LOG)
                     n_err(_("Unknown colon modifier: %s\n"), msl.msl_str);
                  goto jerr;
               }
               if(colresult == a_MSG_S_DELETED){
                  a_msg_list_saw_d = TRU1;
                  f |= MDELETED;
               }
               colmod |= colresult;
            }
         }
         break;
      case a_MSG_T_OPEN:
         n_pstate &= ~n_PS_MSGLIST_DIRECT;
         if(flags & a_RANGE)
            goto jebadrange;
         flags |= a_TOPEN;

#ifdef mx_HAVE_IMAP_SEARCH
         /* C99 */{
            sz ires;

            if((ires = imap_search(msl.msl_str, f)) >= 0){
               if(ires > 0)
                  flags |= a_ANY;
               break;
            }
         }
#else
         if(flags & a_LOG)
            n_err(_("Optional selector not available: %s\n"), msl.msl_str);
#endif
         goto jerr;
      case a_MSG_T_DOLLAR:
      case a_MSG_T_UP:
      case a_MSG_T_SEMI:
         n_pstate |= n_PS_MSGLIST_GABBY;
         /* FALLTHRU */
      case a_MSG_T_DOT: /* Don't set _GABBY for dot to allow history.. */
         n_pstate &= ~n_PS_MSGLIST_DIRECT;
         if((msl.msl_no = a_msg_metamess(msl.msl_str[0], f)) == -1)
            goto jerr;
         goto jnumber;
      case a_MSG_T_BACK:
         n_pstate &= ~n_PS_MSGLIST_DIRECT;
         if(flags & a_RANGE)
            goto jebadrange;

         flags |= a_TBACK;
         for(i = 0; i < msgCount; ++i){
            if((mp = &message[i])->m_flag & MHIDDEN)
               continue;
            if((mp->m_flag & MDELETED) != (unsigned)f){
               if(!a_msg_list_last_saw_d)
                  continue;
               a_msg_list_saw_d = TRU1;
            }
            if(mp->m_flag & MOLDMARK){
               mark(i + 1, f);
               flags &= ~a_TBACK;
               flags |= a_ANY;
            }
         }
         break;
      case a_MSG_T_ASTER:
         n_pstate &= ~n_PS_MSGLIST_DIRECT;
         if(flags & a_RANGE)
            goto jebadrange;
         flags |= a_ASTER;
         break;
      case a_MSG_T_COMMA:
         n_pstate &= ~n_PS_MSGLIST_DIRECT;
         n_pstate |= n_PS_MSGLIST_GABBY;
         if(flags & a_RANGE)
            goto jebadrange;

#ifdef mx_HAVE_IMAP
         if(!(flags & a_HAVE_IMAP_HEADERS) && mb.mb_type == MB_IMAP){
            flags |= a_HAVE_IMAP_HEADERS;
            imap_getheaders(1, msgCount);
         }
#endif

         if(id == NULL){
            if((cp = hfield1("in-reply-to", dot)) != NULL)
               idfield = a_MSG_ID_IN_REPLY_TO;
            else if((cp = hfield1("references", dot)) != NULL){
               struct mx_name *enp;

               if((enp = extract(cp, GREF)) != NULL){
                  while(enp->n_flink != NULL)
                     enp = enp->n_flink;
                  cp = enp->n_name;
                  idfield = a_MSG_ID_REFERENCES;
               }else
                  cp = NULL;
            }

            if(cp != NULL)
               id = savestr(cp);
            else{
               id = N_("Message-ID of parent of \"dot\" is indeterminable\n");
               goto jerrmsg;
            }
         }else if(flags & a_LOG)
            n_err(_("Ignoring redundant specification of , selector\n"));
         break;
      case a_MSG_T_ERROR:
         n_pstate &= ~n_PS_MSGLIST_DIRECT;
         n_pstate |= n_PS_MSGLIST_GABBY;
         goto jerr;
      }

      /* Explicitly disallow invalid ranges for future safety */
      if(msl.msl_cap != NULL && msl.msl_cap->ca_arg.ca_str.s[0] == '-' &&
            !(flags & a_RANGE)){
         if(flags & a_LOG)
            n_err(_("Ignoring invalid range in: %s\n"), msl.msl_input_orig);
         if(*++msl.msl_cap->ca_arg.ca_str.s == '\0')
            msl.msl_cap = msl.msl_cap->ca_next;
      }
   }
   if(flags & a_RANGE){
      id = N_("Missing second range argument\n");
      goto jerrmsg;
   }

   np = a_msg_add_to_nmadat(&nmadat, &nmasize, np, NULL);
   --np;

   /* * is special at this point, after we have parsed the entire line */
   if(flags & a_ASTER){
      for(i = 0; i < msgCount; ++i){
         if((mp = &message[i])->m_flag & MHIDDEN)
            continue;
         if(!a_msg_list_saw_d && (mp->m_flag & MDELETED) != (unsigned)f)
            continue;
         mark(i + 1, f);
         flags |= a_ANY;
      }
      if(!(flags & a_ANY))
         goto jenoapp;
      goto jleave;
   }

   /* If any names were given, add any messages which match */
   if(np > nmadat || id != NULL){
      struct search_expr *sep;

      sep = NULL;

      /* The @ search works with struct search_expr, so build an array.
       * To simplify array, i.e., regex_t destruction, and optimize for the
       * common case we walk the entire array even in case of errors */
      /* XXX Like many other things around here: this should be outsourced */
      if(np > nmadat){
         j = P2UZ(np - nmadat) * sizeof(*sep);
         sep = n_lofi_alloc(j);
         su_mem_set(sep, 0, j);

         for(j = 0, nq = nmadat; *nq != NULL; ++j, ++nq){
            char *xsave, *x, *y;

            sep[j].ss_body = x = xsave = *nq;
            if(*x != '@' || (flags & a_ERROR))
               continue;

            /* Cramp the namelist */
            for(y = &x[1];; ++y){
               if(*y == '\0'){
                  x = NULL;
                  break;
               }
               if(*y == '@'){
                  x = y;
                  break;
               }
            }
            if(x == NULL || &x[-1] == xsave)
jat_where_default:
               sep[j].ss_field = "subject";
            else{
               ++xsave;
               if(*xsave == '~'){
                  sep[j].ss_skin = TRU1;
                  if(++xsave >= x){
                     if(flags & a_LOG)
                        n_err(_("[@..]@ search expression: no namelist, "
                           "only \"~\" skin indicator\n"));
                     flags |= a_ERROR;
                     continue;
                  }
               }
               cp = savestrbuf(xsave, P2UZ(x - xsave));

               /* Namelist could be a regular expression, too */
#ifdef mx_HAVE_REGEX
               if(n_is_maybe_regex(cp)){
                  int s;

                  ASSERT(sep[j].ss_field == NULL);
                  if((s = regcomp(&sep[j].ss__fieldre_buf, cp,
                        REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
                     if(flags & a_LOG)
                        n_err(_("Invalid regular expression: %s: %s\n"),
                           n_shexp_quote_cp(cp, FAL0),
                           n_regex_err_to_doc(NULL, s));
                     flags |= a_ERROR;
                     continue;
                  }
                  sep[j].ss_fieldre = &sep[j].ss__fieldre_buf;
               }else
#endif
                    {
                  struct str sio;

                  /* Because of the special cases we need to trim explicitly
                   * here, they are not covered by su_cs_sep_c() */
                  sio.s = cp;
                  sio.l = P2UZ(x - xsave);
                  if(*(cp = n_str_trim(&sio, n_STR_TRIM_BOTH)->s) == '\0')
                     goto jat_where_default;
                  sep[j].ss_field = cp;
               }
            }

            /* The actual search expression.  If it is empty we only test the
             * field(s) for existence  */
            x = &(x == NULL ? *nq : x)[1];
            if(*x == '\0'){
               sep[j].ss_field_exists = TRU1;
#ifdef mx_HAVE_REGEX
            }else if(n_is_maybe_regex(x)){
               int s;

               sep[j].ss_body = NULL;
               if((s = regcomp(&sep[j].ss__bodyre_buf, x,
                     REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
                  if(flags & a_LOG)
                     n_err(_("Invalid regular expression: %s: %s\n"),
                        n_shexp_quote_cp(x, FAL0),
                        n_regex_err_to_doc(NULL, s));
                  flags |= a_ERROR;
                  continue;
               }
               sep[j].ss_bodyre = &sep[j].ss__bodyre_buf;
#endif
            }else
               sep[j].ss_body = x;
         }
         if(flags & a_ERROR)
            goto jnamesearch_sepfree;
      }

      /* Iterate the entire message array */
#ifdef mx_HAVE_IMAP
         if(!(flags & a_HAVE_IMAP_HEADERS) && mb.mb_type == MB_IMAP){
            flags |= a_HAVE_IMAP_HEADERS;
            imap_getheaders(1, msgCount);
         }
#endif
      if(ok_blook(allnet))
         flags |= a_ALLNET;
      n_autorec_relax_create();
      for(i = 0; i < msgCount; ++i){
         if((mp = &message[i])->m_flag & (MMARK | MHIDDEN))
            continue;
         if(!a_msg_list_saw_d && (mp->m_flag & MDELETED) != (unsigned)f)
            continue;

         flags &= ~a_TMP;
         if(np > nmadat){
            for(nq = nmadat; *nq != NULL; ++nq){
               if(**nq == '@'){
                  if(a_msg_match_at(mp, &sep[P2UZ(nq - nmadat)])){
                     flags |= a_TMP;
                     break;
                  }
               }else if(**nq == '/'){
                  if(a_msg_match_dash(mp, *nq)){
                     flags |= a_TMP;
                     break;
                  }
               }else if(a_msg_match_sender(mp, *nq, (flags & a_ALLNET))){
                  flags |= a_TMP;
                  break;
               }
            }
         }
         if(!(flags & a_TMP) &&
               id != NULL && a_msg_match_mid(mp, id, idfield))
            flags |= a_TMP;

         if(flags & a_TMP){
            mark(i + 1, f);
            flags |= a_ANY;
         }
         n_autorec_relax_unroll();
      }
      n_autorec_relax_gut();

jnamesearch_sepfree:
      if(sep != NULL){
#ifdef mx_HAVE_REGEX
         for(j = P2UZ(np - nmadat); j-- != 0;){
            if(sep[j].ss_fieldre != NULL)
               regfree(sep[j].ss_fieldre);
            if(sep[j].ss_bodyre != NULL)
               regfree(sep[j].ss_bodyre);
         }
#endif
         n_lofi_free(sep);
      }
      if(flags & a_ERROR)
         goto jerr;
   }

   /* If any colon modifiers were given, go through and mark any messages which
    * do satisfy the modifiers */
   if(colmod != 0){
      for(i = 0; i < msgCount; ++i){
         struct a_msg_coltab const *colp;

         if((mp = &message[i])->m_flag & (MMARK | MHIDDEN))
            continue;
         if(!a_msg_list_saw_d && (mp->m_flag & MDELETED) != (unsigned)f)
            continue;

         for(colp = a_msg_coltabs;
               PCMP(colp, <, &a_msg_coltabs[NELEM(a_msg_coltabs)]); ++colp)
            if(colp->mco_bit & colmod){
               /* Is this a colon modifier that requires evaluation? */
               if(colp->mco_mask == 0){
                  if(colp->mco_bit & (a_MSG_S_MLIST |
                           a_MSG_S_MLSUBSCRIBE)){
                     enum mx_mlist_type what;

                     what = (colp->mco_bit & a_MSG_S_MLIST) ? mx_MLIST_KNOWN
                           : mx_MLIST_SUBSCRIBED;
                     if(what == mx_mlist_query_mp(mp, what))
                        goto jcolonmod_mark;
                  }
               }else if((mp->m_flag & colp->mco_mask
                     ) == (enum mflag)colp->mco_equal){
jcolonmod_mark:
                  mark(i + 1, f);
                  flags |= a_ANY;
                  break;
               }
            }
      }
   }

   /* It shall be an error if ` didn't match anything, and nothing else did */
   if((flags & (a_TBACK | a_ANY)) == a_TBACK){
      id = N_("No previously marked messages\n");
      goto jerrmsg;
   }else if(!(flags & a_ANY))
      goto jenoapp;

   ASSERT(!(flags & a_ERROR));
jleave:
   if(flags & a_ALLOC)
      n_lofi_free(nmadat_lofi);
   NYD_OU;
   return (flags & a_ERROR) ? -1 : 0;

jebadrange:
   id = N_("Invalid range endpoint\n");
   goto jerrmsg;
jenoapp:
   id = N_("No applicable messages\n");
jerrmsg:
   if(flags & a_LOG)
      n_err(V_(id));
jerr:
   flags |= a_ERROR;
   goto jleave;
}

static int
a_msg_evalcol(int col){
   struct a_msg_coltab const *colp;
   int rv;
   NYD2_IN;

   rv = 0;
   for(colp = a_msg_coltabs;
         PCMP(colp, <, &a_msg_coltabs[NELEM(a_msg_coltabs)]); ++colp)
      if(colp->mco_char == col){
         rv = colp->mco_bit;
         break;
      }
   NYD2_OU;
   return rv;
}

static boole
a_msg_check(int mno, int f){
   struct message *mp;
   NYD2_IN;

   if(mno < 1 || mno > msgCount){
      n_err(_("%d: Invalid message number\n"), mno);
      mno = 1;
   }else if(((mp = &message[mno - 1])->m_flag & MHIDDEN) ||
         (f != MDELETED && (mp->m_flag & MDELETED) != 0))
      n_err(_("%d: inappropriate message\n"), mno);
   else
      mno = 0;
   NYD2_OU;
   return (mno == 0);
}

static int
a_msg_scan(struct a_msg_speclex *mslp){
   struct a_msg_lex const *lp;
   char *cp, c;
   int rv;
   NYD_IN;

   rv = a_MSG_T_EOL;

   /* Empty cap's even for IGNORE_EMPTY (quoted empty tokens produce output) */
   for(;; mslp->msl_cap = mslp->msl_cap->ca_next){
      if(mslp->msl_cap == NULL)
         goto jleave;

      cp = mslp->msl_cap->ca_arg.ca_str.s;
      if((c = *cp++) != '\0')
         break;
   }

   /* Select members of a message thread */
   if(c == '&'){
      c = *cp;
      if(c == '\0' || su_cs_is_space(c)){
         mslp->msl_str = mslp->msl__smallstrbuf;
         mslp->msl_str[0] = '.';
         mslp->msl_str[1] = '\0';
         if(c == '\0')
            mslp->msl_cap = mslp->msl_cap->ca_next;
         else{
jshexp_err:
            n_err(_("Message list: invalid syntax: %s (in %s)\n"),
               n_shexp_quote_cp(cp, FAL0),
               n_shexp_quote_cp(mslp->msl_input_orig, FAL0));
            rv = a_MSG_T_ERROR;
            goto jleave;
         }
         rv = a_MSG_T_DOT | INT_MIN;
         goto jleave;
      }
      rv = INT_MIN;
      ++cp;
   }

   /* If the leading character is a digit, scan the number and convert it
    * on the fly.  Return a_MSG_T_NUMBER when done */
   if(su_cs_is_digit(c)){
      mslp->msl_no = 0;
      do
         mslp->msl_no = (mslp->msl_no * 10) + c - '0'; /* XXX inline atoi */
      while((c = *cp++, su_cs_is_digit(c)));

      if(c == '\0')
         mslp->msl_cap = mslp->msl_cap->ca_next;
      else{
         --cp;
         /* This could be a range */
         if(c == '-')
            mslp->msl_cap->ca_arg.ca_str.s = cp;
         else
            goto jshexp_err;
      }
      rv |= a_MSG_T_NUMBER;
      goto jleave;
   }

   /* An IMAP SEARCH list. Note that a_MSG_T_OPEN has always been included
    * in singles[] in Mail and mailx. Thus although there is no formal
    * definition for (LIST) lists, they do not collide with historical
    * practice because a subject string (LIST) could never been matched
    * this way */
   if (c == '(') {
      boole inquote;
      u32 level;
      char *tocp;

      (tocp = mslp->msl_str = mslp->msl_cap->ca_arg.ca_str.s)[0] = '(';
      ++tocp;
      level = 1;
      inquote = FAL0;
      do {
         if ((c = *cp++) == '\0') {
jmtop:
            n_err(_("Missing )\n"));
            n_err(_("P.S.: message specifications are now shell tokens, "
               "making it necessary to enclose IMAP search expressions "
               "in (single) quotes, e.g., '(from \"me\")'\n"));
            rv = a_MSG_T_ERROR;
            goto jleave;
         }
         if (inquote && c == '\\') {
            *tocp++ = c;
            c = *cp++;
            if (c == '\0')
               goto jmtop;
         } else if (c == '"')
            inquote = !inquote;
         else if (inquote)
            /*EMPTY*/;
         else if (c == '(')
            ++level;
         else if (c == ')')
            --level;
         else if (su_cs_is_space(c)) {
            /* Replace unquoted whitespace by single space characters, to make
             * the string IMAP SEARCH conformant */
            c = ' ';
            if (tocp[-1] == ' ')
               --tocp;
         }
         *tocp++ = c;
      } while (c != ')' || level > 0);
      *tocp = '\0';
      if(*cp != '\0')
         goto jshexp_err;
      mslp->msl_cap = mslp->msl_cap->ca_next;
      rv |= a_MSG_T_OPEN;
      goto jleave;
   }

   /* Check for single character tokens; return such if found */
   for(lp = a_msg_singles;
         PCMP(lp, <, &a_msg_singles[NELEM(a_msg_singles)]); ++lp){
      if(c == lp->ml_char){
         mslp->msl_str = mslp->msl__smallstrbuf;
         mslp->msl_str[0] = c;
         mslp->msl_str[1] = '\0';
         if(*cp != '\0')
            goto jshexp_err;
         mslp->msl_cap = mslp->msl_cap->ca_next;
         rv = lp->ml_token;
         goto jleave;
      }
   }

   mslp->msl_cap = mslp->msl_cap->ca_next;
   mslp->msl_str = --cp;
   rv = a_MSG_T_STRING;
jleave:
   NYD_OU;
   return rv;
}

static boole
a_msg_match_sender(struct message *mp, char const *str, boole allnet){
   char const *str_base, *np_base, *np;
   char c, nc;
   struct mx_name *namep;
   boole rv;
   NYD2_IN;

   rv = FAL0;

   /* Empty string doesn't match */
   namep = lextract(n_header_senderfield_of(mp), GFULL | GSKIN);

   if(namep == NULL || *(str_base = str) == '\0')
      goto jleave;

   /* *allnet* is POSIX and, since it explicitly mentions login and user names,
    * most likely case-sensitive.  XXX Still allow substr matching, though
    * XXX possibly the first letter should be case-insensitive, then? */
   if(allnet){
      for(; namep != NULL; str = str_base, namep = namep->n_flink){
         for(np_base = np = namep->n_name;;){
            if((c = *str++) == '@')
               c = '\0';
            if((nc = *np++) == '@' || nc == '\0' || c == '\0'){
               if((rv = (c == '\0')))
                  goto jleave;
               break;
            }
            if(c != nc){
               np = ++np_base;
               str = str_base;
            }
         }
      }
   }else{
      /* TODO POSIX says ~"match any address as shown in header overview",
       * TODO but a normalized match would be more sane i guess.
       * TODO mx_name should gain a comparison method, normalize realname
       * TODO content (in TODO) and thus match as likewise
       * TODO "Buddy (Today) " and "(Now) Buddy " */
      boole again_base, again;

      again_base = ok_blook(showname);

      for(; namep != NULL; str = str_base, namep = namep->n_flink){
         again = again_base;
jagain:
         np_base = np = again ? namep->n_fullname : namep->n_name;
         str = str_base;
         for(;;){
            c = *str++;
            if((nc = *np++) == '\0' || c == '\0'){
               if((rv = (c == '\0')))
                  goto jleave;
               break;
            }
            c = su_cs_to_upper(c);
            nc = su_cs_to_upper(nc);
            if(c != nc){
               np = ++np_base;
               str = str_base;
            }
         }

         /* And really if i want to match 'on@' then i want it to match even if
          * *showname* is set! */
         if(again){
            again = FAL0;
            goto jagain;
         }
      }
   }
jleave:
   NYD2_OU;
   return rv;
}

static boole
a_msg_match_mid(struct message *mp, char const *id,
      enum a_msg_idfield idfield){
   char const *cp;
   boole rv;
   NYD2_IN;

   rv = FAL0;

   if((cp = hfield1("message-id", mp)) != NULL){
      switch(idfield){
      case a_MSG_ID_REFERENCES:
         if(!msgidcmp(id, cp))
            rv = TRU1;
         break;
      case a_MSG_ID_IN_REPLY_TO:{
         struct mx_name *np;

         if((np = extract(id, GREF)) != NULL)
            do{
               if(!msgidcmp(np->n_name, cp)){
                  rv = TRU1;
                  break;
               }
            }while((np = np->n_flink) != NULL);
         break;
      }
      }
   }
   NYD2_OU;
   return rv;
}

static boole
a_msg_match_dash(struct message *mp, char const *str){
   static char lastscan[128];

   struct str in, out;
   char *hfield, *hbody;
   boole rv;
   NYD2_IN;

   rv = FAL0;

   if(*++str == '\0')
      str = lastscan;
   else
      su_cs_pcopy_n(lastscan, str, sizeof lastscan); /* XXX use n_str! */

   /* Now look, ignoring case, for the word in the string */
   if(ok_blook(searchheaders) && (hfield = su_cs_find_c(str, ':'))){
      uz l;

      l = P2UZ(hfield - str);
      hfield = n_lofi_alloc(l +1);
      su_mem_copy(hfield, str, l);
      hfield[l] = '\0';
      hbody = hfieldX(hfield, mp);
      n_lofi_free(hfield);
      hfield = n_UNCONST(str + l + 1);
   }else{
      hfield = n_UNCONST(str);
      hbody = hfield1("subject", mp);
   }
   if(hbody == NULL)
      goto jleave;

   in.l = su_cs_len(in.s = hbody);
   mime_fromhdr(&in, &out, TD_ICONV);
   rv = substr(out.s, hfield);
   n_free(out.s);
jleave:
   NYD2_OU;
   return rv;
}

static boole
a_msg_match_at(struct message *mp, struct search_expr *sep){
   char const *field;
   boole rv;
   NYD2_IN;

   /* Namelist regex only matches headers.
    * And there are the special cases header/<, "body"/> and "text"/=, the
    * latter two of which need to be handled in here */
   if((field = sep->ss_field) != NULL){
      if(!su_cs_cmp_case(field, "body") ||
            (field[1] == '\0' && field[0] == '>')){
         rv = FAL0;
jmsg:
         rv = message_match(mp, sep, rv);
         goto jleave;
      }else if(!su_cs_cmp_case(field, "text") ||
            (field[1] == '\0' && field[0] == '=')){
         rv = TRU1;
         goto jmsg;
      }
   }

   rv = n_header_match(mp, sep);
jleave:
   NYD2_OU;
   return rv;
}

static void
a_msg_unmark(int mesg){
   uz i;
   NYD2_IN;

   i = (uz)mesg;
   if(i < 1 || UCMP(z, i, >, msgCount))
      n_panic(_("Bad message number to unmark"));
   message[--i].m_flag &= ~MMARK;
   NYD2_OU;
}

static int
a_msg_metamess(int meta, int f)
{
   int c, m;
   struct message *mp;
   NYD2_IN;

   c = meta;
   switch (c) {
   case '^': /* First 'good' message left */
      mp = mb.mb_threaded ? threadroot : message;
      while (PCMP(mp, <, message + msgCount)) {
         if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (u32)f) {
            c = (int)P2UZ(mp - message + 1);
            goto jleave;
         }
         if (mb.mb_threaded) {
            mp = next_in_thread(mp);
            if (mp == NULL)
               break;
         } else
            ++mp;
      }
      if (!(n_pstate & n_PS_HOOK_MASK))
         n_err(_("No applicable messages\n"));
      goto jem1;

   case '$': /* Last 'good message left */
      mp = mb.mb_threaded
            ? this_in_thread(threadroot, -1) : message + msgCount - 1;
      while (mp >= message) {
         if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (u32)f) {
            c = (int)P2UZ(mp - message + 1);
            goto jleave;
         }
         if (mb.mb_threaded) {
            mp = prev_in_thread(mp);
            if (mp == NULL)
               break;
         } else
            --mp;
      }
      if (!(n_pstate & n_PS_HOOK_MASK))
         n_err(_("No applicable messages\n"));
      goto jem1;

   case '.':
      /* Current message */
      m = dot - message + 1;
      if ((dot->m_flag & MHIDDEN) || (dot->m_flag & MDELETED) != (u32)f) {
         n_err(_("%d: inappropriate message\n"), m);
         goto jem1;
      }
      c = m;
      break;

   case ';':
      /* Previously current message */
      if (prevdot == NULL) {
         n_err(_("No previously current message\n"));
         goto jem1;
      }
      m = prevdot - message + 1;
      if ((prevdot->m_flag & MHIDDEN) ||
            (prevdot->m_flag & MDELETED) != (u32)f) {
         n_err(_("%d: inappropriate message\n"), m);
         goto jem1;
      }
      c = m;
      break;

   default:
      n_err(_("Unknown selector: %c\n"), c);
      goto jem1;
   }
jleave:
   NYD2_OU;
   return c;
jem1:
   c = -1;
   goto jleave;
}

static void
a_msg__threadmark(struct message *self, int f){
   NYD2_IN;
   if(!(self->m_flag & MHIDDEN) &&
         (f == MDELETED || !(self->m_flag & MDELETED) || a_msg_list_saw_d))
      self->m_flag |= MMARK;

   if((self = self->m_child) != NULL){
      goto jcall;
      while((self = self->m_younger) != NULL)
         if(self->m_child != NULL)
jcall:
            a_msg__threadmark(self, f);
         else
            self->m_flag |= MMARK;
   }
   NYD2_OU;
}

FL FILE *
setinput(struct mailbox *mp, struct message *m, enum needspec need){
   enum okay ok;
   FILE *rv;
   NYD_IN;

   rv = NULL;

   switch(need){
   case NEED_HEADER:
      ok = (m->m_content_info & CI_HAVE_HEADER) ? OKAY
            : a_msg_get_header(m);
      break;
   case NEED_BODY:
      ok = (m->m_content_info & CI_HAVE_BODY) ? OKAY : get_body(m);
      break;
   default:
   case NEED_UNSPEC:
      ok = OKAY;
      break;
   }
   if(ok != OKAY)
      goto jleave;

   fflush(mp->mb_otf);
   if(fseek(mp->mb_itf, (long)mailx_positionof(m->m_block, m->m_offset),
         SEEK_SET) == -1){
      n_perr(_("fseek"), 0);
      n_panic(_("temporary file seek"));
   }
   rv = mp->mb_itf;
jleave:
   NYD_OU;
   return rv;
}

FL enum okay
get_body(struct message *mp){
   enum okay rv;
   NYD_IN;
   UNUSED(mp);

   switch(mb.mb_type){
   case MB_FILE:
   case MB_MAILDIR:
      rv = OKAY;
      break;
#ifdef mx_HAVE_POP3
   case MB_POP3:
      rv = mx_pop3_body(mp);
      break;
#endif
#ifdef mx_HAVE_IMAP
   case MB_IMAP:
   case MB_CACHE:
      rv = imap_body(mp);
      break;
#endif
   case MB_VOID:
   default:
      rv = STOP;
      break;
   }
   NYD_OU;
   return rv;
}

FL void
message_reset(void){
   NYD_IN;
   if(message != NULL){
      n_free(message);
      message = NULL;
   }
   msgCount = 0;
   a_msg_mem_space = 0;
   NYD_OU;
}

FL void
message_append(struct message *mp){
   NYD_IN;
   if(UCMP(z, msgCount + 1, >=, a_msg_mem_space)){
      /* XXX remove _mem_space magics (or use s_Vector) */
      a_msg_mem_space = ((a_msg_mem_space >= 128 &&
               a_msg_mem_space <= 1000000)
            ? a_msg_mem_space << 1 : a_msg_mem_space + 64);
      message = n_realloc(message, a_msg_mem_space * sizeof(*message));
   }
   if(msgCount > 0){
      if(mp != NULL)
         message[msgCount - 1] = *mp;
      else
         su_mem_set(&message[msgCount - 1], 0, sizeof *message);
   }
   NYD_OU;
}

FL void
message_append_null(void){
   NYD_IN;
   if(msgCount == 0)
      message_append(NULL);
   setdot(message);
   message[msgCount].m_size = 0;
   message[msgCount].m_lines = 0;
   NYD_OU;
}

FL boole
message_match(struct message *mp, struct search_expr const *sep,
      boole with_headers){
   char *line;
   uz linesize, cnt;
   FILE *fp;
   boole rv;
   NYD_IN;

   rv = FAL0;

   if((fp = mx_fs_tmp_open("mpmatch", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
            mx_FS_O_REGISTER), NIL)) == NIL)
      goto j_leave;

   if(sendmp(mp, fp, NULL, NULL, SEND_TOSRCH, NULL) < 0)
      goto jleave;
   fflush_rewind(fp);
   cnt = fsize(fp);

   mx_fs_linepool_aquire(&line, &linesize);

   if(!with_headers)
      while(fgetline(&line, &linesize, &cnt, NULL, fp, 0))
         if(*line == '\n')
            break;

   while(fgetline(&line, &linesize, &cnt, NULL, fp, 0)){
#ifdef mx_HAVE_REGEX
      if(sep->ss_bodyre != NULL){
         if(regexec(sep->ss_bodyre, line, 0,NULL, 0) == REG_NOMATCH)
            continue;
      }else
#endif
            if(!substr(line, sep->ss_body))
         continue;
      rv = TRU1;
      break;
   }

   mx_fs_linepool_release(line, linesize);

jleave:
   mx_fs_close(fp);
j_leave:
   NYD_OU;
   return rv;
}

FL struct message *
setdot(struct message *mp){
   NYD_IN;
   if(dot != mp){
      prevdot = dot;
      n_pstate &= ~n_PS_DID_PRINT_DOT;
   }
   dot = mp;
   uncollapse1(dot, 0);
   NYD_OU;
   return dot;
}

FL void
touch(struct message *mp){
   NYD_IN;
   mp->m_flag |= MTOUCH;
   if(!(mp->m_flag & MREAD))
      mp->m_flag |= MREAD | MSTATUS;
   NYD_OU;
}

FL int
n_getmsglist(char const *buf, int *vector, int flags,
   struct n_cmd_arg **capp_or_null)
{
   int *ip, mc;
   struct message *mp;
   NYD_IN;

   n_pstate &= ~n_PS_ARGLIST_MASK;
   n_pstate |= n_PS_MSGLIST_DIRECT;
   a_msg_list_last_saw_d = a_msg_list_saw_d;
   a_msg_list_saw_d = FAL0;

   *vector = 0;
   if(capp_or_null != NULL)
      *capp_or_null = NULL;
   if(*buf == '\0'){
      mc = 0;
      goto jleave;
   }

   /* TODO Parse the message spec into an ARGV; this should not happen here,
    * TODO but instead cmd_arg_parse() should feed in the list of parsed tokens
    * TODO to getmsglist(); as of today there are multiple getmsglist() users
    * TODO though, and they need to deal with that, then, too */
   /* C99 */{
      n_CMD_ARG_DESC_SUBCLASS_DEF(getmsglist, 1, pseudo_cad){
         {n_CMD_ARG_DESC_SHEXP | n_CMD_ARG_DESC_OPTION |
               n_CMD_ARG_DESC_GREEDY | n_CMD_ARG_DESC_HONOUR_STOP,
            n_SHEXP_PARSE_TRIM_IFSSPACE | n_SHEXP_PARSE_IFS_VAR |
               n_SHEXP_PARSE_IGNORE_EMPTY}
      }n_CMD_ARG_DESC_SUBCLASS_DEF_END;
      struct n_cmd_arg_ctx cac;

      cac.cac_desc = n_CMD_ARG_DESC_SUBCLASS_CAST(&pseudo_cad);
      cac.cac_indat = buf;
      cac.cac_inlen = UZ_MAX;
      cac.cac_msgflag = flags;
      cac.cac_msgmask = 0;
      if(!n_cmd_arg_parse(&cac)){
         mc = -1;
         goto jleave;
      }else if(cac.cac_no == 0){
         mc = 0;
         goto jleave;
      }else{
         /* Is this indeed a (maybe optional) message list and a target? */
         if(capp_or_null != NULL){
            struct n_cmd_arg *cap, **lcapp;

            if((cap = cac.cac_arg)->ca_next == NULL){
               *capp_or_null = cap;
               mc = 0;
               goto jleave;
            }
            for(;;){
               lcapp = &cap->ca_next;
               if((cap = *lcapp)->ca_next == NULL)
                  break;
            }
            *capp_or_null = cap;
            *lcapp = NULL;

            /* In the list-and-target mode we have to take special care, since
             * some commands use special call conventions historically (use the
             * MBOX, search for a message, whatever).
             * Thus, to allow things like "certsave '' bla" or "save '' ''",
             * watch out for two argument form with empty token first.
             * This special case is documented at the prototype */
            if(cac.cac_arg->ca_next == NULL &&
                  cac.cac_arg->ca_arg.ca_str.s[0] == '\0'){
               mc = 0;
               goto jleave;
            }
         }

         if(msgCount == 0){
            mc = 0;
            goto jleave;
         }else if((mc = a_msg_markall(buf, cac.cac_arg, flags)) < 0){
            mc = -1;
            goto jleave;
         }
      }
   }

   ip = vector;
   if(n_pstate & n_PS_HOOK_NEWMAIL){
      mc = 0;
      for(mp = message; mp < &message[msgCount]; ++mp)
         if(mp->m_flag & MMARK){
            if(!(mp->m_flag & MNEWEST))
               a_msg_unmark((int)P2UZ(mp - message + 1));
            else
               ++mc;
         }
      if(mc == 0){
         mc = -1;
         goto jleave;
      }
   }

   if(mb.mb_threaded == 0){
      for(mp = message; mp < &message[msgCount]; ++mp)
         if(mp->m_flag & MMARK)
            *ip++ = (int)P2UZ(mp - message + 1);
   }else{
      for(mp = threadroot; mp != NULL; mp = next_in_thread(mp))
         if(mp->m_flag & MMARK)
            *ip++ = (int)P2UZ(mp - message + 1);
   }
   *ip = 0;
   mc = (int)P2UZ(ip - vector);
   if(mc != 1)
      n_pstate &= ~n_PS_MSGLIST_DIRECT;
jleave:
   NYD_OU;
   return mc;
}

FL int
first(int f, int m)
{
   struct message *mp;
   int rv;
   NYD_IN;

   if (msgCount == 0) {
      rv = 0;
      goto jleave;
   }

   f &= MDELETED;
   m &= MDELETED;
   for (mp = dot;
         mb.mb_threaded ? (mp != NULL) : PCMP(mp, <, message + msgCount);
         mb.mb_threaded ? (mp = next_in_thread(mp)) : ++mp) {
      if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (u32)f) {
         rv = (int)P2UZ(mp - message + 1);
         goto jleave;
      }
   }

   if (dot > message) {
      for (mp = dot - 1; (mb.mb_threaded ? (mp != NULL) : (mp >= message));
            mb.mb_threaded ? (mp = prev_in_thread(mp)) : --mp) {
         if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (u32)f) {
            rv = (int)P2UZ(mp - message + 1);
            goto jleave;
         }
      }
   }
   rv = 0;
jleave:
   NYD_OU;
   return rv;
}

FL void
mark(int mno, int f){
   struct message *mp;
   int i;
   NYD_IN;

   i = mno;
   if(i < 1 || i > msgCount)
      n_panic(_("Bad message number to mark"));
   mp = &message[--i];

   if(mb.mb_threaded == 1 && a_msg_threadflag)
      a_msg__threadmark(mp, f);
   else{
      ASSERT(!(mp->m_flag & MHIDDEN));
      mp->m_flag |= MMARK;
   }
   NYD_OU;
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/mx/mime-enc.c000066400000000000000000000764401352610246600161470ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ Content-Transfer-Encodings as defined in RFC 2045 (and RFC 2047;
 *@ for _header() versions: including "encoded word" as of RFC 2049):
 *@ - Quoted-Printable, section 6.7
 *@ - Base64, section 6.8
 *@ QP quoting and _b64_decode(), b64_encode() inspired from NetBSDs mailx(1):
 *@   $NetBSD: mime_codecs.c,v 1.9 2009/04/10 13:08:25 christos Exp $
 *@ TODO We have no notion of a "current message context" and thus badly log.
 *@ TODO This is not final yet, v15 will bring "filters".
 *
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE mime_enc
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

#include 
#include 

/* TODO fake */
#include "su/code-in.h"

enum a_me_qact{
   a_ME_N = 0,
   a_ME_Q = 1,       /* Must quote */
   a_ME_SP = 2,      /* sp */
   a_ME_XF = 3,      /* Special character 'F' - maybe quoted */
   a_ME_XD = 4,      /* Special character '.' - maybe quoted */
   a_ME_UU = 5,      /* In header, _ must be quoted in encoded word */
   a_ME_US = '_',    /* In header, ' ' must be quoted as _ in encoded word */
   a_ME_QM = '?',    /* In header, special character ? not always quoted */
   a_ME_EQ = '=',    /* In header, '=' must be quoted in encoded word */
   a_ME_HT ='\t',    /* Body HT=SP.  Head HT=HT, BUT quote in encoded word */
   a_ME_NL = 0,      /* Don't quote '\n' (NL) */
   a_ME_CR = a_ME_Q  /* Always quote a '\r' (CR) */
};

/* Lookup tables to decide whether a character must be encoded or not.
 * Email header differences according to RFC 2047, section 4.2:
 * - also quote SP (as the underscore _), TAB, ?, _, CR, LF
 * - don't care about the special ^F[rom] and ^.$ */
static u8 const a_me_qp_body[] = {
    a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,
    a_ME_Q, a_ME_SP, a_ME_NL,  a_ME_Q,  a_ME_Q, a_ME_CR,  a_ME_Q,  a_ME_Q,
    a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,
    a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,
   a_ME_SP,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,
    a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N, a_ME_XD,  a_ME_N,
    a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,
    a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_Q,  a_ME_N,  a_ME_N,

    a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N, a_ME_XF,  a_ME_N,
    a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,
    a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,
    a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,
    a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,
    a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,
    a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,
    a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_Q,
}, a_me_qp_head[] = {
    a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,
    a_ME_Q, a_ME_HT,  a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,
    a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,
    a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,  a_ME_Q,
   a_ME_US,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,
    a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,
    a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,
    a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N, a_ME_EQ,  a_ME_N, a_ME_QM,

    a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,
    a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,
    a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,
    a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N, a_ME_UU,
    a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,
    a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,
    a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,
    a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_N,  a_ME_Q,
};

/* The decoding table is only accessed via a_ME_B64_DECUI8() */
static char const a_me_b64_enctbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
      "abcdefghijklmnopqrstuvwxyz" "0123456789" "+/";
static signed char const a_me_b64__dectbl[] = {
   -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
   -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
   -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
   52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-2,-1,-1,
   -1, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
   15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
   -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
   41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
};
#define a_ME_B64_EQU (u32)-2
#define a_ME_B64_BAD (u32)-1
#define a_ME_B64_DECUI8(C) \
   ((u8)(C) >= sizeof(a_me_b64__dectbl)\
    ? a_ME_B64_BAD : (u32)a_me_b64__dectbl[(u8)(C)])

/* (Ugly to place an enum here) */
static char const a_me_ctes[] = "7bit\0" "8bit\0" \
      "base64\0" "quoted-printable\0" "binary\0" \
      /* abbrevs */ "8b\0" "b64\0" "qp\0";
enum a_me_ctes_off{
   a_ME_CTES_7B_OFF = 0, a_ME_CTES_7B_LEN = 4,
   a_ME_CTES_8B_OFF = 5, a_ME_CTES_8B_LEN = 4,
   a_ME_CTES_B64_OFF = 10, a_ME_CTES_B64_LEN = 6,
   a_ME_CTES_QP_OFF = 17,  a_ME_CTES_QP_LEN = 16,
   a_ME_CTES_BIN_OFF = 34, a_ME_CTES_BIN_LEN = 6,

   a_ME_CTES_S8B_OFF = 41, a_ME_CTES_S8B_LEN = 2,
   a_ME_CTES_SB64_OFF = 44, a_ME_CTES_SB64_LEN = 3,
   a_ME_CTES_SQP_OFF = 48, a_ME_CTES_SQP_LEN = 2
};

/* Check whether *s must be quoted according to flags, else body rules;
 * sol indicates whether we are at the first character of a line/field */
su_SINLINE enum a_me_qact a_me_mustquote(char const *s, char const *e,
                           boole sol, enum mime_enc_flags flags);

/* Trim WS and make work point to the decodable range of in.
 * Return the amount of bytes a b64_decode operation on that buffer requires,
 * or UZ_MAX on overflow error */
static uz a_me_b64_decode_prepare(struct str *work, struct str const *in);

/* Perform b64_decode on in(put) to sufficiently spaced out(put).
 * Return number of useful bytes in out or -1 on error.
 * Note: may enter endless loop if in->l < 4 and 0 return is not handled! */
static sz a_me_b64_decode(struct str *out, struct str *in);

su_SINLINE enum a_me_qact
a_me_mustquote(char const *s, char const *e, boole sol,
      enum mime_enc_flags flags){
   u8 const *qtab;
   enum a_me_qact a, r;
   NYD2_IN;

   qtab = (flags & (MIMEEF_ISHEAD | MIMEEF_ISENCWORD))
         ? a_me_qp_head : a_me_qp_body;

   if((u8)*s > 0x7F){
      r = a_ME_Q;
      goto jleave;
   }

   a = qtab[(u8)*s];

   if((r = a) == a_ME_N || a == a_ME_Q)
      goto jleave;

   r = a_ME_Q;

   /* Special header fields */
   if(flags & (MIMEEF_ISHEAD | MIMEEF_ISENCWORD)){
      /* Special massage for encoded words */
      if(flags & MIMEEF_ISENCWORD){
         switch(a){
         case a_ME_HT:
         case a_ME_US:
         case a_ME_EQ:
            r = a;
            /* FALLTHRU */
         case a_ME_UU:
            goto jleave;
         default:
            break;
         }
      }

      /* Treat '?' only special if part of '=?' .. '?=' (still too much quoting
       * since it's '=?CHARSET?CTE?stuff?=', and especially the trailing ?=
       * should be hard to match */
      if(a == a_ME_QM && ((!sol && s[-1] == '=') || (s < e && s[1] == '=')))
         goto jleave;
      goto jnquote;
   }

   /* Body-only */

   if(a == a_ME_SP){
      /* WS only if trailing white space */
      if(&s[1] == e || s[1] == '\n')
         goto jleave;
      goto jnquote;
   }

   /* Rest are special begin-of-line cases */
   if(!sol)
      goto jnquote;

   /* ^From */
   if(a == a_ME_XF){
      if(&s[4] < e && s[1] == 'r' && s[2] == 'o' && s[3] == 'm' && s[4] == ' ')
         goto jleave;
      goto jnquote;
   }
   /* ^.$ */
   if(a == a_ME_XD && (&s[1] == e || s[1] == '\n'))
      goto jleave;
jnquote:
   r = 0;
jleave:
   NYD2_OU;
   return r;
}

static uz
a_me_b64_decode_prepare(struct str *work, struct str const *in){
   uz cp_len;
   NYD2_IN;

   *work = *in;
   cp_len = n_str_trim(work, n_STR_TRIM_BOTH)->l;

   if(cp_len > 16){
      /* su_ERR_OVERFLOW */
      if(UZ_MAX / 3 <= cp_len){
         cp_len = UZ_MAX;
         goto jleave;
      }
      cp_len = ((cp_len * 3) >> 2) + (cp_len >> 3);
   }
   cp_len += (2 * 3) +1;
jleave:
   NYD2_OU;
   return cp_len;
}

static sz
a_me_b64_decode(struct str *out, struct str *in){
   u8 *p, pb;
   u8 const *q, *end;
   sz rv;
   NYD2_IN;

   rv = -1;
   p = (u8*)&out->s[out->l];
   q = (u8 const*)in->s;

   for(end = &q[in->l]; P2UZ(end - q) >= 4; q += 4){
      u32 a, b, c, d;

      a = a_ME_B64_DECUI8(q[0]);
      b = a_ME_B64_DECUI8(q[1]);
      c = a_ME_B64_DECUI8(q[2]);
      d = a_ME_B64_DECUI8(q[3]);

      if(UNLIKELY(a >= a_ME_B64_EQU || b >= a_ME_B64_EQU ||
            c == a_ME_B64_BAD || d == a_ME_B64_BAD))
         goto jleave;

      pb = ((a << 2) | ((b & 0x30) >> 4));
      if(pb != (u8)'\r' || !(n_pstate & n_PS_BASE64_STRIP_CR))
         *p++ = pb;

      if(c == a_ME_B64_EQU){ /* got '=' */
         q += 4;
         if(UNLIKELY(d != a_ME_B64_EQU))
            goto jleave;
         break;
      }

      pb = (((b & 0x0F) << 4) | ((c & 0x3C) >> 2));
      if(pb != (u8)'\r' || !(n_pstate & n_PS_BASE64_STRIP_CR))
         *p++ = pb;

      if(d == a_ME_B64_EQU) /* got '=' */
         break;
      pb = (((c & 0x03) << 6) | d);
      if(pb != (u8)'\r' || !(n_pstate & n_PS_BASE64_STRIP_CR))
         *p++ = pb;
   }
   rv ^= rv;

jleave:{
      uz i;

      i = P2UZ((char*)p - out->s);
      out->l = i;
      if(rv == 0)
         rv = (sz)i;
   }
   in->l -= P2UZ(q - (u8*)in->s);
   in->s = n_UNCONST(q);
   NYD2_OU;
   return rv;
}

FL enum mime_enc
mime_enc_target(void){
   char const *cp, *v15;
   enum mime_enc rv;
   NYD2_IN;

   if((v15 = ok_vlook(encoding)) != NULL)
      n_OBSOLETE(_("please use *mime-encoding* instead of *encoding*"));

   if((cp = ok_vlook(mime_encoding)) == NULL && (cp = v15) == NULL)
      rv = MIME_DEFAULT_ENCODING;
   else if(!su_cs_cmp_case(cp, &a_me_ctes[a_ME_CTES_S8B_OFF]) ||
         !su_cs_cmp_case(cp, &a_me_ctes[a_ME_CTES_8B_OFF]))
      rv = MIMEE_8B;
   else if(!su_cs_cmp_case(cp, &a_me_ctes[a_ME_CTES_SB64_OFF]) ||
         !su_cs_cmp_case(cp, &a_me_ctes[a_ME_CTES_B64_OFF]))
      rv = MIMEE_B64;
   else if(!su_cs_cmp_case(cp, &a_me_ctes[a_ME_CTES_SQP_OFF]) ||
         !su_cs_cmp_case(cp, &a_me_ctes[a_ME_CTES_QP_OFF]))
      rv = MIMEE_QP;
   else{
      n_err(_("Warning: invalid *mime-encoding*, using Base64: %s\n"), cp);
      rv = MIMEE_B64;
   }
   NYD2_OU;
   return rv;
}

FL enum mime_enc
mime_enc_from_ctehead(char const *hbody){
   enum mime_enc rv;
   NYD2_IN;

   if(hbody == NULL)
      rv = MIMEE_7B;
   else{
      struct{
         u8 off;
         u8 len;
         u8 enc;
         u8 __dummy;
      } const *cte, cte_base[] = {
         {a_ME_CTES_7B_OFF, a_ME_CTES_7B_LEN, MIMEE_7B, 0},
         {a_ME_CTES_8B_OFF, a_ME_CTES_8B_LEN, MIMEE_8B, 0},
         {a_ME_CTES_B64_OFF, a_ME_CTES_B64_LEN, MIMEE_B64, 0},
         {a_ME_CTES_QP_OFF, a_ME_CTES_QP_LEN, MIMEE_QP, 0},
         {a_ME_CTES_BIN_OFF, a_ME_CTES_BIN_LEN, MIMEE_BIN, 0},
         {0, 0, MIMEE_NONE, 0}
      };
      union {char const *s; uz l;} u;

      if(*hbody == '"')
         for(u.s = ++hbody; *u.s != '\0' && *u.s != '"'; ++u.s)
            ;
      else
         for(u.s = hbody; *u.s != '\0' && !su_cs_is_white(*u.s); ++u.s)
            ;
      u.l = P2UZ(u.s - hbody);

      for(cte = cte_base;;)
         if(cte->len == u.l && !su_cs_cmp_case(&a_me_ctes[cte->off], hbody)){
            rv = cte->enc;
            break;
         }else if((++cte)->enc == MIMEE_NONE){
            rv = MIMEE_NONE;
            break;
         }
   }
   NYD2_OU;
   return rv;
}

FL char const *
mime_enc_from_conversion(enum conversion const convert){
   char const *rv;
   NYD2_IN;

   switch(convert){
   case CONV_7BIT: rv = &a_me_ctes[a_ME_CTES_7B_OFF]; break;
   case CONV_8BIT: rv = &a_me_ctes[a_ME_CTES_8B_OFF]; break;
   case CONV_TOQP: rv = &a_me_ctes[a_ME_CTES_QP_OFF]; break;
   case CONV_TOB64: rv = &a_me_ctes[a_ME_CTES_B64_OFF]; break;
   case CONV_NONE: rv = &a_me_ctes[a_ME_CTES_BIN_OFF]; break;
   default: rv = n_empty; break;
   }
   NYD2_OU;
   return rv;
}

FL uz
mime_enc_mustquote(char const *ln, uz lnlen, enum mime_enc_flags flags){
   uz rv;
   boole sol;
   NYD2_IN;

   for(rv = 0, sol = TRU1; lnlen > 0; sol = FAL0, ++ln, --lnlen)
      switch(a_me_mustquote(ln, ln + lnlen, sol, flags)){
      case a_ME_US:
      case a_ME_EQ:
      case a_ME_HT:
         ASSERT(flags & MIMEEF_ISENCWORD);
         /* FALLTHRU */
      case 0:
         continue;
      default:
         ++rv;
      }
   NYD2_OU;
   return rv;
}

FL uz
qp_encode_calc_size(uz len){
   uz bytes, lines;
   NYD2_IN;

   /* The worst case sequence is 'CRLF' -> '=0D=0A=\n\0'.
    * However, we must be aware that (a) the output may span multiple lines
    * and (b) the input does not end with a newline itself (nonetheless):
    *    LC_ALL=C awk 'BEGIN{
    *       for (i = 1; i < 100000; ++i) printf "\xC3\xBC"
    *    }' |
    *    s-nail -:/ -dSsendcharsets=utf8 -s testsub no@where */

   /* Several su_ERR_OVERFLOW */
   if(len >= UZ_MAX / 3){
      len = UZ_MAX;
      goto jleave;
   }
   bytes = len * 3;
   lines = bytes / QP_LINESIZE;
   len += lines;

   if(len >= UZ_MAX / 3){
      len = UZ_MAX;
      goto jleave;
   }
   /* Trailing hard NL may be missing, so there may be two lines.
    * Thus add soft + hard NL per line and a trailing NUL */
   bytes = len * 3;
   lines = (bytes / QP_LINESIZE) + 1;
   lines <<= 1;
   ++bytes;
   /*if(UZ_MAX - bytes >= lines){
      len = UZ_MAX;
      goto jleave;
   }*/
   bytes += lines;
   len = bytes;
jleave:
   NYD2_OU;
   return len;
}

#ifdef notyet
FL struct str *
qp_encode_cp(struct str *out, char const *cp, enum qpflags flags){
   struct str in;
   NYD_IN;

   in.s = n_UNCONST(cp);
   in.l = su_cs_len(cp);
   out = qp_encode(out, &in, flags);
   NYD_OU;
   return out;
}

FL struct str *
qp_encode_buf(struct str *out, void const *vp, uz vp_len,
      enum qpflags flags){
   struct str in;
   NYD_IN;

   in.s = n_UNCONST(vp);
   in.l = vp_len;
   out = qp_encode(out, &in, flags);
   NYD_OU;
   return out;
}
#endif /* notyet */

FL struct str *
qp_encode(struct str *out, struct str const *in, enum qpflags flags){
   uz lnlen;
   char *qp;
   char const *is, *ie;
   boole sol, seenx;
   NYD_IN;

   sol = (flags & QP_ISHEAD ? FAL0 : TRU1);

   if(!(flags & QP_BUF)){
      if((lnlen = qp_encode_calc_size(in->l)) == UZ_MAX){
         out = NULL;
         goto jerr;
      }
      out->s = (flags & QP_SALLOC) ? n_autorec_alloc(lnlen)
            : n_realloc(out->s, lnlen);
   }
   qp = out->s;
   is = in->s;
   ie = is + in->l;

   if(flags & QP_ISHEAD){
      enum mime_enc_flags ef;

      ef = MIMEEF_ISHEAD | (flags & QP_ISENCWORD ? MIMEEF_ISENCWORD : 0);

      for(seenx = FAL0, sol = TRU1; is < ie; sol = FAL0, ++qp){
         char c;
         enum a_me_qact mq;

         mq = a_me_mustquote(is, ie, sol, ef);
         c = *is++;

         if(mq == a_ME_N){
            /* We convert into a single *encoded-word*, that'll end up in
             * =?C?Q??=; quote '?' from when we're inside there on */
            if(seenx && c == '?')
               goto jheadq;
            *qp = c;
         }else if(mq == a_ME_US)
            *qp = a_ME_US;
         else{
            seenx = TRU1;
jheadq:
            *qp++ = '=';
            qp = n_c_to_hex_base16(qp, c) + 1;
         }
      }
      goto jleave;
   }

   /* The body needs to take care for soft line breaks etc. */
   for(lnlen = 0, seenx = FAL0; is < ie; sol = FAL0){
      char c;
      enum a_me_qact mq;

      mq = a_me_mustquote(is, ie, sol, MIMEEF_NONE);
      c = *is++;

      if(mq == a_ME_N && (c != '\n' || !seenx)){
         *qp++ = c;
         if(++lnlen < QP_LINESIZE - 1)
            continue;
         /* Don't write a soft line break when we're in the last possible
          * column and either an LF has been written or only an LF follows, as
          * that'll end the line anyway */
         /* XXX but - ensure is+1>=ie, then??
          * xxx and/or - what about resetting lnlen; that contra
          * xxx dicts input==1 input line ASSERTion, though */
         if(c == '\n' || is == ie || is[0] == '\n' || is[1] == '\n')
            continue;
jsoftnl:
         qp[0] = '=';
         qp[1] = '\n';
         qp += 2;
         lnlen = 0;
         continue;
      }

      if(lnlen > QP_LINESIZE - 3 - 1){
         qp[0] = '=';
         qp[1] = '\n';
         qp += 2;
         lnlen = 0;
      }
      *qp++ = '=';
      qp = n_c_to_hex_base16(qp, c);
      qp += 2;
      lnlen += 3;
      if(c != '\n' || !seenx)
         seenx = (c == '\r');
      else{
         seenx = FAL0;
         goto jsoftnl;
      }
   }

   /* Enforce soft line break if we haven't seen LF */
   if(in->l > 0 && *--is != '\n'){
      qp[0] = '=';
      qp[1] = '\n';
      qp += 2;
   }
jleave:
   out->l = P2UZ(qp - out->s);
   out->s[out->l] = '\0';
jerr:
   NYD_OU;
   return out;
}

FL boole
qp_decode_header(struct str *out, struct str const *in){
   struct n_string s;
   char const *is, *ie;
   NYD_IN;

   /* su_ERR_OVERFLOW */
   if(UZ_MAX -1 - out->l <= in->l ||
         S32_MAX <= out->l + in->l){ /* XXX wrong, we may replace */
      out->l = 0;
      out = NULL;
      goto jleave;
   }

   n_string_creat(&s);
   n_string_reserve(n_string_take_ownership(&s, out->s,
         (out->l == 0 ? 0 : out->l +1), out->l),
      in->l + (in->l >> 2));

   for(is = in->s, ie = &is[in->l - 1]; is <= ie;){
      s32 c;

      c = *is++;
      if(c == '='){
         if(is >= ie){
            goto jpushc; /* TODO According to RFC 2045, 6.7,
            * ++is; TODO we should warn the user, but have no context
            * goto jehead; TODO to do so; can't over and over */
         }else if((c = n_c_from_hex_base16(is)) >= 0){
            is += 2;
            goto jpushc;
         }else{
            /* Invalid according to RFC 2045, section 6.7 */
            /* TODO Follow RFC 2045, 6.7 advise and simply put through */
            c = '=';
            goto jpushc;
/* TODO jehead:
 * TODO      if(n_psonce & n_PSO_UNICODE)
 *              n_string_push_buf(&s, su_utf_8_replacer,
 *                 sizeof(su_utf_8_replacer) -1);
 * TODO       else{
 * TODO          c = '?';
 * TODO          goto jpushc;
 * TODO       }*/
         }
      }else{
jpushc:
         if(c == '_' /* a_ME_US */)
            c = ' ';
         n_string_push_c(&s, (char)c);
      }
   }

   out->s = n_string_cp(&s);
   out->l = s.s_len;
   n_string_gut(n_string_drop_ownership(&s));
jleave:
   NYD_OU;
   return (out != NULL);
}

FL boole
qp_decode_part(struct str *out, struct str const *in, struct str *outrest,
      struct str *inrest_or_null){
   struct n_string s_b, *s;
   char const *is, *ie;
   NYD_IN;

   if(outrest->l != 0){
      is = out->s;
      *out = *outrest;
      outrest->s = n_UNCONST(is);
      outrest->l = 0;
   }

   /* su_ERR_OVERFLOW */
   if(UZ_MAX -1 - out->l <= in->l ||
         S32_MAX <= out->l + in->l) /* XXX wrong, we may replace */
      goto jerr;

   s = n_string_creat(&s_b);
   s = n_string_take_ownership(s, out->s,
         (out->l == 0 ? 0 : out->l +1), out->l);
   s = n_string_reserve(s, in->l + (in->l >> 2));

   for(is = in->s, ie = &is[in->l - 1]; is <= ie;){
      s32 c;

      if((c = *is++) != '='){
jpushc:
         n_string_push_c(s, (char)c);
         continue;
      }

      /* RFC 2045, 6.7:
       *   Therefore, when decoding a Quoted-Printable body, any
       *   trailing white space on a line must be deleted, as it will
       *   necessarily have been added by intermediate transport
       *   agents */
      for(; is <= ie && su_cs_is_blank(*is); ++is)
         ;
      if(is >= ie){
         /* Soft line break? */
         if(*is == '\n')
            goto jsoftnl;
        goto jpushc; /* TODO According to RFC 2045, 6.7,
         * ++is; TODO we should warn the user, but have no context
         * goto jebody; TODO to do so; can't over and over */
      }

      /* Not a soft line break? */
      if(*is != '\n'){
         if((c = n_c_from_hex_base16(is)) >= 0){
            is += 2;
            goto jpushc;
         }
         /* Invalid according to RFC 2045, section 6.7 */
         /* TODO Follow RFC 2045, 6.7 advise and simply put through */
         c = '=';
         goto jpushc;
/* TODO jebody:
 * TODO   if(n_psonce & n_PSO_UNICODE)
 *           n_string_push_buf(&s, su_utf_8_replacer,
 *              sizeof(su_utf_8_replacer) -1);
 * TODO    else{
 * TODO       c = '?';
 * TODO       goto jpushc;
 * TODO    }*/
      }

      /* CRLF line endings are encoded as QP, followed by a soft line break, so
       * check for this special case, and simply forget we have seen one, so as
       * not to end up with the entire DOS file in a contiguous buffer */
jsoftnl:
      if(s->s_len > 0 && s->s_dat[s->s_len - 1] == '\n'){
#if 0       /* TODO qp_decode_part() we do not normalize CRLF
          * TODO to LF because for that we would need
          * TODO to know if we are about to write to
          * TODO the display or do save the file!
          * TODO 'hope the MIME/send layer rewrite will
          * TODO offer the possibility to DTRT */
         if(s->s_len > 1 && s->s_dat[s->s_len - 2] == '\r')
            n_string_push_c(n_string_trunc(s, s->s_len - 2), '\n');
#endif
         break;
      }

      /* C99 */{
         char *cp;
         uz l;

         if((l = P2UZ(ie - is)) > 0){
            if(inrest_or_null == NULL)
               goto jerr;
            n_str_assign_buf(inrest_or_null, is, l);
         }
         cp = outrest->s;
         outrest->s = n_string_cp(s);
         outrest->l = s->s_len;
         n_string_drop_ownership(s);
         if(cp != NULL)
            n_free(cp);
      }
      break;
   }

   out->s = n_string_cp(s);
   out->l = s->s_len;
   n_string_gut(n_string_drop_ownership(s));
jleave:
   NYD_OU;
   return (out != NULL);
jerr:
   out->l = 0;
   out = NULL;
   goto jleave;
}

FL uz
b64_encode_calc_size(uz len){
   NYD2_IN;
   if(len >= UZ_MAX / 4)
      len = UZ_MAX;
   else{
      len = (len * 4) / 3;
      len += (((len / B64_ENCODE_INPUT_PER_LINE) + 1) * 3);
      len += 2 + 1; /* CRLF, \0 */
   }
   NYD2_OU;
   return len;
}

FL struct str *
b64_encode(struct str *out, struct str const *in, enum b64flags flags){
   u8 const *p;
   uz i, lnlen;
   char *b64;
   NYD_IN;

   ASSERT(!(flags & B64_NOPAD) ||
      !(flags & (B64_CRLF | B64_LF | B64_MULTILINE)));

   p = (u8 const*)in->s;

   if(!(flags & B64_BUF)){
      if((i = b64_encode_calc_size(in->l)) == UZ_MAX){
         out = NULL;
         goto jleave;
      }
      out->s = (flags & B64_SALLOC) ? n_autorec_alloc(i)
            : n_realloc(out->s, i);
   }
   b64 = out->s;

   if(!(flags & (B64_CRLF | B64_LF)))
      flags &= ~B64_MULTILINE;

   for(lnlen = 0, i = in->l; (sz)i > 0; p += 3, i -= 3){
      u32 a, b, c;

      a = p[0];
      b64[0] = a_me_b64_enctbl[a >> 2];

      switch(i){
      case 1:
         b64[1] = a_me_b64_enctbl[((a & 0x3) << 4)];
         b64[2] =
         b64[3] = '=';
         break;
      case 2:
         b = p[1];
         b64[1] = a_me_b64_enctbl[((a & 0x03) << 4) | ((b & 0xF0u) >> 4)];
         b64[2] = a_me_b64_enctbl[((b & 0x0F) << 2)];
         b64[3] = '=';
         break;
      default:
         b = p[1];
         c = p[2];
         b64[1] = a_me_b64_enctbl[((a & 0x03) << 4) | ((b & 0xF0u) >> 4)];
         b64[2] = a_me_b64_enctbl[((b & 0x0F) << 2) | ((c & 0xC0u) >> 6)];
         b64[3] = a_me_b64_enctbl[c & 0x3F];
         break;
      }

      b64 += 4;
      if(!(flags & B64_MULTILINE))
         continue;
      lnlen += 4;
      if(lnlen < B64_LINESIZE)
         continue;

      lnlen = 0;
      if(flags & B64_CRLF)
         *b64++ = '\r';
      if(flags & (B64_CRLF | B64_LF))
         *b64++ = '\n';
   }

   if((flags & (B64_CRLF | B64_LF)) &&
         (!(flags & B64_MULTILINE) || lnlen != 0)){
      if(flags & B64_CRLF)
         *b64++ = '\r';
      if(flags & (B64_CRLF | B64_LF))
         *b64++ = '\n';
   }else if(flags & B64_NOPAD)
      while(b64 != out->s && b64[-1] == '=')
         --b64;

   out->l = P2UZ(b64 - out->s);
   out->s[out->l] = '\0';

   /* Base64 includes + and /, replace them with _ and -.
    * This is base64url according to RFC 4648, then.  Since we only support
    * that for encoding and it is only used for boundary strings, this is
    * yet a primitive implementation; xxx use tables; support decoding */
   if(flags & B64_RFC4648URL){
      char c;

      for(b64 = out->s; (c = *b64) != '\0'; ++b64)
         if(c == '+')
            *b64 = '-';
         else if(c == '/')
               *b64 = '_';
   }
jleave:
   NYD_OU;
   return out;
}

FL struct str *
b64_encode_buf(struct str *out, void const *vp, uz vp_len,
      enum b64flags flags){
   struct str in;
   NYD_IN;

   in.s = n_UNCONST(vp);
   in.l = vp_len;
   out = b64_encode(out, &in, flags);
   NYD_OU;
   return out;
}

#ifdef notyet
FL struct str *
b64_encode_cp(struct str *out, char const *cp, enum b64flags flags){
   struct str in;
   NYD_IN;

   in.s = n_UNCONST(cp);
   in.l = su_cs_len(cp);
   out = b64_encode(out, &in, flags);
   NYD_OU;
   return out;
}
#endif /* notyet */

FL boole
b64_decode(struct str *out, struct str const *in){
   struct str work;
   uz len;
   NYD_IN;

   out->l = 0;

   if((len = a_me_b64_decode_prepare(&work, in)) == UZ_MAX)
      goto jerr;

   /* Ignore an empty input, as may happen for an empty final line */
   if(work.l == 0)
      out->s = n_realloc(out->s, 1);
   else if(work.l >= 4 && !(work.l & 3)){
      out->s = n_realloc(out->s, len +1);
      if((sz)(len = a_me_b64_decode(out, &work)) < 0)
         goto jerr;
   }else
      goto jerr;
   out->s[out->l] = '\0';
jleave:
   NYD_OU;
   return (out != NULL);
jerr:
   out = NULL;
   goto jleave;
}

FL boole
b64_decode_header(struct str *out, struct str const *in){
   struct str outr, inr;
   NYD_IN;

   if(!b64_decode(out, in)){
      su_mem_set(&outr, 0, sizeof outr);
      su_mem_set(&inr, 0, sizeof inr);

      if(!b64_decode_part(out, in, &outr, &inr) || outr.l > 0 || inr.l > 0)
         out = NULL;

      if(inr.s != NULL)
         n_free(inr.s);
      if(outr.s != NULL)
         n_free(outr.s);
   }
   NYD_OU;
   return (out != NULL);
}

FL boole
b64_decode_part(struct str *out, struct str const *in, struct str *outrest,
      struct str *inrest_or_null){
   struct str work, save;
   u32 a, b, c, b64l;
   char ca, cb, cc, cx;
   struct n_string s, workbuf;
   uz len;
   NYD_IN;

   n_string_creat(&s);
   if((len = out->l) > 0 && out->s[len] == '\0')
      (void)n_string_take_ownership(&s, out->s, len +1, len);
   else{
      if(len > 0)
         n_string_push_buf(&s, out->s, len);
      if(out->s != NULL)
         n_free(out->s);
   }
   out->s = NULL, out->l = 0;
   n_string_creat(&workbuf);

   if((len = a_me_b64_decode_prepare(&work, in)) == UZ_MAX)
      goto jerr;

   if(outrest->l > 0){
      n_string_push_buf(&s, outrest->s, outrest->l);
      outrest->l = 0;
   }

   /* su_ERR_OVERFLOW */
   if(UZ_MAX - len <= s.s_len ||
         S32_MAX <= len + s.s_len) /* XXX wrong, we may replace */
      goto jerr;

   if(work.l == 0)
      goto jok;

   /* This text decoder is extremely expensive, especially given that in all
    * but _invalid_ cases it is not even needed!  So try once to do the normal
    * decoding, if that fails, go the hard way */
   save = work;
   out->s = n_string_resize(&s, len + (out->l = b64l = s.s_len))->s_dat;

   if(work.l >= 4 && a_me_b64_decode(out, &work) >= 0){
      n_string_trunc(&s, out->l);
      if(work.l == 0)
         goto jok;
   }

   n_string_trunc(&s, b64l);
   work = save;
   out->s = NULL, out->l = 0;

   /* TODO b64_decode_part() does not yet STOP if it sees padding, whereas
    * TODO OpenSSL and mutt simply bail on such stuff */
   UNINIT(ca, 0); UNINIT(a, 0);
   UNINIT(cb, 0); UNINIT(b, 0);
   UNINIT(cc, 0); UNINIT(c, 0);
   for(b64l = 0;;){
      u32 x;

      x = a_ME_B64_DECUI8((u8)(cx = *work.s));
      switch(b64l){
      case 0:
         if(x >= a_ME_B64_EQU)
            goto jrepl;
         ca = cx;
         a = x;
         ++b64l;
         break;
      case 1:
         if(x >= a_ME_B64_EQU)
            goto jrepl;
         cb = cx;
         b = x;
         ++b64l;
         break;
      case 2:
         if(x == a_ME_B64_BAD)
            goto jrepl;
         cc = cx;
         c = x;
         ++b64l;
         break;
      case 3:
         if(x == a_ME_B64_BAD){
jrepl:
            /* TODO This would be wrong since iconv(3) may be applied first! */
            n_err(_("Invalid base64 encoding ignored\n"));
#if 0
            if(n_psonce & n_PSO_UNICODE)
               n_string_push_buf(&s, su_utf_8_replacer,
                  sizeof(su_utf_8_replacer) -1);
            else
               n_string_push_c(&s, '?');
#endif
            ;
         }else if(c == a_ME_B64_EQU && x != a_ME_B64_EQU){
            /* This is not only invalid but bogus.  Skip it over! */
            /* TODO This would be wrong since iconv(3) may be applied first! */
            n_err(_("Illegal base64 encoding ignored\n"));
#if 0
            n_string_push_buf(&s, su_UTF_8_REPLACER su_UTF_8_REPLACEMENT
               su_UTF_8_REPLACER su_UTF_8_REPLACEMENT,
               (sizeof(su_UTF_8_REPLACER) -1) * 4);
#endif
            b64l = 0;
         }else{
            u8 pb;

            pb = ((a << 2) | ((b & 0x30) >> 4));
            if(pb != (u8)'\r' || !(n_pstate & n_PS_BASE64_STRIP_CR))
               n_string_push_c(&s, (char)pb);
            pb = (((b & 0x0F) << 4) | ((c & 0x3C) >> 2));
            if(pb != (u8)'\r' || !(n_pstate & n_PS_BASE64_STRIP_CR))
               n_string_push_c(&s, (char)pb);
            if(x != a_ME_B64_EQU){
               pb = (((c & 0x03) << 6) | x);
               if(pb != (u8)'\r' || !(n_pstate & n_PS_BASE64_STRIP_CR))
                  n_string_push_c(&s, (char)pb);
            }
            ++b64l;
         }
         break;
      }

      ++work.s;
      if(--work.l == 0){
         if(b64l > 0 && b64l != 4){
            if(inrest_or_null == NULL)
               goto jerr;
            inrest_or_null->s = n_realloc(inrest_or_null->s, b64l +1);
            inrest_or_null->s[0] = ca;
            if(b64l > 1)
               inrest_or_null->s[1] = cb;
            if(b64l > 2)
               inrest_or_null->s[2] = cc;
            inrest_or_null->s[inrest_or_null->l = b64l] = '\0';
         }
         goto jok;
      }
      if(b64l == 4)
         b64l = 0;
   }

jok:
   out->s = n_string_cp(&s);
   out->l = s.s_len;
   n_string_drop_ownership(&s);
jleave:
   n_string_gut(&workbuf);
   n_string_gut(&s);
   NYD_OU;
   return (out != NULL);
jerr:
   out = NULL;
   goto jleave;
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/mx/mime-param.c000066400000000000000000000714511352610246600164770ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ MIME parameter handling.
 *
 * Copyright (c) 2016 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE mime_param
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

#include 
#include 
#include 

#include "mx/random.h"

#include "mx/iconv.h"

/* TODO fake */
#include "su/code-in.h"

struct rfc2231_joiner {
   struct rfc2231_joiner *rj_next;
   u32      rj_no;            /* Continuation number */
   u32      rj_len;           /* of useful data in .rj_dat */
   u32      rj_val_off;       /* Start of value data therein */
   u32      rj_cs_len;        /* Length of charset part */
   boole      rj_is_enc;        /* Is percent encoded */
   u8       __pad[7];
   char const  *rj_dat;
};

struct mime_param_builder {
   struct mime_param_builder *mpb_next;
   struct str  *mpb_result;
   u32      mpb_level;        /* of recursion (<-> continuation number) */
   u32      mpb_name_len;     /* of the parameter .mpb_name */
   u32      mpb_value_len;    /* of remaining value */
   u32      mpb_charset_len;  /* of .mpb_charset (iff in outermost level) */
   u32      mpb_buf_len;      /* Usable result of this level in .mpb_buf */
   boole      mpb_is_enc;       /* Level requires encoding */
   u8       __dummy[1];
   boole      mpb_is_utf8;      /* Encoding is UTF-8 */
   s8       mpb_rv;
   char const  *mpb_name;
   char const  *mpb_value;       /* Remains of, once the level was entered */
   char const  *mpb_charset;     /* *ttycharset* */
   char        *mpb_buf;         /* Pointer to on-stack buffer */
};

/* All ASCII characters which cause RFC 2231 to be applied XXX check -1 slots*/
static boole const        _rfc2231_etab[] = {
    1, 1, 1, 1,  1, 1, 1, 1,  1, 1,-1,-1,  1,-1, 1, 1,   /* NUL..SI */
    1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1,   /* DLE..US */
    1, 0, 1, 0,  0, 1, 0, 1,  1, 1, 1, 0,  1, 0, 0, 1,   /* CAN.. / */
    0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 1, 1,  1, 1, 1, 1,   /*   0.. ? */

    1, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,   /*   @.. O */
    0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 1,  1, 1, 0, 0,   /*   P.. _ */
    0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,   /*   `.. o */
    0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 1,   /*   p..DEL */
};

/* In a headerbody, at a "param=XY" that we're not interested in, skip over the
 * entire construct, return pointer to the first byte thereafter or to NUL */
static char const * _mime_param_skip(char const *hbp);

/* Trim value, which points to after the "name[RFC 2231 stuff]=".
 * On successful return (1,-1; -1 is returned if the value was quoted via
 * double quotation marks) a set end_or_null points to after the value and any
 * possible separator and result->s is the autorec_alloc()d normalized value */
static s8      _mime_param_value_trim(struct str *result, char const *start,
                     char const **end_or_null);

/* mime_param_get() found the desired parameter but it seems to use RFC 2231
 * extended syntax: perform full RFC 2231 parsing starting at this point.
 * Note that _join() returns is-error */
static char *     _rfc2231_param_parse(char const *param, uz plen,
                     char const *hbp);
static boole     __rfc2231_join(struct rfc2231_joiner *head, char **result,
                     char const **emsg);

/* Recursive parameter builder.  Note we have a magic limit of 999 levels.
 * Prepares a portion of output in self->mpb_buf;
 * once >mpb_value is worked completely the deepmost level joins the result
 * into >mpb_result and unrolls the stack. */
static void       _mime_param_create(struct mime_param_builder *self);
static void       __mime_param_join(struct mime_param_builder *head);

static char const *
_mime_param_skip(char const *hbp)
{
   char co, cn;
   NYD2_IN;

   /* Skip over parameter name - note we may have skipped over an entire
    * parameter name and thus point to a "="; i haven't yet truly checked
    * against MIME RFCs, just test for ";" in the meanwhile XXX */
   while ((cn = *hbp) != '\0' && cn != '=' && cn != ';')
      ++hbp;
   if (cn == '\0')
      goto jleave;
   ++hbp;
   if (cn == ';')
      goto jleave;

   while (su_cs_is_white((cn = *hbp))) /* XXX */
      ++hbp;
   if (cn == '\0')
      goto jleave;

   if (cn == '"') {
      co = '\0';
      while ((cn = *++hbp) != '\0' && (cn != '"' || co == '\\'))
         co = (co == '\\') ? '\0' : cn;
      if (cn != '\0' && (cn = *++hbp) == ';')
         ++hbp;
   } else {
      for (;; cn = *++hbp)
         if (cn == '\0' || cn == ';' || su_cs_is_white(cn))
            break;
      if (cn != '\0')
         ++hbp;
   }
jleave:
   NYD2_OU;
   return hbp;
}

static s8
_mime_param_value_trim(struct str *result, char const *start,
   char const **end_or_null)
{
   char const *e;
   char co, cn;
   uz i;
   s8 rv;
   NYD2_IN;

   while (su_cs_is_white(*start)) /* XXX? */
      ++start;

   if (*start == '"') {
      for (co = '\0', e = ++start;; ++e)
         if ((cn = *e) == '\0')
            goto jerr;
         else if (cn == '"' && co != '\\')
            break;
         else if (cn == '\\' && co == '\\')
            co = '\0';
         else
            co = cn;
      i = P2UZ(e++ - start);
      rv = -TRU1;
   } else {
      for (e = start; (cn = *e) != '\0' && !su_cs_is_white(cn) && cn != ';';
            ++e)
         ;
      i = P2UZ(e - start);
      rv = TRU1;
   }

   result->s = n_autorec_alloc(i +1);
   if (rv > 0) {
      su_mem_copy(result->s, start, result->l = i);
      result->s[i] = '\0';
   } else {
      uz j;
      char *cp;

      for (j = 0, cp = result->s, co = '\0'; i-- > 0; co = cn) {
         cn = *start++;
         if (cn != '\\' || co == '\\') {
            cp[j++] = cn;
            if (cn == '\\')
               cn = '\0';
         }
      }
      cp[j] = '\0';

      result->s = cp;
      result->l = j;
   }

   if (end_or_null != NULL) {
      while (*e != '\0' && *e == ';')
         ++e;
      *end_or_null = e;
   }
jleave:
   NYD2_OU;
   return rv;
jerr:
   rv = FAL0;
   goto jleave;
}

static char *
_rfc2231_param_parse(char const *param, uz plen, char const *hbp)
{
   /* TODO Do it for real and unite with mime_param_get() */
   struct str xval;
   char nobuf[32], *eptr, *rv = NULL, c;
   char const *hbp_base, *cp, *emsg = NULL;
   struct rfc2231_joiner *head = NULL, *np;
   boole errors = FAL0;
   uz i;
   NYD2_IN;

   /* We were called by mime_param_get() after a param name match that
    * involved "*", so jump to the matching code */
   hbp_base = hbp;
   goto jumpin;

   for (; *hbp != '\0'; hbp_base = hbp) {
      while (su_cs_is_white(*hbp))
         ++hbp;

      if (!su_cs_cmp_case_n(hbp, param, plen)) {
         hbp += plen;
         while (su_cs_is_white(*hbp))
            ++hbp;
         if (*hbp++ != '*')
               goto jerr;

         /* RFC 2231 extensions: "NAME[*DIGITS][*]=", where "*DIGITS" indicates
          * parameter continuation and the lone asterisk "*" percent encoded
          * values -- if encoding is used the "*0" or lone parameter value
          * MUST be encoded and start with a "CHARSET'LANGUAGE'" construct,
          * where both of CHARSET and LANGUAGE are optional (we do effectively
          * generate error if CHARSET is missing though).
          * Continuations may not use that "C'L'" construct, but be tolerant
          * and ignore those.  Also encoded and non-encoded continuations may
          * occur, i.e., perform percent en-/decoding only as necessary.
          * Continuations may occur in any order */
         /* xxx RFC 2231 parsing ignores language tags */
jumpin:
         for (cp = hbp; su_cs_is_digit(*cp); ++cp)
            ;
         i = P2UZ(cp - hbp);
         if (i != 0) {
            if (i >= sizeof(nobuf)) {
               emsg = N_("too many digits to form a valid number");
               goto jerr;
            } else if ((c = *cp) != '=' && c != '*') {
               emsg = N_("expected = or * after leading digits");
               goto jerr;
            }
            su_mem_copy(nobuf, hbp, i);
            nobuf[i] = '\0';
            if((su_idec_uz_cp(&i, nobuf, 10, NULL
                     ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
                  ) != su_IDEC_STATE_CONSUMED || i >= 999){
               emsg = N_("invalid continuation sequence number");
               goto jerr;
            }
            hbp = ++cp;

            /* Value encoded? */
            if (c == '*') {
               if (*hbp++ != '=')
                  goto jeeqaaster;
            } else if (c != '=') {
jeeqaaster:
               emsg = N_("expected = after asterisk *");
               goto jerr;
            }
         } else {
            /* In continuation mode that is an error, however */
            if (head != NULL) {
               emsg = N_("missing continuation sequence number");
               goto jerr;
            }
            /* Parameter value is encoded, may define encoding */
            c = '*';
            if (*cp != '=')
               goto jeeqaaster;
            hbp = ++cp;
            i = 0;
         }

         /* Create new node and insert it sorted; should be faster than
          * creating an unsorted list and sorting it after parsing */
         np = n_alloc(sizeof *np);
         np->rj_next = NULL;
         np->rj_no = (u32)i;
         np->rj_is_enc = (c == '*');
         np->rj_val_off = np->rj_cs_len = 0;

         if (head == NULL)
            head = np;
         else if (i < head->rj_no) {
            np->rj_next = head;
            head = np;
         } else {
            struct rfc2231_joiner *l = NULL, *x = head;

            while (x != NULL && i > x->rj_no)
               l = x, x = x->rj_next;
            if (x != NULL)
               np->rj_next = x;
            ASSERT(l != NULL);
            l->rj_next = np;
         }

         switch (_mime_param_value_trim(&xval, hbp, &cp)) {
         default:
            emsg = (c == '*') ? N_("invalid value encoding")/* XXX fake */
                  : N_("faulty value - missing closing quotation mark \"?");
            goto jerr;
         case -1:
            /* XXX if (np->is_enc && su_mem_find(np->dat, '\'', i) != NULL) {
             * XXX    emsg = N_("character set info not allowed here");
             * XXX    goto jerr;
             * XXX } */np->rj_is_enc = FAL0; /* Silently ignore */
            /* FALLTHRU */
         case 1:
            if (xval.l >= U32_MAX) {
               emsg = N_("parameter value too long");
               goto jerr;
            }
            np->rj_len = (u32)xval.l;
            np->rj_dat = xval.s;
            break;
         }

         /* Watch out for character set and language info */
         if (np->rj_is_enc &&
               (eptr = su_mem_find(xval.s, '\'', xval.l)) != NULL) {
            np->rj_cs_len = P2UZ(eptr - xval.s);
            if ((eptr = su_mem_find(eptr + 1, '\'', xval.l - np->rj_cs_len - 1)
                  ) == NULL) {
               emsg = N_("faulty RFC 2231 parameter extension");
               goto jerr;
            }
            np->rj_val_off = P2UZ(++eptr - xval.s);
         }

         hbp = cp;
      } else
         hbp = _mime_param_skip(hbp);
   }
   ASSERT(head != NULL); /* (always true due to jumpin:, but..) */

   errors |= __rfc2231_join(head, &rv, &emsg);
   if (errors /*&& (n_poption & n_PO_D_V)*/) {
      /* TODO should set global flags so that at the end of an operation
       * TODO (for a message) a summary can be printed: faulty MIME, xy */
      if (emsg == NULL)
         emsg = N_("multiple causes");
      n_err(_("Message had MIME errors: %s\n"), V_(emsg));
   }
jleave:
   NYD2_OU;
   return rv;

jerr:
   while ((np = head) != NULL) {
      head = np->rj_next;
      n_free(np);
   }
   /*if (n_poption & n_PO_D_V)*/ {
      if (emsg == NULL)
         emsg = N_("expected asterisk *");
      n_err(_("Faulty RFC 2231 MIME parameter value: %s: %s\n"
         "Near: %s\n"), param, V_(emsg), hbp_base);
   }
   rv = NULL;
   goto jleave;
}

static boole
__rfc2231_join(struct rfc2231_joiner *head, char **result, char const **emsg)
{
   struct str sin, sou;
   struct rfc2231_joiner *np;
   char const *cp;
   uz i;
   enum {
      _NONE       = 0,
      _HAVE_ENC   = 1<<0,
      _HAVE_ICONV = 1<<1,
      _SEEN_ANY   = 1<<2,
      _ERRORS     = 1<<3
   } f = _NONE;
   u32 no;
#ifdef mx_HAVE_ICONV
   iconv_t fhicd;
#endif
   NYD2_IN;

#ifdef mx_HAVE_ICONV
   UNINIT(fhicd, (iconv_t)-1);

   if (head->rj_is_enc) {
      char const *tcs;

      f |= _HAVE_ENC;
      if (head->rj_cs_len == 0) {
         /* It is an error if the character set is not set, the language alone
          * cannot convert characters, let aside that we don't use it at all */
         *emsg = N_("MIME RFC 2231 invalidity: missing character set\n");
         f |= _ERRORS;
      } else if (su_cs_cmp_case_n(tcs = ok_vlook(ttycharset),
            head->rj_dat, head->rj_cs_len)) {
         char *cs = n_lofi_alloc(head->rj_cs_len +1);

         su_mem_copy(cs, head->rj_dat, head->rj_cs_len);
         cs[head->rj_cs_len] = '\0';
         if ((fhicd = n_iconv_open(tcs, cs)) != (iconv_t)-1)
            f |= _HAVE_ICONV;
         else {
            *emsg = N_("necessary character set conversion missing");
            f |= _ERRORS;
         }
         n_lofi_free(cs);
      }
   }
#endif

   if (head->rj_no != 0) {
      if (!(f & _ERRORS))
         *emsg = N_("First RFC 2231 parameter value chunk number is not 0");
      f |= _ERRORS;
   }

   for (sou.s = NULL, sou.l = 0, no = 0; (np = head) != NULL; n_free(np)) {
      head = np->rj_next;

      if (np->rj_no != no++) {
         if (!(f & _ERRORS))
            *emsg = N_("RFC 2231 parameter value chunks are not contiguous");
         f |= _ERRORS;
      }

      /* RFC 2231 allows such info only in the first continuation, and
       * furthermore MUSTs the first to be encoded, then */
      if (/*np->rj_is_enc &&*/ np->rj_val_off > 0 &&
            (f & (_HAVE_ENC | _SEEN_ANY)) != _HAVE_ENC) {
         if (!(f & _ERRORS))
            *emsg = N_("invalid redundant RFC 2231 charset/language ignored");
         f |= _ERRORS;
      }
      f |= _SEEN_ANY;

      i = np->rj_len - np->rj_val_off;
      if (!np->rj_is_enc)
         n_str_add_buf(&sou, np->rj_dat + np->rj_val_off, i);
      else {
         /* Perform percent decoding */
         sin.s = n_alloc(i +1);
         sin.l = 0;

         for (cp = np->rj_dat + np->rj_val_off; i > 0;) {
            char c;

            if ((c = *cp++) == '%') {
               s32 cc;

               if (i < 3 || (cc = n_c_from_hex_base16(cp)) < 0) {
                  if (!(f & _ERRORS))
                     *emsg = N_("invalid RFC 2231 percent encoded sequence");
                  f |= _ERRORS;
                  goto jhex_putc;
               }
               sin.s[sin.l++] = (char)cc;
               cp += 2;
               i -= 3;
            } else {
jhex_putc:
               sin.s[sin.l++] = c;
               --i;
            }
         }
         sin.s[sin.l] = '\0';

         n_str_add_buf(&sou, sin.s, sin.l);
         n_free(sin.s);
      }
   }

   /* And add character set conversion on top as necessary.
    * RFC 2231 is pragmatic: encode only mentions percent encoding and the
    * character set for the entire string ("[no] facility for using more
    * than one character set or language"), therefore "continuations may
    * contain a mixture of encoded and unencoded segments" applies to
    * a contiguous string of a single character set that has been torn in
    * pieces due to space restrictions, and it happened that some pieces
    * didn't need to be percent encoded.
    *
    * _In particular_ it therefore doesn't repeat the RFC 2047 paradigm
    * that encoded-words-are-atomic, meaning that a single character-set
    * conversion run over the final, joined, partially percent-decoded value
    * should be sufficient */
#ifdef mx_HAVE_ICONV
   if (f & _HAVE_ICONV) {
      sin.s = NULL;
      sin.l = 0;
      if (n_iconv_str(fhicd, n_ICONV_UNIDEFAULT, &sin, &sou, NULL) != 0) {
         if (!(f & _ERRORS)) /* XXX won't be reported with _UNIDFEFAULT */
            *emsg = N_("character set conversion failed on value");
         f |= _ERRORS;
      }
      n_free(sou.s);
      sou = sin;

      n_iconv_close(fhicd);
   }
#endif

   su_mem_copy(*result = n_autorec_alloc(sou.l +1), sou.s, sou.l +1);
   n_free(sou.s);
   NYD2_OU;
   return ((f & _ERRORS) != 0);
}

static void
_mime_param_create(struct mime_param_builder *self)
{
   struct mime_param_builder next;
   /* Don't use MIME_LINELEN_(MAX|LIMIT) stack buffer sizes: normally we won't
    * exceed plain MIME_LINELEN, so that this would be a factor 10 wastage.
    * On the other hand we may excess _LINELEN to avoid breaking up possible
    * multibyte sequences until sizeof(buf) is reached, but since we (a) don't
    * support stateful encodings and (b) will try to synchronize on UTF-8 this
    * problem is scarce, possibly even artificial */
   char buf[MIN(MIME_LINELEN_MAX >> 1, MIME_LINELEN * 2)],
      *bp, *bp_max, *bp_xmax, *bp_lanoenc;
   char const *vb, *vb_lanoenc;
   uz vl;
   enum {
      _NONE    = 0,
      _ISENC   = 1<<0,
      _HADRAW  = 1<<1,
      _RAW     = 1<<2
   } f = _NONE;
   NYD2_IN;
   LCTA(sizeof(buf) >= MIME_LINELEN * 2, "Buffer to small for operation");

jneed_enc:
   self->mpb_buf = bp = bp_lanoenc = buf;
   self->mpb_buf_len = 0;
   self->mpb_is_enc = ((f & _ISENC) != 0);
   vb_lanoenc = vb = self->mpb_value;
   vl = self->mpb_value_len;

   /* Configure bp_max to fit in SHOULD, bp_xmax to extent */
   bp_max = (buf + MIME_LINELEN) -
         (1 + self->mpb_name_len + sizeof("*999*='';") -1 + 2);
   bp_xmax = (buf + sizeof(buf)) -
         (1 + self->mpb_name_len + sizeof("*999*='';") -1 + 2);
   if ((f & _ISENC) && self->mpb_level == 0) {
      bp_max -= self->mpb_charset_len;
      bp_xmax -= self->mpb_charset_len;
   }
   if (PCMP(bp_max, <=, buf + sizeof("Hunky Dory"))) {
      su_DBG( n_alert("_mime_param_create(): Hunky Dory!"); )
      bp_max = buf + (MIME_LINELEN >> 1); /* And then it is SHOULD, anyway */
   }
   ASSERT(PCMP(bp_max + (4 * 3), <=, bp_xmax)); /* UTF-8 extra pad, below */

   f &= _ISENC;
   while (vl > 0) {
      union {char c; u8 uc;} u; u.c = *vb;

      f |= _RAW;
      if (!(f & _ISENC)) {
         if (u.uc > 0x7F || su_cs_is_cntrl(u.c)) { /* XXX reject _is_cntrl? */
            /* We need to percent encode this character, possibly changing
             * overall strategy, but anyway the one of this level, possibly
             * rendering invalid any output byte we yet have produced here.
             * Instead of throwing away that work just recurse if some fancy
             * magic condition is true */
             /* *However*, many tested MUAs fail to deal with parameters that
              * are splitted across "too many" fields, including ones that
              * misread RFC 2231 to allow only one digit, i.e., a maximum of
              * ten.  This is plain wrong, but that won't help their users */
            if (P2UZ(bp - buf) > /*10 (strawberry) COMPAT*/MIME_LINELEN>>1)
               goto jrecurse;
            f |= _ISENC;
            goto jneed_enc;
         }

         if (u.uc == '"' || u.uc == '\\') {
            f ^= _RAW;
            bp[0] = '\\';
            bp[1] = u.c;
            bp += 2;
         }
      } else if (u.uc > 0x7F || _rfc2231_etab[u.uc]) {
         f ^= _RAW;
         bp[0] = '%';
         n_c_to_hex_base16(bp + 1, u.c);
         bp += 3;
      }

      ++vb;
      --vl;
      if (f & _RAW) {
         f |= _HADRAW;
         vb_lanoenc = vb;
         *bp++ = u.c;
         bp_lanoenc = bp;
      }

      /* If all available space has been consumed we must split.
       * Due to compatibility reasons we must take care not to break up
       * multibyte sequences -- even though RFC 2231 rather implies that the
       * splitted value should be joined (after percent encoded fields have
       * been percent decoded) and the resulting string be treated in the
       * specified character set / language, MUAs have been seen which apply
       * the RFC 2047 encoded-words-are-atomic even to RFC 2231 values, even
       * if stateful encodings cannot truly be supported like that?!..
       *
       * So split at 7-bit character if we have seen any and the wastage isn't
       * too large; recall that we need to keep the overall number of P=V
       * values as low as possible due to compatibility reasons.
       * If we haven't seen any plain bytes be laxe and realize that bp_max
       * reflects SHOULD lines, and try to extend this as long as possible.
       * However, with UTF-8, try to backward synchronize on sequence start */
      if (bp <= bp_max)
         continue;

      if ((f & _HADRAW) && (PCMP(bp - bp_lanoenc, <=, bp_lanoenc - buf) ||
            (!self->mpb_is_utf8 &&
             P2UZ(bp_lanoenc - buf) >= (MIME_LINELEN >> 2)))) {
         bp = bp_lanoenc;
         vl += P2UZ(vb - vb_lanoenc);
         vb = vb_lanoenc;
         goto jrecurse;
      }

      if (self->mpb_is_utf8 && ((u8)(vb[-1]) & 0xC0) != 0x80) {
         bp -= 3;
         --vb;
         ++vl;
         goto jrecurse;
      }

      if (bp <= bp_xmax)
         continue;
      /* (Shit.) */
      goto jrecurse;
   }

   /* That level made the great and completed encoding.  Build result */
   self->mpb_is_enc = ((f & _ISENC) != 0);
   self->mpb_buf_len = P2UZ(bp - buf);
   __mime_param_join(self);
jleave:
   NYD2_OU;
   return;

   /* Need to recurse, take care not to excess magical limit of 999 levels */
jrecurse:
   if (self->mpb_level == 999) {
      /*if (n_poption & n_PO_D_V)*/
         n_err(_("Message RFC 2231 parameters nested too deeply!\n"));
      goto jleave;
   }

   self->mpb_is_enc = ((f & _ISENC) != 0);
   self->mpb_buf_len = P2UZ(bp - buf);

   su_mem_set(&next, 0, sizeof next);
   next.mpb_next = self;
   next.mpb_level = self->mpb_level + 1;
   next.mpb_name_len = self->mpb_name_len;
   next.mpb_value_len = vl;
   next.mpb_is_utf8 = self->mpb_is_utf8;
   next.mpb_name = self->mpb_name;
   next.mpb_value = vb;
   _mime_param_create(&next);
   goto jleave;
}

static void
__mime_param_join(struct mime_param_builder *head)
{
   char nobuf[16];
   struct mime_param_builder *np;
   uz i, ll;  DBG( uz len_max; )
   struct str *result;
   char *cp;
   enum {
      _NONE    = 0,
      _ISENC   = 1<<0,
      _ISQUOTE = 1<<1,
      _ISCONT  = 1<<2
   } f = _NONE;
   NYD2_IN;

   /* Traverse the stack upwards to find out result length (worst case).
    * Reverse the list while doing so */
   for (i = 0, np = head, head = NULL; np != NULL;) {
      struct mime_param_builder *tmp;

      i += np->mpb_buf_len + np->mpb_name_len + sizeof(" *999*=\"\";\n") -1;
      if (np->mpb_is_enc)
         f |= _ISENC;

      tmp = np->mpb_next;
      np->mpb_next = head;
      head = np;
      np = tmp;
   }
   if (f & _ISENC)
      i += head->mpb_charset_len; /* sizeof("''") -1 covered by \"\" above */
   su_DBG( len_max = i; )
   head->mpb_rv = TRU1;

   result = head->mpb_result;
   if (head->mpb_next != NULL)
      f |= _ISCONT;
   cp = result->s = n_autorec_alloc(i +1);

   for (ll = 0, np = head;;) {
      /* Name part */
      su_mem_copy(cp, np->mpb_name, i = np->mpb_name_len);
      cp += i;
      ll += i;

      if (f & _ISCONT) {
         char *cpo = cp, *nop = nobuf + sizeof(nobuf);
         u32 noi = np->mpb_level;

         *--nop = '\0';
         do
            *--nop = "0123456789"[noi % 10];
         while ((noi /= 10) != 0);

         *cp++ = '*';
         while (*nop != '\0')
            *cp++ = *nop++;

         ll += P2UZ(cp - cpo);
      }

      if ((f & _ISENC) || np->mpb_is_enc) {
         *cp++ = '*';
         ++ll;
      }
      *cp++ = '=';
      ++ll;

      /* Value part */
      if (f & _ISENC) {
         f &= ~_ISENC;
         su_mem_copy(cp, np->mpb_charset, i = np->mpb_charset_len);
         cp += i;
         cp[0] = '\'';
         cp[1] = '\'';
         cp += 2;
         ll += i + 2;
      } else if (!np->mpb_is_enc) {
         f |= _ISQUOTE;
         *cp++ = '"';
         ++ll;
      }

      su_mem_copy(cp, np->mpb_buf, i = np->mpb_buf_len);
      cp += i;
      ll += i;

      if (f & _ISQUOTE) {
         f ^= _ISQUOTE;
         *cp++ = '"';
         ++ll;
      }

      if ((np = np->mpb_next) == NULL)
         break;
      *cp++ = ';';
      ++ll;

      i = ll;
      i += np->mpb_name_len + np->mpb_buf_len + sizeof(" *999*=\"\";\n") -1;
      if (i >= MIME_LINELEN) {
         head->mpb_rv = -TRU1;
         *cp++ = '\n';
         ll = 0;
      }

      *cp++ = ' ';
      ++ll;
   }
   *cp = '\0';
   result->l = P2UZ(cp - result->s);
   ASSERT(result->l < len_max);
   NYD2_OU;
}

FL char *
mime_param_get(char const *param, char const *headerbody) /* TODO rewr. */
{
   struct str xval;
   char *rv = NULL;
   uz plen;
   char const *p;
   NYD_IN;

   plen = su_cs_len(param);
   p = headerbody;

   /* At the beginning of headerbody there is no parameter=value pair xxx */
   if (!su_cs_is_white(*p))
      goto jskip1st;

   for (;;) {
      while (su_cs_is_white(*p))
         ++p;

      if (!su_cs_cmp_case_n(p, param, plen)) {
         p += plen;
         while (su_cs_is_white(*p)) /* XXX? */
            ++p;
         switch (*p++) {
         case '*':
            rv = _rfc2231_param_parse(param, plen, p);
            goto jleave;
         case '=':
            if (!_mime_param_value_trim(&xval, p, NULL)) {
               /* XXX LOG? */
               goto jleave;
            }
            rv = xval.s;

            /* We do have a result, but some (elder) software (S-nail .
 * SPDX-License-Identifier: BSD-3-Clause
 */
/*
 * Copyright (c) 1980, 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.
 */
#undef su_FILE
#define su_FILE mime_parse
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

#include 
#include 

#include "mx/cmd-charsetalias.h"
#include "mx/file-streams.h"

/* TODO fake */
#include "su/code-in.h"

/* Fetch plain */
static char *  _mime_parse_ct_plain_from_ct(char const *cth);

static boole  _mime_parse_part(struct message *zmp, struct mimepart *ip,
                  enum mime_parse_flags mpf, int level);

static void    _mime_parse_rfc822(struct message *zmp, struct mimepart *ip,
                  enum mime_parse_flags mpf, int level);

#ifdef mx_HAVE_TLS
static void    _mime_parse_pkcs7(struct message *zmp, struct mimepart *ip,
                  enum mime_parse_flags mpf, int level);
#endif

static boole  _mime_parse_multipart(struct message *zmp,
                  struct mimepart *ip, enum mime_parse_flags mpf, int level);
static void    __mime_parse_new(struct mimepart *ip, struct mimepart **np,
                  off_t offs, int *part);
static void    __mime_parse_end(struct mimepart **np, off_t xoffs,
                  long lines);

static char *
_mime_parse_ct_plain_from_ct(char const *cth)
{
   char *rv_b, *rv;
   NYD2_IN;

   rv_b = savestr(cth);

   if ((rv = su_cs_find_c(rv_b, ';')) != NULL)
      *rv = '\0';

   rv = rv_b + su_cs_len(rv_b);
   while (rv > rv_b && su_cs_is_blank(rv[-1]))
      --rv;
   *rv = '\0';
   NYD2_OU;
   return rv_b;
}

static boole
_mime_parse_part(struct message *zmp, struct mimepart *ip,
   enum mime_parse_flags mpf, int level)
{
   char const *cp;
   boole rv = FAL0;
   NYD_IN;

   ip->m_ct_type = hfield1("content-type", (struct message*)ip);
   if (ip->m_ct_type != NULL)
      ip->m_ct_type_plain = _mime_parse_ct_plain_from_ct(ip->m_ct_type);
   else if (ip->m_parent != NULL && ip->m_parent->m_mimecontent == MIME_DIGEST)
      ip->m_ct_type_plain = "message/rfc822";
   else
      ip->m_ct_type_plain = "text/plain";
   ip->m_ct_type_usr_ovwr = NULL;

   if((cp = ip->m_ct_type) != NULL)
      cp = mime_param_get("charset", cp);
   if(cp == NULL)
      ip->m_charset = ok_vlook(charset_7bit);
   else
      ip->m_charset = mx_charsetalias_expand(cp, FAL0);

   if ((ip->m_ct_enc = hfield1("content-transfer-encoding",
         (struct message*)ip)) == NULL)
      ip->m_ct_enc = mime_enc_from_conversion(CONV_7BIT);
   ip->m_mime_enc = mime_enc_from_ctehead(ip->m_ct_enc);

   if (((cp = hfield1("content-disposition", (struct message*)ip)) == NULL ||
         (ip->m_filename = mime_param_get("filename", cp)) == NULL) &&
         ip->m_ct_type != NULL)
      ip->m_filename = mime_param_get("name", ip->m_ct_type);

   if ((cp = hfield1("content-description", (struct message*)ip)) != NULL)
      ip->m_content_description = cp;

   if ((ip->m_mimecontent = n_mimetype_classify_part(ip,
         ((mpf & MIME_PARSE_FOR_USER_CONTEXT) != 0))) == MIME_822) {
      /* TODO (v15) HACK: message/rfc822 is treated special, that this one is
       * TODO too stupid to apply content-decoding when (falsely) applied */
      if (ip->m_mime_enc != MIMEE_8B && ip->m_mime_enc != MIMEE_7B) {
         n_err(_("Pre-v15 %s cannot handle (falsely) encoded message/rfc822\n"
            "  (not 7bit or 8bit)!  Interpreting as text/plain!\n"),
            n_uagent);
         ip->m_mimecontent = MIME_TEXT_PLAIN;
      }
   }

   ASSERT(ip->m_external_body_url == NULL);
   if(!su_cs_cmp_case(ip->m_ct_type_plain, "message/external-body") &&
         (cp = mime_param_get("access-type", ip->m_ct_type)) != NULL &&
         !su_cs_cmp_case(cp, "URL"))
      ip->m_external_body_url = mime_param_get("url", ip->m_ct_type);

   if (mpf & MIME_PARSE_PARTS) {
      if (level > 9999) { /* TODO MAGIC */
         n_err(_("MIME content too deeply nested\n"));
         goto jleave;
      }
      switch (ip->m_mimecontent) {
      case MIME_PKCS7:
         if (mpf & MIME_PARSE_DECRYPT) {
#ifdef mx_HAVE_TLS
            _mime_parse_pkcs7(zmp, ip, mpf, level);
            if (ip->m_content_info & CI_ENCRYPTED_OK)
               ip->m_content_info |= CI_EXPANDED;
            break;
#else
            n_err(_("No SSL / S/MIME support compiled in\n"));
            goto jleave;
#endif
         }
         break;
      default:
         break;
      case MIME_ALTERNATIVE:
      case MIME_RELATED: /* TODO /related yet handled like /alternative */
      case MIME_DIGEST:
      case MIME_SIGNED:
      case MIME_ENCRYPTED:
      case MIME_MULTI:
         if (!_mime_parse_multipart(zmp, ip, mpf, level))
            goto jleave;
         break;
      case MIME_822:
         _mime_parse_rfc822(zmp, ip, mpf, level);
         break;
      }
   }

   rv = TRU1;
jleave:
   NYD_OU;
   return rv;
}

static void
_mime_parse_rfc822(struct message *zmp, struct mimepart *ip,
   enum mime_parse_flags mpf, int level)
{
   int c, lastc = '\n';
   uz cnt;
   FILE *ibuf;
   off_t offs;
   struct mimepart *np;
   long lines;
   NYD_IN;

   if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL)
      goto jleave;

   cnt = ip->m_size;
   lines = ip->m_lines;
   while (cnt && ((c = getc(ibuf)) != EOF)) {
      --cnt;
      if (c == '\n') {
         --lines;
         if (lastc == '\n')
            break;
      }
      lastc = c;
   }
   offs = ftell(ibuf);

   np = n_autorec_calloc(1, sizeof *np);
   np->m_flag = MNOFROM;
   np->m_content_info = CI_HAVE_HEADER | CI_HAVE_BODY;
   np->m_block = mailx_blockof(offs);
   np->m_offset = mailx_offsetof(offs);
   np->m_size = np->m_xsize = cnt;
   np->m_lines = np->m_xlines = lines;
   np->m_partstring = ip->m_partstring;
   np->m_parent = ip;
   ip->m_multipart = np;

   if (!(mpf & MIME_PARSE_SHALLOW) && ok_blook(rfc822_body_from_)) {
      substdate((struct message*)np);
      np->m_from = fakefrom((struct message*)np);/* TODO strip MNOFROM flag? */
   }

   _mime_parse_part(zmp, np, mpf, level + 1);
jleave:
   NYD_OU;
}

#ifdef mx_HAVE_TLS
static void
_mime_parse_pkcs7(struct message *zmp, struct mimepart *ip,
   enum mime_parse_flags mpf, int level)
{
   struct message m, *xmp;
   struct mimepart *np;
   char *to, *cc;
   NYD_IN;

   su_mem_copy(&m, ip, sizeof m);
   to = hfield1("to", zmp);
   cc = hfield1("cc", zmp);

   if ((xmp = smime_decrypt(&m, to, cc, 0)) != NULL) {
      np = n_autorec_calloc(1, sizeof *np);
      np->m_flag = xmp->m_flag;
      np->m_content_info = xmp->m_content_info | CI_ENCRYPTED | CI_ENCRYPTED_OK;
      np->m_block = xmp->m_block;
      np->m_offset = xmp->m_offset;
      np->m_size = xmp->m_size;
      np->m_xsize = xmp->m_xsize;
      np->m_lines = xmp->m_lines;
      np->m_xlines = xmp->m_xlines;

      /* TODO using part "1" for decrypted content is a hack */
      if ((np->m_partstring = ip->m_partstring) == NULL)
         ip->m_partstring = np->m_partstring = n_UNCONST(n_1);

      if (_mime_parse_part(zmp, np, mpf, level + 1) == OKAY) {
         ip->m_content_info |= CI_ENCRYPTED | CI_ENCRYPTED_OK;
         np->m_parent = ip;
         ip->m_multipart = np;
      }
   } else
      ip->m_content_info |= CI_ENCRYPTED | CI_ENCRYPTED_BAD;
   NYD_OU;
}
#endif /* mx_HAVE_TLS */

static boole
_mime_parse_multipart(struct message *zmp, struct mimepart *ip,
   enum mime_parse_flags mpf, int level)
{
   struct mimepart *np = NULL;
   char *boundary, *line = NULL;
   uz linesize = 0, linelen, cnt, boundlen;
   FILE *ibuf;
   off_t offs;
   int part = 0;
   long lines = 0;
   NYD_IN;

   if ((boundary = mime_param_boundary_get(ip->m_ct_type, &linelen)) == NULL)
      goto jleave;

   boundlen = linelen;
   if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL)
      goto jleave;

   cnt = ip->m_size;
   while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0))
      if (line[0] == '\n')
         break;
   offs = ftell(ibuf);

   /* TODO using part "1" for decrypted content is a hack */
   if (ip->m_partstring == NULL)
      ip->m_partstring = n_UNCONST("1");
   __mime_parse_new(ip, &np, offs, NULL);

   while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
      /* XXX linelen includes LF */
      if (!((lines > 0 || part == 0) && linelen > boundlen &&
            !strncmp(line, boundary, boundlen))) {
         ++lines;
         continue;
      }

      /* Subpart boundary? */
      if (line[boundlen] == '\n') {
         offs = ftell(ibuf);
         if (part > 0) {
            __mime_parse_end(&np, offs - boundlen - 2, lines);
            __mime_parse_new(ip, &np, offs - boundlen - 2, NULL);
         }
         __mime_parse_end(&np, offs, 2);
         __mime_parse_new(ip, &np, offs, &part);
         lines = 0;
         continue;
      }

      /* Final boundary?  Be aware of cases where there is no separating
       * newline in between boundaries, as has been seen in a message with
       * "Content-Type: multipart/appledouble;" */
      if (linelen < boundlen + 2)
         continue;
      linelen -= boundlen + 2;
      if (line[boundlen] != '-' || line[boundlen + 1] != '-' ||
            (linelen > 0 && line[boundlen + 2] != '\n'))
         continue;
      offs = ftell(ibuf);
      if (part != 0) {
         __mime_parse_end(&np, offs - boundlen - 4, lines);
         __mime_parse_new(ip, &np, offs - boundlen - 4, NULL);
      }
      __mime_parse_end(&np, offs + cnt, 2);
      break;
   }
   if (np) {
      offs = ftell(ibuf);
      __mime_parse_end(&np, offs, lines);
   }

   for (np = ip->m_multipart; np != NULL; np = np->m_nextpart)
      if (np->m_mimecontent != MIME_DISCARD)
         _mime_parse_part(zmp, np, mpf, level + 1);

jleave:
   if (line != NULL)
      n_free(line);
   NYD_OU;
   return TRU1;
}

static void
__mime_parse_new(struct mimepart *ip, struct mimepart **np, off_t offs,
   int *part)
{
   struct mimepart *pp;
   NYD_IN;

   *np = n_autorec_calloc(1, sizeof **np);
   (*np)->m_flag = MNOFROM;
   (*np)->m_content_info = CI_HAVE_HEADER | CI_HAVE_BODY;
   (*np)->m_block = mailx_blockof(offs);
   (*np)->m_offset = mailx_offsetof(offs);

   if (part) {
      uz i;

      ++(*part);
      i = (ip->m_partstring != NULL) ? su_cs_len(ip->m_partstring) : 0;
      i += 20;
      (*np)->m_partstring = n_autorec_alloc(i);
      if (ip->m_partstring)
         snprintf((*np)->m_partstring, i, "%s.%u", ip->m_partstring, *part);
      else
         snprintf((*np)->m_partstring, i, "%u", *part);
   } else
      (*np)->m_mimecontent = MIME_DISCARD;
   (*np)->m_parent = ip;

   if (ip->m_multipart) {
      for (pp = ip->m_multipart; pp->m_nextpart != NULL; pp = pp->m_nextpart)
         ;
      pp->m_nextpart = *np;
   } else
      ip->m_multipart = *np;
   NYD_OU;
}

static void
__mime_parse_end(struct mimepart **np, off_t xoffs, long lines)
{
   off_t offs;
   NYD_IN;

   offs = mailx_positionof((*np)->m_block, (*np)->m_offset);
   (*np)->m_size = (*np)->m_xsize = xoffs - offs;
   (*np)->m_lines = (*np)->m_xlines = lines;
   *np = NULL;
   NYD_OU;
}

FL struct mimepart *
mime_parse_msg(struct message *mp, enum mime_parse_flags mpf)
{
   struct mimepart *ip;
   NYD_IN;

   ip = n_autorec_calloc(1, sizeof *ip);
   ip->m_flag = mp->m_flag;
   ip->m_content_info = mp->m_content_info;
   ip->m_block = mp->m_block;
   ip->m_offset = mp->m_offset;
   ip->m_size = mp->m_size;
   ip->m_xsize = mp->m_xsize;
   ip->m_lines = mp->m_lines;
   ip->m_xlines = mp->m_lines;
   if (!_mime_parse_part(mp, ip, mpf, 0))
      ip = NULL;
   NYD_OU;
   return ip;
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/mx/mime-types.c000066400000000000000000001233301352610246600165350ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ `(un)?mimetype' and other mime.types(5) related facilities.
 *@ "Keep in sync with" ./mime.types.
 *
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE mime_types
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

#include 
#include 
#include 

#include "mx/file-streams.h"
/* TODO that this does not belong: clear */
#include "mx/filter-html.h"

/* TODO fake */
#include "su/code-in.h"

enum mime_type {
   _MT_APPLICATION,
   _MT_AUDIO,
   _MT_IMAGE,
   _MT_MESSAGE,
   _MT_MULTIPART,
   _MT_TEXT,
   _MT_VIDEO,
   _MT_OTHER,
   __MT_TMIN = 0u,
   __MT_TMAX = _MT_OTHER,
   __MT_TMASK = 0x07u,

   _MT_CMD = 1u<< 8,          /* Via `mimetype' (not struct mtbltin) */
   _MT_USR = 1u<< 9,          /* VAL_MIME_TYPES_USR */
   _MT_SYS = 1u<<10,          /* VAL_MIME_TYPES_SYS */
   _MT_FSPEC = 1u<<11,        /* Loaded via f= *mimetypes-load-control* spec. */

   a_MT_TM_PLAIN = 1u<<16,    /* Without pipe handler display as text */
   a_MT_TM_SOUP_h = 2u<<16,   /* Ditto, but HTML tagsoup parser if possible */
   a_MT_TM_SOUP_H = 3u<<16,   /* HTML tagsoup parser, else NOT plain text */
   a_MT_TM_QUIET = 4u<<16,    /* No "no mime handler available" message */
   a_MT__TM_MARKMASK = 7u<<16
};

enum mime_type_class {
   _MT_C_NONE,
   _MT_C_CLEAN = _MT_C_NONE,  /* Plain RFC 5322 message */
   _MT_C_DEEP_INSPECT = 1u<<0,   /* Always test all the file */
   _MT_C_NCTT = 1u<<1,        /* *contenttype == NULL */
   _MT_C_ISTXT = 1u<<2,       /* *contenttype =~ text\/ */
   _MT_C_ISTXTCOK = 1u<<3,    /* _ISTXT + *mime-allow-text-controls* */
   _MT_C_HIGHBIT = 1u<<4,     /* Not 7bit clean */
   _MT_C_LONGLINES = 1u<<5,   /* MIME_LINELEN_LIMIT exceed. */
   _MT_C_CTRLCHAR = 1u<<6,    /* Control characters seen */
   _MT_C_HASNUL = 1u<<7,      /* Contains \0 characters */
   _MT_C_NOTERMNL = 1u<<8,    /* Lacks a final newline */
   _MT_C_FROM_ = 1u<<9,       /* ^From_ seen */
   _MT_C_FROM_1STLINE = 1u<<10,  /* From_ line seen */
   _MT_C_SUGGEST_DONE = 1u<<16,  /* Inspector suggests to stop further parse */
   _MT_C__1STLINE = 1u<<17    /* .. */
};

struct mtbltin {
   u32         mtb_flags;
   u32         mtb_mtlen;
   char const     *mtb_line;
};

struct mtnode {
   struct mtnode  *mt_next;
   u32         mt_flags;
   u32         mt_mtlen;   /* Length of MIME type string, rest thereafter */
   /* C99 forbids flexible arrays in union, so unfortunately we waste a pointer
    * that could already store character data here */
   char const     *mt_line;
};

struct mtlookup {
   char const           *mtl_name;
   uz               mtl_nlen;
   struct mtnode const  *mtl_node;
   char                 *mtl_result;   /* If requested, salloc()ed MIME type */
};

struct mt_class_arg {
   char const *mtca_buf;
   uz mtca_len;
   sz mtca_curlnlen;
   /*char mtca_lastc;*/
   char mtca_c;
   u8 mtca__dummy[3];
   enum mime_type_class mtca_mtc;
   u64 mtca_all_len;
   u64 mtca_all_highbit; /* TODO not yet interpreted */
   u64 mtca_all_bogus;
};

static struct mtbltin const   _mt_bltin[] = {
#include "gen-mime-types.h" /* */
};

static char const             _mt_typnames[][16] = {
   "application/", "audio/", "image/",
   "message/", "multipart/", "text/",
   "video/"
};
CTAV(_MT_APPLICATION == 0 && _MT_AUDIO == 1 && _MT_IMAGE == 2 &&
   _MT_MESSAGE == 3 && _MT_MULTIPART == 4 && _MT_TEXT == 5 &&
   _MT_VIDEO == 6);

/* */
static boole           _mt_is_init;
static struct mtnode    *_mt_list;

/* Initialize MIME type list in order */
static void             _mt_init(void);
static boole           __mt_load_file(u32 orflags,
                           char const *file, char **line, uz *linesize);

/* Create (prepend) a new MIME type; cmdcalled results in a bit more verbosity
 * for `mimetype' */
static struct mtnode *  _mt_create(boole cmdcalled, u32 orflags,
                           char const *line, uz len);

/* Try to find MIME type by X (after zeroing mtlp), return NULL if not found;
 * if with_result >mtl_result will be created upon success for the former */
static struct mtlookup * _mt_by_filename(struct mtlookup *mtlp,
                           char const *name, boole with_result);
static struct mtlookup * _mt_by_mtname(struct mtlookup *mtlp,
                           char const *mtname);

/* In-depth inspection of raw content: call _round() repeatedly, last time with
 * a 0 length buffer, finally check .mtca_mtc for result.
 * No further call is needed if _round() return includes _MT_C_SUGGEST_DONE,
 * as the resulting classification is unambiguous */
su_SINLINE struct mt_class_arg * _mt_classify_init(struct mt_class_arg *mtcap,
                                 enum mime_type_class initval);
static enum mime_type_class   _mt_classify_round(struct mt_class_arg *mtcap);

/* We need an in-depth inspection of an application/octet-stream part */
static enum mimecontent _mt_classify_os_part(u32 mce, struct mimepart *mpp,
                           boole deep_inspect);

/* Check whether a *pipe-XY* handler is applicable, and adjust flags according
 * to the defined trigger characters; upon entry MIME_HDL_NULL is set, and that
 * isn't changed if mhp doesn't apply */
static enum mime_handler_flags a_mt_pipe_check(struct mime_handler *mhp);

static void
_mt_init(void)
{
   struct mtnode *tail;
   char c, *line;
   uz linesize;
   u32 i, j;
   char const *srcs_arr[10], *ccp, **srcs;
   NYD_IN;

   /*if (_mt_is_init)
    *  goto jleave;*/

   /* Always load our built-ins */
   for (tail = NULL, i = 0; i < NELEM(_mt_bltin); ++i) {
      struct mtbltin const *mtbp = _mt_bltin + i;
      struct mtnode *mtnp = n_alloc(sizeof *mtnp);

      if (tail != NULL)
         tail->mt_next = mtnp;
      else
         _mt_list = mtnp;
      tail = mtnp;
      mtnp->mt_next = NULL;
      mtnp->mt_flags = mtbp->mtb_flags;
      mtnp->mt_mtlen = mtbp->mtb_mtlen;
      mtnp->mt_line = mtbp->mtb_line;
   }

   /* Decide which files sources have to be loaded */
   if ((ccp = ok_vlook(mimetypes_load_control)) == NULL)
      ccp = "US";
   else if (*ccp == '\0')
      goto jleave;

   srcs = srcs_arr + 2;
   srcs[-1] = srcs[-2] = NULL;

   if (su_cs_find_c(ccp, '=') != NULL) {
      line = savestr(ccp);

      while ((ccp = su_cs_sep_c(&line, ',', TRU1)) != NULL) {
         switch ((c = *ccp)) {
         case 'S': case 's':
            srcs_arr[1] = VAL_MIME_TYPES_SYS;
            if (0) {
               /* FALLTHRU */
         case 'U': case 'u':
               srcs_arr[0] = VAL_MIME_TYPES_USR;
            }
            if (ccp[1] != '\0')
               goto jecontent;
            break;
         case 'F': case 'f':
            if (*++ccp == '=' && *++ccp != '\0') {
               if (P2UZ(srcs - srcs_arr) < NELEM(srcs_arr))
                  *srcs++ = ccp;
               else
                  n_err(_("*mimetypes-load-control*: too many sources, "
                        "skipping %s\n"), n_shexp_quote_cp(ccp, FAL0));
               continue;
            }
            /* FALLTHRU */
         default:
            goto jecontent;
         }
      }
   } else for (i = 0; (c = ccp[i]) != '\0'; ++i)
      switch (c) {
      case 'S': case 's': srcs_arr[1] = VAL_MIME_TYPES_SYS; break;
      case 'U': case 'u': srcs_arr[0] = VAL_MIME_TYPES_USR; break;
      default:
jecontent:
         n_err(_("*mimetypes-load-control*: unsupported content: %s\n"), ccp);
         goto jleave;
      }

   /* Load all file-based sources in the desired order */
   mx_fs_linepool_aquire(&line, &linesize);
   for(j = 0, i = S(u32,P2UZ(srcs - srcs_arr)), srcs = srcs_arr;
         i > 0; ++j, ++srcs, --i)
      if(*srcs == NIL)
         continue;
      else if(!__mt_load_file((j == 0 ? _MT_USR
               : (j == 1 ? _MT_SYS : _MT_FSPEC)), *srcs, &line, &linesize)) {
         if((n_poption & n_PO_D_V) || j > 1)
            n_err(_("*mimetypes-load-control*: cannot open or load %s\n"),
               n_shexp_quote_cp(*srcs, FAL0));
      }
   mx_fs_linepool_release(line, linesize);

jleave:
   _mt_is_init = TRU1;
   NYD_OU;
}

static boole
__mt_load_file(u32 orflags, char const *file, char **line, uz *linesize)
{
   char const *cp;
   FILE *fp;
   struct mtnode *head, *tail, *mtnp;
   uz len;
   NYD_IN;

   if((cp = fexpand(file, FEXP_LOCAL | FEXP_NOPROTO)) == NIL ||
         (fp = mx_fs_open(cp, "r")) == NIL){
      cp = NIL;
      goto jleave;
   }

   for (head = tail = NULL; fgetline(line, linesize, NULL, &len, fp, 0) != 0;)
      if ((mtnp = _mt_create(FAL0, orflags, *line, len)) != NULL) {
         if (head == NULL)
            head = tail = mtnp;
         else
            tail->mt_next = mtnp;
         tail = mtnp;
      }
   if (head != NULL) {
      tail->mt_next = _mt_list;
      _mt_list = head;
   }

   mx_fs_close(fp);
jleave:
   NYD_OU;
   return (cp != NULL);
}

static struct mtnode *
_mt_create(boole cmdcalled, u32 orflags, char const *line, uz len)
{
   struct mtnode *mtnp;
   char const *typ, *subtyp;
   uz tlen, i;
   NYD_IN;

   mtnp = NULL;

   /* Drop anything after a comment first TODO v15: only when read from file */
   if ((typ = su_mem_find(line, '#', len)) != NULL)
      len = P2UZ(typ - line);

   /* Then trim any trailing whitespace from line (including NL/CR) */
   /* C99 */{
      struct str work;

      work.s = n_UNCONST(line);
      work.l = len;
      line = n_str_trim(&work, n_STR_TRIM_BOTH)->s;
      len = work.l;
   }
   typ = line;

   /* (But wait - is there a type marker?) */
   tlen = len;
   if(!(orflags & (_MT_USR | _MT_SYS)) && (*typ == '?' || *typ == '@')){
      if(*typ == '@') /* v15compat (plus trailing below) */
         n_OBSOLETE2(_("`mimetype': type markers (and much more) use ? not @"),
            line);
      if(len < 2)
         goto jeinval;
      if(typ[1] == ' '){
         orflags |= a_MT_TM_PLAIN;
         typ += 2;
         len -= 2;
         line += 2;
      }else if(len > 3){
         if(typ[2] == ' ')
            i = 3;
         else if(len > 4 && (typ[2] == '?' || typ[2] == '@') && typ[3] == ' ')
            i = 4;
         else
            goto jeinval;

         switch(typ[1]){
         default: goto jeinval;
         case 't': orflags |= a_MT_TM_PLAIN; break;
         case 'h': orflags |= a_MT_TM_SOUP_h; break;
         case 'H': orflags |= a_MT_TM_SOUP_H; break;
         case 'q': orflags |= a_MT_TM_QUIET; break;
         }
         typ += i;
         len -= i;
         line += i;
      }else
         goto jeinval;
   }

   while (len > 0 && !su_cs_is_blank(*line))
      ++line, --len;
   /* Ignore empty lines and even incomplete specifications (only MIME type)
    * because this is quite common in mime.types(5) files */
   if (len == 0 || (tlen = P2UZ(line - typ)) == 0) {
      if (cmdcalled || (orflags & _MT_FSPEC)) {
         if(len == 0){
            line = _("(no value)");
            len = su_cs_len(line);
         }
         n_err(_("Empty MIME type or no extensions given: %.*s\n"),
            (int)len, line);
      }
      goto jleave;
   }

   if ((subtyp = su_mem_find(typ, '/', tlen)) == NULL || subtyp[1] == '\0' ||
         su_cs_is_space(subtyp[1])) {
jeinval:
      if(cmdcalled || (orflags & _MT_FSPEC) || (n_poption & n_PO_D_V))
         n_err(_("%s MIME type: %.*s\n"),
            (cmdcalled ? _("Invalid") : _("mime.types(5): invalid")),
            (int)tlen, typ);
      goto jleave;
   }
   ++subtyp;

   /* Map to mime_type */
   tlen = P2UZ(subtyp - typ);
   for (i = __MT_TMIN;;) {
      if (!su_cs_cmp_case_n(_mt_typnames[i], typ, tlen)) {
         orflags |= i;
         tlen = P2UZ(line - subtyp);
         typ = subtyp;
         break;
      }
      if (++i == __MT_TMAX) {
         orflags |= _MT_OTHER;
         tlen = P2UZ(line - typ);
         break;
      }
   }

   /* Strip leading whitespace from the list of extensions;
    * trailing WS has already been trimmed away above.
    * Be silent on slots which define a mimetype without any value */
   while (len > 0 && su_cs_is_blank(*line))
      ++line, --len;
   if (len == 0)
      goto jleave;

   /*  */
   mtnp = n_alloc(sizeof(*mtnp) + tlen + len +1);
   mtnp->mt_next = NULL;
   mtnp->mt_flags = orflags;
   mtnp->mt_mtlen = (u32)tlen;
   {  char *l = (char*)(mtnp + 1);
      mtnp->mt_line = l;
      su_mem_copy(l, typ, tlen);
      su_mem_copy(l + tlen, line, len);
      tlen += len;
      l[tlen] = '\0';
   }

jleave:
   NYD_OU;
   return mtnp;
}

static struct mtlookup *
_mt_by_filename(struct mtlookup *mtlp, char const *name, boole with_result)
{
   struct mtnode *mtnp;
   uz nlen, i, j;
   char const *ext, *cp;
   NYD2_IN;

   su_mem_set(mtlp, 0, sizeof *mtlp);

   if ((nlen = su_cs_len(name)) == 0) /* TODO name should be a URI */
      goto jnull_leave;
   /* We need a period TODO we should support names like README etc. */
   for (i = nlen; name[--i] != '.';)
      if (i == 0 || name[i] == '/') /* XXX no magics */
         goto jnull_leave;
   /* While here, basename() it */
   while (i > 0 && name[i - 1] != '/')
      --i;
   name += i;
   nlen -= i;
   mtlp->mtl_name = name;
   mtlp->mtl_nlen = nlen;

   if (!_mt_is_init)
      _mt_init();

   /* ..all the MIME types */
   for (mtnp = _mt_list; mtnp != NULL; mtnp = mtnp->mt_next)
      for (ext = mtnp->mt_line + mtnp->mt_mtlen;; ext = cp) {
         cp = ext;
         while (su_cs_is_white(*cp))
            ++cp;
         ext = cp;
         while (!su_cs_is_white(*cp) && *cp != '\0')
            ++cp;

         if ((i = P2UZ(cp - ext)) == 0)
            break;
         /* Don't allow neither of ".txt" or "txt" to match "txt" */
         else if (i + 1 >= nlen || name[(j = nlen - i) - 1] != '.' ||
               su_cs_cmp_case_n(name + j, ext, i))
            continue;

         /* Found it */
         mtlp->mtl_node = mtnp;

         if (!with_result)
            goto jleave;

         if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
            name = n_empty;
            j = 0;
         } else {
            name = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
            j = su_cs_len(name);
         }
         i = mtnp->mt_mtlen;
         mtlp->mtl_result = n_autorec_alloc(i + j +1);
         if (j > 0)
            su_mem_copy(mtlp->mtl_result, name, j);
         su_mem_copy(mtlp->mtl_result + j, mtnp->mt_line, i);
         mtlp->mtl_result[j += i] = '\0';
         goto jleave;
      }
jnull_leave:
   mtlp = NULL;
jleave:
   NYD2_OU;
   return mtlp;
}

static struct mtlookup *
_mt_by_mtname(struct mtlookup *mtlp, char const *mtname)
{
   struct mtnode *mtnp;
   uz nlen, i, j;
   char const *cp;
   NYD2_IN;

   su_mem_set(mtlp, 0, sizeof *mtlp);

   if ((mtlp->mtl_nlen = nlen = su_cs_len(mtlp->mtl_name = mtname)) == 0)
      goto jnull_leave;

   if (!_mt_is_init)
      _mt_init();

   /* ..all the MIME types */
   for (mtnp = _mt_list; mtnp != NULL; mtnp = mtnp->mt_next) {
         if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
            cp = n_empty;
            j = 0;
         } else {
            cp = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
            j = su_cs_len(cp);
         }
         i = mtnp->mt_mtlen;

         if (i + j == mtlp->mtl_nlen) {
            char *xmt = n_lofi_alloc(i + j +1);
            if (j > 0)
               su_mem_copy(xmt, cp, j);
            su_mem_copy(xmt + j, mtnp->mt_line, i);
            xmt[j += i] = '\0';
            i = su_cs_cmp_case(mtname, xmt);
            n_lofi_free(xmt);

            if (!i) {
               /* Found it */
               mtlp->mtl_node = mtnp;
               goto jleave;
            }
         }
      }
jnull_leave:
   mtlp = NULL;
jleave:
   NYD2_OU;
   return mtlp;
}

su_SINLINE struct mt_class_arg *
_mt_classify_init(struct mt_class_arg * mtcap, enum mime_type_class initval)
{
   NYD2_IN;
   su_mem_set(mtcap, 0, sizeof *mtcap);
   /*mtcap->mtca_lastc =*/ mtcap->mtca_c = EOF;
   mtcap->mtca_mtc = initval | _MT_C__1STLINE;
   NYD2_OU;
   return mtcap;
}

static enum mime_type_class
_mt_classify_round(struct mt_class_arg *mtcap) /* TODO dig UTF-8 for !text/!! */
{
   /* TODO BTW., after the MIME/send layer rewrite we could use a MIME
    * TODO boundary of "=-=-=" if we would add a B_ in EQ spirit to F_,
    * TODO and report that state to the outer world */
#define F_        "From "
#define F_SIZEOF  (sizeof(F_) -1)
   char f_buf[F_SIZEOF], *f_p = f_buf;
   char const *buf;
   uz blen;
   sz curlnlen;
   s64 alllen;
   int c, lastc;
   enum mime_type_class mtc;
   NYD2_IN;

   buf = mtcap->mtca_buf;
   blen = mtcap->mtca_len;
   curlnlen = mtcap->mtca_curlnlen;
   alllen = mtcap->mtca_all_len;
   c = mtcap->mtca_c;
   /*lastc = mtcap->mtca_lastc;*/
   mtc = mtcap->mtca_mtc;

   for (;; ++curlnlen) {
      if(blen == 0){
         /* Real EOF, or only current buffer end? */
         if(mtcap->mtca_len == 0){
            lastc = c;
            c = EOF;
         }else{
            lastc = EOF;
            break;
         }
      }else{
         ++alllen;
         lastc = c;
         c = (uc)*buf++;
      }
      --blen;

      if (c == '\0') {
         mtc |= _MT_C_HASNUL;
         if (!(mtc & _MT_C_ISTXTCOK)) {
            mtc |= _MT_C_SUGGEST_DONE;
            break;
         }
         continue;
      }
      if (c == '\n' || c == EOF) {
         mtc &= ~_MT_C__1STLINE;
         if (curlnlen >= MIME_LINELEN_LIMIT)
            mtc |= _MT_C_LONGLINES;
         if (c == EOF)
            break;
         f_p = f_buf;
         curlnlen = -1;
         continue;
      }
      /* A bit hairy is handling of \r=\x0D=CR.
       * RFC 2045, 6.7:
       * Control characters other than TAB, or CR and LF as parts of CRLF
       * pairs, must not appear.  \r alone does not force _CTRLCHAR below since
       * we cannot peek the next character.  Thus right here, inspect the last
       * seen character for if its \r and set _CTRLCHAR in a delayed fashion */
       /*else*/ if (lastc == '\r')
         mtc |= _MT_C_CTRLCHAR;

      /* Control character? XXX this is all ASCII here */
      if (c < 0x20 || c == 0x7F) {
         /* RFC 2045, 6.7, as above ... */
         if (c != '\t' && c != '\r')
            mtc |= _MT_C_CTRLCHAR;

         /* If there is a escape sequence in reverse solidus notation defined
          * for this in ANSI X3.159-1989 (ANSI C89), don't treat it as a control
          * for real.  I.e., \a=\x07=BEL, \b=\x08=BS, \t=\x09=HT.  Don't follow
          * libmagic(1) in respect to \v=\x0B=VT.  \f=\x0C=NP; do ignore
          * \e=\x1B=ESC */
         if ((c >= '\x07' && c <= '\x0D') || c == '\x1B')
            continue;

         /* As a special case, if we are going for displaying data to the user
          * or quoting a message then simply continue this, in the end, in case
          * we get there, we will decide upon the all_len/all_bogus ratio
          * whether this is usable plain text or not */
         ++mtcap->mtca_all_bogus;
         if(mtc & _MT_C_DEEP_INSPECT)
            continue;

         mtc |= _MT_C_HASNUL; /* Force base64 */
         if (!(mtc & _MT_C_ISTXTCOK)) {
            mtc |= _MT_C_SUGGEST_DONE;
            break;
         }
      } else if ((u8)c & 0x80) {
         mtc |= _MT_C_HIGHBIT;
         ++mtcap->mtca_all_highbit;
         if (!(mtc & (_MT_C_NCTT | _MT_C_ISTXT))) { /* TODO _NCTT?? */
            mtc |= _MT_C_HASNUL /* Force base64 */ | _MT_C_SUGGEST_DONE;
            break;
         }
      } else if (!(mtc & _MT_C_FROM_) && UCMP(z, curlnlen, <, F_SIZEOF)) {
         *f_p++ = (char)c;
         if (UCMP(z, curlnlen, ==, F_SIZEOF - 1) &&
               P2UZ(f_p - f_buf) == F_SIZEOF &&
               !su_mem_cmp(f_buf, F_, F_SIZEOF)){
            mtc |= _MT_C_FROM_;
            if (mtc & _MT_C__1STLINE)
               mtc |= _MT_C_FROM_1STLINE;
         }
      }
   }
   if (c == EOF && lastc != '\n')
      mtc |= _MT_C_NOTERMNL;

   mtcap->mtca_curlnlen = curlnlen;
   /*mtcap->mtca_lastc = lastc*/;
   mtcap->mtca_c = c;
   mtcap->mtca_mtc = mtc;
   mtcap->mtca_all_len = alllen;
   NYD2_OU;
   return mtc;
#undef F_
#undef F_SIZEOF
}

static enum mimecontent
_mt_classify_os_part(u32 mce, struct mimepart *mpp, boole deep_inspect)
{
   struct str in = {NULL, 0}, outrest, inrest, dec;
   struct mt_class_arg mtca;
   boole did_inrest;
   enum mime_type_class mtc;
   int lc, c;
   uz cnt, lsz;
   FILE *ibuf;
   off_t start_off;
   enum mimecontent mc;
   NYD2_IN;

   ASSERT(mpp->m_mime_enc != MIMEE_BIN);

   outrest = inrest = dec = in;
   mc = MIME_UNKNOWN;
   mtc = 0;
   did_inrest = FAL0;

   /* TODO v15-compat Note we actually bypass our usual file handling by
    * TODO directly using fseek() on mb.mb_itf -- the v15 rewrite will change
    * TODO all of this, and until then doing it like this is the only option
    * TODO to integrate nicely into whoever calls us */
   start_off = ftell(mb.mb_itf);
   if ((ibuf = setinput(&mb, (struct message*)mpp, NEED_BODY)) == NULL) {
jos_leave:
      fseek(mb.mb_itf, start_off, SEEK_SET);
      goto jleave;
   }
   cnt = mpp->m_size;

   /* Skip part headers */
   for (lc = '\0'; cnt > 0; lc = c, --cnt)
      if ((c = getc(ibuf)) == EOF || (c == '\n' && lc == '\n'))
         break;
   if (cnt == 0 || ferror(ibuf))
      goto jos_leave;

   /* So now let's inspect the part content, decoding content-transfer-encoding
    * along the way TODO this should simply be "mime_factory_create(MPP)"!
    * TODO In fact m_mime_classifier_(setup|call|call_part|finalize)() and the
    * TODO state(s) (the _MT_C states) should become reported to the outer
    * TODO world like that (see MIME boundary TODO around here) */
   _mt_classify_init(&mtca, (_MT_C_ISTXT |
      (deep_inspect ? _MT_C_DEEP_INSPECT : _MT_C_NONE)));

   for (lsz = 0;;) {
      boole dobuf;

      c = (--cnt == 0) ? EOF : getc(ibuf);
      if ((dobuf = (c == '\n'))) {
         /* Ignore empty lines */
         if (lsz == 0)
            continue;
      } else if ((dobuf = (c == EOF))) {
         if (lsz == 0 && outrest.l == 0)
            break;
      }

      if (in.l + 1 >= lsz)
         in.s = n_realloc(in.s, lsz += LINESIZE);
      if (c != EOF)
         in.s[in.l++] = (char)c;
      if (!dobuf)
         continue;

jdobuf:
      switch (mpp->m_mime_enc) {
      case MIMEE_B64:
         if (!b64_decode_part(&dec, &in, &outrest,
               (did_inrest ? NULL : &inrest))) {
            mtca.mtca_mtc = _MT_C_HASNUL;
            goto jstopit; /* break;break; */
         }
         break;
      case MIMEE_QP:
         /* Drin */
         if (!qp_decode_part(&dec, &in, &outrest, &inrest)) {
            mtca.mtca_mtc = _MT_C_HASNUL;
            goto jstopit; /* break;break; */
         }
         if (dec.l == 0 && c != EOF) {
            in.l = 0;
            continue;
         }
         break;
      default:
         /* Temporarily switch those two buffers.. */
         dec = in;
         in.s = NULL;
         in.l = 0;
         break;
      }

      mtca.mtca_buf = dec.s;
      mtca.mtca_len = (sz)dec.l;
      if ((mtc = _mt_classify_round(&mtca)) & _MT_C_SUGGEST_DONE) {
         mtc = _MT_C_HASNUL;
         break;
      }

      if (c == EOF)
         break;
      /* ..and restore switched */
      if (in.s == NULL) {
         in = dec;
         dec.s = NULL;
      }
      in.l = dec.l = 0;
   }

   if ((in.l = inrest.l) > 0) {
      in.s = inrest.s;
      inrest.s = NULL;
      did_inrest = TRU1;
      goto jdobuf;
   }
   if (outrest.l > 0)
      goto jdobuf;
jstopit:
   if (in.s != NULL)
      n_free(in.s);
   if (dec.s != NULL)
      n_free(dec.s);
   if (outrest.s != NULL)
      n_free(outrest.s);
   if (inrest.s != NULL)
      n_free(inrest.s);

   fseek(mb.mb_itf, start_off, SEEK_SET);

   if (!(mtc & (_MT_C_HASNUL /*| _MT_C_CTRLCHAR XXX really? */))) {
      /* In that special relaxed case we may very well wave through
       * octet-streams full of control characters, as they do no harm
       * TODO This should be part of m_mime_classifier_finalize() then! */
      if(deep_inspect &&
            mtca.mtca_all_len - mtca.mtca_all_bogus < mtca.mtca_all_len >> 2)
         goto jleave;

      mc = MIME_TEXT_PLAIN;
      if (mce & MIMECE_ALL_OVWR)
         mpp->m_ct_type_plain = "text/plain";
      if (mce & (MIMECE_BIN_OVWR | MIMECE_ALL_OVWR))
         mpp->m_ct_type_usr_ovwr = "text/plain";
   }
jleave:
   NYD2_OU;
   return mc;
}

static enum mime_handler_flags
a_mt_pipe_check(struct mime_handler *mhp){
   enum mime_handler_flags rv_orig, rv;
   char const *cp;
   NYD2_IN;

   rv_orig = rv = mhp->mh_flags;

   /* Do we have any handler for this part? */
   if(*(cp = mhp->mh_shell_cmd) == '\0')
      goto jleave;
   else if(*cp++ != '?' && cp[-1] != '@'/* v15compat */){
      rv |= MIME_HDL_CMD;
      goto jleave;
   }else if(*cp == '\0'){
      if(cp[-1] == '@')
         n_OBSOLETE2(_("*pipe-TYPE/SUBTYPE*+': type markers (and much more) "
            "use ? not @"), mhp->mh_shell_cmd);
      rv |= MIME_HDL_TEXT;
      goto jleave;
   }

jnextc:
   switch(*cp){
   case '*': rv |= MIME_HDL_COPIOUSOUTPUT; ++cp; goto jnextc;
   case '#': rv |= MIME_HDL_NOQUOTE; ++cp; goto jnextc;
   case '&': rv |= MIME_HDL_ASYNC; ++cp; goto jnextc;
   case '!': rv |= MIME_HDL_NEEDSTERM; ++cp; goto jnextc;
   case '+':
      if(rv & MIME_HDL_TMPF)
         rv |= MIME_HDL_TMPF_UNLINK;
      rv |= MIME_HDL_TMPF;
      ++cp;
      goto jnextc;
   case '=':
      rv |= MIME_HDL_TMPF_FILL;
      ++cp;
      goto jnextc;
   case '@':/* v15compat */
      /* FALLTHRU */
   case '?':
      ++cp;
      /* FALLTHRU */
   default:
      break;
   }
   mhp->mh_shell_cmd = cp;

   /* Implications */
   if(rv & MIME_HDL_TMPF_FILL)
      rv |= MIME_HDL_TMPF;

   /* Exceptions */
   if(rv & MIME_HDL_ISQUOTE){
      if(rv & MIME_HDL_NOQUOTE)
         goto jerr;

      /* Cannot fetch data back from asynchronous process */
      if(rv & MIME_HDL_ASYNC)
         goto jerr;

      /* TODO Can't use a "needsterminal" program for quoting */
      if(rv & MIME_HDL_NEEDSTERM)
         goto jerr;
   }

   if(rv & MIME_HDL_NEEDSTERM){
      if(rv & MIME_HDL_COPIOUSOUTPUT){
         n_err(_("MIME type handlers: cannot use needsterminal and "
            "copiousoutput together\n"));
         goto jerr;
      }
      if(rv & MIME_HDL_ASYNC){
         n_err(_("MIME type handlers: cannot use needsterminal and "
            "x-mailx-async together\n"));
         goto jerr;
      }

      /* needsterminal needs a terminal */
      if(!(n_psonce & n_PSO_INTERACTIVE))
         goto jerr;
   }

   if(rv & MIME_HDL_ASYNC){
      if(rv & MIME_HDL_COPIOUSOUTPUT){
         n_err(_("MIME type handlers: cannot use x-mailx-async and "
            "copiousoutput together\n"));
         goto jerr;
      }
      if(rv & MIME_HDL_TMPF_UNLINK){
         n_err(_("MIME type handlers: cannot use x-mailx-async and "
            "x-mailx-tmpfile-unlink together\n"));
         goto jerr;
      }
   }

   /* TODO mailcap-only: TMPF_UNLINK): needs -tmpfile OR -tmpfile-fill */

   rv |= MIME_HDL_CMD;
jleave:
   mhp->mh_flags = rv;
   NYD2_OU;
   return rv;
jerr:
   rv = rv_orig;
   goto jleave;
}

FL int
c_mimetype(void *v){
   struct n_string s_b, *s;
   struct mtnode *mtnp;
   char **argv;
   NYD_IN;

   if(!_mt_is_init)
      _mt_init();

   s = n_string_creat_auto(&s_b);

   if(*(argv = v) == NULL){
      FILE *fp;
      uz l;

      if(_mt_list == NULL){
         fprintf(n_stdout, _("# `mimetype': no mime.types(5) available\n"));
         goto jleave;
      }

      if((fp = mx_fs_tmp_open("mimetype", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
               mx_FS_O_REGISTER), NIL)) == NIL){
         n_perr(_("tmpfile"), 0);
         v = NIL;
         goto jleave;
      }

      s = n_string_reserve(s, 63);

      for(l = 0, mtnp = _mt_list; mtnp != NULL; ++l, mtnp = mtnp->mt_next){
         char const *cp;

         s = n_string_trunc(s, 0);

         switch(mtnp->mt_flags & a_MT__TM_MARKMASK){
         case a_MT_TM_PLAIN: cp = "?t "; break;
         case a_MT_TM_SOUP_h: cp = "?h "; break;
         case a_MT_TM_SOUP_H: cp = "?H "; break;
         case a_MT_TM_QUIET: cp = "?q "; break;
         default: cp = NULL; break;
         }
         if(cp != NULL)
            s = n_string_push_cp(s, cp);

         if((mtnp->mt_flags & __MT_TMASK) != _MT_OTHER)
            s = n_string_push_cp(s, _mt_typnames[mtnp->mt_flags &__MT_TMASK]);

         s = n_string_push_buf(s, mtnp->mt_line, mtnp->mt_mtlen);
         s = n_string_push_c(s, ' ');
         s = n_string_push_c(s, ' ');
         s = n_string_push_cp(s, &mtnp->mt_line[mtnp->mt_mtlen]);

         fprintf(fp, "mimetype %s%s\n", n_string_cp(s),
            ((n_poption & n_PO_D_V) == 0 ? n_empty
               : (mtnp->mt_flags & _MT_USR ? " # user"
               : (mtnp->mt_flags & _MT_SYS ? " # system"
               : (mtnp->mt_flags & _MT_FSPEC ? " # f= file"
               : (mtnp->mt_flags & _MT_CMD ? " # command" : " # built-in"))))));
       }

      page_or_print(fp, l);
      mx_fs_close(fp);
   }else{
      for(; *argv != NULL; ++argv){
         if(s->s_len > 0)
            s = n_string_push_c(s, ' ');
         s = n_string_push_cp(s, *argv);
      }

      mtnp = _mt_create(TRU1, _MT_CMD, n_string_cp(s), s->s_len);
      if(mtnp != NULL){
         mtnp->mt_next = _mt_list;
         _mt_list = mtnp;
      }else
         v = NULL;
   }
jleave:
   NYD_OU;
   return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
}

FL int
c_unmimetype(void *v)
{
   char **argv = v;
   struct mtnode *lnp, *mtnp;
   boole match;
   NYD_IN;

   /* Need to load that first as necessary */
   if (!_mt_is_init)
      _mt_init();

   for (; *argv != NULL; ++argv) {
      if (!su_cs_cmp_case(*argv, "reset")) {
         _mt_is_init = FAL0;
         goto jdelall;
      }

      if (argv[0][0] == '*' && argv[0][1] == '\0') {
jdelall:
         while ((mtnp = _mt_list) != NULL) {
            _mt_list = mtnp->mt_next;
            n_free(mtnp);
         }
         continue;
      }

      for (match = FAL0, lnp = NULL, mtnp = _mt_list; mtnp != NULL;) {
         char const *typ;
         char *val;
         uz i;

         if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
            typ = n_empty;
            i = 0;
         } else {
            typ = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
            i = su_cs_len(typ);
         }

         val = n_lofi_alloc(i + mtnp->mt_mtlen +1);
         su_mem_copy(val, typ, i);
         su_mem_copy(val + i, mtnp->mt_line, mtnp->mt_mtlen);
         val[i += mtnp->mt_mtlen] = '\0';
         i = su_cs_cmp_case(val, *argv);
         n_lofi_free(val);

         if (!i) {
            struct mtnode *nnp = mtnp->mt_next;
            if (lnp == NULL)
               _mt_list = nnp;
            else
               lnp->mt_next = nnp;
            n_free(mtnp);
            mtnp = nnp;
            match = TRU1;
         } else
            lnp = mtnp, mtnp = mtnp->mt_next;
      }
      if (!match) {
         if (!(n_pstate & n_PS_ROBOT) || (n_poption & n_PO_D_V))
            n_err(_("No such MIME type: %s\n"), *argv);
         v = NULL;
      }
   }
   NYD_OU;
   return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
}

FL boole
n_mimetype_check_mtname(char const *name)
{
   struct mtlookup mtl;
   boole rv;
   NYD_IN;

   rv = (_mt_by_mtname(&mtl, name) != NULL);
   NYD_OU;
   return rv;
}

FL char *
n_mimetype_classify_filename(char const *name)
{
   struct mtlookup mtl;
   NYD_IN;

   _mt_by_filename(&mtl, name, TRU1);
   NYD_OU;
   return mtl.mtl_result;
}

FL enum conversion
n_mimetype_classify_file(FILE *fp, char const **contenttype,
   char const **charset, int *do_iconv, boole no_mboxo)
{
   /* TODO classify once only PLEASE PLEASE PLEASE */
   /* TODO message/rfc822 is special in that it may only be 7bit, 8bit or
    * TODO binary according to RFC 2046, 5.2.1
    * TODO The handling of which is a hack */
   boole rfc822;
   enum mime_type_class mtc;
   enum mime_enc menc;
   off_t fpsz;
   enum conversion c;
   NYD_IN;

   ASSERT(ftell(fp) == 0x0l);

   *do_iconv = 0;

   if (*contenttype == NULL) {
      mtc = _MT_C_NCTT;
      rfc822 = FAL0;
   } else if (!su_cs_cmp_case_n(*contenttype, "text/", 5)) {
      mtc = ok_blook(mime_allow_text_controls)
         ? _MT_C_ISTXT | _MT_C_ISTXTCOK : _MT_C_ISTXT;
      rfc822 = FAL0;
   } else if (!su_cs_cmp_case(*contenttype, "message/rfc822")) {
      mtc = _MT_C_ISTXT;
      rfc822 = TRU1;
   } else {
      mtc = _MT_C_CLEAN;
      rfc822 = FAL0;
   }

   menc = mime_enc_target();

   if ((fpsz = fsize(fp)) == 0)
      goto j7bit;
   else {
      char buf[BUFFER_SIZE];
      struct mt_class_arg mtca;

      _mt_classify_init(&mtca, mtc);
      for (;;) {
         mtca.mtca_len = fread(buf, sizeof(buf[0]), NELEM(buf), fp);
         mtca.mtca_buf = buf;
         if ((mtc = _mt_classify_round(&mtca)) & _MT_C_SUGGEST_DONE)
            break;
         if (mtca.mtca_len == 0)
            break;
      }
      /* TODO ferror(fp) ! */
      rewind(fp);
   }

   if (mtc & _MT_C_HASNUL) {
      menc = MIMEE_B64;
      /* Don't overwrite a text content-type to allow UTF-16 and such, but only
       * on request; else enforce what file(1)/libmagic(3) would suggest */
      if (mtc & _MT_C_ISTXTCOK)
         goto jcharset;
      if (mtc & (_MT_C_NCTT | _MT_C_ISTXT))
         *contenttype = "application/octet-stream";
      goto jleave;
   }

   if(mtc & (_MT_C_LONGLINES | _MT_C_CTRLCHAR | _MT_C_NOTERMNL | _MT_C_FROM_)){
      if(menc != MIMEE_B64 && menc != MIMEE_QP){
         /* If the user chooses 8bit, and we do not privacy-sign the message,
          * then if encoding would be enforced only because of a ^From_, no */
         if((mtc & (_MT_C_LONGLINES | _MT_C_CTRLCHAR | _MT_C_NOTERMNL |
               _MT_C_FROM_)) != _MT_C_FROM_ || no_mboxo)
            menc = MIMEE_QP;
         else{
            ASSERT(menc != MIMEE_7B);
            menc = (mtc & _MT_C_HIGHBIT) ? MIMEE_8B : MIMEE_7B;
         }
      }
      *do_iconv = ((mtc & _MT_C_HIGHBIT) != 0);
   }else if(mtc & _MT_C_HIGHBIT){
      if(mtc & (_MT_C_NCTT | _MT_C_ISTXT))
         *do_iconv = TRU1;
   }else
j7bit:
      menc = MIMEE_7B;
   if(mtc & _MT_C_NCTT)
      *contenttype = "text/plain";

   /* Not an attachment with specified charset? */
jcharset:
   if (*charset == NULL) /* TODO MIME/send: iter active? iter! else */
      *charset = (mtc & _MT_C_HIGHBIT) ? charset_iter_or_fallback()
            : ok_vlook(charset_7bit);
jleave:
   /* TODO mime_type_file_classify() shouldn't return conversion */
   if (rfc822) {
      if (mtc & _MT_C_FROM_1STLINE) {
         n_err(_("Pre-v15 %s cannot handle message/rfc822 that "
              "indeed is a RFC 4155 MBOX!\n"
            "  Forcing a content-type of application/mbox!\n"),
            n_uagent);
         *contenttype = "application/mbox";
         goto jnorfc822;
      }
      c = (menc == MIMEE_7B ? CONV_7BIT
            : (menc == MIMEE_8B ? CONV_8BIT
            /* May have only 7-bit, 8-bit and binary.  Try to avoid latter */
            : ((mtc & _MT_C_HASNUL) ? CONV_NONE
            : ((mtc & _MT_C_HIGHBIT) ? CONV_8BIT : CONV_7BIT))));
   } else
jnorfc822:
      c = (menc == MIMEE_7B ? CONV_7BIT
            : (menc == MIMEE_8B ? CONV_8BIT
            : (menc == MIMEE_QP ? CONV_TOQP : CONV_TOB64)));
   NYD_OU;
   return c;
}

FL enum mimecontent
n_mimetype_classify_part(struct mimepart *mpp, boole for_user_context){
   /* TODO n_mimetype_classify_part() <-> m_mime_classifier_ with life cycle */
   struct mtlookup mtl;
   enum mimecontent mc;
   char const *ct;
   union {char const *cp; u32 f;} mce;
   boole is_os;
   NYD_IN;

   mc = MIME_UNKNOWN;
   if ((ct = mpp->m_ct_type_plain) == NULL) /* TODO may not */
      ct = n_empty;

   if((mce.cp = ok_vlook(mime_counter_evidence)) != NULL && *mce.cp != '\0'){
      if((su_idec_u32_cp(&mce.f, mce.cp, 0, NULL
               ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
            ) != su_IDEC_STATE_CONSUMED){
         n_err(_("Invalid *mime-counter-evidence* value content\n"));
         is_os = FAL0;
      }else{
         mce.f |= MIMECE_SET;
         is_os = !su_cs_cmp_case(ct, "application/octet-stream");

         if(mpp->m_filename != NULL && (is_os || (mce.f & MIMECE_ALL_OVWR))){
            if(_mt_by_filename(&mtl, mpp->m_filename, TRU1) == NULL){
               if(is_os)
                  goto jos_content_check;
            }else if(is_os || su_cs_cmp_case(ct, mtl.mtl_result)){
               if(mce.f & MIMECE_ALL_OVWR)
                  mpp->m_ct_type_plain = ct = mtl.mtl_result;
               if(mce.f & (MIMECE_BIN_OVWR | MIMECE_ALL_OVWR))
                  mpp->m_ct_type_usr_ovwr = ct = mtl.mtl_result;
            }
         }
      }
   }else
      is_os = FAL0;

   if(*ct == '\0' || su_cs_find_c(ct, '/') == NULL) /* Compat with non-MIME */
      mc = MIME_TEXT;
   else if(su_cs_starts_with_case(ct, "text/")){
      ct += sizeof("text/") -1;
      if(!su_cs_cmp_case(ct, "plain"))
         mc = MIME_TEXT_PLAIN;
      else if(!su_cs_cmp_case(ct, "html"))
         mc = MIME_TEXT_HTML;
      else
         mc = MIME_TEXT;
   }else if(su_cs_starts_with_case(ct, "message/")){
      ct += sizeof("message/") -1;
      if(!su_cs_cmp_case(ct, "rfc822"))
         mc = MIME_822;
      else
         mc = MIME_MESSAGE;
   }else if(su_cs_starts_with_case(ct, "multipart/")){
      struct multi_types{
         char mt_name[12];
         enum mimecontent mt_mc;
      } const mta[] = {
         {"alternative\0", MIME_ALTERNATIVE},
         {"related", MIME_RELATED},
         {"digest", MIME_DIGEST},
         {"signed", MIME_SIGNED},
         {"encrypted", MIME_ENCRYPTED}
      }, *mtap;

      for(ct += sizeof("multipart/") -1, mtap = mta;;)
         if(!su_cs_cmp_case(ct, mtap->mt_name)){
            mc = mtap->mt_mc;
            break;
         }else if(++mtap == mta + NELEM(mta)){
            mc = MIME_MULTI;
            break;
         }
   }else if(su_cs_starts_with_case(ct, "application/")){
      if(is_os)
         goto jos_content_check;
      ct += sizeof("application/") -1;
      if(!su_cs_cmp_case(ct, "pkcs7-mime") ||
            !su_cs_cmp_case(ct, "x-pkcs7-mime"))
         mc = MIME_PKCS7;
   }
jleave:
   NYD_OU;
   return mc;

jos_content_check:
   if((mce.f & MIMECE_BIN_PARSE) && mpp->m_mime_enc != MIMEE_BIN &&
         mpp->m_charset != NULL)
      mc = _mt_classify_os_part(mce.f, mpp, for_user_context);
   goto jleave;
}

FL enum mime_handler_flags
n_mimetype_handler(struct mime_handler *mhp, struct mimepart const *mpp,
   enum sendaction action)
{
#define __S    "pipe-"
#define __L    (sizeof(__S) -1)
   struct mtlookup mtl;
   char *buf, *cp;
   enum mime_handler_flags rv, xrv;
   char const *es, *cs, *ccp;
   uz el, cl, l;
   NYD_IN;

   su_mem_set(mhp, 0, sizeof *mhp);
   buf = NULL;

   rv = MIME_HDL_NULL;
   if (action == SEND_QUOTE || action == SEND_QUOTE_ALL)
      rv |= MIME_HDL_ISQUOTE;
   else if (action != SEND_TODISP && action != SEND_TODISP_ALL &&
         action != SEND_TODISP_PARTS)
      goto jleave;

   el = ((es = mpp->m_filename) != NULL &&
         (es = su_cs_rfind_c(es, '.')) != NULL &&
         *++es != '\0') ? su_cs_len(es) : 0;
   cl = ((cs = mpp->m_ct_type_usr_ovwr) != NULL ||
         (cs = mpp->m_ct_type_plain) != NULL) ? su_cs_len(cs) : 0;
   if ((l = MAX(el, cl)) == 0) {
      /* TODO this should be done during parse time! */
      goto jleave;
   }

   /* We don't pass the flags around, so ensure carrier is up-to-date */
   mhp->mh_flags = rv;

   buf = n_lofi_alloc(__L + l +1);
   su_mem_copy(buf, __S, __L);

   /* File-extension handlers take precedence.
    * Yes, we really "fail" here for file extensions which clash MIME types */
   if (el > 0) {
      su_mem_copy(buf + __L, es, el +1);
      for (cp = buf + __L; *cp != '\0'; ++cp)
         *cp = su_cs_to_lower(*cp);

      if ((mhp->mh_shell_cmd = ccp = n_var_vlook(buf, FAL0)) != NULL) {
         rv = a_mt_pipe_check(mhp);
         goto jleave;
      }
   }

   /* Then MIME Content-Type:, if any */
   if (cl == 0)
      goto jleave;

   su_mem_copy(buf + __L, cs, cl +1);
   for (cp = buf + __L; *cp != '\0'; ++cp)
      *cp = su_cs_to_lower(*cp);

   if ((mhp->mh_shell_cmd = n_var_vlook(buf, FAL0)) != NULL) {
      rv = a_mt_pipe_check(mhp);
      goto jleave;
   }

   if (_mt_by_mtname(&mtl, cs) != NULL)
      switch (mtl.mtl_node->mt_flags & a_MT__TM_MARKMASK) {
#ifndef mx_HAVE_FILTER_HTML_TAGSOUP
      case a_MT_TM_SOUP_H:
         break;
#endif
      case a_MT_TM_SOUP_h:
#ifdef mx_HAVE_FILTER_HTML_TAGSOUP
      case a_MT_TM_SOUP_H:
         mhp->mh_ptf = &mx_flthtml_process_main;
         mhp->mh_msg.l = su_cs_len(mhp->mh_msg.s =
               n_UNCONST(_("Built-in HTML tagsoup filter")));
         rv ^= MIME_HDL_NULL | MIME_HDL_PTF;
         goto jleave;
#endif
         /* FALLTHRU */
      case a_MT_TM_PLAIN:
         mhp->mh_msg.l = su_cs_len(mhp->mh_msg.s = n_UNCONST(_("Plain text")));
         rv ^= MIME_HDL_NULL | MIME_HDL_TEXT;
         goto jleave;
      case a_MT_TM_QUIET:
         mhp->mh_msg.l = 0;
         mhp->mh_msg.s = n_UNCONST(n_empty);
         goto jleave;
      default:
         break;
      }

jleave:
   if(buf != NULL)
      n_lofi_free(buf);

   xrv = rv;
   if((rv &= MIME_HDL_TYPE_MASK) == MIME_HDL_NULL){
      if(mhp->mh_msg.s == NULL)
         mhp->mh_msg.l = su_cs_len(mhp->mh_msg.s = n_UNCONST(
               A_("[-- No MIME handler installed, or not applicable --]\n")));
   }else if(rv == MIME_HDL_CMD && !(xrv & MIME_HDL_COPIOUSOUTPUT) &&
         action != SEND_TODISP_PARTS){
      mhp->mh_msg.l = su_cs_len(mhp->mh_msg.s = n_UNCONST(
            _("[-- Use the command `mimeview' to display this --]\n")));
      xrv &= ~MIME_HDL_TYPE_MASK;
      xrv |= (rv = MIME_HDL_MSG);
   }
   mhp->mh_flags = xrv;

   NYD_OU;
   return rv;
#undef __L
#undef __S
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/mx/mime.c000066400000000000000000001272761352610246600154100ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ MIME support functions.
 *@ TODO Complete rewrite.
 *
 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: BSD-4-Clause
 */
/*
 * Copyright (c) 2000
 * Gunnar Ritter.  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 Gunnar Ritter
 *    and his contributors.
 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER 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 GUNNAR RITTER 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.
 */
#undef su_FILE
#define su_FILE mime
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

#include 
#include 
#include 

/* TODO nonsense (should be filter chain!) */
#include "mx/filter-quote.h"
#include "mx/iconv.h"
#include "mx/names.h"
#include "mx/sigs.h"
#include "mx/ui-str.h"

/* TODO fake */
#include "su/code-in.h"

/* Don't ask, but it keeps body and soul together */
enum a_mime_structure_hack{
   a_MIME_SH_NONE,
   a_MIME_SH_COMMENT,
   a_MIME_SH_QUOTE
};

static char                   *_cs_iter_base, *_cs_iter;
#ifdef mx_HAVE_ICONV
# define _CS_ITER_GET() \
   ((_cs_iter != NULL) ? _cs_iter : ok_vlook(CHARSET_8BIT_OKEY))
#else
# define _CS_ITER_GET() ((_cs_iter != NULL) ? _cs_iter : ok_vlook(ttycharset))
#endif
#define _CS_ITER_STEP() _cs_iter = su_cs_sep_c(&_cs_iter_base, ',', TRU1)

/* Is 7-bit enough? */
#ifdef mx_HAVE_ICONV
static boole           _has_highbit(char const *s);
static boole           _name_highbit(struct mx_name *np);
#endif

/* fwrite(3) while checking for displayability */
static sz          _fwrite_td(struct str const *input,
                           boole failiconv, enum tdflags flags,
                           struct str *outrest, struct quoteflt *qf);

/* Convert header fields to RFC 2047 format and write to the file fo */
static sz          mime_write_tohdr(struct str *in, FILE *fo,
                           uz *colp, enum a_mime_structure_hack msh);

#ifdef mx_HAVE_ICONV
static sz a_mime__convhdra(struct str *inp, FILE *fp, uz *colp,
                  enum a_mime_structure_hack msh);
#else
# define a_mime__convhdra(S,F,C,MSH) mime_write_tohdr(S, F, C, MSH)
#endif

/* Write an address to a header field */
static sz          mime_write_tohdr_a(struct str *in, FILE *f,
                           uz *colp, enum a_mime_structure_hack msh);

/* Append to buf, handling resizing */
static void             _append_str(char **buf, uz *size, uz *pos,
                           char const *str, uz len);
static void             _append_conv(char **buf, uz *size, uz *pos,
                           char const *str, uz len);

#ifdef mx_HAVE_ICONV
static boole
_has_highbit(char const *s)
{
   boole rv = TRU1;
   NYD_IN;

   if (s) {
      do
         if ((u8)*s & 0x80)
            goto jleave;
      while (*s++ != '\0');
   }
   rv = FAL0;
jleave:
   NYD_OU;
   return rv;
}

static boole
_name_highbit(struct mx_name *np)
{
   boole rv = TRU1;
   NYD_IN;

   while (np) {
      if (_has_highbit(np->n_name) || _has_highbit(np->n_fullname))
         goto jleave;
      np = np->n_flink;
   }
   rv = FAL0;
jleave:
   NYD_OU;
   return rv;
}
#endif /* mx_HAVE_ICONV */

static sigjmp_buf       __mimefwtd_actjmp; /* TODO someday.. */
static int              __mimefwtd_sig; /* TODO someday.. */
static n_sighdl_t  __mimefwtd_opipe;
static void
__mimefwtd_onsig(int sig) /* TODO someday, we won't need it no more */
{
   NYD; /* Signal handler */
   __mimefwtd_sig = sig;
   siglongjmp(__mimefwtd_actjmp, 1);
}

static sz
_fwrite_td(struct str const *input, boole failiconv, enum tdflags flags,
   struct str *outrest, struct quoteflt *qf)
{
   /* TODO note: after send/MIME layer rewrite we will have a string pool
    * TODO so that memory allocation count drops down massively; for now,
    * TODO v14.* that is, we pay a lot & heavily depend on the allocator */
   /* TODO well if we get a broken pipe here, and it happens to
    * TODO happen pretty easy when sleeping in a full pipe buffer,
    * TODO then the current codebase performs longjump away;
    * TODO this leaves memory leaks behind ('think up to 3 per,
    * TODO dep. upon alloca availability).  For this to be fixed
    * TODO we either need to get rid of the longjmp()s (tm) or
    * TODO the storage must come from the outside or be tracked
    * TODO in a carrier struct.  Best both.  But storage reuse
    * TODO would be a bigbig win besides */
   /* *input* _may_ point to non-modifyable buffer; but even then it only
    * needs to be dup'ed away if we have to transform the content */
   struct str in, out;
   sz rv;
   NYD_IN;
   UNUSED(failiconv);
   UNUSED(outrest);

   in = *input;
   out.s = NULL;
   out.l = 0;

#ifdef mx_HAVE_ICONV
   if ((flags & TD_ICONV) && iconvd != (iconv_t)-1) {
      int err;
      char *buf;

      buf = NULL;

      if (outrest != NULL && outrest->l > 0) {
         in.l = outrest->l + input->l;
         in.s = buf = n_alloc(in.l +1);
         su_mem_copy(in.s, outrest->s, outrest->l);
         su_mem_copy(&in.s[outrest->l], input->s, input->l);
         outrest->l = 0;
      }

      rv = 0;

      /* TODO Sigh, no problem if we have a filter that has a buffer (or
       * TODO become fed with entire lines, whatever), but for now we need
       * TODO to ensure we pass entire lines from in here to iconv(3), because
       * TODO the Citrus iconv(3) will fail tests with stateful encodings
       * TODO if we do not (only seen on FreeBSD) */
#if 0 /* TODO actually not needed indeed, it was known iswprint() error! */
      if(!(flags & _TD_EOF) && outrest != NULL){
         uz i, j;
         char const *cp;

         if((cp = su_mem_find(in.s, '\n', j = in.l)) != NULL){
            i = P2UZ(cp - in.s);
            j -= i;
            while(j > 0 && *cp == '\n') /* XXX one iteration too much */
               ++cp, --j, ++i;
            if(j != 0)
               n_str_assign_buf(outrest, cp, j);
            in.l = i;
         }else{
            n_str_assign(outrest, &in);
            goto jleave;
         }
      }
#endif

      if((err = n_iconv_str(iconvd,
            (failiconv ? n_ICONV_NONE : n_ICONV_UNIDEFAULT),
            &out, &in, &in)) != 0){
         if(err != su_ERR_INVAL)
            n_iconv_reset(iconvd);

         if(outrest != NULL && in.l > 0){
            /* Incomplete multibyte at EOF is special xxx _INVAL? */
            if (flags & _TD_EOF) {
               out.s = n_realloc(out.s, out.l + sizeof(su_utf8_replacer));
               if(n_psonce & n_PSO_UNICODE){
                  su_mem_copy(&out.s[out.l], su_utf8_replacer,
                     sizeof(su_utf8_replacer) -1);
                  out.l += sizeof(su_utf8_replacer) -1;
               }else
                  out.s[out.l++] = '?';
            } else
               n_str_add(outrest, &in);
         }else
            rv = -1;
      }
      in = out;
      out.l = 0;
      out.s = NULL;
      flags &= ~_TD_BUFCOPY;

      if(buf != NULL)
         n_free(buf);
      if(rv < 0)
         goto jleave;
   }else
#endif /* mx_HAVE_ICONV */
   /* Else, if we will modify the data bytes and thus introduce the potential
    * of messing up multibyte sequences which become splitted over buffer
    * boundaries TODO and unless we don't have our filter chain which will
    * TODO make these hacks go by, buffer data until we see a NL */
         if((flags & (TD_ISPR | TD_DELCTRL)) && outrest != NULL &&
#ifdef mx_HAVE_ICONV
         iconvd == (iconv_t)-1 &&
#endif
         (!(flags & _TD_EOF) || outrest->l > 0)
   ) {
      uz i;
      char *cp;

      for (cp = &in.s[in.l]; cp > in.s && cp[-1] != '\n'; --cp)
         ;
      i = P2UZ(cp - in.s);

      if (i != in.l) {
         if (i > 0) {
            n_str_assign_buf(outrest, cp, in.l - i);
            cp = n_alloc(i +1);
            su_mem_copy(cp, in.s, in.l = i);
            (in.s = cp)[in.l = i] = '\0';
            flags &= ~_TD_BUFCOPY;
         } else {
            n_str_add_buf(outrest, input->s, input->l);
            rv = 0;
            goto jleave;
         }
      }
   }

   if (flags & TD_ISPR)
      makeprint(&in, &out);
   else if (flags & _TD_BUFCOPY)
      n_str_dup(&out, &in);
   else
      out = in;
   if (flags & TD_DELCTRL)
      out.l = delctrl(out.s, out.l);

   __mimefwtd_sig = 0;
   __mimefwtd_opipe = safe_signal(SIGPIPE, &__mimefwtd_onsig);
   if (sigsetjmp(__mimefwtd_actjmp, 1)) {
      rv = 0;
      goto j__sig;
   }

   rv = quoteflt_push(qf, out.s, out.l);

j__sig:
   if (out.s != in.s)
      n_free(out.s);
   if (in.s != input->s)
      n_free(in.s);
   safe_signal(SIGPIPE, __mimefwtd_opipe);
   if (__mimefwtd_sig != 0)
      n_raise(__mimefwtd_sig);
jleave:
   NYD_OU;
   return rv;
}

static sz
mime_write_tohdr(struct str *in, FILE *fo, uz *colp,
   enum a_mime_structure_hack msh)
{
   /* TODO mime_write_tohdr(): we don't know the name of our header->maxcol..
    * TODO  MIME/send layer rewrite: more available state!!
    * TODO   Because of this we cannot make a difference in between structured
    * TODO   and unstructured headers (RFC 2047, 5. (2))
    * TODO   This means, e.g., that this gets called multiple times for a
    * TODO   structured header and always starts thinking it is at column 0.
    * TODO   I.e., it may get called for only the content of a comment etc.,
    * TODO   not knowing anything of its context.
    * TODO   Instead we should have a list of header body content tokens,
    * TODO   convert them, and then dump the converted tokens, breaking lines.
    * TODO   I.e., get rid of convhdra, mime_write_tohdr_a and such...
    * TODO   Somewhen, the following should produce smooth stuff:
    * TODO   '  "Hallo\"," Dr. Backe "Bl\"ö\"d" (Gell) 
    * TODO    "Nochm\"a\"l"(Dümm)'
    * TODO NOT MULTIBYTE SAFE IF AN ENCODED WORD HAS TO BE SPLITTED!
    * TODO  To be better we had to mbtowc_l() (non-std! and no locale!!) and
    * TODO   work char-wise!  ->  S-CText..
    * TODO  The real problem for STD compatibility is however that "in" is
    * TODO   already iconv(3) encoded to the target character set!  We could
    * TODO   also solve it (very expensively!) if we would narrow down to an
    * TODO   encoded word and then iconv(3)+MIME encode in one go, in which
    * TODO   case multibyte errors could be caught! */
   enum {
      /* Maximum line length */
      a_MAXCOL_NENC = MIME_LINELEN,
      a_MAXCOL = MIME_LINELEN_RFC2047
   };

   struct str cout, cin;
   enum {
      _FIRST      = 1<<0,  /* Nothing written yet, start of string */
      _MSH_NOTHING = 1<<1, /* Now, really: nothing at all has been written */
      a_ANYENC = 1<<2,     /* We have RFC 2047 anything at least once */
      _NO_QP      = 1<<3,  /* No quoted-printable allowed */
      _NO_B64     = 1<<4,  /* Ditto, base64 */
      _ENC_LAST   = 1<<5,  /* Last round generated encoded word */
      _SHOULD_BEE = 1<<6,  /* Avoid lines longer than SHOULD via encoding */
      _RND_SHIFT  = 7,
      _RND_MASK   = (1<<_RND_SHIFT) - 1,
      _SPACE      = 1<<(_RND_SHIFT+1),    /* Leading whitespace */
      _8BIT       = 1<<(_RND_SHIFT+2),    /* High bit set */
      _ENCODE     = 1<<(_RND_SHIFT+3),    /* Need encoding */
      _ENC_B64    = 1<<(_RND_SHIFT+4),    /* - let it be base64 */
      _OVERLONG   = 1<<(_RND_SHIFT+5)     /* Temporarily rised limit */
   } flags;
   char const *cset7, *cset8, *wbot, *upper, *wend, *wcur;
   u32 cset7_len, cset8_len;
   uz col, i, j;
   sz size;

   NYD_IN;

   cout.s = NULL, cout.l = 0;
   cset7 = ok_vlook(charset_7bit);
   cset7_len = (u32)su_cs_len(cset7);
   cset8 = _CS_ITER_GET(); /* TODO MIME/send layer: iter active? iter! else */
   cset8_len = (u32)su_cs_len(cset8);

   flags = _FIRST;
   if(msh != a_MIME_SH_NONE)
      flags |= _MSH_NOTHING;

   /* RFC 1468, "MIME Considerations":
    *     ISO-2022-JP may also be used in MIME Part 2 headers.  The "B"
    *     encoding should be used with ISO-2022-JP text. */
   /* TODO of course, our current implementation won't deal properly with
    * TODO any stateful encoding at all... (the standard says each encoded
    * TODO word must include all necessary reset sequences..., i.e., each
    * TODO encoded word must be a self-contained iconv(3) life cycle) */
   if (!su_cs_cmp_case(cset8, "iso-2022-jp") || mime_enc_target() == MIMEE_B64)
      flags |= _NO_QP;

   wbot = in->s;
   upper = wbot + in->l;
   size = 0;

   if(colp == NULL || (col = *colp) == 0)
      col = sizeof("Mail-Followup-To: ") -1; /* TODO dreadful thing */

   /* The user may specify empy quoted-strings or comments, keep them! */
   if(wbot == upper) {
      if(flags & _MSH_NOTHING){
         flags &= ~_MSH_NOTHING;
         putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo);
         size = 1;
         ++col;
      }
   } else for (; wbot < upper; flags &= ~_FIRST, wbot = wend) {
      flags &= _RND_MASK;
      wcur = wbot;
      while (wcur < upper && su_cs_is_white(*wcur)) {
         flags |= _SPACE;
         ++wcur;
      }

      /* Any occurrence of whitespace resets prevention of lines >SHOULD via
       * enforced encoding (xxx SHOULD, but.. encoding is expensive!!) */
      if (flags & _SPACE)
         flags &= ~_SHOULD_BEE;

     /* Data ends with WS - dump it and done.
      * Also, if we have seen multiple successive whitespace characters, then
      * if there was no encoded word last, i.e., if we can simply take them
      * over to the output as-is, keep one WS for possible later separation
      * purposes and simply print the others as-is, directly! */
      if (wcur == upper) {
         wend = wcur;
         goto jnoenc_putws;
      }
      if ((flags & (_ENC_LAST | _SPACE)) == _SPACE && wcur - wbot > 1) {
         wend = wcur - 1;
         goto jnoenc_putws;
      }

      /* Skip over a word to next non-whitespace, keep track along the way
       * whether our 7-bit charset suffices to represent the data */
      for (wend = wcur; wend < upper; ++wend) {
         if (su_cs_is_white(*wend))
            break;
         if ((uc)*wend & 0x80)
            flags |= _8BIT;
      }

      /* Decide whether the range has to become encoded or not */
      i = P2UZ(wend - wcur);
      j = mime_enc_mustquote(wcur, i, MIMEEF_ISHEAD);
      /* If it just cannot fit on a SHOULD line length, force encode */
      if (i > a_MAXCOL_NENC) {
         flags |= _SHOULD_BEE; /* (Sigh: SHOULD only, not MUST..) */
         goto j_beejump;
      }
      if ((flags & _SHOULD_BEE) || j > 0) {
j_beejump:
         flags |= _ENCODE;
         /* Use base64 if requested or more than 50% -37.5-% of the bytes of
          * the string need to be encoded */
         if ((flags & _NO_QP) || j >= i >> 1)/*(i >> 2) + (i >> 3))*/
            flags |= _ENC_B64;
      }
      su_DBG( if (flags & _8BIT) ASSERT(flags & _ENCODE); )

      if (!(flags & _ENCODE)) {
         /* Encoded word produced, but no linear whitespace for necessary RFC
          * 2047 separation?  Generate artificial data (bad standard!) */
         if ((flags & (_ENC_LAST | _SPACE)) == _ENC_LAST) {
            if (col >= a_MAXCOL) {
               putc('\n', fo);
               ++size;
               col = 0;
            }
            if(flags & _MSH_NOTHING){
               flags &= ~_MSH_NOTHING;
               putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo);
               ++size;
               ++col;
            }
            putc(' ', fo);
            ++size;
            ++col;
         }

jnoenc_putws:
         flags &= ~_ENC_LAST;

         /* todo No effort here: (1) v15.0 has to bring complete rewrite,
          * todo (2) the standard is braindead and (3) usually this is one
          * todo word only, and why be smarter than the standard? */
jnoenc_retry:
         i = P2UZ(wend - wbot);
         if (i + col + ((flags & _MSH_NOTHING) != 0) <=
                  (flags & _OVERLONG ? MIME_LINELEN_MAX
                   : (flags & a_ANYENC ? a_MAXCOL : a_MAXCOL_NENC))) {
            if(flags & _MSH_NOTHING){
               flags &= ~_MSH_NOTHING;
               putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo);
               ++size;
               ++col;
            }
            i = fwrite(wbot, sizeof *wbot, i, fo);
            size += i;
            col += i;
            continue;
         }

         /* Doesn't fit, try to break the line first; */
         if (col > 1) {
            putc('\n', fo);
            if (su_cs_is_white(*wbot)) {
               putc((uc)*wbot, fo);
               ++wbot;
            } else
               putc(' ', fo); /* Bad standard: artificial data! */
            size += 2;
            col = 1;
            if(flags & _MSH_NOTHING){
               flags &= ~_MSH_NOTHING;
               putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo);
               ++size;
               ++col;
            }
            flags |= _OVERLONG;
            goto jnoenc_retry;
         }

         /* It is so long that it needs to be broken, effectively causing
          * artificial spaces to be inserted (bad standard), yuck */
         /* todo This is not multibyte safe, as above; and completely stupid
          * todo P.S.: our _SHOULD_BEE prevents these cases in the meanwhile */
/* FIXME n_PSO_UNICODE and parse using UTF-8 sync possibility! */
         wcur = wbot + MIME_LINELEN_MAX - 8;
         while (wend > wcur)
            wend -= 4;
         goto jnoenc_retry;
      } else {
         /* Encoding to encoded word(s); deal with leading whitespace, place
          * a separator first as necessary: encoded words must always be
          * separated from text and other encoded words with linear WS.
          * And if an encoded word was last, intermediate whitespace must
          * also be encoded, otherwise it would get stripped away! */
         wcur = n_UNCONST(n_empty);
         if ((flags & (_ENC_LAST | _SPACE)) != _SPACE) {
            /* Reinclude whitespace */
            flags &= ~_SPACE;
            /* We don't need to place a separator at the very beginning */
            if (!(flags & _FIRST))
               wcur = n_UNCONST(" ");
         } else
            wcur = wbot++;

         flags |= a_ANYENC | _ENC_LAST;
         n_pstate |= n_PS_HEADER_NEEDED_MIME;

         /* RFC 2047:
          *    An 'encoded-word' may not be more than 75 characters long,
          *    including 'charset', 'encoding', 'encoded-text', and
          *    delimiters.  If it is desirable to encode more text than will
          *    fit in an 'encoded-word' of 75 characters, multiple
          *    'encoded-word's (separated by CRLF SPACE) may be used.
          *
          *    While there is no limit to the length of a multiple-line
          *    header field, each line of a header field that contains one
          *    or more 'encoded-word's is limited to 76 characters */
jenc_retry:
         cin.s = n_UNCONST(wbot);
         cin.l = P2UZ(wend - wbot);

         /* C99 */{
            struct str *xout;

            if(flags & _ENC_B64)
               xout = b64_encode(&cout, &cin, B64_ISHEAD | B64_ISENCWORD);
            else
               xout = qp_encode(&cout, &cin, QP_ISHEAD | QP_ISENCWORD);
            if(xout == NULL){
               size = -1;
               break;
            }
            j = xout->l;
         }
         /* (Avoid trigraphs in the RFC 2047 placeholder..) */
         i = j + (flags & _8BIT ? cset8_len : cset7_len) + sizeof("=!!B!!=") -1;
         if (*wcur != '\0')
            ++i;

jenc_retry_same:
         /* Unfortunately RFC 2047 explicitly disallows encoded words to be
          * longer (just like RFC 5322's "a line SHOULD fit in 78 but MAY be
          * 998 characters long"), so we cannot use the _OVERLONG mechanism,
          * even though all tested mailers seem to support it */
         if (i + col <= (/*flags & _OVERLONG ? MIME_LINELEN_MAX :*/ a_MAXCOL)) {
            if(flags & _MSH_NOTHING){
               flags &= ~_MSH_NOTHING;
               putc((msh == a_MIME_SH_COMMENT ? '(' : '"'), fo);
               ++size;
               ++col;
            }
            fprintf(fo, "%.1s=?%s?%c?%.*s?=",
               wcur, (flags & _8BIT ? cset8 : cset7),
               (flags & _ENC_B64 ? 'B' : 'Q'),
               (int)cout.l, cout.s);
            size += i;
            col += i;
            continue;
         }

         /* Doesn't fit, try to break the line first */
         /* TODO I've commented out the _FIRST test since we (1) cannot do
          * TODO _OVERLONG since (MUAs support but) the standard disallows,
          * TODO and because of our iconv problem i prefer an empty first line
          * TODO in favour of a possibly messed up multibytes character. :-( */
         if (col > 1 /* TODO && !(flags & _FIRST)*/) {
            putc('\n', fo);
            size += 2;
            col = 1;
            if (!(flags & _SPACE)) {
               putc(' ', fo);
               wcur = n_UNCONST(n_empty);
               /*flags |= _OVERLONG;*/
               goto jenc_retry_same;
            } else {
               putc((uc)*wcur, fo);
               if (su_cs_is_white(*(wcur = wbot)))
                  ++wbot;
               else {
                  flags &= ~_SPACE;
                  wcur = n_UNCONST(n_empty);
               }
               /*flags &= ~_OVERLONG;*/
               goto jenc_retry;
            }
         }

         /* It is so long that it needs to be broken, effectively causing
          * artificial data to be inserted (bad standard), yuck */
         /* todo This is not multibyte safe, as above */
         /*if (!(flags & _OVERLONG)) { Mechanism explicitly forbidden by 2047
            flags |= _OVERLONG;
            goto jenc_retry;
         }*/

/* FIXME n_PSO_UNICODE and parse using UTF-8 sync possibility! */
         i = P2UZ(wend - wbot) + !!(flags & _SPACE);
         j = 3 + !(flags & _ENC_B64);
         for (;;) {
            wend -= j;
            i -= j;
            /* (Note the problem most likely is the transfer-encoding blow,
             * which is why we test this *after* the decrements.. */
            if (i <= a_MAXCOL)
               break;
         }
         goto jenc_retry;
      }
   }

   if(!(flags & _MSH_NOTHING) && msh != a_MIME_SH_NONE){
      putc((msh == a_MIME_SH_COMMENT ? ')' : '"'), fo);
      ++size;
      ++col;
   }

   if(cout.s != NULL)
      n_free(cout.s);

   if(colp != NULL)
      *colp = col;
   NYD_OU;
   return size;
}

#ifdef mx_HAVE_ICONV
static sz
a_mime__convhdra(struct str *inp, FILE *fp, uz *colp,
      enum a_mime_structure_hack msh){
   struct str ciconv;
   sz rv;
   NYD_IN;

   rv = 0;
   ciconv.s = NULL;

   if(inp->l > 0 && iconvd != (iconv_t)-1){
      ciconv.l = 0;
      if(n_iconv_str(iconvd, n_ICONV_NONE, &ciconv, inp, NULL) != 0){
         n_iconv_reset(iconvd);
         goto jleave;
      }
      *inp = ciconv;
   }

   rv = mime_write_tohdr(inp, fp, colp, msh);
jleave:
   if(ciconv.s != NULL)
      n_free(ciconv.s);
   NYD_OU;
   return rv;
}
#endif /* mx_HAVE_ICONV */

static sz
mime_write_tohdr_a(struct str *in, FILE *f, uz *colp,
   enum a_mime_structure_hack msh)
{
   struct str xin;
   uz i;
   char const *cp, *lastcp;
   sz size, x;
   NYD_IN;

   in->s[in->l] = '\0';

   if((cp = routeaddr(lastcp = in->s)) != NULL && cp > lastcp) {
      xin.s = n_UNCONST(lastcp);
      xin.l = P2UZ(cp - lastcp);
      if ((size = a_mime__convhdra(&xin, f, colp, msh)) < 0)
         goto jleave;
      lastcp = cp;
   } else {
      cp = lastcp;
      size = 0;
   }

   for( ; *cp != '\0'; ++cp){
      switch(*cp){
      case '(':
         i = P2UZ(cp - lastcp);
         if(i > 0){
            if(fwrite(lastcp, 1, i, f) != i)
               goto jerr;
            size += i;
         }
         lastcp = ++cp;
         cp = skip_comment(cp);
         if(cp > lastcp)
            --cp;
         /* We want to keep empty comments, too! */
         xin.s = n_UNCONST(lastcp);
         xin.l = P2UZ(cp - lastcp);
         if ((x = a_mime__convhdra(&xin, f, colp, a_MIME_SH_COMMENT)) < 0)
            goto jerr;
         size += x;
         lastcp = &cp[1];
         break;
      case '"':
         i = P2UZ(cp - lastcp);
         if(i > 0){
            if(fwrite(lastcp, 1, i, f) != i)
               goto jerr;
            size += i;
         }
         for(lastcp = ++cp; *cp != '\0'; ++cp){
            if(*cp == '"')
               break;
            if(*cp == '\\' && cp[1] != '\0')
               ++cp;
         }
         /* We want to keep empty quoted-strings, too! */
         xin.s = n_UNCONST(lastcp);
         xin.l = P2UZ(cp - lastcp);
         if((x = a_mime__convhdra(&xin, f, colp, a_MIME_SH_QUOTE)) < 0)
            goto jerr;
         size += x;
         ++size;
         lastcp = &cp[1];
         break;
      }
   }

   i = P2UZ(cp - lastcp);
   if(i > 0){
      if(fwrite(lastcp, 1, i, f) != i)
         goto jerr;
      size += i;
   }
jleave:
   NYD_OU;
   return size;
jerr:
   size = -1;
   goto jleave;
}

static void
_append_str(char **buf, uz *size, uz *pos, char const *str, uz len)
{
   NYD_IN;
   *buf = n_realloc(*buf, *size += len);
   su_mem_copy(&(*buf)[*pos], str, len);
   *pos += len;
   NYD_OU;
}

static void
_append_conv(char **buf, uz *size, uz *pos, char const *str, uz len)
{
   struct str in, out;
   NYD_IN;

   in.s = n_UNCONST(str);
   in.l = len;
   mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
   _append_str(buf, size, pos, out.s, out.l);
   n_free(out.s);
   NYD_OU;
}

FL boole
charset_iter_reset(char const *a_charset_to_try_first) /* TODO elim. dups! */
{
   char const *sarr[3];
   uz sarrl[3], len;
   char *cp;
   NYD_IN;
   UNUSED(a_charset_to_try_first);

#ifdef mx_HAVE_ICONV
   sarr[2] = ok_vlook(CHARSET_8BIT_OKEY);

   if(a_charset_to_try_first != NULL &&
         su_cs_cmp(a_charset_to_try_first, sarr[2]))
      sarr[0] = a_charset_to_try_first;
   else
      sarr[0] = NULL;

   if((sarr[1] = ok_vlook(sendcharsets)) == NULL &&
         ok_blook(sendcharsets_else_ttycharset)){
      cp = n_UNCONST(ok_vlook(ttycharset));
      if(su_cs_cmp(cp, sarr[2]) && (sarr[0] == NULL || su_cs_cmp(cp, sarr[0])))
         sarr[1] = cp;
   }
#else
   sarr[2] = ok_vlook(ttycharset);
#endif

   sarrl[2] = len = su_cs_len(sarr[2]);
#ifdef mx_HAVE_ICONV
   if ((cp = n_UNCONST(sarr[1])) != NULL)
      len += (sarrl[1] = su_cs_len(cp));
   else
      sarrl[1] = 0;
   if ((cp = n_UNCONST(sarr[0])) != NULL)
      len += (sarrl[0] = su_cs_len(cp));
   else
      sarrl[0] = 0;
#endif

   _cs_iter_base = cp = n_autorec_alloc(len + 1 + 1 +1);

#ifdef mx_HAVE_ICONV
   if ((len = sarrl[0]) != 0) {
      su_mem_copy(cp, sarr[0], len);
      cp[len] = ',';
      cp += ++len;
   }
   if ((len = sarrl[1]) != 0) {
      su_mem_copy(cp, sarr[1], len);
      cp[len] = ',';
      cp += ++len;
   }
#endif
   len = sarrl[2];
   su_mem_copy(cp, sarr[2], len);
   cp[len] = '\0';

   _CS_ITER_STEP();
   NYD_OU;
   return (_cs_iter != NULL);
}

FL boole
charset_iter_next(void)
{
   boole rv;
   NYD_IN;

   _CS_ITER_STEP();
   rv = (_cs_iter != NULL);
   NYD_OU;
   return rv;
}

FL boole
charset_iter_is_valid(void)
{
   boole rv;
   NYD_IN;

   rv = (_cs_iter != NULL);
   NYD_OU;
   return rv;
}

FL char const *
charset_iter(void)
{
   char const *rv;
   NYD_IN;

   rv = _cs_iter;
   NYD_OU;
   return rv;
}

FL char const *
charset_iter_or_fallback(void)
{
   char const *rv;
   NYD_IN;

   rv = _CS_ITER_GET();
   NYD_OU;
   return rv;
}

FL void
charset_iter_recurse(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
{
   NYD_IN;
   outer_storage[0] = _cs_iter_base;
   outer_storage[1] = _cs_iter;
   NYD_OU;
}

FL void
charset_iter_restore(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
{
   NYD_IN;
   _cs_iter_base = outer_storage[0];
   _cs_iter = outer_storage[1];
   NYD_OU;
}

#ifdef mx_HAVE_ICONV
FL char const *
need_hdrconv(struct header *hp) /* TODO once only, then iter */
{
   struct n_header_field *hfp;
   char const *rv;
   NYD_IN;

   rv = NULL;

   /* C99 */{
      struct n_header_field *chlp[3]; /* TODO JOINED AFTER COMPOSE! */
      u32 i;

      chlp[0] = n_poption_arg_C;
      chlp[1] = n_customhdr_list;
      chlp[2] = hp->h_user_headers;

      for(i = 0; i < NELEM(chlp); ++i)
         if((hfp = chlp[i]) != NULL)
            do if(_has_highbit(hfp->hf_dat + hfp->hf_nl +1))
               goto jneeds;
            while((hfp = hfp->hf_next) != NULL);
   }

   if (hp->h_mft != NULL) {
      if (_name_highbit(hp->h_mft))
         goto jneeds;
   }
   if (hp->h_from != NULL) {
      if (_name_highbit(hp->h_from))
         goto jneeds;
   } else if (_has_highbit(myaddrs(NULL)))
      goto jneeds;
   if (hp->h_reply_to) {
      if (_name_highbit(hp->h_reply_to))
         goto jneeds;
   } else {
      char const *v15compat;

      if((v15compat = ok_vlook(replyto)) != NULL)
         n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
      if(_has_highbit(v15compat))
         goto jneeds;
      if(_has_highbit(ok_vlook(reply_to)))
         goto jneeds;
   }
   if (hp->h_sender) {
      if (_name_highbit(hp->h_sender))
         goto jneeds;
   } else if (_has_highbit(ok_vlook(sender)))
      goto jneeds;

   if (_name_highbit(hp->h_to))
      goto jneeds;
   if (_name_highbit(hp->h_cc))
      goto jneeds;
   if (_name_highbit(hp->h_bcc))
      goto jneeds;
   if (_has_highbit(hp->h_subject))
jneeds:
      rv = _CS_ITER_GET(); /* TODO MIME/send: iter active? iter! else */
   NYD_OU;
   return rv;
}
#endif /* mx_HAVE_ICONV */

FL void
mime_fromhdr(struct str const *in, struct str *out, enum tdflags flags)
{
   /* TODO mime_fromhdr(): is called with strings that contain newlines;
    * TODO this is the usual newline problem all around the codebase;
    * TODO i.e., if we strip it, then the display misses it ;>
    * TODO this is why it is so messy and why S-nail v14.2 plus additional
    * TODO patch for v14.5.2 (and maybe even v14.5.3 subminor) occurred, and
    * TODO why our display reflects what is contained in the message: the 1:1
    * TODO relationship of message content and display!
    * TODO instead a header line should be decoded to what it is (a single
    * TODO line that is) and it should be objective to the backend whether
    * TODO it'll be folded to fit onto the display or not, e.g., for search
    * TODO purposes etc.  then the only condition we have to honour in here
    * TODO is that whitespace in between multiple adjacent MIME encoded words
    * TODO á la RFC 2047 is discarded; i.e.: this function should deal with
    * TODO RFC 2047 and be renamed: mime_fromhdr() -> mime_rfc2047_decode() */
   struct str cin, cout;
   char *p, *op, *upper;
   u32 convert, lastenc, lastoutl;
#ifdef mx_HAVE_ICONV
   char const *tcs;
   char *cbeg;
   iconv_t fhicd = (iconv_t)-1;
#endif
   NYD_IN;

   out->l = 0;
   if (in->l == 0) {
      *(out->s = n_alloc(1)) = '\0';
      goto jleave;
   }
   out->s = NULL;

#ifdef mx_HAVE_ICONV
   tcs = ok_vlook(ttycharset);
#endif
   p = in->s;
   upper = p + in->l;
   lastenc = lastoutl = 0;

   while (p < upper) {
      op = p;
      if (*p == '=' && *(p + 1) == '?') {
         p += 2;
#ifdef mx_HAVE_ICONV
         cbeg = p;
#endif
         while (p < upper && *p != '?')
            ++p;  /* strip charset */
         if (p >= upper)
            goto jnotmime;
         ++p;
#ifdef mx_HAVE_ICONV
         if (flags & TD_ICONV) {
            uz i = P2UZ(p - cbeg);
            char *ltag, *cs = n_lofi_alloc(i);

            su_mem_copy(cs, cbeg, --i);
            cs[i] = '\0';
            /* RFC 2231 extends the RFC 2047 character set definition in
             * encoded words by language tags - silently strip those off */
            if ((ltag = su_cs_find_c(cs, '*')) != NULL)
               *ltag = '\0';

            if (fhicd != (iconv_t)-1)
               n_iconv_close(fhicd);
            fhicd = su_cs_cmp_case(cs, tcs)
                  ? n_iconv_open(tcs, cs) : (iconv_t)-1;
            n_lofi_free(cs);
         }
#endif
         switch (*p) {
         case 'B': case 'b':
            convert = CONV_FROMB64;
            break;
         case 'Q': case 'q':
            convert = CONV_FROMQP;
            break;
         default: /* invalid, ignore */
            goto jnotmime;
         }
         if (*++p != '?')
            goto jnotmime;
         cin.s = ++p;
         cin.l = 1;
         for (;;) {
            if (PCMP(p + 1, >=, upper))
               goto jnotmime;
            if (*p++ == '?' && *p == '=')
               break;
            ++cin.l;
         }
         ++p;
         --cin.l;

         cout.s = NULL;
         cout.l = 0;
         if (convert == CONV_FROMB64) {
            if(!b64_decode_header(&cout, &cin))
               n_str_assign_cp(&cout, _("[Invalid Base64 encoding]"));
         }else if(!qp_decode_header(&cout, &cin))
            n_str_assign_cp(&cout, _("[Invalid Quoted-Printable encoding]"));
         /* Normalize all decoded newlines to spaces XXX only \0/\n yet */
         /* C99 */{
            char const *xcp;
            boole any;
            uz i, j;

            for(any = FAL0, i = cout.l; i-- != 0;)
               switch(cout.s[i]){
               case '\0':
               case '\n':
                  any = TRU1;
                  cout.s[i] = ' ';
                  /* FALLTHRU */
               default:
                  break;

               }

            if(any){
               /* I18N: must be non-empty, last must be closing bracket/xy */
               xcp = _("[Content normalized: ]");
               i = su_cs_len(xcp);
               j = cout.l;
               n_str_add_buf(&cout, xcp, i);
               su_mem_move(&cout.s[i - 1], cout.s, j);
               su_mem_copy(&cout.s[0], xcp, i - 1);
               cout.s[cout.l - 1] = xcp[i - 1];
            }
         }


         out->l = lastenc;
#ifdef mx_HAVE_ICONV
         /* TODO Does not really work if we have assigned some ASCII or even
          * TODO translated strings because of errors! */
         if ((flags & TD_ICONV) && fhicd != (iconv_t)-1) {
            cin.s = NULL, cin.l = 0; /* XXX string pool ! */
            convert = n_iconv_str(fhicd, n_ICONV_UNIDEFAULT, &cin, &cout, NULL);
            out = n_str_add(out, &cin);
            if (convert) {/* su_ERR_INVAL at EOS */
               n_iconv_reset(fhicd);
               out = n_str_add_buf(out, n_qm, 1); /* TODO unicode replacement */
            }
            n_free(cin.s);
         } else
#endif
            out = n_str_add(out, &cout);
         lastenc = lastoutl = out->l;
         n_free(cout.s);
      } else
jnotmime: {
         boole onlyws;

         p = op;
         onlyws = (lastenc > 0);
         for (;;) {
            if (++op == upper)
               break;
            if (op[0] == '=' && (PCMP(op + 1, ==, upper) || op[1] == '?'))
               break;
            if (onlyws && !su_cs_is_blank(*op))
               onlyws = FAL0;
         }

         out = n_str_add_buf(out, p, P2UZ(op - p));
         p = op;
         if (!onlyws || lastoutl != lastenc)
            lastenc = out->l;
         lastoutl = out->l;
      }
   }
   out->s[out->l] = '\0';

   if (flags & TD_ISPR) {
      makeprint(out, &cout);
      n_free(out->s);
      *out = cout;
   }
   if (flags & TD_DELCTRL)
      out->l = delctrl(out->s, out->l);
#ifdef mx_HAVE_ICONV
   if (fhicd != (iconv_t)-1)
      n_iconv_close(fhicd);
#endif
jleave:
   NYD_OU;
   return;
}

FL char *
mime_fromaddr(char const *name)
{
   char const *cp, *lastcp;
   char *res = NULL;
   uz ressz = 1, rescur = 0;
   NYD_IN;

   if (name == NULL)
      goto jleave;
   if (*name == '\0') {
      res = savestr(name);
      goto jleave;
   }

   if ((cp = routeaddr(name)) != NULL && cp > name) {
      _append_conv(&res, &ressz, &rescur, name, P2UZ(cp - name));
      lastcp = cp;
   } else
      cp = lastcp = name;

   for ( ; *cp; ++cp) {
      switch (*cp) {
      case '(':
         _append_str(&res, &ressz, &rescur, lastcp, P2UZ(cp - lastcp + 1));
         lastcp = ++cp;
         cp = skip_comment(cp);
         if (--cp > lastcp)
            _append_conv(&res, &ressz, &rescur, lastcp, P2UZ(cp - lastcp));
         lastcp = cp;
         break;
      case '"':
         while (*cp) {
            if (*++cp == '"')
               break;
            if (*cp == '\\' && cp[1] != '\0')
               ++cp;
         }
         break;
      }
   }
   if (cp > lastcp)
      _append_str(&res, &ressz, &rescur, lastcp, P2UZ(cp - lastcp));
   /* C99 */{
      char *x;

      x = res;
      res = savestrbuf(res, rescur);
      if(x != NULL)
         n_free(x);
   }
jleave:
   NYD_OU;
   return res;
}

FL sz
xmime_write(char const *ptr, uz size, FILE *f, enum conversion convert,
   enum tdflags dflags, struct str * volatile outrest,
   struct str * volatile inrest)
{
   sz rv;
   struct quoteflt *qf;
   NYD_IN;

   quoteflt_reset(qf = quoteflt_dummy(), f);
   rv = mime_write(ptr, size, f, convert, dflags, qf, outrest, inrest);
   quoteflt_flush(qf);
   NYD_OU;
   return rv;
}

static sigjmp_buf       __mimemw_actjmp; /* TODO someday.. */
static int              __mimemw_sig; /* TODO someday.. */
static n_sighdl_t  __mimemw_opipe;
static void
__mimemw_onsig(int sig) /* TODO someday, we won't need it no more */
{
   NYD; /* Signal handler */
   __mimemw_sig = sig;
   siglongjmp(__mimemw_actjmp, 1);
}

FL sz
mime_write(char const *ptr, uz size, FILE *f,
   enum conversion convert, enum tdflags volatile dflags,
   struct quoteflt *qf, struct str * volatile outrest,
   struct str * volatile inrest)
{
   /* TODO note: after send/MIME layer rewrite we will have a string pool
    * TODO so that memory allocation count drops down massively; for now,
    * TODO v14.0 that is, we pay a lot & heavily depend on the allocator.
    * TODO P.S.: furthermore all this encapsulated in filter objects instead */
   struct str in, out;
   sz volatile xsize;
   NYD_IN;

   dflags |= _TD_BUFCOPY;
   in.s = n_UNCONST(ptr);
   in.l = size;
   out.s = NULL;
   out.l = 0;

   if((xsize = size) == 0){
      if(inrest != NULL && inrest->l != 0)
         goto jinrest;
      if(outrest != NULL && outrest->l != 0)
         goto jconvert;
      goto jleave;
   }

   /* TODO This crap requires linewise input, then.  We need a filter chain
    * TODO as in input->iconv->base64 where each filter can have its own
    * TODO buffer, with a filter->fflush() call to get rid of those! */
#ifdef mx_HAVE_ICONV
   if ((dflags & TD_ICONV) && iconvd != (iconv_t)-1 &&
         (convert == CONV_TOQP || convert == CONV_8BIT ||
         convert == CONV_TOB64 || convert == CONV_TOHDR)) {
      if (n_iconv_str(iconvd, n_ICONV_NONE, &out, &in, NULL) != 0) {
         n_iconv_reset(iconvd);
         /* TODO This causes hard-failure.  We would need to have an action
          * TODO policy FAIL|IGNORE|SETERROR(but continue) */
         xsize = -1;
         goto jleave;
      }
      in = out;
      out.s = NULL;
      dflags &= ~_TD_BUFCOPY;
   }
#endif

jinrest:
   if(inrest != NULL && inrest->l > 0){
      if(size == 0){
         in = *inrest;
         inrest->s = NULL;
         inrest->l = 0;
      }else{
         out.s = n_alloc(in.l + inrest->l + 1);
         su_mem_copy(out.s, inrest->s, inrest->l);
         if(in.l > 0)
            su_mem_copy(&out.s[inrest->l], in.s, in.l);
         if(in.s != ptr)
            n_free(in.s);
         (in.s = out.s)[in.l += inrest->l] = '\0';
         inrest->l = 0;
         out.s = NULL;
      }
      dflags &= ~_TD_BUFCOPY;
   }

jconvert:
   __mimemw_sig = 0;
   __mimemw_opipe = safe_signal(SIGPIPE, &__mimemw_onsig);
   if (sigsetjmp(__mimemw_actjmp, 1))
      goto jleave;

   switch (convert) {
   case CONV_FROMQP:
      if(!qp_decode_part(&out, &in, outrest, inrest)){
         n_err(_("Invalid Quoted-Printable encoding ignored\n"));
         xsize = 0; /* TODO size = -1 stops outer levels! */
         break;
      }
      goto jqpb64_dec;
   case CONV_TOQP:
      if(qp_encode(&out, &in, QP_NONE) == NULL){
         xsize = 0; /* TODO size = -1 stops outer levels! */
         break;
      }
      goto jqpb64_enc;
   case CONV_8BIT:
      xsize = quoteflt_push(qf, in.s, in.l);
      break;
   case CONV_FROMB64:
      if(!b64_decode_part(&out, &in, outrest, inrest))
         goto jeb64;
      outrest = NULL;
      if(0){
      /* FALLTHRU */
   case CONV_FROMB64_T:
         if(!b64_decode_part(&out, &in, outrest, inrest)){
jeb64:
            n_err(_("Invalid Base64 encoding ignored\n"));
            xsize = 0; /* TODO size = -1 stops outer levels! */
            break;
         }
      }
jqpb64_dec:
      if ((xsize = out.l) != 0)
         xsize = _fwrite_td(&out, FAL0, (dflags & ~_TD_BUFCOPY), outrest, qf);
      break;
   case CONV_TOB64:
      /* TODO hack which is necessary unless this is a filter based approach
       * TODO and each filter has its own buffer (as necessary): we must not
       * TODO pass through a number of bytes which causes padding, otherwise we
       * TODO produce multiple adjacent base64 streams, and that is not treated
       * TODO in the same relaxed fashion like completely bogus bytes by at
       * TODO least mutt and OpenSSL.  So we need an expensive workaround
       * TODO unless we have input->iconv->base64 filter chain as such!! :( */
      if(size != 0 && /* for Coverity, else ASSERT() */ inrest != NULL){
         if(in.l > B64_ENCODE_INPUT_PER_LINE){
            uz i;

            i = in.l % B64_ENCODE_INPUT_PER_LINE;
            in.l -= i;

            if(i != 0){
               ASSERT(inrest->l == 0);
               inrest->s = n_realloc(inrest->s, i +1);
               su_mem_copy(inrest->s, &in.s[in.l], i);
               inrest->s[inrest->l = i] = '\0';
            }
         }else if(in.l < B64_ENCODE_INPUT_PER_LINE){
            inrest->s = n_realloc(inrest->s, in.l +1);
            su_mem_copy(inrest->s, in.s, in.l);
            inrest->s[inrest->l = in.l] = '\0';
            in.l = 0;
            xsize = 0;
            break;
         }
      }
      if(b64_encode(&out, &in, B64_LF | B64_MULTILINE) == NULL){
         xsize = -1;
         break;
      }
jqpb64_enc:
      xsize = fwrite(out.s, sizeof *out.s, out.l, f);
      if (xsize != (sz)out.l)
         xsize = -1;
      break;
   case CONV_FROMHDR:
      mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV | (dflags & TD_DELCTRL));
      xsize = quoteflt_push(qf, out.s, out.l);
      break;
   case CONV_TOHDR:
      xsize = mime_write_tohdr(&in, f, NULL, a_MIME_SH_NONE);
      break;
   case CONV_TOHDR_A:{
      uz col;

      if(dflags & _TD_BUFCOPY){
         n_str_dup(&out, &in);
         in = out;
         out.s = NULL;
         dflags &= ~_TD_BUFCOPY;
      }
      col = 0;
      xsize = mime_write_tohdr_a(&in, f, &col, a_MIME_SH_NONE);
      }break;
   default:
      xsize = _fwrite_td(&in, TRU1, dflags, NULL, qf);
      break;
   }

jleave:
   if (out.s != NULL)
      n_free(out.s);
   if (in.s != ptr)
      n_free(in.s);
   safe_signal(SIGPIPE, __mimemw_opipe);
   if (__mimemw_sig != 0)
      n_raise(__mimemw_sig);
   NYD_OU;
   return xsize;
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/mx/mta-aliases.c000066400000000000000000000273151352610246600166520ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ Implementation of mta-aliases.h. XXX Support multiple files
 *
 * Copyright (c) 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE mta_aliases
#define mx_SOURCE
#define mx_SOURCE_MTA_ALIASES

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

su_EMPTY_FILE()
#ifdef mx_HAVE_MTA_ALIASES
#include  /* TODO su_path_info */

#include 
#include 
#include 

#include "mx/file-streams.h"
#include "mx/names.h"

#include "mx/mta-aliases.h"
#include "su/code-in.h"

struct a_mtaali_g{
   char *mag_path; /* MTA alias file path, expanded (and init switch) */
   s64 mag_mtime; /* Modification time once last read in */
   s64 mag_size; /* Ditto, file size */
   /* We store n_strlist values which are set to "name + NUL + boole",
    * where the boole indicates whether name is a NAME (needs recursion) */
   struct su_cs_dict mag_dict;
};

struct a_mtaali_stack{
   struct a_mtaali_g *mas_entry;
   char const *mas_path;
   char const *mas_user;
   struct su_cs_dict mas_dict;
   struct stat mas_sb;
};

struct a_mtaali_query{
   struct su_cs_dict *maq_dp;
   struct mx_name *maq_result;
   s32 maq_err;
   u32 maq_type;
};

static struct a_mtaali_g a_mtaali_g; /* XXX debug atexit */

static void a_mtaali_gut_csd(struct su_cs_dict *csdp);

static s32 a_mtaali_cache_check(char const *usrfile);
static s32 a_mtaali_read_file(struct a_mtaali_stack *masp);

static void a_mtaali_expand(uz lvl, char const *name,
      struct a_mtaali_query *maqp);

static void
a_mtaali_gut_csd(struct su_cs_dict *csdp){
   struct n_strlist *slp, *tmp;
   struct su_cs_dict_view csdv;
   NYD2_IN;

   su_cs_dict_view_setup(&csdv, csdp);

   su_CS_DICT_VIEW_FOREACH(&csdv){
      for(slp = S(struct n_strlist*,su_cs_dict_view_data(&csdv));
            slp != NIL;){
         tmp = slp;
         slp = slp->sl_next;
         su_FREE(tmp);
      }
   }

   su_cs_dict_gut(csdp);
   NYD2_OU;
}

static s32
a_mtaali_cache_check(char const *usrfile){
   struct a_mtaali_stack mas;
   s32 rv;
   NYD_IN;

   if((mas.mas_path =
         fexpand(mas.mas_user = usrfile, FEXP_LOCAL | FEXP_NOPROTO)) == NIL){
      rv = su_ERR_NOENT;
      goto jerr;
   }else if(stat(mas.mas_path, &mas.mas_sb) == -1){
      rv = su_err_no();
jerr:
      n_err(_("*mta_aliases*: %s: %s\n"),
         n_shexp_quote_cp(mas.mas_user, FAL0), su_err_doc(rv));
   }else if(a_mtaali_g.mag_path == NIL ||
         su_cs_cmp(mas.mas_path, a_mtaali_g.mag_path) ||
         a_mtaali_g.mag_mtime < mas.mas_sb.st_mtime ||
         a_mtaali_g.mag_size != mas.mas_sb.st_size){
      if((rv = a_mtaali_read_file(&mas)) == su_ERR_NONE){
         if(a_mtaali_g.mag_path == NIL)
            su_cs_dict_create(&a_mtaali_g.mag_dict,
               (su_CS_DICT_POW2_SPACED | su_CS_DICT_CASE), NIL);
         else
            su_FREE(a_mtaali_g.mag_path);

         a_mtaali_g.mag_path = su_cs_dup(mas.mas_path, 0);
         a_mtaali_g.mag_mtime = S(s64,mas.mas_sb.st_mtime);
         a_mtaali_g.mag_size = S(s64,mas.mas_sb.st_size);
         su_cs_dict_swap(&a_mtaali_g.mag_dict, &mas.mas_dict);
         a_mtaali_gut_csd(&mas.mas_dict);
      }
   }else
      rv = su_ERR_NONE;

   NYD_OU;
   return rv;
}

static s32
a_mtaali_read_file(struct a_mtaali_stack *masp){
   struct str line, l;
   struct n_string s, *sp;
   struct su_cs_dict *dp;
   s32 rv;
   FILE *afp;
   NYD_IN;

   if((afp = mx_fs_open(masp->mas_path, "r")) == NIL){
      rv = su_err_no();
      n_err(_("*mta-aliases*: cannot open %s: %s\n"),
         n_shexp_quote_cp(masp->mas_user, FAL0), su_err_doc(rv));
      goto jleave;
   }

   dp = su_cs_dict_create(&masp->mas_dict,
         (su_CS_DICT_POW2_SPACED | su_CS_DICT_CASE), NIL);
   sp = n_string_creat_auto(&s);
   sp = n_string_book(sp, 512);

   /* Read in the database */
   su_mem_set(&line, 0, sizeof line);

   while((rv = readline_restart(afp, &line.s, &line.l, 0)) >= 0){
      /* :: According to Postfix aliases(5) */
      l.s = line.s;
      l.l = S(uz,rv);
      n_str_trim(&l, n_STR_TRIM_BOTH);

      /* :
       *    Empty lines and whitespace-only lines are ignored, as are lines
       *    whose first non-whitespace character is a `#'. */
      if(l.l == 0 || l.s[0] == '#')
         continue;

      /* :
       *    A logical line starts with non-whitespace text.  A line that starts
       *    with whitespace continues a logical line. */
      if(l.s != line.s || sp->s_len == 0){
         sp = n_string_push_buf(sp, l.s, l.l);
         continue;
      }

      ASSERT(sp->s_len > 0);
jparse_line:{
         /* :
          *    An alias definition has the form
          *       name: value1, value2, ...
          *    ...
          *    The name is a local address (no domain part).  Use double quotes
          *    when the name contains any special characters such as
          *    whitespace, `#', `:', or `@'. The name is folded to lowercase,
          *    in order to make database lookups case insensitive.
          * XXX Don't support quoted names nor special characters (manual!) */
         struct str l2;
         char c;

         l2.l = sp->s_len;
         l2.s = n_string_cp(sp);

         if((l2.s = su_cs_find_c(l2.s, ':')) == NIL ||
               (l2.l = P2UZ(l2.s - sp->s_dat)) == 0){
            l.s = UNCONST(char*,N_("invalid line"));
            rv = su_ERR_INVAL;
            goto jparse_err;
         }

         /* XXX Manual! "name" may only be a Unix username (useradd(8)):
          *    Usernames must start with a lower case letter or an underscore,
          *    followed by lower case letters, digits, underscores, or dashes.
          *    They can end with a dollar sign. In regular expression terms:
          *       [a-z_][a-z0-9_-]*[$]?
          *    Usernames may only be up to 32 characters long.
          * Test against alpha since the csdict will lowercase names.. */
         *l2.s = '\0';
         c = *(l2.s = sp->s_dat);
         if(!su_cs_is_alpha(c) && c != '_'){
jename:
            l.s = UNCONST(char*,N_("not a valid name\n"));
            rv = su_ERR_INVAL;
            goto jparse_err;
         }
         while((c = *++l2.s) != '\0')
            if(!su_cs_is_alnum(c) && c != '_' && c != '-'){
               if(c == '$' && *l2.s == '\0')
                  break;
               goto jename;
            }

         /* Be strict regarding file content */
         if(UNLIKELY(su_cs_dict_has_key(dp, sp->s_dat))){
            l.s = UNCONST(char*,N_("duplicate name"));
            rv = su_ERR_ADDRINUSE;
            goto jparse_err;
         }else{
            /* Seems to be a usable name.  Parse data off */
            struct n_strlist *head, **tailp;
            struct mx_name *nphead,*np;

            nphead = lextract(l2.s = &sp->s_dat[l2.l + 1], GTO | GFULL |
                  GQUOTE_ENCLOSED_OK);

            if(UNLIKELY(nphead == NIL)){
jeval:
               n_err(_("*mta_aliases*: %s: ignoring empty/unsupported value: "
                     "%s: %s\n"),
                  n_shexp_quote_cp(masp->mas_user, FAL0),
                  n_shexp_quote_cp(sp->s_dat, FAL0),
                  n_shexp_quote_cp(l2.s, FAL0));
               continue;
            }

            for(np = nphead; np != NIL; np = np->n_flink)
               /* TODO :include:/file/path directive not yet <> manual! */
               if((np->n_flags & mx_NAME_ADDRSPEC_ISFILE) &&
                     su_cs_starts_with(np->n_name, ":include:"))
                  goto jeval;

            for(head = NIL, tailp = &head, np = nphead;
                  np != NIL; np = np->n_flink){
               struct n_strlist *slp;

               l2.l = su_cs_len(np->n_fullname) +1;
               slp = n_STRLIST_ALLOC(l2.l + 1);
               *tailp = slp;
               slp->sl_next = NIL;
               tailp = &slp->sl_next;
               slp->sl_len = l2.l -1;
               su_mem_copy(slp->sl_dat, np->n_fullname, l2.l);
               slp->sl_dat[l2.l] = ((np->n_flags & mx_NAME_ADDRSPEC_ISNAME
                     ) != 0);
            }

            if((rv = su_cs_dict_insert(dp, sp->s_dat, head)) != su_ERR_NONE){
               n_err(_("*mta_aliases*: failed to create storage: %s\n"),
                  su_err_doc(rv));
               goto jdone;
            }
         }
      }

      /* Worked last line leftover? */
      if(l.l == 0){
         /*sp = n_string_trunc(sp, 0);
          *break;*/
         goto jparse_done;
      }
      sp = n_string_assign_buf(sp, l.s, l.l);
   }
   /* Last line leftover to parse? */
   if(sp->s_len > 0){
      l.l = 0;
      goto jparse_line;
   }

jparse_done:
   rv = su_ERR_NONE;
jdone:
   if(line.s != NIL)
      n_free(line.s);

   if(rv != su_ERR_NONE)
      a_mtaali_gut_csd(dp);

   mx_fs_close(afp);
jleave:
   NYD_OU;
   return rv;

jparse_err:
   n_err("*mta-aliases*: %s: %s: %s\n",
      n_shexp_quote_cp(masp->mas_user, FAL0), V_(l.s),
      n_shexp_quote_cp(sp->s_dat, FAL0));
   goto jdone;
}

static void
a_mtaali_expand(uz lvl, char const *name, struct a_mtaali_query *maqp){
   struct n_strlist *slp;
   NYD2_IN;

   ++lvl;

   if((slp = S(struct n_strlist*,su_cs_dict_lookup(maqp->maq_dp, name))
         ) == NIL){
jput_name:
      maqp->maq_err = su_ERR_DESTADDRREQ;
      maqp->maq_result = cat(nalloc(name, maqp->maq_type | GFULL),
            maqp->maq_result);
   }else do{
      /* Is it a name itself? */
      if(slp->sl_dat[slp->sl_len + 1] != FAL0){
         if(UCMP(z, lvl, <, n_ALIAS_MAXEXP)) /* TODO not a real error! */
            a_mtaali_expand(lvl, slp->sl_dat, maqp);
         else{
            n_err(_("*mta_aliases*: stopping recursion at depth %d\n"),
               n_ALIAS_MAXEXP);
            goto jput_name;
         }
      }else
         maqp->maq_result = cat(maqp->maq_result,
               nalloc(slp->sl_dat, maqp->maq_type | GFULL));
   }while((slp = slp->sl_next) != NIL);

   NYD2_OU;
}

s32
mx_mta_aliases_expand(struct mx_name **npp){
   struct a_mtaali_query maq;
   struct mx_name *np, *nphead;
   s32 rv;
   char const *file;
   NYD_IN;

   rv = su_ERR_NONE;

   /* Is there the possibility we have to do anything? */
   if((file = ok_vlook(mta_aliases)) == NIL)
      goto jleave;

   for(np = *npp; np != NIL; np = np->n_flink)
      if(!(np->n_type & GDEL) && (np->n_flags & mx_NAME_ADDRSPEC_ISNAME))
         break;
   if(np == NIL)
      goto jleave;

   /* Then lookup the cache, creating/updating it first as necessary */
   if((rv = a_mtaali_cache_check(file)) != su_ERR_NONE)
      goto jleave;

   if(su_cs_dict_count(maq.maq_dp = &a_mtaali_g.mag_dict) == 0){
      rv = su_ERR_DESTADDRREQ;
      goto jleave;
   }

   nphead = *npp;
   maq.maq_result = *npp = NIL;
   maq.maq_err = su_ERR_NONE;

   for(np = nphead; np != NIL; np = np->n_flink){
      if(np->n_flags & mx_NAME_ADDRSPEC_ISNAME){
         maq.maq_type = np->n_type;
         a_mtaali_expand(0, np->n_name, &maq);
      }else
         maq.maq_result = cat(maq.maq_result, ndup(np, np->n_type));
   }

   *npp = maq.maq_result;
   rv = maq.maq_err;
jleave:
   NYD_OU;
   return rv;
}

#include "su/code-ou.h"
#endif /* mx_HAVE_MTA_ALIASES */
/* s-it-mode */
s-nail-14.9.15/src/mx/names.c000066400000000000000000001124721352610246600155540ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ Implementation of names.h.
 *@ XXX Use a su_cs_set for alternates stuff?
 *
 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: BSD-3-Clause XXX ISC once yank stuff+ changed
 */
/*
 * Copyright (c) 1980, 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.
 */
#undef su_FILE
#define su_FILE names
#define mx_SOURCE
#define mx_SOURCE_NAMES /* XXX a lie - it is rather n_ yet */

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

#include 
#include 
#include 
#include 

#include "mx/iconv.h"
#include "mx/mta-aliases.h"

#include "mx/names.h"
#include "su/code-in.h"

/* ..of a_nm_alias_dp.
 * We rely on resorting, and use has_key()...lookup() (a_nm_alias_expand()).
 * The value is a n_strlist*, which we manage directly (no toolbox).
 * name::n_name, after .sl_dat[.sl_len] one boole that indicates
 * recursion-allowed, thereafter name::n_fullname (empty if EQ n_name) */
#define a_NM_ALIAS_FLAGS (su_CS_DICT_POW2_SPACED |\
      su_CS_DICT_HEAD_RESORT | su_CS_DICT_AUTO_SHRINK | su_CS_DICT_ERR_PASS)
#define a_NM_ALIAS_TRESHOLD_SHIFT 2

/* ..of a_nm_a8s_dp */
#define a_NM_A8S_FLAGS (su_CS_DICT_CASE |\
      su_CS_DICT_AUTO_SHRINK | su_CS_DICT_ERR_PASS)
#define a_NM_A8S_TRESHOLD_SHIFT 2

static struct su_cs_dict *a_nm_alias_dp, a_nm_alias__d; /* XXX atexit gut()..*/
static struct su_cs_dict *a_nm_a8s_dp, a_nm_a8s__d; /* XXX .. (DVL()) */

/* Same name, while taking care for *allnet*? */
static boole a_nm_is_same_name(char const *n1, char const *n2);

/* Mark all (!file, !pipe) nodes with the given name */
static struct mx_name *a_nm_namelist_mark_name(struct mx_name *np,
      char const *name);

/* Grab a single name (liberal name) */
static char const *a_nm_yankname(char const *ap, char *wbuf,
      char const *separators, int keepcomms);

/* Extraction multiplexer that splits an input line to names */
static struct mx_name *a_nm_extract1(char const *line, enum gfield ntype,
      char const *separators, boole keepcomms);

/* elide() helper */
static su_sz a_nm_elide_sort(void const *s1, void const *s2);

/* Recursively expand an alias name, adjust nlist for result and return it;
 * limit expansion to some fixed level.
 * metoo=*metoo*, logname=$LOGNAME == optimization */
static struct mx_name *a_nm_alias_expand(uz level, struct mx_name *nlist,
      char const *name, int ntype, boole metoo, char const *logname);

/* */
static struct n_strlist *a_nm_alias_dump(char const *cmdname, char const *key,
      void const *dat);

/* */
static struct n_strlist *a_nm_a8s_dump(char const *cmdname, char const *key,
      void const *dat);

static boole
a_nm_is_same_name(char const *n1, char const *n2){
   boole rv;
   char c1, c2, c1r, c2r;
   NYD2_IN;

   if(ok_blook(allnet)){
      for(;; ++n1, ++n2){
         c1 = *n1;
         c1 = su_cs_to_lower(c1);
         c1r = (c1 == '\0' || c1 == '@');
         c2 = *n2;
         c2 = su_cs_to_lower(c2);
         c2r = (c2 == '\0' || c2 == '@');

         if(c1r || c2r){
            rv = (c1r == c2r);
            break;
         }else if(c1 != c2){
            rv = FAL0;
            break;
         }
      }
   }else
      rv = !su_cs_cmp_case(n1, n2);
   NYD2_OU;
   return rv;
}

static struct mx_name *
a_nm_namelist_mark_name(struct mx_name *np, char const *name){
   struct mx_name *p;
   NYD2_IN;

   for(p = np; p != NULL; p = p->n_flink)
      if(!(p->n_type & GDEL) &&
            !(p->n_flags & (S(u32,S32_MIN) | mx_NAME_ADDRSPEC_ISFILE |
               mx_NAME_ADDRSPEC_ISPIPE)) &&
            a_nm_is_same_name(p->n_name, name))
         p->n_flags |= S(u32,S32_MIN);
   NYD2_OU;
   return np;
}

static char const *
a_nm_yankname(char const *ap, char *wbuf, char const *separators,
   int keepcomms)
{
   char const *cp;
   char *wp, c, inquote, lc, lastsp;
   NYD_IN;

   *(wp = wbuf) = '\0';

   /* Skip over intermediate list trash, as in ".org>  ,  " */
   for (c = *ap; su_cs_is_blank(c) || c == ','; c = *++ap)
      ;
   if (c == '\0') {
      cp = NULL;
      goto jleave;
   }

   /* Parse a full name: TODO RFC 5322
    * - Keep everything in quotes, liberal handle *quoted-pair*s therein
    * - Skip entire (nested) comments
    * - In non-quote, non-comment, join adjacent space to a single SP
    * - Understand separators only in non-quote, non-comment context,
    *   and only if not part of a *quoted-pair* (XXX too liberal) */
   cp = ap;
   for (inquote = lc = lastsp = 0;; lc = c, ++cp) {
      c = *cp;
      if (c == '\0')
         break;
      if (c == '\\')
         goto jwpwc;
      if (c == '"') {
         if (lc != '\\')
            inquote = !inquote;
#if 0 /* TODO when doing real RFC 5322 parsers - why have i done this? */
         else
            --wp;
#endif
         goto jwpwc;
      }
      if (inquote || lc == '\\') {
jwpwc:
         *wp++ = c;
         lastsp = 0;
         continue;
      }
      if (c == '(') {
         ap = cp;
         cp = skip_comment(cp + 1);
         if (keepcomms)
            while (ap < cp)
               *wp++ = *ap++;
         --cp;
         lastsp = 0;
         continue;
      }
      if (su_cs_find_c(separators, c) != NULL)
         break;

      lc = lastsp;
      lastsp = su_cs_is_blank(c);
      if (!lastsp || !lc)
         *wp++ = c;
   }
   if (su_cs_is_blank(lc))
      --wp;

   *wp = '\0';
jleave:
   NYD_OU;
   return cp;
}

static struct mx_name *
a_nm_extract1(char const *line, enum gfield ntype, char const *seps,
      boole keepcomms){
   struct mx_name *headp, *tailp, *np;
   NYD_IN;

   headp = NULL;

   if(line == NULL || *line == '\0')
      ;
   else if(ntype & GNOT_A_LIST)
      /* Not GNULL_OK! (yet: see below) */
      headp = nalloc(line, ntype | GSKIN | GNOT_A_LIST);
   else{
      char const *cp;
      char *nbuf;

      nbuf = n_lofi_alloc(su_cs_len(cp = line) +1);

      for(tailp = headp;
            ((cp = a_nm_yankname(cp, nbuf, seps, keepcomms)) != NULL);){
         /* TODO Cannot set GNULL_OK because otherwise this software blows up.
          * TODO We will need a completely new way of reporting the errors of
          * TODO is_addr_invalid() ... */
         if((np = nalloc(nbuf, ntype /*| GNULL_OK*/)) != NULL){
            if((np->n_blink = tailp) != NULL)
               tailp->n_flink = np;
            else
               headp = np;
            tailp = np;
         }
      }

      n_lofi_free(nbuf);
   }

   NYD_OU;
   return headp;
}

static su_sz
a_nm_elide_sort(void const *s1, void const *s2){
   struct mx_name const *np1, *np2;
   su_sz rv;
   NYD2_IN;

   np1 = s1;
   np2 = s2;
   if(!(rv = su_cs_cmp_case(np1->n_name, np2->n_name))){
      LCTAV(GTO < GCC && GCC < GBCC);
      rv = (np1->n_type & (GTO | GCC | GBCC)) -
            (np2->n_type & (GTO | GCC | GBCC));
   }
   NYD2_OU;
   return rv;
}

static struct mx_name *
a_nm_alias_expand(uz level, struct mx_name *nlist, char const *name, int ntype,
      boole metoo, char const *logname){
   struct mx_name *np, *nlist_tail;
   char const *ccp;
   struct n_strlist const *slp, *slp_base, *slp_next;
   NYD2_IN;
   ASSERT_NYD(a_nm_alias_dp != NIL);
   ASSERT(mx_alias_is_valid_name(name));

   if(UCMP(z, level++, ==, n_ALIAS_MAXEXP)){ /* TODO not a real error!! */
      n_err(_("alias: stopping recursion at depth %d\n"), n_ALIAS_MAXEXP);
      slp_next = NIL;
      ccp = name;
      goto jlinkin;
   }

   slp_next = slp_base =
   slp = S(struct n_strlist const*,su_cs_dict_lookup(a_nm_alias_dp, name));

   if(slp == NIL){
      ccp = name;
      goto jlinkin;
   }
   do{ /* while(slp != NIL); */
      slp_next = slp->sl_next;

      if(slp->sl_len == 0)
         continue;

      /* Cannot shadow itself.  Recursion allowed for target? */
      if(su_cs_cmp(name, slp->sl_dat) && slp->sl_dat[slp->sl_len + 1] != FAL0){
         /* For S-nail(1), the "alias" may *be* the sender in that a name
          * to a full address specification */
         nlist = a_nm_alias_expand(level, nlist, slp->sl_dat, ntype, metoo,
               logname);
         continue;
      }

      /* Here we should allow to expand to itself if only person in alias */
      if(metoo || slp_base->sl_next == NIL ||
            !a_nm_is_same_name(slp->sl_dat, logname)){
         /* Use .n_name if .n_fullname is not set */
         if(*(ccp = &slp->sl_dat[slp->sl_len + 2]) == '\0')
            ccp = slp->sl_dat;

jlinkin:
         if((np = n_extract_single(ccp, ntype | GFULL)) != NIL){
            if((nlist_tail = nlist) != NIL){ /* XXX su_list_push()! */
               while(nlist_tail->n_flink != NIL)
                  nlist_tail = nlist_tail->n_flink;
               nlist_tail->n_flink = np;
               np->n_blink = nlist_tail;
            }else
               nlist = np;
         }
      }
   }while((slp = slp_next) != NIL);

   NYD2_OU;
   return nlist;
}

static struct n_strlist *
a_nm_alias_dump(char const *cmdname, char const *key, void const *dat){
   struct n_string s_b, *s;
   struct n_strlist *slp;
   NYD2_IN;

   s = n_string_creat_auto(&s_b);
   s = n_string_resize(s, 511);
   s = n_string_trunc(s, VSTRUCT_SIZEOF(struct n_strlist, sl_dat)); /* gross */

   s = n_string_push_cp(s, cmdname);
   s = n_string_push_c(s, ' ');
   s = n_string_push_cp(s, key); /*n_shexp_quote_cp(key, TRU1); valid alias */

   for(slp = UNCONST(struct n_strlist*,dat); slp != NIL; slp = slp->sl_next){
      s = n_string_push_c(s, ' ');
      /* Use .n_fullname if available, fall back to .n_name */
      key = &slp->sl_dat[slp->sl_len + 2];
      if(*key == '\0')
         key = slp->sl_dat;
      s = n_string_push_cp(s, n_shexp_quote_cp(key, TRU1));
   }

   slp = C(struct n_strlist*,S(void const*,n_string_cp(s)));
   slp->sl_next = NIL;
   slp->sl_len = s->s_len - VSTRUCT_SIZEOF(struct n_strlist, sl_dat);

   NYD2_OU;
   return slp;
}

static struct n_strlist *
a_nm_a8s_dump(char const *cmdname, char const *key, void const *dat){
   /* XXX real strlist + str_to_fmt() */
   struct n_strlist *slp;
   uz kl;
   NYD2_IN;
   UNUSED(cmdname);
   UNUSED(dat);

   /*key = n_shexp_quote_cp(key, TRU1); plain address: not needed */
   kl = su_cs_len(key);

   slp = n_STRLIST_AUTO_ALLOC(kl +1);
   slp->sl_next = NIL;
   slp->sl_len = kl;
   su_mem_copy(slp->sl_dat, key, kl +1);
   NYD2_OU;
   return slp;
}

struct mx_name *
nalloc(char const *str, enum gfield ntype)
{
   struct n_addrguts ag;
   struct str in, out;
   struct mx_name *np;
   NYD_IN;
   ASSERT(!(ntype & GFULLEXTRA) || (ntype & GFULL) != 0);

   str = n_addrspec_with_guts(&ag, str, ntype);
   if(str == NULL){
      /* TODO this may not return NULL but for new-style callers */
      if(ntype & GNULL_OK){
         np = NULL;
         goto jleave;
      }
   }
   ntype &= ~(GNOT_A_LIST | GNULL_OK | GMAILTO_URI); /* (all this a hack is) */
   str = ag.ag_input; /* Take the possibly reordered thing */

   if (!(ag.ag_n_flags & mx_NAME_NAME_SALLOC)) {
      ag.ag_n_flags |= mx_NAME_NAME_SALLOC;
      np = n_autorec_alloc(sizeof(*np) + ag.ag_slen +1);
      su_mem_copy(np + 1, ag.ag_skinned, ag.ag_slen +1);
      ag.ag_skinned = (char*)(np + 1);
   } else
      np = n_autorec_alloc(sizeof *np);

   np->n_flink = NULL;
   np->n_blink = NULL;
   np->n_type = ntype;
   np->n_fullname = np->n_name = ag.ag_skinned;
   np->n_fullextra = NULL;
   np->n_flags = ag.ag_n_flags;

   if (ntype & GFULL) {
      if (ag.ag_ilen == ag.ag_slen
#ifdef mx_HAVE_IDNA
            && !(ag.ag_n_flags & mx_NAME_IDNA)
#endif
      )
         goto jleave;
      if (ag.ag_n_flags & mx_NAME_ADDRSPEC_ISFILEORPIPE)
         goto jleave;

      /* n_fullextra is only the complete name part without address.
       * Beware of "-r ''", don't treat that as FULLEXTRA */
      if ((ntype & GFULLEXTRA) && ag.ag_ilen > ag.ag_slen + 2) {
         uz s = ag.ag_iaddr_start, e = ag.ag_iaddr_aend, i;
         char const *cp;

         if (s == 0 || str[--s] != '<' || str[e++] != '>')
            goto jskipfullextra;
         i = ag.ag_ilen - e;
         in.s = n_lofi_alloc(s + 1 + i +1);
         while(s > 0 && su_cs_is_blank(str[s - 1]))
            --s;
         su_mem_copy(in.s, str, s);
         if (i > 0) {
            in.s[s++] = ' ';
            while (su_cs_is_blank(str[e])) {
               ++e;
               if (--i == 0)
                  break;
            }
            if (i > 0)
               su_mem_copy(&in.s[s], &str[e], i);
         }
         s += i;
         in.s[in.l = s] = '\0';
         mime_fromhdr(&in, &out, /* TODO TD_ISPR |*/ TD_ICONV);

         for (cp = out.s, i = out.l; i > 0 && su_cs_is_space(*cp); --i, ++cp)
            ;
         while (i > 0 && su_cs_is_space(cp[i - 1]))
            --i;
         np->n_fullextra = savestrbuf(cp, i);

         n_lofi_free(in.s);
         n_free(out.s);
      }
jskipfullextra:

      /* n_fullname depends on IDNA conversion */
#ifdef mx_HAVE_IDNA
      if (!(ag.ag_n_flags & mx_NAME_IDNA)) {
#endif
         in.s = UNCONST(char*,str);
         in.l = ag.ag_ilen;
#ifdef mx_HAVE_IDNA
      } else {
         /* The domain name was IDNA and has been converted.  We also have to
          * ensure that the domain name in .n_fullname is replaced with the
          * converted version, since MIME doesn't perform encoding of addrs */
         /* TODO This definetely doesn't belong here! */
         uz l = ag.ag_iaddr_start,
            lsuff = ag.ag_ilen - ag.ag_iaddr_aend;
         in.s = n_lofi_alloc(l + ag.ag_slen + lsuff +1);
         su_mem_copy(in.s, str, l);
         su_mem_copy(in.s + l, ag.ag_skinned, ag.ag_slen);
         l += ag.ag_slen;
         su_mem_copy(in.s + l, str + ag.ag_iaddr_aend, lsuff);
         l += lsuff;
         in.s[l] = '\0';
         in.l = l;
      }
#endif
      mime_fromhdr(&in, &out, /* TODO TD_ISPR |*/ TD_ICONV);
      np->n_fullname = savestr(out.s);
      n_free(out.s);
#ifdef mx_HAVE_IDNA
      if (ag.ag_n_flags & mx_NAME_IDNA)
         n_lofi_free(in.s);
#endif
   }

jleave:
   NYD_OU;
   return np;
}

struct mx_name *
nalloc_fcc(char const *file){
   struct mx_name *nnp;
   NYD_IN;

   nnp = n_autorec_alloc(sizeof *nnp);
   nnp->n_flink = nnp->n_blink = NULL;
   nnp->n_type = GBCC | GBCC_IS_FCC; /* xxx Bcc: <- namelist_vaporise_head */
   nnp->n_flags = mx_NAME_NAME_SALLOC | mx_NAME_SKINNED |
         mx_NAME_ADDRSPEC_ISFILE;
   nnp->n_fullname = nnp->n_name = savestr(file);
   nnp->n_fullextra = NULL;
   NYD_OU;
   return nnp;
}

struct mx_name *
ndup(struct mx_name *np, enum gfield ntype)
{
   struct mx_name *nnp;
   NYD_IN;

   if ((ntype & (GFULL | GSKIN)) && !(np->n_flags & mx_NAME_SKINNED)) {
      nnp = nalloc(np->n_name, ntype);
      goto jleave;
   }

   nnp = n_autorec_alloc(sizeof *np);
   nnp->n_flink = nnp->n_blink = NULL;
   nnp->n_type = ntype;
   nnp->n_flags = np->n_flags | mx_NAME_NAME_SALLOC;
   nnp->n_name = savestr(np->n_name);

   if (np->n_name == np->n_fullname || !(ntype & (GFULL | GSKIN))) {
      nnp->n_fullname = nnp->n_name;
      nnp->n_fullextra = NULL;
   } else {
      nnp->n_fullname = savestr(np->n_fullname);
      nnp->n_fullextra = (np->n_fullextra == NULL) ? NULL
            : savestr(np->n_fullextra);
   }

jleave:
   NYD_OU;
   return nnp;
}

struct mx_name *
cat(struct mx_name *n1, struct mx_name *n2){
   struct mx_name *tail;
   NYD_IN;

   tail = n2;
   if(n1 == NULL)
      goto jleave;

   tail = n1;
   if(n2 == NULL || (n2->n_type & GDEL))
      goto jleave;

   while(tail->n_flink != NULL)
      tail = tail->n_flink;
   tail->n_flink = n2;
   n2->n_blink = tail;
   tail = n1;

jleave:
   NYD_OU;
   return tail;
}

struct mx_name *
n_namelist_dup(struct mx_name const *np, enum gfield ntype){
   struct mx_name *nlist, *xnp;
   NYD_IN;

   for(nlist = xnp = NULL; np != NULL; np = np->n_flink){
      struct mx_name *x;

      if(!(np->n_type & GDEL)){
         x = ndup(UNCONST(struct mx_name*,np), (np->n_type & ~GMASK) | ntype);
         if((x->n_blink = xnp) == NULL)
            nlist = x;
         else
            xnp->n_flink = x;
         xnp = x;
      }
   }
   NYD_OU;
   return nlist;
}

u32
count(struct mx_name const *np){
   u32 c;
   NYD_IN;

   for(c = 0; np != NIL; np = np->n_flink)
      if(!(np->n_type & GDEL))
         ++c;
   NYD_OU;
   return c;
}

u32
count_nonlocal(struct mx_name const *np){
   u32 c;
   NYD_IN;

   for(c = 0; np != NIL; np = np->n_flink)
      if(!(np->n_type & GDEL) &&
            !(np->n_flags & mx_NAME_ADDRSPEC_ISFILEORPIPE))
         ++c;
   NYD_OU;
   return c;
}

struct mx_name *
extract(char const *line, enum gfield ntype)
{
   struct mx_name *rv;
   NYD_IN;

   rv = a_nm_extract1(line, ntype, " \t,", 0);
   NYD_OU;
   return rv;
}

struct mx_name *
lextract(char const *line, enum gfield ntype)
{
   char *cp;
   struct mx_name *rv;
   NYD_IN;

   if(!(ntype & GSHEXP_PARSE_HACK) || !(expandaddr_to_eaf() & EAF_SHEXP_PARSE))
      cp = NULL;
   else{
      struct str sin;
      struct n_string s_b, *s;
      enum n_shexp_state shs;

      n_autorec_relax_create();
      s = n_string_creat_auto(&s_b);
      sin.s = UNCONST(char*,line); /* logical */
      sin.l = UZ_MAX;
      shs = n_shexp_parse_token((n_SHEXP_PARSE_LOG |
            n_SHEXP_PARSE_IGNORE_EMPTY | n_SHEXP_PARSE_QUOTE_AUTO_FIXED |
            n_SHEXP_PARSE_QUOTE_AUTO_DSQ), s, &sin, NULL);
      if(!(shs & n_SHEXP_STATE_ERR_MASK) && (shs & n_SHEXP_STATE_STOP)){
         line = cp = n_lofi_alloc(s->s_len +1);
         su_mem_copy(cp, n_string_cp(s), s->s_len +1);
      }else
         line = cp = su_NIL;
      n_autorec_relax_gut();
   }

   if(line == su_NIL)
      rv = su_NIL;
   else if((ntype & GNOT_A_LIST) || strpbrk(line, ",\"\\(<|"))
      rv = a_nm_extract1(line, ntype, ",", 1);
   else
      rv = extract(line, ntype);

   if(cp != su_NIL)
      n_lofi_free(cp);
   NYD_OU;
   return rv;
}

struct mx_name *
n_extract_single(char const *line, enum gfield ntype){
   struct mx_name *rv;
   NYD_IN;

   rv = nalloc(line, ntype | GSKIN | GNOT_A_LIST | GNULL_OK);
   NYD_OU;
   return rv;
}

char *
detract(struct mx_name *np, enum gfield ntype)
{
   char *topp, *cp;
   struct mx_name *p;
   int flags, s;
   NYD_IN;

   topp = NULL;
   if (np == NULL)
      goto jleave;

   flags = ntype & (GCOMMA | GNAMEONLY);
   ntype &= ~(GCOMMA | GNAMEONLY);
   s = 0;

   for (p = np; p != NULL; p = p->n_flink) {
      if (ntype && (p->n_type & GMASK) != ntype)
         continue;
      s += su_cs_len(flags & GNAMEONLY ? p->n_name : p->n_fullname) +1;
      if (flags & GCOMMA)
         ++s;
   }
   if (s == 0)
      goto jleave;

   s += 2;
   topp = n_autorec_alloc(s);
   cp = topp;
   for (p = np; p != NULL; p = p->n_flink) {
      if (ntype && (p->n_type & GMASK) != ntype)
         continue;
      cp = su_cs_pcopy(cp, (flags & GNAMEONLY ? p->n_name : p->n_fullname));
      if ((flags & GCOMMA) && p->n_flink != NULL)
         *cp++ = ',';
      *cp++ = ' ';
   }
   *--cp = 0;
   if ((flags & GCOMMA) && *--cp == ',')
      *cp = 0;
jleave:
   NYD_OU;
   return topp;
}

struct mx_name *
grab_names(enum n_go_input_flags gif, char const *field, struct mx_name *np,
      int comma, enum gfield gflags)
{
   struct mx_name *nq;
   NYD_IN;

jloop:
   np = lextract(n_go_input_cp(gif, field, detract(np, comma)), gflags);
   for (nq = np; nq != NULL; nq = nq->n_flink)
      if (is_addr_invalid(nq, EACM_NONE))
         goto jloop;
   NYD_OU;
   return np;
}

boole
name_is_same_domain(struct mx_name const *n1, struct mx_name const *n2)
{
   char const *d1, *d2;
   boole rv;
   NYD_IN;

   d1 = su_cs_rfind_c(n1->n_name, '@');
   d2 = su_cs_rfind_c(n2->n_name, '@');

   rv = (d1 != NULL && d2 != NULL) ? !su_cs_cmp_case(++d1, ++d2) : FAL0;

   NYD_OU;
   return rv;
}

struct mx_name *
checkaddrs(struct mx_name *np, enum expand_addr_check_mode eacm,
   s8 *set_on_error)
{
   struct mx_name *n;
   NYD_IN;

   for (n = np; n != NULL; n = n->n_flink) {
      s8 rv;

      if ((rv = is_addr_invalid(n, eacm)) != 0) {
         if (set_on_error != NULL)
            *set_on_error |= rv; /* don't loose -1! */
         if (eacm & EAF_MAYKEEP) /* TODO HACK!  See definition! */
            continue;
         if (n->n_blink)
            n->n_blink->n_flink = n->n_flink;
         if (n->n_flink)
            n->n_flink->n_blink = n->n_blink;
         if (n == np)
            np = n->n_flink;
      }
   }
   NYD_OU;
   return np;
}

struct mx_name *
n_namelist_vaporise_head(struct header *hp, boole metoo,
   boole strip_alternates, enum expand_addr_check_mode eacm, s8 *set_on_error)
{
   /* TODO namelist_vaporise_head() is incredibly expensive and redundant */
   struct mx_name *tolist, *np, **npp;
   NYD_IN;

   tolist = cat(hp->h_to, cat(hp->h_cc, cat(hp->h_bcc, hp->h_fcc)));
   hp->h_to = hp->h_cc = hp->h_bcc = hp->h_fcc = NULL;

   tolist = usermap(tolist, metoo);

   /* MTA aliases are resolved last */
#ifdef mx_HAVE_MTA_ALIASES
   switch(mx_mta_aliases_expand(&tolist)){
   case su_ERR_DESTADDRREQ:
   case su_ERR_NONE:
   case su_ERR_NOENT:
      break;
   default:
      *set_on_error |= TRU1;
      break;
   }
#endif

   if(strip_alternates)
      tolist = mx_alternates_remove(tolist, TRU1);

   tolist = elide(tolist);

   tolist = checkaddrs(tolist, eacm, set_on_error);

   for (np = tolist; np != NULL; np = np->n_flink) {
      switch (np->n_type & (GDEL | GMASK)) {
      case GTO:   npp = &hp->h_to; break;
      case GCC:   npp = &hp->h_cc; break;
      case GBCC:  npp = &hp->h_bcc; break;
      default:    continue;
      }
      *npp = cat(*npp, ndup(np, np->n_type | GFULL));
   }

   NYD_OU;
   return tolist;
}

struct mx_name *
usermap(struct mx_name *names, boole force_metoo){
   struct su_cs_dict_view dv;
   struct mx_name *nlist, **nlist_tail, *np, *nxtnp;
   int metoo;
   char const *logname;
   NYD_IN;

   logname = ok_vlook(LOGNAME);
   metoo = (force_metoo || ok_blook(metoo));
   nlist = NIL;
   nlist_tail = &nlist;
   np = names;

   if(a_nm_alias_dp != NIL)
      su_cs_dict_view_setup(&dv, a_nm_alias_dp);

   for(; np != NULL; np = nxtnp){
      ASSERT(!(np->n_type & GDEL)); /* TODO legacy */
      nxtnp = np->n_flink;

      /* Only valid alias names may enter expansion; even so GFULL may cause
       * .n_fullname to be different memory, it will be bitwise equal) */
      if(is_fileorpipe_addr(np) || (np->n_name != np->n_fullname &&
               su_cs_cmp(np->n_name, np->n_fullname)) ||
            a_nm_alias_dp == NIL || !su_cs_dict_view_find(&dv, np->n_name)){
         np->n_blink = *nlist_tail;
         np->n_flink = NIL;
         *nlist_tail = np;
         nlist_tail = &np->n_flink;
      }else{
         nlist = a_nm_alias_expand(0, nlist, np->n_name, np->n_type, metoo,
               logname);
         if((np = nlist) == NIL)
            nlist_tail = &nlist;
         else for(;; np = np->n_flink)
            if(np->n_flink == NIL){
               nlist_tail = &np->n_flink;
               break;
            }
      }
   }

   NYD_OU;
   return nlist;
}

struct mx_name *
elide(struct mx_name *names)
{
   uz i, j, k;
   struct mx_name *nlist, *np, **nparr;
   NYD_IN;

   nlist = NULL;

   if(names == NULL)
      goto jleave;

   /* Throw away all deleted nodes */
   for(np = NULL, i = 0; names != NULL; names = names->n_flink)
      if(!(names->n_type & GDEL)){
         names->n_blink = np;
         if(np != NULL)
            np->n_flink = names;
         else
            nlist = names;
         np = names;
         ++i;
      }
   if(nlist == NULL || i == 1)
      goto jleave;
   np->n_flink = NULL;

   /* Create a temporay array and sort that */
   nparr = n_lofi_alloc(sizeof(*nparr) * i);

   for(i = 0, np = nlist; np != NULL; np = np->n_flink)
      nparr[i++] = np;

   su_sort_shell_vpp(su_S(void const**,nparr), i, &a_nm_elide_sort);

   /* Remove duplicates XXX speedup, or list_uniq()! */
   for(j = 0, --i; j < i;){
      if(su_cs_cmp_case(nparr[j]->n_name, nparr[k = j + 1]->n_name))
         ++j;
      else{
         for(; k < i; ++k)
            nparr[k] = nparr[k + 1];
         --i;
      }
   }

   /* Throw away all list members which are not part of the array.
    * Note this keeps the original, possibly carefully crafted, order of the
    * addressees, thus.. */
   for(np = nlist; np != NULL; np = np->n_flink){
      for(j = 0; j <= i; ++j)
         /* The order of pointers depends on the sorting algorithm, and
          * therefore our desire to keep the original order of addessees cannot
          * be guaranteed when there are multiple equal names (ham zebra ham)
          * of equal weight: we need to compare names _once_again_ :( */
         if(nparr[j] != NULL && !su_cs_cmp_case(np->n_name, nparr[j]->n_name)){
            nparr[j] = NULL;
            goto jiter;
         }
      /* Drop it */
      if(np == nlist){
         nlist = np->n_flink;
         np->n_blink = NULL;
      }else
         np->n_blink->n_flink = np->n_flink;
      if(np->n_flink != NULL)
         np->n_flink->n_blink = np->n_blink;
jiter:;
   }

   n_lofi_free(nparr);
jleave:
   NYD_OU;
   return nlist;
}

int
c_alias(void *vp){
   struct su_cs_dict_view dv;
   union {void const *cvp; boole haskey; struct n_strlist *slp;} dat;
   int rv;
   char const **argv, *key;
   NYD_IN;

   if((key = *(argv = S(char const**,vp))) == NIL){
      dat.slp = NIL;
      rv = !(mx_xy_dump_dict("alias", a_nm_alias_dp, &dat.slp, NIL,
               &a_nm_alias_dump) &&
            mx_page_or_print_strlist("alias", dat.slp));
      goto jleave;
   }

   if(argv[1] != NIL && argv[2] == NIL && key[0] == '-' && key[1] == '\0')
      key = argv[1];

   if(!mx_alias_is_valid_name(key)){
      n_err(_("alias: not a valid name: %s\n"), n_shexp_quote_cp(key, FAL0));
      rv = 1;
      goto jleave;
   }

   if(a_nm_alias_dp != NIL && su_cs_dict_view_find(
            su_cs_dict_view_setup(&dv, a_nm_alias_dp), key))
      dat.cvp = su_cs_dict_view_data(&dv);
   else
      dat.cvp = NIL;

   if(argv[1] == NIL || key == argv[1]){
      if(dat.cvp != NIL){
         if(argv[1] == NIL){
            dat.slp = a_nm_alias_dump("alias", key, dat.cvp);
            rv = (fputs(dat.slp->sl_dat, n_stdout) == EOF);
            rv |= (putc('\n', n_stdout) == EOF);
         }else{
            struct mx_name *np;

            np = a_nm_alias_expand(0, NIL, key, 0, TRU1, ok_vlook(LOGNAME));
            np = elide(np);
            rv = (fprintf(n_stdout, "alias %s", key) < 0);
            if(!rv){
               for(; np != NIL; np = np->n_flink){
                  rv |= (putc(' ', n_stdout) == EOF);
                  rv |= (fputs(n_shexp_quote_cp(np->n_fullname, TRU1),
                        n_stdout) == EOF);
               }
               rv |= (putc('\n', n_stdout) == EOF);
            }
         }
      }else{
         n_err(_("No such alias: %s\n"), n_shexp_quote_cp(key, FAL0));
         rv = 1;
      }
   }else{
      struct n_strlist *head, **tailp;
      boole exists;
      char const *val1, *val2;

      if(a_nm_alias_dp == NIL)
         a_nm_alias_dp = su_cs_dict_set_treshold_shift(
               su_cs_dict_create(&a_nm_alias__d, a_NM_ALIAS_FLAGS, NIL),
               a_NM_ALIAS_TRESHOLD_SHIFT);

      if((exists = (head = dat.slp) != NIL)){
         while(dat.slp->sl_next != NIL)
            dat.slp = dat.slp->sl_next;
         tailp = &dat.slp->sl_next;
      }else
         head = NIL, tailp = &head;

      while((val1 = *++argv) != NIL){
         uz l1, l2;
         struct mx_name *np;
         boole norecur, name_eq_fullname;

         if((norecur = (*val1 == '\\')))
            ++val1;

         /* We need to allow empty targets */
         name_eq_fullname = TRU1;
         if(*val1 == '\0')
            val2 = val1;
         else if((np = n_extract_single(val1, GFULL)) != NIL){
            val1 = np->n_name;
            val2 = np->n_fullname;
            if((name_eq_fullname = !su_cs_cmp(val1, val2)))
               val2 = su_empty;
         }else{
            n_err(_("alias: %s: invalid argument: %s\n"),
               key, n_shexp_quote_cp(val1, FAL0));
            /*rv = 1;*/
            continue;
         }

         l1 = su_cs_len(val1) +1;
         l2 = su_cs_len(val2) +1;
         dat.slp = n_STRLIST_ALLOC(l1 + 1 + l2);
         *tailp = dat.slp;
         dat.slp->sl_next = NIL;
         tailp = &dat.slp->sl_next;
         dat.slp->sl_len = l1 -1;
         su_mem_copy(dat.slp->sl_dat, val1, l1);
         dat.slp->sl_dat[l1++] = (!norecur && name_eq_fullname &&
               mx_alias_is_valid_name(val1));
         su_mem_copy(&dat.slp->sl_dat[l1], val2, l2);
      }

      if(exists){
         su_cs_dict_view_set_data(&dv, head);
         rv = !TRU1;
      }else
         rv = !(su_cs_dict_insert(a_nm_alias_dp, key, head) == 0);
   }

jleave:
   NYD_OU;
   return rv;
}

int
c_unalias(void *vp){ /* XXX how about toolbox and generic unxy_dict()? */
   struct su_cs_dict_view dv;
   struct n_strlist *slp;
   char const **argv, *key;
   int rv;
   NYD_IN;

   rv = 0;
   key = (argv = vp)[0];

   if(a_nm_alias_dp != NIL)
      su_cs_dict_view_setup(&dv, a_nm_alias_dp);

   do{
      if(key[1] == '\0' && key[0] == '*'){
         if(a_nm_alias_dp != NIL){
            su_CS_DICT_VIEW_FOREACH(&dv){
               slp = S(struct n_strlist*,su_cs_dict_view_data(&dv));
               do{
                  vp = slp;
                  slp = slp->sl_next;
                  n_free(vp);
               }while(slp != NIL);
            }
            su_cs_dict_clear(a_nm_alias_dp);
         }
      }else if(!su_cs_dict_view_find(&dv, key)){
         n_err(_("No such `alias': %s\n"), n_shexp_quote_cp(key, FAL0));
         rv = 1;
      }else{
         slp = S(struct n_strlist*,su_cs_dict_view_data(&dv));
         do{
            vp = slp;
            slp = slp->sl_next;
            n_free(vp);
         }while(slp != NIL);
         su_cs_dict_view_remove(&dv);
      }
   }while((key = *++argv) != NIL);

   NYD_OU;
   return rv;
}

boole
mx_alias_is_valid_name(char const *name){
   char c;
   char const *cp;
   boole rv;
   NYD2_IN;

   for(rv = TRU1, cp = name++; (c = *cp++) != '\0';)
      /* User names, plus things explicitly mentioned in Postfix aliases(5).
       * As extensions allow high-bit bytes, exclamation mark and period:
       *    [[:alnum:]_#:@!.-]+ */
      /* TODO alias_is_valid_name(): locale dependent validity check,
       * TODO with Unicode prefix valid UTF-8!
       * TODO no support for trailing $ yet, as says Linux usernames! */
      if(!su_cs_is_alnum(c) && c != '_' && c != '-' &&
            c != '#' && c != ':' && c != '@' &&
            /* Extensions */
            !(S(u8,c) & 0x80) && c != '!' && c != '.'){
         rv = FAL0;
         break;
      }
   NYD2_OU;
   return rv;
}

int
c_alternates(void *vp){
   struct n_string s_b, *s;
   struct n_strlist *slp;
   int rv;
   char const **argv, *varname, *key;
   NYD_IN;

   n_pstate_err_no = su_ERR_NONE;

   argv = S(char const**,vp);
   varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NIL;

   if((key = *argv) == NIL){
      slp = NIL;
      rv = !mx_xy_dump_dict("alternates", a_nm_a8s_dp, &slp, NIL,
               &a_nm_a8s_dump);
      if(!rv){
         s = n_string_creat_auto(&s_b);
         s = n_string_book(s, 500); /* xxx */

         for(; slp != NIL; slp = slp->sl_next){
            if(s->s_len > 0)
               s = n_string_push_c(s, ' ');
            s = n_string_push_buf(s, slp->sl_dat, slp->sl_len);
         }
         key = n_string_cp(s);

         if(varname != NIL){
            if(!n_var_vset(varname, S(up,key))){
               n_pstate_err_no = su_ERR_NOTSUP;
               rv = 1;
            }
         }else if(*key != '\0')
            rv = !(fprintf(n_stdout, "alternates %s\n", key) >= 0);
         else
            rv = !(fprintf(n_stdout, _("# no alternates registered\n")) >= 0);
      }
   }else{
      if(varname != NULL)
         n_err(_("`alternates': `vput' only supported for show mode\n"));

      if(a_nm_a8s_dp == NIL)
         a_nm_a8s_dp = su_cs_dict_set_treshold_shift(
               su_cs_dict_create(&a_nm_a8s__d, a_NM_A8S_FLAGS, NIL),
               a_NM_A8S_TRESHOLD_SHIFT);
      /* In POSIX mode this command declares a, not appends to a list */
      else if(ok_blook(posix))
         su_cs_dict_clear_elems(a_nm_a8s_dp);

      for(rv = 0; (key = *argv++) != NIL;){
         struct mx_name *np;

         if((np = n_extract_single(key, 0)) == NIL ||
               (np = checkaddrs(np, EACM_STRICT, NIL)) == NIL){
            n_err(_("Invalid `alternates' argument: %s\n"),
               n_shexp_quote_cp(key, FAL0));
            n_pstate_err_no = su_ERR_INVAL;
            rv = 1;
            continue;
         }
         key = np->n_name;

         if(su_cs_dict_replace(a_nm_a8s_dp, key, NIL) > 0){
            n_err(_("Failed to create `alternates' storage: %s\n"),
               n_shexp_quote_cp(key, FAL0));
            n_pstate_err_no = su_ERR_INVAL;
            rv = 1;
         }
      }
   }

   NYD_OU;
   return rv;
}

int
c_unalternates(void *vp){
   int rv;
   NYD_IN;

   rv = !mx_unxy_dict("alternates", a_nm_a8s_dp, vp);
   NYD_OU;
   return rv;
}

struct mx_name *
mx_alternates_remove(struct mx_name *np, boole keep_single){
   /* XXX keep a single pointer, initial null, and immediate remove nodes
    * XXX on successful match unless keep single and that pointer null! */
   struct su_cs_dict_view dv;
   struct mx_name *xp, *newnp;
   NYD_IN;

   /* Delete the temporary bit from all */
   for(xp = np; xp != NULL; xp = xp->n_flink)
      xp->n_flags &= ~S(u32,S32_MIN);

   /* Mark all possible alternate names (xxx sic: instead walk over namelist
    * and hash-lookup alternate instead (unless *allnet*) */
   if(a_nm_a8s_dp != NIL)
      su_CS_DICT_FOREACH(a_nm_a8s_dp, &dv)
         np = a_nm_namelist_mark_name(np, su_cs_dict_view_key(&dv));

   np = a_nm_namelist_mark_name(np, ok_vlook(LOGNAME));

   if((xp = n_extract_single(ok_vlook(sender), GEXTRA)) != NIL)
      np = a_nm_namelist_mark_name(np, xp->n_name);
   else for(xp = lextract(ok_vlook(from), GEXTRA | GSKIN); xp != NULL;
         xp = xp->n_flink)
      np = a_nm_namelist_mark_name(np, xp->n_name);

   /* C99 */{
      char const *v15compat;

      if((v15compat = ok_vlook(replyto)) != NULL){
         n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
         for(xp = lextract(v15compat, GEXTRA | GSKIN); xp != NULL;
               xp = xp->n_flink)
            np = a_nm_namelist_mark_name(np, xp->n_name);
      }
   }

   for(xp = lextract(ok_vlook(reply_to), GEXTRA | GSKIN); xp != NULL;
         xp = xp->n_flink)
      np = a_nm_namelist_mark_name(np, xp->n_name);

   /* Clean the list by throwing away all deleted or marked (but one) nodes */
   for(xp = newnp = NULL; np != NULL; np = np->n_flink){
      if(np->n_type & GDEL)
         continue;
      if(np->n_flags & S(u32,S32_MIN)){
         if(!keep_single)
            continue;
         keep_single = FAL0;
      }

      np->n_blink = xp;
      if(xp != NULL)
         xp->n_flink = np;
      else
         newnp = np;
      xp = np;
      xp->n_flags &= ~S(u32,S32_MIN);
   }
   if(xp != NULL)
      xp->n_flink = NULL;
   np = newnp;

   NYD_OU;
   return np;
}

boole
mx_name_is_mine(char const *name){
   struct su_cs_dict_view dv;
   struct mx_name *xp;
   NYD_IN;

   if(a_nm_is_same_name(ok_vlook(LOGNAME), name))
      goto jleave;

   if(a_nm_a8s_dp != NIL){
      if(!ok_blook(allnet)){
         if(su_cs_dict_has_key(a_nm_a8s_dp, name))
            goto jleave;
      }else su_CS_DICT_FOREACH(a_nm_a8s_dp, &dv)
         if(a_nm_is_same_name(name, su_cs_dict_view_key(&dv)))
            goto jleave;
   }

   for(xp = lextract(ok_vlook(from), GEXTRA | GSKIN); xp != NULL;
         xp = xp->n_flink)
      if(a_nm_is_same_name(xp->n_name, name))
         goto jleave;

   /* C99 */{
      char const *v15compat;

      if((v15compat = ok_vlook(replyto)) != NULL){
         n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
         for(xp = lextract(v15compat, GEXTRA | GSKIN); xp != NULL;
               xp = xp->n_flink)
            if(a_nm_is_same_name(xp->n_name, name))
               goto jleave;
      }
   }

   for(xp = lextract(ok_vlook(reply_to), GEXTRA | GSKIN); xp != NULL;
         xp = xp->n_flink)
      if(a_nm_is_same_name(xp->n_name, name))
         goto jleave;

   if((xp = n_extract_single(ok_vlook(sender), GEXTRA)) != NIL &&
         a_nm_is_same_name(xp->n_name, name))
      goto jleave;

   name = NIL;
jleave:
   NYD_OU;
   return (name != NIL);
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/mx/net-gssapi.h000066400000000000000000000233771352610246600165350ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ Implementation of GSS-API authentication.
 *@ According to RFC 4954 (SMTP), RFC 5034 (POP3), RFC 4422/4959 (IMAP).
 *
 * Copyright (c) 2014 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: BSD-4-Clause
 */
/*
 * Partially derived from sample code in:
 *
 * GSS-API Programming Guide
 * Part No: 806-3814-10
 * Sun Microsystems, Inc. 901 San Antonio Road, Palo Alto, CA 94303-4900 U.S.A.
 * (c) 2000 Sun Microsystems
 */
/*
 * Copyright 1994 by OpenVision Technologies, Inc.
 * 
 * Permission to use, copy, modify, distribute, and sell this software
 * and its documentation for any purpose is hereby granted without fee,
 * provided that the above copyright notice appears in all copies and
 * that both that copyright notice and this permission notice appear in
 * supporting documentation, and that the name of OpenVision not be used
 * in advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission. OpenVision makes no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied warranty.
 * 
 * OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, 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.
 */
#ifdef mx_HAVE_GSSAPI
#ifndef a_NET_GSSAPI_H
# define a_NET_GSSAPI_H 1

#ifndef GSSAPI_REG_INCLUDE
# include 
# ifdef GSSAPI_OLD_STYLE
#  include 
#  define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name
#  define m_DEFINED_GCC_C_NT_HOSTBASED_SERVICE
# endif
#else
# include 
#endif

#elif a_NET_GSSAPI_H == 1
# undef a_NET_GSSAPI_H
# define a_NET_GSSAPI_H 2

/* */
static boole su_CONCAT(su_FILE,_gss)(struct mx_socket *sp, struct mx_url *urlp,
      struct mx_cred_ctx *credp,
# ifdef mx_SOURCE_NET_SMTP
      struct a_netsmtp_line *slp
# elif defined mx_SOURCE_NET_POP3 || defined mx_SOURCE_NET_IMAP
      struct mailbox *mp
# endif
      );

/* */
static void su_CONCAT(su_FILE,_gss__error)(char const *s, OM_uint32 maj_stat,
      OM_uint32 min_stat);
static void su_CONCAT(su_FILE,_gss__error1)(char const *s, OM_uint32 code,
      int typ);

#elif a_NET_GSSAPI_H == 2

static boole
su_CONCAT(su_FILE,_gss)(struct mx_socket *sop, struct mx_url *urlp,
      struct mx_cred_ctx *credp,
# ifdef mx_SOURCE_NET_SMTP
      struct a_netsmtp_line *slp
# elif defined mx_SOURCE_NET_POP3 || defined mx_SOURCE_NET_IMAP
      struct mailbox *mp
# endif
      ){
   enum{
      a_F_NONE,
      a_F_TARGET_NAME = 1u<<0,
      a_F_GSS_CONTEXT = 1u<<1,
      a_F_SEND_TOK = 1u<<2,
      a_F_RECV_TOK = 1u<<3,
      a_F_SETUP = 1u<<4,
      a_F_OUTBUF = 1u<<5
   };

# if defined mx_SOURCE_NET_POP3
   int poprv;
# elif defined mx_SOURCE_NET_IMAP
   FILE *queuefp = NIL;
# endif
   struct str in, out;
   gss_buffer_desc send_tok, recv_tok;
   gss_ctx_id_t gss_context;
   int conf_state;
   gss_name_t target_name;
   OM_uint32 maj_stat, min_stat, ret_flags;
   char *buf;
   u32 f;
   boole ok;
   NYD_IN;
   UNUSED(sop);

   ok = FAL0;
   f = a_F_NONE;
   buf = NIL;

   if(INT_MAX - 1 - 5 <= urlp->url_host.l ||
         INT_MAX - 1 - 4 <= credp->cc_user.l){
      n_err(_("Credentials overflow buffer sizes\n"));
      goto jleave;
   }

   send_tok.value = buf = n_lofi_alloc(
         (send_tok.length = urlp->url_host.l + 5) +1);
   /* C99 */{
      uz i;

# ifdef mx_SOURCE_NET_SMTP
      su_mem_copy(send_tok.value, "smtp@", i = 5);
# elif defined mx_SOURCE_NET_POP3
      su_mem_copy(send_tok.value, "pop@", i = 4);
# elif defined mx_SOURCE_NET_IMAP
      su_mem_copy(send_tok.value, "imap@", i = 5);
# endif
      su_mem_copy(&S(char*,send_tok.value)[i], urlp->url_host.s,
         urlp->url_host.l +1);
   }

   maj_stat = gss_import_name(&min_stat, &send_tok,
         GSS_C_NT_HOSTBASED_SERVICE, &target_name);
   f |= a_F_TARGET_NAME;
   if(maj_stat != GSS_S_COMPLETE){
      su_CONCAT(su_FILE,_gss__error)(savestrbuf(send_tok.value,
         send_tok.length), maj_stat, min_stat);
      goto jleave;
   }

   /* */
# ifdef mx_SOURCE_NET_IMAP
   if(!(mp->mb_flags & MB_SASL_IR)){
      IMAP_OUT(savecat(tag(1), NETLINE(" AUTHENTICATE GSSAPI")),
         0, goto jleave);
      imap_answer(mp, 1);
      if(response_type != RESPONSE_CONT)
         goto jleave;
   }
# endif

   gss_context = GSS_C_NO_CONTEXT;
   for(;;){
      maj_stat = gss_init_sec_context(&min_stat,
            GSS_C_NO_CREDENTIAL,
            &gss_context,
            target_name,
            GSS_C_NO_OID,
            GSS_C_MUTUAL_FLAG | GSS_C_SEQUENCE_FLAG,
            0,
            GSS_C_NO_CHANNEL_BINDINGS,
            ((f & a_F_SETUP) ? &recv_tok : GSS_C_NO_BUFFER),
            NIL,
            &send_tok,
            &ret_flags,
            NIL);
      f |= a_F_GSS_CONTEXT | a_F_SEND_TOK;
      if(maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED){
         su_CONCAT(su_FILE,_gss__error)("gss_init_sec_context",
            maj_stat, min_stat);
         goto jleave;
      }
      if(f & a_F_OUTBUF){
         f ^= a_F_OUTBUF;
         n_free(out.s);
      }

      if(b64_encode_buf(&out, send_tok.value, send_tok.length,
            B64_SALLOC | B64_CRLF) == NIL)
         goto jleave;
      gss_release_buffer(&min_stat, &send_tok);
      f &= ~a_F_SEND_TOK;
      if(!(f & a_F_SETUP)){
         f |= a_F_SETUP;
# if defined mx_SOURCE_NET_SMTP || defined mx_SOURCE_NET_POP3
         out.s = savecat("AUTH GSSAPI ", out.s);
# elif defined mx_SOURCE_NET_IMAP
         if(mp->mb_flags & MB_SASL_IR)
            out.s = savecat(tag(1), savecat(" AUTHENTICATE GSSAPI ", out.s));
# endif
      }

# ifdef mx_SOURCE_NET_SMTP
      a_SMTP_OUT(out.s);
      a_SMTP_ANSWER(3, FAL0, TRU1);
      in = slp->sl_dat;
# elif defined mx_SOURCE_NET_POP3
      a_POP3_OUT(poprv, out.s, MB_COMD, goto jleave);
      a_POP3_ANSWER(poprv, goto jleave);
      in.l = su_cs_len(in.s = a_pop3_realdat);
# elif defined mx_SOURCE_NET_IMAP
      IMAP_OUT(out.s, 0, goto jleave);
      imap_answer(mp, 1);
      if(response_type != RESPONSE_CONT)
         goto jleave;
      in.l = su_cs_len(in.s = responded_text);
# endif

      out.s = NIL;
      f |= a_F_OUTBUF;
      if(!b64_decode(&out, &in)){
         n_err(_("Invalid base64 encoding from GSSAPI server\n"));
         goto jleave;
      }
      recv_tok.value = out.s;
      recv_tok.length = out.l;

      if(maj_stat != GSS_S_CONTINUE_NEEDED)
         break;
   }

   maj_stat = gss_unwrap(&min_stat, gss_context, &recv_tok, &send_tok,
         &conf_state, NIL);
   f |= a_F_SEND_TOK;
   if(maj_stat != GSS_S_COMPLETE){
      su_CONCAT(su_FILE,_gss__error)("unwrapping data", maj_stat, min_stat);
      goto jleave;
   }

   gss_release_buffer(&min_stat, &send_tok);
   n_free(out.s);
   f &= ~(a_F_OUTBUF | a_F_SEND_TOK);

   /* First octet: bit-mask with protection mechanisms (1 = no protection
    *    mechanism).
    * Second to fourth octet: maximum message size in network byte order.
    * Fifth and following octets: user name string */
   n_lofi_free(buf);
   in.s = buf = n_lofi_alloc((send_tok.length = 4u + credp->cc_user.l) +1);
   su_mem_copy(&in.s[4], credp->cc_user.s, credp->cc_user.l +1);
   in.s[0] = 1;
   in.s[1] = 0;
   in.s[2] = in.s[3] = S(char,0xFF);
   send_tok.value = in.s;
   maj_stat = gss_wrap(&min_stat, gss_context, 0, GSS_C_QOP_DEFAULT, &send_tok,
         &conf_state, &recv_tok);
   f |= a_F_RECV_TOK;
   if(maj_stat != GSS_S_COMPLETE){
      su_CONCAT(su_FILE,_gss__error)("wrapping data", maj_stat, min_stat);
      goto jleave;
   }
   if(b64_encode_buf(&out, recv_tok.value, recv_tok.length,
         B64_SALLOC | B64_CRLF) == NIL)
      goto jleave;

# ifdef mx_SOURCE_NET_SMTP
   a_SMTP_OUT(out.s);
   a_SMTP_ANSWER(2, FAL0, FAL0);
# elif defined mx_SOURCE_NET_POP3
   a_POP3_OUT(poprv, out.s, MB_COMD, goto jleave);
   a_POP3_ANSWER(poprv, goto jleave);
# elif defined mx_SOURCE_NET_IMAP
   IMAP_OUT(out.s, 0, goto jleave);
   while(mp->mb_active & MB_COMD)
      ok = imap_answer(mp, 1);
# endif

   ok = TRU1;
jleave:
   if(f & a_F_RECV_TOK)
      gss_release_buffer(&min_stat, &recv_tok);
   if(f & a_F_SEND_TOK)
      gss_release_buffer(&min_stat, &send_tok);
   if(f & a_F_TARGET_NAME)
      gss_release_name(&min_stat, &target_name);
   if(f & a_F_GSS_CONTEXT)
      gss_delete_sec_context(&min_stat, &gss_context, GSS_C_NO_BUFFER);
   if((f & a_F_OUTBUF) && out.s != NIL)
      n_free(out.s);
   if(buf != NIL)
      n_lofi_free(buf);
   NYD_OU;
   return ok;
}

static void
su_CONCAT(su_FILE,_gss__error)(char const *s, OM_uint32 maj_stat,
      OM_uint32 min_stat){
   NYD2_IN;
   su_CONCAT(su_FILE,_gss__error1)(s, maj_stat, GSS_C_GSS_CODE);
   su_CONCAT(su_FILE,_gss__error1)(s, min_stat, GSS_C_MECH_CODE);
   NYD2_OU;
}

static void
su_CONCAT(su_FILE,_gss__error1)(char const *s, OM_uint32 code, int typ){
   gss_buffer_desc msg = GSS_C_EMPTY_BUFFER;
   OM_uint32 maj_stat, min_stat;
   OM_uint32 msg_ctx;
   NYD2_IN;

   msg_ctx = 0;
   do{
      maj_stat = gss_display_status(&min_stat, code, typ, GSS_C_NO_OID,
            &msg_ctx, &msg);
      if(maj_stat == GSS_S_COMPLETE){
         n_err(_("GSSAPI error: %s / %.*s\n"),
            s, S(int,msg.length), S(char*,msg.value));
         gss_release_buffer(&min_stat, &msg);
      }else{
         n_err(_("GSSAPI error: %s / unknown\n"), s);
         break;
      }
   }while(msg_ctx);

   NYD2_OU;
}

# ifdef m_DEFINED_GCC_C_NT_HOSTBASED_SERVICE
#  undef GSS_C_NT_HOSTBASED_SERVICE
# endif

#else
# error a_NET_GSSAPI_H included thrice already
#endif
#endif /* mx_HAVE_GSSAPI */
/* s-it-mode */
s-nail-14.9.15/src/mx/net-pop3.c000066400000000000000000000757511352610246600161260ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ Implementation of net-pop3.h.
 *@ TODO UIDL (as struct message.m_uid, *headline* %U), etc...
 *@ TODO enum okay -> boole
 *
 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: BSD-4-Clause
 */
/*
 * Copyright (c) 2002
 * Gunnar Ritter.  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 Gunnar Ritter
 *    and his contributors.
 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER 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 GUNNAR RITTER 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.
 */
#undef su_FILE
#define su_FILE net_pop3
#define mx_SOURCE
#define mx_SOURCE_NET_POP3

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

su_EMPTY_FILE()
#ifdef mx_HAVE_POP3
#include 
#include 
#include 

#include "mx/cred-auth.h"
#include "mx/cred-md5.h"
#include "mx/file-streams.h"
#include "mx/net-socket.h"
#include "mx/sigs.h"

#ifdef mx_HAVE_GSSAPI
# include "mx/net-gssapi.h" /* $(MX_SRCDIR) */
#endif

#include "mx/net-pop3.h"
/* TODO fake */
#include "su/code-in.h"

struct a_pop3_ctx{
   struct mx_socket *pc_sockp;
   struct mx_cred_ctx pc_cred;
   struct mx_url pc_url;
};

static struct str a_pop3_dat;
static char const *a_pop3_realdat;
static sigjmp_buf a_pop3_jmp;
static n_sighdl_t a_pop3_savealrm;
static s32 a_pop3_keepalive;
static int volatile a_pop3_lock;

/* Perform entire login handshake */
static enum okay a_pop3_login(struct mailbox *mp, struct a_pop3_ctx *pcp);

#ifdef mx_HAVE_MD5
/* APOP: get greeting credential or NIL... */
static char *a_pop3_lookup_apop_timestamp(char const *bp);

/* ...and authenticate */
static enum okay a_pop3_auth_apop(struct mailbox *mp,
      struct a_pop3_ctx const *pcp, char const *ts);
#endif

/* Several (other) authentication methods */
static enum okay a_pop3_auth_plain(struct mailbox *mp,
      struct a_pop3_ctx const *pcp);
static enum okay a_pop3_auth_oauthbearer(struct mailbox *mp,
      struct a_pop3_ctx const *pcp);
static enum okay a_pop3_auth_external(struct mailbox *mp,
      struct a_pop3_ctx const *pcp);

static void a_pop3_timer_off(void);
static enum okay a_pop3_answer(struct mailbox *mp);
static enum okay a_pop3_finish(struct mailbox *mp);
static void a_pop3_catch(int s);
static void a_pop3_maincatch(int s);
static enum okay a_pop3_noop1(struct mailbox *mp);
static void a_pop3alarm(int s);
static enum okay a_pop3_stat(struct mailbox *mp, off_t *size, int *cnt);
static enum okay a_pop3_list(struct mailbox *mp, int n, uz *size);
static void a_pop3_setptr(struct mailbox *mp, struct a_pop3_ctx const *pcp);
static enum okay a_pop3_get(struct mailbox *mp, struct message *m,
      enum needspec need);
static enum okay a_pop3_exit(struct mailbox *mp);
static enum okay a_pop3_delete(struct mailbox *mp, int n);
static enum okay a_pop3_update(struct mailbox *mp);

#ifdef mx_HAVE_GSSAPI
# include 
#endif

/* Indirect POP3 I/O */
#define a_POP3_OUT(RV,X,Y,ACTIONSTOP) \
do{\
   if(((RV) = a_pop3_finish(mp)) == STOP){\
      ACTIONSTOP;\
   }\
   if(n_poption & n_PO_D_VV)\
      n_err(">>> %s", X);\
   mp->mb_active |= Y;\
   if(((RV) = mx_socket_write(mp->mb_sock, X)) == STOP){\
      ACTIONSTOP;\
   }\
}while(0)

#define a_POP3_ANSWER(RV,ACTIONSTOP) \
do if(((RV) = a_pop3_answer(mp)) == STOP){\
   ACTIONSTOP;\
}while(0)

static enum okay
a_pop3_login(struct mailbox *mp, struct a_pop3_ctx *pcp){
#ifdef mx_HAVE_MD5
   char *ts;
#endif
   enum okey_xlook_mode oxm;
   enum okay rv;
   NYD_IN;

   oxm = (ok_vlook(v15_compat) != NIL) ? OXM_ALL : OXM_PLAIN | OXM_U_H_P;

   /* Get the greeting, check whether APOP is advertised */
   a_POP3_ANSWER(rv, goto jleave);
#ifdef mx_HAVE_MD5
   ts = (pcp->pc_cred.cc_authtype == mx_CRED_AUTHTYPE_PLAIN)
         ? a_pop3_lookup_apop_timestamp(a_pop3_realdat) : NIL;
#endif

   /* If not yet secured, can we upgrade to TLS? */
#ifdef mx_HAVE_TLS
   if(!(pcp->pc_url.url_flags & mx_URL_TLS_REQUIRED)){
      if(xok_blook(pop3_use_starttls, &pcp->pc_url, oxm)){
         a_POP3_OUT(rv, "STLS" NETNL, MB_COMD, goto jleave);
         a_POP3_ANSWER(rv, goto jleave);
         if(!n_tls_open(&pcp->pc_url, pcp->pc_sockp)){
            rv = STOP;
            goto jleave;
         }
      }else if(pcp->pc_cred.cc_needs_tls){
         n_err(_("POP3 authentication %s needs TLS "
            "(*pop3-use-starttls* set?)\n"),
            pcp->pc_cred.cc_auth);
         rv = STOP;
         goto jleave;
      }
   }
#else
   if(pcp->pc_cred.cc_needs_tls ||
         xok_blook(pop3_use_starttls, &pcp->pc_url, oxm)){
      n_err(_("No TLS support compiled in\n"));
      rv = STOP;
      goto jleave;
   }
#endif

   /* Use the APOP single roundtrip? */
#ifdef mx_HAVE_MD5
   if(ts != NIL && !xok_blook(pop3_no_apop, &pcp->pc_url, oxm)){
      if((rv = a_pop3_auth_apop(mp, pcp, ts)) != OKAY){
         char const *ccp;

# ifdef mx_HAVE_TLS
         if(pcp->pc_sockp->s_use_tls)
            ccp = _("over a TLS encrypted connection");
         else
# endif
            ccp = _("(unfortunely without TLS!)");
         n_err(_("POP3 APOP authentication failed!\n"
            "  Server announced support - please set *pop3-no-apop*,\n"
            "  it enforces plain authentication %s\n"), ccp);
      }
      goto jleave;
   }
#endif

   switch(pcp->pc_cred.cc_authtype){
   case mx_CRED_AUTHTYPE_PLAIN:
      rv = a_pop3_auth_plain(mp, pcp);
      break;
   case mx_CRED_AUTHTYPE_OAUTHBEARER:
      rv = a_pop3_auth_oauthbearer(mp, pcp);
      break;
   case mx_CRED_AUTHTYPE_EXTERNAL:
   case mx_CRED_AUTHTYPE_EXTERNANON:
      rv = a_pop3_auth_external(mp, pcp);
      break;
#ifdef mx_HAVE_GSSAPI
   case mx_CRED_AUTHTYPE_GSSAPI:
      if(n_poption & n_PO_D)
         n_err(_(">>> We would perform GSS-API authentication now\n"));
      else if(!su_CONCAT(su_FILE,_gss)(mp->mb_sock, &pcp->pc_url,
            &pcp->pc_cred, mp))
         goto jleave;
      break;
#endif
   default:
      rv = FAL0;
      break;
   }

jleave:
   NYD_OU;
   return rv;
}

#ifdef mx_HAVE_MD5
static char *
a_pop3_lookup_apop_timestamp(char const *bp){
   /* RFC 1939:
    * A POP3 server which implements the APOP command will include
    * a timestamp in its banner greeting.  The syntax of the timestamp
    * corresponds to the "msg-id" in [RFC822]
    * RFC 822:
    * msg-id   = "<" addr-spec ">"
    * addr-spec   = local-part "@" domain */
   char const *cp, *ep;
   uz tl;
   char *rp;
   boole hadat;
   NYD_IN;

   hadat = FAL0;
   rp = NIL;

   if((cp = su_cs_find_c(bp, '<')) == NIL)
      goto jleave;

   /* xxx What about malformed APOP timestamp (<@>) here? */
   for(ep = cp; *ep != '\0'; ++ep){
      if(su_cs_is_space(*ep))
         goto jleave;
      else if(*ep == '@')
         hadat = TRU1;
      else if(*ep == '>'){
         if(!hadat)
            goto jleave;
         break;
      }
   }
   if(*ep != '>')
      goto jleave;

   tl = P2UZ(++ep - cp);
   rp = n_autorec_alloc(tl +1);
   su_mem_copy(rp, cp, tl);
   rp[tl] = '\0';

jleave:
   NYD_OU;
   return rp;
}

static enum okay
a_pop3_auth_apop(struct mailbox *mp, struct a_pop3_ctx const *pcp,
      char const *ts){
   unsigned char digest[mx_MD5_DIGEST_SIZE];
   char hex[mx_MD5_TOHEX_SIZE], *cp;
   mx_md5_t ctx;
   uz i;
   enum okay rv;
   NYD_IN;

   mx_md5_init(&ctx);
   mx_md5_update(&ctx, S(uc*,UNCONST(char*,ts)), su_cs_len(ts));
   mx_md5_update(&ctx, S(uc*,pcp->pc_cred.cc_pass.s), pcp->pc_cred.cc_pass.l);
   mx_md5_final(digest, &ctx);
   mx_md5_tohex(hex, digest);

   rv = STOP;

   i = pcp->pc_cred.cc_user.l;
   cp = n_lofi_alloc(5 + i + 1 + mx_MD5_TOHEX_SIZE + sizeof(NETNL)-1 +1);

   su_mem_copy(cp, "APOP ", 5);
   su_mem_copy(&cp[5], pcp->pc_cred.cc_user.s, i);
   i += 5;
   cp[i++] = ' ';
   su_mem_copy(&cp[i], hex, mx_MD5_TOHEX_SIZE);
   i += mx_MD5_TOHEX_SIZE;
   su_mem_copy(&cp[i], NETNL, sizeof(NETNL));
   a_POP3_OUT(rv, cp, MB_COMD, goto jleave);
   a_POP3_ANSWER(rv, goto jleave);

   rv = OKAY;
jleave:
   n_lofi_free(cp);
   NYD_OU;
   return rv;
}
#endif /* mx_HAVE_MD5 */

static enum okay
a_pop3_auth_plain(struct mailbox *mp, struct a_pop3_ctx const *pcp){
   char *cp;
   enum okay rv;
   NYD_IN;

   cp = n_lofi_alloc(MAX(pcp->pc_cred.cc_user.l, pcp->pc_cred.cc_pass.l) +
         5 + sizeof(NETNL)-1 +1);

   rv = STOP;

   su_mem_copy(cp, "USER ", 5);
   su_mem_copy(&cp[5], pcp->pc_cred.cc_user.s, pcp->pc_cred.cc_user.l);
   su_mem_copy(&cp[5 + pcp->pc_cred.cc_user.l], NETNL, sizeof(NETNL));
   a_POP3_OUT(rv, cp, MB_COMD, goto jleave);
   a_POP3_ANSWER(rv, goto jleave);

   su_mem_copy(cp, "PASS ", 5);
   su_mem_copy(&cp[5], pcp->pc_cred.cc_pass.s, pcp->pc_cred.cc_pass.l);
   su_mem_copy(&cp[5 + pcp->pc_cred.cc_pass.l], NETNL, sizeof(NETNL));
   a_POP3_OUT(rv, cp, MB_COMD, goto jleave);
   a_POP3_ANSWER(rv, goto jleave);

   rv = OKAY;
jleave:
   n_lofi_free(cp);
   NYD_OU;
   return rv;
}

static enum okay
a_pop3_auth_oauthbearer(struct mailbox *mp, struct a_pop3_ctx const *pcp){
   struct str b64;
   int i;
   uz cnt;
   char *cp;
   enum okay rv;
   NYD_IN;

   rv = STOP;
   cp = NIL;

   /* Calculate required storage */
   cnt = pcp->pc_cred.cc_user.l;
#define a_MAX \
   (2 + sizeof("AUTH XOAUTH2 " "user=\001auth=Bearer \001\001" NETNL))

   if(pcp->pc_cred.cc_pass.l >= UZ_MAX - a_MAX ||
         cnt >= UZ_MAX - a_MAX - pcp->pc_cred.cc_pass.l){
jerr_cred:
      n_err(_("Credentials overflow buffer sizes\n"));
      goto jleave;
   }
   cnt += pcp->pc_cred.cc_pass.l;

   cnt += a_MAX;
#undef a_MAX
   if((cnt = b64_encode_calc_size(cnt)) == UZ_MAX)
      goto jerr_cred;

   cp = n_lofi_alloc(cnt +1);

   /* Then create login query */
   i = snprintf(cp, cnt +1, "user=%s\001auth=Bearer %s\001\001",
      pcp->pc_cred.cc_user.s, pcp->pc_cred.cc_pass.s);
   if(b64_encode_buf(&b64, cp, i, B64_SALLOC) == NIL)
      goto jleave;

   cnt = sizeof("AUTH XOAUTH2 ") -1;
   su_mem_copy(cp, "AUTH XOAUTH2 ", cnt);
   su_mem_copy(&cp[cnt], b64.s, b64.l);
   su_mem_copy(&cp[cnt += b64.l], NETNL, sizeof(NETNL));

   a_POP3_OUT(rv, cp, MB_COMD, goto jleave);
   a_POP3_ANSWER(rv, goto jleave);

   rv = OKAY;
jleave:
   if(cp != NIL)
      n_lofi_free(cp);
   NYD_OU;
   return rv;
}

static enum okay
a_pop3_auth_external(struct mailbox *mp, struct a_pop3_ctx const *pcp){
   char *cp;
   uz cnt;
   enum okay rv;
   NYD_IN;

   rv = STOP;

   /* Calculate required storage */
   cnt = pcp->pc_cred.cc_user.l;
#define a_MAX \
   (sizeof("AUTH EXTERNAL ") -1 + sizeof(NETNL) -1 +1)

   if(cnt >= UZ_MAX - a_MAX){
      n_err(_("Credentials overflow buffer sizes\n"));
      goto j_leave;
   }
   cnt += a_MAX;

   cp = n_lofi_alloc(cnt);

   su_mem_copy(cp, NETLINE("AUTH EXTERNAL"),
      sizeof(NETLINE("AUTH EXTERNAL")));
   a_POP3_OUT(rv, cp, MB_COMD, goto jleave);
   a_POP3_ANSWER(rv, goto jleave);

   if(pcp->pc_cred.cc_authtype == mx_CRED_AUTHTYPE_EXTERNANON)
      cnt = 0;
   else
      su_mem_copy(&cp[0], pcp->pc_cred.cc_user.s,
         cnt = pcp->pc_cred.cc_user.l);
   su_mem_copy(&cp[cnt], NETNL, sizeof(NETNL));
   a_POP3_OUT(rv, cp, MB_COMD, goto jleave);
   a_POP3_ANSWER(rv, goto jleave);

   rv = OKAY;
jleave:
   n_lofi_free(cp);
j_leave:
   NYD_OU;
   return rv;
}

static void
a_pop3_timer_off(void){
   NYD_IN;
   if(a_pop3_keepalive > 0){
      alarm(0);
      safe_signal(SIGALRM, a_pop3_savealrm);
   }
   NYD_OU;
}

static enum okay
a_pop3_answer(struct mailbox *mp){
   int i;
   uz blen;
   enum okay rv;
   NYD_IN;

   rv = STOP;
jretry:
   if((i = mx_socket_getline(&a_pop3_dat.s, &a_pop3_dat.l, &blen, mp->mb_sock)
         ) > 0){
      if((mp->mb_active & (MB_COMD | MB_MULT)) == MB_MULT)
         goto jmultiline;

      if(n_poption & n_PO_D_VV)
         n_err(">>> SERVER: %s\n", a_pop3_dat.s);

      switch(*a_pop3_dat.s){
      case '+':
         while(blen > 0 &&
               (a_pop3_dat.s[blen - 1] == NETNL[0] ||
                a_pop3_dat.s[blen - 1] == NETNL[1]))
            a_pop3_dat.s[--blen] = '\0';

         if(blen == 0)
            a_pop3_realdat = su_empty;
         else{
            for(a_pop3_realdat = a_pop3_dat.s;
                  *a_pop3_realdat != '\0' && !su_cs_is_space(*a_pop3_realdat);
                  ++a_pop3_realdat)
               ;
            while(*a_pop3_realdat != '\0' && su_cs_is_space(*a_pop3_realdat))
               ++a_pop3_realdat;
         }
         rv = OKAY;
         mp->mb_active &= ~MB_COMD;
         break;
      case '-':
         rv = STOP;
         mp->mb_active = MB_NONE;
         while(blen > 0 &&
               (a_pop3_dat.s[blen - 1] == NETNL[0] ||
                a_pop3_dat.s[blen - 1] == NETNL[1]))
            a_pop3_dat.s[--blen] = '\0';
         n_err(_("POP3 error: %s\n"), a_pop3_dat.s);
         break;
      default:
         /* If the answer starts neither with '+' nor with '-', it must be part
          * of a multiline response.  Get lines until a single dot appears */
jmultiline:
         while(a_pop3_dat.s[0] != '.' || a_pop3_dat.s[1] != NETNL[0] ||
               a_pop3_dat.s[2] != NETNL[1] || a_pop3_dat.s[3] != '\0'){
            i = mx_socket_getline(&a_pop3_dat.s, &a_pop3_dat.l, NIL,
                  mp->mb_sock);
            if(i <= 0)
               goto jeof;
         }
         mp->mb_active &= ~MB_MULT;
         if(mp->mb_active != MB_NONE)
            goto jretry;
      }
   }else{
jeof:
      rv = STOP;
      mp->mb_active = MB_NONE;
   }

   NYD_OU;
   return rv;
}

static enum okay
a_pop3_finish(struct mailbox *mp){
   NYD_IN;
   while(mp->mb_sock->s_fd > 0 && mp->mb_active != MB_NONE)
      a_pop3_answer(mp);
   NYD_OU;
   return OKAY; /* XXX ? */
}

static void
a_pop3_catch(int s){
   switch(s){
   case SIGINT:
      /*n_err_sighdl(_("Interrupt during POP3 operation\n"));*/
      interrupts = 2; /* Force "Interrupt" message shall we onintr(0) */
      siglongjmp(a_pop3_jmp, 1);
   case SIGPIPE:
      n_err_sighdl(_("Received SIGPIPE during POP3 operation\n"));
      break;
   }
}

static void
a_pop3_maincatch(int s){
   UNUSED(s);
   if(interrupts == 0)
      n_err_sighdl(_("\n(Interrupt -- one more to abort operation)\n"));
   else{
      interrupts = 1;
      siglongjmp(a_pop3_jmp, 1);
   }
}

static enum okay
a_pop3_noop1(struct mailbox *mp){
   enum okay rv;
   NYD_IN;

   a_POP3_OUT(rv, "NOOP" NETNL, MB_COMD, goto jleave);
   a_POP3_ANSWER(rv, goto jleave);
jleave:
   NYD_OU;
   return rv;
}

static void
a_pop3alarm(int s){
   n_sighdl_t volatile saveint, savepipe;
   UNUSED(s);

   if(a_pop3_lock++ == 0){
      mx_sigs_all_holdx();
      if((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
         safe_signal(SIGINT, &a_pop3_maincatch);
      savepipe = safe_signal(SIGPIPE, SIG_IGN);
      if(sigsetjmp(a_pop3_jmp, 1)){
         interrupts = 0;
         safe_signal(SIGINT, saveint);
         safe_signal(SIGPIPE, savepipe);
         goto jbrk;
      }
      if(savepipe != SIG_IGN)
         safe_signal(SIGPIPE, &a_pop3_catch);
      mx_sigs_all_rele();

      if(a_pop3_noop1(&mb) != OKAY){
         safe_signal(SIGINT, saveint);
         safe_signal(SIGPIPE, savepipe);
         goto jleave;
      }
      safe_signal(SIGINT, saveint);
      safe_signal(SIGPIPE, savepipe);
   }
jbrk:
   alarm(a_pop3_keepalive);
jleave:
   --a_pop3_lock;
}

static enum okay
a_pop3_stat(struct mailbox *mp, off_t *size, int *cnt){
   char const *cp;
   enum okay rv;
   NYD_IN;

   a_POP3_OUT(rv, "STAT" NETNL, MB_COMD, goto jleave);
   a_POP3_ANSWER(rv, goto jleave);

   rv = STOP;
   cp = a_pop3_realdat;

   if(*cp != '\0'){
      uz i;

      if(su_idec_uz_cp(&i, cp, 10, &cp) & su_IDEC_STATE_EMASK)
         goto jerr;
      if(i > INT_MAX)
         goto jerr;
      *cnt = S(int,i);

      while(*cp != '\0' && !su_cs_is_space(*cp))
         ++cp;
      while(*cp != '\0' && su_cs_is_space(*cp))
         ++cp;

      if(*cp == '\0')
         goto jerr;
      if(su_idec_uz_cp(&i, cp, 10, NIL) & su_IDEC_STATE_EMASK)
         goto jerr;
      *size = S(off_t,i);
      rv = OKAY;
   }

   if(rv == STOP)
jerr:
      n_err(_("Invalid POP3 STAT response: %s\n"), a_pop3_dat.s);
jleave:
   NYD_OU;
   return rv;
}

static enum okay
a_pop3_list(struct mailbox *mp, int n, uz *size){
   char o[LINESIZE];
   char const *cp;
   enum okay rv;
   NYD_IN;

   snprintf(o, sizeof o, "LIST %u" NETNL, n);
   a_POP3_OUT(rv, o, MB_COMD, goto jleave);
   a_POP3_ANSWER(rv, goto jleave);

   cp = a_pop3_realdat;
   while(*cp != '\0' && !su_cs_is_space(*cp))
      ++cp;
   while(*cp != '\0' && su_cs_is_space(*cp))
      ++cp;
   if(*cp != '\0')
      su_idec_uz_cp(size, cp, 10, NIL);

jleave:
   NYD_OU;
   return rv;
}

static void
a_pop3_setptr(struct mailbox *mp, struct a_pop3_ctx const *pcp){
   uz i;
   enum needspec ns;
   NYD_IN;

   message = n_calloc(msgCount + 1, sizeof *message);
   message[msgCount].m_size = 0;
   message[msgCount].m_lines = 0;
   dot = message; /* (Just do it: avoid crash -- shall i now do ointr(0).. */

   for(i = 0; UCMP(z, i, <, msgCount); ++i){
      struct message *m;

      m = &message[i];
      m->m_flag = MUSED | MNEW | MNOFROM | MNEWEST;
      m->m_block = 0;
      m->m_offset = 0;
      m->m_size = m->m_xsize = 0;
   }

   for(i = 0; UCMP(z, i, <, msgCount); ++i)
      if(!a_pop3_list(mp, i + 1, &message[i].m_xsize))
         goto jleave;

   /* Force the load of all messages right now */
   ns = xok_blook(pop3_bulk_load, &pcp->pc_url, OXM_ALL)
         ? NEED_BODY : NEED_HEADER;
   for(i = 0; UCMP(z, i, <, msgCount); ++i)
      if(!a_pop3_get(mp, message + i, ns))
         goto jleave;

   n_autorec_relax_create();
   for(i = 0; UCMP(z, i, <, msgCount); ++i){
      char const *cp;
      struct message *m;

      m = &message[i];

      if((cp = hfield1("status", m)) != NIL)
         while(*cp != '\0'){
            if(*cp == 'R')
               m->m_flag |= MREAD;
            else if(*cp == 'O')
               m->m_flag &= ~MNEW;
            ++cp;
         }

      substdate(m);
      n_autorec_relax_unroll();
   }
   n_autorec_relax_gut();

   setdot(message);
jleave:
   NYD_OU;
}

static enum okay
a_pop3_get(struct mailbox *mp, struct message *m, enum needspec volatile need){
   char o[LINESIZE], *line, *lp;
   n_sighdl_t volatile saveint, savepipe;
   uz linesize, linelen, size;
   int number, lines;
   int volatile emptyline;
   off_t offset;
   enum okay volatile rv;
   NYD_IN;

   mx_fs_linepool_aquire(&line, &linesize);
   saveint = savepipe = SIG_IGN;
   number = S(int,P2UZ(m - message + 1));
   emptyline = 0;
   rv = STOP;

   if(mp->mb_sock == NIL || mp->mb_sock->s_fd < 0){
      n_err(_("POP3 connection already closed\n"));
      ++a_pop3_lock;
      goto jleave;
   }

   if(a_pop3_lock++ == 0){
      mx_sigs_all_holdx();
      if((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
         safe_signal(SIGINT, &a_pop3_maincatch);
      savepipe = safe_signal(SIGPIPE, SIG_IGN);
      if(sigsetjmp(a_pop3_jmp, 1))
         goto jleave;
      if(savepipe != SIG_IGN)
         safe_signal(SIGPIPE, &a_pop3_catch);
      mx_sigs_all_rele();
   }

   fseek(mp->mb_otf, 0L, SEEK_END);
   offset = ftell(mp->mb_otf);
jretry:
   switch(need){
   case NEED_HEADER:
      snprintf(o, sizeof o, "TOP %u 0" NETNL, number);
      break;
   case NEED_BODY:
      snprintf(o, sizeof o, "RETR %u" NETNL, number);
      break;
   case NEED_UNSPEC:
      n_panic("net-pop3.c bug\n");
   }
   a_POP3_OUT(rv, o, MB_COMD | MB_MULT, goto jleave);

   if(a_pop3_answer(mp) == STOP){
      if(need == NEED_HEADER){
         /* The TOP POP3 command is optional, so retry with entire message */
         need = NEED_BODY;
         goto jretry;
      }
      goto jleave;
   }

   size = 0;
   lines = 0;
   while(mx_socket_getline(&line, &linesize, &linelen, mp->mb_sock) > 0){
      if(line[0] == '.' && line[1] == NETNL[0] && line[2] == NETNL[1] &&
            line[3] == '\0'){
         mp->mb_active &= ~MB_MULT;
         break;
      }
      if(line[0] == '.'){
         lp = line + 1;
         --linelen;
      }else
         lp = line;
      /* TODO >>
       * Need to mask 'From ' lines. This cannot be done properly
       * since some servers pass them as 'From ' and others as
       * '>From '. Although one could identify the first kind of
       * server in principle, it is not possible to identify the
       * second as '>From ' may also come from a server of the
       * first type as actual data. So do what is absolutely
       * necessary only - mask 'From '.
       *
       * If the line is the first line of the message header, it
       * is likely a real 'From ' line. In this case, it is just
       * ignored since it violates all standards.
       * TODO i have *never* seen the latter?!?!?
       * TODO <<
       */
      /* Since we simply copy over data without doing any transfer
       * encoding reclassification/adjustment we *have* to perform
       * RFC 4155 compliant From_ quoting here */
      if(emptyline && is_head(lp, linelen, FAL0)){
         putc('>', mp->mb_otf);
         ++size;
      }
      lines++;
      if(lp[linelen-1] == NETNL[1] &&
            (linelen == 1 || lp[linelen-2] == NETNL[0])){
         emptyline = linelen <= 2;
         if(linelen > 2)
            fwrite(lp, 1, linelen - 2, mp->mb_otf);
         putc('\n', mp->mb_otf);
         size += linelen - 1;
      }else{
         emptyline = 0;
         fwrite(lp, 1, linelen, mp->mb_otf);
         size += linelen;
      }
   }
   if(!emptyline){
      /* TODO This is very ugly; but some POP3 daemons don't end a
       * TODO message with NETNL NETNL, and we need \n\n for mbox format.
       * TODO That is to say we do it wrong here in order to get it right
       * TODO when send.c stuff or with MBOX handling, even though THIS
       * TODO line is solely a property of the MBOX database format! */
      putc('\n', mp->mb_otf);
      ++lines;
      ++size;
   }
   m->m_size = size;
   m->m_lines = lines;
   m->m_block = mailx_blockof(offset);
   m->m_offset = mailx_offsetof(offset);
   fflush(mp->mb_otf);

   switch(need){
   case NEED_HEADER:
      m->m_content_info |= CI_HAVE_HEADER;
      break;
   case NEED_BODY:
      m->m_content_info |= CI_HAVE_HEADER | CI_HAVE_BODY;
      m->m_xlines = m->m_lines;
      m->m_xsize = m->m_size;
      break;
   case NEED_UNSPEC:
      break;
   }

   rv = OKAY;
jleave:
   mx_fs_linepool_release(line, linesize);
   if(saveint != SIG_IGN)
      safe_signal(SIGINT, saveint);
   if(savepipe != SIG_IGN)
      safe_signal(SIGPIPE, savepipe);
   --a_pop3_lock;
   NYD_OU;
   if(interrupts)
      n_raise(SIGINT);
   return rv;
}

static enum okay
a_pop3_exit(struct mailbox *mp){
   enum okay rv;
   NYD_IN;

   a_POP3_OUT(rv, "QUIT" NETNL, MB_COMD, goto jleave);
   a_POP3_ANSWER(rv, goto jleave);
jleave:
   NYD_OU;
   return rv;
}

static enum okay
a_pop3_delete(struct mailbox *mp, int n){
   char o[LINESIZE];
   enum okay rv;
   NYD_IN;

   snprintf(o, sizeof o, "DELE %u" NETNL, n);
   a_POP3_OUT(rv, o, MB_COMD, goto jleave);
   a_POP3_ANSWER(rv, goto jleave);
jleave:
   NYD_OU;
   return rv;
}

static enum okay
a_pop3_update(struct mailbox *mp){
   struct message *m;
   int dodel, c, gotcha, held;
   NYD_IN;

   if(!(n_pstate & n_PS_EDIT)){
      holdbits();
      c = 0;
      for(m = message; PCMP(m, <, &message[msgCount]); ++m)
         if(m->m_flag & MBOX)
            ++c;
      if(c > 0)
         makembox();
   }

   gotcha = held = 0;
   for(m = message; PCMP(m, <, message + msgCount); ++m){
      if(n_pstate & n_PS_EDIT)
         dodel = m->m_flag & MDELETED;
      else
         dodel = !((m->m_flag & MPRESERVE) || !(m->m_flag & MTOUCH));
      if(dodel){
         a_pop3_delete(mp, P2UZ(m - message + 1));
         ++gotcha;
      }else
         ++held;
   }

   /* C99 */{
      char const *dnq;

      dnq = n_shexp_quote_cp(displayname, FAL0);

      if(gotcha && (n_pstate & n_PS_EDIT)){
         fprintf(n_stdout, _("%s "), dnq);
         fprintf(n_stdout, (ok_blook(bsdcompat) || ok_blook(bsdmsgs))
            ? _("complete\n") : _("updated\n"));
      }else if(held && !(n_pstate & n_PS_EDIT)){
         if(held == 1)
            fprintf(n_stdout, _("Held 1 message in %s\n"), dnq);
         else
            fprintf(n_stdout, _("Held %d messages in %s\n"), held, dnq);
      }
   }
   fflush(n_stdout);

   NYD_OU;
   return OKAY;
}

#ifdef mx_HAVE_GSSAPI
# include 
#endif

#undef a_POP3_OUT
#undef a_POP3_ANSWER

enum okay
mx_pop3_noop(void){
   n_sighdl_t volatile saveint, savepipe;
   enum okay volatile rv;
   NYD_IN;

   rv = STOP;
   a_pop3_lock = 1;

   mx_sigs_all_holdx();
   if((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
      safe_signal(SIGINT, &a_pop3_maincatch);
   savepipe = safe_signal(SIGPIPE, SIG_IGN);
   if(sigsetjmp(a_pop3_jmp, 1) == 0){
      if(savepipe != SIG_IGN)
         safe_signal(SIGPIPE, &a_pop3_catch);
      mx_sigs_all_rele();
      rv = a_pop3_noop1(&mb);
   }
   safe_signal(SIGINT, saveint);
   safe_signal(SIGPIPE, savepipe);

   a_pop3_lock = 0;
   NYD_OU;
   return rv;
}

int
mx_pop3_setfile(char const *who, char const *server, enum fedit_mode fm){
   struct a_pop3_ctx pc;
   n_sighdl_t saveint, savepipe;
   char const *cp;
   int volatile rv;
   NYD_IN;

   rv = 1;
   if(fm & FEDIT_NEWMAIL)
      goto jleave;
   rv = -1;

   if(!mx_url_parse(&pc.pc_url, CPROTO_POP3, server))
      goto jleave;
   if(ok_vlook(v15_compat) == NIL && pc.pc_url.url_pass.s != NIL){
      n_err(_("POP3: new-style URL used without *v15-compat* being set: %s\n"),
         n_shexp_quote_cp(server, FAL0));
      goto jleave;
   }

   if(!((ok_vlook(v15_compat) != NIL)
         ? mx_cred_auth_lookup(&pc.pc_cred, &pc.pc_url)
         : mx_cred_auth_lookup_old(&pc.pc_cred, CPROTO_POP3,
            ((pc.pc_url.url_flags & mx_URL_HAD_USER)
             ? pc.pc_url.url_eu_h_p.s
             : pc.pc_url.url_u_h_p.s))))
      goto jleave;

   if(!quit(FAL0))
      goto jleave;

   pc.pc_sockp = su_TALLOC(struct mx_socket, 1);
   if(!mx_socket_open(pc.pc_sockp, &pc.pc_url)){
      su_FREE(pc.pc_sockp);
      goto jleave;
   }

   rv = 1;

   if(fm & FEDIT_SYSBOX)
      n_pstate &= ~n_PS_EDIT;
   else
      n_pstate |= n_PS_EDIT;

   if(mb.mb_sock != NIL){
      if(mb.mb_sock->s_fd >= 0)
         mx_socket_close(mb.mb_sock);
      su_FREE(mb.mb_sock);
      mb.mb_sock = NIL;
   }

   if(mb.mb_itf != NIL){
      fclose(mb.mb_itf);
      mb.mb_itf = NIL;
   }
   if(mb.mb_otf != NIL){
      fclose(mb.mb_otf);
      mb.mb_otf = NIL;
   }

   initbox(pc.pc_url.url_p_u_h_p);
   mb.mb_type = MB_VOID;
   a_pop3_lock = 1;

   saveint = safe_signal(SIGINT, SIG_IGN);
   savepipe = safe_signal(SIGPIPE, SIG_IGN);
   if(sigsetjmp(a_pop3_jmp, 1)){
      mb.mb_sock = NIL;
      mx_socket_close(pc.pc_sockp);
      su_FREE(pc.pc_sockp);
      n_err(_("POP3 connection closed\n"));
      safe_signal(SIGINT, saveint);
      safe_signal(SIGPIPE, savepipe);

      a_pop3_lock = 0;
      rv = -1;
      if(interrupts > 0)
         n_raise(SIGINT);
      goto jleave;
   }
   if(saveint != SIG_IGN)
      safe_signal(SIGINT, &a_pop3_catch);
   if(savepipe != SIG_IGN)
      safe_signal(SIGPIPE, &a_pop3_catch);

   if((cp = xok_vlook(pop3_keepalive, &pc.pc_url, OXM_ALL)) != NIL){
      su_idec_s32_cp(&a_pop3_keepalive, cp, 10, NIL);
      if(a_pop3_keepalive > 0){ /* Is a "positive number" */
         a_pop3_savealrm = safe_signal(SIGALRM, a_pop3alarm);
         alarm(a_pop3_keepalive);
      }
   }

   pc.pc_sockp->s_desc = (pc.pc_url.url_flags & mx_URL_TLS_REQUIRED)
         ? "POP3S" : "POP3";
   pc.pc_sockp->s_onclose = &a_pop3_timer_off;
   mb.mb_sock = pc.pc_sockp;

   if(a_pop3_login(&mb, &pc) != OKAY ||
         a_pop3_stat(&mb, &mailsize, &msgCount) != OKAY){
      mb.mb_sock = NIL;
      mx_socket_close(pc.pc_sockp);
      su_FREE(pc.pc_sockp);
      a_pop3_timer_off();

      safe_signal(SIGINT, saveint);
      safe_signal(SIGPIPE, savepipe);
      a_pop3_lock = 0;
      goto jleave;
   }

   setmsize(msgCount);
   mb.mb_type = MB_POP3;
   mb.mb_perm = ((n_poption & n_PO_R_FLAG) || (fm & FEDIT_RDONLY))
         ? 0 : MB_DELE;
   a_pop3_setptr(&mb, &pc);

   /*if (!(fm & FEDIT_NEWMAIL)) */{
      n_pstate &= ~n_PS_SAW_COMMAND;
      n_pstate |= n_PS_SETFILE_OPENED;
   }

   safe_signal(SIGINT, saveint);
   safe_signal(SIGPIPE, savepipe);
   a_pop3_lock = 0;

   if((n_poption & (n_PO_EXISTONLY | n_PO_HEADERLIST)) == n_PO_EXISTONLY){
      rv = (msgCount == 0);
      goto jleave;
   }

   if(!(n_pstate & n_PS_EDIT) && msgCount == 0){
      if(!ok_blook(emptystart))
         n_err(_("No mail for %s at %s\n"), who, pc.pc_url.url_p_eu_h_p);
      goto jleave;
   }

   rv = 0;
jleave:
   NYD_OU;
   return rv;
}

enum okay
mx_pop3_header(struct message *m){
   enum okay rv;
   NYD_IN;

   /* TODO no URL here, no OXM possible; (however it is used in setfile()..) */
   rv = a_pop3_get(&mb, m,
         (ok_blook(pop3_bulk_load) ? NEED_BODY : NEED_HEADER));
   NYD_OU;
   return rv;
}

enum okay
mx_pop3_body(struct message *m){
   enum okay rv;
   NYD_IN;

   rv = a_pop3_get(&mb, m, NEED_BODY);
   NYD_OU;
   return rv;
}

boole
mx_pop3_quit(boole hold_sigs_on){
   n_sighdl_t volatile saveint, savepipe;
   boole rv;
   NYD_IN;

   if(hold_sigs_on)
      rele_sigs();

   rv = FAL0;

   if(mb.mb_sock == NIL || mb.mb_sock->s_fd < 0){
      n_err(_("POP3 connection already closed\n"));
      rv = TRU1;
      goto jleave;
   }

   a_pop3_lock = 1;
   saveint = safe_signal(SIGINT, SIG_IGN);
   savepipe = safe_signal(SIGPIPE, SIG_IGN);
   if(sigsetjmp(a_pop3_jmp, 1)){
      safe_signal(SIGINT, saveint);
      safe_signal(SIGPIPE, savepipe);
      a_pop3_lock = 0;
      interrupts = 0;
      goto jleave;
   }
   if(saveint != SIG_IGN)
      safe_signal(SIGINT, &a_pop3_catch);
   if(savepipe != SIG_IGN)
      safe_signal(SIGPIPE, &a_pop3_catch);

   a_pop3_update(&mb);
   a_pop3_exit(&mb);
   mx_socket_close(mb.mb_sock);
   su_FREE(mb.mb_sock);
   mb.mb_sock = NIL;

   safe_signal(SIGINT, saveint);
   safe_signal(SIGPIPE, savepipe);
   a_pop3_lock = 0;

   rv = TRU1;
jleave:
   if(hold_sigs_on)
      hold_sigs();
   NYD_OU;
   return rv;
}

#include "su/code-ou.h"
#endif /* mx_HAVE_POP3 */
/* s-it-mode */
s-nail-14.9.15/src/mx/net-smtp.c000066400000000000000000000326221352610246600162160ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ Implementation of net-smtp.h.
 *@ TODO - use initial responses to save a round-trip (RFC 4954)
 *@ TODO - more (verbose) understanding+rection upon STATUS CODES
 *@ TODO - this is so dumb :(; except on macos we can shutdown.
 *@ TODO   do not care no more after 221? seen some short hangs.
 *
 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: BSD-4-Clause
 */
/*
 * Copyright (c) 2000
 * Gunnar Ritter.  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 Gunnar Ritter
 *    and his contributors.
 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER 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 GUNNAR RITTER 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.
 */
#undef su_FILE
#define su_FILE net_smtp
#define mx_SOURCE
#define mx_SOURCE_NET_SMTP

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

su_EMPTY_FILE()
#ifdef mx_HAVE_SMTP
#include 

#include 
#include 

#include "mx/cred-auth.h"
#include "mx/cred-md5.h"
#include "mx/file-streams.h"
#include "mx/names.h"
#include "mx/sigs.h"
#include "mx/net-socket.h"

#ifdef mx_HAVE_GSSAPI
# include "mx/net-gssapi.h" /* $(MX_SRCDIR) */
#endif

#include "mx/net-smtp.h"
#include "su/code-in.h"

struct a_netsmtp_line{
   struct str sl_dat;
   struct str sl_buf;
};

static sigjmp_buf a_netsmtp_jmp;

static void a_netsmtp_onsig(int signo);

/* Get the SMTP server's answer, expecting val */
static int a_netsmtp_read(struct mx_socket *sp, struct a_netsmtp_line *slp,
      int val, boole ign_eof, boole want_dat);

/* Talk to a SMTP server */
static boole a_netsmtp_talk(struct mx_socket *sp, struct sendbundle *sbp);

#ifdef mx_HAVE_GSSAPI
# include 
#endif

/* Indirect SMTP I/O */
#define a_SMTP_OUT(X) \
do{\
   char const *__cx__ = (X);\
   \
   if(n_poption & n_PO_D_VV){\
      /* TODO for now n_err() cannot normalize newlines in %s expansions */\
      char *__x__, *__y__;\
      uz __z__;\
      \
      __y__ = UNCONST(char*,__cx__);\
      __z__ = su_cs_len(__y__);\
      __x__ = n_lofi_alloc(__z__);\
      \
      su_mem_copy(__x__, __y__, __z__);\
      __y__ = &__x__[__z__];\
      \
      while(__y__ > __x__ && (__y__[-1] == '\n' || __y__[-1] == '\r'))\
         --__y__;\
      *__y__ = '\0';\
      n_err(">>> %s\n", __x__);\
      \
      n_lofi_free(__x__);\
   }\
   \
   if(!(n_poption & n_PO_D))\
      mx_socket_write(sop, __cx__);\
}while(0)

#define a_SMTP_ANSWER(X, IGNEOF, WANTDAT) \
do if(!(n_poption & n_PO_D)){\
   int y;\
   \
   if((y = a_netsmtp_read(sop, slp, X, IGNEOF, WANTDAT)) != (X) &&\
         (!(IGNEOF) || y != -1))\
      goto jleave;\
}while(0)

static void
a_netsmtp_onsig(int signo){
   UNUSED(signo);
   siglongjmp(a_netsmtp_jmp, 1);
}

static int
a_netsmtp_read(struct mx_socket *sop, struct a_netsmtp_line *slp, int val,
      boole ign_eof, boole want_dat){
   int rv, len;
   char *cp;
   NYD_IN;

   do{
      if((len = mx_socket_getline(&slp->sl_buf.s, &slp->sl_buf.l, NIL, sop)
            ) < 6){
         if(len >= 0 && !ign_eof)
            n_err(_("Unexpected EOF on SMTP connection\n"));
         rv = -1;
         goto jleave;
      }
      if(n_poption & n_PO_VV)
         n_err(">>> SERVER: %s", slp->sl_buf.s);

      switch(slp->sl_buf.s[0]){
      case '1': rv = 1; break;
      case '2': rv = 2; break;
      case '3': rv = 3; break;
      case '4': rv = 4; break;
      default: rv = 5; break;
      }
      if(val != rv)
         n_err(_("SMTP server: %s"), slp->sl_buf.s);
   }while(slp->sl_buf.s[3] == '-');

   if(want_dat){
      for(cp = slp->sl_buf.s; su_cs_is_digit(*cp); --len, ++cp)
         ;
      for(; su_cs_is_blank(*cp); --len, ++cp)
         ;
      slp->sl_dat.s = cp;
      ASSERT(len >= 2);
      len -= 2;
      cp[slp->sl_dat.l = S(uz,len)] = '\0';
   }

jleave:
   NYD_OU;
   return rv;
}

static boole
a_netsmtp_talk(struct mx_socket *sop, struct sendbundle *sbp){
   enum{
      a_ERROR = 1u<<0,
      a_IS_OAUTHBEARER = 1u<<1,
      a_IN_HEAD = 1u<<2,
      a_IN_BCC = 1u<<3
   };

   char o[LINESIZE]; /* TODO n_string++ */
   char const *hostname;
   struct a_netsmtp_line _sl, *slp = &_sl;
   struct str b64;
   struct mx_name *np;
   uz blen, cnt;
   u8 f;
   NYD_IN;

   f = a_ERROR | a_IN_HEAD;
   hostname = n_nodename(TRU1);
   su_mem_set(slp, 0, sizeof(*slp));

   /* Read greeting */
   a_SMTP_ANSWER(2, FAL0, FAL0);

#ifdef mx_HAVE_TLS
   if(!sop->s_use_tls){
      if(xok_blook(smtp_use_starttls, sbp->sb_urlp, OXM_ALL)){
         snprintf(o, sizeof o, NETLINE("EHLO %s"), hostname);
         a_SMTP_OUT(o);
         a_SMTP_ANSWER(2, FAL0, FAL0);

         a_SMTP_OUT(NETLINE("STARTTLS"));
         a_SMTP_ANSWER(2, FAL0, FAL0);

         if(!(n_poption & n_PO_D) && !n_tls_open(sbp->sb_urlp, sop))
            goto jleave;
      }else if(sbp->sb_credp->cc_needs_tls){
         n_err(_("SMTP authentication %s needs TLS "
            "(*smtp-use-starttls* set?)\n"),
            sbp->sb_credp->cc_auth);
         goto jleave;
      }
   }
#else
   if(sbp->sb_credp->cc_needs_tls ||
         xok_blook(smtp_use_starttls, sbp->sb_urlp, OXM_ALL)){
      n_err(_("No TLS support compiled in\n"));
      goto jleave;
   }
#endif

   /* Shorthand: no authentication, plain HELO? */
   if(sbp->sb_credp->cc_authtype == mx_CRED_AUTHTYPE_NONE){
      snprintf(o, sizeof o, NETLINE("HELO %s"), hostname);
      a_SMTP_OUT(o);
      a_SMTP_ANSWER(2, FAL0, FAL0);
      goto jsend;
   }

   /* We'll have to deal with authentication */
   snprintf(o, sizeof o, NETLINE("EHLO %s"), hostname);
   a_SMTP_OUT(o);
   a_SMTP_ANSWER(2, FAL0, FAL0);

   switch(sbp->sb_credp->cc_authtype){
   case mx_CRED_AUTHTYPE_OAUTHBEARER:
      f |= a_IS_OAUTHBEARER;
      /* FALLTHRU */
   case mx_CRED_AUTHTYPE_PLAIN:
   default: /* (this does not happen) */
      /* Calculate required storage */
      cnt = sbp->sb_credp->cc_user.l;
#define a_MAX \
   (2 + sizeof("AUTH XOAUTH2 " "user=\001auth=Bearer \001\001" NETNL))

      if(sbp->sb_credp->cc_pass.l >= UZ_MAX - a_MAX ||
            cnt >= UZ_MAX - a_MAX - sbp->sb_credp->cc_pass.l){
jerr_cred:
         n_err(_("Credentials overflow buffer sizes\n"));
         goto jleave;
      }
      cnt += sbp->sb_credp->cc_pass.l;

      cnt += a_MAX;
      if((cnt = b64_encode_calc_size(cnt)) == UZ_MAX)
         goto jerr_cred;
      if(cnt >= sizeof(o))
         goto jerr_cred;
#undef a_MAX

      /* Then create login query */
      if(f & a_IS_OAUTHBEARER){
         int i;

         i = snprintf(o, sizeof o, "user=%s\001auth=Bearer %s\001\001",
            sbp->sb_credp->cc_user.s, sbp->sb_credp->cc_pass.s);
         if(b64_encode_buf(&b64, o, i, B64_SALLOC) == NIL)
            goto jleave;
         snprintf(o, sizeof o, NETLINE("AUTH XOAUTH2 %s"), b64.s);
         b64.s = o;
      }else{
         int i;

         a_SMTP_OUT(NETLINE("AUTH PLAIN"));
         a_SMTP_ANSWER(3, FAL0, FAL0);

         i = snprintf(o, sizeof o, "%c%s%c%s",
            '\0', sbp->sb_credp->cc_user.s, '\0', sbp->sb_credp->cc_pass.s);
         if(b64_encode_buf(&b64, o, i, B64_SALLOC | B64_CRLF) == NIL)
            goto jleave;
      }
      a_SMTP_OUT(b64.s);
      a_SMTP_ANSWER(2, FAL0, FAL0);
      /* TODO OAUTHBEARER ERROR: send empty message to gain actual error
       * message (when status was 334) */
      break;

   case mx_CRED_AUTHTYPE_EXTERNAL:
#define a_MAX (sizeof("AUTH EXTERNAL " NETNL))
      cnt = b64_encode_calc_size(sbp->sb_credp->cc_user.l);
      if(/*cnt == UZ_MAX ||*/ cnt >= sizeof(o) - a_MAX)
         goto jerr_cred;
#undef a_MAX

      su_mem_copy(o, "AUTH EXTERNAL ", sizeof("AUTH EXTERNAL ") -1);
      b64.s = &o[sizeof("AUTH EXTERNAL ") -1];
      b64_encode_buf(&b64, sbp->sb_credp->cc_user.s, sbp->sb_credp->cc_user.l,
         B64_BUF | B64_CRLF);
      a_SMTP_OUT(o);
      a_SMTP_ANSWER(2, FAL0, FAL0);
      break;

   case mx_CRED_AUTHTYPE_EXTERNANON:
      a_SMTP_OUT(NETLINE("AUTH EXTERNAL ="));
      a_SMTP_ANSWER(2, FAL0, FAL0);
      break;

   case mx_CRED_AUTHTYPE_LOGIN:
      if(b64_encode_calc_size(sbp->sb_credp->cc_user.l) == UZ_MAX ||
            b64_encode_calc_size(sbp->sb_credp->cc_pass.l) == UZ_MAX)
         goto jerr_cred;

      a_SMTP_OUT(NETLINE("AUTH LOGIN"));
      a_SMTP_ANSWER(3, FAL0, FAL0);

      if(b64_encode_buf(&b64, sbp->sb_credp->cc_user.s,
            sbp->sb_credp->cc_user.l, B64_SALLOC | B64_CRLF) == NIL)
         goto jleave;
      a_SMTP_OUT(b64.s);
      a_SMTP_ANSWER(3, FAL0, FAL0);

      if(b64_encode_buf(&b64, sbp->sb_credp->cc_pass.s,
            sbp->sb_credp->cc_pass.l, B64_SALLOC | B64_CRLF) == NIL)
         goto jleave;
      a_SMTP_OUT(b64.s);
      a_SMTP_ANSWER(2, FAL0, FAL0);
      break;

#ifdef mx_HAVE_MD5
   case mx_CRED_AUTHTYPE_CRAM_MD5:{
      char *cp;

      a_SMTP_OUT(NETLINE("AUTH CRAM-MD5"));
      a_SMTP_ANSWER(3, FAL0, TRU1);

      if((cp = mx_md5_cram_string(&sbp->sb_credp->cc_user,
            &sbp->sb_credp->cc_pass, slp->sl_dat.s)) == NIL)
         goto jerr_cred;
      a_SMTP_OUT(cp);
      a_SMTP_ANSWER(2, FAL0, FAL0);
      }break;
#endif

#ifdef mx_HAVE_GSSAPI
   case mx_CRED_AUTHTYPE_GSSAPI:
      if(n_poption & n_PO_D)
         n_err(_(">>> We would perform GSS-API authentication now\n"));
      else if(!su_CONCAT(su_FILE,_gss)(sop, sbp->sb_urlp, sbp->sb_credp, slp))
         goto jleave;
      break;
#endif
   }

jsend:
   snprintf(o, sizeof o, NETLINE("MAIL FROM:<%s>"), sbp->sb_urlp->url_u_h.s);
   a_SMTP_OUT(o);
   a_SMTP_ANSWER(2, FAL0, FAL0);

   for(np = sbp->sb_to; np != NIL; np = np->n_flink){
      if(!(np->n_type & GDEL)){ /* TODO should not happen!?! */
         if(np->n_flags & mx_NAME_ADDRSPEC_WITHOUT_DOMAIN)
            snprintf(o, sizeof o, NETLINE("RCPT TO:<%s@%s>"),
               np->n_name, hostname);
         else
            snprintf(o, sizeof o, NETLINE("RCPT TO:<%s>"), np->n_name);
         a_SMTP_OUT(o);
         a_SMTP_ANSWER(2, FAL0, FAL0);
      }
   }

   a_SMTP_OUT(NETLINE("DATA"));
   a_SMTP_ANSWER(3, FAL0, FAL0);

   fflush_rewind(sbp->sb_input);
   cnt = fsize(sbp->sb_input);
   while(fgetline(&slp->sl_buf.s, &slp->sl_buf.l, &cnt, &blen, sbp->sb_input,
         1) != NIL){
      if(f & a_IN_HEAD){
         if(*slp->sl_buf.s == '\n')
            f &= ~(a_IN_HEAD | a_IN_BCC);
         else if((f & a_IN_BCC) && su_cs_is_blank(*slp->sl_buf.s))
            continue;
         /* We know what we have generated first, so do not look for whitespace
          * before the ':' */
         else if(!su_cs_cmp_case_n(slp->sl_buf.s, "bcc:", 4)){
            f |= a_IN_BCC;
            continue;
         }else
            f &= ~a_IN_BCC;
      }

      if(*slp->sl_buf.s == '.' && !(n_poption & n_PO_D))
         mx_socket_write1(sop, ".", 1, 1); /* TODO I/O rewrite.. */
      slp->sl_buf.s[blen - 1] = NETNL[0];
      slp->sl_buf.s[blen] = NETNL[1];
      slp->sl_buf.s[blen + 1] = '\0';
      a_SMTP_OUT(slp->sl_buf.s);
   }
   a_SMTP_OUT(NETLINE("."));
   a_SMTP_ANSWER(2, FAL0, FAL0);

   a_SMTP_OUT(NETLINE("QUIT"));
   a_SMTP_ANSWER(2, TRU1, FAL0);

   f &= ~a_ERROR;
jleave:
   if(slp->sl_buf.s != NIL)
      n_free(slp->sl_buf.s);
   NYD_OU;
   return ((f & a_ERROR) == 0);
}

#ifdef mx_HAVE_GSSAPI
# include 
#endif

#undef a_SMTP_OUT
#undef a_SMTP_ANSWER

boole
mx_smtp_mta(struct sendbundle *sbp){
   struct mx_socket so;
   n_sighdl_t volatile saveterm, savepipe;
   boole volatile rv;
   NYD_IN;

   rv = FAL0;

   saveterm = safe_signal(SIGTERM, SIG_IGN);
   savepipe = safe_signal(SIGPIPE, SIG_IGN);
   if(sigsetjmp(a_netsmtp_jmp, 1))
      goto jleave;
   if(saveterm != SIG_IGN)
      safe_signal(SIGTERM, &a_netsmtp_onsig);
   safe_signal(SIGPIPE, &a_netsmtp_onsig);

   if(n_poption & n_PO_D)
      su_mem_set(&so, 0, sizeof so);
   else if(!mx_socket_open(&so, sbp->sb_urlp))
      goto j_leave;

   so.s_desc = "SMTP";
   rv = a_netsmtp_talk(&so, sbp);

jleave:
   if(!(n_poption & n_PO_D))
      mx_socket_close(&so);
j_leave:
   safe_signal(SIGPIPE, savepipe);
   safe_signal(SIGTERM, saveterm);
   NYD_OU;
   return rv;
}

#include "su/code-ou.h"
#endif /* mx_HAVE_SMTP */
/* s-it-mode */
s-nail-14.9.15/src/mx/net-socket.c000066400000000000000000000553251352610246600165300ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ Socket operations.
 *
 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: BSD-3-Clause
 */
/*
 * 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.
 */
#undef su_FILE
#define su_FILE net_socket
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

su_EMPTY_FILE()
#ifdef mx_HAVE_NET
# ifdef mx_HAVE_NONBLOCKSOCK
/*#  include */
#  include 
/*#  include */
#  include 
/*#  include */
/*#  include */
/*#  include */
/*#  include */
/*#  include */

#  include 
# endif

#include 

#include 
#ifdef mx_HAVE_ARPA_INET_H
# include 
#endif
#include 

#ifdef mx_HAVE_XTLS
# include 
# include 
# include 
# include 
# include 
#endif

#include 
#include 

#include "mx/sigs.h"
#include "mx/url.h"

#include "mx/net-socket.h"
/* TODO fake */
#include "su/code-in.h"

/* */
static boole a_netso_open(struct mx_socket *sop, struct mx_url *urlp);

/* */
static int a_netso_connect(int fd, struct sockaddr *soap, uz soapl);

/* Write to socket fd, restarting on EINTR, unless anything is written */
static long a_netso_xwrite(int fd, char const *data, uz size);

static sigjmp_buf a_netso_actjmp; /* TODO someday, we won't need it no more */
static int a_netso_sig; /* TODO someday, we won't need it no more */

static void
a_netso_onsig(int sig) /* TODO someday, we won't need it no more */
{
   NYD; /* Signal handler */
   if (a_netso_sig == -1) {
      fprintf(n_stderr, _("\nInterrupting this operation may turn "
         "the DNS resolver unusable\n"));
      a_netso_sig = 0;
   } else {
      a_netso_sig = sig;
      siglongjmp(a_netso_actjmp, 1);
   }
}

static boole
a_netso_open(struct mx_socket *sop, struct mx_url *urlp) /*TODO sigs;refactor*/
{
# ifdef mx_HAVE_SO_XTIMEO
   struct timeval tv;
# endif
# ifdef mx_HAVE_SO_LINGER
   struct linger li;
# endif
# ifdef mx_HAVE_GETADDRINFO
#  ifndef NI_MAXHOST
#   define NI_MAXHOST 1025
#  endif
   char hbuf[NI_MAXHOST];
   struct addrinfo hints, *res0 = NULL, *res;
# else
   struct sockaddr_in servaddr;
   struct in_addr **pptr;
   struct hostent *hp;
   struct servent *ep;
# endif
   n_sighdl_t volatile ohup, oint;
   char const * volatile serv;
   int volatile sofd = -1, errval;
   NYD2_IN;

   su_mem_set(sop, 0, sizeof *sop);
   UNINIT(errval, 0);

   serv = (urlp->url_port != NULL) ? urlp->url_port : urlp->url_proto;

   if (n_poption & n_PO_D_V)
      n_err(_("Resolving host %s:%s ... "), urlp->url_host.s, serv);

   /* Signal handling (in respect to a_netso_sig dealing) is heavy, but no
    * healing until v15.0 and i want to end up with that functionality */
   hold_sigs();
   a_netso_sig = 0;
   ohup = safe_signal(SIGHUP, &a_netso_onsig);
   oint = safe_signal(SIGINT, &a_netso_onsig);
   if (sigsetjmp(a_netso_actjmp, 0)) {
jpseudo_jump:
      n_err("%s\n",
         (a_netso_sig == SIGHUP ? _("Hangup") : _("Interrupted")));
      if (sofd >= 0) {
         close(sofd);
         sofd = -1;
      }
      goto jjumped;
   }
   rele_sigs();

# ifdef mx_HAVE_GETADDRINFO
   for (;;) {
      su_mem_set(&hints, 0, sizeof hints);
      hints.ai_socktype = SOCK_STREAM;
      a_netso_sig = -1;
      errval = getaddrinfo(urlp->url_host.s, serv, &hints, &res0);
      if (a_netso_sig != -1) {
         a_netso_sig = SIGINT;
         goto jpseudo_jump;
      }
      a_netso_sig = 0;
      if (errval == 0)
         break;

      if (n_poption & n_PO_D_V)
         n_err(_("failed\n"));
      n_err(_("Lookup of %s:%s failed: %s\n"),
         urlp->url_host.s, serv, gai_strerror(errval));

      /* Error seems to depend on how "smart" the /etc/service code is: is it
       * "able" to state whether the service as such is NONAME or does it only
       * check for the given ai_socktype.. */
      if (errval == EAI_NONAME || errval == EAI_SERVICE) {
         if (serv == urlp->url_proto &&
               (serv = mx_url_servbyname(urlp->url_proto, NIL, NIL)) != NIL &&
               *serv != '\0') {
            n_err(_("  Trying standard protocol port %s\n"), serv);
            n_err(_("  If that succeeds consider including the "
               "port in the URL!\n"));
            continue;
         }
         if (serv != urlp->url_port)
            n_err(_("  Including a port number in the URL may "
               "circumvent this problem\n"));
      }
      ASSERT(sofd == -1);
      errval = 0;
      goto jjumped;
   }
   if (n_poption & n_PO_D_V)
      n_err(_("done\n"));

   for (res = res0; res != NULL && sofd < 0; res = res->ai_next) {
      if (n_poption & n_PO_D_V) {
         if (getnameinfo(res->ai_addr, res->ai_addrlen, hbuf, sizeof hbuf,
               NULL, 0, NI_NUMERICHOST))
            su_mem_copy(hbuf, "unknown host", sizeof("unknown host"));
         n_err(_("%sConnecting to %s:%s ... "),
               (res == res0 ? n_empty : "\n"), hbuf, serv);
      }

      sofd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
      if(sofd >= 0 &&
            (errval = a_netso_connect(sofd, res->ai_addr, res->ai_addrlen)
               ) != su_ERR_NONE)
         sofd = -1;
   }

jjumped:
   if (res0 != NULL) {
      freeaddrinfo(res0);
      res0 = NULL;
   }

# else /* mx_HAVE_GETADDRINFO */
   if (serv == urlp->url_proto) {
      if ((ep = getservbyname(n_UNCONST(serv), "tcp")) != NULL)
         urlp->url_portno = ntohs(ep->s_port);
      else {
         if (n_poption & n_PO_D_V)
            n_err(_("failed\n"));
         if ((serv = mx_url_servbyname(urlp->url_proto, &urlp->url_portno, NIL)
               ) != NIL && *serv != '\0')
            n_err(_("  Unknown service: %s\n"), urlp->url_proto);
            n_err(_("  Trying standard protocol port %s\n"), serv);
            n_err(_("  If that succeeds consider including the "
               "port in the URL!\n"));
         else {
            n_err(_("  Unknown service: %s\n"), urlp->url_proto);
            n_err(_("  Including a port number in the URL may "
               "circumvent this problem\n"));
            ASSERT(sofd == -1 && errval == 0);
            goto jjumped;
         }
      }
   }

   a_netso_sig = -1;
   hp = gethostbyname(urlp->url_host.s);
   if (a_netso_sig != -1) {
      a_netso_sig = SIGINT;
      goto jpseudo_jump;
   }
   a_netso_sig = 0;

   if (hp == NULL) {
      char const *emsg;

      if (n_poption & n_PO_D_V)
         n_err(_("failed\n"));
      switch (h_errno) {
      case HOST_NOT_FOUND: emsg = N_("host not found"); break;
      default:
      case TRY_AGAIN:      emsg = N_("(maybe) try again later"); break;
      case NO_RECOVERY:    emsg = N_("non-recoverable server error"); break;
      case NO_DATA:        emsg = N_("valid name without IP address"); break;
      }
      n_err(_("Lookup of %s:%s failed: %s\n"),
         urlp->url_host.s, serv, V_(emsg));
      goto jjumped;
   } else if (n_poption & n_PO_D_V)
      n_err(_("done\n"));

   pptr = (struct in_addr**)hp->h_addr_list;
   if ((sofd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
      n_perr(_("could not create socket"), 0);
      ASSERT(sofd == -1 && errval == 0);
      goto jjumped;
   }

   su_mem_set(&servaddr, 0, sizeof servaddr);
   servaddr.sin_family = AF_INET;
   servaddr.sin_port = htons(urlp->url_portno);
   su_mem_copy(&servaddr.sin_addr, *pptr, sizeof(struct in_addr));
   if (n_poption & n_PO_D_V)
      n_err(_("%sConnecting to %s:%d ... "),
         n_empty, inet_ntoa(**pptr), (int)urlp->url_portno);
   if((errval = a_netso_connect(sofd, (struct sockaddr*)&servaddr,
         sizeof servaddr)) != su_ERR_NONE)
      sofd = -1;
jjumped:
# endif /* !mx_HAVE_GETADDRINFO */

   hold_sigs();
   safe_signal(SIGINT, oint);
   safe_signal(SIGHUP, ohup);
   rele_sigs();

   if (sofd < 0) {
      if (errval != 0) {
         n_perr(_("Could not connect"), errval);
         su_err_set_no(errval);
      }
      goto jleave;
   }

   sop->s_fd = sofd;
   if (n_poption & n_PO_D_V)
      n_err(_("connected.\n"));

   /* And the regular timeouts XXX configurable */
# ifdef mx_HAVE_SO_XTIMEO
   tv.tv_sec = 42;
   tv.tv_usec = 0;
   (void)setsockopt(sofd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv);
   (void)setsockopt(sofd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
# endif
# ifdef mx_HAVE_SO_LINGER
   li.l_onoff = 1;
   li.l_linger = 42;
   (void)setsockopt(sofd, SOL_SOCKET, SO_LINGER, &li, sizeof li);
# endif

   /* SSL/TLS upgrade? */
# ifdef mx_HAVE_TLS
   hold_sigs();

#  if defined mx_HAVE_GETADDRINFO && defined SSL_CTRL_SET_TLSEXT_HOSTNAME
      /* TODO the SSL_ def check should NOT be here */
   if(urlp->url_flags & mx_URL_TLS_MASK){
      su_mem_set(&hints, 0, sizeof hints);
      hints.ai_family = AF_UNSPEC;
      hints.ai_flags = AI_NUMERICHOST;
      res0 = NULL;
      if(getaddrinfo(urlp->url_host.s, NULL, &hints, &res0) == 0)
         freeaddrinfo(res0);
      else
         urlp->url_flags |= mx_URL_HOST_IS_NAME;
   }
#  endif

   if (urlp->url_flags & mx_URL_TLS_REQUIRED) {
      ohup = safe_signal(SIGHUP, &a_netso_onsig);
      oint = safe_signal(SIGINT, &a_netso_onsig);
      if (sigsetjmp(a_netso_actjmp, 0)) {
         n_err(_("%s during SSL/TLS handshake\n"),
            (a_netso_sig == SIGHUP ? _("Hangup") : _("Interrupted")));
         goto jsclose;
      }
      rele_sigs();

      if(!n_tls_open(urlp, sop)){
jsclose:
         mx_socket_close(sop);
         sofd = -1;
      }else if(urlp->url_cproto == CPROTO_CERTINFO)
         mx_socket_close(sop);

      hold_sigs();
      safe_signal(SIGINT, oint);
      safe_signal(SIGHUP, ohup);
   }

   rele_sigs();
# endif /* mx_HAVE_TLS */

jleave:
   /* May need to bounce the signal to the go.c trampoline (or wherever) */
   if (a_netso_sig != 0) {
      sigset_t cset;
      sigemptyset(&cset);
      sigaddset(&cset, a_netso_sig);
      sigprocmask(SIG_UNBLOCK, &cset, NULL);
      n_raise(a_netso_sig);
   }
   NYD2_OU;
   return (sofd >= 0);
}

static int
a_netso_connect(int fd, struct sockaddr *soap, uz soapl){
   int rv;
   NYD_IN;

#ifdef mx_HAVE_NONBLOCKSOCK
   rv = fcntl(fd, F_GETFL, 0);
   if(rv != -1 && !fcntl(fd, F_SETFL, rv | O_NONBLOCK)){
      fd_set fdset;
      struct timeval tv; /* XXX configurable */
      socklen_t sol;
      boole show_progress;
      uz cnt;
      int i, soe;

      if(connect(fd, soap, soapl) && (i = su_err_no()) != su_ERR_INPROGRESS){
         rv = i;
         goto jerr_noerrno;
      }

      show_progress = ((n_poption & n_PO_D_V) ||
               ((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT)));

      FD_ZERO(&fdset);
      FD_SET(fd, &fdset);
      /* C99 */{
         char const *cp;

         if((cp = ok_vlook(socket_connect_timeout)) == NULL ||
               (su_idec_uz_cp(&cnt, cp, 0, NULL), cnt < 2))
            cnt = 42; /* XXX mx-config.h */

         if(show_progress){
            tv.tv_sec = 2;
            cnt >>= 1;
         }else{
            tv.tv_sec = (long)cnt; /* XXX */
            cnt = 1;
         }
      }
jrewait:
      tv.tv_usec = 0;
      if((soe = select(fd + 1, NULL, &fdset, NULL, &tv)) == 1){
         i = rv;
         sol = sizeof rv;
         getsockopt(fd, SOL_SOCKET, SO_ERROR, &rv, &sol);
         fcntl(fd, F_SETFL, i);
         if(show_progress)
            n_err(" ");
      }else if(soe == 0){
         if(show_progress && --cnt > 0){
            n_err(".");
            tv.tv_sec = 2;
            goto jrewait;
         }
         n_err(_(" timeout\n"));
         close(fd);
         rv = su_ERR_TIMEDOUT;
      }else
         goto jerr;
   }else
#endif /* mx_HAVE_NONBLOCKSOCK */

         if(!connect(fd, soap, soapl))
      rv = su_ERR_NONE;
   else{
#ifdef mx_HAVE_NONBLOCKSOCK
jerr:
#endif
      rv = su_err_no();
#ifdef mx_HAVE_NONBLOCKSOCK
jerr_noerrno:
#endif
      n_perr(_("connect(2) failed:"), rv);
      close(fd);
   }
   NYD_OU;
   return rv;
}

static long
a_netso_xwrite(int fd, char const *data, uz size)
{
   long rv = -1, wo;
   uz wt = 0;
   NYD_IN;

   do {
      if ((wo = write(fd, data + wt, size - wt)) < 0) {
         if (su_err_no() == su_ERR_INTR)
            continue;
         else
            goto jleave;
      }
      wt += wo;
   } while (wt < size);
   rv = (long)size;
jleave:
   NYD_OU;
   return rv;
}

boole
mx_socket_open(struct mx_socket *sop, struct mx_url *urlp){
   char const *cp;
   boole rv;
   NYD_IN;

   rv = FAL0;

   /* We may have a proxy configured */
   if((cp = xok_vlook(socks_proxy, urlp, OXM_ALL)) == NULL)
      rv = a_netso_open(sop, urlp);
   else{
      u8 pbuf[4 + 1 + 255 + 2];
      uz i;
      char const *emsg;
      struct mx_url url2;

      if(!mx_url_parse(&url2, CPROTO_SOCKS, cp)){
         n_err(_("Failed to parse *socks-proxy*: %s\n"), cp);
         goto jleave;
      }
      if(urlp->url_host.l > 255){
         n_err(_("*socks-proxy*: hostname too long: %s\n"),
            urlp->url_input);
         goto jleave;
      }

      if (n_poption & n_PO_D_V)
         n_err(_("Connecting via *socks-proxy* to %s:%s ...\n"),
            urlp->url_host.s,
            (urlp->url_port != NULL ? urlp->url_port : urlp->url_proto));

      if(!a_netso_open(sop, &url2)){
         n_err(_("Failed to connect to *socks-proxy*: %s\n"), cp);
         goto jleave;
      }

      /* RFC 1928: version identifier/method selection message */
      pbuf[0] = 0x05; /* VER: protocol version: X'05' */
      pbuf[1] = 0x01; /* NMETHODS: 1 */
      pbuf[2] = 0x00; /* METHOD: X'00' NO AUTHENTICATION REQUIRED */
      if(write(sop->s_fd, pbuf, 3) != 3){
jerrsocks:
         n_perr("*socks-proxy*", 0);
jesocks:
         mx_socket_close(sop);
         goto jleave;
      }

      /* Receive greeting */
      if(read(sop->s_fd, pbuf, 2) != 2)
         goto jerrsocks;
      if(pbuf[0] != 0x05 || pbuf[1] != 0x00){
jesocksreply:
         emsg = N_("unexpected reply\n");
jesocksreplymsg:
         /* I18N: error message and failing URL */
         n_err(_("*socks-proxy*: %s: %s\n"), V_(emsg), cp);
         goto jesocks;
      }

      /* RFC 1928: CONNECT request */
      pbuf[0] = 0x05; /* VER: protocol version: X'05' */
      pbuf[1] = 0x01; /* CMD: CONNECT X'01' */
      pbuf[2] = 0x00; /* RESERVED */
      pbuf[3] = 0x03; /* ATYP: domain name */
      pbuf[4] = (u8)urlp->url_host.l;
      su_mem_copy(&pbuf[i = 5], urlp->url_host.s, urlp->url_host.l);
      /* C99 */{
         u16 x;

         x = htons(urlp->url_portno);
         su_mem_copy(&pbuf[i += urlp->url_host.l], (u8*)&x, sizeof x);
         i += sizeof x;
      }
      if(write(sop->s_fd, pbuf, i) != (sz)i)
         goto jerrsocks;

      /* Connect result */
      if((i = read(sop->s_fd, pbuf, 4)) != 4)
         goto jerrsocks;
      /* Version 5, reserved must be 0 */
      if(pbuf[0] != 0x05 || pbuf[2] != 0x00)
         goto jesocksreply;
      /* Result */
      switch(pbuf[1]){
      case 0x00: emsg = NULL; break;
      case 0x01: emsg = N_("SOCKS server failure"); break;
      case 0x02: emsg = N_("connection not allowed by ruleset"); break;
      case 0x03: emsg = N_("network unreachable"); break;
      case 0x04: emsg = N_("host unreachable"); break;
      case 0x05: emsg = N_("connection refused"); break;
      case 0x06: emsg = N_("TTL expired"); break;
      case 0x07: emsg = N_("command not supported"); break;
      case 0x08: emsg = N_("address type not supported"); break;
      default: emsg = N_("unknown SOCKS error code"); break;
      }
      if(emsg != NULL)
         goto jesocksreplymsg;

      /* Address type variable; read the BND.PORT with it.
       * This is actually false since RFC 1928 says that the BND.ADDR reply to
       * CONNECT contains the IP address, so only 0x01 and 0x04 are allowed */
      switch(pbuf[3]){
      case 0x01: i = 4; break;
      case 0x03: i = 1; break;
      case 0x04: i = 16; break;
      default: goto jesocksreply;
      }
      i += sizeof(u16);
      if(read(sop->s_fd, pbuf, i) != (sz)i)
         goto jerrsocks;
      if(i == 1 + sizeof(u16)){
         i = pbuf[0];
         if(read(sop->s_fd, pbuf, i) != (sz)i)
            goto jerrsocks;
      }
      rv = TRU1;
   }
jleave:
   NYD_OU;
   return rv;
}

int
mx_socket_close(struct mx_socket *sop)
{
   int i;
   NYD_IN;

   i = sop->s_fd;
   sop->s_fd = -1;
   /* TODO NOTE: we MUST NOT close the descriptor 0 here...
    * TODO of course this should be handled in a VMAILFS->open() .s_fd=-1,
    * TODO but unfortunately it isn't yet */
   if (i <= 0)
      i = 0;
   else {
      if (sop->s_onclose != NULL)
         (*sop->s_onclose)();
      if (sop->s_wbuf != NULL)
         n_free(sop->s_wbuf);
# ifdef mx_HAVE_XTLS
      if (sop->s_use_tls) {
         void *s_tls = sop->s_tls;

         sop->s_tls = NULL;
         sop->s_use_tls = 0;
         while (!SSL_shutdown(s_tls)) /* XXX proper error handling;signals! */
            ;
         SSL_free(s_tls);
      }
# endif
      i = close(i);
   }
   NYD_OU;
   return i;
}

enum okay
mx_socket_write(struct mx_socket *sop, char const *data) /* XXX INLINE */
{
   enum okay rv;
   NYD2_IN;

   rv = mx_socket_write1(sop, data, su_cs_len(data), 0);
   NYD2_OU;
   return rv;
}

enum okay
mx_socket_write1(struct mx_socket *sop, char const *data, int size,
   int use_buffer)
{
   enum okay rv = STOP;
   int x;
   NYD2_IN;

   if (use_buffer > 0) {
      int di;

      if (sop->s_wbuf == NULL) {
         sop->s_wbufsize = 4096;
         sop->s_wbuf = n_alloc(sop->s_wbufsize);
         sop->s_wbufpos = 0;
      }
      while (sop->s_wbufpos + size > sop->s_wbufsize) {
         di = sop->s_wbufsize - sop->s_wbufpos;
         size -= di;
         if (sop->s_wbufpos > 0) {
            su_mem_copy(&sop->s_wbuf[sop->s_wbufpos], data, di);
            rv = mx_socket_write1(sop, sop->s_wbuf, sop->s_wbufsize, -1);
         } else
            rv = mx_socket_write1(sop, data, sop->s_wbufsize, -1);
         if (rv != OKAY)
            goto jleave;
         data += di;
         sop->s_wbufpos = 0;
      }
      if (size == sop->s_wbufsize) {
         rv = mx_socket_write1(sop, data, sop->s_wbufsize, -1);
         if (rv != OKAY)
            goto jleave;
      } else if (size > 0) {
         su_mem_copy(&sop->s_wbuf[sop->s_wbufpos], data, size);
         sop->s_wbufpos += size;
      }
      rv = OKAY;
      goto jleave;
   } else if (use_buffer == 0 && sop->s_wbuf != NULL && sop->s_wbufpos > 0) {
      x = sop->s_wbufpos;
      sop->s_wbufpos = 0;
      if ((rv = mx_socket_write1(sop, sop->s_wbuf, x, -1)) != OKAY)
         goto jleave;
   }
   if (size == 0) {
      rv = OKAY;
      goto jleave;
   }

# ifdef mx_HAVE_XTLS
   if (sop->s_use_tls) {
jssl_retry:
      x = SSL_write(sop->s_tls, data, size);
      if (x < 0) {
         switch (SSL_get_error(sop->s_tls, x)) {
         case SSL_ERROR_WANT_READ:
         case SSL_ERROR_WANT_WRITE:
            goto jssl_retry;
         }
      }
   } else
# endif
   {
      x = a_netso_xwrite(sop->s_fd, data, size);
   }
   if (x != size) {
      char o[512];

      snprintf(o, sizeof o, "%s write error",
         (sop->s_desc ? sop->s_desc : "socket"));
# ifdef mx_HAVE_XTLS
      if (sop->s_use_tls)
         ssl_gen_err("%s", o);
      else
# endif
         n_perr(o, 0);
      if (x < 0)
         mx_socket_close(sop);
      rv = STOP;
      goto jleave;
   }
   rv = OKAY;
jleave:
   NYD2_OU;
   return rv;
}

int
(mx_socket_getline)(char **line, uz *linesize, uz *linelen,
   struct mx_socket *sop  su_DBG_LOC_ARGS_DECL)
{
   int rv;
   uz lsize;
   char *lp_base, *lp;
   NYD2_IN;

   lsize = *linesize;
   lp_base = *line;
   lp = lp_base;

   if (sop->s_rsz < 0) {
      mx_socket_close(sop);
      rv = sop->s_rsz;
      goto jleave;
   }

   do {
      if (lp_base == NULL || PCMP(lp, >, lp_base + lsize - 128)) {
         uz diff = P2UZ(lp - lp_base);
         *linesize = (lsize += 256); /* XXX magic */
         *line = lp_base = su_MEM_REALLOC_LOCOR(lp_base, lsize,
               su_DBG_LOC_ARGS_ORUSE);
         lp = lp_base + diff;
      }

      if (sop->s_rbufptr == NULL ||
            PCMP(sop->s_rbufptr, >=, sop->s_rbuf + sop->s_rsz)) {
# ifdef mx_HAVE_XTLS
         if (sop->s_use_tls) {
jssl_retry:
            sop->s_rsz = SSL_read(sop->s_tls, sop->s_rbuf, sizeof sop->s_rbuf);
            if (sop->s_rsz <= 0) {
               if (sop->s_rsz < 0) {
                  char o[512];

                  switch(SSL_get_error(sop->s_tls, sop->s_rsz)) {
                  case SSL_ERROR_WANT_READ:
                  case SSL_ERROR_WANT_WRITE:
                     goto jssl_retry;
                  }
                  snprintf(o, sizeof o, "%s",
                     (sop->s_desc ?  sop->s_desc : "socket"));
                  ssl_gen_err("%s", o);
               }
               break;
            }
         } else
# endif
         {
jagain:
            sop->s_rsz = read(sop->s_fd, sop->s_rbuf, sizeof sop->s_rbuf);
            if (sop->s_rsz <= 0) {
               if (sop->s_rsz < 0) {
                  char o[512];

                  if (su_err_no() == su_ERR_INTR)
                     goto jagain;
                  snprintf(o, sizeof o, "%s",
                     (sop->s_desc ?  sop->s_desc : "socket"));
                  n_perr(o, 0);
               }
               break;
            }
         }
         sop->s_rbufptr = sop->s_rbuf;
      }
   } while ((*lp++ = *sop->s_rbufptr++) != '\n');
   *lp = '\0';
   lsize = P2UZ(lp - lp_base);

   if (linelen)
      *linelen = lsize;
   rv = (int)lsize;
jleave:
   NYD2_OU;
   return rv;
}

#include "su/code-ou.h"
#endif /* mx_HAVE_NET */
/* s-it-mode */
s-nail-14.9.15/src/mx/obs-imap-cache.c000066400000000000000000000610221352610246600172130ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ A cache for IMAP.
 *
 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: BSD-4-Clause
 */
/*
 * Copyright (c) 2004
 * Gunnar Ritter.  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 Gunnar Ritter
 *    and his contributors.
 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER 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 GUNNAR RITTER 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.
 */
#undef su_FILE
#define su_FILE obs_imap_cache
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

su_EMPTY_FILE()
#ifdef mx_HAVE_IMAP
#include 

#include 
#include 
#include 

#include "mx/file-locks.h"
#include "mx/file-streams.h"
#include "mx/url.h"

/* TODO fake */
#include "su/code-in.h"

static char *           encname(struct mailbox *mp, const char *name, int same,
                           const char *box);
static char *           encuid(struct mailbox *mp, u64 uid);
static FILE *           clean(struct mailbox *mp, struct cw *cw);
static u64 *         builds(long *contentelem);
static void             purge(struct mailbox *mp, struct message *m, long mc,
                           struct cw *cw, const char *name);
static int              longlt(const void *a, const void *b);
static void             remve(unsigned long n);
static FILE *           cache_queue1(struct mailbox *mp, char const *mode,
                           char **xname);
static enum okay        dequeue1(struct mailbox *mp);

static const char infofmt[] = "%c %lu %d %lu %ld";
#define INITSKIP 128L
#define USEBITS(f)  \
   ((f) & (MSAVED|MDELETED|MREAD|MBOXED|MNEW|MFLAGGED|MANSWERED|MDRAFTED))

static const char README1[] = "\
This is a cache directory maintained by " VAL_UAGENT "(1).\n\
You should not change any files within.\n\
Nevertheless, the structure is as follows: Each subdirectory of the\n\
current directory represents an IMAP account, and each subdirectory\n\
below that represents a mailbox. Each mailbox directory contains a file\n\
named UIDVALIDITY which describes the validity in relation to the version\n\
on the server. Other files have names corresponding to their IMAP UID.\n";
static const char README2[] = "\n\
The first 128 bytes of these files are used to store message attributes; the\n\
following data is equivalent to compress(1) output. So if you have to save a\n\
message by hand because of an emergency, throw away the first 128 bytes and\n\
decompress the rest, as e.g. \"dd if=FILE skip=1 bs=128 | zcat\" does.\n";
static const char README3[] = "\n\
Files named QUEUE contain data that will be sent do the IMAP server next\n\
time a connection is made in online mode.\n";
static const char README4[] = "\n\
You can safely delete any file or directory here, unless it contains a QUEUE\n\
file that is not empty; " VAL_UAGENT
   " will download the data again and will also\n\
write new cache entries if configured in this way. If you do not wish to use\n\
the cache anymore, delete the entire directory and unset the *imap-cache*\n\
variable in " VAL_UAGENT "(1).\n";

static char *
encname(struct mailbox *mp, const char *name, int same, const char *box)
{
   char *cachedir, *eaccount, *ename, *res;
   int resz;
   NYD2_IN;

   ename = mx_url_xenc(name, TRU1);
   if (mp->mb_cache_directory && same && box == NULL) {
      res = n_autorec_alloc(resz = su_cs_len(mp->mb_cache_directory) +
            su_cs_len(ename) + 2);
      snprintf(res, resz, "%s%s%s", mp->mb_cache_directory,
         (*ename ? "/" : ""), ename);
   } else {
      res = NULL;

      if ((cachedir = ok_vlook(imap_cache)) == NULL ||
            (cachedir = fexpand(cachedir, FEXP_LOCAL | FEXP_NOPROTO)) == NULL)
         goto jleave;
      eaccount = mx_url_xenc(mp->mb_imap_account, TRU1);

      if (box != NULL || su_cs_cmp_case(box = mp->mb_imap_mailbox, "INBOX")) {
         boole err;

         box = imap_path_encode(box, &err);
         if(err)
            goto jleave;
         box = mx_url_xenc(box, TRU1);
      } else
         box = "INBOX";

      res = n_autorec_alloc(resz = su_cs_len(cachedir) + su_cs_len(eaccount) +
            su_cs_len(box) + su_cs_len(ename) + 4);
      snprintf(res, resz, "%s/%s/%s%s%s", cachedir, eaccount, box,
            (*ename ? "/" : ""), ename);
   }
jleave:
   NYD2_OU;
   return res;
}

static char *
encuid(struct mailbox *mp, u64 uid)
{
   char buf[64], *cp;
   NYD2_IN;

   snprintf(buf, sizeof buf, "%" PRIu64, uid);
   cp = encname(mp, buf, 1, NULL);
   NYD2_OU;
   return cp;
}

FL enum okay
getcache1(struct mailbox *mp, struct message *m, enum needspec need,
   int setflags)
{
   FILE *fp;
   long n = 0, size = 0, xsize, xtime, xlines = -1, lines = 0;
   int lastc = EOF, i, xflag, inheader = 1;
   char b, iob[32768];
   off_t offset;
   void *zp;
   enum okay rv = STOP;
   NYD2_IN;

   if (setflags == 0 && ((mp->mb_type != MB_IMAP && mp->mb_type != MB_CACHE) ||
         m->m_uid == 0))
      goto jleave;
   if((fp = mx_fs_open(encuid(mp, m->m_uid), "r")) == NIL)
      goto jleave;

   mx_file_lock(fileno(fp), mx_FILE_LOCK_TYPE_READ, 0,0, 0);
   if (fscanf(fp, infofmt, &b, (unsigned long*)&xsize, &xflag,
         (unsigned long*)&xtime, &xlines) < 4)
      goto jfail;
   if (need != NEED_UNSPEC) {
      switch (b) {
      case 'H':
         if (need == NEED_HEADER)
            goto jsuccess;
         goto jfail;
      case 'B':
         if (need == NEED_HEADER || need == NEED_BODY)
            goto jsuccess;
         goto jfail;
      default:
         goto jfail;
      }
   }
jsuccess:
   if (b == 'N')
      goto jflags;
   if (fseek(fp, INITSKIP, SEEK_SET) < 0)
      goto jfail;
   zp = zalloc(fp);
   if (fseek(mp->mb_otf, 0L, SEEK_END) < 0) {
      zfree(zp);
      goto jfail;
   }
   offset = ftell(mp->mb_otf);
   while (inheader && (n = zread(zp, iob, sizeof iob)) > 0) {
      size += n;
      for (i = 0; i < n; i++) {
         if (iob[i] == '\n') {
            lines++;
            if (lastc == '\n')
               inheader = 0;
         }
         lastc = iob[i]&0377;
      }
      fwrite(iob, 1, n, mp->mb_otf);
   }
   if (n > 0 && need == NEED_BODY) {
      while ((n = zread(zp, iob, sizeof iob)) > 0) {
         size += n;
         for (i = 0; i < n; i++)
            if (iob[i] == '\n')
               lines++;
         fwrite(iob, 1, n, mp->mb_otf);
      }
   }
   fflush(mp->mb_otf);
   if (zfree(zp) < 0 || n < 0 || ferror(fp) || ferror(mp->mb_otf))
      goto jfail;

   m->m_size = size;
   m->m_lines = lines;
   m->m_block = mailx_blockof(offset);
   m->m_offset = mailx_offsetof(offset);
jflags:
   if (setflags) {
      m->m_xsize = xsize;
      m->m_time = xtime;
      if (setflags & 2) {
         m->m_flag = xflag | MNOFROM;
         if (b != 'B')
            m->m_flag |= MHIDDEN;
      }
   }
   if (xlines > 0 && m->m_xlines <= 0)
      m->m_xlines = xlines;
   switch (b) {
   case 'B':
      m->m_xsize = xsize;
      if (xflag == MREAD && xlines > 0)
         m->m_flag |= MFULLYCACHED;
      if (need == NEED_BODY) {
         m->m_content_info |= CI_HAVE_HEADER | CI_HAVE_BODY;
         if (m->m_lines > 0)
            m->m_xlines = m->m_lines;
         break;
      }
      /*FALLTHRU*/
   case 'H':
      m->m_content_info |= CI_HAVE_HEADER;
      break;
   case 'N':
      break;
   }
   rv = OKAY;
jfail:
   mx_fs_close(fp);
jleave:
   NYD2_OU;
   return rv;
}

FL enum okay
getcache(struct mailbox *mp, struct message *m, enum needspec need)
{
   enum okay rv;
   NYD_IN;

   rv = getcache1(mp, m, need, 0);
   NYD_OU;
   return rv;
}

FL void
putcache(struct mailbox *mp, struct message *m)
{
   char iob[32768], *name, ob;
   FILE *ibuf, *obuf;
   int c, oflag;
   long n, cnt, oldoffset, osize, otime, olines = -1;
   void *zp;
   NYD_IN;

   if ((mp->mb_type != MB_IMAP && mp->mb_type != MB_CACHE) || m->m_uid == 0 ||
         m->m_time == 0 || (m->m_flag & (MTOUCH|MFULLYCACHED)) == MFULLYCACHED)
      goto jleave;
   if (m->m_content_info & CI_HAVE_BODY)
      c = 'B';
   else if (m->m_content_info & CI_HAVE_HEADER)
      c = 'H';
   else if (!(m->m_content_info & CI_HAVE_MASK))
      c = 'N';
   else
      goto jleave;
   if ((oldoffset = ftell(mp->mb_itf)) < 0) /* XXX weird err hdling */
      oldoffset = 0;
   if((obuf = mx_fs_open(name = encuid(mp, m->m_uid), "r+")) == NIL){
      if((obuf = mx_fs_open(name, "w")) == NIL)
         goto jleave;
      mx_file_lock(fileno(obuf), mx_FILE_LOCK_TYPE_WRITE, 0,0, 0); /* XXX err*/
   }else{
      mx_file_lock(fileno(obuf), mx_FILE_LOCK_TYPE_READ, 0,0, 0); /* XXX err */
      if (fscanf(obuf, infofmt, &ob, (unsigned long*)&osize, &oflag,
            (unsigned long*)&otime, &olines) >= 4 && ob != '\0' &&
            (ob == 'B' || (ob == 'H' && c != 'B'))) {
         if (m->m_xlines <= 0 && olines > 0)
            m->m_xlines = olines;
         if ((c != 'N' && (uz)osize != m->m_xsize) ||
               oflag != (int)USEBITS(m->m_flag) || otime != m->m_time ||
               (m->m_xlines > 0 && olines != m->m_xlines)) {
            fflush(obuf);
            rewind(obuf);
            fprintf(obuf, infofmt, ob, (unsigned long)m->m_xsize,
               USEBITS(m->m_flag), (unsigned long)m->m_time, m->m_xlines);
            putc('\n', obuf);
         }
         mx_fs_close(obuf);
         goto jleave;
      }
      fflush(obuf);
      rewind(obuf);
      ftruncate(fileno(obuf), 0);
   }

   if((ibuf = setinput(mp, m, NEED_UNSPEC)) == NIL){
      mx_fs_close(obuf);
      goto jleave;
   }

   if (c == 'N')
      goto jdone;
   fseek(obuf, INITSKIP, SEEK_SET);
   zp = zalloc(obuf);
   cnt = m->m_size;
   while (cnt > 0) {
      n = (cnt > (long)sizeof iob) ? (long)sizeof iob : cnt;
      cnt -= n;
      if ((uz)n != fread(iob, 1, n, ibuf) ||
            n != (long)zwrite(zp, iob, n)) {
         unlink(name);
         zfree(zp);
         goto jout;
      }
   }
   if (zfree(zp) < 0) {
      unlink(name);
      goto jout;
   }
jdone:
   rewind(obuf);
   fprintf(obuf, infofmt, c, (unsigned long)m->m_xsize, USEBITS(m->m_flag),
         (unsigned long)m->m_time, m->m_xlines);
   putc('\n', obuf);
   if (ferror(obuf)) {
      unlink(name);
      goto jout;
   }
   if (c == 'B' && USEBITS(m->m_flag) == MREAD)
      m->m_flag |= MFULLYCACHED;

jout:
   if(!mx_fs_close(obuf)){
      m->m_flag &= ~MFULLYCACHED;
      unlink(name);
   }
   (void)fseek(mp->mb_itf, oldoffset, SEEK_SET);
jleave:
   NYD_OU;
}

FL void
initcache(struct mailbox *mp)
{
   char *name, *uvname;
   FILE *uvfp;
   u64 uv;
   struct cw cw;
   NYD_IN;

   if (mp->mb_cache_directory != NULL)
      n_free(mp->mb_cache_directory);
   mp->mb_cache_directory = NULL;
   if ((name = encname(mp, "", 1, NULL)) == NULL)
      goto jleave;
   mp->mb_cache_directory = su_cs_dup(name, 0);
   if ((uvname = encname(mp, "UIDVALIDITY", 1, NULL)) == NULL)
      goto jleave;
   if (cwget(&cw) == STOP)
      goto jleave;

   if((uvfp = mx_fs_open(uvname, "r+")) == NIL ||
         (mx_file_lock(fileno(uvfp), mx_FILE_LOCK_TYPE_READ, 0,0, 0), 0) ||
         fscanf(uvfp, "%" PRIu64 , &uv) != 1 || uv != mp->mb_uidvalidity) {
      if ((uvfp = clean(mp, &cw)) == NULL)
         goto jout;
   } else {
      fflush(uvfp);
      rewind(uvfp);
   }

   mx_file_lock(fileno(uvfp), mx_FILE_LOCK_TYPE_WRITE, 0,0, 0);
   fprintf(uvfp, "%" PRIu64 "\n", mp->mb_uidvalidity);

   /* C99 */{
      int x;

      x = ferror(uvfp);

      if(!mx_fs_close(uvfp) || x){
         unlink(uvname);
         mp->mb_uidvalidity = 0;
      }
   }

jout:
   cwrelse(&cw);
jleave:
   NYD_OU;
}

FL void
purgecache(struct mailbox *mp, struct message *m, long mc)
{
   char *name;
   struct cw cw;
   NYD_IN;

   if ((name = encname(mp, "", 1, NULL)) == NULL)
      goto jleave;
   if (cwget(&cw) == STOP)
      goto jleave;
   purge(mp, m, mc, &cw, name);
   cwrelse(&cw);
jleave:
   NYD_OU;
}

static FILE *
clean(struct mailbox *mp, struct cw *cw)
{
   char *cachedir, *eaccount, *buf;
   char const *emailbox;
   int bufsz;
   DIR *dirp;
   struct dirent *dp;
   FILE *fp = NULL;
   NYD_IN;

   if ((cachedir = ok_vlook(imap_cache)) == NULL ||
         (cachedir = fexpand(cachedir, FEXP_LOCAL | FEXP_NOPROTO)) == NULL)
      goto jleave;
   eaccount = mx_url_xenc(mp->mb_imap_account, TRU1);
   if (su_cs_cmp_case(emailbox = mp->mb_imap_mailbox, "INBOX")) {
      boole err;

      emailbox = imap_path_encode(emailbox, &err);
      if(err)
         goto jleave;
      emailbox = mx_url_xenc(emailbox, TRU1);
   }
   buf = n_autorec_alloc(bufsz = su_cs_len(cachedir) + su_cs_len(eaccount) +
         su_cs_len(emailbox) + 40);
   if (!n_path_mkdir(cachedir))
      goto jleave;
   snprintf(buf, bufsz, "%s/README", cachedir);
   if((fp = mx_fs_open(buf, "wx")) != NIL){
      fputs(README1, fp);
      fputs(README2, fp);
      fputs(README3, fp);
      fputs(README4, fp);
      mx_fs_close(fp);
   }
   fp = NULL;
   snprintf(buf, bufsz, "%s/%s/%s", cachedir, eaccount, emailbox);
   if (!n_path_mkdir(buf))
      goto jleave;
   if (chdir(buf) < 0)
      goto jleave;
   if ((dirp = opendir(".")) == NULL)
      goto jout;
   while ((dp = readdir(dirp)) != NULL) {
      if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' ||
            (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
         continue;
      unlink(dp->d_name);
   }
   closedir(dirp);
   fp = mx_fs_open("UIDVALIDITY", "w");
jout:
   if (cwret(cw) == STOP) {
      n_err(_("Fatal: Cannot change back to current directory.\n"));
      abort();
   }
jleave:
   NYD_OU;
   return fp;
}

static u64 *
builds(long *contentelem)
{
   u64 n, *contents = NULL;
   long contentalloc = 0;
   char const *x;
   DIR *dirp;
   struct dirent *dp;
   NYD_IN;

   *contentelem = 0;
   if ((dirp = opendir(".")) == NULL)
      goto jleave;
   while ((dp = readdir(dirp)) != NULL) {
      if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' ||
            (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
         continue;

      su_idec_u64_cp(&n, dp->d_name, 10, &x);/* TODO errors? */
      if (*x != '\0')
         continue;
      if (*contentelem >= contentalloc - 1)
         contents = n_realloc(contents,
               (contentalloc += 200) * sizeof *contents);
      contents[(*contentelem)++] = n;
   }
   closedir(dirp);
   if (*contentelem > 0) {
      contents[*contentelem] = 0;
      qsort(contents, *contentelem, sizeof *contents, longlt);
   }
jleave:
   NYD_OU;
   return contents;
}

static void
purge(struct mailbox *mp, struct message *m, long mc, struct cw *cw,
   const char *name)
{
   u64 *contents;
   long i, j, contentelem;
   NYD_IN;
   UNUSED(mp);

   if (chdir(name) < 0)
      goto jleave;
   contents = builds(&contentelem);
   if (contents != NULL) {
      i = j = 0;
      while (j < contentelem) {
         if (i < mc && m[i].m_uid == contents[j]) {
            i++;
            j++;
         } else if (i < mc && m[i].m_uid < contents[j])
            i++;
         else
            remve(contents[j++]);
      }
      n_free(contents);
   }
   if (cwret(cw) == STOP) {
      n_err(_("Fatal: Cannot change back to current directory.\n"));
      abort();
   }
jleave:
   NYD_OU;
}

static int
longlt(const void *a, const void *b)
{
   union {long l; int i;} u;
   NYD_IN;

   u.l = *(long const*)a - *(long const*)b;
   u.i = (u.l < 0) ? -1 : ((u.l > 0) ? 1 : 0);
   NYD_OU;
   return u.i;
}

static void
remve(unsigned long n)
{
   char buf[30];
   NYD_IN;

   snprintf(buf, sizeof buf, "%lu", n);
   unlink(buf);
   NYD_OU;
}

FL void
delcache(struct mailbox *mp, struct message *m)
{
   char *fn;
   NYD_IN;

   fn = encuid(mp, m->m_uid);
   if (fn && unlink(fn) == 0)
      m->m_flag |= MUNLINKED;
   NYD_OU;
}

FL enum okay
cache_setptr(enum fedit_mode fm, int transparent)
{
   struct cw cw;
   int i, omsgCount = 0;
   char *name;
   u64 *contents;
   long contentelem;
   struct message *omessage;
   enum okay rv = STOP;
   NYD_IN;

   omessage = message;
   omsgCount = msgCount;

   if (mb.mb_cache_directory != NULL) {
      n_free(mb.mb_cache_directory);
      mb.mb_cache_directory = NULL;
   }
   if ((name = encname(&mb, "", 1, NULL)) == NULL)
      goto jleave;
   mb.mb_cache_directory = su_cs_dup(name, 0);
   if (cwget(&cw) == STOP)
      goto jleave;
   if (chdir(name) < 0)
      goto jleave;
   contents = builds(&contentelem);
   msgCount = contentelem;
   message = n_calloc(msgCount + 1, sizeof *message);
   if (cwret(&cw) == STOP) {
      n_err(_("Fatal: Cannot change back to current directory.\n"));
      abort();
   }
   cwrelse(&cw);

   srelax_hold();
   for (i = 0; i < msgCount; i++) {
      message[i].m_uid = contents[i];
      getcache1(&mb, &message[i], NEED_UNSPEC, 3);
      srelax();
   }
   srelax_rele();

   if (contents != NULL)
      n_free(contents);
   mb.mb_type = MB_CACHE;
   mb.mb_perm = ((n_poption & n_PO_R_FLAG) || (fm & FEDIT_RDONLY)
         ) ? 0 : MB_DELE;
   if(omessage != NULL){
      if(transparent)
         /* This frees the message */
         transflags(omessage, omsgCount, 1);
      else
         n_free(omessage);
   }
   setdot(message);
   rv = OKAY;
jleave:
   NYD_OU;
   return rv;
}

FL enum okay
cache_list(struct mailbox *mp, const char *base, int strip, FILE *fp)
{
   char *name, *cachedir, *eaccount;
   DIR *dirp;
   struct dirent *dp;
   const char *cp, *bp, *cp2;
   int namesz;
   enum okay rv = STOP;
   NYD_IN;

   if ((cachedir = ok_vlook(imap_cache)) == NULL ||
         (cachedir = fexpand(cachedir, FEXP_LOCAL | FEXP_NOPROTO)) == NULL)
      goto jleave;
   eaccount = mx_url_xenc(mp->mb_imap_account, TRU1);
   name = n_autorec_alloc(namesz = su_cs_len(cachedir) +
         su_cs_len(eaccount) + 2);
   snprintf(name, namesz, "%s/%s", cachedir, eaccount);
   if ((dirp = opendir(name)) == NULL)
      goto jleave;
   while ((dp = readdir(dirp)) != NULL) {
      if (dp->d_name[0] == '.')
         continue;
      cp = cp2 = imap_path_decode(mx_url_xdec(dp->d_name), NULL);
      for (bp = base; *bp && *bp == *cp2; bp++)
         cp2++;
      if (*bp)
         continue;
      cp = strip ? cp2 : cp;
      fprintf(fp, "%s\n", *cp ? cp : "INBOX");
   }
   closedir(dirp);
   rv = OKAY;
jleave:
   NYD_OU;
   return rv;
}

FL enum okay
cache_remove(const char *name)
{
   struct stat st;
   DIR *dirp;
   struct dirent *dp;
   char *path, *dir;
   int pathsize, pathend, n;
   enum okay rv = OKAY;
   NYD_IN;

   if ((dir = encname(&mb, "", 0, imap_fileof(name))) == NULL)
      goto jleave;
   pathend = su_cs_len(dir);
   path = n_alloc(pathsize = pathend + 30);
   su_mem_copy(path, dir, pathend);
   path[pathend++] = '/';
   path[pathend] = '\0';
   if ((dirp = opendir(path)) == NULL) {
      n_free(path);
      goto jleave;
   }
   while ((dp = readdir(dirp)) != NULL) {
      if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' ||
            (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
         continue;
      n = su_cs_len(dp->d_name) + 1;
      if (pathend + n > pathsize)
         path = n_realloc(path, pathsize = pathend + n + 30);
      su_mem_copy(path + pathend, dp->d_name, n);
      if (stat(path, &st) < 0 || (st.st_mode & S_IFMT) != S_IFREG)
         continue;
      if (unlink(path) < 0) {
         n_perr(path, 0);
         closedir(dirp);
         n_free(path);
         rv = STOP;
         goto jleave;
      }
   }
   closedir(dirp);
   path[pathend] = '\0';
   rmdir(path);   /* no error on failure, might contain submailboxes */
   n_free(path);
jleave:
   NYD_OU;
   return rv;
}

FL enum okay
cache_rename(const char *old, const char *new)
{
   char *olddir, *newdir;
   enum okay rv = OKAY;
   NYD_IN;

   if ((olddir = encname(&mb, "", 0, imap_fileof(old))) == NULL ||
         (newdir = encname(&mb, "",0, imap_fileof(new))) == NULL)
      goto jleave;
   if (rename(olddir, newdir) < 0) {
      n_perr(olddir, 0);
      rv = STOP;
   }
jleave:
   NYD_OU;
   return rv;
}

FL u64
cached_uidvalidity(struct mailbox *mp)
{
   FILE *uvfp;
   char *uvname;
   u64 uv;
   NYD_IN;

   if ((uvname = encname(mp, "UIDVALIDITY", 1, NULL)) == NULL) {
      uv = 0;
      goto jleave;
   }
   if((uvfp = mx_fs_open(uvname, "r")) == NIL ||
         (mx_file_lock(fileno(uvfp), mx_FILE_LOCK_TYPE_READ, 0,0, 0), 0) ||
         fscanf(uvfp, "%" PRIu64, &uv) != 1)
      uv = 0;
   if(uvfp != NIL)
      mx_fs_close(uvfp);
jleave:
   NYD_OU;
   return uv;
}

static FILE *
cache_queue1(struct mailbox *mp, char const *mode, char **xname)
{
   char *name;
   FILE *fp = NULL;
   NYD_IN;

   if ((name = encname(mp, "QUEUE", 0, NULL)) == NULL)
      goto jleave;
   if((fp = mx_fs_open(name, mode)) != NIL)
      mx_file_lock(fileno(fp), mx_FILE_LOCK_TYPE_WRITE, 0,0, 0);
   if (xname)
      *xname = name;
jleave:
   NYD_OU;
   return fp;
}

FL FILE *
cache_queue(struct mailbox *mp)
{
   FILE *fp;
   NYD_IN;

   fp = cache_queue1(mp, "a", NULL);
   if (fp == NULL)
      n_err(_("Cannot queue IMAP command. Retry when online.\n"));
   NYD_OU;
   return fp;
}

FL enum okay
cache_dequeue(struct mailbox *mp)
{
   int bufsz;
   char *cachedir, *eaccount, *buf, *oldbox;
   DIR *dirp;
   struct dirent *dp;
   enum okay rv = OKAY;
   NYD_IN;

   if ((cachedir = ok_vlook(imap_cache)) == NULL ||
         (cachedir = fexpand(cachedir, FEXP_LOCAL | FEXP_NOPROTO)) == NULL)
      goto jleave;
   eaccount = mx_url_xenc(mp->mb_imap_account, TRU1);
   buf = n_autorec_alloc(bufsz = su_cs_len(cachedir) +
         su_cs_len(eaccount) + 2);
   snprintf(buf, bufsz, "%s/%s", cachedir, eaccount);
   if ((dirp = opendir(buf)) == NULL)
      goto jleave;
   oldbox = mp->mb_imap_mailbox;
   while ((dp = readdir(dirp)) != NULL) {
      if (dp->d_name[0] == '.')
         continue;
      /* FIXME MUST BLOCK SIGNALS IN ORDER TO ENSURE PROPER RESTORE!
       * (but wuuuuh, what a shit!) */
      mp->mb_imap_mailbox = su_cs_dup(
            imap_path_decode(mx_url_xdec(dp->d_name), NULL), 0);
      dequeue1(mp);
      {  char *x = mp->mb_imap_mailbox;
         mp->mb_imap_mailbox = oldbox;
         n_free(x);
      }
   }
   closedir(dirp);
jleave:
   NYD_OU;
   return rv;
}

static enum okay
dequeue1(struct mailbox *mp)
{
   FILE *fp = NULL, *uvfp = NULL;
   char *qname, *uvname;
   u64 uv;
   off_t is_size;
   int is_count;
   enum okay rv = OKAY;
   NYD_IN;

   fp = cache_queue1(mp, "r+", &qname);
   if (fp != NULL && fsize(fp) > 0) {
      if (imap_select(mp, &is_size, &is_count, mp->mb_imap_mailbox, FEDIT_NONE)
            != OKAY) {
         n_err(_("Cannot select \"%s\" for dequeuing.\n"), mp->mb_imap_mailbox);
         goto jsave;
      }
      if ((uvname = encname(mp, "UIDVALIDITY", 0, NULL)) == NULL ||
            (uvfp = mx_fs_open(uvname, "r")) == NIL ||
            (mx_file_lock(fileno(uvfp), mx_FILE_LOCK_TYPE_READ, 0,0, 0), 0) ||
            fscanf(uvfp, "%" PRIu64, &uv) != 1 || uv != mp->mb_uidvalidity) {
         n_err(_("Unique identifiers for \"%s\" are out of date. "
            "Cannot commit IMAP commands.\n"), mp->mb_imap_mailbox);
jsave:
         n_err(_("Saving IMAP commands to *DEAD*\n"));
         savedeadletter(fp, 0);
         ftruncate(fileno(fp), 0);
         mx_fs_close(fp);
         if(uvfp != NIL)
            mx_fs_close(uvfp);
         rv = STOP;
         goto jleave;
      }
      mx_fs_close(uvfp);
      printf("Committing IMAP commands for \"%s\"\n", mp->mb_imap_mailbox);
      imap_dequeue(mp, fp);
   }

   if(fp != NIL){
      mx_fs_close(fp);
      unlink(qname);
   }
jleave:
   NYD_OU;
   return rv;
}

#include "su/code-ou.h"
#endif /* mx_HAVE_IMAP */
/* s-it-mode */
s-nail-14.9.15/src/mx/obs-imap.c000066400000000000000000003621171352610246600161630ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ IMAP v4r1 client following RFC 2060.
 *@ TODO Anything. SASL-IR for more.
 *
 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: BSD-4-Clause
 */
/*
 * Copyright (c) 2004
 * Gunnar Ritter.  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 Gunnar Ritter
 *    and his contributors.
 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER 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 GUNNAR RITTER 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.
 */
#undef su_FILE
#define su_FILE obs_imap
#define mx_SOURCE
#define mx_SOURCE_NET_IMAP

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

su_EMPTY_FILE()
#ifdef mx_HAVE_IMAP
#include 

#include 
#ifdef mx_HAVE_ARPA_INET_H
# include 
#endif
#include 

#include 
#include 
#include 
#include 

#include "mx/cred-auth.h"
#include "mx/cred-md5.h"
#include "mx/iconv.h"
#include "mx/file-streams.h"
#include "mx/sigs.h"
#include "mx/net-socket.h"
#include "mx/ui-str.h"

#ifdef mx_HAVE_GSSAPI
# include "mx/net-gssapi.h" /* $(MX_SRCDIR) */
#endif

/* TODO fake */
#include "su/code-in.h"

#define IMAP_ANSWER() \
{\
   if (mp->mb_type != MB_CACHE) {\
      enum okay ok = OKAY;\
      while (mp->mb_active & MB_COMD)\
         ok = imap_answer(mp, 1);\
      if (ok == STOP)\
         return STOP;\
   }\
}

/* TODO IMAP_OUT() simply returns instead of doing "actioN" if imap_finish()
 * TODO fails, which leaves behind leaks in, e.g., imap_append1()!
 * TODO IMAP_XOUT() was added due to this, but (1) needs to be used everywhere
 * TODO and (2) doesn't handle all I/O errors itself, yet, too.
 * TODO I.e., that should be a function, not a macro ... or so.
 * TODO This entire module needs MASSIVE work! */
#define IMAP_OUT(X,Y,ACTION)  IMAP_XOUT(X, Y, ACTION, return STOP)
#define IMAP_XOUT(X,Y,ACTIONERR,ACTIONBAIL) \
{\
   if (mp->mb_type != MB_CACHE) {\
      if (imap_finish(mp) == STOP) {\
         ACTIONBAIL;\
      }\
      if (n_poption & n_PO_D_VV)\
         n_err(">>> %s", X);\
      mp->mb_active |= Y;\
      if (mx_socket_write(mp->mb_sock, X) == STOP) {\
         ACTIONERR;\
      }\
   } else {\
      if (queuefp != NULL)\
         fputs(X, queuefp);\
   }\
}

static struct record {
   struct record  *rec_next;
   unsigned long  rec_count;
   enum rec_type {
      REC_EXISTS,
      REC_EXPUNGE
   }              rec_type;
} *record, *recend;

static enum {
   RESPONSE_TAGGED,
   RESPONSE_DATA,
   RESPONSE_FATAL,
   RESPONSE_CONT,
   RESPONSE_ILLEGAL
} response_type;

static enum {
   RESPONSE_OK,
   RESPONSE_NO,
   RESPONSE_BAD,
   RESPONSE_PREAUTH,
   RESPONSE_BYE,
   RESPONSE_OTHER,
   RESPONSE_UNKNOWN
} response_status;

static char *responded_tag;
static char *responded_text;
static char *responded_other_text;
static long responded_other_number;

static enum {
   MAILBOX_DATA_FLAGS,
   MAILBOX_DATA_LIST,
   MAILBOX_DATA_LSUB,
   MAILBOX_DATA_MAILBOX,
   MAILBOX_DATA_SEARCH,
   MAILBOX_DATA_STATUS,
   MAILBOX_DATA_EXISTS,
   MAILBOX_DATA_RECENT,
   MESSAGE_DATA_EXPUNGE,
   MESSAGE_DATA_FETCH,
   CAPABILITY_DATA,
   RESPONSE_OTHER_UNKNOWN
} response_other;

static enum list_attributes {
   LIST_NONE         = 000,
   LIST_NOINFERIORS  = 001,
   LIST_NOSELECT     = 002,
   LIST_MARKED       = 004,
   LIST_UNMARKED     = 010
} list_attributes;

static int  list_hierarchy_delimiter;
static char *list_name;

struct list_item {
   struct list_item     *l_next;
   char                 *l_name;
   char                 *l_base;
   enum list_attributes l_attr;
   int                  l_delim;
   int                  l_level;
   int                  l_has_children;
};

static char             *imapbuf;   /* TODO not static, use pool */
static uz           imapbufsize;
static sigjmp_buf       imapjmp;
static n_sighdl_t  savealrm;
static int              imapkeepalive;
static long             had_exists = -1;
static long             had_expunge = -1;
static long             expunged_messages;
static int volatile     imaplock;
static int              same_imap_account;
static boole           _imap_rdonly;

static char *imap_quotestr(char const *s);
static char *imap_unquotestr(char const *s);
static void imap_delim_init(struct mailbox *mp, struct mx_url const *urlp);
static char const *a_imap_path_normalize(struct mailbox *mp, char const *cp,
      boole look_delim); /* for `imapcodec' only! */
/* Returns NULL on error */
static char *imap_path_quote(struct mailbox *mp, char const *cp);
static void       imap_other_get(char *pp);
static void       imap_response_get(const char **cp);
static void       imap_response_parse(void);
static enum okay  imap_answer(struct mailbox *mp, int errprnt);
static enum okay  imap_parse_list(void);
static enum okay  imap_finish(struct mailbox *mp);
static void       imap_timer_off(void);
static void       imapcatch(int s);
static void       _imap_maincatch(int s);
static enum okay  imap_noop1(struct mailbox *mp);
static void       rec_queue(enum rec_type type, unsigned long cnt);
static enum okay  rec_dequeue(void);
static void       rec_rmqueue(void);
static void       imapalarm(int s);
static enum okay  imap_preauth(struct mailbox *mp, struct mx_url *urlp,
      struct mx_cred_ctx *ccred);
static enum okay  imap_capability(struct mailbox *mp);
static enum okay a_imap_auth(struct mailbox *mp, struct mx_url *urlp,
      struct mx_cred_ctx *ccredp);
#ifdef mx_HAVE_MD5
static enum okay  imap_cram_md5(struct mailbox *mp,
      struct mx_cred_ctx *ccred);
#endif
static enum okay  imap_login(struct mailbox *mp, struct mx_cred_ctx *ccred);
static enum okay a_imap_oauthbearer(struct mailbox *mp,
      struct mx_cred_ctx *ccp);
static enum okay a_imap_external(struct mailbox *mp, struct mx_cred_ctx *ccp);
static enum okay  imap_flags(struct mailbox *mp, unsigned X, unsigned Y);
static void       imap_init(struct mailbox *mp, int n);
static void       imap_setptr(struct mailbox *mp, int nmail, int transparent,
                     int *prevcount);
static boole     _imap_getcred(struct mailbox *mbp, struct mx_cred_ctx *ccredp,
                     struct mx_url *urlp);
static int _imap_setfile1(char const *who, struct mx_url *urlp,
            enum fedit_mode fm, int transparent);
static int        imap_fetchdata(struct mailbox *mp, struct message *m,
                     uz expected, int need, const char *head,
                     uz headsize, long headlines);
static void       imap_putstr(struct mailbox *mp, struct message *m,
                     const char *str, const char *head, uz headsize,
                     long headlines);
static enum okay  imap_get(struct mailbox *mp, struct message *m,
                     enum needspec need);
static void       commitmsg(struct mailbox *mp, struct message *to,
                     struct message *from, enum content_info content_info);
static enum okay  imap_fetchheaders(struct mailbox *mp, struct message *m,
                     int bot, int top);
static enum okay  imap_exit(struct mailbox *mp);
static enum okay  imap_delete(struct mailbox *mp, int n, struct message *m,
                     int needstat);
static enum okay  imap_close(struct mailbox *mp);
static enum okay  imap_update(struct mailbox *mp);
static enum okay  imap_store(struct mailbox *mp, struct message *m, int n,
                     int c, const char *sp, int needstat);
static enum okay  imap_unstore(struct message *m, int n, const char *flag);
static const char *tag(int new);
static char *     imap_putflags(int f);
static void       imap_getflags(const char *cp, char const **xp, enum mflag *f);
static enum okay  imap_append1(struct mailbox *mp, const char *name, FILE *fp,
                     off_t off1, long xsize, enum mflag flag, time_t t);
static enum okay  imap_append0(struct mailbox *mp, const char *name, FILE *fp,
                     long offset);
static enum okay  imap_list1(struct mailbox *mp, const char *base,
                     struct list_item **list, struct list_item **lend,
                     int level);
static enum okay  imap_list(struct mailbox *mp, const char *base, int strip,
                     FILE *fp);
static enum okay  imap_copy1(struct mailbox *mp, struct message *m, int n,
                     const char *name);
static enum okay  imap_copyuid_parse(const char *cp,
                     u64 *uidvalidity, u64 *olduid, u64 *newuid);
static enum okay  imap_appenduid_parse(const char *cp,
                     u64 *uidvalidity, u64 *uid);
static enum okay  imap_copyuid(struct mailbox *mp, struct message *m,
                     const char *name);
static enum okay  imap_appenduid(struct mailbox *mp, FILE *fp, time_t t,
                     long off1, long xsize, long size, long lines, int flag,
                     const char *name);
static enum okay  imap_appenduid_cached(struct mailbox *mp, FILE *fp);
#ifdef mx_HAVE_IMAP_SEARCH
static sz    imap_search2(struct mailbox *mp, struct message *m, int cnt,
                     const char *spec, int f);
#endif
static enum okay  imap_remove1(struct mailbox *mp, const char *name);
static enum okay  imap_rename1(struct mailbox *mp, const char *old,
                     const char *new);
static char *     imap_strex(char const *cp, char const **xp);
static enum okay  check_expunged(void);

#ifdef mx_HAVE_GSSAPI
# include 
#endif

static char *
imap_quotestr(char const *s)
{
   char *n, *np;
   NYD2_IN;

   np = n = n_autorec_alloc(2 * su_cs_len(s) + 3);
   *np++ = '"';
   while (*s) {
      if (*s == '"' || *s == '\\')
         *np++ = '\\';
      *np++ = *s++;
   }
   *np++ = '"';
   *np = '\0';
   NYD2_OU;
   return n;
}

static char *
imap_unquotestr(char const *s)
{
   char *n, *np;
   NYD2_IN;

   if (*s != '"') {
      n = savestr(s);
      goto jleave;
   }

   np = n = n_autorec_alloc(su_cs_len(s) + 1);
   while (*++s) {
      if (*s == '\\')
         s++;
      else if (*s == '"')
         break;
      *np++ = *s;
   }
   *np = '\0';
jleave:
   NYD2_OU;
   return n;
}

static void
imap_delim_init(struct mailbox *mp, struct mx_url const *urlp){
   uz i;
   char const *cp;
   NYD2_IN;

   mp->mb_imap_delim[0] = '\0';

   if((cp = xok_vlook(imap_delim, urlp, OXM_ALL)) != NULL){
      i = su_cs_len(cp);

      if(i == 0){
         cp = n_IMAP_DELIM;
         i = sizeof(n_IMAP_DELIM) -1;
         goto jcopy;
      }

      if(i < NELEM(mp->mb_imap_delim))
jcopy:
         su_mem_copy(&mb.mb_imap_delim[0], cp, i +1);
      else
         n_err(_("*imap-delim* for %s is too long: %s\n"),
            urlp->url_input, cp);
   }
   NYD2_OU;
}

static char const *
a_imap_path_normalize(struct mailbox *mp, char const *cp, boole look_delim){
   char const *dcp;
   boole dosrch;
   char dc, *rv_base, *rv, c, lc;
   NYD2_IN;
   ASSERT(mp == NIL || !look_delim);
   ASSERT(!look_delim || mp == NIL);

   /* Unless we operate in free fly, honour a non-set *imap-delim* to mean
    * "use exactly what i have specified" */
   if(mp == NIL){
      if(look_delim && (dcp = ok_vlook(imap_delim)) != NIL)
         dc = *dcp;
      else{
         dcp = n_IMAP_DELIM;
         dc = '\0';
      }
   }else if((dc = (dcp = mp->mb_imap_delim)[0]) == '\0')
      dcp = n_IMAP_DELIM;

   dosrch = (dcp[1] != '\0');

   /* Plain names don't need path quoting */
   /* C99 */{
      uz i, j;
      char const *cpx;

      for(cpx = cp;; ++cpx)
         if((c = *cpx) == '\0')
            goto jleave;
         /* Without *imap-delim*, use the first separator discovered in path */
         else if(dc == '\0'){
            ASSERT(!su_cs_cmp(dcp, n_IMAP_DELIM));
            if(su_cs_find_c(dcp, c)){
               dc = c;
               break;
            }
         }else if(c == dc)
            break;
         else if(dosrch && su_cs_find_c(dcp, c) != NIL)
            break;

      /* And we don't need to reevaluate what we have seen yet */
      i = P2UZ(cpx - cp);
      rv = rv_base = n_autorec_alloc(i + (j = su_cs_len(cpx) +1));
      if(i > 0)
         su_mem_copy(rv, cp, i);
      su_mem_copy(&rv[i], cpx, j);
      rv += i;
      cp = cpx;
   }

   /* Squeeze adjacent delimiters, convert remain to dc */
   for(lc = '\0'; (c = *cp++) != '\0'; lc = c){
      if(c != dc && dosrch && su_cs_find_c(dcp, c) != NIL)
         c = dc;
      if(c != dc || lc != dc)
         *rv++ = c;
   }
   *rv = '\0';

   cp = rv_base;
jleave:
   NYD2_OU;
   return cp;
}

#ifdef mx_HAVE_GSSAPI
# include 
#endif

FL char const *
imap_path_encode(char const *cp, boole *err_or_null){
   /* To a large extend inspired by dovecot(1) */
   struct str out;
   boole err_def;
   u8 *be16p_base, *be16p;
   char const *emsg;
   char c;
   uz l, l_plain;
   NYD2_IN;

   if(err_or_null == NULL)
      err_or_null = &err_def;
   *err_or_null = FAL0;

   /* Is this a string that works out as "plain US-ASCII"? */
   for(l = 0;; ++l)
      if((c = cp[l]) == '\0')
         goto jleave;
      else if(c <= 0x1F || c >= 0x7F || c == '&')
         break;

   *err_or_null = TRU1;

   /* We need to encode in mUTF-7!  For that, we first have to convert the
    * local charset to UTF-8, then convert all characters which need to be
    * encoded (except plain "&") to UTF-16BE first, then that to mUTF-7.
    * We can skip the UTF-8 conversion occasionally, however */
#if (defined mx_HAVE_DEVEL || !defined mx_HAVE_ALWAYS_UNICODE_LOCALE) &&\
      defined mx_HAVE_ICONV
   if(!(n_psonce & n_PSO_UNICODE)){
      char const *x;

      emsg = N_("iconv(3) from locale charset to UTF-8 failed");
      if((x = n_iconv_onetime_cp(n_ICONV_NONE, "utf-8", ok_vlook(ttycharset),
            cp)) == NULL)
         goto jerr;
      cp = x;

      /* So: Why not start all over again?
       * Is this a string that works out as "plain US-ASCII"? */
      for(l = 0;; ++l)
         if((c = cp[l]) == '\0')
            goto jleave;
         else if(c <= 0x1F || c >= 0x7F || c == '&')
            break;
   }
#endif

   /* We need to encode, save what we have, encode the rest */
   l_plain = l;

   for(cp += l, l = 0; cp[l] != '\0'; ++l)
      ;
   be16p_base = n_autorec_alloc((l << 1) +1); /* XXX use n_string, resize */

   out.s = n_autorec_alloc(l_plain + (l << 2) +1); /* XXX use n_string.. */
   if(l_plain > 0)
      su_mem_copy(out.s, &cp[-l_plain], out.l = l_plain);
   else
      out.l = 0;
   su_DBG( l_plain += (l << 2); )

   while(l > 0){
      c = *cp++;
      --l;

      if(c == '&'){
         out.s[out.l + 0] = '&';
         out.s[out.l + 1] = '-';
         out.l += 2;
      }else if(c > 0x1F && c < 0x7F)
         out.s[out.l++] = c;
      else{
         static char const mb64ct[] =
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
         u32 utf32;

         /* Convert consecutive non-representables */
         emsg = N_("Invalid UTF-8 sequence, cannot convert to UTF-32");

         for(be16p = be16p_base, --cp, ++l;;){
            if((utf32 = su_utf8_to_32(&cp, &l)) == U32_MAX)
               goto jerr;

            /* TODO S-CText: magic utf16 conversions */
            if(utf32 < 0x10000){
               be16p[1] = utf32 & 0xFF;
               be16p[0] = (utf32 >>= 8, utf32 &= 0xFF);
               be16p += 2;
            }else{
               u16 s7e;

               utf32 -= 0x10000;
               s7e = 0xD800u | (utf32 >> 10);
               be16p[1] = s7e & 0xFF;
               be16p[0] = (s7e >>= 8, s7e &= 0xFF);
               s7e = 0xDC00u | (utf32 &= 0x03FF);
               be16p[3] = s7e & 0xFF;
               be16p[2] = (s7e >>= 8, s7e &= 0xFF);
               be16p += 4;
            }

            if(l == 0)
               break;
            if((c = *cp) > 0x1F && c < 0x7F)
               break;
         }

         /* And then warp that UTF-16BE to mUTF-7 */
         out.s[out.l++] = '&';
         utf32 = (u32)P2UZ(be16p - be16p_base);
         be16p = be16p_base;

         for(; utf32 >= 3; be16p += 3, utf32 -= 3){
            out.s[out.l+0] = mb64ct[                            be16p[0] >> 2 ];
            out.s[out.l+1] = mb64ct[((be16p[0] & 0x03) << 4) | (be16p[1] >> 4)];
            out.s[out.l+2] = mb64ct[((be16p[1] & 0x0F) << 2) | (be16p[2] >> 6)];
            out.s[out.l+3] = mb64ct[  be16p[2] & 0x3F];
            out.l += 4;
         }
         if(utf32 > 0){
            out.s[out.l + 0] = mb64ct[be16p[0] >> 2];
            if(--utf32 == 0){
               out.s[out.l + 1] = mb64ct[ (be16p[0] & 0x03) << 4];
               out.l += 2;
            }else{
               out.s[out.l + 1] = mb64ct[((be16p[0] & 0x03) << 4) |
                     (be16p[1] >> 4)];
               out.s[out.l + 2] = mb64ct[ (be16p[1] & 0x0F) << 2];
               out.l += 3;
            }
         }
         out.s[out.l++] = '-';
      }
   }
   out.s[out.l] = '\0';
   ASSERT(out.l <= l_plain);
   *err_or_null = FAL0;
   cp = out.s;
jleave:
   NYD2_OU;
   return cp;
jerr:
   n_err(_("Cannot encode IMAP path %s\n  %s\n"), cp, V_(emsg));
   goto jleave;
}

FL char *
imap_path_decode(char const *path, boole *err_or_null){
   /* To a large extend inspired by dovecot(1) TODO use string */
   boole err_def;
   u8 *mb64p_base, *mb64p, *mb64xp;
   char const *emsg, *cp;
   char *rv_base, *rv, c;
   uz l_orig, l, i;
   NYD2_IN;

   if(err_or_null == NULL)
      err_or_null = &err_def;
   *err_or_null = FAL0;

   l = l_orig = su_cs_len(path);
   rv = rv_base = n_autorec_alloc(l << 1);
   su_mem_copy(rv, path, l +1);

   /* xxx Don't check for invalid characters from malicious servers */
   if(l == 0 || (cp = su_mem_find(path, '&', l)) == NULL)
      goto jleave;

   *err_or_null = TRU1;

   emsg = N_("Invalid mUTF-7 encoding");
   i = P2UZ(cp - path);
   rv += i;
   l -= i;
   mb64p_base = NULL;

   while(l > 0){
      if((c = *cp) != '&'){
         if(c <= 0x1F || c >= 0x7F){
            emsg = N_("Invalid mUTF-7: unencoded control or 8-bit byte");
            goto jerr;
         }
         *rv++ = c;
         ++cp;
         --l;
      }else if(--l == 0)
         goto jeincpl;
      else if(*++cp == '-'){
         *rv++ = '&';
         ++cp;
         --l;
      }else if(l < 3){
jeincpl:
         emsg = N_("Invalid mUTF-7: incomplete input");
         goto jerr;
      }else{
         /* mUTF-7 -> UTF-16BE -> UTF-8 */
         static u8 const mb64dt[256] = {
#undef XX
#define XX 0xFFu
            XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
            XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
            XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,62, 63,XX,XX,XX,
            52,53,54,55, 56,57,58,59, 60,61,XX,XX, XX,XX,XX,XX,
            XX, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
            15,16,17,18, 19,20,21,22, 23,24,25,XX, XX,XX,XX,XX,
            XX,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
            41,42,43,44, 45,46,47,48, 49,50,51,XX, XX,XX,XX,XX,
            XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
            XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
            XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
            XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
            XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
            XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
            XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
            XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX
         };

         if(mb64p_base == NULL)
            mb64p_base = n_autorec_alloc(l);

         /* Decode the mUTF-7 to what is indeed UTF-16BE */
         for(mb64p = mb64p_base;;){
            ASSERT(l >= 3);
            if((mb64p[0] = mb64dt[(u8)cp[0]]) == XX ||
                  (mb64p[1] = mb64dt[(u8)cp[1]]) == XX)
               goto jerr;
            mb64p += 2;

            c = cp[2];
            cp += 3;
            l -= 3;
            if(c == '-')
               break;
            if((*mb64p++ = mb64dt[(u8)c]) == XX)
               goto jerr;

            if(l == 0)
               goto jerr;
            --l;
            if((c = *cp++) == '-')
               break;
            if((*mb64p++ = mb64dt[(u8)c]) == XX)
               goto jerr;

            if(l < 3){
               if(l > 0 && *cp == '-'){
                  --l;
                  ++cp;
                  break;
               }
               goto jerr;
            }
         }
#undef XX

         if(l >= 2 && cp[0] == '&' && cp[1] != '-'){
            emsg = N_("Invalid mUTF-7, consecutive encoded sequences");
            goto jerr;
         }

         /* Yet halfway decoded mUTF-7, go remaining way to gain UTF-16BE */
         i = P2UZ(mb64p - mb64p_base);
         mb64p = mb64xp = mb64p_base;

         while(i > 0){
            u8 unil, u0, u1, u2, u3;

            unil = (i >= 4) ? 4 : i & 0x3;
            i -= unil;
            u0 = mb64xp[0];
            u1 = mb64xp[1];
            u2 = (unil < 3) ? 0 : mb64xp[2];
            u3 = (unil < 4) ? 0 : mb64xp[3];
            mb64xp += unil;
            *mb64p++ = (u0 <<= 2) | (u1 >> 4);
            if(unil < 3)
               break;
            *mb64p++ = (u1 <<= 4) | (u2 >> 2);
            if(unil < 4)
               break;
            *mb64p++ = (u2 <<= 6, u2 &= 0xC0) | u3;
         }

         /* UTF-16BE we convert to UTF-8 */
         i = P2UZ(mb64p - mb64p_base);
         if(i & 1){
            emsg = N_("Odd bytecount for UTF-16BE input");
            goto jerr;
         }

         /* TODO S-CText: magic utf16 conversions */
         emsg = N_("Invalid UTF-16BE encoding");

         for(mb64p = mb64p_base; i > 0;){
            u32 utf32;
            u16 uhi, ulo;

            uhi = mb64p[0];
            uhi <<= 8;
            uhi |= mb64p[1];

            /* Not a surrogate? */
            if(uhi < 0xD800 || uhi > 0xDFFF){
               utf32 = uhi;
               mb64p += 2;
               i -= 2;
            }else if(uhi > 0xDBFF)
               goto jerr;
            else if(i < 4){
               emsg = N_("Incomplete UTF-16BE surrogate pair");
               goto jerr;
            }else{
               ulo = mb64p[2];
               ulo <<= 8;
               ulo |= mb64p[3];
               if(ulo < 0xDC00 || ulo > 0xDFFF)
                  goto jerr;

               utf32 = (uhi &= 0x03FF);
               utf32 <<= 10;
               utf32 += 0x10000;
               utf32 |= (ulo &= 0x03FF);
               mb64p += 4;
               i -= 4;
            }

            utf32 = su_utf32_to_8(utf32, rv);
            rv += utf32;
         }
      }
   }
   *rv = '\0';

   /* We can skip the UTF-8 conversion occasionally */
#if (defined mx_HAVE_DEVEL || !defined mx_HAVE_ALWAYS_UNICODE_LOCALE) &&\
      defined mx_HAVE_ICONV
   if(!(n_psonce & n_PSO_UNICODE)){
      emsg = N_("iconv(3) from UTF-8 to locale charset failed");
      if((rv = n_iconv_onetime_cp(n_ICONV_NONE, NULL, NULL, rv_base)) == NULL)
         goto jerr;
   }
#endif

   *err_or_null = FAL0;
   rv = rv_base;
jleave:
   NYD2_OU;
   return rv;
jerr:
   n_err(_("Cannot decode IMAP path %s\n  %s\n"), path, V_(emsg));
   su_mem_copy(rv = rv_base, path, ++l_orig);
   goto jleave;
}

static char *
imap_path_quote(struct mailbox *mp, char const *cp){
   boole err;
   char *rv;
   NYD2_IN;

   cp = a_imap_path_normalize(mp, cp, FAL0);
   cp = imap_path_encode(cp, &err);
   rv = err ? NULL : imap_quotestr(cp);
   NYD2_OU;
   return rv;
}

static void
imap_other_get(char *pp)
{
   char *xp;
   NYD2_IN;

   if (su_cs_cmp_case_n(pp, "FLAGS ", 6) == 0) {
      pp += 6;
      response_other = MAILBOX_DATA_FLAGS;
   } else if (su_cs_cmp_case_n(pp, "LIST ", 5) == 0) {
      pp += 5;
      response_other = MAILBOX_DATA_LIST;
   } else if (su_cs_cmp_case_n(pp, "LSUB ", 5) == 0) {
      pp += 5;
      response_other = MAILBOX_DATA_LSUB;
   } else if (su_cs_cmp_case_n(pp, "MAILBOX ", 8) == 0) {
      pp += 8;
      response_other = MAILBOX_DATA_MAILBOX;
   } else if (su_cs_cmp_case_n(pp, "SEARCH ", 7) == 0) {
      pp += 7;
      response_other = MAILBOX_DATA_SEARCH;
   } else if (su_cs_cmp_case_n(pp, "STATUS ", 7) == 0) {
      pp += 7;
      response_other = MAILBOX_DATA_STATUS;
   } else if (su_cs_cmp_case_n(pp, "CAPABILITY ", 11) == 0) {
      pp += 11;
      response_other = CAPABILITY_DATA;
   } else {
      responded_other_number = strtol(pp, &xp, 10);
      while (*xp == ' ')
         ++xp;
      if (su_cs_cmp_case_n(xp, "EXISTS\r\n", 8) == 0) {
         response_other = MAILBOX_DATA_EXISTS;
      } else if (su_cs_cmp_case_n(xp, "RECENT\r\n", 8) == 0) {
         response_other = MAILBOX_DATA_RECENT;
      } else if (su_cs_cmp_case_n(xp, "EXPUNGE\r\n", 9) == 0) {
         response_other = MESSAGE_DATA_EXPUNGE;
      } else if (su_cs_cmp_case_n(xp, "FETCH ", 6) == 0) {
         pp = &xp[6];
         response_other = MESSAGE_DATA_FETCH;
      } else
         response_other = RESPONSE_OTHER_UNKNOWN;
   }
   responded_other_text = pp;
   NYD2_OU;
}

static void
imap_response_get(const char **cp)
{
   NYD2_IN;
   if (su_cs_cmp_case_n(*cp, "OK ", 3) == 0) {
      *cp += 3;
      response_status = RESPONSE_OK;
   } else if (su_cs_cmp_case_n(*cp, "NO ", 3) == 0) {
      *cp += 3;
      response_status = RESPONSE_NO;
   } else if (su_cs_cmp_case_n(*cp, "BAD ", 4) == 0) {
      *cp += 4;
      response_status = RESPONSE_BAD;
   } else if (su_cs_cmp_case_n(*cp, "PREAUTH ", 8) == 0) {
      *cp += 8;
      response_status = RESPONSE_PREAUTH;
   } else if (su_cs_cmp_case_n(*cp, "BYE ", 4) == 0) {
      *cp += 4;
      response_status = RESPONSE_BYE;
   } else
      response_status = RESPONSE_OTHER;
   NYD2_OU;
}

static void
imap_response_parse(void)
{
   static char *parsebuf; /* TODO Use pool */
   static uz  parsebufsize;

   const char *ip = imapbuf;
   char *pp;
   NYD2_IN;

   if (parsebufsize < imapbufsize + 1)
      parsebuf = n_realloc(parsebuf, parsebufsize = imapbufsize);
   su_mem_copy(parsebuf, imapbuf, su_cs_len(imapbuf) + 1);
   pp = parsebuf;
   switch (*ip) {
   case '+':
      response_type = RESPONSE_CONT;
      ip++;
      pp++;
      while (*ip == ' ') {
         ip++;
         pp++;
      }
      break;
   case '*':
      ip++;
      pp++;
      while (*ip == ' ') {
         ip++;
         pp++;
      }
      imap_response_get(&ip);
      pp = &parsebuf[ip - imapbuf];
      switch (response_status) {
      case RESPONSE_BYE:
         response_type = RESPONSE_FATAL;
         break;
      default:
         response_type = RESPONSE_DATA;
      }
      break;
   default:
      responded_tag = parsebuf;
      while (*pp && *pp != ' ')
         pp++;
      if (*pp == '\0') {
         response_type = RESPONSE_ILLEGAL;
         break;
      }
      *pp++ = '\0';
      while (*pp && *pp == ' ')
         pp++;
      if (*pp == '\0') {
         response_type = RESPONSE_ILLEGAL;
         break;
      }
      ip = &imapbuf[pp - parsebuf];
      response_type = RESPONSE_TAGGED;
      imap_response_get(&ip);
      pp = &parsebuf[ip - imapbuf];
   }
   responded_text = pp;
   if (response_type != RESPONSE_CONT && response_type != RESPONSE_ILLEGAL &&
         response_status == RESPONSE_OTHER)
      imap_other_get(pp);
   NYD2_OU;
}

static enum okay
imap_answer(struct mailbox *mp, int errprnt)
{
   int i, complete;
   enum okay rv;
   NYD2_IN;

   rv = OKAY;
   if (mp->mb_type == MB_CACHE)
      goto jleave;
   rv = STOP;
jagain:
   if (mx_socket_getline(&imapbuf, &imapbufsize, NULL, mp->mb_sock) > 0) {
      if (n_poption & n_PO_D_VV)
         n_err(">>> SERVER: %s", imapbuf);
      imap_response_parse();
      if (response_type == RESPONSE_ILLEGAL)
         goto jagain;
      if (response_type == RESPONSE_CONT) {
         rv = OKAY;
         goto jleave;
      }
      if (response_status == RESPONSE_OTHER) {
         if (response_other == MAILBOX_DATA_EXISTS) {
            had_exists = responded_other_number;
            rec_queue(REC_EXISTS, responded_other_number);
            if (had_expunge > 0)
               had_expunge = 0;
         } else if (response_other == MESSAGE_DATA_EXPUNGE) {
            rec_queue(REC_EXPUNGE, responded_other_number);
            if (had_expunge < 0)
               had_expunge = 0;
            had_expunge++;
            expunged_messages++;
         }
      }
      complete = 0;
      if (response_type == RESPONSE_TAGGED) {
         if (su_cs_cmp_case(responded_tag, tag(0)) == 0)
            complete |= 1;
         else
            goto jagain;
      }
      switch (response_status) {
      case RESPONSE_PREAUTH:
         mp->mb_active &= ~MB_PREAUTH;
         /*FALLTHRU*/
      case RESPONSE_OK:
jokay:
         rv = OKAY;
         complete |= 2;
         break;
      case RESPONSE_NO:
      case RESPONSE_BAD:
jstop:
         complete |= 2;
         if (errprnt)
            n_err(_("IMAP error: %s"), responded_text);
         break;
      case RESPONSE_UNKNOWN:  /* does not happen */
      case RESPONSE_BYE:
         i = mp->mb_active;
         mp->mb_active = MB_NONE;
         if (i & MB_BYE)
            goto jokay;
         goto jstop;
      case RESPONSE_OTHER:
         rv = OKAY;
         break;
      }
      if (response_status != RESPONSE_OTHER &&
            su_cs_cmp_case_n(responded_text, "[ALERT] ", 8) == 0)
         n_err(_("IMAP alert: %s"), &responded_text[8]);
      if (complete == 3)
         mp->mb_active &= ~MB_COMD;
   } else
      mp->mb_active = MB_NONE;
jleave:
   NYD2_OU;
   return rv;
}

static enum okay
imap_parse_list(void)
{
   char *cp;
   enum okay rv;
   NYD2_IN;

   rv = STOP;

   cp = responded_other_text;
   list_attributes = LIST_NONE;
   if (*cp == '(') {
      while (*cp && *cp != ')') {
         if (*cp == '\\') {
            if (su_cs_cmp_case_n(&cp[1], "Noinferiors ", 12) == 0) {
               list_attributes |= LIST_NOINFERIORS;
               cp += 12;
            } else if (su_cs_cmp_case_n(&cp[1], "Noselect ", 9) == 0) {
               list_attributes |= LIST_NOSELECT;
               cp += 9;
            } else if (su_cs_cmp_case_n(&cp[1], "Marked ", 7) == 0) {
               list_attributes |= LIST_MARKED;
               cp += 7;
            } else if (su_cs_cmp_case_n(&cp[1], "Unmarked ", 9) == 0) {
               list_attributes |= LIST_UNMARKED;
               cp += 9;
            }
         }
         cp++;
      }
      if (*++cp != ' ')
         goto jleave;
      while (*cp == ' ')
         cp++;
   }

   list_hierarchy_delimiter = EOF;
   if (*cp == '"') {
      if (*++cp == '\\')
         cp++;
      list_hierarchy_delimiter = *cp++ & 0377;
      if (cp[0] != '"' || cp[1] != ' ')
         goto jleave;
      cp++;
   } else if (cp[0] == 'N' && cp[1] == 'I' && cp[2] == 'L' && cp[3] == ' ') {
      list_hierarchy_delimiter = EOF;
      cp += 3;
   }

   while (*cp == ' ')
      cp++;
   list_name = cp;
   while (*cp && *cp != '\r')
      cp++;
   *cp = '\0';
   rv = OKAY;
jleave:
   NYD2_OU;
   return rv;
}

static enum okay
imap_finish(struct mailbox *mp)
{
   NYD_IN;
   while(mp->mb_sock != NIL && mp->mb_sock->s_fd > 0 &&
         (mp->mb_active & MB_COMD))
      imap_answer(mp, 1);
   NYD_OU;
   return OKAY;
}

static void
imap_timer_off(void)
{
   NYD_IN;
   if (imapkeepalive > 0) {
      alarm(0);
      safe_signal(SIGALRM, savealrm);
   }
   NYD_OU;
}

static void
imapcatch(int s)
{
   NYD; /*  Signal handler */
   switch (s) {
   case SIGINT:
      n_err_sighdl(_("Interrupt\n"));
      siglongjmp(imapjmp, 1);
      /*NOTREACHED*/
   case SIGPIPE:
      n_err_sighdl(_("Received SIGPIPE during IMAP operation\n"));
      break;
   }
}

static void
_imap_maincatch(int s)
{
   NYD; /*  Signal handler */
   UNUSED(s);
   if (interrupts++ == 0) {
      n_err_sighdl(_("Interrupt\n"));
      return;
   }
   n_go_onintr_for_imap();
}

static enum okay
imap_noop1(struct mailbox *mp)
{
   char o[LINESIZE];
   FILE *queuefp = NULL;
   NYD;

   snprintf(o, sizeof o, "%s NOOP\r\n", tag(1));
   IMAP_OUT(o, MB_COMD, return STOP)
   IMAP_ANSWER()
   return OKAY;
}

FL char const *
imap_fileof(char const *xcp)
{
   char const *cp = xcp;
   int state = 0;
   NYD_IN;

   while (*cp) {
      if (cp[0] == ':' && cp[1] == '/' && cp[2] == '/') {
         cp += 3;
         state = 1;
      }
      if (cp[0] == '/' && state == 1) {
         ++cp;
         goto jleave;
      }
      if (cp[0] == '/') {
         cp = xcp;
         goto jleave;
      }
      ++cp;
   }
jleave:
   NYD_OU;
   return cp;
}

FL enum okay
imap_noop(void)
{
   n_sighdl_t volatile oldint, oldpipe;
   enum okay volatile rv = STOP;
   NYD_IN;

   if (mb.mb_type != MB_IMAP)
      goto jleave;

   imaplock = 1;
   if ((oldint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
      safe_signal(SIGINT, &_imap_maincatch);
   oldpipe = safe_signal(SIGPIPE, SIG_IGN);
   if (sigsetjmp(imapjmp, 1) == 0) {
      if (oldpipe != SIG_IGN)
         safe_signal(SIGPIPE, imapcatch);

      rv = imap_noop1(&mb);
   }
   safe_signal(SIGINT, oldint);
   safe_signal(SIGPIPE, oldpipe);
   imaplock = 0;
jleave:
   NYD_OU;
   if (interrupts)
      n_go_onintr_for_imap();
   return rv;
}

static void
rec_queue(enum rec_type rt, unsigned long cnt)
{
   struct record *rp;
   NYD_IN;

   rp = n_calloc(1, sizeof *rp);
   rp->rec_type = rt;
   rp->rec_count = cnt;
   if (record && recend) {
      recend->rec_next = rp;
      recend = rp;
   } else
      record = recend = rp;
   NYD_OU;
}

static enum okay
rec_dequeue(void)
{
   struct message *omessage;
   struct record *rp, *rq;
   uz exists = 0, i;
   enum okay rv = STOP;
   NYD_IN;

   if (record == NULL)
      goto jleave;

   omessage = message;
   message = n_alloc((msgCount+1) * sizeof *message);
   if (msgCount)
      su_mem_copy(message, omessage, msgCount * sizeof *message);
   su_mem_set(&message[msgCount], 0, sizeof *message);

   rp = record, rq = NULL;
   rv = OKAY;
   while (rp != NULL) {
      switch (rp->rec_type) {
      case REC_EXISTS:
         exists = rp->rec_count;
         break;
      case REC_EXPUNGE:
         if (rp->rec_count == 0) {
            rv = STOP;
            break;
         }
         if (rp->rec_count > (unsigned long)msgCount) {
            if (exists == 0 || rp->rec_count > exists--)
               rv = STOP;
            break;
         }
         if (exists > 0)
            exists--;
         delcache(&mb, &message[rp->rec_count-1]);
         su_mem_move(&message[rp->rec_count-1], &message[rp->rec_count],
            ((msgCount - rp->rec_count + 1) * sizeof *message));
         --msgCount;
         /* If the message was part of a collapsed thread,
          * the m_collapsed field of one of its ancestors
          * should be incremented. It seems hardly possible
          * to do this with the current message structure,
          * though. The result is that a '+' may be shown
          * in the header summary even if no collapsed
          * children exists */
         break;
      }
      if (rq != NULL)
         n_free(rq);
      rq = rp;
      rp = rp->rec_next;
   }
   if (rq != NULL)
      n_free(rq);

   record = recend = NULL;
   if (rv == OKAY && UCMP(z, exists, >, msgCount)) {
      message = n_realloc(message, (exists + 1) * sizeof *message);
      su_mem_set(&message[msgCount], 0,
         (exists - msgCount + 1) * sizeof *message);
      for (i = msgCount; i < exists; ++i)
         imap_init(&mb, i);
      imap_flags(&mb, msgCount+1, exists);
      msgCount = exists;
   }

   if (rv == STOP) {
      n_free(message);
      message = omessage;
   }
jleave:
   NYD_OU;
   return rv;
}

static void
rec_rmqueue(void)
{
   struct record *rp;
   NYD_IN;

   for (rp = record; rp != NULL;) {
      struct record *tmp = rp;
      rp = rp->rec_next;
      n_free(tmp);
   }
   record = recend = NULL;
   NYD_OU;
}

/*ARGSUSED*/
static void
imapalarm(int s)
{
   n_sighdl_t volatile saveint, savepipe;
   NYD; /* Signal handler */
   UNUSED(s);

   if (imaplock++ == 0) {
      if ((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
         safe_signal(SIGINT, &_imap_maincatch);
      savepipe = safe_signal(SIGPIPE, SIG_IGN);
      if (sigsetjmp(imapjmp, 1)) {
         safe_signal(SIGINT, saveint);
         safe_signal(SIGPIPE, savepipe);
         goto jbrk;
      }
      if (savepipe != SIG_IGN)
         safe_signal(SIGPIPE, imapcatch);
      if (imap_noop1(&mb) != OKAY) {
         safe_signal(SIGINT, saveint);
         safe_signal(SIGPIPE, savepipe);
         goto jleave;
      }
      safe_signal(SIGINT, saveint);
      safe_signal(SIGPIPE, savepipe);
   }
jbrk:
   alarm(imapkeepalive);
jleave:
   --imaplock;
}

static enum okay
imap_preauth(struct mailbox *mp, struct mx_url *urlp,
   struct mx_cred_ctx *ccred)
{
   NYD;

   mp->mb_active |= MB_PREAUTH;
   imap_answer(mp, 1);

#ifdef mx_HAVE_TLS
   if(!mp->mb_sock->s_use_tls){
      if(xok_blook(imap_use_starttls, urlp, OXM_ALL)){
         FILE *queuefp = NULL;
         char o[LINESIZE];

         snprintf(o, sizeof o, "%s STARTTLS\r\n", tag(1));
         IMAP_OUT(o, MB_COMD, return STOP)
         IMAP_ANSWER()
         if(!n_tls_open(urlp, mp->mb_sock))
            return STOP;
      }else if(ccred->cc_needs_tls){
         n_err(_("IMAP authentication %s needs TLS "
            "(*imap-use-starttls* set?)\n"),
            ccred->cc_auth);
         return STOP;
      }
   }
#else
   if(ccred->cc_needs_tls || xok_blook(imap_use_starttls, urlp, OXM_ALL)){
      n_err(_("No TLS support compiled in\n"));
      return STOP;
   }
#endif

   imap_capability(mp);
   return OKAY;
}

static enum okay
imap_capability(struct mailbox *mp)
{
   char o[LINESIZE];
   FILE *queuefp = NULL;
   enum okay ok = STOP;
   const char *cp;
   NYD;

   snprintf(o, sizeof o, "%s CAPABILITY\r\n", tag(1));
   IMAP_OUT(o, MB_COMD, return STOP)
   while (mp->mb_active & MB_COMD) {
      ok = imap_answer(mp, 0);
      if (response_status == RESPONSE_OTHER &&
            response_other == CAPABILITY_DATA) {
         cp = responded_other_text;
         while (*cp) {
            while (su_cs_is_space(*cp))
               ++cp;
            if (strncmp(cp, "UIDPLUS", 7) == 0 && su_cs_is_space(cp[7]))
               /* RFC 2359 */
               mp->mb_flags |= MB_UIDPLUS;
            else if(strncmp(cp, "SASL-IR ", 7) == 0 && su_cs_is_space(cp[7]))
               /* RFC 4959 */
               mp->mb_flags |= MB_SASL_IR;
            while (*cp && !su_cs_is_space(*cp))
               ++cp;
         }
      }
   }
   return ok;
}

static enum okay
a_imap_auth(struct mailbox *mp, struct mx_url *urlp,
      struct mx_cred_ctx *ccredp){
   enum okay rv;
   NYD_IN;
   UNUSED(urlp);

   if(!(mp->mb_active & MB_PREAUTH))
      rv = OKAY;
   else switch(ccredp->cc_authtype){
   case mx_CRED_AUTHTYPE_LOGIN:
      rv = imap_login(mp, ccredp);
      break;
   case mx_CRED_AUTHTYPE_OAUTHBEARER:
      rv = a_imap_oauthbearer(mp, ccredp);
      break;
   case mx_CRED_AUTHTYPE_EXTERNAL:
   case mx_CRED_AUTHTYPE_EXTERNANON:
      rv = a_imap_external(mp, ccredp);
      break;
#ifdef mx_HAVE_MD5
   case mx_CRED_AUTHTYPE_CRAM_MD5:
      rv = imap_cram_md5(mp, ccredp);
      break;
#endif
#ifdef mx_HAVE_GSSAPI
   case mx_CRED_AUTHTYPE_GSSAPI:
      rv = OKAY;
      if(n_poption & n_PO_D)
         n_err(_(">>> We would perform GSS-API authentication now\n"));
      else if(!su_CONCAT(su_FILE,_gss)(mp->mb_sock, urlp, ccredp, mp))
         rv = STOP;
      break;
#endif
   default:
      rv = STOP;
      break;
   }

   NYD_OU;
   return rv;
}

#ifdef mx_HAVE_MD5
static enum okay
imap_cram_md5(struct mailbox *mp, struct mx_cred_ctx *ccred)
{
   char o[LINESIZE], *cp;
   FILE *queuefp = NULL;
   enum okay rv = STOP;
   NYD_IN;

   snprintf(o, sizeof o, "%s AUTHENTICATE CRAM-MD5\r\n", tag(1));
   IMAP_XOUT(o, 0, goto jleave, goto jleave);
   imap_answer(mp, 1);
   if (response_type != RESPONSE_CONT)
      goto jleave;

   cp = mx_md5_cram_string(&ccred->cc_user, &ccred->cc_pass, responded_text);
   if(cp == NULL)
      goto jleave;
   IMAP_XOUT(cp, MB_COMD, goto jleave, goto jleave);
   while (mp->mb_active & MB_COMD)
      rv = imap_answer(mp, 1);
jleave:
   NYD_OU;
   return rv;
}
#endif /* mx_HAVE_MD5 */

static enum okay
imap_login(struct mailbox *mp, struct mx_cred_ctx *ccred)
{
   char o[LINESIZE];
   FILE *queuefp = NULL;
   enum okay rv = STOP;
   NYD_IN;

   snprintf(o, sizeof o, "%s LOGIN %s %s\r\n",
      tag(1), imap_quotestr(ccred->cc_user.s),
      imap_quotestr(ccred->cc_pass.s));
   IMAP_XOUT(o, MB_COMD, goto jleave, goto jleave);
   while (mp->mb_active & MB_COMD)
      rv = imap_answer(mp, 1);
jleave:
   NYD_OU;
   return rv;
}

static enum okay
a_imap_oauthbearer(struct mailbox *mp, struct mx_cred_ctx *ccp){
   struct str b64;
   int i;
   uz cnt;
   boole nsaslir;
   char *cp;
   FILE *queuefp;
   enum okay rv;
   NYD_IN;

   rv = STOP;
   queuefp = NIL;
   cp = NIL;
   nsaslir = !(mp->mb_flags & MB_SASL_IR);

   /* Calculate required storage */
   cnt = ccp->cc_user.l;
#define a_MAX \
   (sizeof("T1 ") -1 +\
   sizeof("AUTHENTICATE XOAUTH2 ") -1 +\
    2 + sizeof("user=\001auth=Bearer \001\001") -1 +\
    sizeof(NETNL) -1 +1)

   if(ccp->cc_pass.l >= UZ_MAX - a_MAX ||
         cnt >= UZ_MAX - a_MAX - ccp->cc_pass.l){
jerr_cred:
      n_err(_("Credentials overflow buffer sizes\n"));
      goto jleave;
   }
   cnt += ccp->cc_pass.l;

   cnt += a_MAX;
#undef a_MAX
   if((cnt = b64_encode_calc_size(cnt)) == UZ_MAX)
      goto jerr_cred;

   cp = n_lofi_alloc(cnt +1);

   /* Then create login query */
   i = snprintf(cp, cnt +1, "user=%s\001auth=Bearer %s\001\001",
         ccp->cc_user.s, ccp->cc_pass.s);
   if(b64_encode_buf(&b64, cp, i, B64_SALLOC) == NIL)
      goto jleave;
   else{
      char const *tp;

      tp = tag(TRU1);
      cnt = su_cs_len(tp);
      su_mem_copy(cp, tp, cnt);
   }
   su_mem_copy(&cp[cnt], " AUTHENTICATE XOAUTH2 ",
      sizeof(" AUTHENTICATE XOAUTH2 ") /*-1*/);
   cnt += sizeof(" AUTHENTICATE XOAUTH2 ") -1 - 1;
   if(!nsaslir){
      su_mem_copy(&cp[++cnt], b64.s, b64.l);
      cnt += b64.l;
   }
   su_mem_copy(&cp[cnt], NETNL, sizeof(NETNL));

   IMAP_XOUT(cp, (nsaslir ? 0 : MB_COMD), goto jleave, goto jleave);
   rv = imap_answer(mp, 1);
   if(rv == STOP)
      goto jleave;

   if(nsaslir){
      if(response_type != RESPONSE_CONT)
         goto jleave;

      su_mem_copy(cp, b64.s, b64.l);
      su_mem_copy(&cp[b64.l], NETNL, sizeof(NETNL));
      IMAP_XOUT(cp, MB_COMD, goto jleave, goto jleave);
   }

   while(mp->mb_active & MB_COMD)
      rv = imap_answer(mp, 1);
jleave:
   if(cp != NIL)
      n_lofi_free(cp);
   NYD_OU;
   return rv;
}

static enum okay
a_imap_external(struct mailbox *mp, struct mx_cred_ctx *ccp){
   uz cnt;
   boole nsaslir;
   char *cp;
   FILE *queuefp;
   enum okay rv;
   NYD_IN;

   rv = STOP;
   queuefp = NIL;
   cp = NIL;
   nsaslir = !(mp->mb_flags & MB_SASL_IR);

   if(ccp->cc_authtype == mx_CRED_AUTHTYPE_EXTERNANON){
      ccp->cc_user.l = !nsaslir;
      ccp->cc_user.s = UNCONST(char*,"=");
   }

   /* Calculate required storage */
   cnt = ccp->cc_user.l;
#define a_MAX \
   (sizeof("T1 ") -1 +\
   sizeof("AUTHENTICATE EXTERNAL ") -1 +\
    sizeof(NETNL) -1 +1)

   if(cnt >= UZ_MAX - a_MAX){
      n_err(_("Credentials overflow buffer sizes\n"));
      goto jleave;
   }

   cnt += a_MAX;
#undef a_MAX

   cp = n_lofi_alloc(cnt +1);

   /* C99 */{
      char const *tp;

      tp = tag(TRU1);
      cnt = su_cs_len(tp);
      su_mem_copy(cp, tp, cnt);
   }
   su_mem_copy(&cp[cnt], " AUTHENTICATE EXTERNAL ",
      sizeof(" AUTHENTICATE EXTERNAL ") /*-1*/);
   cnt += sizeof(" AUTHENTICATE EXTERNAL ") -1 - 1;

   if(!nsaslir){
      su_mem_copy(&cp[++cnt], ccp->cc_user.s, ccp->cc_user.l);
      cnt += ccp->cc_user.l;
   }
   su_mem_copy(&cp[cnt], NETNL, sizeof(NETNL));

   IMAP_XOUT(cp, (nsaslir ? 0 : MB_COMD), goto jleave, goto jleave);
   rv = imap_answer(mp, 1);
   if(rv == STOP)
      goto jleave;

   if(nsaslir){
      if(response_type != RESPONSE_CONT)
         goto jleave;

      su_mem_copy(cp, ccp->cc_user.s, ccp->cc_user.l);
      su_mem_copy(&cp[ccp->cc_user.l], NETNL, sizeof(NETNL));

      IMAP_XOUT(cp, MB_COMD, goto jleave, goto jleave);
   }

   while(mp->mb_active & MB_COMD)
      rv = imap_answer(mp, 1);
jleave:
   if(cp != NIL)
      n_lofi_free(cp);
   NYD_OU;
   return rv;
}

FL enum okay
imap_select(struct mailbox *mp, off_t *size, int *cnt, const char *mbx,
   enum fedit_mode fm)
{
   char o[LINESIZE];
   char const *qname, *cp;
   FILE *queuefp;
   enum okay ok;
   NYD;
   UNUSED(size);

   ok = STOP;
   queuefp = NULL;

   if((qname = imap_path_quote(mp, mbx)) == NULL)
      goto jleave;

   ok = OKAY;

   mp->mb_uidvalidity = 0;
   snprintf(o, sizeof o, "%s %s %s\r\n", tag(1),
      (fm & FEDIT_RDONLY ? "EXAMINE" : "SELECT"), qname);
   IMAP_OUT(o, MB_COMD, ok = STOP;goto jleave)
   while (mp->mb_active & MB_COMD) {
      ok = imap_answer(mp, 1);
      if (response_status != RESPONSE_OTHER &&
            (cp = su_cs_find_case(responded_text, "[UIDVALIDITY ")) != NULL)
         su_idec_u64_cp(&mp->mb_uidvalidity, &cp[13], 10, NULL);/* TODO err? */
   }
   *cnt = (had_exists > 0) ? had_exists : 0;
   if (response_status != RESPONSE_OTHER &&
         su_cs_cmp_case_n(responded_text, "[READ-ONLY] ", 12) == 0)
      mp->mb_perm = 0;
jleave:
   return ok;
}

static enum okay
imap_flags(struct mailbox *mp, unsigned X, unsigned Y)
{
   char o[LINESIZE];
   FILE *queuefp = NULL;
   char const *cp;
   struct message *m;
   unsigned x = X, y = Y, n;
   NYD;

   snprintf(o, sizeof o, "%s FETCH %u:%u (FLAGS UID)\r\n", tag(1), x, y);
   IMAP_OUT(o, MB_COMD, return STOP)
   while (mp->mb_active & MB_COMD) {
      imap_answer(mp, 1);
      if (response_status == RESPONSE_OTHER &&
            response_other == MESSAGE_DATA_FETCH) {
         n = responded_other_number;
         if (n < x || n > y)
            continue;
         m = &message[n-1];
         m->m_xsize = 0;
      } else
         continue;

      if ((cp = su_cs_find_case(responded_other_text, "FLAGS ")) != NULL) {
         cp += 5;
         while (*cp == ' ')
            cp++;
         if (*cp == '(')
            imap_getflags(cp, &cp, &m->m_flag);
      }

      if ((cp = su_cs_find_case(responded_other_text, "UID ")) != NULL)
         su_idec_u64_cp(&m->m_uid, &cp[4], 10, NULL);/* TODO errors? */
      getcache1(mp, m, NEED_UNSPEC, 1);
      m->m_flag &= ~MHIDDEN;
   }

   while (x <= y && message[x-1].m_xsize && message[x-1].m_time)
      x++;
   while (y > x && message[y-1].m_xsize && message[y-1].m_time)
      y--;
   if (x <= y) {
      snprintf(o, sizeof o, "%s FETCH %u:%u (RFC822.SIZE INTERNALDATE)\r\n",
         tag(1), x, y);
      IMAP_OUT(o, MB_COMD, return STOP)
      while (mp->mb_active & MB_COMD) {
         imap_answer(mp, 1);
         if (response_status == RESPONSE_OTHER &&
               response_other == MESSAGE_DATA_FETCH) {
            n = responded_other_number;
            if (n < x || n > y)
               continue;
            m = &message[n-1];
         } else
            continue;
         if ((cp = su_cs_find_case(responded_other_text, "RFC822.SIZE ")
               ) != NULL)
            m->m_xsize = strtol(&cp[12], NULL, 10);
         if ((cp = su_cs_find_case(responded_other_text, "INTERNALDATE ")
               ) != NULL)
            m->m_time = imap_read_date_time(&cp[13]);
      }
   }

   srelax_hold();
   for (n = X; n <= Y; ++n) {
      putcache(mp, &message[n-1]);
      srelax();
   }
   srelax_rele();
   return OKAY;
}

static void
imap_init(struct mailbox *mp, int n)
{
   struct message *m;
   NYD_IN;
   UNUSED(mp);

   m = message + n;
   m->m_flag = MUSED | MNOFROM;
   m->m_block = 0;
   m->m_offset = 0;
   NYD_OU;
}

static void
imap_setptr(struct mailbox *mp, int nmail, int transparent, int *prevcount)
{
   struct message *omessage = 0;
   int i, omsgCount = 0;
   enum okay dequeued = STOP;
   NYD_IN;

   if (nmail || transparent) {
      omessage = message;
      omsgCount = msgCount;
   }
   if (nmail)
      dequeued = rec_dequeue();

   if (had_exists >= 0) {
      if (dequeued != OKAY)
         msgCount = had_exists;
      had_exists = -1;
   }
   if (had_expunge >= 0) {
      if (dequeued != OKAY)
         msgCount -= had_expunge;
      had_expunge = -1;
   }

   if (nmail && expunged_messages)
      printf("Expunged %ld message%s.\n", expunged_messages,
         (expunged_messages != 1 ? "s" : ""));
   *prevcount = omsgCount - expunged_messages;
   expunged_messages = 0;
   if (msgCount < 0) {
      fputs("IMAP error: Negative message count\n", stderr);
      msgCount = 0;
   }

   if (dequeued != OKAY) {
      message = n_calloc(msgCount + 1, sizeof *message);
      for (i = 0; i < msgCount; i++)
         imap_init(mp, i);
      if (!nmail && mp->mb_type == MB_IMAP)
         initcache(mp);
      if (msgCount > 0)
         imap_flags(mp, 1, msgCount);
      message[msgCount].m_size = 0;
      message[msgCount].m_lines = 0;
      rec_rmqueue();
   }
   if (nmail || transparent)
      transflags(omessage, omsgCount, transparent);
   else
      setdot(message);
   NYD_OU;
}

FL int
imap_setfile(char const * volatile who, const char *xserver,
   enum fedit_mode fm)
{
   struct mx_url url;
   int rv;
   NYD_IN;

   if(!mx_url_parse(&url, CPROTO_IMAP, xserver)){
      rv = 1;
      goto jleave;
   }
   if (ok_vlook(v15_compat) == NIL && url.url_pass.s != NIL)
      n_err(_("New-style URL used without *v15-compat* being set!\n"));

   _imap_rdonly = ((fm & FEDIT_RDONLY) != 0);
   rv = _imap_setfile1(who, &url, fm, 0);
jleave:
   NYD_OU;
   return rv;
}

static boole
_imap_getcred(struct mailbox *mbp, struct mx_cred_ctx *ccredp,
   struct mx_url *urlp)
{
   boole rv = FAL0;
   NYD_IN;

   if (ok_vlook(v15_compat) != su_NIL)
      rv = mx_cred_auth_lookup(ccredp, urlp);
   else {
      char *var, *old,
         *xuhp = ((urlp->url_flags & mx_URL_HAD_USER) ? urlp->url_eu_h_p.s
               : urlp->url_u_h_p.s);

      if ((var = mbp->mb_imap_pass) != NULL) {
         var = savecat("password-", xuhp);
         if ((old = n_UNCONST(n_var_vlook(var, FAL0))) != NULL)
            old = su_cs_dup(old, 0);
         n_var_vset(var, (up)mbp->mb_imap_pass);
      }
      rv = mx_cred_auth_lookup_old(ccredp, CPROTO_IMAP, xuhp);
      if (var != NULL) {
         if (old != NULL) {
            n_var_vset(var, (up)old);
            n_free(old);
         } else
            n_var_vclear(var);
      }
   }

   NYD_OU;
   return rv;
}

static int
_imap_setfile1(char const * volatile who, struct mx_url *urlp,
   enum fedit_mode volatile fm, int volatile transparent)
{
   struct mx_socket so;
   struct mx_cred_ctx ccred;
   n_sighdl_t volatile saveint, savepipe;
   char const *cp;
   int rv;
   int volatile prevcount = 0;
   enum mbflags same_flags;
   NYD_IN;

   if (fm & FEDIT_NEWMAIL) {
      saveint = safe_signal(SIGINT, SIG_IGN);
      savepipe = safe_signal(SIGPIPE, SIG_IGN);
      if (saveint != SIG_IGN)
         safe_signal(SIGINT, imapcatch);
      if (savepipe != SIG_IGN)
         safe_signal(SIGPIPE, imapcatch);
      imaplock = 1;
      goto jnmail;
   }

   same_flags = mb.mb_flags;
   same_imap_account = 0;
   if (mb.mb_imap_account != NULL &&
         (mb.mb_type == MB_IMAP || mb.mb_type == MB_CACHE)) {
      if(mb.mb_sock != NIL && mb.mb_sock->s_fd > 0 && mb.mb_sock->s_rsz >= 0 &&
            !su_cs_cmp(mb.mb_imap_account, urlp->url_p_eu_h_p) &&
            disconnected(mb.mb_imap_account) == 0) {
         same_imap_account = 1;
         if (urlp->url_pass.s == NULL && mb.mb_imap_pass != NULL)
/*
            goto jduppass;
      } else if ((transparent || mb.mb_type == MB_CACHE) &&
            !su_cs_cmp(mb.mb_imap_account, urlp->url_p_eu_h_p) &&
            urlp->url_pass.s == NULL && mb.mb_imap_pass != NULL)
jduppass:
*/
         urlp->url_pass.l = su_cs_len(urlp->url_pass.s =
               savestr(mb.mb_imap_pass));
      }
   }

   if (!same_imap_account && mb.mb_imap_pass != NULL) {
      n_free(mb.mb_imap_pass);
      mb.mb_imap_pass = NULL;
   }
   if (!_imap_getcred(&mb, &ccred, urlp)) {
      rv = -1;
      goto jleave;
   }

   su_mem_set(&so, 0, sizeof so);
   so.s_fd = -1;
   if (!same_imap_account) {
      if (!disconnected(urlp->url_p_eu_h_p) && !mx_socket_open(&so, urlp)) {
         rv = -1;
         goto jleave;
      }
   }else if(mb.mb_sock != NIL)
      so = *mb.mb_sock;

   if(!transparent){
      if(!quit(FAL0)){
         rv = -1;
         goto jleave;
      }
   }

   if (fm & FEDIT_SYSBOX)
      n_pstate &= ~n_PS_EDIT;
   else
      n_pstate |= n_PS_EDIT;
   if (mb.mb_imap_account != NULL)
      n_free(mb.mb_imap_account);
   if (mb.mb_imap_pass != NULL)
      n_free(mb.mb_imap_pass);
   mb.mb_imap_account = su_cs_dup(urlp->url_p_eu_h_p, 0);
   /* TODO This is a hack to allow '@boxname'; in the end everything will be an
    * TODO object, and mailbox will naturally have an URL and credentials */
   mb.mb_imap_pass = su_cs_dup_cbuf(ccred.cc_pass.s, ccred.cc_pass.l, 0);

   if(!same_imap_account && mb.mb_sock != NIL){
      if(mb.mb_sock->s_fd >= 0)
         mx_socket_close(mb.mb_sock);
      su_FREE(mb.mb_sock);
      mb.mb_sock = NIL;
   }
   same_imap_account = 0;

   if (!transparent) {
      if (mb.mb_itf) {
         fclose(mb.mb_itf);
         mb.mb_itf = NULL;
      }
      if (mb.mb_otf) {
         fclose(mb.mb_otf);
         mb.mb_otf = NULL;
      }
      if (mb.mb_imap_mailbox != NULL)
         n_free(mb.mb_imap_mailbox);
      ASSERT(urlp->url_path.s != NULL);
      imap_delim_init(&mb, urlp);
      mb.mb_imap_mailbox = su_cs_dup(a_imap_path_normalize(&mb,
            urlp->url_path.s, FAL0), 0);
      initbox(savecatsep(urlp->url_p_eu_h_p,
         (mb.mb_imap_delim[0] != '\0' ? mb.mb_imap_delim[0] : n_IMAP_DELIM[0]),
         mb.mb_imap_mailbox));
   }
   mb.mb_type = MB_VOID;
   mb.mb_active = MB_NONE;

   imaplock = 1;
   saveint = safe_signal(SIGINT, SIG_IGN);
   savepipe = safe_signal(SIGPIPE, SIG_IGN);
   if (sigsetjmp(imapjmp, 1)) {
      /* Not safe to use &so; save to use mb.mb_sock?? :-( TODO */
      if(mb.mb_sock != NIL){
         if(mb.mb_sock->s_fd >= 0)
            mx_socket_close(mb.mb_sock);
         su_FREE(mb.mb_sock);
         mb.mb_sock = NIL;
      }
      safe_signal(SIGINT, saveint);
      safe_signal(SIGPIPE, savepipe);
      imaplock = 0;

      mb.mb_type = MB_VOID;
      mb.mb_active = MB_NONE;
      rv = (fm & (FEDIT_SYSBOX | FEDIT_NEWMAIL)) ? 1 : -1;
      goto jleave;
   }
   if (saveint != SIG_IGN)
      safe_signal(SIGINT, imapcatch);
   if (savepipe != SIG_IGN)
      safe_signal(SIGPIPE, imapcatch);

   if(mb.mb_sock == NIL || mb.mb_sock->s_fd < 0){
      if (disconnected(mb.mb_imap_account)) {
         if (cache_setptr(fm, transparent) == STOP)
            n_err(_("Mailbox \"%s\" is not cached\n"), urlp->url_p_eu_h_p_p);
         goto jdone;
      }
      if ((cp = xok_vlook(imap_keepalive, urlp, OXM_ALL)) != NULL) {
         if ((imapkeepalive = strtol(cp, NULL, 10)) > 0) {
            savealrm = safe_signal(SIGALRM, imapalarm);
            alarm(imapkeepalive);
         }
      }

      if(mb.mb_sock == NIL)
         mb.mb_sock = su_TALLOC(struct mx_socket, 1);
      *mb.mb_sock = so;
      mb.mb_sock->s_desc = "IMAP";
      mb.mb_sock->s_onclose = imap_timer_off;
      if (imap_preauth(&mb, urlp, &ccred) != OKAY ||
            a_imap_auth(&mb, urlp, &ccred) != OKAY) {
         if(mb.mb_sock->s_fd >= 0)
            mx_socket_close(mb.mb_sock);
         su_FREE(mb.mb_sock);
         mb.mb_sock = NIL;
         imap_timer_off();
         safe_signal(SIGINT, saveint);
         safe_signal(SIGPIPE, savepipe);
         imaplock = 0;
         rv = (fm & (FEDIT_SYSBOX | FEDIT_NEWMAIL)) ? 1 : -1;
         goto jleave;
      }
   } else   /* same account */
      mb.mb_flags |= same_flags;

   if (n_poption & n_PO_R_FLAG)
      fm |= FEDIT_RDONLY;
   mb.mb_perm = (fm & FEDIT_RDONLY) ? 0 : MB_DELE;
   mb.mb_type = MB_IMAP;
   cache_dequeue(&mb);
   ASSERT(urlp->url_path.s != NULL);
   if (imap_select(&mb, &mailsize, &msgCount, urlp->url_path.s, fm) != OKAY) {
      mx_socket_close(mb.mb_sock);
      su_FREE(mb.mb_sock);
      mb.mb_sock = NIL;
      /*imap_timer_off();*/
      safe_signal(SIGINT, saveint);
      safe_signal(SIGPIPE, savepipe);
      imaplock = 0;
      mb.mb_type = MB_VOID;
      rv = (fm & (FEDIT_SYSBOX | FEDIT_NEWMAIL)) ? 1 : -1;
      goto jleave;
   }

jnmail:
   imap_setptr(&mb, ((fm & FEDIT_NEWMAIL) != 0), transparent,
      UNVOLATILE(int*,&prevcount));
jdone:
   setmsize(msgCount);
   safe_signal(SIGINT, saveint);
   safe_signal(SIGPIPE, savepipe);
   imaplock = 0;

   if (!(fm & FEDIT_NEWMAIL) && mb.mb_type == MB_IMAP)
      purgecache(&mb, message, msgCount);
   if (((fm & FEDIT_NEWMAIL) || transparent) && mb.mb_sorted) {
      mb.mb_threaded = 0;
      c_sort((void*)-1);
   }

   if (!(fm & FEDIT_NEWMAIL) && !transparent) {
      n_pstate &= ~n_PS_SAW_COMMAND;
      n_pstate |= n_PS_SETFILE_OPENED;
   }

   if ((n_poption & n_PO_EXISTONLY) && (mb.mb_type == MB_IMAP ||
         mb.mb_type == MB_CACHE)) {
      rv = (msgCount == 0);
      goto jleave;
   }

   if (!(fm & FEDIT_NEWMAIL) && !(n_pstate & n_PS_EDIT) && msgCount == 0) {
      if ((mb.mb_type == MB_IMAP || mb.mb_type == MB_CACHE) &&
            !ok_blook(emptystart)){
         char const *intro;

         if(who == NULL)
            intro = who = n_empty;
         else
            intro = _(" for ");
         n_err(_("No mail%s%s at %s\n"), intro, who, urlp->url_p_eu_h_p_p);
      }
      rv = 1;
      goto jleave;
   }

   if (fm & FEDIT_NEWMAIL)
      newmailinfo(prevcount);
   rv = 0;
jleave:
   NYD_OU;
   return rv;
}

static int
imap_fetchdata(struct mailbox *mp, struct message *m, uz expected,
   int need, const char *head, uz headsize, long headlines)
{
   char *line = NULL, *lp;
   uz linesize = 0, linelen, size = 0;
   int emptyline = 0, lines = 0, excess = 0;
   off_t offset;
   NYD_IN;

   fseek(mp->mb_otf, 0L, SEEK_END);
   offset = ftell(mp->mb_otf);

   if (head)
      fwrite(head, 1, headsize, mp->mb_otf);

   while (mx_socket_getline(&line, &linesize, &linelen, mp->mb_sock) > 0) {
      lp = line;
      if (linelen > expected) {
         excess = linelen - expected;
         linelen = expected;
      }
      /* TODO >>
       * Need to mask 'From ' lines. This cannot be done properly
       * since some servers pass them as 'From ' and others as
       * '>From '. Although one could identify the first kind of
       * server in principle, it is not possible to identify the
       * second as '>From ' may also come from a server of the
       * first type as actual data. So do what is absolutely
       * necessary only - mask 'From '.
       *
       * If the line is the first line of the message header, it
       * is likely a real 'From ' line. In this case, it is just
       * ignored since it violates all standards.
       * TODO can the latter *really* happen??
       * TODO <<
       */
      /* Since we simply copy over data without doing any transfer
       * encoding reclassification/adjustment we *have* to perform
       * RFC 4155 compliant From_ quoting here */
      if (emptyline && is_head(lp, linelen, FAL0)) {
         fputc('>', mp->mb_otf);
         ++size;
      }
      emptyline = 0;
      if (lp[linelen-1] == '\n' && (linelen == 1 || lp[linelen-2] == '\r')) {
         if (linelen > 2) {
            fwrite(lp, 1, linelen - 2, mp->mb_otf);
            size += linelen - 1;
         } else {
            emptyline = 1;
            ++size;
         }
         fputc('\n', mp->mb_otf);
      } else {
         fwrite(lp, 1, linelen, mp->mb_otf);
         size += linelen;
      }
      ++lines;
      if ((expected -= linelen) <= 0)
         break;
   }
   if (!emptyline) {
      /* TODO This is very ugly; but some IMAP daemons don't end a
       * TODO message with \r\n\r\n, and we need \n\n for mbox format.
       * TODO That is to say we do it wrong here in order to get it right
       * TODO when send.c stuff or with MBOX handling, even though THIS
       * TODO line is solely a property of the MBOX database format! */
      fputc('\n', mp->mb_otf);
      ++lines;
      ++size;
   }
   fflush(mp->mb_otf);

   if (m != NULL) {
      m->m_size = size + headsize;
      m->m_lines = lines + headlines;
      m->m_block = mailx_blockof(offset);
      m->m_offset = mailx_offsetof(offset);
      switch (need) {
      case NEED_HEADER:
         m->m_content_info = CI_HAVE_HEADER;
         break;
      case NEED_BODY:
         m->m_content_info = CI_HAVE_HEADER | CI_HAVE_BODY;
         m->m_xlines = m->m_lines;
         m->m_xsize = m->m_size;
         break;
      }
   }
   n_free(line);
   NYD_OU;
   return excess;
}

static void
imap_putstr(struct mailbox *mp, struct message *m, const char *str,
   const char *head, uz headsize, long headlines)
{
   off_t offset;
   uz len;
   NYD_IN;

   len = su_cs_len(str);
   fseek(mp->mb_otf, 0L, SEEK_END);
   offset = ftell(mp->mb_otf);
   if (head)
      fwrite(head, 1, headsize, mp->mb_otf);
   if (len > 0) {
      fwrite(str, 1, len, mp->mb_otf);
      fputc('\n', mp->mb_otf);
      ++len;
   }
   fflush(mp->mb_otf);

   if (m != NULL) {
      m->m_size = headsize + len;
      m->m_lines = headlines + 1;
      m->m_block = mailx_blockof(offset);
      m->m_offset = mailx_offsetof(offset);
      m->m_content_info |= CI_HAVE_HEADER | CI_HAVE_BODY;
      m->m_xlines = m->m_lines;
      m->m_xsize = m->m_size;
   }
   NYD_OU;
}

static enum okay
imap_get(struct mailbox *mp, struct message *m, enum needspec need)
{
   char o[LINESIZE];
   struct message mt;
   n_sighdl_t volatile saveint, savepipe;
   char * volatile head;
   char const *cp, *loc, * volatile item, * volatile resp;
   uz expected;
   uz volatile headsize;
   int number;
   FILE *queuefp;
   long volatile headlines;
   long n;
   enum okay volatile ok;
   NYD;

   saveint = savepipe = SIG_IGN;
   head = NULL;
   cp = loc = item = resp = NULL;
   headsize = 0;
   number = (int)P2UZ(m - message + 1);
   queuefp = NULL;
   headlines = 0;
   ok = STOP;

   if (getcache(mp, m, need) == OKAY)
      return OKAY;
   if (mp->mb_type == MB_CACHE) {
      n_err(_("Message %lu not available\n"), (ul)number);
      return STOP;
   }

   if(mp->mb_sock == NIL || mp->mb_sock->s_fd < 0){
      n_err(_("IMAP connection closed\n"));
      return STOP;
   }

   switch (need) {
   case NEED_HEADER:
      resp = item = "RFC822.HEADER";
      break;
   case NEED_BODY:
      item = "BODY.PEEK[]";
      resp = "BODY[]";
      if ((m->m_content_info & CI_HAVE_HEADER) && m->m_size) {
         char *hdr = n_alloc(m->m_size);
         fflush(mp->mb_otf);
         if (fseek(mp->mb_itf, (long)mailx_positionof(m->m_block, m->m_offset),
               SEEK_SET) < 0 ||
               fread(hdr, 1, m->m_size, mp->mb_itf) != m->m_size) {
            n_free(hdr);
            break;
         }
         head = hdr;
         headsize = m->m_size;
         headlines = m->m_lines;
         item = "BODY.PEEK[TEXT]";
         resp = "BODY[TEXT]";
      }
      break;
   case NEED_UNSPEC:
      return STOP;
   }

   imaplock = 1;
   savepipe = safe_signal(SIGPIPE, SIG_IGN);
   if (sigsetjmp(imapjmp, 1)) {
      safe_signal(SIGINT, saveint);
      safe_signal(SIGPIPE, savepipe);
      imaplock = 0;
      return STOP;
   }
   if ((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
      safe_signal(SIGINT, &_imap_maincatch);
   if (savepipe != SIG_IGN)
      safe_signal(SIGPIPE, imapcatch);

   if (m->m_uid)
      snprintf(o, sizeof o, "%s UID FETCH %" PRIu64 " (%s)\r\n",
         tag(1), m->m_uid, item);
   else {
      if (check_expunged() == STOP)
         goto out;
      snprintf(o, sizeof o, "%s FETCH %u (%s)\r\n", tag(1), number, item);
   }
   IMAP_OUT(o, MB_COMD, goto out)
   for (;;) {
      u64 uid;

      ok = imap_answer(mp, 1);
      if (ok == STOP)
         break;
      if (response_status != RESPONSE_OTHER ||
            response_other != MESSAGE_DATA_FETCH)
         continue;
      if ((loc = su_cs_find_case(responded_other_text, resp)) == NULL)
         continue;
      uid = 0;
      if (m->m_uid) {
         if ((cp = su_cs_find_case(responded_other_text, "UID "))) {
            su_idec_u64_cp(&uid, &cp[4], 10, NULL);/* TODO errors? */
            n = 0;
         } else
            n = -1;
      } else
         n = responded_other_number;
      if ((cp = su_cs_rfind_c(responded_other_text, '{')) == NULL) {
         if (m->m_uid ? m->m_uid != uid : n != number)
            continue;
         if ((cp = su_cs_find_c(loc, '"')) != NULL) {
            cp = imap_unquotestr(cp);
            imap_putstr(mp, m, cp, head, headsize, headlines);
         } else {
            m->m_content_info |= CI_HAVE_HEADER | CI_HAVE_BODY;
            m->m_xlines = m->m_lines;
            m->m_xsize = m->m_size;
         }
         goto out;
      }
      expected = atol(&cp[1]);
      if (m->m_uid ? n == 0 && m->m_uid != uid : n != number) {
         imap_fetchdata(mp, NULL, expected, need, NULL, 0, 0);
         continue;
      }
      mt = *m;
      imap_fetchdata(mp, &mt, expected, need, head, headsize, headlines);
      if (n >= 0) {
         commitmsg(mp, m, &mt, mt.m_content_info);
         break;
      }
      if (n == -1 &&
            mx_socket_getline(&imapbuf, &imapbufsize, NULL, mp->mb_sock
               ) > 0) {
         if (n_poption & n_PO_VV)
            fputs(imapbuf, stderr);
         if ((cp = su_cs_find_case(imapbuf, "UID ")) != NULL) {
            su_idec_u64_cp(&uid, &cp[4], 10, NULL);/* TODO errors? */
            if (uid == m->m_uid) {
               commitmsg(mp, m, &mt, mt.m_content_info);
               break;
            }
         }
      }
   }
out:
   while (mp->mb_active & MB_COMD)
      ok = imap_answer(mp, 1);

   if (saveint != SIG_IGN)
      safe_signal(SIGINT, saveint);
   if (savepipe != SIG_IGN)
      safe_signal(SIGPIPE, savepipe);
   imaplock--;

   if (ok == OKAY)
      putcache(mp, m);
   if (head != NULL)
      n_free(head);
   if (interrupts)
      n_go_onintr_for_imap();
   return ok;
}

FL enum okay
imap_header(struct message *m)
{
   enum okay rv;
   NYD_IN;

   rv = imap_get(&mb, m, NEED_HEADER);
   NYD_OU;
   return rv;
}


FL enum okay
imap_body(struct message *m)
{
   enum okay rv;
   NYD_IN;

   rv = imap_get(&mb, m, NEED_BODY);
   NYD_OU;
   return rv;
}

static void
commitmsg(struct mailbox *mp, struct message *tomp, struct message *frommp,
   enum content_info content_info)
{
   NYD_IN;
   tomp->m_size = frommp->m_size;
   tomp->m_lines = frommp->m_lines;
   tomp->m_block = frommp->m_block;
   tomp->m_offset = frommp->m_offset;
   tomp->m_content_info = content_info & CI_HAVE_MASK;
   if (content_info & CI_HAVE_BODY) {
      tomp->m_xlines = frommp->m_lines;
      tomp->m_xsize = frommp->m_size;
   }
   putcache(mp, tomp);
   NYD_OU;
}

static enum okay
imap_fetchheaders(struct mailbox *mp, struct message *m, int bot, int topp)
{
   /* bot > topp */
   char o[LINESIZE];
   char const *cp;
   struct message mt;
   uz expected;
   int n = 0;
   FILE *queuefp = NULL;
   enum okay ok;
   NYD;

   if (m[bot].m_uid)
      snprintf(o, sizeof o,
         "%s UID FETCH %" PRIu64 ":%" PRIu64 " (RFC822.HEADER)\r\n",
         tag(1), m[bot-1].m_uid, m[topp-1].m_uid);
   else {
      if (check_expunged() == STOP)
         return STOP;
      snprintf(o, sizeof o, "%s FETCH %u:%u (RFC822.HEADER)\r\n",
         tag(1), bot, topp);
   }
   IMAP_OUT(o, MB_COMD, return STOP)

   srelax_hold();
   for (;;) {
      ok = imap_answer(mp, 1);
      if (response_status != RESPONSE_OTHER)
         break;
      if (response_other != MESSAGE_DATA_FETCH)
         continue;
      if (ok == STOP || (cp=su_cs_rfind_c(responded_other_text, '{')) == 0) {
         srelax_rele();
         return STOP;
      }
      if (su_cs_find_case(responded_other_text, "RFC822.HEADER") == NULL)
         continue;
      expected = atol(&cp[1]);
      if (m[bot-1].m_uid) {
         if ((cp = su_cs_find_case(responded_other_text, "UID ")) != NULL) {
            u64 uid;

            su_idec_u64_cp(&uid, &cp[4], 10, NULL);/* TODO errors? */
            for (n = bot; n <= topp; n++)
               if (uid == m[n-1].m_uid)
                  break;
            if (n > topp) {
               imap_fetchdata(mp, NULL, expected, NEED_HEADER, NULL, 0, 0);
               continue;
            }
         } else
            n = -1;
      } else {
         n = responded_other_number;
         if (n <= 0 || n > msgCount) {
            imap_fetchdata(mp, NULL, expected, NEED_HEADER, NULL, 0, 0);
            continue;
         }
      }
      imap_fetchdata(mp, &mt, expected, NEED_HEADER, NULL, 0, 0);
      if (n >= 0 && !(m[n-1].m_content_info & CI_HAVE_HEADER))
         commitmsg(mp, &m[n-1], &mt, CI_HAVE_HEADER);
      if(n == -1 &&
            mx_socket_getline(&imapbuf, &imapbufsize, NULL, mp->mb_sock) > 0){
         if (n_poption & n_PO_VV)
            fputs(imapbuf, stderr);
         if ((cp = su_cs_find_case(imapbuf, "UID ")) != NULL) {
            u64 uid;

            su_idec_u64_cp(&uid, &cp[4], 10, NULL);/* TODO errors? */
            for (n = bot; n <= topp; n++)
               if (uid == m[n-1].m_uid)
                  break;
            if (n <= topp && !(m[n-1].m_content_info & CI_HAVE_HEADER))
               commitmsg(mp, &m[n-1], &mt, CI_HAVE_HEADER);
         }
      }
      srelax();
   }
   srelax_rele();

   while (mp->mb_active & MB_COMD)
      ok = imap_answer(mp, 1);
   return ok;
}

FL void
imap_getheaders(int volatile bot, int volatile topp) /* TODO iterator!! */
{
   n_sighdl_t saveint, savepipe;
   /*enum okay ok = STOP;*/
   int i, chunk = 256;
   NYD;

   if (mb.mb_type == MB_CACHE)
      return;
   if (bot < 1)
      bot = 1;
   if (topp > msgCount)
      topp = msgCount;
   for (i = bot; i < topp; i++) {
      if ((message[i-1].m_content_info & CI_HAVE_HEADER) ||
            getcache(&mb, &message[i-1], NEED_HEADER) == OKAY)
         bot = i+1;
      else
         break;
   }
   for (i = topp; i > bot; i--) {
      if ((message[i-1].m_content_info & CI_HAVE_HEADER) ||
            getcache(&mb, &message[i-1], NEED_HEADER) == OKAY)
         topp = i-1;
      else
         break;
   }
   if (bot >= topp)
      return;

   imaplock = 1;
   if ((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
      safe_signal(SIGINT, &_imap_maincatch);
   savepipe = safe_signal(SIGPIPE, SIG_IGN);
   if (sigsetjmp(imapjmp, 1) == 0) {
      if (savepipe != SIG_IGN)
         safe_signal(SIGPIPE, imapcatch);

      for (i = bot; i <= topp; i += chunk) {
         int j = i + chunk - 1;
         j = MIN(j, topp);
         if (visible(message + j))
            /*ok = */imap_fetchheaders(&mb, message, i, j);
         if (interrupts)
            n_go_onintr_for_imap(); /* XXX imaplock? */
      }
   }
   safe_signal(SIGINT, saveint);
   safe_signal(SIGPIPE, savepipe);
   imaplock = 0;
}

static enum okay
__imap_exit(struct mailbox *mp)
{
   char o[LINESIZE];
   FILE *queuefp = NULL;
   NYD;

   mp->mb_active |= MB_BYE;
   snprintf(o, sizeof o, "%s LOGOUT\r\n", tag(1));
   IMAP_OUT(o, MB_COMD, return STOP)
   IMAP_ANSWER()
   return OKAY;
}

static enum okay
imap_exit(struct mailbox *mp)
{
   enum okay rv;
   NYD_IN;

   rv = __imap_exit(mp);
#if 0 /* TODO the option today: memory leak(s) and halfway reuse or nottin */
   n_free(mp->mb_imap_pass);
   n_free(mp->mb_imap_account);
   n_free(mp->mb_imap_mailbox);
   if (mp->mb_cache_directory != NULL)
      n_free(mp->mb_cache_directory);
#ifndef mx_HAVE_DEBUG /* TODO ASSERT LEGACY */
   mp->mb_imap_account =
   mp->mb_imap_mailbox =
   mp->mb_cache_directory = "";
#else
   mp->mb_imap_account = NULL; /* for ASSERT legacy time.. */
   mp->mb_imap_mailbox = NULL;
   mp->mb_cache_directory = NULL;
#endif
#endif
   if(mp->mb_sock != NIL){
      if(mp->mb_sock->s_fd >= 0)
         mx_socket_close(mp->mb_sock);
      su_FREE(mp->mb_sock);
      mp->mb_sock = NIL;
   }
   NYD_OU;
   return rv;
}

static enum okay
imap_delete(struct mailbox *mp, int n, struct message *m, int needstat)
{
   NYD_IN;
   imap_store(mp, m, n, '+', "\\Deleted", needstat);
   if (mp->mb_type == MB_IMAP)
      delcache(mp, m);
   NYD_OU;
   return OKAY;
}

static enum okay
imap_close(struct mailbox *mp)
{
   char o[LINESIZE];
   FILE *queuefp = NULL;
   NYD;

   snprintf(o, sizeof o, "%s CLOSE\r\n", tag(1));
   IMAP_OUT(o, MB_COMD, return STOP)
   IMAP_ANSWER()
   return OKAY;
}

static enum okay
imap_update(struct mailbox *mp)
{
   struct message *m;
   int dodel, c, gotcha = 0, held = 0, modflags = 0, needstat, stored = 0;
   NYD_IN;

   if (!(n_pstate & n_PS_EDIT) && mp->mb_perm != 0) {
      holdbits();
      c = 0;
      for (m = message; PCMP(m, <, message + msgCount); ++m)
         if (m->m_flag & MBOX)
            ++c;
      if (c > 0)
         if (makembox() == STOP)
            goto jbypass;
   }

   gotcha = held = 0;
   for (m = message; PCMP(m, <, message + msgCount); ++m) {
      if (mp->mb_perm == 0)
         dodel = 0;
      else if (n_pstate & n_PS_EDIT)
         dodel = ((m->m_flag & MDELETED) != 0);
      else
         dodel = !((m->m_flag & MPRESERVE) || !(m->m_flag & MTOUCH));

      /* Fetch the result after around each 800 STORE commands
       * sent (approx. 32k data sent). Otherwise, servers will
       * try to flush the return queue at some point, leading
       * to a deadlock if we are still writing commands but not
       * reading their results */
      needstat = stored > 0 && stored % 800 == 0;
      /* Even if this message has been deleted, continue
       * to set further flags. This is necessary to support
       * Gmail semantics, where "delete" actually means
       * "archive", and the flags are applied to the copy
       * in "All Mail" */
      if ((m->m_flag & (MREAD | MSTATUS)) == (MREAD | MSTATUS)) {
         imap_store(mp, m, m-message+1, '+', "\\Seen", needstat);
         stored++;
      }
      if (m->m_flag & MFLAG) {
         imap_store(mp, m, m-message+1, '+', "\\Flagged", needstat);
         stored++;
      }
      if (m->m_flag & MUNFLAG) {
         imap_store(mp, m, m-message+1, '-', "\\Flagged", needstat);
         stored++;
      }
      if (m->m_flag & MANSWER) {
         imap_store(mp, m, m-message+1, '+', "\\Answered", needstat);
         stored++;
      }
      if (m->m_flag & MUNANSWER) {
         imap_store(mp, m, m-message+1, '-', "\\Answered", needstat);
         stored++;
      }
      if (m->m_flag & MDRAFT) {
         imap_store(mp, m, m-message+1, '+', "\\Draft", needstat);
         stored++;
      }
      if (m->m_flag & MUNDRAFT) {
         imap_store(mp, m, m-message+1, '-', "\\Draft", needstat);
         stored++;
      }

      if (dodel) {
         imap_delete(mp, m-message+1, m, needstat);
         stored++;
         gotcha++;
      } else if (mp->mb_type != MB_CACHE ||
            (!(n_pstate & n_PS_EDIT) &&
             !(m->m_flag & (MBOXED | MSAVED | MDELETED))) ||
            (m->m_flag & (MBOXED | MPRESERVE | MTOUCH)) ==
               (MPRESERVE | MTOUCH) ||
               ((n_pstate & n_PS_EDIT) && !(m->m_flag & MDELETED)))
         held++;
      if (m->m_flag & MNEW) {
         m->m_flag &= ~MNEW;
         m->m_flag |= MSTATUS;
      }
   }
jbypass:
   if (gotcha)
      imap_close(mp);

   for (m = &message[0]; PCMP(m, <, message + msgCount); ++m)
      if (!(m->m_flag & MUNLINKED) &&
            m->m_flag & (MBOXED | MDELETED | MSAVED | MSTATUS | MFLAG |
               MUNFLAG | MANSWER | MUNANSWER | MDRAFT | MUNDRAFT)) {
         putcache(mp, m);
         modflags++;
      }

   /* XXX should be readonly (but our IMAP code is weird...) */
   if (!(n_poption & (n_PO_EXISTONLY | n_PO_HEADERSONLY | n_PO_HEADERLIST)) &&
         mb.mb_perm != 0) {
      if ((gotcha || modflags) && (n_pstate & n_PS_EDIT)) {
         printf(_("\"%s\" "), displayname);
         printf((ok_blook(bsdcompat) || ok_blook(bsdmsgs))
            ? _("complete\n") : _("updated.\n"));
      } else if (held && !(n_pstate & n_PS_EDIT)) {
         if (held == 1)
            printf(_("Held 1 message in %s\n"), displayname);
         else
            printf(_("Held %d messages in %s\n"), held, displayname);
      }
      fflush(stdout);
   }
   NYD_OU;
   return OKAY;
}

FL boole
imap_quit(boole hold_sigs_on)
{
   n_sighdl_t volatile saveint, savepipe;
   boole rv;
   NYD_IN;

   if(hold_sigs_on)
      rele_sigs();

   if (mb.mb_type == MB_CACHE) {
      rv = (imap_update(&mb) == OKAY);
      goto jleave;
   }

   rv = FAL0;

   if(mb.mb_sock == NIL || mb.mb_sock->s_fd < 0){
      n_err(_("IMAP connection closed\n"));
      goto jleave;
   }

   imaplock = 1;
   saveint = safe_signal(SIGINT, SIG_IGN);
   savepipe = safe_signal(SIGPIPE, SIG_IGN);
   if (sigsetjmp(imapjmp, 1)) {
      safe_signal(SIGINT, saveint);
      safe_signal(SIGPIPE, saveint);
      imaplock = 0;
      goto jleave;
   }
   if (saveint != SIG_IGN)
      safe_signal(SIGINT, imapcatch);
   if (savepipe != SIG_IGN)
      safe_signal(SIGPIPE, imapcatch);

   rv = (imap_update(&mb) == OKAY);
   if(!same_imap_account && imap_exit(&mb) != OKAY)
      rv = FAL0;

   safe_signal(SIGINT, saveint);
   safe_signal(SIGPIPE, savepipe);
   imaplock = 0;
jleave:
   if(hold_sigs_on)
      hold_sigs();
   NYD_OU;
   return rv;
}

static enum okay
imap_store(struct mailbox *mp, struct message *m, int n, int c, const char *xsp,
   int needstat)
{
   char o[LINESIZE];
   FILE *queuefp = NULL;
   NYD;

   if (mp->mb_type == MB_CACHE && (queuefp = cache_queue(mp)) == NULL)
      return STOP;
   if (m->m_uid)
      snprintf(o, sizeof o, "%s UID STORE %" PRIu64 " %cFLAGS (%s)\r\n",
         tag(1), m->m_uid, c, xsp);
   else {
      if (check_expunged() == STOP)
         return STOP;
      snprintf(o, sizeof o, "%s STORE %u %cFLAGS (%s)\r\n", tag(1), n, c, xsp);
   }
   IMAP_OUT(o, MB_COMD, return STOP)
   if (needstat)
      IMAP_ANSWER()
   else
      mb.mb_active &= ~MB_COMD;

   if(queuefp != NIL)
      mx_fs_close(queuefp);
   return OKAY;
}

FL enum okay
imap_undelete(struct message *m, int n)
{
   enum okay rv;
   NYD_IN;

   rv = imap_unstore(m, n, "\\Deleted");
   NYD_OU;
   return rv;
}

FL enum okay
imap_unread(struct message *m, int n)
{
   enum okay rv;
   NYD_IN;

   rv = imap_unstore(m, n, "\\Seen");
   NYD_OU;
   return rv;
}

static enum okay
imap_unstore(struct message *m, int n, const char *flag)
{
   n_sighdl_t saveint, savepipe;
   enum okay volatile rv = STOP;
   NYD_IN;

   imaplock = 1;
   if ((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
      safe_signal(SIGINT, &_imap_maincatch);
   savepipe = safe_signal(SIGPIPE, SIG_IGN);
   if (sigsetjmp(imapjmp, 1) == 0) {
      if (savepipe != SIG_IGN)
         safe_signal(SIGPIPE, imapcatch);

      rv = imap_store(&mb, m, n, '-', flag, 1);
   }
   safe_signal(SIGINT, saveint);
   safe_signal(SIGPIPE, savepipe);
   imaplock = 0;

   NYD_OU;
   if (interrupts)
      n_go_onintr_for_imap();
   return rv;
}

static const char *
tag(int new)
{
   static char ts[20];
   static long n;
   NYD2_IN;

   if (new)
      ++n;
   snprintf(ts, sizeof ts, "T%lu", n);
   NYD2_OU;
   return ts;
}

FL int
c_imapcodec(void *vp){
   boole err;
   uz alen;
   char const **argv, *varname, *varres, *act, *cp;
   NYD_IN;

   argv = vp;
   varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;

   act = *argv;
   for(cp = act; *cp != '\0' && !su_cs_is_space(*cp); ++cp)
      ;
   if(act == cp)
      goto jesynopsis;
   alen = P2UZ(cp - act);
   if(*cp != '\0')
      ++cp;

   n_pstate_err_no = su_ERR_NONE;
   varres = a_imap_path_normalize(NULL, cp, TRU1);

   if(su_cs_starts_with_case_n("encode", act, alen))
      varres = imap_path_encode(varres, &err);
   else if(su_cs_starts_with_case_n("decode", act, alen))
      varres = imap_path_decode(varres, &err);
   else
      goto jesynopsis;

   if(err){
      n_pstate_err_no = su_ERR_CANCELED;
      varres = cp;
      vp = NULL;
   }

   if(varname != NULL){
      if(!n_var_vset(varname, (up)varres)){
         n_pstate_err_no = su_ERR_NOTSUP;
         vp = NULL;
      }
   }else{
      struct str in, out;

      in.l = su_cs_len(in.s = n_UNCONST(varres));
      makeprint(&in, &out);
      if(fprintf(n_stdout, "%s\n", out.s) < 0){
         n_pstate_err_no = su_err_no();
         vp = NULL;
      }
      n_free(out.s);
   }

jleave:
   NYD_OU;
   return (vp != NULL ? 0 : 1);
jesynopsis:
   n_err(_("Synopsis: imapcodec:  \n"));
   n_pstate_err_no = su_ERR_INVAL;
   vp = NULL;
   goto jleave;
}

FL int
c_imap_imap(void *vp)
{
   char o[LINESIZE];
   n_sighdl_t saveint, savepipe;
   struct mailbox *mp = &mb;
   FILE *queuefp = NULL;
   enum okay volatile ok = STOP;
   NYD;

   if (mp->mb_type != MB_IMAP) {
      printf("Not operating on an IMAP mailbox.\n");
      return 1;
   }
   imaplock = 1;
   if ((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
      safe_signal(SIGINT, &_imap_maincatch);
   savepipe = safe_signal(SIGPIPE, SIG_IGN);
   if (sigsetjmp(imapjmp, 1) == 0) {
      if (savepipe != SIG_IGN)
         safe_signal(SIGPIPE, imapcatch);

      snprintf(o, sizeof o, "%s %s\r\n", tag(1), (char *)vp);
      IMAP_OUT(o, MB_COMD, goto out)
      while (mp->mb_active & MB_COMD) {
         ok = imap_answer(mp, 0);
         fputs(responded_text, stdout);
      }
   }
out:
   safe_signal(SIGINT, saveint);
   safe_signal(SIGPIPE, savepipe);
   imaplock = 0;

   if (interrupts)
      n_go_onintr_for_imap();
   return ok != OKAY;
}

FL int
imap_newmail(int nmail)
{
   NYD_IN;

   if (nmail && had_exists < 0 && had_expunge < 0) {
      imaplock = 1;
      imap_noop();
      imaplock = 0;
   }

   if (had_exists == msgCount && had_expunge < 0)
      /* Some servers always respond with EXISTS to NOOP. If
       * the mailbox has been changed but the number of messages
       * has not, an EXPUNGE must also had been sent; otherwise,
       * nothing has changed */
      had_exists = -1;
   NYD_OU;
   return (had_expunge >= 0 ? 2 : (had_exists >= 0 ? 1 : 0));
}

static char *
imap_putflags(int f)
{
   const char *cp;
   char *buf, *bp;
   NYD2_IN;

   bp = buf = n_autorec_alloc(100);
   if (f & (MREAD | MFLAGGED | MANSWERED | MDRAFTED)) {
      *bp++ = '(';
      if (f & MREAD) {
         if (bp[-1] != '(')
            *bp++ = ' ';
         for (cp = "\\Seen"; *cp; cp++)
            *bp++ = *cp;
      }
      if (f & MFLAGGED) {
         if (bp[-1] != '(')
            *bp++ = ' ';
         for (cp = "\\Flagged"; *cp; cp++)
            *bp++ = *cp;
      }
      if (f & MANSWERED) {
         if (bp[-1] != '(')
            *bp++ = ' ';
         for (cp = "\\Answered"; *cp; cp++)
            *bp++ = *cp;
      }
      if (f & MDRAFT) {
         if (bp[-1] != '(')
            *bp++ = ' ';
         for (cp = "\\Draft"; *cp; cp++)
            *bp++ = *cp;
      }
      *bp++ = ')';
      *bp++ = ' ';
   }
   *bp = '\0';
   NYD2_OU;
   return buf;
}

static void
imap_getflags(const char *cp, char const **xp, enum mflag *f)
{
   NYD2_IN;
   while (*cp != ')') {
      if (*cp == '\\') {
         if (su_cs_cmp_case_n(cp, "\\Seen", 5) == 0)
            *f |= MREAD;
         else if (su_cs_cmp_case_n(cp, "\\Recent", 7) == 0)
            *f |= MNEW;
         else if (su_cs_cmp_case_n(cp, "\\Deleted", 8) == 0)
            *f |= MDELETED;
         else if (su_cs_cmp_case_n(cp, "\\Flagged", 8) == 0)
            *f |= MFLAGGED;
         else if (su_cs_cmp_case_n(cp, "\\Answered", 9) == 0)
            *f |= MANSWERED;
         else if (su_cs_cmp_case_n(cp, "\\Draft", 6) == 0)
            *f |= MDRAFTED;
      }
      cp++;
   }

   if (xp != NULL)
      *xp = cp;
   NYD2_OU;
}

static enum okay
imap_append1(struct mailbox *mp, const char *name, FILE *fp, off_t off1,
   long xsize, enum mflag flag, time_t t)
{
   char o[LINESIZE], *buf;
   uz bufsize, buflen, cnt;
   long size, lines, ysize;
   char const *qname;
   boole twice;
   FILE *queuefp;
   enum okay rv;
   NYD_IN;

   rv = STOP;
   queuefp = NULL;
   twice = FAL0;
   buf = NULL;

   if((qname = imap_path_quote(mp, name)) == NULL)
      goto jleave;

   if (mp->mb_type == MB_CACHE) {
      queuefp = cache_queue(mp);
      if (queuefp == NULL) {
         buf = NULL;
         goto jleave;
      }
      rv = OKAY;
   }

   buf = n_alloc(bufsize = LINESIZE);
   buflen = 0;
jagain:
   size = xsize;
   cnt = fsize(fp);
   if (fseek(fp, off1, SEEK_SET) < 0) {
      rv = STOP;
      goto jleave;
   }

   snprintf(o, sizeof o, "%s APPEND %s %s%s {%ld}\r\n",
         tag(1), qname, imap_putflags(flag), imap_make_date_time(t), size);
   IMAP_XOUT(o, MB_COMD, goto jleave, rv=STOP;goto jleave)
   while (mp->mb_active & MB_COMD) {
      rv = imap_answer(mp, twice);
      if (response_type == RESPONSE_CONT)
         break;
   }

   if (mp->mb_type != MB_CACHE && rv == STOP) {
      if (!twice)
         goto jtrycreate;
      else
         goto jleave;
   }

   lines = ysize = 0;
   while (size > 0) {
      fgetline(&buf, &bufsize, &cnt, &buflen, fp, 1);
      lines++;
      ysize += buflen;
      buf[buflen - 1] = '\r';
      buf[buflen] = '\n';
      if (mp->mb_type != MB_CACHE)
         mx_socket_write1(mp->mb_sock, buf, buflen+1, 1);
      else if (queuefp)
         fwrite(buf, 1, buflen+1, queuefp);
      size -= buflen + 1;
   }
   if (mp->mb_type != MB_CACHE)
      mx_socket_write(mp->mb_sock, "\r\n");
   else if (queuefp)
      fputs("\r\n", queuefp);
   while (mp->mb_active & MB_COMD) {
      rv = imap_answer(mp, 0);
      if (response_status == RESPONSE_NO /*&&
            su_cs_cmp_case_n(responded_text,
               "[TRYCREATE] ", 12) == 0*/) {
jtrycreate:
         if (twice) {
            rv = STOP;
            goto jleave;
         }
         twice = TRU1;
         snprintf(o, sizeof o, "%s CREATE %s\r\n", tag(1), qname);
         IMAP_XOUT(o, MB_COMD, goto jleave, rv=STOP;goto jleave)
         while (mp->mb_active & MB_COMD)
            rv = imap_answer(mp, 1);
         if (rv == STOP)
            goto jleave;
         imap_created_mailbox++;
         goto jagain;
      } else if (rv != OKAY)
         n_err(_("IMAP error: %s"), responded_text);
      else if (response_status == RESPONSE_OK && (mp->mb_flags & MB_UIDPLUS))
         imap_appenduid(mp, fp, t, off1, xsize, ysize, lines, flag, name);
   }

jleave:
   if(queuefp != NIL)
      mx_fs_close(queuefp);
   if(buf != NIL)
      n_free(buf);
   NYD_OU;
   return rv;
}

static enum okay
imap_append0(struct mailbox *mp, const char *name, FILE *fp, long offset)
{
   char *buf, *bp, *lp;
   uz bufsize, buflen, cnt;
   off_t off1 = -1, offs;
   int flag;
   enum {_NONE = 0, _INHEAD = 1<<0, _NLSEP = 1<<1} state;
   time_t tim;
   long size;
   enum okay rv;
   NYD_IN;

   buf = n_alloc(bufsize = LINESIZE);
   buflen = 0;
   cnt = fsize(fp);
   offs = offset /* BSD will move due to O_APPEND! ftell(fp) */;
   time(&tim);
   size = 0;

   for (flag = MNEW, state = _NLSEP;;) {
      bp = fgetline(&buf, &bufsize, &cnt, &buflen, fp, 1);

      if (bp == NULL ||
            ((state & (_INHEAD | _NLSEP)) == _NLSEP &&
             is_head(buf, buflen, FAL0))) {
         if (off1 != (off_t)-1) {
            rv = imap_append1(mp, name, fp, off1, size, flag, tim);
            if (rv == STOP)
               goto jleave;
            fseek(fp, offs+buflen, SEEK_SET);
         }
         off1 = offs + buflen;
         size = 0;
         flag = MNEW;
         state = _INHEAD;
         if (bp == NULL)
            break;
         tim = unixtime(buf);
      } else
         size += buflen+1;
      offs += buflen;

      state &= ~_NLSEP;
      if (buf[0] == '\n') {
         state &= ~_INHEAD;
         state |= _NLSEP;
      } else if (state & _INHEAD) {
         if (su_cs_cmp_case_n(buf, "status", 6) == 0) {
            lp = &buf[6];
            while (su_cs_is_white(*lp))
               lp++;
            if (*lp == ':')
               while (*++lp != '\0')
                  switch (*lp) {
                  case 'R':
                     flag |= MREAD;
                     break;
                  case 'O':
                     flag &= ~MNEW;
                     break;
                  }
         } else if (su_cs_cmp_case_n(buf, "x-status", 8) == 0) {
            lp = &buf[8];
            while (su_cs_is_white(*lp))
               lp++;
            if (*lp == ':')
               while (*++lp != '\0')
                  switch (*lp) {
                  case 'F':
                     flag |= MFLAGGED;
                     break;
                  case 'A':
                     flag |= MANSWERED;
                     break;
                  case 'T':
                     flag |= MDRAFTED;
                     break;
                  }
         }
      }
   }
   rv = OKAY;
jleave:
   n_free(buf);
   NYD_OU;
   return rv;
}

FL enum okay
imap_append(const char *xserver, FILE *fp, long offset)
{
   n_sighdl_t volatile saveint, savepipe;
   struct mx_url url;
   struct mx_cred_ctx ccred;
   enum okay volatile rv = STOP;
   NYD_IN;

   if(!mx_url_parse(&url, CPROTO_IMAP, xserver))
      goto j_leave;
   if(ok_vlook(v15_compat) == NIL &&
         (!(url.url_flags & mx_URL_HAD_USER) || url.url_pass.s != NIL))
      n_err(_("New-style URL used without *v15-compat* being set!\n"));
   ASSERT(url.url_path.s != NULL);

   imaplock = 1;
   if ((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
      safe_signal(SIGINT, &_imap_maincatch);
   savepipe = safe_signal(SIGPIPE, SIG_IGN);
   if (sigsetjmp(imapjmp, 1))
      goto jleave;
   if (savepipe != SIG_IGN)
      safe_signal(SIGPIPE, imapcatch);

   if((mb.mb_type == MB_CACHE ||
         (mb.mb_sock != NIL && mb.mb_sock->s_fd > 0)) &&
         mb.mb_imap_account &&
         !su_cs_cmp(url.url_p_eu_h_p, mb.mb_imap_account)) {
      rv = imap_append0(&mb, url.url_path.s, fp, offset);
   } else {
      struct mailbox mx;

      su_mem_set(&mx, 0, sizeof mx);

      if (!_imap_getcred(&mx, &ccred, &url))
         goto jleave;

      imap_delim_init(&mx, &url);
      mx.mb_imap_mailbox = su_cs_dup(
            a_imap_path_normalize(&mx, url.url_path.s, FAL0), 0);

      if(disconnected(url.url_p_eu_h_p) == 0){
         mx.mb_sock = su_TALLOC(struct mx_socket, 1);
         if(!mx_socket_open(mx.mb_sock, &url)){
            su_FREE(mx.mb_sock);
            goto jfail;
         }
         mx.mb_sock->s_desc = "IMAP";
         mx.mb_type = MB_IMAP;
         mx.mb_imap_account = n_UNCONST(url.url_p_eu_h_p);
         /* TODO the code now did
          * TODO mx.mb_imap_mailbox = mbx->url.url_patth.s;
          * TODO though imap_mailbox is sfree()d and mbx
          * TODO is possibly even a constant
          * TODO i changed this to su_cs_dup() sofar, as is used
          * TODO somewhere else in this file for this! */
         if(imap_preauth(&mx, &url, &ccred) != OKAY ||
               a_imap_auth(&mx, &url, &ccred) != OKAY){
            mx_socket_close(mx.mb_sock);
            su_FREE(mx.mb_sock);
            goto jfail;
         }
         rv = imap_append0(&mx, url.url_path.s, fp, offset);
         imap_exit(&mx);
      } else {
         mx.mb_imap_account = n_UNCONST(url.url_p_eu_h_p);
         mx.mb_type = MB_CACHE;
         rv = imap_append0(&mx, url.url_path.s, fp, offset);
      }
jfail:
      ;
   }

jleave:
   safe_signal(SIGINT, saveint);
   safe_signal(SIGPIPE, savepipe);
   imaplock = 0;
j_leave:
   NYD_OU;
   if (interrupts)
      n_go_onintr_for_imap();
   return rv;
}

static enum okay
imap_list1(struct mailbox *mp, const char *base, struct list_item **list,
   struct list_item **lend, int level)
{
   char o[LINESIZE], *cp;
   struct list_item *lp;
   const char *qname, *bp;
   FILE *queuefp;
   enum okay ok;
   NYD;

   ok = STOP;
   queuefp = NULL;

   if((qname = imap_path_quote(mp, base)) == NULL)
      goto jleave;

   *list = *lend = NULL;
   snprintf(o, sizeof o, "%s LIST %s %%\r\n", tag(1), qname);
   IMAP_OUT(o, MB_COMD, goto jleave)
   while (mp->mb_active & MB_COMD) {
      ok = imap_answer(mp, 1);
      if (response_status == RESPONSE_OTHER &&
            response_other == MAILBOX_DATA_LIST && imap_parse_list() == OKAY) {
         cp = imap_path_decode(imap_unquotestr(list_name), NULL);
         lp = n_autorec_calloc(1, sizeof *lp);
         lp->l_name = cp;
         for (bp = base; *bp != '\0' && *bp == *cp; ++bp)
            ++cp;
         lp->l_base = *cp ? cp : savestr(base);
         lp->l_attr = list_attributes;
         lp->l_level = level+1;
         lp->l_delim = list_hierarchy_delimiter;
         if (*list && *lend) {
            (*lend)->l_next = lp;
            *lend = lp;
         } else
            *list = *lend = lp;
      }
   }
jleave:
   return ok;
}

static enum okay
imap_list(struct mailbox *mp, const char *base, int strip, FILE *fp)
{
   struct list_item *list, *lend, *lp, *lx, *ly;
   int n, depth;
   const char *bp;
   char *cp;
   enum okay rv;
   NYD_IN;

   depth = (cp = ok_vlook(imap_list_depth)) != NULL ? atoi(cp) : 2;
   if ((rv = imap_list1(mp, base, &list, &lend, 0)) == STOP)
      goto jleave;
   rv = OKAY;
   if (list == NULL || lend == NULL)
      goto jleave;

   for (lp = list; lp; lp = lp->l_next)
      if (lp->l_delim != '/' && lp->l_delim != EOF && lp->l_level < depth &&
            !(lp->l_attr & LIST_NOINFERIORS)) {
         cp = n_autorec_alloc((n = su_cs_len(lp->l_name)) + 2);
         su_mem_copy(cp, lp->l_name, n);
         cp[n] = lp->l_delim;
         cp[n+1] = '\0';
         if (imap_list1(mp, cp, &lx, &ly, lp->l_level) == OKAY && lx && ly) {
            lp->l_has_children = 1;
            if (su_cs_cmp(cp, lx->l_name) == 0)
               lx = lx->l_next;
            if (lx) {
               lend->l_next = lx;
               lend = ly;
            }
         }
      }

   for (lp = list; lp; lp = lp->l_next) {
      if (strip) {
         cp = lp->l_name;
         for (bp = base; *bp && *bp == *cp; bp++)
            cp++;
      } else
         cp = lp->l_name;
      if (!(lp->l_attr & LIST_NOSELECT))
         fprintf(fp, "%s\n", *cp ? cp : base);
      else if (lp->l_has_children == 0)
         fprintf(fp, "%s%c\n", *cp ? cp : base,
            (lp->l_delim != EOF ? lp->l_delim : '\n'));
   }
jleave:
   NYD_OU;
   return rv;
}

FL int
imap_folders(const char * volatile name, int strip)
{
   n_sighdl_t saveint, savepipe;
   const char * volatile fold, *cp, *xsp;
   FILE * volatile fp;
   int volatile rv = 1;
   NYD_IN;

   cp = protbase(name);
   xsp = mb.mb_imap_account;
   if (xsp == NULL || su_cs_cmp(cp, xsp)) {
      n_err(
         _("Cannot perform `folders' but when on the very IMAP "
         "account; the current one is\n  `%s' -- "
         "try `folders @'\n"),
         (xsp != NULL ? xsp : _("[NONE]")));
      goto jleave;
   }

   fold = imap_fileof(name);
   if(n_psonce & n_PSO_TTYOUT){
      if((fp = mx_fs_tmp_open("imapfold", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
               mx_FS_O_REGISTER), NIL)) == NIL){
         n_perr(_("tmpfile"), 0);
         goto jleave;
      }
   } else
      fp = stdout;

   imaplock = 1;
   if ((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
      safe_signal(SIGINT, &_imap_maincatch);
   savepipe = safe_signal(SIGPIPE, SIG_IGN);
   if (sigsetjmp(imapjmp, 1)) /* TODO imaplock? */
      goto junroll;
   if (savepipe != SIG_IGN)
      safe_signal(SIGPIPE, imapcatch);

   if (mb.mb_type == MB_CACHE)
      cache_list(&mb, fold, strip, fp);
   else
      imap_list(&mb, fold, strip, fp);

   imaplock = 0;
   if (interrupts) {
      if(n_psonce & n_PSO_TTYOUT)
         mx_fs_close(fp);
      rv = 0;
      goto jleave;
   }
   fflush(fp);

   if (n_psonce & n_PSO_TTYOUT) {
      rewind(fp);
      if (fsize(fp) > 0){
         page_or_print(fp, 0);
         rv = 0;
      }else
         n_err(_("Folder not found\n"));
   }else
      rv = 0;

junroll:
   safe_signal(SIGINT, saveint);
   safe_signal(SIGPIPE, savepipe);
   if(n_psonce & n_PSO_TTYOUT)
      mx_fs_close(fp);

jleave:
   NYD_OU;
   if (interrupts)
      n_go_onintr_for_imap();
   return rv;
}

static enum okay
imap_copy1(struct mailbox *mp, struct message *m, int n, const char *name)
{
   char o[LINESIZE];
   const char *qname;
   boole twice, stored;
   FILE *queuefp;
   enum okay ok;
   NYD;

   ok = STOP;
   queuefp = NULL;
   twice = stored = FAL0;

   /* C99 */{
      uz i;

      i = su_cs_len(name = imap_fileof(name));
      if(i == 0 || (i > 0 && name[i - 1] == '/'))
         name = savecat(name, "INBOX");
      if((qname = imap_path_quote(mp, name)) == NULL)
         goto jleave;
   }

   if (mp->mb_type == MB_CACHE) {
      if ((queuefp = cache_queue(mp)) == NULL)
         goto jleave;
      ok = OKAY;
   }

   /* Since it is not possible to set flags on the copy, recently
    * set flags must be set on the original to include it in the copy */
   if ((m->m_flag & (MREAD | MSTATUS)) == (MREAD | MSTATUS))
      imap_store(mp, m, n, '+', "\\Seen", 0);
   if (m->m_flag&MFLAG)
      imap_store(mp, m, n, '+', "\\Flagged", 0);
   if (m->m_flag&MUNFLAG)
      imap_store(mp, m, n, '-', "\\Flagged", 0);
   if (m->m_flag&MANSWER)
      imap_store(mp, m, n, '+', "\\Answered", 0);
   if (m->m_flag&MUNANSWER)
      imap_store(mp, m, n, '-', "\\Flagged", 0);
   if (m->m_flag&MDRAFT)
      imap_store(mp, m, n, '+', "\\Draft", 0);
   if (m->m_flag&MUNDRAFT)
      imap_store(mp, m, n, '-', "\\Draft", 0);
again:
   if (m->m_uid)
      snprintf(o, sizeof o, "%s UID COPY %" PRIu64 " %s\r\n",
         tag(1), m->m_uid, qname);
   else {
      if (check_expunged() == STOP)
         goto out;
      snprintf(o, sizeof o, "%s COPY %u %s\r\n", tag(1), n, qname);
   }
   IMAP_OUT(o, MB_COMD, goto out)
   while (mp->mb_active & MB_COMD)
      ok = imap_answer(mp, twice);

   if (mp->mb_type == MB_IMAP && mp->mb_flags & MB_UIDPLUS &&
         response_status == RESPONSE_OK)
      imap_copyuid(mp, m, name);

   if (response_status == RESPONSE_NO && !twice) {
      snprintf(o, sizeof o, "%s CREATE %s\r\n", tag(1), qname);
      IMAP_OUT(o, MB_COMD, goto out)
      while (mp->mb_active & MB_COMD)
         ok = imap_answer(mp, 1);
      if (ok == OKAY) {
         imap_created_mailbox++;
         goto again;
      }
   }

   if(queuefp != NIL)
      mx_fs_close(queuefp);

   /* ... and reset the flag to its initial value so that the 'exit'
    * command still leaves the message unread */
out:
   if ((m->m_flag & (MREAD | MSTATUS)) == (MREAD | MSTATUS)) {
      imap_store(mp, m, n, '-', "\\Seen", 0);
      stored = TRU1;
   }
   if (m->m_flag & MFLAG) {
      imap_store(mp, m, n, '-', "\\Flagged", 0);
      stored = TRU1;
   }
   if (m->m_flag & MUNFLAG) {
      imap_store(mp, m, n, '+', "\\Flagged", 0);
      stored = TRU1;
   }
   if (m->m_flag & MANSWER) {
      imap_store(mp, m, n, '-', "\\Answered", 0);
      stored = TRU1;
   }
   if (m->m_flag & MUNANSWER) {
      imap_store(mp, m, n, '+', "\\Answered", 0);
      stored = TRU1;
   }
   if (m->m_flag & MDRAFT) {
      imap_store(mp, m, n, '-', "\\Draft", 0);
      stored = TRU1;
   }
   if (m->m_flag & MUNDRAFT) {
      imap_store(mp, m, n, '+', "\\Draft", 0);
      stored = TRU1;
   }
   if (stored) {
      mp->mb_active |= MB_COMD;
      (void)imap_finish(mp);
   }
jleave:
   return ok;
}

FL enum okay
imap_copy(struct message *m, int n, const char *name)
{
   n_sighdl_t saveint, savepipe;
   enum okay volatile rv = STOP;
   NYD_IN;

   imaplock = 1;
   if ((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
      safe_signal(SIGINT, &_imap_maincatch);
   savepipe = safe_signal(SIGPIPE, SIG_IGN);
   if (sigsetjmp(imapjmp, 1) == 0) {
      if (savepipe != SIG_IGN)
         safe_signal(SIGPIPE, imapcatch);

      rv = imap_copy1(&mb, m, n, name);
   }
   safe_signal(SIGINT, saveint);
   safe_signal(SIGPIPE, savepipe);
   imaplock = 0;

   NYD_OU;
   if (interrupts)
      n_go_onintr_for_imap();
   return rv;
}

static enum okay
imap_copyuid_parse(const char *cp, u64 *uidvalidity, u64 *olduid,
   u64 *newuid)
{
   char const *xp, *yp, *zp;
   enum okay rv;
   NYD_IN;

   su_idec_u64_cp(uidvalidity, cp, 10, &xp); /* TODO errors */
   su_idec_u64_cp(olduid, xp, 10, &yp); /* TODO errors */
   su_idec_u64_cp(newuid, yp, 10, &zp); /* TODO errors */
   rv = (*uidvalidity && *olduid && *newuid && xp > cp && *xp == ' ' &&
      yp > xp && *yp == ' ' && zp > yp && *zp == ']');
   NYD_OU;
   return rv;
}

static enum okay
imap_appenduid_parse(const char *cp, u64 *uidvalidity, u64 *uid)
{
   char const *xp, *yp;
   enum okay rv;
   NYD_IN;

   su_idec_u64_cp(uidvalidity, cp, 10, &xp); /* TODO errors */
   su_idec_u64_cp(uid, xp, 10, &yp); /* TODO errors */
   rv = (*uidvalidity && *uid && xp > cp && *xp == ' ' && yp > xp &&
      *yp == ']');
   NYD_OU;
   return rv;
}

static enum okay
imap_copyuid(struct mailbox *mp, struct message *m, const char *name)
{
   struct mailbox xmb;
   struct message xm;
   const char *cp;
   u64 uidvalidity, olduid, newuid;
   enum okay rv;
   NYD_IN;

   rv = STOP;

   su_mem_set(&xmb, 0, sizeof xmb);

   if ((cp = su_cs_find_case(responded_text, "[COPYUID ")) == NULL ||
         imap_copyuid_parse(&cp[9], &uidvalidity, &olduid, &newuid) == STOP)
      goto jleave;

   rv = OKAY;

   xmb = *mp;
   xmb.mb_cache_directory = NULL;
   xmb.mb_imap_account = su_cs_dup(mp->mb_imap_account, 0);
   xmb.mb_imap_pass = su_cs_dup(mp->mb_imap_pass, 0);
   su_mem_copy(&xmb.mb_imap_delim[0], &mp->mb_imap_delim[0],
      sizeof(xmb.mb_imap_delim));
   xmb.mb_imap_mailbox = su_cs_dup(a_imap_path_normalize(&xmb, name, FAL0), 0);
   if (mp->mb_cache_directory != NULL)
      xmb.mb_cache_directory = su_cs_dup(mp->mb_cache_directory, 0);
   xmb.mb_uidvalidity = uidvalidity;
   initcache(&xmb);

   if (m == NULL) {
      su_mem_set(&xm, 0, sizeof xm);
      xm.m_uid = olduid;
      if ((rv = getcache1(mp, &xm, NEED_UNSPEC, 3)) != OKAY)
         goto jleave;
      getcache(mp, &xm, NEED_HEADER);
      getcache(mp, &xm, NEED_BODY);
   } else {
      if ((m->m_content_info & CI_HAVE_HEADER) == 0)
         getcache(mp, m, NEED_HEADER);
      if ((m->m_content_info & CI_HAVE_BODY) == 0)
         getcache(mp, m, NEED_BODY);
      xm = *m;
   }
   xm.m_uid = newuid;
   xm.m_flag &= ~MFULLYCACHED;
   putcache(&xmb, &xm);
jleave:
   if (xmb.mb_cache_directory != NULL)
      n_free(xmb.mb_cache_directory);
   if (xmb.mb_imap_mailbox != NULL)
      n_free(xmb.mb_imap_mailbox);
   if (xmb.mb_imap_pass != NULL)
      n_free(xmb.mb_imap_pass);
   if (xmb.mb_imap_account != NULL)
      n_free(xmb.mb_imap_account);
   NYD_OU;
   return rv;
}

static enum okay
imap_appenduid(struct mailbox *mp, FILE *fp, time_t t, long off1, long xsize,
   long size, long lines, int flag, const char *name)
{
   struct mailbox xmb;
   struct message xm;
   const char *cp;
   u64 uidvalidity, uid;
   enum okay rv;
   NYD_IN;

   rv = STOP;

   if ((cp = su_cs_find_case(responded_text, "[APPENDUID ")) == NULL ||
         imap_appenduid_parse(&cp[11], &uidvalidity, &uid) == STOP)
      goto jleave;

   rv = OKAY;

   xmb = *mp;
   xmb.mb_cache_directory = NULL;
   /* XXX mb_imap_delim reused */
   xmb.mb_imap_mailbox = su_cs_dup(a_imap_path_normalize(&xmb, name, FAL0), 0);
   xmb.mb_uidvalidity = uidvalidity;
   xmb.mb_otf = xmb.mb_itf = fp;
   initcache(&xmb);
   su_mem_set(&xm, 0, sizeof xm);
   xm.m_flag = (flag & MREAD) | MNEW;
   xm.m_time = t;
   xm.m_block = mailx_blockof(off1);
   xm.m_offset = mailx_offsetof(off1);
   xm.m_size = size;
   xm.m_xsize = xsize;
   xm.m_lines = xm.m_xlines = lines;
   xm.m_uid = uid;
   xm.m_content_info = CI_HAVE_HEADER | CI_HAVE_BODY;
   putcache(&xmb, &xm);

   n_free(xmb.mb_imap_mailbox);
jleave:
   NYD_OU;
   return rv;
}

static enum okay
imap_appenduid_cached(struct mailbox *mp, FILE *fp)
{
   FILE *tp = NULL;
   time_t t;
   long size, xsize, ysize, lines;
   enum mflag flag = MNEW;
   char *name, *buf, *bp;
   char const *cp;
   uz bufsize, buflen, cnt;
   enum okay rv = STOP;
   NYD_IN;

   buf = n_alloc(bufsize = LINESIZE);
   buflen = 0;
   cnt = fsize(fp);
   if (fgetline(&buf, &bufsize, &cnt, &buflen, fp, 0) == NULL)
      goto jstop;

   for (bp = buf; *bp != ' '; ++bp) /* strip old tag */
      ;
   while (*bp == ' ')
      ++bp;

   if ((cp = su_cs_rfind_c(bp, '{')) == NULL)
      goto jstop;

   xsize = atol(&cp[1]) + 2;
   if ((name = imap_strex(&bp[7], &cp)) == NULL)
      goto jstop;
   while (*cp == ' ')
      cp++;

   if (*cp == '(') {
      imap_getflags(cp, &cp, &flag);
      while (*++cp == ' ')
         ;
   }
   t = imap_read_date_time(cp);

   if((tp = mx_fs_tmp_open("imapapui", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
            mx_FS_O_REGISTER), NIL)) == NIL)
      goto jstop;

   size = xsize;
   ysize = lines = 0;
   while (size > 0) {
      if (fgetline(&buf, &bufsize, &cnt, &buflen, fp, 0) == NULL)
         goto jstop;
      size -= buflen;
      buf[--buflen] = '\0';
      buf[buflen-1] = '\n';
      fwrite(buf, 1, buflen, tp);
      ysize += buflen;
      ++lines;
   }
   fflush(tp);
   rewind(tp);

   imap_appenduid(mp, tp, t, 0, xsize-2, ysize-1, lines-1, flag,
      imap_unquotestr(name));
   rv = OKAY;

jstop:
   n_free(buf);
   if(tp != NIL)
      mx_fs_close(tp);
   NYD_OU;
   return rv;
}

#ifdef mx_HAVE_IMAP_SEARCH
static sz
imap_search2(struct mailbox *mp, struct message *m, int cnt, const char *spec,
   int f)
{
   char *o, *cs, c;
   uz n;
   FILE *queuefp = NULL;
   int i;
   const char *cp, *xp;
   sz rv = -1;
   NYD;

   c = 0;
   for (cp = spec; *cp; cp++)
      c |= *cp;
   if (c & 0200) {
      cp = ok_vlook(ttycharset);
# ifdef mx_HAVE_ICONV
      if(su_cs_cmp_case(cp, "utf-8") && su_cs_cmp_case(cp, "utf8")){ /* XXX */
         char const *nspec;

         if((nspec = n_iconv_onetime_cp(n_ICONV_DEFAULT, "utf-8", cp, spec)
               ) != NULL){
            spec = nspec;
            cp = "utf-8";
         }
      }
# endif
      cp = imap_quotestr(cp);
      cs = n_lofi_alloc(n = su_cs_len(cp) + 10);
      snprintf(cs, n, "CHARSET %s ", cp);
   } else
      cs = n_UNCONST(n_empty);

   o = n_lofi_alloc(n = su_cs_len(spec) + 60);
   snprintf(o, n, "%s UID SEARCH %s%s\r\n", tag(1), cs, spec);
   IMAP_OUT(o, MB_COMD, goto out)
   /* C99 */{
      enum okay ok;

      for (rv = 0, ok = OKAY; (mp->mb_active & MB_COMD);) {
         if (imap_answer(mp, 0) != OKAY) {
            rv = -1;
            ok = STOP;
         }
         if (response_status == RESPONSE_OTHER &&
               response_other == MAILBOX_DATA_SEARCH) {
            xp = responded_other_text;
            while (*xp && *xp != '\r') {
               u64 uid;

               su_idec_u64_cp(&uid, xp, 10, &xp);/* TODO errors? */
               for (i = 0; i < cnt; i++)
                  if (m[i].m_uid == uid && !(m[i].m_flag & MHIDDEN) &&
                        (f == MDELETED || !(m[i].m_flag & MDELETED))){
                     if(ok == OKAY){
                        mark(i+1, f);
                        ++rv;
                     }
                  }
            }
         }
      }
   }
out:
   n_lofi_free(o);
   if(cs != n_empty)
      n_lofi_free(cs);
   return rv;
}

FL sz
imap_search1(const char * volatile spec, int f)
{
   n_sighdl_t saveint, savepipe;
   sz volatile rv = -1;
   NYD_IN;

   if (mb.mb_type != MB_IMAP)
      goto jleave;

   imaplock = 1;
   if ((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
      safe_signal(SIGINT, &_imap_maincatch);
   savepipe = safe_signal(SIGPIPE, SIG_IGN);
   if (sigsetjmp(imapjmp, 1) == 0) {
      if (savepipe != SIG_IGN)
         safe_signal(SIGPIPE, imapcatch);

      rv = imap_search2(&mb, message, msgCount, spec, f);
   }
   safe_signal(SIGINT, saveint);
   safe_signal(SIGPIPE, savepipe);
   imaplock = 0;
jleave:
   NYD_OU;
   if (interrupts)
      n_go_onintr_for_imap();
   return rv;
}
#endif /* mx_HAVE_IMAP_SEARCH */

FL int
imap_thisaccount(const char *cp)
{
   int rv;
   NYD_IN;

   if (mb.mb_type != MB_CACHE && mb.mb_type != MB_IMAP)
      rv = 0;
   else if ((mb.mb_type != MB_CACHE &&
            (mb.mb_sock == NIL || mb.mb_sock->s_fd < 0)) ||
         mb.mb_imap_account == NULL)
      rv = 0;
   else
      rv = !su_cs_cmp(protbase(cp), mb.mb_imap_account);
   NYD_OU;
   return rv;
}

FL enum okay
imap_remove(const char * volatile name)
{
   n_sighdl_t volatile saveint, savepipe;
   enum okay volatile rv = STOP;
   NYD_IN;

   if (mb.mb_type != MB_IMAP) {
      n_err(_("Refusing to remove \"%s\" in disconnected mode\n"), name);
      goto jleave;
   }

   if (!imap_thisaccount(name)) {
      n_err(_("Can only remove mailboxes on current IMAP server: "
         "\"%s\" not removed\n"), name);
      goto jleave;
   }

   imaplock = 1;
   if ((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
      safe_signal(SIGINT, &_imap_maincatch);
   savepipe = safe_signal(SIGPIPE, SIG_IGN);
   if (sigsetjmp(imapjmp, 1) == 0) {
      if (savepipe != SIG_IGN)
         safe_signal(SIGPIPE, imapcatch);

      rv = imap_remove1(&mb, imap_fileof(name));
   }
   safe_signal(SIGINT, saveint);
   safe_signal(SIGPIPE, savepipe);
   imaplock = 0;

   if (rv == OKAY)
      rv = cache_remove(name);
jleave:
   NYD_OU;
   if (interrupts)
      n_go_onintr_for_imap();
   return rv;
}

static enum okay
imap_remove1(struct mailbox *mp, const char *name)
{
   char *o;
   int os;
   char const *qname;
   FILE *queuefp;
   enum okay ok;
   NYD;

   ok = STOP;
   queuefp = NULL;

   if((qname = imap_path_quote(mp, name)) != NULL){
      o = n_lofi_alloc(os = su_cs_len(qname) + 100);
      snprintf(o, os, "%s DELETE %s\r\n", tag(1), qname);
      IMAP_OUT(o, MB_COMD, goto out)
      while (mp->mb_active & MB_COMD)
         ok = imap_answer(mp, 1);
out:
      n_lofi_free(o);
   }
   return ok;
}

FL enum okay
imap_rename(const char *old, const char *new)
{
   n_sighdl_t saveint, savepipe;
   enum okay volatile rv = STOP;
   NYD_IN;

   if (mb.mb_type != MB_IMAP) {
      n_err(_("Refusing to rename mailboxes in disconnected mode\n"));
      goto jleave;
   }

   if (!imap_thisaccount(old) || !imap_thisaccount(new)) {
      n_err(_("Can only rename mailboxes on current IMAP "
            "server: \"%s\" not renamed to \"%s\"\n"), old, new);
      goto jleave;
   }

   imaplock = 1;
   if ((saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
      safe_signal(SIGINT, &_imap_maincatch);
   savepipe = safe_signal(SIGPIPE, SIG_IGN);
   if (sigsetjmp(imapjmp, 1) == 0) {
      if (savepipe != SIG_IGN)
         safe_signal(SIGPIPE, imapcatch);

      rv = imap_rename1(&mb, imap_fileof(old), imap_fileof(new));
   }
   safe_signal(SIGINT, saveint);
   safe_signal(SIGPIPE, savepipe);
   imaplock = 0;

   if (rv == OKAY)
      rv = cache_rename(old, new);
jleave:
   NYD_OU;
   if (interrupts)
      n_go_onintr_for_imap();
   return rv;
}

static enum okay
imap_rename1(struct mailbox *mp, const char *old, const char *new)
{
   char *o;
   int os;
   char const *qoname, *qnname;
   FILE *queuefp;
   enum okay ok;
   NYD;

   ok = STOP;
   queuefp = NULL;

   if((qoname = imap_path_quote(mp, old)) != NULL &&
         (qnname = imap_path_quote(mp, new)) != NULL){
      o = n_lofi_alloc(os = su_cs_len(qoname) + su_cs_len(qnname) + 100);
      snprintf(o, os, "%s RENAME %s %s\r\n", tag(1), qoname, qnname);
      IMAP_OUT(o, MB_COMD, goto out)
      while (mp->mb_active & MB_COMD)
         ok = imap_answer(mp, 1);
out:
      n_lofi_free(o);
   }
   return ok;
}

FL enum okay
imap_dequeue(struct mailbox *mp, FILE *fp)
{
   char o[LINESIZE], *newname, *buf, *bp, *cp, iob[4096];
   uz bufsize, buflen, cnt;
   long offs, offs1, offs2, octets;
   int twice, gotcha = 0;
   FILE *queuefp = NULL;
   enum okay ok = OKAY, rok = OKAY;
   NYD;

   buf = n_alloc(bufsize = LINESIZE);
   buflen = 0;
   cnt = fsize(fp);
   while ((offs1 = ftell(fp)) >= 0 &&
         fgetline(&buf, &bufsize, &cnt, &buflen, fp, 0) != NULL) {
      for (bp = buf; *bp != ' '; ++bp) /* strip old tag */
         ;
      while (*bp == ' ')
         ++bp;
      twice = 0;
      if ((offs = ftell(fp)) < 0)
         goto fail;
again:
      snprintf(o, sizeof o, "%s %s", tag(1), bp);
      if (su_cs_cmp_case_n(bp, "UID COPY ", 9) == 0) {
         cp = &bp[9];
         while (su_cs_is_digit(*cp))
            cp++;
         if (*cp != ' ')
            goto fail;
         while (*cp == ' ')
            cp++;
         if ((newname = imap_strex(cp, NULL)) == NULL)
            goto fail;
         IMAP_OUT(o, MB_COMD, continue)
         while (mp->mb_active & MB_COMD)
            ok = imap_answer(mp, twice);
         if (response_status == RESPONSE_NO && twice++ == 0)
            goto trycreate;
         if (response_status == RESPONSE_OK && mp->mb_flags & MB_UIDPLUS) {
            imap_copyuid(mp, NULL, imap_unquotestr(newname));
         }
      } else if (su_cs_cmp_case_n(bp, "UID STORE ", 10) == 0) {
         IMAP_OUT(o, MB_COMD, continue)
         while (mp->mb_active & MB_COMD)
            ok = imap_answer(mp, 1);
         if (ok == OKAY)
            gotcha++;
      } else if (su_cs_cmp_case_n(bp, "APPEND ", 7) == 0) {
         if ((cp = su_cs_rfind_c(bp, '{')) == NULL)
            goto fail;
         octets = atol(&cp[1]) + 2;
         if ((newname = imap_strex(&bp[7], NULL)) == NULL)
            goto fail;
         IMAP_OUT(o, MB_COMD, continue)
         while (mp->mb_active & MB_COMD) {
            ok = imap_answer(mp, twice);
            if (response_type == RESPONSE_CONT)
               break;
         }
         if (ok == STOP) {
            if (twice++ == 0 && fseek(fp, offs, SEEK_SET) >= 0)
               goto trycreate;
            goto fail;
         }
         while (octets > 0) {
            uz n = (UCMP(z, octets, >, sizeof iob)
                  ? sizeof iob : (uz)octets);
            octets -= n;
            if (n != fread(iob, 1, n, fp))
               goto fail;
            mx_socket_write1(mp->mb_sock, iob, n, 1);
         }
         mx_socket_write(mp->mb_sock, "");
         while (mp->mb_active & MB_COMD) {
            ok = imap_answer(mp, 0);
            if (response_status == RESPONSE_NO && twice++ == 0) {
               if (fseek(fp, offs, SEEK_SET) < 0)
                  goto fail;
               goto trycreate;
            }
         }
         if (response_status == RESPONSE_OK && mp->mb_flags & MB_UIDPLUS) {
            if ((offs2 = ftell(fp)) < 0)
               goto fail;
            fseek(fp, offs1, SEEK_SET);
            if (imap_appenduid_cached(mp, fp) == STOP) {
               (void)fseek(fp, offs2, SEEK_SET);
               goto fail;
            }
         }
      } else {
fail:
         n_err(_("Invalid command in IMAP cache queue: \"%s\"\n"), bp);
         rok = STOP;
      }
      continue;
trycreate:
      snprintf(o, sizeof o, "%s CREATE %s\r\n", tag(1), newname);
      IMAP_OUT(o, MB_COMD, continue)
      while (mp->mb_active & MB_COMD)
         ok = imap_answer(mp, 1);
      if (ok == OKAY)
         goto again;
   }
   fflush(fp);
   rewind(fp);
   ftruncate(fileno(fp), 0);
   if (gotcha)
      imap_close(mp);
   n_free(buf);
   return rok;
}

static char *
imap_strex(char const *cp, char const **xp)
{
   char const *cq;
   char *n = NULL;
   NYD_IN;

   if (*cp != '"')
      goto jleave;

   for (cq = cp + 1; *cq != '\0'; ++cq) {
      if (*cq == '\\')
         cq++;
      else if (*cq == '"')
         break;
   }
   if (*cq != '"')
      goto jleave;

   n = n_autorec_alloc(cq - cp + 2);
   su_mem_copy(n, cp, cq - cp +1);
   n[cq - cp + 1] = '\0';
   if (xp != NULL)
      *xp = cq + 1;
jleave:
   NYD_OU;
   return n;
}

static enum okay
check_expunged(void)
{
   enum okay rv;
   NYD_IN;

   if (expunged_messages > 0) {
      n_err(_("Command not executed - messages have been expunged\n"));
      rv = STOP;
   } else
      rv = OKAY;
   NYD_OU;
   return rv;
}

FL int
c_connect(void *vp) /* TODO v15-compat mailname<->URL (with password) */
{
   struct mx_url url;
   int rv, omsgCount = msgCount;
   NYD_IN;
   UNUSED(vp);

   if(mb.mb_type == MB_IMAP && mb.mb_sock != NIL && mb.mb_sock->s_fd > 0){
      n_err(_("Already connected\n"));
      rv = 1;
      goto jleave;
   }

   if(!mx_url_parse(&url, CPROTO_IMAP, mailname)){
      rv = 1;
      goto jleave;
   }
   ok_bclear(disconnected);
   n_var_vclear(savecat("disconnected-", url.url_u_h_p.s));

   if (mb.mb_type == MB_CACHE) {
      enum fedit_mode fm = FEDIT_NONE;
      if (_imap_rdonly)
         fm |= FEDIT_RDONLY;
      if (!(n_pstate & n_PS_EDIT))
         fm |= FEDIT_SYSBOX;
      _imap_setfile1(NULL, &url, fm, 1);
      if (msgCount > omsgCount)
         newmailinfo(omsgCount);
   }
   rv = 0;
jleave:
   NYD_OU;
   return rv;
}

FL int
c_disconnect(void *vp) /* TODO v15-compat mailname<->URL (with password) */
{
   struct mx_url url;
   int rv = 1, *msgvec = vp;
   NYD_IN;

   if (mb.mb_type == MB_CACHE) {
      n_err(_("Not connected\n"));
      goto jleave;
   }
   if (mb.mb_type != MB_IMAP || cached_uidvalidity(&mb) == 0) {
      n_err(_("The current mailbox is not cached\n"));
      goto jleave;
   }

   if(!mx_url_parse(&url, CPROTO_IMAP, mailname))
      goto jleave;

   if (*msgvec)
      c_cache(vp);
   ok_bset(disconnected);
   if (mb.mb_type == MB_IMAP) {
      enum fedit_mode fm = FEDIT_NONE;
      if (_imap_rdonly)
         fm |= FEDIT_RDONLY;
      if (!(n_pstate & n_PS_EDIT))
         fm |= FEDIT_SYSBOX;
      if(mb.mb_sock != NIL){
         if(mb.mb_sock->s_fd >= 0)
            mx_socket_close(mb.mb_sock);
         su_FREE(mb.mb_sock);
         mb.mb_sock = NIL;
      }
      _imap_setfile1(NULL, &url, fm, 1);
   }
   rv = 0;
jleave:
   NYD_OU;
   return rv;
}

FL int
c_cache(void *vp)
{
   int rv = 1, *msgvec = vp, *ip;
   struct message *mp;
   NYD_IN;

   if (mb.mb_type != MB_IMAP) {
      n_err(_("Not connected to an IMAP server\n"));
      goto jleave;
   }
   if (cached_uidvalidity(&mb) == 0) {
      n_err(_("The current mailbox is not cached\n"));
      goto jleave;
   }

   srelax_hold();
   for (ip = msgvec; *ip; ++ip) {
      mp = &message[*ip - 1];
      if (!(mp->m_content_info & CI_HAVE_BODY)) {
         get_body(mp);
         srelax();
      }
   }
   srelax_rele();
   rv = 0;
jleave:
   NYD_OU;
   return rv;
}

FL int
disconnected(const char *file)
{
   struct mx_url url;
   int rv = 1;
   NYD_IN;

   if (ok_blook(disconnected)) {
      rv = 1;
      goto jleave;
   }

   if(!mx_url_parse(&url, CPROTO_IMAP, file)){
      rv = 0;
      goto jleave;
   }
   rv = (n_var_vlook(savecat("disconnected-", url.url_u_h_p.s), FAL0) != NULL);

jleave:
   NYD_OU;
   return rv;
}

FL void
transflags(struct message *omessage, long omsgCount, int transparent)
{
   struct message *omp, *nmp, *newdot, *newprevdot;
   int hf;
   NYD_IN;

   omp = omessage;
   nmp = message;
   newdot = message;
   newprevdot = NULL;
   while (PCMP(omp, <, omessage + omsgCount) &&
         PCMP(nmp, <, message + msgCount)) {
      if (dot && nmp->m_uid == dot->m_uid)
         newdot = nmp;
      if (prevdot && nmp->m_uid == prevdot->m_uid)
         newprevdot = nmp;
      if (omp->m_uid == nmp->m_uid) {
         hf = nmp->m_flag & MHIDDEN;
         if (transparent && mb.mb_type == MB_IMAP)
            omp->m_flag &= ~MHIDDEN;
         *nmp++ = *omp++;
         if (transparent && mb.mb_type == MB_CACHE)
            nmp[-1].m_flag |= hf;
      } else if (omp->m_uid < nmp->m_uid)
         ++omp;
      else
         ++nmp;
   }
   dot = newdot;
   setdot(newdot);
   prevdot = newprevdot;
   n_free(omessage);
   NYD_OU;
}

FL time_t
imap_read_date_time(const char *cp)
{
   char buf[3];
   time_t t;
   int i, year, month, day, hour, minute, second, sign = -1;
   NYD2_IN;

   /* "25-Jul-2004 15:33:44 +0200"
    * |    |    |    |    |    |
    * 0    5   10   15   20   25 */
   if (cp[0] != '"' || su_cs_len(cp) < 28 || cp[27] != '"')
      goto jinvalid;
   day = strtol(&cp[1], NULL, 10);
   for (i = 0;;) {
      if (su_cs_cmp_case_n(&cp[4], n_month_names[i], 3) == 0)
         break;
      if (n_month_names[++i][0] == '\0')
         goto jinvalid;
   }
   month = i + 1;
   year = strtol(&cp[8], NULL, 10);
   hour = strtol(&cp[13], NULL, 10);
   minute = strtol(&cp[16], NULL, 10);
   second = strtol(&cp[19], NULL, 10);
   if ((t = combinetime(year, month, day, hour, minute, second)) == (time_t)-1)
      goto jinvalid;
   switch (cp[22]) {
   case '-':
      sign = 1;
      break;
   case '+':
      break;
   default:
      goto jinvalid;
   }
   buf[2] = '\0';
   buf[0] = cp[23];
   buf[1] = cp[24];
   t += strtol(buf, NULL, 10) * sign * 3600;
   buf[0] = cp[25];
   buf[1] = cp[26];
   t += strtol(buf, NULL, 10) * sign * 60;
jleave:
   NYD2_OU;
   return t;
jinvalid:
   time(&t);
   goto jleave;
}

FL const char *
imap_make_date_time(time_t t)
{
   static char s[40];
   char const *mn;
   s32 y, md, th, tm, ts;
   struct tm *tmp;
   int tzdiff, tzdiff_hour, tzdiff_min;
   time_t t2;
   NYD2_IN;

jredo:
   if((t2 = mktime(gmtime(&t))) == (time_t)-1){
      t = 0;
      goto jredo;
   }
   tzdiff = t - t2;
   if((tmp = localtime(&t)) == NULL){
      t = 0;
      goto jredo;
   }

   tzdiff_hour = (int)(tzdiff / 60);
   tzdiff_min = tzdiff_hour % 60;
   tzdiff_hour /= 60;
   if (tmp->tm_isdst > 0)
      tzdiff_hour++;

   if(UNLIKELY((y = tmp->tm_year) < 0 || y >= 9999/*S32_MAX*/ - 1900)){
      y = 1970;
      mn = n_month_names[0];
      md = 1;
      th = tm = ts = 0;
   }else{
      y += 1900;
      mn = (tmp->tm_mon >= 0 && tmp->tm_mon <= 11)
            ? n_month_names[tmp->tm_mon] : n_qm;

      if((md = tmp->tm_mday) < 1 || md > 31)
         md = 1;

      if((th = tmp->tm_hour) < 0 || th > 23)
         th = 0;
      if((tm = tmp->tm_min) < 0 || tm > 59)
         tm = 0;
      if((ts = tmp->tm_sec) < 0 || ts > 60)
         ts = 0;
   }

   snprintf(s, sizeof s, "\"%02d-%s-%04d %02d:%02d:%02d %+03d%02d\"",
         md, mn, y, th, tm, ts, tzdiff_hour, tzdiff_min);
   NYD2_OU;
   return s;
}

FL char *
(protbase)(char const *cp  su_DBG_LOC_ARGS_DECL)
{
   char *n, *np;
   NYD2_IN;

   np = n = su_MEM_BAG_SELF_AUTO_ALLOC_LOCOR(su_cs_len(cp) +1,
         su_DBG_LOC_ARGS_ORUSE);

   /* Just ignore the `is-system-mailbox' prefix XXX */
   if (cp[0] == '%' && cp[1] == ':')
      cp += 2;

   while (*cp != '\0') {
      if (cp[0] == ':' && cp[1] == '/' && cp[2] == '/') {
         *np++ = *cp++;
         *np++ = *cp++;
         *np++ = *cp++;
      } else if (cp[0] == '/')
         break;
      else
         *np++ = *cp++;
   }
   *np = '\0';
   NYD2_OU;
   return n;
}

#include "su/code-ou.h"
#endif /* mx_HAVE_IMAP */
/* s-it-mode */
s-nail-14.9.15/src/mx/obs-lzw.c000066400000000000000000000466671352610246600160620ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ LZW file compression.
 *
 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: BSD-4-Clause
 */
/*-
 * Copyright (c) 1985, 1986, 1992, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Diomidis Spinellis and James A. Woods, derived from original
 * work by Spencer Thomas and Joseph Orost.
 *
 * 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.
 * 4. 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 zopen.c	8.1 (Berkeley) 6/27/93	*/
/*	from FreeBSD: /repoman/r/ncvs/src/usr.bin/compress/zopen.c,v
 *	1.5.6.1 2002/07/16 00:52:08 tjr Exp */
/*	from FreeBSD: git://git.freebsd.org/freebsd,
 *	master:usr.bin/compress/zopen.c,
 *	(Fix handling of corrupt compress(1)ed data. [11:04], 2011-09-28) */

/*-
 * lzw.c - File compression ala IEEE Computer, June 1984.
 *
 * Compress authors:
 *		Spencer W. Thomas	(decvax!utah-cs!thomas)
 *		Jim McKie		(decvax!mcvax!jim)
 *		Steve Davies		(decvax!vax135!petsd!peora!srd)
 *		Ken Turkowski		(decvax!decwrl!turtlevax!ken)
 *		James A. Woods		(decvax!ihnp4!ames!jaw)
 *		Joe Orost		(decvax!vax135!petsd!joe)
 *
 * Cleaned up and converted to library returning I/O streams by
 * Diomidis Spinellis .
 *
 * Adopted for Heirloom mailx by Gunnar Ritter.
 */
#undef su_FILE
#define su_FILE obs_lzw
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

su_EMPTY_FILE()
#ifdef mx_HAVE_IMAP
#include 

/* TODO fake */
#include "su/code-in.h"

/* Minimize differences to FreeBSDs usr.bin/compress/zopen.c */
#undef u_int
#define u_int		unsigned int
#undef u_short
#define u_short		unsigned short
#undef u_char
#define u_char		unsigned char
#define count		cnt

#define	BITS		16		/* Default bits. */
#define	HSIZE		69001		/* 95% occupancy */

/* A code_int must be able to hold 2**BITS values of type int, and also -1. */
typedef long code_int;
typedef long count_int;

typedef u_char char_type;
static char_type magic_header[] = {0x1F, 0x9D}; /* \037, \235 */

#define	BIT_MASK	0x1f		/* Defines for third byte of header. */
#define	BLOCK_MASK	0x80

/*
 * Masks 0x40 and 0x20 are free.  I think 0x20 should mean that there is
 * a fourth header byte (for expansion).
 */
#define	INIT_BITS 9			/* Initial number of bits/code. */

#define	MAXCODE(n_bits)	((1 << (n_bits)) - 1)

struct s_zstate {
	FILE *zs_fp;			/* File stream for I/O */
	char zs_mode;			/* r or w */
	enum {
		S_START, S_MIDDLE, S_EOF
	} zs_state;			/* State of computation */
	u_int zs_n_bits;		/* Number of bits/code. */
	u_int zs_maxbits;		/* User settable max # bits/code. */
	code_int zs_maxcode;		/* Maximum code, given n_bits. */
	code_int zs_maxmaxcode;		/* Should NEVER generate this code. */
	count_int zs_htab [HSIZE];
	u_short zs_codetab [HSIZE];
	code_int zs_hsize;		/* For dynamic table sizing. */
	code_int zs_free_ent;		/* First unused entry. */
	/*
	 * Block compression parameters -- after all codes are used up,
	 * and compression rate changes, start over.
	 */
	int zs_block_compress;
	int zs_clear_flg;
	long zs_ratio;
	count_int zs_checkpoint;
	u_int zs_offset;
	long zs_in_count;		/* Length of input. */
	long zs_bytes_out;		/* Length of compressed output. */
	long zs_out_count;		/* # of codes output (for debugging). */
	char_type zs_buf[BITS];
	union {
		struct {
			long zs_fcode;
			code_int zs_ent;
			code_int zs_hsize_reg;
			int zs_hshift;
		} w;			/* Write parameters */
		struct {
			char_type *zs_stackp;
			int zs_finchar;
			code_int zs_code, zs_oldcode, zs_incode;
			int zs_roffset, zs_size;
			char_type zs_gbuf[BITS];
		} r;			/* Read parameters */
	} u;
};

/* Definitions to retain old variable names */
#define	fp		zs->zs_fp
#define	zmode		zs->zs_mode
#define	state		zs->zs_state
#define	n_bits		zs->zs_n_bits
#define	maxbits		zs->zs_maxbits
#define	maxcode		zs->zs_maxcode
#define	maxmaxcode	zs->zs_maxmaxcode
#define	htab		zs->zs_htab
#define	codetab		zs->zs_codetab
#define	hsize		zs->zs_hsize
#define	free_ent	zs->zs_free_ent
#define	block_compress	zs->zs_block_compress
#define	clear_flg	zs->zs_clear_flg
#define	ratio		zs->zs_ratio
#define	checkpoint	zs->zs_checkpoint
#define	offset		zs->zs_offset
#define	in_count	zs->zs_in_count
#define	bytes_out	zs->zs_bytes_out
#define	out_count	zs->zs_out_count
#define	buf		zs->zs_buf
#define	fcode		zs->u.w.zs_fcode
#define	hsize_reg	zs->u.w.zs_hsize_reg
#define	ent		zs->u.w.zs_ent
#define	hshift		zs->u.w.zs_hshift
#define	stackp		zs->u.r.zs_stackp
#define	finchar		zs->u.r.zs_finchar
#define	code		zs->u.r.zs_code
#define	oldcode		zs->u.r.zs_oldcode
#define	incode		zs->u.r.zs_incode
#define	roffset		zs->u.r.zs_roffset
#define	size		zs->u.r.zs_size
#define	gbuf		zs->u.r.zs_gbuf

/*
 * To save much memory, we overlay the table used by compress() with those
 * used by decompress().  The tab_prefix table is the same size and type as
 * the codetab.  The tab_suffix table needs 2**BITS characters.  We get this
 * from the beginning of htab.  The output stack uses the rest of htab, and
 * contains characters.  There is plenty of room for any possible stack
 * (stack used to be 8000 characters).
 */

#define	htabof(i)	htab[i]
#define	codetabof(i)	codetab[i]

#define	tab_prefixof(i)	codetabof(i)
#define	tab_suffixof(i)	((char_type *)(htab))[i]
#define	de_stack	((char_type *)&tab_suffixof(1 << BITS))

#define	CHECK_GAP 10000		/* Ratio check interval. */

/*
 * the next two codes should not be changed lightly, as they must not
 * lie within the contiguous general code space.
 */
#define	FIRST	257		/* First free entry. */
#define	CLEAR	256		/* Table clear output code. */

static int	cl_block(struct s_zstate *);
static void	cl_hash(struct s_zstate *, count_int);
static code_int	getcode(struct s_zstate *);
static int	output(struct s_zstate *, code_int);

/*-
 * Algorithm from "A Technique for High Performance Data Compression",
 * Terry A. Welch, IEEE Computer Vol 17, No 6 (June 1984), pp 8-19.
 *
 * Algorithm:
 * 	Modified Lempel-Ziv method (LZW).  Basically finds common
 * substrings and replaces them with a variable size code.  This is
 * deterministic, and can be done on the fly.  Thus, the decompression
 * procedure needs no input table, but tracks the way the table was built.
 */

/*-
 * compress write
 *
 * Algorithm:  use open addressing double hashing (no chaining) on the
 * prefix code / next character combination.  We do a variant of Knuth's
 * algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
 * secondary probe.  Here, the modular division first probe is gives way
 * to a faster exclusive-or manipulation.  Also do block compression with
 * an adaptive reset, whereby the code table is cleared when the compression
 * ratio decreases, but after the table fills.  The variable-length output
 * codes are re-sized at this point, and a special CLEAR code is generated
 * for the decompressor.  Late addition:  construct the table according to
 * file size for noticeable speed improvement on small files.  Please direct
 * questions about this implementation to ames!jaw.
 */
FL int
zwrite(void *cookie, const char *wbp, int num)
{
	code_int i;
	int c, disp;
	struct s_zstate *zs;
	const u_char *bp;
	u_char tmp;
	int count;

	if (num == 0)
		return (0);

	zs = cookie;
	zmode = 'w';
	count = num;
	bp = (const u_char *)wbp;
	if (state == S_MIDDLE)
		goto middle;
	state = S_MIDDLE;

	maxmaxcode = 1L << maxbits;
	if (fwrite(magic_header,
	    sizeof(char), sizeof(magic_header), fp) != sizeof(magic_header))
		return (-1);
	tmp = (u_char)((maxbits) | block_compress);
	if (fwrite(&tmp, sizeof(char), sizeof(tmp), fp) != sizeof(tmp))
		return (-1);

	offset = 0;
	bytes_out = 3;		/* Includes 3-byte header mojo. */
	out_count = 0;
	clear_flg = 0;
	ratio = 0;
	in_count = 1;
	checkpoint = CHECK_GAP;
	maxcode = MAXCODE(n_bits = INIT_BITS);
	free_ent = ((block_compress) ? FIRST : 256);

	ent = *bp++;
	--count;

	hshift = 0;
	for (fcode = (long)hsize; fcode < 65536L; fcode *= 2L)
		hshift++;
	hshift = 8 - hshift;	/* Set hash code range bound. */

	hsize_reg = hsize;
	cl_hash(zs, (count_int)hsize_reg);	/* Clear hash table. */

middle:	for (i = 0; count--;) {
		c = *bp++;
		in_count++;
		fcode = (long)(((long)c << maxbits) + ent);
		i = ((c << hshift) ^ ent);	/* Xor hashing. */

		if (htabof(i) == fcode) {
			ent = codetabof(i);
			continue;
		} else if ((long)htabof(i) < 0)	/* Empty slot. */
			goto nomatch;
		disp = hsize_reg - i;	/* Secondary hash (after G. Knott). */
		if (i == 0)
			disp = 1;
probe:		if ((i -= disp) < 0)
			i += hsize_reg;

		if (htabof(i) == fcode) {
			ent = codetabof(i);
			continue;
		}
		if ((long)htabof(i) >= 0)
			goto probe;
nomatch:	if (output(zs, (code_int) ent) == -1)
			return (-1);
		out_count++;
		ent = c;
		if (free_ent < maxmaxcode) {
			codetabof(i) = free_ent++;	/* code -> hashtable */
			htabof(i) = fcode;
		} else if ((count_int)in_count >=
		    checkpoint && block_compress) {
			if (cl_block(zs) == -1)
				return (-1);
		}
	}
	return (num);
}

FL int
zfree(void *cookie)
{
	struct s_zstate *zs;

	zs = cookie;
	if (zmode == 'w') {		/* Put out the final code. */
		if (output(zs, (code_int) ent) == -1) {
			n_free(zs);
			return (-1);
		}
		out_count++;
		if (output(zs, (code_int) - 1) == -1) {
			n_free(zs);
			return (-1);
		}
	}
	n_free(zs);
	return (0);
}

/*-
 * Output the given code.
 * Inputs:
 * 	code:	A n_bits-bit integer.  If == -1, then EOF.  This assumes
 *		that n_bits =< (long)wordsize - 1.
 * Outputs:
 * 	Outputs code to the file.
 * Assumptions:
 *	Chars are 8 bits long.
 * Algorithm:
 * 	Maintain a BITS character long buffer (so that 8 codes will
 * fit in it exactly).  Use the VAX insv instruction to insert each
 * code in turn.  When the buffer fills up empty it and start over.
 */

static char_type lmask[9] =
	{0xff, 0xfe, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80, 0x00};
static char_type rmask[9] =
	{0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff};

static int
output(struct s_zstate *zs, code_int ocode)
{
	int r_off;
	u_int bits;
	char_type *bp;

	r_off = offset;
	bits = n_bits;
	bp = buf;
	if (ocode >= 0) {
		/* Get to the first byte. */
		bp += (r_off >> 3);
		r_off &= 7;
		/*
		 * Since ocode is always >= 8 bits, only need to mask the first
		 * hunk on the left.
		 */
		*bp = (*bp & rmask[r_off]) | ((ocode << r_off) & lmask[r_off]);
		bp++;
		bits -= (8 - r_off);
		ocode >>= 8 - r_off;
		/* Get any 8 bit parts in the middle (<=1 for up to 16 bits). */
		if (bits >= 8) {
			*bp++ = ocode;
			ocode >>= 8;
			bits -= 8;
		}
		/* Last bits. */
		if (bits)
			*bp = ocode;
		offset += n_bits;
		if (offset == (n_bits << 3)) {
			bp = buf;
			bits = n_bits;
			bytes_out += bits;
			if (fwrite(bp, sizeof(char), bits, fp) != bits)
				return (-1);
			bp += bits;
			bits = 0;
			offset = 0;
		}
		/*
		 * If the next entry is going to be too big for the ocode size,
		 * then increase it, if possible.
		 */
		if (free_ent > maxcode || (clear_flg > 0)) {
		       /*
			* Write the whole buffer, because the input side won't
			* discover the size increase until after it has read it.
			*/
			if (offset > 0) {
				if (fwrite(buf, 1, n_bits, fp) != n_bits)
					return (-1);
				bytes_out += n_bits;
			}
			offset = 0;

			if (clear_flg) {
				maxcode = MAXCODE(n_bits = INIT_BITS);
				clear_flg = 0;
			} else {
				n_bits++;
				if (n_bits == maxbits)
					maxcode = maxmaxcode;
				else
					maxcode = MAXCODE(n_bits);
			}
		}
	} else {
		/* At EOF, write the rest of the buffer. */
		if (offset > 0) {
			offset = (offset + 7) / 8;
			if (fwrite(buf, 1, offset, fp) != offset)
				return (-1);
			bytes_out += offset;
		}
		offset = 0;
	}
	return (0);
}

/*
 * Decompress read.  This routine adapts to the codes in the file building
 * the "string" table on-the-fly; requiring no table to be stored in the
 * compressed file.  The tables used herein are shared with those of the
 * compress() routine.  See the definitions above.
 */
FL int
zread(void *cookie, char *rbp, int num)
{
	u_int count;
	struct s_zstate *zs;
	u_char *bp, header[3];

	if (num == 0)
		return (0);

	zs = cookie;
	count = num;
	bp = (u_char *)rbp;
	switch (state) {
	case S_START:
		state = S_MIDDLE;
		break;
	case S_MIDDLE:
		goto middle;
	case S_EOF:
		goto eof;
	}

	/* Check the magic number */
	if (fread(header,
	    sizeof(char), sizeof(header), fp) != sizeof(header) ||
	    su_mem_cmp(header, magic_header, sizeof(magic_header)) != 0) {
		return (-1);
	}
	maxbits = header[2];	/* Set -b from file. */
	block_compress = maxbits & BLOCK_MASK;
	maxbits &= BIT_MASK;
	maxmaxcode = 1L << maxbits;
	if (maxbits > BITS || maxbits < 12) {
		return (-1);
	}
	/* As above, initialize the first 256 entries in the table. */
	maxcode = MAXCODE(n_bits = INIT_BITS);
	for (code = 255; code >= 0; code--) {
		tab_prefixof(code) = 0;
		tab_suffixof(code) = (char_type) code;
	}
	free_ent = block_compress ? FIRST : 256;

	finchar = oldcode = getcode(zs);
	if (oldcode == -1)	/* EOF already? */
		return (0);	/* Get out of here */

	/* First code must be 8 bits = char. */
	*bp++ = (u_char)finchar;
	count--;
	stackp = de_stack;

	while ((code = getcode(zs)) > -1) {

		if ((code == CLEAR) && block_compress) {
			for (code = 255; code >= 0; code--)
				tab_prefixof(code) = 0;
			clear_flg = 1;
			free_ent = FIRST;
			oldcode = -1;
			continue;
		}
		incode = code;

		/* Special case for kWkWk string. */
		if (code >= free_ent) {
			if (code > free_ent || oldcode == -1) {
				return (-1);
			}
			*stackp++ = finchar;
			code = oldcode;
		}
		/*
		 * The above condition ensures that code < free_ent.
		 * The construction of tab_prefixof in turn guarantees that
		 * each iteration decreases code and therefore stack usage is
		 * bound by 1 << BITS - 256.
		 */

		/* Generate output characters in reverse order. */
		while (code >= 256) {
			*stackp++ = tab_suffixof(code);
			code = tab_prefixof(code);
		}
		*stackp++ = finchar = tab_suffixof(code);

		/* And put them out in forward order.  */
middle:		do {
			if (count-- == 0)
				return (num);
			*bp++ = *--stackp;
		} while (stackp > de_stack);

		/* Generate the new entry. */
		if ((code = free_ent) < maxmaxcode && oldcode != -1) {
			tab_prefixof(code) = (u_short) oldcode;
			tab_suffixof(code) = finchar;
			free_ent = code + 1;
		}

		/* Remember previous code. */
		oldcode = incode;
	}
	state = S_EOF;
eof:	return (num - count);
}

/*-
 * Read one code from the standard input.  If EOF, return -1.
 * Inputs:
 * 	stdin
 * Outputs:
 * 	code or -1 is returned.
 */
static code_int
getcode(struct s_zstate *zs)
{
	code_int gcode;
	int r_off, bits;
	char_type *bp;

	bp = gbuf;
	if (clear_flg > 0 || roffset >= size || free_ent > maxcode) {
		/*
		 * If the next entry will be too big for the current gcode
		 * size, then we must increase the size.  This implies reading
		 * a new buffer full, too.
		 */
		if (free_ent > maxcode) {
			n_bits++;
			if (n_bits == maxbits)	/* Won't get any bigger now. */
				maxcode = maxmaxcode;
			else
				maxcode = MAXCODE(n_bits);
		}
		if (clear_flg > 0) {
			maxcode = MAXCODE(n_bits = INIT_BITS);
			clear_flg = 0;
		}
		size = fread(gbuf, 1, n_bits, fp);
		if (size <= 0)			/* End of file. */
			return (-1);
		roffset = 0;
		/* Round size down to integral number of codes. */
		size = (size << 3) - (n_bits - 1);
	}
	r_off = roffset;
	bits = n_bits;

	/* Get to the first byte. */
	bp += (r_off >> 3);
	r_off &= 7;

	/* Get first part (low order bits). */
	gcode = (*bp++ >> r_off);
	bits -= (8 - r_off);
	r_off = 8 - r_off;	/* Now, roffset into gcode word. */

	/* Get any 8 bit parts in the middle (<=1 for up to 16 bits). */
	if (bits >= 8) {
		gcode |= *bp++ << r_off;
		r_off += 8;
		bits -= 8;
	}

	/* High order bits. */
	gcode |= (*bp & rmask[bits]) << r_off;
	roffset += n_bits;

	return (gcode);
}

static int
cl_block(struct s_zstate *zs)		/* Table clear for block compress. */
{
	long rat;

	checkpoint = in_count + CHECK_GAP;

	if (in_count > 0x007fffff) {	/* Shift will overflow. */
		rat = bytes_out >> 8;
		if (rat == 0)		/* Don't divide by zero. */
			rat = 0x7fffffff;
		else
			rat = in_count / rat;
	} else
		rat = (in_count << 8) / bytes_out;	/* 8 fractional bits. */
	if (rat > ratio)
		ratio = rat;
	else {
		ratio = 0;
		cl_hash(zs, (count_int) hsize);
		free_ent = FIRST;
		clear_flg = 1;
		if (output(zs, (code_int) CLEAR) == -1)
			return (-1);
	}
	return (0);
}

static void
cl_hash(struct s_zstate *zs, count_int cl_hsize)	/* Reset code table. */
{
	count_int *htab_p;
	long i, m1;

	m1 = -1;
	htab_p = htab + cl_hsize;
	i = cl_hsize - 16;
	do {			/* Might use Sys V su_mem_set(3) here. */
		*(htab_p - 16) = m1;
		*(htab_p - 15) = m1;
		*(htab_p - 14) = m1;
		*(htab_p - 13) = m1;
		*(htab_p - 12) = m1;
		*(htab_p - 11) = m1;
		*(htab_p - 10) = m1;
		*(htab_p - 9) = m1;
		*(htab_p - 8) = m1;
		*(htab_p - 7) = m1;
		*(htab_p - 6) = m1;
		*(htab_p - 5) = m1;
		*(htab_p - 4) = m1;
		*(htab_p - 3) = m1;
		*(htab_p - 2) = m1;
		*(htab_p - 1) = m1;
		htab_p -= 16;
	} while ((i -= 16) >= 0);
	for (i += 16; i > 0; i--)
		*--htab_p = m1;
}

#undef fp
FL void *
zalloc(FILE *fp)
{
#define bits	BITS
	struct s_zstate *zs;

	zs = n_calloc(1, sizeof *zs);
	maxbits = bits ? bits : BITS;	/* User settable max # bits/code. */
	maxmaxcode = 1L << maxbits;	/* Should NEVER generate this code. */
	hsize = HSIZE;			/* For dynamic table sizing. */
	free_ent = 0;			/* First unused entry. */
	block_compress = BLOCK_MASK;
	clear_flg = 0;
	ratio = 0;
	checkpoint = CHECK_GAP;
	in_count = 1;			/* Length of input. */
	out_count = 0;			/* # of codes output (for debugging). */
	state = S_START;
	roffset = 0;
	size = 0;
	zs->zs_fp = fp;
	return zs;
}

#undef u_int
#undef u_short
#undef u_char
#undef count
#undef BITS
#undef HSIZE
#undef BIT_MASK
#undef BLOCK_MASK
#undef INIT_BITS
#undef MAXCODE
#undef fp
#undef zmode
#undef state
#undef n_bits
#undef maxbits
#undef maxcode
#undef maxmaxcode
#undef htab
#undef codetab
#undef hsize
#undef free_ent
#undef block_compress
#undef clear_flg
#undef ratio
#undef checkpoint
#undef offset
#undef in_count
#undef bytes_out
#undef out_count
#undef buf
#undef fcode
#undef hsize_reg
#undef ent
#undef hshift
#undef stackp
#undef finchar
#undef code
#undef oldcode
#undef incode
#undef roffset
#undef size
#undef gbuf

#include "su/code-ou.h"
#endif /* ndef mx_HAVE_IMAP */
s-nail-14.9.15/src/mx/path.c000066400000000000000000000105071352610246600154010ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ Path and directory related operations.
 *
 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: BSD-3-Clause TODO ISC
 */
/*
 * Copyright (c) 1980, 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.
 */
#undef su_FILE
#define su_FILE path
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

#include 

/* TODO fake */
#include "su/code-in.h"

FL boole
n_is_dir(char const *name, boole check_access){
   struct stat sbuf;
   boole rv;
   NYD2_IN;

   if((rv = (stat(name, &sbuf) == 0))){
      if((rv = (S_ISDIR(sbuf.st_mode) != 0)) && check_access){
         int mode;

         mode = R_OK | X_OK;
         if(check_access != TRUM1)
            mode |= W_OK;
         rv = (access(name, mode) == 0);
      }
   }
   NYD2_OU;
   return rv;
}

FL boole
n_path_mkdir(char const *name){
   struct stat st;
   boole rv;
   NYD_IN;

jredo:
   if(!mkdir(name, 0777))
      rv = TRU1;
   else{
      int e = su_err_no();

      /* Try it recursively */
      if(e == su_ERR_NOENT){
         char const *vp;

         if((vp = su_cs_rfind_c(name, '/')) != NULL){ /* TODO magic dirsep */
            while(vp > name && vp[-1] == '/')
               --vp;
            vp = savestrbuf(name, P2UZ(vp - name));

            if(n_path_mkdir(vp))
               goto jredo;
         }
      }

      rv = ((e == su_ERR_EXIST || e == su_ERR_NOSYS) && !stat(name, &st) &&
            S_ISDIR(st.st_mode));
   }
   NYD_OU;
   return rv;
}

FL boole
n_path_rm(char const *name){
   struct stat sb;
   boole rv;
   NYD2_IN;

   if(stat(name, &sb) != 0)
      rv = FAL0;
   else if(!S_ISREG(sb.st_mode))
      rv = TRUM1;
   else
      rv = (unlink(name) == 0);
   NYD2_OU;
   return rv;
}

#ifdef mx_HAVE_FCHDIR
FL enum okay
cwget(struct cw *cw)
{
   enum okay rv = STOP;
   NYD_IN;

   if ((cw->cw_fd = open(".", O_RDONLY)) == -1)
      goto jleave;
   if (fchdir(cw->cw_fd) == -1) {
      close(cw->cw_fd);
      goto jleave;
   }
   rv = OKAY;
jleave:
   NYD_OU;
   return rv;
}

FL enum okay
cwret(struct cw *cw)
{
   enum okay rv = STOP;
   NYD_IN;

   if (!fchdir(cw->cw_fd))
      rv = OKAY;
   NYD_OU;
   return rv;
}

FL void
cwrelse(struct cw *cw)
{
   NYD_IN;
   close(cw->cw_fd);
   NYD_OU;
}

#else /* !mx_HAVE_FCHDIR */
FL enum okay
cwget(struct cw *cw)
{
   enum okay rv = STOP;
   NYD_IN;

   if (getcwd(cw->cw_wd, sizeof cw->cw_wd) != NULL && !chdir(cw->cw_wd))
      rv = OKAY;
   NYD_OU;
   return rv;
}

FL enum okay
cwret(struct cw *cw)
{
   enum okay rv = STOP;
   NYD_IN;

   if (!chdir(cw->cw_wd))
      rv = OKAY;
   NYD_OU;
   return rv;
}

FL void
cwrelse(struct cw *cw)
{
   NYD_IN;
   UNUSED(cw);
   NYD_OU;
}
#endif /* !mx_HAVE_FCHDIR */

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/mx/quit.c000066400000000000000000000452561352610246600154400ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ Termination processing. TODO MBOX -> VFS; error handling: catastrophe!
 *
 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: BSD-3-Clause
 */
/*
 * Copyright (c) 1980, 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.
 */
#undef su_FILE
#define su_FILE quit
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

#include 

#include "mx/dig-msg.h"
#include "mx/file-locks.h"
#include "mx/file-streams.h"
#include "mx/net-pop3.h"
#include "mx/sigs.h"
#include "mx/tty.h"

#include 

/* TODO fake */
#include "su/code-in.h"

enum quitflags {
   QUITFLAG_HOLD      = 1<<0,
   QUITFLAG_KEEP      = 1<<1,
   QUITFLAG_KEEPSAVE  = 1<<2,
   QUITFLAG_APPEND    = 1<<3
};

struct quitnames {
   enum quitflags flag;
   enum okeys     okey;
};

static struct quitnames const _quitnames[] = {
   {QUITFLAG_HOLD, ok_b_hold},
   {QUITFLAG_KEEP, ok_b_keep},
   {QUITFLAG_KEEPSAVE, ok_b_keepsave},
   {QUITFLAG_APPEND, ok_b_append}
};

static char _mboxname[PATH_MAX];  /* Name of mbox */

/* Touch the indicated file */
static void _alter(char const *name);

/* Preserve all the appropriate messages back in the system mailbox, and print
 * a nice message indicated how many were saved.  On any error, just return -1.
 * Else return 0.  Incorporate the any new mail that we found */
static int  writeback(FILE *res, FILE *obuf);

/* Terminate an editing session by attempting to write out the user's file from
 * the temporary.  Save any new stuff appended to the file */
static boole edstop(void);

static void
_alter(char const *name) /* TODO error handling */
{
#ifdef mx_HAVE_UTIMENSAT
   struct timespec tsa[2];
#else
   struct stat sb;
   struct utimbuf utb;
#endif
   struct n_timespec const *tsp;
   NYD_IN;

   tsp = n_time_now(TRU1); /* TODO -> eventloop */

#ifdef mx_HAVE_UTIMENSAT
   tsa[0].tv_sec = tsp->ts_sec + 1;
   tsa[0].tv_nsec = tsp->ts_nsec;
   tsa[1].tv_nsec = UTIME_OMIT;
   utimensat(AT_FDCWD, name, tsa, 0);
#else
   if (!stat(name, &sb)) {
      utb.actime = tsp->ts_sec;
      utb.modtime = sb.st_mtime;
      utime(name, &utb);
   }
#endif
   NYD_OU;
}

static int
writeback(FILE *res, FILE *obuf) /* TODO errors */
{
   struct message *mp;
   int rv = -1, p, c;
   NYD_IN;

   if (fseek(obuf, 0L, SEEK_SET) == -1)
      goto jleave;

   srelax_hold();
   for (p = 0, mp = message; PCMP(mp, <, message + msgCount); ++mp)
      if ((mp->m_flag & MPRESERVE) || !(mp->m_flag & MTOUCH)) {
         ++p;
         if (sendmp(mp, obuf, NULL, NULL, SEND_MBOX, NULL) < 0) {
            n_perr(mailname, 0);
            srelax_rele();
            goto jerror;
         }
         srelax();
      }
   srelax_rele();

   if(res != NULL){
      boole lastnl;

      for(lastnl = FAL0; (c = getc(res)) != EOF && putc(c, obuf) != EOF;)
         lastnl = (c == '\n') ? (lastnl ? TRU2 : TRU1) : FAL0;
      if(lastnl != TRU2)
         putc('\n', obuf);
   }
   ftrunc(obuf);

   if (ferror(obuf)) {
      n_perr(mailname, 0);
jerror:
      fseek(obuf, 0L, SEEK_SET);
      goto jleave;
   }
   if (fseek(obuf, 0L, SEEK_SET) == -1)
      goto jleave;

   _alter(mailname);
   if (p == 1)
      fprintf(n_stdout, _("Held 1 message in %s\n"), displayname);
   else
      fprintf(n_stdout, _("Held %d messages in %s\n"), p, displayname);
   rv = 0;
jleave:
   NYD_OU;
   return rv;
}

static boole
edstop(void) /* TODO oh my god */
{
   int gotcha, c;
   struct message *mp;
   FILE *obuf = NULL, *ibuf = NULL;
   struct stat statb;
   enum mx_fs_open_state fs;
   boole rv;
   NYD_IN;

   rv = TRU1;

   if (mb.mb_perm == 0)
      goto j_leave;

   for (mp = message, gotcha = 0; PCMP(mp, <, message + msgCount); ++mp) {
      if (mp->m_flag & MNEW) {
         mp->m_flag &= ~MNEW;
         mp->m_flag |= MSTATUS;
      }
      if (mp->m_flag & (MODIFY | MDELETED | MSTATUS | MFLAG | MUNFLAG |
            MANSWER | MUNANSWER | MDRAFT | MUNDRAFT))
         ++gotcha;
   }
   if (!gotcha)
      goto jleave;

   rv = FAL0;

   /* TODO This is too simple minded?  We should regenerate an index file
    * TODO to be able to truly tell whether *anything* has changed!
    * TODO (Or better: only come here.. then!  It is an *object method!* */
   /* TODO Ignoring stat error is easy, huh? */
   if(!stat(mailname, &statb) && statb.st_size > mailsize){
      if((obuf = mx_fs_tmp_open("edstop", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
               mx_FS_O_REGISTER), NIL)) == NIL){
         n_perr(_("tmpfile"), 0);
         goto jleave;
      }
      if((ibuf = mx_fs_open_any(mailname, "r", NIL)) == NIL){
         n_perr(mailname, 0);
         goto jleave;
      }

      mx_file_lock(fileno(ibuf), mx_FILE_LOCK_TYPE_READ, 0,0, UZ_MAX);
      fseek(ibuf, (long)mailsize, SEEK_SET);
      while ((c = getc(ibuf)) != EOF) /* xxx bytewise??? TODO ... I/O error? */
         putc(c, obuf);
      mx_fs_close(ibuf);
      ibuf = obuf;
      fflush_rewind(obuf);
      /*obuf = NIL;*/
   }

   fprintf(n_stdout, _("%s "), n_shexp_quote_cp(displayname, FAL0));
   fflush(n_stdout);

   if((obuf = mx_fs_open_any(mailname, "r+", &fs)) == NIL){
      int e = su_err_no();
      n_perr(n_shexp_quote_cp(mailname, FAL0), e);
      goto jleave;
   }
   mx_file_lock(fileno(obuf), mx_FILE_LOCK_TYPE_WRITE, 0,0, UZ_MAX);
   ftrunc(obuf);

   srelax_hold();
   c = 0;
   for (mp = message; PCMP(mp, <, message + msgCount); ++mp) {
      if (mp->m_flag & MDELETED)
         continue;
      ++c;
      if (sendmp(mp, obuf, NULL, NULL, SEND_MBOX, NULL) < 0) {
         srelax_rele();
         n_err(_("Failed to finalize %s\n"), n_shexp_quote_cp(mailname, FAL0));
         goto jleave;
      }
      srelax();
   }
   srelax_rele();

   gotcha = (c == 0 && ibuf == NULL);
   if (ibuf != NULL) {
      boole lastnl;

      for(lastnl = FAL0; (c = getc(ibuf)) != EOF && putc(c, obuf) != EOF;)
         lastnl = (c == '\n') ? (lastnl ? TRU2 : TRU1) : FAL0;
      if(lastnl != TRU2 && (fs & n_PROTO_MASK) == n_PROTO_FILE)
         putc('\n', obuf);
   }
   /* May nonetheless be a broken MBOX TODO really: VFS, object KNOWS!! */
   else if(!gotcha && (fs & n_PROTO_MASK) == n_PROTO_FILE)
      n_folder_mbox_prepare_append(obuf, NULL);
   fflush(obuf);
   if (ferror(obuf)) {
      n_err(_("Failed to finalize %s\n"), n_shexp_quote_cp(mailname, FAL0));
      goto jleave;
   }

   if(gotcha){
      /* Non-system boxes are never removed except forced via POSIX mode */
#ifdef mx_HAVE_FTRUNCATE
      ftruncate(fileno(obuf), 0);
#else
      int fd;

      if((fd = open(mailname, (O_WRONLY | O_CREAT | n_O_NOXY_BITS | O_TRUNC),
            0600)) != -1)
         close(fd);
#endif

      if(ok_blook(posix) && !ok_blook(keep) && n_path_rm(mailname))
         fputs(_("removed\n"), n_stdout);
      else
         fputs(_("truncated\n"), n_stdout);
   } else
      fputs((ok_blook(bsdcompat) || ok_blook(bsdmsgs))
         ? _("complete\n") : _("updated.\n"), n_stdout);
   fflush(n_stdout);

   rv = TRU1;
jleave:
   if(obuf != NIL)
      mx_fs_close(obuf);
   if(ibuf != NIL)
      mx_fs_close(ibuf);
   if(!rv){
      /* TODO The codebase aborted by jumping to the main loop here.
       * TODO The OpenBSD mailx simply ignores this error.
       * TODO For now we follow the latter unless we are interactive,
       * TODO in which case we ask the user whether the error is to be
       * TODO ignored or not.  More of this around here in this file! */
      rv = mx_tty_yesorno(_("Continue, possibly losing changes"), TRU1);
   }
j_leave:
   NYD_OU;
   return rv;
}

FL boole
quit(boole hold_sigs_on)
{
   int p, modify, anystat, c;
   FILE *fbuf, *lckfp, *rbuf, *abuf;
   struct message *mp;
   struct stat minfo;
   boole rv;
   NYD_IN;

   if(!hold_sigs_on)
      hold_sigs();

   rv = FAL0;
   fbuf = lckfp = rbuf = NIL;
   if(mb.mb_digmsg != NIL)
      mx_dig_msg_on_mailbox_close(&mb);
   temporary_folder_hook_unroll();

   /* If we are read only, we can't do anything, so just return quickly */
   /* TODO yet we cannot return quickly if resources have to be released!
    * TODO somewhen it'll be mailbox->quit() anyway, for now do it by hand
    *if (mb.mb_perm == 0)
    *   goto jleave;*/
   p = (mb.mb_perm == 0);

   switch (mb.mb_type) {
   case MB_FILE:
      break;
#ifdef mx_HAVE_MAILDIR
   case MB_MAILDIR:
      rv = maildir_quit(TRU1);
      goto jleave;
#endif
#ifdef mx_HAVE_POP3
   case MB_POP3:
      rv = mx_pop3_quit(TRU1);
      goto jleave;
#endif
#ifdef mx_HAVE_IMAP
   case MB_IMAP:
   case MB_CACHE:
      rv = imap_quit(TRU1);
      goto jleave;
#endif
   case MB_VOID:
      rv = TRU1;
      /* FALLTHRU */
   default:
      goto jleave;
   }
   if (p) {
      rv = TRU1;
      goto jleave; /* TODO */
   }

   /* If editing (not reading system mail box), then do the work in edstop() */
   if (n_pstate & n_PS_EDIT) {
      rv = edstop();
      goto jleave;
   }

   /* See if there any messages to save in mbox.  If no, we
    * can save copying mbox to /tmp and back.
    *
    * Check also to see if any files need to be preserved.
    * Delete all untouched messages to keep them out of mbox.
    * If all the messages are to be preserved, just exit with
    * a message */
   fbuf = mx_fs_open_any(mailname, "r+", NIL);
   if(fbuf == NIL){
      if(su_err_no() != su_ERR_NOENT)
jnewmail:
         fprintf(n_stdout, _("Thou hast new mail.\n"));
      rv = TRU1;
      goto jleave;
   }

   if((lckfp = mx_file_dotlock(mailname, fileno(fbuf), mx_FILE_LOCK_TYPE_WRITE,
         0,0, UZ_MAX)) == NIL){
      n_perr(_("Unable to (dot) lock mailbox"), 0);
      mx_fs_close(fbuf);
      fbuf = NIL;
      rv = mx_tty_yesorno(_("Continue, possibly losing changes"), TRU1);
      goto jleave;
   }

   rbuf = NULL;
   if (!fstat(fileno(fbuf), &minfo) && minfo.st_size > mailsize) {
      boole lastnl;

      fprintf(n_stdout, _("New mail has arrived.\n"));
      rbuf = mx_fs_tmp_open("quit", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
               mx_FS_O_REGISTER), NIL);
      if(rbuf == NIL || fbuf == NIL)
         goto jnewmail;
      fseek(fbuf, (long)mailsize, SEEK_SET);
      for(lastnl = FAL0; (c = getc(fbuf)) != EOF && putc(c, rbuf) != EOF;)
         lastnl = (c == '\n') ? (lastnl ? TRU2 : TRU1) : FAL0;
      if(lastnl != TRU2)
         putc('\n', rbuf);
      fflush_rewind(rbuf);
   }

   anystat = holdbits();
   modify = 0;
   for (c = 0, p = 0, mp = message; PCMP(mp, <, message + msgCount); ++mp) {
      if (mp->m_flag & MBOX)
         c++;
      if (mp->m_flag & MPRESERVE)
         p++;
      if (mp->m_flag & MODIFY)
         modify++;
   }
   if (p == msgCount && !modify && !anystat) {
      rv = TRU1;
      if (p == 1)
         fprintf(n_stdout, _("Held 1 message in %s\n"), displayname);
      else if (p > 1)
         fprintf(n_stdout, _("Held %d messages in %s\n"), p, displayname);
      goto jleave;
   }

   if (c == 0) {
      if (p != 0) {
         if (writeback(rbuf, fbuf) >= 0)
            rv = TRU1;
         else
            rv = mx_tty_yesorno(_("Continue, possibly losing changes"), TRU1);
         goto jleave;
      }
      goto jcream;
   }

   if (makembox() == STOP) {
      rv = mx_tty_yesorno(_("Continue, possibly losing changes"), TRU1);
      goto jleave;
   }

   /* Now we are ready to copy back preserved files to the system mailbox, if
    * any were requested */
   if (p != 0) {
      if (writeback(rbuf, fbuf) < 0)
         rv = mx_tty_yesorno(_("Continue, possibly losing changes"), TRU1);
      goto jleave;
   }

   /* Finally, remove his file.  If new mail has arrived, copy it back */
jcream:
   if (rbuf != NULL) {
      abuf = fbuf;
      fseek(abuf, 0L, SEEK_SET);
      while ((c = getc(rbuf)) != EOF)
         putc(c, abuf);
      ftrunc(abuf);
      _alter(mailname);
      rv = TRU1;
   } else {
#ifdef mx_HAVE_FTRUNCATE
      ftruncate(fileno(fbuf), 0);
#else
      int fd;

      if((fd = open(mailname, (O_WRONLY | O_CREAT | n_O_NOXY_BITS | O_TRUNC),
            0600)) != -1)
         close(fd);
#endif
      if(!ok_blook(keep))
         n_path_rm(mailname);
      rv = TRU1;
   }

jleave:
   if(rbuf != NIL)
      mx_fs_close(rbuf);
   if(fbuf != NIL){
      mx_fs_close(fbuf);
      if(lckfp != NIL && lckfp != R(FILE*,-1))
         mx_fs_pipe_close(lckfp, FAL0);
   }

   if(!hold_sigs_on)
      rele_sigs();
   NYD_OU;
   return rv;
}

FL int
holdbits(void)
{
   struct message *mp;
   int anystat, autohold, holdbit, nohold;
   NYD_IN;

   anystat = 0;
   autohold = ok_blook(hold);
   holdbit = autohold ? MPRESERVE : MBOX;
   nohold = MBOX | MSAVED | MDELETED | MPRESERVE;
   if (ok_blook(keepsave))
      nohold &= ~MSAVED;
   for (mp = message; PCMP(mp, <, message + msgCount); ++mp) {
      if (mp->m_flag & MNEW) {
         mp->m_flag &= ~MNEW;
         mp->m_flag |= MSTATUS;
      }
      if (mp->m_flag & (MSTATUS | MFLAG | MUNFLAG | MANSWER | MUNANSWER |
            MDRAFT | MUNDRAFT))
         ++anystat;
      if (!(mp->m_flag & MTOUCH))
         mp->m_flag |= MPRESERVE;
      if (!(mp->m_flag & nohold))
         mp->m_flag |= holdbit;
   }
   NYD_OU;
   return anystat;
}

FL enum okay
makembox(void) /* TODO oh my god (also error reporting) */
{
   struct message *mp;
   char *mbox;
   int mcount, c;
   FILE *ibuf = NULL, *obuf, *abuf;
   enum mx_fs_open_state fs;
   enum okay rv = STOP;
   NYD_IN;

   mbox = _mboxname;
   mcount = 0;
   if(ok_blook(append)){
      if((obuf = mx_fs_open_any(mbox, "a+", &fs)) == NIL){
         n_perr(mbox, 0);
         goto jleave;
      }
      if((fs & n_PROTO_MASK) == n_PROTO_FILE)
         n_folder_mbox_prepare_append(obuf, NULL);
   }else{
      struct mx_fs_tmp_ctx *fstcp;

      if((obuf = mx_fs_tmp_open("makembox", (mx_FS_O_WRONLY |
               mx_FS_O_HOLDSIGS | mx_FS_O_REGISTER), &fstcp)) == NIL){
         n_perr(_("creation of temporary mail quit file"), 0);
         goto jleave;
      }
      if((ibuf = mx_fs_open(fstcp->fstc_filename, "r")) == NIL)
         n_perr(fstcp->fstc_filename, 0);
      mx_fs_tmp_release(fstcp);
      if(ibuf == NIL){
         mx_fs_close(obuf);
         goto jleave;
      }

      if((abuf = mx_fs_open_any(mbox, "r", &fs)) != NIL){
         boole lastnl;

         for (lastnl = FAL0; (c = getc(abuf)) != EOF && putc(c, obuf) != EOF;)
            lastnl = (c == '\n') ? (lastnl ? TRU2 : TRU1) : FAL0;
         if(lastnl != TRU2 && (fs & n_PROTO_MASK) == n_PROTO_FILE)
            putc('\n', obuf);

         mx_fs_close(abuf);
      }
      if(ferror(obuf)){
         n_perr(_("temporary mail quit file"), 0);
         mx_fs_close(ibuf);
         mx_fs_close(obuf);
         goto jleave;
      }
      mx_fs_close(obuf);

      if((c = open(mbox, (O_WRONLY | O_CREAT | mx_O_NOXY_BITS | O_TRUNC), 0666)
            ) != -1)
         close(c);
      if((obuf = mx_fs_open_any(mbox, "r+", &fs)) == NIL){
         n_perr(mbox, 0);
         mx_fs_close(ibuf);
         goto jleave;
      }
   }

   srelax_hold();
   for (mp = message; PCMP(mp, <, message + msgCount); ++mp) {
      if (mp->m_flag & MBOX) {
         ++mcount;
#ifdef mx_HAVE_IMAP
         if((fs & n_PROTO_MASK) == n_PROTO_IMAP &&
               !n_ignore_is_any(n_IGNORE_SAVE) && imap_thisaccount(mbox)){
            if(imap_copy(mp, P2UZ(mp - message + 1), mbox) == STOP)
               goto jcopyerr;
         }else
#endif
         if (sendmp(mp, obuf, n_IGNORE_SAVE, NULL, SEND_MBOX, NULL) < 0) {
#ifdef mx_HAVE_IMAP
jcopyerr:
#endif
            n_perr(mbox, 0);
            srelax_rele();
            if(ibuf != NIL)
               mx_fs_close(ibuf);
            mx_fs_close(obuf);
            goto jleave;
         }
         mp->m_flag |= MBOXED;
         srelax();
      }
   }
   srelax_rele();

   /* Copy the user's old mbox contents back to the end of the stuff we just
    * saved.  If we are appending, this is unnecessary */
   if (!ok_blook(append)) {
      boole lastnl;

      rewind(ibuf);
      for(lastnl = FAL0; (c = getc(ibuf)) != EOF && putc(c, obuf) != EOF;)
         lastnl = (c == '\n') ? (lastnl ? TRU2 : TRU1) : FAL0;
      if(lastnl != TRU2 && (fs & n_PROTO_MASK) == n_PROTO_FILE)
         putc('\n', obuf);
      mx_fs_close(ibuf);
      fflush(obuf);
   }
   ftrunc(obuf);
   if(ferror(obuf)){
      n_perr(mbox, 0);
      mx_fs_close(obuf);
      goto jleave;
   }
   if(!mx_fs_close(obuf)){
#ifdef mx_HAVE_IMAP
      if((fs & n_PROTO_MASK) != n_PROTO_IMAP)
#endif
         n_perr(mbox, 0);
      goto jleave;
   }
   if (mcount == 1)
      fprintf(n_stdout, _("Saved 1 message in mbox\n"));
   else
      fprintf(n_stdout, _("Saved %d messages in mbox\n"), mcount);
   rv = OKAY;
jleave:
   NYD_OU;
   return rv;
}

FL void
save_mbox_for_possible_quitstuff(void){ /* TODO try to get rid of that */
   char const *cp;
   NYD2_IN;

   if((cp = fexpand("&", FEXP_NVAR)) == NULL)
      cp = n_empty;
   su_cs_pcopy_n(_mboxname, cp, sizeof _mboxname);
   NYD2_OU;
}

FL int
savequitflags(void)
{
   enum quitflags qf = 0;
   uz i;
   NYD_IN;

   for (i = 0; i < NELEM(_quitnames); ++i)
      if (n_var_oklook(_quitnames[i].okey) != NULL)
         qf |= _quitnames[i].flag;
   NYD_OU;
   return qf;
}

FL void
restorequitflags(int qf)
{
   uz i;
   NYD_IN;

   for (i = 0;  i < NELEM(_quitnames); ++i) {
      char *x = n_var_oklook(_quitnames[i].okey);
      if (qf & _quitnames[i].flag) {
         if (x == NULL)
            n_var_okset(_quitnames[i].okey, TRU1);
      } else if (x != NULL)
         n_var_okclear(_quitnames[i].okey);
   }
   NYD_OU;
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/mx/random.c000066400000000000000000000237071352610246600157330ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ Implementation of random.h.
 *
 * Copyright (c) 2015 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE random
#define mx_SOURCE
#define mx_SOURCE_RANDOM

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

#include "mx/random.h"
#if mx_HAVE_RANDOM != mx_RANDOM_IMPL_ARC4 &&\
      mx_HAVE_RANDOM != mx_RANDOM_IMPL_TLS
# define a_RAND_USE_BUILTIN
# if mx_HAVE_RANDOM == mx_RANDOM_IMPL_GETRANDOM
#  include mx_RANDOM_GETRANDOM_H
# endif
# ifdef mx_HAVE_SCHED_YIELD
#  include 
# endif
#endif

#include 

#ifdef a_RAND_USE_BUILTIN
# include 
#endif

/* Already: #include "mx/random.h" */
#include "su/code-in.h"

#ifdef a_RAND_USE_BUILTIN
union a_rand_state{
   struct a_rand_arc4{
      u8 _dat[256];
      u8 _i;
      u8 _j;
      u8 __pad[6];
   } a;
   u8 b8[sizeof(struct a_rand_arc4)];
   u32 b32[sizeof(struct a_rand_arc4) / sizeof(u32)];
};
#endif

#ifdef a_RAND_USE_BUILTIN
static union a_rand_state *a_rand;
#endif

/* Our ARC4 random generator with its completely unacademical pseudo
 * initialization (shall /dev/urandom fail) */
#ifdef a_RAND_USE_BUILTIN
static void a_rand_init(void);
su_SINLINE u8 a_rand_get8(void);
static u32 a_rand_weak(u32 seed);
#endif

#ifdef a_RAND_USE_BUILTIN
static void
a_rand_init(void){
   union {int fd; uz i;} u;
   NYD2_IN;

   a_rand = n_alloc(sizeof *a_rand);

# if mx_HAVE_RANDOM == mx_RANDOM_IMPL_GETRANDOM
   /* getrandom(2) guarantees 256 without su_ERR_INTR..
    * However, support sequential reading to avoid possible hangs that have
    * been reported on the ML (2017-08-22, s-nail/s-mailx freezes when
    * mx_HAVE_GETRANDOM is #defined) */
   LCTA(sizeof(a_rand->a._dat) <= 256,
      "Buffer too large to be served without su_ERR_INTR error");
   LCTA(sizeof(a_rand->a._dat) >= 256,
      "Buffer too small to serve used array indices");
   /* C99 */{
      uz o, i;

      for(o = 0, i = sizeof a_rand->a._dat;;){
         sz gr;

         gr = mx_RANDOM_GETRANDOM_FUN(&a_rand->a._dat[o], i);
         if(gr == -1 && su_err_no() == su_ERR_NOSYS)
            break;
         a_rand->a._i = (a_rand->a._dat[a_rand->a._dat[1] ^
               a_rand->a._dat[84]]);
         a_rand->a._j = (a_rand->a._dat[a_rand->a._dat[65] ^
               a_rand->a._dat[42]]);
         /* ..but be on the safe side */
         if(gr > 0){
            i -= S(uz,gr);
            if(i == 0)
               goto jleave;
            o += S(uz,gr);
         }
         n_err(_("Not enough entropy for the "
            "P(seudo)R(andom)N(umber)G(enerator), waiting a bit\n"));
         n_msleep(250, FAL0);
      }
   }

# elif mx_HAVE_RANDOM == mx_RANDOM_IMPL_URANDOM
   if((u.fd = open("/dev/urandom", O_RDONLY)) != -1){
      boole ok;

      ok = (sizeof(a_rand->a._dat) == S(uz,read(u.fd,
            a_rand->a._dat, sizeof(a_rand->a._dat))));
      close(u.fd);

      a_rand->a._i = (a_rand->a._dat[a_rand->a._dat[1] ^ a_rand->a._dat[84]]);
      a_rand->a._j = (a_rand->a._dat[a_rand->a._dat[65] ^ a_rand->a._dat[42]]);
      if(ok)
         goto jleave;
   }
# elif mx_HAVE_RANDOM != mx_RANDOM_IMPL_BUILTIN
#  error a_rand_init(): the value of mx_HAVE_RANDOM is not supported
# endif

   /* As a fallback, a homebrew seed */
   if(n_poption & n_PO_D_V)
      n_err(_("P(seudo)R(andom)N(umber)G(enerator): "
         "creating homebrew seed\n"));
   /* C99 */{
# ifdef mx_HAVE_CLOCK_GETTIME
      struct timespec ts;
# else
      struct timeval ts;
# endif
      boole slept;
      u32 seed, rnd, t, k;

      /* We first do three rounds, and then add onto that a (cramped) random
       * number of rounds; in between we give up our timeslice once (from our
       * point of view) */
      seed = R(up,a_rand) & U32_MAX;
      rnd = 3;
      slept = FAL0;

      for(;;){
         /* Stir the entire pool once */
         for(u.i = NELEM(a_rand->b32); u.i-- != 0;){

# ifdef mx_HAVE_CLOCK_GETTIME
            clock_gettime(CLOCK_REALTIME, &ts);
            t = S(u32,ts.tv_nsec);
# else
            gettimeofday(&ts, NIL);
            t = S(u32,ts.tv_usec);
# endif
            if(rnd & 1)
               t = (t >> 16) | (t << 16);
            a_rand->b32[u.i] ^= a_rand_weak(seed ^ t);
            a_rand->b32[t % NELEM(a_rand->b32)] ^= seed;
            if(rnd == 7 || rnd == 17)
               a_rand->b32[u.i] ^= a_rand_weak(seed ^ S(u32,ts.tv_sec));
            k = a_rand->b32[u.i] % NELEM(a_rand->b32);
            a_rand->b32[k] ^= a_rand->b32[u.i];
            seed ^= a_rand_weak(a_rand->b32[k]);
            if((rnd & 3) == 3)
               seed ^= su_prime_lookup_next(seed);
         }

         if(--rnd == 0){
            if(slept)
               break;
            rnd = (a_rand_get8() % 5) + 3;
# ifdef mx_HAVE_SCHED_YIELD
            sched_yield();
# elif defined mx_HAVE_NANOSLEEP
            ts.tv_sec = 0, ts.tv_nsec = 0;
            nanosleep(&ts, NIL);
# else
            rnd += 10;
# endif
            slept = TRU1;
         }
      }

      for(u.i = sizeof(a_rand->b8) * ((a_rand_get8() % 5)  + 1);
            u.i != 0; --u.i)
         a_rand_get8();
      goto jleave; /* (avoid unused warning) */
   }
jleave:
   NYD2_OU;
}

su_SINLINE u8
a_rand_get8(void){
   u8 si, sj;

   si = a_rand->a._dat[++a_rand->a._i];
   sj = a_rand->a._dat[a_rand->a._j += si];
   a_rand->a._dat[a_rand->a._i] = sj;
   a_rand->a._dat[a_rand->a._j] = si;
   return a_rand->a._dat[S(u8,si + sj)];
}

static u32
a_rand_weak(u32 seed){
   /* From "Random number generators: good ones are hard to find",
    * Park and Miller, Communications of the ACM, vol. 31, no. 10,
    * October 1988, p. 1195.
    * (In fact: FreeBSD 4.7, /usr/src/lib/libc/stdlib/random.c.) */
   u32 hi;

   if(seed == 0)
      seed = 123459876;
   hi =  seed /  127773;
         seed %= 127773;
   seed = (seed * 16807) - (hi * 2836);
   if(S(s32,seed) < 0)
      seed += S32_MAX;
   return seed;
}
#endif /* a_RAND_USE_BUILTIN */

char *
mx_random_create_buf(char *dat, uz len, u32 *reprocnt_or_nil){
   struct str b64;
   char *indat, *cp, *oudat;
   uz i, inlen, oulen;
   NYD_IN;

   if(!(n_psonce & n_PSO_RANDOM_INIT)){
      n_psonce |= n_PSO_RANDOM_INIT;

      if(n_poption & n_PO_D_V){
         char const *prngn;

#if mx_HAVE_RANDOM == mx_RANDOM_IMPL_ARC4
         prngn = "arc4random";
#elif mx_HAVE_RANDOM == mx_RANDOM_IMPL_TLS
         prngn = "*TLS RAND_*";
#elif mx_HAVE_RANDOM == mx_RANDOM_IMPL_GETRANDOM
         prngn = "getrandom(2/3) + builtin ARC4";
#elif mx_HAVE_RANDOM == mx_RANDOM_IMPL_URANDOM
         prngn = "/dev/urandom + builtin ARC4";
#elif mx_HAVE_RANDOM == mx_RANDOM_IMPL_BUILTIN
         prngn = "builtin ARC4";
#else
# error mx_random_create_buf(): the value of mx_HAVE_RANDOM is not supported
#endif
         n_err(_("P(seudo)R(andom)N(umber)G(enerator): %s\n"), prngn);
      }

#ifdef a_RAND_USE_BUILTIN
      a_rand_init();
#endif
   }

   /* We use our base64 encoder with _NOPAD set, so ensure the encoded result
    * with PAD stripped is still longer than what the user requests, easy way.
    * The relation of base64 is fixed 3 in = 4 out, and we do not want to
    * include the base64 PAD characters in our random string: give some pad */
   i = len;
   if((inlen = i % 3) != 0)
      i += 3 - inlen;
jinc1:
   inlen = i >> 2;
   oulen = inlen << 2;
   if(oulen < len){
      i += 3;
      goto jinc1;
   }
   inlen = inlen + (inlen << 1);

   indat = n_lofi_alloc(inlen +1);

   if(!su_state_has(su_STATE_REPRODUCIBLE) || reprocnt_or_nil == NIL){
#if mx_HAVE_RANDOM == mx_RANDOM_IMPL_TLS
      mx_tls_rand_bytes(indat, inlen);
#elif mx_HAVE_RANDOM != mx_RANDOM_IMPL_ARC4
      for(i = inlen; i-- > 0;)
         indat[i] = S(char,a_rand_get8());
#else
      for(cp = indat, i = inlen; i > 0;){
         union {u32 i4; char c[4];} r;
         uz j;

         r.i4 = S(u32,arc4random());
         switch((j = i & 3)){
         case 0: cp[3] = r.c[3]; j = 4; /* FALLTHRU */
         case 3: cp[2] = r.c[2]; /* FALLTHRU */
         case 2: cp[1] = r.c[1]; /* FALLTHRU */
         default: cp[0] = r.c[0]; break;
         }
         cp += j;
         i -= j;
      }
#endif
   }else{
      for(cp = indat, i = inlen; i > 0;){
         union {u32 i4; char c[4];} r;
         uz j;

         r.i4 = ++*reprocnt_or_nil;
         if(su_BOM_IS_BIG()){ /* TODO BSWAP */
            char x;

            x = r.c[0];
            r.c[0] = r.c[3];
            r.c[3] = x;
            x = r.c[1];
            r.c[1] = r.c[2];
            r.c[2] = x;
         }
         switch((j = i & 3)){
         case 0: cp[3] = r.c[3]; j = 4; /* FALLTHRU */
         case 3: cp[2] = r.c[2]; /* FALLTHRU */
         case 2: cp[1] = r.c[1]; /* FALLTHRU */
         default: cp[0] = r.c[0]; break;
         }
         cp += j;
         i -= j;
      }
   }

   oudat = (len >= oulen) ? dat : n_lofi_alloc(oulen +1);
   b64.s = oudat;
   b64_encode_buf(&b64, indat, inlen, B64_BUF | B64_RFC4648URL | B64_NOPAD);
   ASSERT(b64.l >= len);
   su_mem_copy(dat, b64.s, len);
   dat[len] = '\0';
   if(oudat != dat)
      n_lofi_free(oudat);

   n_lofi_free(indat);

   NYD_OU;
   return dat;
}

char *
mx_random_create_cp(uz len, u32 *reprocnt_or_nil){
   char *dat;
   NYD_IN;

   dat = n_autorec_alloc(len +1);
   dat = mx_random_create_buf(dat, len, reprocnt_or_nil);
   NYD_OU;
   return dat;
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/mx/send.c000066400000000000000000001533511352610246600154030ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ Message content preparation (sendmp()).
 *
 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: BSD-3-Clause
 */
/*
 * Copyright (c) 1980, 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.
 */
#undef su_FILE
#define su_FILE send
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

#include 
#include 

#include "mx/child.h"
#include "mx/colour.h"
#include "mx/file-streams.h"
/* TODO but only for creating chain! */
#include "mx/filter-quote.h"
#include "mx/iconv.h"
#include "mx/random.h"
#include "mx/sigs.h"
#include "mx/tty.h"
#include "mx/ui-str.h"

/* TODO fake */
#include "su/code-in.h"

static sigjmp_buf _send_pipejmp;

/* Going for user display, print Part: info string */
static void          _print_part_info(FILE *obuf, struct mimepart const *mpp,
                        struct n_ignore const *doitp, int level,
                        struct quoteflt *qf, u64 *stats);

/* Create a pipe; if mpp is not NULL, place some n_PIPEENV_* environment
 * variables accordingly */
static FILE *        _pipefile(struct mime_handler *mhp,
                        struct mimepart const *mpp, FILE **qbuf,
                        char const *tmpname, int term_infd);

/* Call mime_write() as approbiate and adjust statistics */
su_SINLINE sz _out(char const *buf, uz len, FILE *fp,
      enum conversion convert, enum sendaction action, struct quoteflt *qf,
      u64 *stats, struct str *outrest, struct str *inrest);

/* Simply (!) print out a LF */
static boole a_send_out_nl(FILE *fp, u64 *stats);

/* SIGPIPE handler */
static void          _send_onpipe(int signo);

/* Send one part */
static int           sendpart(struct message *zmp, struct mimepart *ip,
                        FILE *obuf, struct n_ignore const *doitp,
                        struct quoteflt *qf, enum sendaction action,
                        char **linedat, uz *linesize,
                        u64 *stats, int level);

/* Dependent on *mime-alternative-favour-rich* (favour_rich) do a tree walk
 * and check whether there are any such down mpp, which is a .m_multipart of
 * an /alternative container..
 * Afterwards the latter will flag all the subtree accordingly, setting
 * MDISPLAY in mimepart.m_flag if a part shall be displayed.
 * TODO of course all this is hacky in that it should ONLY be done so */
static boole        _send_al7ive_have_better(struct mimepart *mpp,
                        enum sendaction action, boole want_rich);
static void          _send_al7ive_flag_tree(struct mimepart *mpp,
                        enum sendaction action, boole want_rich);

/* Get a file for an attachment */
static FILE *        newfile(struct mimepart *ip, boole volatile *ispipe);

static void          pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf,
                        struct quoteflt *qf, u64 *stats);

/* Output a reasonable looking status field */
static void          statusput(const struct message *mp, FILE *obuf,
                        struct quoteflt *qf, u64 *stats);
static void          xstatusput(const struct message *mp, FILE *obuf,
                        struct quoteflt *qf, u64 *stats);

static void          put_from_(FILE *fp, struct mimepart *ip, u64 *stats);

static void
_print_part_info(FILE *obuf, struct mimepart const *mpp, /* TODO strtofmt.. */
   struct n_ignore const *doitp, int level, struct quoteflt *qf, u64 *stats)
{
   char buf[64];
   struct str ti, to;
   boole want_ct, needsep;
   struct str const *cpre, *csuf;
   char const *cp;
   NYD2_IN;

   cpre = csuf = NULL;
#ifdef mx_HAVE_COLOUR
   if(mx_COLOUR_IS_ACTIVE()){
      struct mx_colour_pen *cpen;

      cpen = mx_colour_pen_create(mx_COLOUR_ID_VIEW_PARTINFO, NULL);
      if((cpre = mx_colour_pen_to_str(cpen)) != NIL)
         csuf = mx_colour_reset_to_str();
   }
#endif

   /* Take care of "99.99", i.e., 5 */
   if ((cp = mpp->m_partstring) == NULL || cp[0] == '\0')
      cp = n_qm;
   if (level || (cp[0] != '1' && cp[1] == '\0') || (cp[0] == '1' && /* TODO */
         cp[1] == '.' && cp[2] != '1')) /* TODO code should not look like so */
      _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);

   /* Part id, content-type, encoding, charset */
   if (cpre != NULL)
      _out(cpre->s, cpre->l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
   _out("[-- #", 5, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
   _out(cp, su_cs_len(cp), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);

   to.l = snprintf(buf, sizeof buf, " %" PRIuZ "/%" PRIuZ " ",
         (uz)mpp->m_lines, (uz)mpp->m_size);
   _out(buf, to.l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);

   needsep = FAL0;

    if((cp = mpp->m_ct_type_usr_ovwr) != NULL){
      _out("+", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
      want_ct = TRU1;
   }else if((want_ct = n_ignore_is_ign(doitp,
         "content-type", sizeof("content-type") -1)))
      cp = mpp->m_ct_type_plain;
   if (want_ct && (to.l = su_cs_len(cp)) > 30 &&
            su_cs_starts_with_case(cp, "application/")) {
      uz const al = sizeof("appl../") -1, fl = sizeof("application/") -1;
      uz i = to.l - fl;
      char *x = n_autorec_alloc(al + i +1);

      su_mem_copy(x, "appl../", al);
      su_mem_copy(x + al, cp + fl, i +1);
      cp = x;
      to.l = al + i;
   }
   if(cp != NULL){
      _out(cp, to.l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
      needsep = TRU1;
   }

   if(mpp->m_multipart == NULL/* TODO */ && (cp = mpp->m_ct_enc) != NULL &&
         (!su_cs_cmp_case(cp, "7bit") ||
          n_ignore_is_ign(doitp, "content-transfer-encoding",
            sizeof("content-transfer-encoding") -1))){
      if(needsep)
         _out(", ", 2, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
      if (to.l > 25 && !su_cs_cmp_case(cp, "quoted-printable"))
         cp = "qu.-pr.";
      _out(cp, su_cs_len(cp), obuf, CONV_NONE, SEND_MBOX, qf, stats,
         NULL,NULL);
      needsep = TRU1;
   }

   if (want_ct && mpp->m_multipart == NULL/* TODO */ &&
         (cp = mpp->m_charset) != NULL) {
      if(needsep)
         _out(", ", 2, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
      _out(cp, su_cs_len(cp), obuf, CONV_NONE, SEND_MBOX, qf, stats,
         NULL,NULL);
   }

   needsep = !needsep;
   _out(&" --]"[su_S(su_u8,needsep)], 4 - needsep,
      obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
   if (csuf != NULL)
      _out(csuf->s, csuf->l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
   _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);

   /* */
   if (mpp->m_content_info & CI_MIME_ERRORS) {
      if (cpre != NULL)
         _out(cpre->s, cpre->l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
            NULL, NULL);
      _out("[-- ", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);

      ti.l = su_cs_len(ti.s = n_UNCONST(_("Defective MIME structure")));
      makeprint(&ti, &to);
      to.l = delctrl(to.s, to.l);
      _out(to.s, to.l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
      n_free(to.s);

      _out(" --]", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
      if (csuf != NULL)
         _out(csuf->s, csuf->l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
            NULL, NULL);
      _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
   }

   /* Content-Description */
   if (n_ignore_is_ign(doitp, "content-description", 19) &&
         (cp = mpp->m_content_description) != NULL && *cp != '\0') {
      if (cpre != NULL)
         _out(cpre->s, cpre->l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
            NULL, NULL);
      _out("[-- ", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);

      ti.l = su_cs_len(ti.s = n_UNCONST(mpp->m_content_description));
      mime_fromhdr(&ti, &to, TD_ISPR | TD_ICONV);
      _out(to.s, to.l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
      n_free(to.s);

      _out(" --]", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
      if (csuf != NULL)
         _out(csuf->s, csuf->l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
            NULL, NULL);
      _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
   }

   /* Filename */
   if (n_ignore_is_ign(doitp, "content-disposition", 19) &&
         mpp->m_filename != NULL && *mpp->m_filename != '\0') {
      if (cpre != NULL)
         _out(cpre->s, cpre->l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
            NULL, NULL);
      _out("[-- ", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);

      ti.l = su_cs_len(ti.s = mpp->m_filename);
      makeprint(&ti, &to);
      to.l = delctrl(to.s, to.l);
      _out(to.s, to.l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
      n_free(to.s);

      _out(" --]", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
      if (csuf != NULL)
         _out(csuf->s, csuf->l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
            NULL, NULL);
      _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
   }
   NYD2_OU;
}

static FILE *
_pipefile(struct mime_handler *mhp, struct mimepart const *mpp, FILE **qbuf,
   char const *tmpname, int term_infd)
{
   static u32 reprocnt;
   struct str s;
   char const *env_addon[9 +8/*v15*/], *cp, *sh;
   uz i;
   FILE *rbuf;
   NYD_IN;

   rbuf = *qbuf;

   if(mhp->mh_flags & MIME_HDL_ISQUOTE){
      if((*qbuf = mx_fs_tmp_open("sendp", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
               mx_FS_O_REGISTER), NIL)) == NIL){
         n_perr(_("tmpfile"), 0);
         *qbuf = rbuf;
      }
   }

   if ((mhp->mh_flags & MIME_HDL_TYPE_MASK) == MIME_HDL_PTF) {
      union {int (*ptf)(void); char const *sh;} u;

      fflush(*qbuf);
      if (*qbuf != n_stdout) /* xxx never?  v15: it'll be a filter anyway */
         fflush(n_stdout);

      u.ptf = mhp->mh_ptf;
      if((rbuf = mx_fs_pipe_open(R(char*,-1), "W", u.sh, NIL, fileno(*qbuf))
            ) == NIL)
         goto jerror;
      goto jleave;
   }

   i = 0;

   /* MAILX_FILENAME */
   if (mpp == NULL || (cp = mpp->m_filename) == NULL)
      cp = n_empty;
   env_addon[i++] = str_concat_csvl(&s, n_PIPEENV_FILENAME, "=", cp, NULL)->s;
env_addon[i++] = str_concat_csvl(&s, "NAIL_FILENAME", "=", cp, NULL)->s;/*v15*/

   /* MAILX_FILENAME_GENERATED *//* TODO pathconf NAME_MAX; but user can create
    * TODO a file wherever he wants!  *Do* create a zero-size temporary file
    * TODO and give *that* path as MAILX_FILENAME_TEMPORARY, clean it up once
    * TODO the pipe returns?  Like this we *can* verify path/name issues! */
   cp = mx_random_create_cp(MIN(NAME_MAX - 3, 16), &reprocnt);
   env_addon[i++] = str_concat_csvl(&s, n_PIPEENV_FILENAME_GENERATED, "=", cp,
         NULL)->s;
env_addon[i++] = str_concat_csvl(&s, "NAIL_FILENAME_GENERATED", "=", cp,/*v15*/
      NULL)->s;

   /* MAILX_CONTENT{,_EVIDENCE} */
   if (mpp == NULL || (cp = mpp->m_ct_type_plain) == NULL)
      cp = n_empty;
   env_addon[i++] = str_concat_csvl(&s, n_PIPEENV_CONTENT, "=", cp, NULL)->s;
env_addon[i++] = str_concat_csvl(&s, "NAIL_CONTENT", "=", cp, NULL)->s;/*v15*/

   if (mpp != NULL && mpp->m_ct_type_usr_ovwr != NULL)
      cp = mpp->m_ct_type_usr_ovwr;
   env_addon[i++] = str_concat_csvl(&s, n_PIPEENV_CONTENT_EVIDENCE, "=", cp,
         NULL)->s;
env_addon[i++] = str_concat_csvl(&s, "NAIL_CONTENT_EVIDENCE", "=", cp,/* v15 */
      NULL)->s;

   /* message/external-body, access-type=url */
   env_addon[i++] = str_concat_csvl(&s, n_PIPEENV_EXTERNAL_BODY_URL, "=",
         ((mpp != NULL && (cp = mpp->m_external_body_url) != NULL
            ) ? cp : n_empty), NULL)->s;

   /* MAILX_FILENAME_TEMPORARY? */
   if (tmpname != NULL) {
      env_addon[i++] = str_concat_csvl(&s,
            n_PIPEENV_FILENAME_TEMPORARY, "=", tmpname, NULL)->s;
env_addon[i++] = str_concat_csvl(&s,
         "NAIL_FILENAME_TEMPORARY", "=", tmpname, NULL)->s;/* v15 */
   }

   /* TODO we should include header information, especially From:, so
    * TODO that same-origin can be tested for e.g. external-body!!! */

   env_addon[i] = NULL;
   sh = ok_vlook(SHELL);

   if (mhp->mh_flags & MIME_HDL_NEEDSTERM) {
      struct mx_child_ctx cc;
      sigset_t nset;

      sigemptyset(&nset);
      mx_child_ctx_setup(&cc);
      cc.cc_flags = mx_CHILD_RUN_WAIT_LIFE;
      cc.cc_mask = &nset;
      cc.cc_fds[mx_CHILD_FD_IN] = term_infd;
      cc.cc_cmd = sh;
      cc.cc_args[0] = "-c";
      cc.cc_args[1] = mhp->mh_shell_cmd;
      cc.cc_env_addon = env_addon;

      rbuf = !mx_child_run(&cc) ? NIL : R(FILE*,-1);
   }else{
      rbuf = mx_fs_pipe_open(mhp->mh_shell_cmd, "W", sh, env_addon,
            (mhp->mh_flags & MIME_HDL_ASYNC ? mx_CHILD_FD_NULL
             : fileno(*qbuf)));
jerror:
      if(rbuf == NIL)
         n_err(_("Cannot run MIME type handler: %s: %s\n"),
            mhp->mh_msg, su_err_doc(su_err_no()));
      else{
         fflush(*qbuf);
         if(*qbuf != n_stdout)
            fflush(n_stdout);
      }
   }
jleave:
   NYD_OU;
   return rbuf;
}

su_SINLINE sz
_out(char const *buf, uz len, FILE *fp, enum conversion convert, enum
   sendaction action, struct quoteflt *qf, u64 *stats, struct str *outrest,
   struct str *inrest)
{
   sz size = 0, n;
   int flags;
   NYD_IN;

   /* TODO We should not need is_head() here, i think in v15 the actual Mailbox
    * TODO subclass should detect From_ cases and either re-encode the part
    * TODO in question, or perform From_ quoting as necessary!?!?!?  How?!? */
   /* C99 */{
      boole from_;

      if((action == SEND_MBOX || action == SEND_DECRYPT) &&
            (from_ = is_head(buf, len, TRU1))){
         if(from_ != TRUM1 || (mb.mb_active & MB_BAD_FROM_) ||
               ok_blook(mbox_rfc4155)){
            putc('>', fp);
            ++size;
         }
      }
   }

   flags = ((int)action & _TD_EOF);
   action &= ~_TD_EOF;
   n = mime_write(buf, len, fp,
         action == SEND_MBOX ? CONV_NONE : convert,
         flags | ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
            action == SEND_TODISP_PARTS ||
            action == SEND_QUOTE || action == SEND_QUOTE_ALL)
         ?  TD_ISPR | TD_ICONV
         : (action == SEND_TOSRCH || action == SEND_TOPIPE ||
               action == SEND_TOFILE)
            ? TD_ICONV : (action == SEND_SHOW ? TD_ISPR : TD_NONE)),
         qf, outrest, inrest);
   if (n < 0)
      size = n;
   else if (n > 0) {
      size += n;
      if (stats != NULL)
         *stats += size;
   }
   NYD_OU;
   return size;
}

static boole
a_send_out_nl(FILE *fp, u64 *stats){
   struct quoteflt *qf;
   boole rv;
   NYD2_IN;

   quoteflt_reset(qf = quoteflt_dummy(), fp);
   rv = (_out("\n", 1, fp, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL) > 0);
   quoteflt_flush(qf);
   NYD2_OU;
   return rv;
}

static void
_send_onpipe(int signo)
{
   NYD; /* Signal handler */
   UNUSED(signo);
   siglongjmp(_send_pipejmp, 1);
}

static sigjmp_buf       __sendp_actjmp; /* TODO someday.. */
static int              __sendp_sig; /* TODO someday.. */
static n_sighdl_t  __sendp_opipe;
static void
__sendp_onsig(int sig) /* TODO someday, we won't need it no more */
{
   NYD; /* Signal handler */
   __sendp_sig = sig;
   siglongjmp(__sendp_actjmp, 1);
}

static int
sendpart(struct message *zmp, struct mimepart *ip, FILE * volatile obuf,
   struct n_ignore const *doitp, struct quoteflt *qf,
   enum sendaction volatile action,
   char **linedat, uz *linesize, u64 * volatile stats, int level)
{
   int volatile rv = 0;
   struct mime_handler mh_stack, * volatile mhp;
   struct str outrest, inrest;
   char *cp;
   char const * volatile tmpname = NULL;
   uz linelen, cnt;
   int volatile dostat, term_infd;
   int c;
   struct mimepart * volatile np;
   FILE * volatile ibuf = NULL, * volatile pbuf = obuf,
      * volatile qbuf = obuf, *origobuf = obuf;
   enum conversion volatile convert;
   n_sighdl_t volatile oldpipe = SIG_DFL;
   NYD_IN;

   UNINIT(term_infd, 0);
   UNINIT(cnt, 0);

   quoteflt_reset(qf, obuf);

#if 0 /* TODO PART_INFO should be displayed here!! search PART_INFO */
   if(ip->m_mimecontent != MIME_DISCARD && level > 0)
      _print_part_info(obuf, ip, doitp, level, qf, stats);
#endif

   if (ip->m_mimecontent == MIME_PKCS7) {
      if (ip->m_multipart &&
            action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
         goto jheaders_skip;
   }

   dostat = 0;
   if (level == 0 && action != SEND_TODISP_PARTS) {
      if (doitp != NULL) {
         if (!n_ignore_is_ign(doitp, "status", 6))
            dostat |= 1;
         if (!n_ignore_is_ign(doitp, "x-status", 8))
            dostat |= 2;
      } else
         dostat = 3;
   }

   if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL) {
      rv = -1;
      goto jleave;
   }

   if(action == SEND_TODISP || action == SEND_TODISP_ALL ||
         action == SEND_TODISP_PARTS ||
         action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
         action == SEND_TOSRCH)
      dostat |= 4;

   cnt = ip->m_size;

   if (ip->m_mimecontent == MIME_DISCARD)
      goto jheaders_skip;

   if (!(ip->m_flag & MNOFROM))
      while (cnt && (c = getc(ibuf)) != EOF) {
         cnt--;
         if (c == '\n')
            break;
      }
   convert = (dostat & 4) ? CONV_FROMHDR : CONV_NONE;

   /* Work the headers */
   /* C99 */{
   struct n_string hl, *hlp;
   uz lineno = 0;
   boole hstop/*see below, hany*/;

   hlp = n_string_creat_auto(&hl); /* TODO pool [or, v15: filter!] */
   /* Reserve three lines, still not enough for references and DKIM etc. */
   hlp = n_string_reserve(hlp, MAX(MIME_LINELEN, MIME_LINELEN_RFC2047) * 3);

   for(hstop = /*see below hany =*/ FAL0; !hstop;){
      uz lcnt;

      lcnt = cnt;
      if(fgetline(linedat, linesize, &cnt, &linelen, ibuf, 0) == NULL)
         break;
      ++lineno;
      if (linelen == 0 || (cp = *linedat)[0] == '\n')
         /* If line is blank, we've reached end of headers */
         break;
      if(cp[linelen - 1] == '\n'){
         cp[--linelen] = '\0';
         if(linelen == 0)
            break;
      }

      /* Are we in a header? */
      if(hlp->s_len > 0){
         if(!su_cs_is_blank(*cp)){
            fseek(ibuf, -(off_t)(lcnt - cnt), SEEK_CUR);
            cnt = lcnt;
            goto jhdrput;
         }
         goto jhdrpush;
      }else{
         /* Pick up the header field if we have one */
         while((c = *cp) != ':' && !su_cs_is_space(c) && c != '\0')
            ++cp;
         for(;;){
            if(!su_cs_is_space(c) || c == '\0')
               break;
            c = *++cp;
         }
         if(c != ':'){
            /* That won't work with MIME when saving etc., before v15 */
            if (lineno != 1)
               /* XXX This disturbs, and may happen multiple times, and we
                * XXX cannot heal it for multipart except for display s_dat, ':', hlp->s_len)) = '\0';
      /* C99 */{
         uz i;

         i = P2UZ(cp - hlp->s_dat);
         if((doitp != NULL && n_ignore_is_ign(doitp, hlp->s_dat, i)) ||
               !su_cs_cmp_case(hlp->s_dat, "status") ||
               !su_cs_cmp_case(hlp->s_dat, "x-status") ||
               (action == SEND_MBOX &&
                  (!su_cs_cmp_case(hlp->s_dat, "content-length") ||
                   !su_cs_cmp_case(hlp->s_dat, "lines")) &&
                !ok_blook(keep_content_length)))
            goto jhdrtrunc;
      }

      /* Dump it */
      mx_COLOUR(
         if(mx_COLOUR_IS_ACTIVE())
            mx_colour_put(mx_COLOUR_ID_VIEW_HEADER, hlp->s_dat);
      )
      *cp = ':';
      _out(hlp->s_dat, hlp->s_len, obuf, convert, action, qf, stats, NULL,NULL);
      mx_COLOUR(
         if(mx_COLOUR_IS_ACTIVE())
            mx_colour_reset();
      )
      if(dostat & 4)
         _out("\n", sizeof("\n") -1, obuf, convert, action, qf, stats,
            NULL,NULL);
      /*see below hany = TRU1;*/

jhdrtrunc:
      hlp = n_string_trunc(hlp, 0);
   }
   hstop = TRU1;
   if(hlp->s_len > 0)
      goto jhdrput;

   /* We've reached end of headers, so eventually force out status: field and
    * note that we are no longer in header fields */
   if(dostat & 1){
      statusput(zmp, obuf, qf, stats);
      /*see below hany = TRU1;*/
   }
   if(dostat & 2){
      xstatusput(zmp, obuf, qf, stats);
      /*see below hany = TRU1;*/
   }
   if(/* TODO PART_INFO hany && */ doitp != n_IGNORE_ALL)
      _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
   } /* C99 */

   quoteflt_flush(qf);

   if(ferror(obuf)){
      rv = -1;
      goto jleave;
   }

jheaders_skip:
   su_mem_set(mhp = &mh_stack, 0, sizeof mh_stack);

   switch (ip->m_mimecontent) {
   case MIME_822:
      switch (action) {
      case SEND_TODISP_PARTS:
         goto jleave;
      case SEND_TODISP:
      case SEND_TODISP_ALL:
      case SEND_QUOTE:
      case SEND_QUOTE_ALL:
         if (ok_blook(rfc822_body_from_)) {
            if (!qf->qf_bypass) {
               uz i = fwrite(qf->qf_pfix, sizeof *qf->qf_pfix,
                     qf->qf_pfix_len, obuf);
               if (i == qf->qf_pfix_len && stats != NULL)
                  *stats += i;
            }
            put_from_(obuf, ip->m_multipart, stats);
         }
         /* FALLTHRU */
      case SEND_TOSRCH:
      case SEND_DECRYPT:
         goto jmulti;
      case SEND_TOFILE:
      case SEND_TOPIPE:
         put_from_(obuf, ip->m_multipart, stats);
         /* FALLTHRU */
      case SEND_MBOX:
      case SEND_RFC822:
      case SEND_SHOW:
         break;
      }
      break;
   case MIME_TEXT_HTML:
   case MIME_TEXT:
   case MIME_TEXT_PLAIN:
      switch (action) {
      case SEND_TODISP:
      case SEND_TODISP_ALL:
      case SEND_TODISP_PARTS:
      case SEND_QUOTE:
      case SEND_QUOTE_ALL:
         if ((mhp = ip->m_handler) == NULL)
            n_mimetype_handler(mhp =
               ip->m_handler = n_autorec_alloc(sizeof(*mhp)), ip, action);
         switch (mhp->mh_flags & MIME_HDL_TYPE_MASK) {
         case MIME_HDL_NULL:
            if(action != SEND_TODISP_PARTS)
               break;
            /* FALLTHRU */
         case MIME_HDL_MSG:/* TODO these should be part of partinfo! */
            if(mhp->mh_msg.l > 0)
               _out(mhp->mh_msg.s, mhp->mh_msg.l, obuf, CONV_NONE, SEND_MBOX,
                  qf, stats, NULL, NULL);
            /* We would print this as plain text, so better force going home */
            goto jleave;
         case MIME_HDL_CMD:
            if(action == SEND_TODISP_PARTS &&
                  (mhp->mh_flags & MIME_HDL_COPIOUSOUTPUT))
               goto jleave;
            break;
         case MIME_HDL_TEXT:
         case MIME_HDL_PTF:
            if(action == SEND_TODISP_PARTS)
               goto jleave;
            break;
         default:
            break;
         }
         /* FALLTRHU */
      default:
         break;
      }
      break;
   case MIME_DISCARD:
      if (action != SEND_DECRYPT)
         goto jleave;
      break;
   case MIME_PKCS7:
      if (action != SEND_MBOX && action != SEND_RFC822 &&
            action != SEND_SHOW && ip->m_multipart != NULL)
         goto jmulti;
      /* FALLTHRU */
   default:
      switch (action) {
      case SEND_TODISP:
      case SEND_TODISP_ALL:
      case SEND_TODISP_PARTS:
      case SEND_QUOTE:
      case SEND_QUOTE_ALL:
         if ((mhp = ip->m_handler) == NULL)
            n_mimetype_handler(mhp = ip->m_handler =
               n_autorec_alloc(sizeof(*mhp)), ip, action);
         switch (mhp->mh_flags & MIME_HDL_TYPE_MASK) {
         default:
         case MIME_HDL_NULL:
            if (action != SEND_TODISP && action != SEND_TODISP_ALL &&
                  (level != 0 || cnt))
               goto jleave;
            /* FALLTHRU */
         case MIME_HDL_MSG:/* TODO these should be part of partinfo! */
            if(mhp->mh_msg.l > 0)
               _out(mhp->mh_msg.s, mhp->mh_msg.l, obuf, CONV_NONE, SEND_MBOX,
                  qf, stats, NULL, NULL);
            /* We would print this as plain text, so better force going home */
            goto jleave;
         case MIME_HDL_CMD:
            if(action == SEND_TODISP_PARTS){
               if(mhp->mh_flags & MIME_HDL_COPIOUSOUTPUT)
                  goto jleave;
               else{
                  _print_part_info(obuf, ip, doitp, level, qf, stats);
                  /* Because: interactive OR batch mode, so */
                  if(!mx_tty_yesorno(_("Run MIME handler for this part?"),
                        su_state_has(su_STATE_REPRODUCIBLE)))
                     goto jleave;
               }
            }
            break;
         case MIME_HDL_TEXT:
         case MIME_HDL_PTF:
            if(action == SEND_TODISP_PARTS)
               goto jleave;
            break;
         }
         break;
      case SEND_TOFILE:
      case SEND_TOPIPE:
      case SEND_TOSRCH:
      case SEND_DECRYPT:
      case SEND_MBOX:
      case SEND_RFC822:
      case SEND_SHOW:
         break;
      }
      break;
   case MIME_ALTERNATIVE:
      if ((action == SEND_TODISP || action == SEND_QUOTE) &&
            !ok_blook(print_alternatives)) {
         /* XXX This (a) should not remain (b) should be own fun
          * TODO (despite the fact that v15 will do this completely differently
          * TODO by having an action-specific "manager" that will traverse the
          * TODO parsed MIME tree and decide for each part whether it'll be
          * TODO displayed or not *before* we walk the tree for doing action */
         struct mpstack {
            struct mpstack *outer;
            struct mimepart *mp;
         } outermost, * volatile curr, * volatile mpsp;
         enum {
            _NONE,
            _DORICH  = 1<<0,  /* We are looking for rich parts */
            _HADPART = 1<<1,  /* Did print a part already */
            _NEEDNL  = 1<<3   /* Need a visual separator */
         } flags;
         struct n_sigman smalter;

         (curr = &outermost)->outer = NULL;
         curr->mp = ip;
         flags = ok_blook(mime_alternative_favour_rich) ? _DORICH : _NONE;
         if (!_send_al7ive_have_better(ip->m_multipart, action,
               ((flags & _DORICH) != 0)))
            flags ^= _DORICH;
         _send_al7ive_flag_tree(ip->m_multipart, action,
            ((flags & _DORICH) != 0));

         n_SIGMAN_ENTER_SWITCH(&smalter, n_SIGMAN_ALL) {
         case 0:
            break;
         default:
            rv = -1;
            goto jalter_leave;
         }

         for (np = ip->m_multipart;;) {
jalter_redo:
            for (; np != NULL; np = np->m_nextpart) {
               if (action != SEND_QUOTE && np->m_ct_type_plain != NULL) {
                  if (flags & _NEEDNL)
                     _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats,
                        NULL, NULL);
                  _print_part_info(obuf, np, doitp, level, qf, stats);
               }
               flags |= _NEEDNL;

               switch (np->m_mimecontent) {
               case MIME_ALTERNATIVE:
               case MIME_RELATED:
               case MIME_DIGEST:
               case MIME_SIGNED:
               case MIME_ENCRYPTED:
               case MIME_MULTI:
                  mpsp = n_autorec_alloc(sizeof *mpsp);
                  mpsp->outer = curr;
                  mpsp->mp = np->m_multipart;
                  curr->mp = np;
                  curr = mpsp;
                  np = mpsp->mp;
                  flags &= ~_NEEDNL;
                  goto jalter_redo;
               default:
                  if (!(np->m_flag & MDISPLAY))
                     break;
                  /* This thing we gonna do */
                  quoteflt_flush(qf);
                  if ((flags & _HADPART) && action == SEND_QUOTE)
                     /* XXX (void)*/a_send_out_nl(obuf, stats);
                  flags |= _HADPART;
                  flags &= ~_NEEDNL;
                  rv = sendpart(zmp, np, obuf, doitp, qf, action,
                        linedat, linesize, stats, level + 1);
                  quoteflt_reset(qf, origobuf);
                  if (rv < 0)
                     curr = &outermost; /* Cause overall loop termination */
                  break;
               }
            }

            mpsp = curr->outer;
            if (mpsp == NULL)
               break;
            curr = mpsp;
            np = curr->mp->m_nextpart;
         }
jalter_leave:
         n_sigman_leave(&smalter, n_SIGMAN_VIPSIGS_NTTYOUT);
         goto jleave;
      }
      /* FALLTHRU */
   case MIME_RELATED:
   case MIME_DIGEST:
   case MIME_SIGNED:
   case MIME_ENCRYPTED:
   case MIME_MULTI:
      switch (action) {
      case SEND_TODISP:
      case SEND_TODISP_ALL:
      case SEND_TODISP_PARTS:
      case SEND_QUOTE:
      case SEND_QUOTE_ALL:
      case SEND_TOFILE:
      case SEND_TOPIPE:
      case SEND_TOSRCH:
      case SEND_DECRYPT:
jmulti:
         if ((action == SEND_TODISP || action == SEND_TODISP_ALL) &&
             ip->m_multipart != NULL &&
             ip->m_multipart->m_mimecontent == MIME_DISCARD &&
             ip->m_multipart->m_nextpart == NULL) {
            char const *x = _("[Missing multipart boundary - use `show' "
                  "to display the raw message]\n");
            _out(x, su_cs_len(x), obuf, CONV_NONE, SEND_MBOX, qf, stats,
               NULL,NULL);
         }

         for (np = ip->m_multipart; np != NULL; np = np->m_nextpart) {
            boole volatile ispipe;

            if (np->m_mimecontent == MIME_DISCARD && action != SEND_DECRYPT)
               continue;

            ispipe = FAL0;
            switch (action) {
            case SEND_TOFILE:
               if (np->m_partstring &&
                     np->m_partstring[0] == '1' && np->m_partstring[1] == '\0')
                  break;
               stats = NULL;
               /* TODO Always open multipart on /dev/null, it's a hack to be
                * TODO able to dive into that structure, and still better
                * TODO than asking the user for something stupid.
                * TODO oh, wait, we did ask for a filename for this MIME mail,
                * TODO and that outer container is useless anyway ;-P */
               if(np->m_multipart != NULL && np->m_mimecontent != MIME_822) {
                  if((obuf = mx_fs_open(n_path_devnull, "w")) == NIL)
                     continue;
               }else if((obuf = newfile(np, &ispipe)) == NIL)
                  continue;
               if(ispipe){
                  oldpipe = safe_signal(SIGPIPE, &_send_onpipe);
                  if(sigsetjmp(_send_pipejmp, 1)){
                     rv = -1;
                     goto jpipe_close;
                  }
               }
               break;
            case SEND_TODISP:
            case SEND_TODISP_ALL:
               if (ip->m_mimecontent != MIME_ALTERNATIVE &&
                     ip->m_mimecontent != MIME_RELATED &&
                     ip->m_mimecontent != MIME_DIGEST &&
                     ip->m_mimecontent != MIME_SIGNED &&
                     ip->m_mimecontent != MIME_ENCRYPTED &&
                     ip->m_mimecontent != MIME_MULTI)
                  break;
               _print_part_info(obuf, np, doitp, level, qf, stats);
               break;
            case SEND_TODISP_PARTS:
            case SEND_QUOTE:
            case SEND_QUOTE_ALL:
            case SEND_MBOX:
            case SEND_RFC822:
            case SEND_SHOW:
            case SEND_TOSRCH:
            case SEND_DECRYPT:
            case SEND_TOPIPE:
               break;
            }

            quoteflt_flush(qf);
            if ((action == SEND_QUOTE || action == SEND_QUOTE_ALL) &&
                  np->m_multipart == NULL && ip->m_parent != NULL)
               /*XXX (void)*/a_send_out_nl(obuf, stats);
            if (sendpart(zmp, np, obuf, doitp, qf, action, linedat, linesize,
                  stats, level+1) < 0)
               rv = -1;
            quoteflt_reset(qf, origobuf);

            if (action == SEND_QUOTE) {
               if (ip->m_mimecontent != MIME_RELATED)
                  break;
            }
            if (action == SEND_TOFILE && obuf != origobuf) {
               if(!ispipe)
                  mx_fs_close(obuf);
               else {
jpipe_close:
                  mx_fs_pipe_close(obuf, TRU1);
                  safe_signal(SIGPIPE, oldpipe);
               }
            }
         }
         goto jleave;
      case SEND_MBOX:
      case SEND_RFC822:
      case SEND_SHOW:
         break;
      }
      break;
   }

   /* Copy out message body */
   if (doitp == n_IGNORE_ALL && level == 0) /* skip final blank line */
      --cnt;
   switch (ip->m_mime_enc) {
   case MIMEE_BIN:
   case MIMEE_7B:
   case MIMEE_8B:
      convert = CONV_NONE;
      break;
   case MIMEE_QP:
      convert = CONV_FROMQP;
      break;
   case MIMEE_B64:
      switch (ip->m_mimecontent) {
      case MIME_TEXT:
      case MIME_TEXT_PLAIN:
      case MIME_TEXT_HTML:
         convert = CONV_FROMB64_T;
         break;
      default:
         switch (mhp->mh_flags & MIME_HDL_TYPE_MASK) {
         case MIME_HDL_TEXT:
         case MIME_HDL_PTF:
            convert = CONV_FROMB64_T;
            break;
         default:
            convert = CONV_FROMB64;
            break;
         }
         break;
      }
      break;
   default:
      convert = CONV_NONE;
   }

   /* TODO Unless we have filters, ensure iconvd==-1 so that mime.c:fwrite_td()
    * TODO cannot mess things up misusing outrest as line buffer */
#ifdef mx_HAVE_ICONV
   if (iconvd != (iconv_t)-1) {
      n_iconv_close(iconvd);
      iconvd = (iconv_t)-1;
   }
#endif

   if (action == SEND_DECRYPT || action == SEND_MBOX ||
         action == SEND_RFC822 || action == SEND_SHOW)
      convert = CONV_NONE;
#ifdef mx_HAVE_ICONV
   else if ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
            action == SEND_TODISP_PARTS ||
            action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
            action == SEND_TOSRCH || action == SEND_TOFILE) &&
         (ip->m_mimecontent == MIME_TEXT_PLAIN ||
            ip->m_mimecontent == MIME_TEXT_HTML ||
            ip->m_mimecontent == MIME_TEXT ||
            (mhp->mh_flags & MIME_HDL_TYPE_MASK) == MIME_HDL_TEXT ||
            (mhp->mh_flags & MIME_HDL_TYPE_MASK) == MIME_HDL_PTF)) {
      char const *tcs;

      tcs = ok_vlook(ttycharset);
      if (su_cs_cmp_case(tcs, ip->m_charset) &&
            su_cs_cmp_case(ok_vlook(charset_7bit), ip->m_charset)) {
         iconvd = n_iconv_open(tcs, ip->m_charset);
         if (iconvd == (iconv_t)-1 && su_err_no() == su_ERR_INVAL) {
            n_err(_("Cannot convert from %s to %s\n"), ip->m_charset, tcs);
            /*rv = 1; goto jleave;*/
         }
      }
   }
#endif

   switch (mhp->mh_flags & MIME_HDL_TYPE_MASK) {
   case MIME_HDL_CMD:
      if(!(mhp->mh_flags & MIME_HDL_COPIOUSOUTPUT)){
         if(action != SEND_TODISP_PARTS)
            goto jmhp_default;
         /* Ach, what a hack!  We need filters.. v15! */
         if(convert != CONV_FROMB64_T)
            action = SEND_TOPIPE;
      }
      /* FALLTHRU */
   case MIME_HDL_PTF:
      tmpname = NULL;
      qbuf = obuf;

      term_infd = mx_CHILD_FD_PASS;
      if (mhp->mh_flags & (MIME_HDL_TMPF | MIME_HDL_NEEDSTERM)) {
         struct mx_fs_tmp_ctx *fstcp;
         enum mx_fs_oflags of;

         of = mx_FS_O_RDWR | mx_FS_O_REGISTER;
         if(!(mhp->mh_flags & MIME_HDL_TMPF)){
            term_infd = 0;
            mhp->mh_flags |= MIME_HDL_TMPF_FILL;
            of |= mx_FS_O_UNLINK;
         }else{
            /* (async and unlink are mutual exclusive) */
            if(mhp->mh_flags & MIME_HDL_TMPF_UNLINK)
               of |= mx_FS_O_REGISTER_UNLINK;
         }

         if((pbuf = mx_fs_tmp_open((mhp->mh_flags & MIME_HDL_TMPF_FILL
                     ? "mimehdlfill" : "mimehdl"), of,
               (mhp->mh_flags & MIME_HDL_TMPF ? &fstcp : NIL))) == NIL)
            goto jesend;

         if(mhp->mh_flags & MIME_HDL_TMPF)
            tmpname = fstcp->fstc_filename; /* In autorec storage! */

         if(mhp->mh_flags & MIME_HDL_TMPF_FILL){
            if(term_infd == 0)
               term_infd = fileno(pbuf);
            goto jsend;
         }
      }

jpipe_for_real:
      pbuf = _pipefile(mhp, ip, UNVOLATILE(FILE**,&qbuf), tmpname, term_infd);
      if (pbuf == NULL) {
jesend:
         pbuf = qbuf = NULL;
         rv = -1;
         goto jend;
      } else if ((mhp->mh_flags & MIME_HDL_NEEDSTERM) && pbuf == (FILE*)-1) {
         pbuf = qbuf = NULL;
         goto jend;
      }
      tmpname = NULL;
      action = SEND_TOPIPE;
      if (pbuf != qbuf) {
         oldpipe = safe_signal(SIGPIPE, &_send_onpipe);
         if (sigsetjmp(_send_pipejmp, 1))
            goto jend;
      }
      break;

   default:
jmhp_default:
      mhp->mh_flags = MIME_HDL_NULL;
      pbuf = qbuf = obuf;
      break;
   }

jsend:
   {
   boole volatile eof;
   boole save_qf_bypass = qf->qf_bypass;
   u64 *save_stats = stats;

   if (pbuf != origobuf) {
      qf->qf_bypass = TRU1;/* XXX legacy (remove filter instead) */
      stats = NULL;
   }
   eof = FAL0;
   outrest.s = inrest.s = NULL;
   outrest.l = inrest.l = 0;

   if (pbuf == qbuf) {
      __sendp_sig = 0;
      __sendp_opipe = safe_signal(SIGPIPE, &__sendp_onsig);
      if (sigsetjmp(__sendp_actjmp, 1)) {
         n_pstate &= ~n_PS_BASE64_STRIP_CR;/* (but protected by outer sigman) */
         if (outrest.s != NULL)
            n_free(outrest.s);
         if (inrest.s != NULL)
            n_free(inrest.s);
#ifdef mx_HAVE_ICONV
         if (iconvd != (iconv_t)-1)
            n_iconv_close(iconvd);
#endif
         safe_signal(SIGPIPE, __sendp_opipe);
         n_raise(__sendp_sig);
      }
   }

   quoteflt_reset(qf, pbuf);
   if((dostat & 4) && pbuf == origobuf) /* TODO */
      n_pstate |= n_PS_BASE64_STRIP_CR;
   while (!eof && fgetline(linedat, linesize, &cnt, &linelen, ibuf, 0)) {
joutln:
      if (_out(*linedat, linelen, pbuf, convert, action, qf, stats, &outrest,
            (action & _TD_EOF ? NULL : &inrest)) < 0 || ferror(pbuf)) {
         rv = -1; /* XXX Should bail away?! */
         break;
      }
   }
   if(eof <= FAL0 && rv >= 0 && (outrest.l != 0 || inrest.l != 0)){
      linelen = 0;
      if(eof || inrest.l == 0)
         action |= _TD_EOF;
      eof = eof ? TRU1 : TRUM1;
      goto joutln;
   }
   n_pstate &= ~n_PS_BASE64_STRIP_CR;
   action &= ~_TD_EOF;

   /* TODO HACK: when sending to the display we yet get fooled if a message
    * TODO doesn't end in a newline, because of our input/output 1:1.
    * TODO This should be handled automatically by a display filter, then */
   if(rv >= 0 && !qf->qf_nl_last &&
         (action == SEND_TODISP || action == SEND_TODISP_ALL ||
          action == SEND_QUOTE || action == SEND_QUOTE_ALL))
      rv = quoteflt_push(qf, "\n", 1);

   quoteflt_flush(qf);

   if (rv >= 0 && (mhp->mh_flags & MIME_HDL_TMPF_FILL)) {
      mhp->mh_flags &= ~MIME_HDL_TMPF_FILL;
      fflush(pbuf);
      really_rewind(pbuf);
      /* Don't fs_close() a tmp_open() thing due to FS_O_UNREGISTER_UNLINK++ */
      goto jpipe_for_real;
   }

   if (pbuf == qbuf)
      safe_signal(SIGPIPE, __sendp_opipe);

   if (outrest.s != NULL)
      n_free(outrest.s);
   if (inrest.s != NULL)
      n_free(inrest.s);

   if (pbuf != origobuf) {
      qf->qf_bypass = save_qf_bypass;
      stats = save_stats;
   }
   }

jend:
   if(pbuf != qbuf){
      mx_fs_pipe_close(pbuf, !(mhp->mh_flags & MIME_HDL_ASYNC));
      safe_signal(SIGPIPE, oldpipe);
      if (rv >= 0 && qbuf != NULL && qbuf != obuf)
         pipecpy(qbuf, obuf, origobuf, qf, stats);
   }

#ifdef mx_HAVE_ICONV
   if (iconvd != (iconv_t)-1)
      n_iconv_close(iconvd);
#endif

jleave:
   NYD_OU;
   return rv;
}

static boole
_send_al7ive_have_better(struct mimepart *mpp, enum sendaction action,
   boole want_rich)
{
   boole rv = FAL0;
   NYD_IN;

   for (; mpp != NULL; mpp = mpp->m_nextpart) {
      switch (mpp->m_mimecontent) {
      case MIME_TEXT_PLAIN:
         if (!want_rich)
            goto jflag;
         continue;
      case MIME_ALTERNATIVE:
      case MIME_RELATED:
      case MIME_DIGEST:
      case MIME_SIGNED:
      case MIME_ENCRYPTED:
      case MIME_MULTI:
         /* Be simple and recurse */
         if (_send_al7ive_have_better(mpp->m_multipart, action, want_rich))
            goto jleave;
         continue;
      default:
         break;
      }

      if (mpp->m_handler == NULL)
         n_mimetype_handler(mpp->m_handler =
            n_autorec_alloc(sizeof(*mpp->m_handler)), mpp, action);
      switch (mpp->m_handler->mh_flags & MIME_HDL_TYPE_MASK) {
      case MIME_HDL_TEXT:
         if (!want_rich)
            goto jflag;
         break;
      case MIME_HDL_PTF:
         if (want_rich) {
jflag:
            mpp->m_flag |= MDISPLAY;
            ASSERT(mpp->m_parent != NULL);
            mpp->m_parent->m_flag |= MDISPLAY;
            rv = TRU1;
         }
         break;
      case MIME_HDL_CMD:
         if (want_rich && (mpp->m_handler->mh_flags & MIME_HDL_COPIOUSOUTPUT))
            goto jflag;
         /* FALLTHRU */
      default:
         break;
      }
   }
jleave:
   NYD_OU;
   return rv;
}

static void
_send_al7ive_flag_tree(struct mimepart *mpp, enum sendaction action,
   boole want_rich)
{
   boole hot;
   NYD_IN;

   ASSERT(mpp->m_parent != NULL);
   hot = ((mpp->m_parent->m_flag & MDISPLAY) != 0);

   for (; mpp != NULL; mpp = mpp->m_nextpart) {
      switch (mpp->m_mimecontent) {
      case MIME_TEXT_PLAIN:
         if (hot && !want_rich)
            mpp->m_flag |= MDISPLAY;
         continue;
      case MIME_ALTERNATIVE:
      case MIME_RELATED:
      case MIME_DIGEST:
      case MIME_SIGNED:
      case MIME_ENCRYPTED:
      case MIME_MULTI:
         /* Be simple and recurse */
         if (_send_al7ive_have_better(mpp->m_multipart, action, want_rich))
            goto jleave;
         continue;
      default:
         break;
      }

      if (mpp->m_handler == NULL)
         n_mimetype_handler(mpp->m_handler =
            n_autorec_alloc(sizeof(*mpp->m_handler)), mpp, action);
      switch (mpp->m_handler->mh_flags & MIME_HDL_TYPE_MASK) {
      case MIME_HDL_TEXT:
         if (hot && !want_rich)
            mpp->m_flag |= MDISPLAY;
         break;
      case MIME_HDL_PTF:
         if (hot && want_rich)
            mpp->m_flag |= MDISPLAY;
         break;
      case MIME_HDL_CMD:
         if (hot && want_rich &&
               (mpp->m_handler->mh_flags & MIME_HDL_COPIOUSOUTPUT))
            mpp->m_flag |= MDISPLAY;
         break;
         break;
      default:
         break;
      }
   }
jleave:
   NYD_OU;
}

static FILE *
newfile(struct mimepart *ip, boole volatile *ispipe)
{
   struct str in, out;
   char *f;
   FILE *fp;
   NYD_IN;

   f = ip->m_filename;
   *ispipe = FAL0;

   if (f != NULL && f != (char*)-1) {
      in.s = f;
      in.l = su_cs_len(f);
      makeprint(&in, &out);
      out.l = delctrl(out.s, out.l);
      f = savestrbuf(out.s, out.l);
      n_free(out.s);
   }

   /* In interactive mode, let user perform all kind of expansions as desired,
    * and offer |SHELL-SPEC pipe targets, too */
   if (n_psonce & n_PSO_INTERACTIVE) {
      struct str prompt;
      struct n_string shou, *shoup;
      char *f2, *f3;

      shoup = n_string_creat_auto(&shou);

      /* TODO Generic function which asks for filename.
       * TODO If the current part is the first textpart the target
       * TODO is implicit from outer `write' etc! */
      /* I18N: Filename input prompt with file type indication */
      str_concat_csvl(&prompt, _("Enter filename for part "),
         (ip->m_partstring != NULL ? ip->m_partstring : n_qm),
         " (", ip->m_ct_type_plain, "): ", NULL);
jgetname:
      f2 = n_go_input_cp(n_GO_INPUT_CTX_DEFAULT | n_GO_INPUT_HIST_ADD,
            prompt.s, ((f != (char*)-1 && f != NULL)
               ? n_shexp_quote_cp(f, FAL0) : NULL));
      if(f2 != NULL){
         in.s = n_UNCONST(f2);
         in.l = UZ_MAX;
         if((n_shexp_parse_token((n_SHEXP_PARSE_TRUNC |
                  n_SHEXP_PARSE_TRIM_SPACE | n_SHEXP_PARSE_TRIM_IFSSPACE |
                  n_SHEXP_PARSE_LOG | n_SHEXP_PARSE_IGNORE_EMPTY),
                  shoup, &in, NULL
               ) & (n_SHEXP_STATE_STOP |
                  n_SHEXP_STATE_OUTPUT | n_SHEXP_STATE_ERR_MASK)
               ) != (n_SHEXP_STATE_STOP | n_SHEXP_STATE_OUTPUT))
            goto jgetname;
         f2 = n_string_cp(shoup);
      }
      if (f2 == NULL || *f2 == '\0') {
         if (n_poption & n_PO_D_V)
            n_err(_("... skipping this\n"));
         n_string_gut(shoup);
         fp = NULL;
         goto jleave;
      }

      if (*f2 == '|')
         /* Pipes are expanded by the shell */
         f = f2;
      else if ((f3 = fexpand(f2, FEXP_LOCAL | FEXP_NVAR)) == NULL)
         /* (Error message written by fexpand()) */
         goto jgetname;
      else
         f = f3;

      n_string_gut(shoup);
   }

   if (f == NULL || f == (char*)-1 || *f == '\0')
      fp = NULL;
   else if(n_psonce & n_PSO_INTERACTIVE){
      if(*f == '|'){
         fp = mx_fs_pipe_open(&f[1], "w", ok_vlook(SHELL), NIL, -1);
         if(!(*ispipe = (fp != NIL)))
            n_perr(f, 0);
      }else if((fp = mx_fs_open(f, "w")) == NIL)
         n_err(_("Cannot open %s\n"), n_shexp_quote_cp(f, FAL0));
   }else{
      /* Be very picky in non-interactive mode: actively disallow pipes,
       * prevent directory separators, and any filename member that would
       * become expanded by the shell if the name would be echo(1)ed */
      if(su_cs_first_of(f, "/" n_SHEXP_MAGIC_PATH_CHARS) != su_UZ_MAX){
         char c;

         for(out.s = n_autorec_alloc((su_cs_len(f) * 3) +1), out.l = 0;
               (c = *f++) != '\0';)
            if(su_cs_find_c("/" n_SHEXP_MAGIC_PATH_CHARS, c)){
               out.s[out.l++] = '%';
               n_c_to_hex_base16(&out.s[out.l], c);
               out.l += 2;
            }else
               out.s[out.l++] = c;
         out.s[out.l] = '\0';
         f = out.s;
      }

      /* Avoid overwriting of existing files */
      while((fp = mx_fs_open(f, "wx")) == NIL){
         int e;

         if((e = su_err_no()) != su_ERR_EXIST){
            n_err(_("Cannot open %s: %s\n"),
               n_shexp_quote_cp(f, FAL0), su_err_doc(e));
            break;
         }

         if(ip->m_partstring != NULL)
            f = savecatsep(f, '#', ip->m_partstring);
         else
            f = savecat(f, "#.");
      }
   }
jleave:
   NYD_OU;
   return fp;
}

static void
pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf, struct quoteflt *qf,
   u64 *stats)
{
   char *line;
   uz linesize, linelen, cnt;
   sz all_sz, i;
   NYD_IN;

   fflush(pipebuf);
   rewind(pipebuf);
   cnt = S(uz,fsize(pipebuf));
   all_sz = 0;

   mx_fs_linepool_aquire(&line, &linesize);
   quoteflt_reset(qf, outbuf);
   while(fgetline(&line, &linesize, &cnt, &linelen, pipebuf, 0) != NIL){
      if((i = quoteflt_push(qf, line, linelen)) < 0)
         break;
      all_sz += i;
   }
   if((i = quoteflt_flush(qf)) > 0)
      all_sz += i;
   mx_fs_linepool_release(line, linesize);

   if(all_sz > 0 && outbuf == origobuf && stats != NIL)
      *stats += all_sz;
   mx_fs_close(pipebuf);
   NYD_OU;
}

static void
statusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
   u64 *stats)
{
   char statout[3], *cp = statout;
   NYD_IN;

   if (mp->m_flag & MREAD)
      *cp++ = 'R';
   if (!(mp->m_flag & MNEW))
      *cp++ = 'O';
   *cp = 0;
   if (statout[0]) {
      int i = fprintf(obuf, "%.*sStatus: %s\n", (int)qf->qf_pfix_len,
            (qf->qf_bypass ? NULL : qf->qf_pfix), statout);
      if (i > 0 && stats != NULL)
         *stats += i;
   }
   NYD_OU;
}

static void
xstatusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
   u64 *stats)
{
   char xstatout[4];
   char *xp = xstatout;
   NYD_IN;

   if (mp->m_flag & MFLAGGED)
      *xp++ = 'F';
   if (mp->m_flag & MANSWERED)
      *xp++ = 'A';
   if (mp->m_flag & MDRAFTED)
      *xp++ = 'T';
   *xp = 0;
   if (xstatout[0]) {
      int i = fprintf(obuf, "%.*sX-Status: %s\n", (int)qf->qf_pfix_len,
            (qf->qf_bypass ? NULL : qf->qf_pfix), xstatout);
      if (i > 0 && stats != NULL)
         *stats += i;
   }
   NYD_OU;
}

static void
put_from_(FILE *fp, struct mimepart *ip, u64 *stats)
{
   char const *froma, *date, *nl;
   int i;
   NYD_IN;

   if (ip != NULL && ip->m_from != NULL) {
      froma = ip->m_from;
      date = n_time_ctime(ip->m_time, NULL);
      nl = "\n";
   } else {
      froma = ok_vlook(LOGNAME);
      date = time_current.tc_ctime;
      nl = n_empty;
   }

   mx_COLOUR(
      if(mx_COLOUR_IS_ACTIVE())
         mx_colour_put(mx_COLOUR_ID_VIEW_FROM_, NULL);
   )
   i = fprintf(fp, "From %s %s%s", froma, date, nl);
   mx_COLOUR(
      if(mx_COLOUR_IS_ACTIVE())
         mx_colour_reset();
   )
   if (i > 0 && stats != NULL)
      *stats += i;
   NYD_OU;
}

FL int
sendmp(struct message *mp, FILE *obuf, struct n_ignore const *doitp,
   char const *prefix, enum sendaction action, u64 *stats)
{
   struct n_sigman linedat_protect;
   struct quoteflt qf;
   FILE *ibuf;
   enum mime_parse_flags mpf;
   struct mimepart *ip;
   uz linesize, cnt, size, i;
   char *linedat;
   int rv, c;
   NYD_IN;

   time_current_update(&time_current, TRU1);
   rv = -1;
   linedat = NULL;
   linesize = 0;
   quoteflt_init(&qf, prefix, (prefix == NULL));

   n_SIGMAN_ENTER_SWITCH(&linedat_protect, n_SIGMAN_ALL){
   case 0:
      break;
   default:
      goto jleave;
   }

   if (mp == dot && action != SEND_TOSRCH)
      n_pstate |= n_PS_DID_PRINT_DOT;
   if (stats != NULL)
      *stats = 0;

   /* First line is the From_ line, so no headers there to worry about */
   if ((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL)
      goto jleave;

   cnt = mp->m_size;
   size = 0;
   {
   boole nozap;
   char const *cpre = n_empty, *csuf = n_empty;

#ifdef mx_HAVE_COLOUR
   if(mx_COLOUR_IS_ACTIVE()){
      struct mx_colour_pen *cpen;
      struct str const *s;

      cpen = mx_colour_pen_create(mx_COLOUR_ID_VIEW_FROM_,NULL);
      if((s = mx_colour_pen_to_str(cpen)) != NIL){
         cpre = s->s;
         s = mx_colour_reset_to_str();
         if(s != NIL)
            csuf = s->s;
      }
   }
#endif

   nozap = (doitp != n_IGNORE_ALL && doitp != n_IGNORE_FWD &&
         action != SEND_RFC822 &&
         !n_ignore_is_ign(doitp, "from_", sizeof("from_") -1));
   if (mp->m_flag & (MNOFROM | MBADFROM_)) {
      if (nozap)
         size = fprintf(obuf, "%s%.*sFrom %s %s%s\n",
               cpre, (int)qf.qf_pfix_len,
               (qf.qf_bypass ? n_empty : qf.qf_pfix), fakefrom(mp),
               n_time_ctime(mp->m_time, NULL), csuf);
   } else if (nozap) {
      if (!qf.qf_bypass) {
         i = fwrite(qf.qf_pfix, sizeof *qf.qf_pfix, qf.qf_pfix_len, obuf);
         if (i != qf.qf_pfix_len)
            goto jleave;
         size += i;
      }
#ifdef mx_HAVE_COLOUR
      if(*cpre != '\0'){
         fputs(cpre, obuf);
         cpre = (char const*)0x1;
      }
#endif

      while (cnt > 0 && (c = getc(ibuf)) != EOF) {
#ifdef mx_HAVE_COLOUR
         if(c == '\n' && *csuf != '\0'){
            cpre = (char const*)0x1;
            fputs(csuf, obuf);
         }
#endif
         putc(c, obuf);
         ++size;
         --cnt;
         if (c == '\n')
            break;
      }

#ifdef mx_HAVE_COLOUR
      if(*csuf != '\0' && cpre != (char const*)0x1 && *cpre != '\0')
         fputs(csuf, obuf);
#endif
   } else {
      while (cnt > 0 && (c = getc(ibuf)) != EOF) {
         --cnt;
         if (c == '\n')
            break;
      }
   }
   }
   if (size > 0 && stats != NULL)
      *stats += size;

   mpf = MIME_PARSE_NONE;
   if (action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
      mpf |= MIME_PARSE_PARTS | MIME_PARSE_DECRYPT;
   if(action == SEND_TODISP || action == SEND_TODISP_ALL ||
         action == SEND_QUOTE || action == SEND_QUOTE_ALL)
      mpf |= MIME_PARSE_FOR_USER_CONTEXT;
   if ((ip = mime_parse_msg(mp, mpf)) == NULL)
      goto jleave;

   rv = sendpart(mp, ip, obuf, doitp, &qf, action, &linedat, &linesize,
         stats, 0);

   n_sigman_cleanup_ping(&linedat_protect);
jleave:
   n_pstate &= ~n_PS_BASE64_STRIP_CR;
   quoteflt_destroy(&qf);
   if(linedat != NULL)
      n_free(linedat);
   NYD_OU;
   n_sigman_leave(&linedat_protect, n_SIGMAN_VIPSIGS_NTTYOUT);
   return rv;
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/mx/sendout.c000066400000000000000000002513451352610246600161350ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ Message sending lifecycle, header composing, etc.
 *
 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: BSD-3-Clause
 */
/*
 * Copyright (c) 1980, 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.
 */
#undef su_FILE
#define su_FILE sendout
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

#include 
#include 

#include "mx/child.h"
#include "mx/cmd-mlist.h"
#include "mx/cred-auth.h"
#include "mx/file-locks.h"
#include "mx/file-streams.h"
#include "mx/iconv.h"
#include "mx/names.h"
#include "mx/net-smtp.h"
#include "mx/random.h"
#include "mx/sigs.h"
#include "mx/tty.h"
#include "mx/url.h"

/* TODO fake */
#include "su/code-in.h"

#undef SEND_LINESIZE
#define SEND_LINESIZE \
   ((1024 / B64_ENCODE_INPUT_PER_LINE) * B64_ENCODE_INPUT_PER_LINE)

enum a_sendout_addrline_flags{
   a_SENDOUT_AL_INC_INVADDR = 1<<0, /* _Do_ include invalid addresses */
   a_SENDOUT_AL_DOMIME = 1<<1,      /* Perform MIME conversion */
   a_SENDOUT_AL_COMMA = GCOMMA,
   a_SENDOUT_AL_FILES = GFILES,
   _a_SENDOUT_AL_GMASK = a_SENDOUT_AL_COMMA | a_SENDOUT_AL_FILES
};
CTA(!(_a_SENDOUT_AL_GMASK & (a_SENDOUT_AL_INC_INVADDR|a_SENDOUT_AL_DOMIME)),
   "Code-required condition not satisfied but actual bit carrier value");

enum a_sendout_sendwait_flags{
   a_SENDOUT_SWF_NONE,
   a_SENDOUT_SWF_MTA = 1u<<0,
   a_SENDOUT_SWF_PCC = 1u<<1,
   a_SENDOUT_SWF_MASK = a_SENDOUT_SWF_MTA | a_SENDOUT_SWF_PCC
};

static char const *__sendout_ident; /* TODO temporary; rewrite n_puthead() */
static char *  _sendout_boundary;
static s8   _sendout_error;

/* */
static u32 a_sendout_sendwait_to_swf(void);

/* *fullnames* appears after command line arguments have been parsed */
static struct mx_name *a_sendout_fullnames_cleanup(struct mx_name *np);

/* */
static boole a_sendout_put_name(char const *line, enum gfield w,
      enum sendaction action, char const *prefix, FILE *fo,
      struct mx_name **xp);

/* Place Content-Type:, Content-Transfer-Encoding:, Content-Disposition:
 * headers, respectively */
static int a_sendout_put_ct(FILE *fo, char const *contenttype,
               char const *charset);
su_SINLINE int a_sendout_put_cte(FILE *fo, enum conversion conv);
static int a_sendout_put_cd(FILE *fo, char const *cd, char const *filename);

/* Put all entries of the given header list */
static boole        _sendout_header_list(FILE *fo, struct n_header_field *hfp,
                        boole nodisp);

/* */
static s32 a_sendout_body(FILE *fo, FILE *fi, enum conversion convert);

/* Write an attachment to the file buffer, converting to MIME */
static s32 a_sendout_attach_file(struct header *hp, struct attachment *ap,
      FILE *fo, boole force);
static s32 a_sendout__attach_file(struct header *hp, struct attachment *ap,
      FILE *f, boole force);

/* There are non-local receivers, collect credentials etc. */
static boole        _sendbundle_setup_creds(struct sendbundle *sbpm,
                        boole signing_caps);

/* Attach a message to the file buffer */
static s32 a_sendout_attach_msg(struct header *hp, struct attachment *ap,
               FILE *fo);

/* Generate the body of a MIME multipart message */
static s32 make_multipart(struct header *hp, int convert, FILE *fi,
   FILE *fo, char const *contenttype, char const *charset, boole force);

/* Prepend a header in front of the collected stuff and return the new file */
static FILE *a_sendout_infix(struct header *hp, FILE *fi, boole dosign,
      boole force);

/* Check whether Disposition-Notification-To: is desired */
static boole        _check_dispo_notif(struct mx_name *mdn, struct header *hp,
                        FILE *fo);

/* Send mail to a bunch of user names.  The interface is through mail() */
static int a_sendout_sendmail(void *v, enum n_mailsend_flags msf);

/* Deal with file and pipe addressees */
static struct mx_name *a_sendout_file_a_pipe(struct mx_name *names, FILE *fo,
                     boole *senderror);

/* Record outgoing mail if instructed to do so; in *record* unless to is set */
static boole        mightrecord(FILE *fp, struct mx_name *to, boole resend);

static boole a_sendout__savemail(char const *name, FILE *fp, boole resend);

/*  */
static boole a_sendout_transfer(struct sendbundle *sbp, boole *senderror);

/* Actual MTA interaction */
static boole a_sendout_mta_start(struct sendbundle *sbp);
static char const **a_sendout_mta_file_args(struct mx_name *to,
      struct header *hp);
static void a_sendout_mta_file_debug(struct sendbundle *sbp, char const *mta,
      char const **args);
static boole a_sendout_mta_test(struct sendbundle *sbp, char const *mta);

/* Create a Message-ID: header field.  Use either host name or from address */
static char const *a_sendout_random_id(struct header *hp, boole msgid);

/* Format the given header line to not exceed 72 characters */
static boole a_sendout_put_addrline(char const *hname, struct mx_name *np,
               FILE *fo, enum a_sendout_addrline_flags saf);

/* Rewrite a message for resending, adding the Resent-Headers */
static int           infix_resend(FILE *fi, FILE *fo, struct message *mp,
                        struct mx_name *to, int add_resent);

static u32
a_sendout_sendwait_to_swf(void){ /* TODO should happen at var assign time */
   char *buf;
   u32 rv;
   char const *cp;
   NYD2_IN;

   if((cp = ok_vlook(sendwait)) == NIL)
      rv = a_SENDOUT_SWF_NONE;
   else if(*cp == '\0')
      rv = a_SENDOUT_SWF_MASK;
   else{
      rv = a_SENDOUT_SWF_NONE;

      for(buf = savestr(cp); (cp = su_cs_sep_c(&buf, ',', TRU1)) != NIL;){
         if(!su_cs_cmp_case(cp, "mta"))
            rv |= a_SENDOUT_SWF_MTA;
         else if(!su_cs_cmp_case(cp, "pcc"))
            rv |= a_SENDOUT_SWF_PCC;
         else if(n_poption & n_PO_D_V)
            n_err(_("Unknown *sendwait* content: %s\n"),
               n_shexp_quote_cp(cp, FAL0));
      }
   }
   NYD2_OU;
   return rv;
}

static struct mx_name *
a_sendout_fullnames_cleanup(struct mx_name *np){
   struct mx_name *xp;
   NYD2_IN;

   for(xp = np; xp != NULL; xp = xp->n_flink){
      xp->n_type &= ~(GFULL | GFULLEXTRA);
      xp->n_fullname = xp->n_name;
      xp->n_fullextra = NULL;
   }
   NYD2_OU;
   return np;
}

static boole
a_sendout_put_name(char const *line, enum gfield w, enum sendaction action,
   char const *prefix, FILE *fo, struct mx_name **xp){
   boole rv;
   struct mx_name *np;
   NYD_IN;

   np = (w & GNOT_A_LIST ? n_extract_single : lextract)(line, GEXTRA | GFULL);
   if(xp != NULL)
      *xp = np;

   if(np == NULL)
      rv = FAL0;
   else
      rv = a_sendout_put_addrline(prefix, np, fo, ((w & GCOMMA) |
            ((action != SEND_TODISP) ? a_SENDOUT_AL_DOMIME : 0)));
   NYD_OU;
   return rv;
}

static int
a_sendout_put_ct(FILE *fo, char const *contenttype, char const *charset){
   int rv;
   NYD2_IN;

   if((rv = fprintf(fo, "Content-Type: %s", contenttype)) < 0)
      goto jerr;

   if(charset == NULL)
      goto jend;

   if(putc(';', fo) == EOF)
      goto jerr;
   ++rv;

   if(su_cs_len(contenttype) + sizeof("Content-Type: ;")-1 > 50){
      if(putc('\n', fo) == EOF)
         goto jerr;
      ++rv;
   }

   /* C99 */{
      int i;

      i = fprintf(fo, " charset=%s", charset);
      if(i < 0)
         goto jerr;
      rv += i;
   }

jend:
   if(putc('\n', fo) == EOF)
      goto jerr;
   ++rv;
jleave:
   NYD2_OU;
   return rv;
jerr:
   rv = -1;
   goto jleave;
}

su_SINLINE int
a_sendout_put_cte(FILE *fo, enum conversion conv){
   int rv;
   NYD2_IN;

   /* RFC 2045, 6.1.:
    *    This is the default value -- that is,
    *    "Content-Transfer-Encoding: 7BIT" is assumed if the
    *     Content-Transfer-Encoding header field is not present.
    */
   rv = (conv == CONV_7BIT) ? 0
         : fprintf(fo, "Content-Transfer-Encoding: %s\n",
            mime_enc_from_conversion(conv));
   NYD2_OU;
   return rv;
}

static int
a_sendout_put_cd(FILE *fo, char const *cd, char const *filename){
   struct str f;
   s8 mpc;
   int rv;
   NYD2_IN;

   f.s = NULL;

   /* xxx Ugly with the trailing space in case of wrap! */
   if((rv = fprintf(fo, "Content-Disposition: %s; ", cd)) < 0)
      goto jerr;

   if(!(mpc = mime_param_create(&f, "filename", filename)))
      goto jerr;
   /* Always fold if result contains newlines */
   if(mpc < 0 || f.l + rv > MIME_LINELEN) { /* FIXME MIME_LINELEN_MAX */
      if(putc('\n', fo) == EOF || putc(' ', fo) == EOF)
         goto jerr;
      rv += 2;
   }
   if(fputs(f.s, fo) == EOF || putc('\n', fo) == EOF)
      goto jerr;
   rv += (int)++f.l;

jleave:
   NYD2_OU;
   return rv;
jerr:
   rv = -1;
   goto jleave;

}

static boole
_sendout_header_list(FILE *fo, struct n_header_field *hfp, boole nodisp){
   boole rv;
   NYD2_IN;

   for(rv = TRU1; hfp != NULL; hfp = hfp->hf_next)
      if(fwrite(hfp->hf_dat, sizeof(char), hfp->hf_nl, fo) != hfp->hf_nl ||
            putc(':', fo) == EOF || putc(' ', fo) == EOF ||
            xmime_write(hfp->hf_dat + hfp->hf_nl +1, hfp->hf_bl, fo,
               (!nodisp ? CONV_NONE : CONV_TOHDR),
               (!nodisp ? TD_ISPR | TD_ICONV : TD_ICONV), NULL, NULL) < 0 ||
            putc('\n', fo) == EOF){
         rv = FAL0;
         break;
      }
   NYD2_OU;
   return rv;
}

static s32
a_sendout_body(FILE *fo, FILE *fi, enum conversion convert){
   struct str outrest, inrest;
   boole iseof, seenempty;
   char *buf;
   uz size, bufsize, cnt;
   s32 rv;
   NYD2_IN;

   rv = su_ERR_INVAL;
   buf = n_alloc(bufsize = SEND_LINESIZE);
   outrest.s = inrest.s = NULL;
   outrest.l = inrest.l = 0;

   if(convert == CONV_TOQP || convert == CONV_8BIT || convert == CONV_7BIT
#ifdef mx_HAVE_ICONV
         || iconvd != (iconv_t)-1
#endif
   ){
      fflush(fi);
      cnt = fsize(fi);
   }

   seenempty = iseof = FAL0;
   while(!iseof){
      if(convert == CONV_TOQP || convert == CONV_8BIT || convert == CONV_7BIT
#ifdef mx_HAVE_ICONV
            || iconvd != (iconv_t)-1
#endif
      ){
         if(fgetline(&buf, &bufsize, &cnt, &size, fi, 0) == NULL)
            break;
         if(convert != CONV_TOQP && seenempty && is_head(buf, size, FAL0)){
            if(bufsize - 1 >= size + 1){
               bufsize += 32;
               buf = su_MEM_REALLOC(buf, bufsize);
            }
            su_mem_move(&buf[1], &buf[0], ++size);
            buf[0] = '>';
            seenempty = FAL0;
         }else
            seenempty = (size == 1 /*&& buf[0] == '\n'*/);
      }else if((size = fread(buf, sizeof *buf, bufsize, fi)) == 0)
         break;
joutln:
      if(xmime_write(buf, size, fo, convert, TD_ICONV, &outrest,
            (iseof > FAL0 ? NULL : &inrest)) < 0)
         goto jleave;
   }
   if(iseof <= FAL0 && (outrest.l != 0 || inrest.l != 0)){
      size = 0;
      iseof = (iseof || inrest.l == 0) ? TRU1 : TRUM1;
      goto joutln;
   }

   rv = ferror(fi) ? su_ERR_IO : su_ERR_NONE;
jleave:
   if(outrest.s != NULL)
      n_free(outrest.s);
   if(inrest.s != NULL)
      n_free(inrest.s);
   n_free(buf);

   NYD2_OU;
   return rv;
}

static s32
a_sendout_attach_file(struct header *hp, struct attachment *ap, FILE *fo,
   boole force)
{
   /* TODO of course, the MIME classification needs to performed once
    * TODO only, not for each and every charset anew ... ;-// */
   char *charset_iter_orig[2];
   boole any;
   long offs;
   s32 err;
   NYD_IN;

   err = su_ERR_NONE;

   /* Is this already in target charset?  Simply copy over */
   if(ap->a_conv == AC_TMPFILE){
      err = a_sendout__attach_file(hp, ap, fo, force);
      mx_fs_close(ap->a_tmpf);
      su_DBG( ap->a_tmpf = NIL; )
      goto jleave;
   }

   /* If we don't apply charset conversion at all (fixed input=ouput charset)
    * we also simply copy over, since it's the users desire */
   if (ap->a_conv == AC_FIX_INCS) {
      ap->a_charset = ap->a_input_charset;
      err = a_sendout__attach_file(hp, ap, fo, force);
      goto jleave;
   } else
      ASSERT(ap->a_input_charset != NULL);

   /* Otherwise we need to iterate over all possible output charsets */
   if ((offs = ftell(fo)) == -1) {
      err = su_ERR_IO;
      goto jleave;
   }
   charset_iter_recurse(charset_iter_orig);
   for(any = FAL0, charset_iter_reset(NULL);; any = TRU1, charset_iter_next()){
      boole myforce;

      myforce = FAL0;
      if (!charset_iter_is_valid()) {
         if(!any || !(myforce = force)){
            err = su_ERR_NOENT;
            break;
         }
      }
      err = a_sendout__attach_file(hp, ap, fo, myforce);
      if (err == su_ERR_NONE || (err != su_ERR_ILSEQ && err != su_ERR_INVAL))
         break;
      clearerr(fo);
      if (fseek(fo, offs, SEEK_SET) == -1) {
         err = su_ERR_IO;
         break;
      }
      if (ap->a_conv != AC_DEFAULT) {
         err = su_ERR_ILSEQ;
         break;
      }
      ap->a_charset = NULL;
   }
   charset_iter_restore(charset_iter_orig);
jleave:
   NYD_OU;
   return err;
}

static s32
a_sendout__attach_file(struct header *hp, struct attachment *ap, FILE *fo,
   boole force)
{
   FILE *fi;
   char const *charset;
   enum conversion convert;
   int do_iconv;
   s32 err;
   NYD_IN;

   err = su_ERR_NONE;

   /* Either charset-converted temporary file, or plain path */
   if (ap->a_conv == AC_TMPFILE) {
      fi = ap->a_tmpf;
      ASSERT(ftell(fi) == 0);
   }else if((fi = mx_fs_open(ap->a_path, "r")) == NIL){
      err = su_err_no();
      n_err(_("%s: %s\n"), n_shexp_quote_cp(ap->a_path, FAL0),
         su_err_doc(err));
      goto jleave;
   }

   /* MIME part header for attachment */
   {  char const *ct, *cp;

      /* No MBOXO quoting here, never!! */
      ct = ap->a_content_type;
      charset = ap->a_charset;
      convert = n_mimetype_classify_file(fi, (char const**)&ct,
         &charset, &do_iconv, TRU1);

      if (charset == NULL || ap->a_conv == AC_FIX_INCS ||
            ap->a_conv == AC_TMPFILE)
         do_iconv = 0;
      if(force && do_iconv){
         convert = CONV_TOB64;
         ap->a_content_type = ct = "application/octet-stream";
         ap->a_charset = charset = NULL;
         do_iconv = FAL0;
      }

      if (fprintf(fo, "\n--%s\n", _sendout_boundary) < 0 ||
            a_sendout_put_ct(fo, ct, charset) < 0 ||
            a_sendout_put_cte(fo, convert) < 0 ||
            a_sendout_put_cd(fo, ap->a_content_disposition, ap->a_name) < 0)
         goto jerr_header;

      if((cp = ok_vlook(stealthmua)) == NULL || !su_cs_cmp(cp, "noagent")){
         struct mx_name *np;

         /* TODO RFC 2046 specifies that the same Content-ID should be used
          * TODO for identical data; this is too hard for use right now,
          * TODO because if done right it should be checksum based!?! */
         if((np = ap->a_content_id) != NULL)
            cp = np->n_name;
         else
            cp = a_sendout_random_id(hp, FAL0);

         if(cp != NULL && fprintf(fo, "Content-ID: <%s>\n", cp) < 0)
            goto jerr_header;
      }

      if ((cp = ap->a_content_description) != NULL &&
            (fputs("Content-Description: ", fo) == EOF ||
             xmime_write(cp, su_cs_len(cp), fo, CONV_TOHDR,
               (TD_ISPR | TD_ICONV), NULL, NULL) < 0 || putc('\n', fo) == EOF))
         goto jerr_header;

      if (putc('\n', fo) == EOF) {
jerr_header:
         err = su_err_no();
         goto jerr_fclose;
      }
   }

#ifdef mx_HAVE_ICONV
   if (iconvd != (iconv_t)-1)
      n_iconv_close(iconvd);
   if (do_iconv) {
      if (su_cs_cmp_case(charset, ap->a_input_charset) &&
            (iconvd = n_iconv_open(charset, ap->a_input_charset)
               ) == (iconv_t)-1 && (err = su_err_no()) != 0) {
         if (err == su_ERR_INVAL)
            n_err(_("Cannot convert from %s to %s\n"), ap->a_input_charset,
               charset);
         else
            n_err(_("iconv_open: %s\n"), su_err_doc(err));
         goto jerr_fclose;
      }
   }
#endif

   err = a_sendout_body(fo, fi, convert);
jerr_fclose:
   if(ap->a_conv != AC_TMPFILE)
      mx_fs_close(fi);

jleave:
   NYD_OU;
   return err;
}

static boole
_sendbundle_setup_creds(struct sendbundle *sbp, boole signing_caps)
{
   boole v15, rv = FAL0;
   char *shost, *from;
   NYD_IN;

   v15 = (ok_vlook(v15_compat) != su_NIL);
   shost = (v15 ? ok_vlook(smtp_hostname) : NULL);
   from = ((signing_caps || !v15 || shost == NULL)
         ? skin(myorigin(sbp->sb_hp)) : NULL);

   if (signing_caps) {
      if (from == NULL) {
#ifdef mx_HAVE_SMIME
         n_err(_("No *from* address for signing specified\n"));
         goto jleave;
#endif
      } else
         sbp->sb_signer.l = su_cs_len(sbp->sb_signer.s = from);
   }

#ifndef mx_HAVE_SMTP
   rv = TRU1;
#else
   if(sbp->sb_urlp == NIL){
      rv = TRU1;
      goto jleave;
   }

   if (v15) {
      if (shost == NULL) {
         if (from == NULL)
            goto jenofrom;
         sbp->sb_urlp->url_u_h.l = su_cs_len(sbp->sb_urlp->url_u_h.s = from);
      } else
         __sendout_ident = sbp->sb_urlp->url_u_h.s;
      if(!mx_cred_auth_lookup(sbp->sb_credp, sbp->sb_urlp))
         goto jleave;
   }else{
      if((sbp->sb_urlp->url_flags & mx_URL_HAD_USER) ||
            sbp->sb_urlp->url_pass.s != NULL){
         n_err(_("New-style URL used without *v15-compat* being set\n"));
         goto jleave;
      }
      /* TODO part of the entire myorigin() disaster, get rid of this! */
      if (from == NULL) {
jenofrom:
         n_err(_("Your configuration requires a *from* address, "
            "but none was given\n"));
         goto jleave;
      }
      if(!mx_cred_auth_lookup_old(sbp->sb_credp, CPROTO_SMTP, from))
         goto jleave;
      sbp->sb_urlp->url_u_h.l = su_cs_len(sbp->sb_urlp->url_u_h.s = from);
   }

   rv = TRU1;
#endif /* mx_HAVE_SMTP */
#if defined mx_HAVE_SMIME || defined mx_HAVE_SMTP
jleave:
#endif
   NYD_OU;
   return rv;
}

static s32
a_sendout_attach_msg(struct header *hp, struct attachment *ap, FILE *fo)
{
   struct message *mp;
   char const *ccp;
   s32 err;
   NYD_IN;
   UNUSED(hp);

   err = su_ERR_NONE;

   if(fprintf(fo, "\n--%s\nContent-Type: message/rfc822\n"
         "Content-Disposition: inline\n", _sendout_boundary) < 0)
      goto jerr;

   if((ccp = ok_vlook(stealthmua)) == NULL || !su_cs_cmp(ccp, "noagent")){
      struct mx_name *np;

      /* TODO RFC 2046 specifies that the same Content-ID should be used
       * TODO for identical data; this is too hard for use right now,
       * TODO because if done right it should be checksum based!?! */
      if((np = ap->a_content_id) != NULL)
         ccp = np->n_name;
      else
         ccp = a_sendout_random_id(hp, FAL0);

      if(ccp != NULL && fprintf(fo, "Content-ID: <%s>\n", ccp) < 0)
         goto jerr;
   }

   if((ccp = ap->a_content_description) != NULL &&
         (fputs("Content-Description: ", fo) == EOF ||
          xmime_write(ccp, su_cs_len(ccp), fo, CONV_TOHDR,
            (TD_ISPR | TD_ICONV), NULL, NULL) < 0 || putc('\n', fo) == EOF))
      goto jerr;
   if(putc('\n', fo) == EOF)
      goto jerr;

   mp = message + ap->a_msgno - 1;
   touch(mp);
   if(sendmp(mp, fo, 0, NULL, SEND_RFC822, NULL) < 0)
jerr:
      if((err = su_err_no()) == su_ERR_NONE)
         err = su_ERR_IO;
   NYD_OU;
   return err;
}

static s32
make_multipart(struct header *hp, int convert, FILE *fi, FILE *fo,
   char const *contenttype, char const *charset, boole force)
{
   struct attachment *att;
   s32 err;
   NYD_IN;

   err = su_ERR_NONE;

   if(fputs("This is a multi-part message in MIME format.\n", fo) == EOF)
      goto jerr;

   if(fsize(fi) != 0){
      char const *cp;

      if(fprintf(fo, "\n--%s\n", _sendout_boundary) < 0 ||
            a_sendout_put_ct(fo, contenttype, charset) < 0 ||
            a_sendout_put_cte(fo, convert) < 0 ||
            fprintf(fo, "Content-Disposition: inline\n") < 0)
         goto jerr;
      if (((cp = ok_vlook(stealthmua)) == NULL || !su_cs_cmp(cp, "noagent")) &&
            (cp = a_sendout_random_id(hp, FAL0)) != NULL &&
            fprintf(fo, "Content-ID: <%s>\n", cp) < 0)
         goto jerr;
      if(putc('\n', fo) == EOF)
         goto jerr;

      if((err = a_sendout_body(fo, fi, convert)) != su_ERR_NONE)
         goto jleave;

      if(ferror(fi))
         goto jerr;
   }

   for (att = hp->h_attach; att != NULL; att = att->a_flink) {
      if (att->a_msgno) {
         if ((err = a_sendout_attach_msg(hp, att, fo)) != su_ERR_NONE)
            goto jleave;
      }else if((err = a_sendout_attach_file(hp, att, fo, force)
            ) != su_ERR_NONE)
         goto jleave;
   }

   /* the final boundary with two attached dashes */
   if(fprintf(fo, "\n--%s--\n", _sendout_boundary) < 0)
jerr:
      if((err = su_err_no()) == su_ERR_NONE)
         err = su_ERR_IO;
jleave:
   NYD_OU;
   return err;
}

static FILE *
a_sendout_infix(struct header *hp, FILE *fi, boole dosign, boole force)
{
   struct mx_fs_tmp_ctx *fstcp;
   enum conversion convert;
   int do_iconv, err;
   char const *contenttype, *charset;
   FILE *nfo, *nfi;
#ifdef mx_HAVE_ICONV
   char const *tcs, *convhdr = NULL;
#endif
   NYD_IN;

   nfi = NULL;
   charset = NULL;
   do_iconv = 0;
   err = su_ERR_NONE;

   if((nfo = mx_fs_tmp_open("infix", (mx_FS_O_WRONLY | mx_FS_O_HOLDSIGS |
            mx_FS_O_REGISTER), &fstcp)) == NIL){
      n_perr(_("infix: temporary mail file"), err = su_err_no());
      goto jleave;
   }

   if((nfi = mx_fs_open(fstcp->fstc_filename, "r")) == NIL){
      n_perr(fstcp->fstc_filename, err = su_err_no());
      mx_fs_close(nfo);
   }

   mx_fs_tmp_release(fstcp);

   if(nfi == NIL)
      goto jleave;

   n_pstate &= ~n_PS_HEADER_NEEDED_MIME; /* TODO hack -> be carrier tracked */

   /* C99 */{
      boole no_mboxo;

      no_mboxo = dosign;

      /* Will be NULL for text/plain */
      if((n_poption & n_PO_Mm_FLAG) && n_poption_arg_Mm != NULL){
         contenttype = n_poption_arg_Mm;
         no_mboxo = TRU1;
      }else
         contenttype = "text/plain";

      convert = n_mimetype_classify_file(fi, &contenttype, &charset, &do_iconv,
            no_mboxo);
   }

#ifdef mx_HAVE_ICONV
   /* This is the logic behind *charset-force-transport*.  XXX very weird
    * XXX as said a thousand times, Part==object, has dump_to_{wire,user}, and
    * XXX does it (including _force_) for _itself_ only; the global header
    * XXX has then to become spliced in (for multipart messages) */
   if(force && do_iconv){
      convert = CONV_TOB64;
      contenttype = "application/octet-stream";
      charset = NULL;
      do_iconv = FAL0;
   }

   tcs = ok_vlook(ttycharset);

   if ((convhdr = need_hdrconv(hp))) {
      if (iconvd != (iconv_t)-1) /* XXX  */
         n_iconv_close(iconvd);
      if (su_cs_cmp_case(convhdr, tcs) != 0 &&
            (iconvd = n_iconv_open(convhdr, tcs)) == (iconv_t)-1 &&
            (err = su_err_no()) != su_ERR_NONE)
         goto jiconv_err;
   }
#endif
   if(!n_puthead(FAL0, hp, nfo,
         (GTO | GSUBJECT | GCC | GBCC | GNL | GCOMMA | GUA | GMIME | GMSGID |
         GIDENT | GREF | GDATE), SEND_MBOX, convert, contenttype, charset)){
      if((err = su_err_no()) == su_ERR_NONE)
         err = su_ERR_IO;
      goto jerr;
   }
#ifdef mx_HAVE_ICONV
   if (iconvd != (iconv_t)-1)
      n_iconv_close(iconvd);
#endif

#ifdef mx_HAVE_ICONV
   if (do_iconv && charset != NULL) { /*TODO charset->n_mimetype_classify_file*/
      if (su_cs_cmp_case(charset, tcs) != 0 &&
            (iconvd = n_iconv_open(charset, tcs)) == (iconv_t)-1 &&
            (err = su_err_no()) != su_ERR_NONE) {
jiconv_err:
         if (err == su_ERR_INVAL)
            n_err(_("Cannot convert from %s to %s\n"), tcs, charset);
         else
            n_perr("iconv_open", err);
         goto jerr;
      }
   }
#endif

   if(hp->h_attach != NULL){
      if((err = make_multipart(hp, convert, fi, nfo, contenttype, charset,
            force)) != su_ERR_NONE)
         goto jerr;
   }else if((err = a_sendout_body(nfo, fi, convert)) != su_ERR_NONE)
      goto jerr;

   if(fflush(nfo) == EOF)
      err = su_err_no();
jerr:
   mx_fs_close(nfo);

   if(err == su_ERR_NONE){
      fflush_rewind(nfi);
      mx_fs_close(fi);
   }else{
      mx_fs_close(nfi);
      nfi = NIL;
   }

jleave:
#ifdef mx_HAVE_ICONV
   if(iconvd != (iconv_t)-1)
      n_iconv_close(iconvd);
#endif
   if(nfi == NULL)
      su_err_set_no(err);
   NYD_OU;
   return nfi;
}

static boole
_check_dispo_notif(struct mx_name *mdn, struct header *hp, FILE *fo)
{
   char const *from;
   boole rv = TRU1;
   NYD_IN;

   /* TODO smtp_disposition_notification (RFC 3798): relation to return-path
    * TODO not yet checked */
   if (!ok_blook(disposition_notification_send))
      goto jleave;

   if (mdn != NULL && mdn != (struct mx_name*)0x1)
      from = mdn->n_name;
   else if ((from = myorigin(hp)) == NULL) {
      if (n_poption & n_PO_D_V)
         n_err(_("*disposition-notification-send*: *from* not set\n"));
      goto jleave;
   }

   if (!a_sendout_put_addrline("Disposition-Notification-To:",
         nalloc(n_UNCONST(from), 0), fo, 0))
      rv = FAL0;
jleave:
   NYD_OU;
   return rv;
}

static int
a_sendout_sendmail(void *v, enum n_mailsend_flags msf)
{
   struct header head;
   char *str = v;
   int rv;
   NYD_IN;

   su_mem_set(&head, 0, sizeof head);
   head.h_mailx_command = "mail";
   if((head.h_to = lextract(str, GTO |
         (ok_blook(fullnames) ? GFULL | GSKIN : GSKIN))) != NULL)
      head.h_mailx_raw_to = n_namelist_dup(head.h_to, head.h_to->n_type);
   rv = n_mail1(msf, &head, NULL, NULL);
   NYD_OU;
   return (rv != OKAY); /* reverse! */
}

static struct mx_name *
a_sendout_file_a_pipe(struct mx_name *names, FILE *fo, boole *senderror){
   boole mfap;
   char const *sh;
   u32 pipecnt, xcnt, i, swf;
   struct mx_name *np;
   FILE *fp, **fppa;
   NYD_IN;

   fp = NIL;
   fppa = NIL;

   /* Look through all recipients and do a quick return if no file or pipe
    * addressee is found */
   for(pipecnt = xcnt = 0, np = names; np != NIL; np = np->n_flink){
      if(np->n_type & GDEL)
         continue;
      switch(np->n_flags & mx_NAME_ADDRSPEC_ISFILEORPIPE){
      case mx_NAME_ADDRSPEC_ISFILE: ++xcnt; break;
      case mx_NAME_ADDRSPEC_ISPIPE: ++pipecnt; break;
      }
   }
   if((pipecnt | xcnt) == 0)
      goto jleave;

   /* Otherwise create an array of file descriptors for each found pipe
    * addressee to get around the dup(2)-shared-file-offset problem, i.e.,
    * each pipe subprocess needs its very own file descriptor, and we need
    * to deal with that.
    * This is true even if *sendwait* requires fully synchronous mode, since
    * the shell handlers can fork away and pass the descriptor around, so we
    * cannot simply use a single one and rewind that after the top children
    * shell has returned.
    * To make our life a bit easier let's just use the auto-reclaimed
    * string storage */
   if(pipecnt == 0 || (n_poption & n_PO_D)){
      pipecnt = 0;
      sh = NIL;
   }else{
      i = sizeof(FILE*) * pipecnt;
      fppa = n_lofi_alloc(i);
      su_mem_set(fppa, 0, i);

      sh = ok_vlook(SHELL);
   }

   mfap = ok_blook(mbox_fcc_and_pcc);
   swf = a_sendout_sendwait_to_swf();

   for(np = names; np != NIL; np = np->n_flink){
      if(!(np->n_flags & mx_NAME_ADDRSPEC_ISFILEORPIPE) || (np->n_type & GDEL))
         continue;

      /* In days of old we removed the entry from the the list; now for sake of
       * header expansion we leave it in and mark it as deleted */
      np->n_type |= GDEL;

      if(n_poption & n_PO_D_VV)
         n_err(_(">>> Writing message via %s\n"),
            n_shexp_quote_cp(np->n_name, FAL0));
      /* We _do_ write to STDOUT, anyway! */
      if((n_poption & n_PO_D) &&
            ((np->n_flags & mx_NAME_ADDRSPEC_ISPIPE) ||
               np->n_name[0] != '-' || np->n_name[1] != '\0'))
         continue;

      /* See if we have copied the complete message out yet.  If not, do so */
      if(fp == NIL){
         int c;
         struct mx_fs_tmp_ctx *fstcp;

         if((fp = mx_fs_tmp_open("outof", (mx_FS_O_RDWR | mx_FS_O_HOLDSIGS |
                  mx_FS_O_REGISTER), &fstcp)) == NIL){
            n_perr(_("Creation of temporary image"), 0);
            pipecnt = 0;
            goto jerror;
         }

         for(i = 0; i < pipecnt; ++i)
            if((fppa[i] = mx_fs_open(fstcp->fstc_filename, "r")) == NIL){
               n_perr(_("Creation of pipe image descriptor"), 0);
               break;
            }

         mx_fs_tmp_release(fstcp);

         if(i != pipecnt){
            pipecnt = i;
            goto jerror;
         }

         if(mfap)
            fprintf(fp, "From %s %s",
               ok_vlook(LOGNAME), time_current.tc_ctime);
         c = EOF;
         while(i = c, (c = getc(fo)) != EOF)
            putc(c, fp);
         rewind(fo);
         if((int)i != '\n')
            putc('\n', fp);
         if(mfap)
            putc('\n', fp);
         fflush(fp);
         if(ferror(fp)){
            n_perr(_("Finalizing write of temporary image"), 0);
            goto jerror;
         }

         /* From now on use xcnt as a counter for pipecnt */
         xcnt = 0;
      }

      /* Now either copy "image" to the desired file or give it as the
       * standard input to the desired program as appropriate */
      if(np->n_flags & mx_NAME_ADDRSPEC_ISPIPE){
         struct mx_child_ctx cc;
         sigset_t nset;

         sigemptyset(&nset);
         sigaddset(&nset, SIGHUP);
         sigaddset(&nset, SIGINT);
         sigaddset(&nset, SIGQUIT);

         mx_child_ctx_setup(&cc);
         cc.cc_flags = mx_CHILD_SPAWN_CONTROL |
               (swf & a_SENDOUT_SWF_PCC ? mx_CHILD_RUN_WAIT_LIFE : 0);
         cc.cc_mask = &nset;
         cc.cc_fds[mx_CHILD_FD_IN] = fileno(fppa[xcnt]);
         cc.cc_fds[mx_CHILD_FD_OUT] = mx_CHILD_FD_NULL;
         cc.cc_cmd = sh;
         cc.cc_args[0] = "-c";
         cc.cc_args[1] = &np->n_name[1];

         if(!mx_child_run(&cc)){
            n_err(_("Piping message to %s failed\n"),
               n_shexp_quote_cp(np->n_name, FAL0));
            goto jerror;
         }

         /* C99 */{
            FILE *tmp;

            tmp = fppa[xcnt];
            fppa[xcnt++] = NIL;
            mx_fs_close(tmp);
         }

         if(!(swf & a_SENDOUT_SWF_PCC))
            mx_child_forget(&cc);
      }else{
         int c;
         FILE *fout;
         char const *fname, *fnameq;

         if((fname = fexpand(np->n_name, FEXP_NSHELL)) == NIL)
            goto jerror;
         fnameq = n_shexp_quote_cp(fname, FAL0);

         if(fname[0] == '-' && fname[1] == '\0')
            fout = n_stdout;
         else{
            int xerr;
            enum mx_fs_open_state fs;

            if((fout = mx_fs_open_any(fname, (mfap ? "a+" : "w"), &fs)
                  ) == NIL){
               xerr = su_err_no();
jefile:
               n_err(_("Writing message to %s failed: %s\n"),
                  fnameq, su_err_doc(xerr));
               goto jerror;
            }

            if((fs & (n_PROTO_MASK | mx_FS_OPEN_STATE_EXISTS)) ==
                  (n_PROTO_FILE | mx_FS_OPEN_STATE_EXISTS)){
               mx_file_lock(fileno(fout), mx_FILE_LOCK_TYPE_WRITE, 0,0,
                     UZ_MAX);

               if(mfap && (xerr = n_folder_mbox_prepare_append(fout, NIL)
                     ) != su_ERR_NONE)
                  goto jefile;
            }
         }

         rewind(fp);
         while((c = getc(fp)) != EOF)
            putc(c, fout);
         if(ferror(fout)){
            n_err(_("Writing message to %s failed: %s\n"),
               fnameq, _("write error"));
            *senderror = TRU1;
         }

         if(fout != n_stdout)
            mx_fs_close(fout);
      }
   }

jleave:
   if(fp != NIL)
      mx_fs_close(fp);
   if(fppa != NIL){
      for(i = 0; i < pipecnt; ++i)
         if((fp = fppa[i]) != NIL)
            mx_fs_close(fp);
      n_lofi_free(fppa);
   }
   NYD_OU;
   return names;

jerror:
   *senderror = TRU1;
   while(np != NIL){
      if(np->n_flags & mx_NAME_ADDRSPEC_ISFILEORPIPE)
         np->n_type |= GDEL;
      np = np->n_flink;
   }
   goto jleave;
}

static boole
mightrecord(FILE *fp, struct mx_name *to, boole resend){
   char *cp;
   char const *ccp;
   boole rv;
   NYD2_IN;

   rv = TRU1;

   if(n_poption & n_PO_D)
      ccp = NULL;
   else if(to != NULL){
      ccp = cp = savestr(to->n_name);
      while(*cp != '\0' && *cp != '@')
         ++cp;
      *cp = '\0';
   }else
      ccp = ok_vlook(record);

   if(ccp != NULL){
      if((cp = fexpand(ccp, FEXP_NSHELL)) == NULL)
         goto jbail;

      switch(*(ccp = cp)){
      case '.':
         if(cp[1] != '/'){ /* DIRSEP */
      default:
            if(ok_blook(outfolder)){
               struct str s;
               char const *nccp, *folder;

               switch(which_protocol(ccp, TRU1, FAL0, &nccp)){
               case PROTO_FILE:
                  ccp = "file://";
                  if(0){
                  /* FALLTHRU */
               case PROTO_MAILDIR:
#ifdef mx_HAVE_MAILDIR
                     ccp = "maildir://";
#else
                     n_err(_("*record*: *outfolder*: no Maildir directory "
                        "support compiled in\n"));
                     goto jbail;
#endif
                  }
                  folder = n_folder_query();
#ifdef mx_HAVE_IMAP
                  if(which_protocol(folder, FAL0, FAL0, NULL) == PROTO_IMAP){
                     n_err(_("*record*: *outfolder* set, *folder* is IMAP "
                        "based: only one protocol per file is possible\n"));
                     goto jbail;
                  }
#endif
                  ccp = str_concat_csvl(&s, ccp, folder, nccp, NULL)->s;
                  /* FALLTHRU */
               default:
                  break;
               }
            }
         }
         /* FALLTHRU */
      case '/':
         break;
      }

      if(!a_sendout__savemail(ccp, fp, resend)){
jbail:
         n_err(_("Failed to save message in %s - message not sent\n"),
            n_shexp_quote_cp(ccp, FAL0));
         n_exit_status |= n_EXIT_ERR;
         savedeadletter(fp, 1);
         rv = FAL0;
      }
   }
   NYD2_OU;
   return rv;
}

static boole
a_sendout__savemail(char const *name, FILE *fp, boole resend){
   FILE *fo;
   uz bufsize, buflen, cnt;
   enum mx_fs_open_state fs;
   boole rv, emptyline;
   char *buf;
   NYD_IN;
   UNUSED(resend);

   buf = n_alloc(bufsize = LINESIZE);
   rv = FAL0;

   if((fo = mx_fs_open_any(name, "a+", &fs)) == NIL){
      n_perr(name, 0);
      goto j_leave;
   }

   if((fs & (n_PROTO_MASK | mx_FS_OPEN_STATE_EXISTS)) ==
         (n_PROTO_FILE | mx_FS_OPEN_STATE_EXISTS)){
      int xerr;

      /* TODO RETURN check, but be aware of protocols: v15: Mailbox->lock()!
       * TODO BETTER yet: should be returned in lock state already! */
      mx_file_lock(fileno(fo), mx_FILE_LOCK_TYPE_WRITE, 0,0, UZ_MAX);

      if((xerr = n_folder_mbox_prepare_append(fo, NULL)) != su_ERR_NONE){
         n_perr(name, xerr);
         goto jleave;
      }
   }

   fflush_rewind(fp);
   rv = TRU1;

   fprintf(fo, "From %s %s", ok_vlook(LOGNAME), time_current.tc_ctime);
   for(emptyline = FAL0, buflen = 0, cnt = fsize(fp);
         fgetline(&buf, &bufsize, &cnt, &buflen, fp, 0) != NULL;){
      /* Only if we are resending it can happen that we have to quote From_
       * lines here; we don't generate messages which are ambiguous ourselves.
       * xxx No longer true after (Reintroduce ^From_ MBOXO with
       * xxx *mime-encoding*=8b (too many!)[..]) */
      /*if(resend){*/
         if(emptyline && is_head(buf, buflen, FAL0))
            putc('>', fo);
      /*}su_DBG(else ASSERT(!is_head(buf, buflen, FAL0)); )*/

      emptyline = (buflen > 0 && *buf == '\n');
      fwrite(buf, sizeof *buf, buflen, fo);
   }
   if(buflen > 0 && buf[buflen - 1] != '\n')
      putc('\n', fo);
   putc('\n', fo);
   fflush(fo);
   if(ferror(fo)){
      n_perr(name, 0);
      rv = FAL0;
   }

jleave:
   really_rewind(fp);
   rv = mx_fs_close(fo);
j_leave:
   n_free(buf);
   NYD_OU;
   return rv;
}

static boole
a_sendout_transfer(struct sendbundle *sbp, boole *senderror)
{
   u32 cnt;
   struct mx_name *np;
   boole rv;
   NYD_IN;
   UNUSED(senderror);

   rv = TRU1;

   for (cnt = 0, np = sbp->sb_to; np != NULL;) {
      char const k[] = "smime-encrypt-", *cp;
      uz nl = strlen(np->n_name);
      char *vs = n_lofi_alloc(sizeof(k)-1 + nl +1);
      memcpy(vs, k, sizeof(k) -1);
      memcpy(vs + sizeof(k) -1, np->n_name, nl +1);

      if ((cp = n_var_vlook(vs, FAL0)) != NULL) {
#ifdef mx_HAVE_SMIME
         FILE *ef;

         if ((ef = smime_encrypt(sbp->sb_input, cp, np->n_name)) != NULL) {
            FILE *fisave = sbp->sb_input;
            struct mx_name *nsave = sbp->sb_to;

            sbp->sb_to = ndup(np, np->n_type & ~(GFULL | GSKIN));
            sbp->sb_input = ef;
            if(!a_sendout_mta_start(sbp))
               rv = FAL0;
            sbp->sb_to = nsave;
            sbp->sb_input = fisave;

            mx_fs_close(ef);
         } else {
#else
            n_err(_("No S/MIME support compiled in\n"));
            rv = FAL0;
#endif
            n_err(_("Message not sent to: %s\n"), np->n_name);
            _sendout_error = TRU1;
#ifdef mx_HAVE_SMIME
         }
#endif
         rewind(sbp->sb_input);

         if (np->n_flink != NULL)
            np->n_flink->n_blink = np->n_blink;
         if (np->n_blink != NULL)
            np->n_blink->n_flink = np->n_flink;
         if (np == sbp->sb_to)
            sbp->sb_to = np->n_flink;
         np = np->n_flink;
      } else {
         ++cnt;
         np = np->n_flink;
      }
      n_lofi_free(vs);
   }

   if (cnt > 0 && (ok_blook(smime_force_encryption) ||
         !a_sendout_mta_start(sbp)))
      rv = FAL0;
   NYD_OU;
   return rv;
}

static boole
a_sendout_mta_start(struct sendbundle *sbp)
{
   struct mx_child_ctx cc;
   sigset_t nset;
   char const **args, *mta;
   boole rv, dowait;
   NYD_IN;

   /* Let rv mean "is smtp-based MTA" */
   if((mta = ok_vlook(smtp)) != NULL){
      n_OBSOLETE(_("please don't use *smtp*: assign a smtp:// URL to *mta*!"));
      /* For *smtp* the smtp:// protocol was optional; be simple: don't check
       * that *smtp* is misused with file:// or so */
      if(mx_url_servbyname(mta, NIL, NIL) == NIL)
         mta = savecat("smtp://", mta);
      rv = TRU1;
   }else{
      boole issnd;
      u16 pno;
      char const *proto;

      mta = ok_vlook(mta); /* TODO v15: what solely remains in here */
      if((proto = ok_vlook(sendmail)) != NULL)
         n_OBSOLETE(_("please use *mta* instead of *sendmail*"));
      if(proto != NULL && !su_cs_cmp(mta, VAL_MTA))
         mta = proto;

      /* TODO for now this is pretty hacky: in v15 we should simply create
       * TODO an URL object; i.e., be able to do so, and it does it right
       * TODO I.e.,: url_creat(&url, ok_vlook(mta)); */
      if((proto = mx_url_servbyname(mta, &pno, &issnd)) != NIL){
         if(*proto == '\0'){
            if(pno == 0){
               mta += sizeof("file://") -1;
               rv = FAL0;
            }else{
               /* test -> stdout, test://X -> X */
               mta += sizeof("test") -1;
               if(mta[0] == ':' && mta[1] == '/' && mta[2] == '/')
                  mta += 3;
               rv = TRUM1;
            }
         }else if(!issnd){
            n_err(_("*mta* does not denote a message sending protocol: %s\n"),
               n_shexp_quote_cp(mta, FAL0));
            rv = FAL0;
            goto jstop;
         }else
            rv = TRU1;
      }else
         rv = FAL0;
   }

   sigemptyset(&nset);
   sigaddset(&nset, SIGHUP);
   sigaddset(&nset, SIGINT);
   sigaddset(&nset, SIGQUIT);
   sigaddset(&nset, SIGTSTP);
   sigaddset(&nset, SIGTTIN);
   sigaddset(&nset, SIGTTOU);
   mx_child_ctx_setup(&cc);
   dowait = ((n_poption & n_PO_D_V) ||
         (a_sendout_sendwait_to_swf() & a_SENDOUT_SWF_MTA));
   if(rv != TRU1 || dowait)
      cc.cc_flags |= mx_CHILD_SPAWN_CONTROL;
   cc.cc_mask = &nset;

   if(rv != TRU1){
      char const *mta_base;

      if((mta = fexpand(mta_base = mta, FEXP_LOCAL | FEXP_NOPROTO)) == NULL){
         n_err(_("*mta* variable file expansion failure: %s\n"),
            n_shexp_quote_cp(mta_base, FAL0));
         goto jstop;
      }

      if(rv == TRUM1){
         rv = a_sendout_mta_test(sbp, mta);
         goto jleave;
      }

      args = a_sendout_mta_file_args(sbp->sb_to, sbp->sb_hp);
      if(n_poption & n_PO_D){
         a_sendout_mta_file_debug(sbp, mta, args);
         rv = TRU1;
         goto jleave;
      }

      /* Wait with control pipe close until after exec */
      ASSERT(cc.cc_flags & mx_CHILD_SPAWN_CONTROL);
      cc.cc_flags |= mx_CHILD_SPAWN_CONTROL_LINGER;
      cc.cc_fds[mx_CHILD_FD_IN] = fileno(sbp->sb_input);
   }else/* if(rv == TRU1)*/{
      UNINIT(args, NULL);
#ifndef mx_HAVE_SMTP
      n_err(_("No SMTP support compiled in\n"));
      goto jstop;
#else
      if(n_poption & n_PO_D){
         (void)mx_smtp_mta(sbp);
         rv = TRU1;
         goto jleave;
      }

      cc.cc_fds[mx_CHILD_FD_IN] = mx_CHILD_FD_NULL;
      cc.cc_fds[mx_CHILD_FD_OUT] = mx_CHILD_FD_NULL;
#endif
   }

   /* Fork, set up the temporary mail file as standard input for "mail", and
    * exec with the user list we generated far above */
   if(!mx_child_fork(&cc)){
      if(cc.cc_flags & mx_CHILD_SPAWN_CONTROL_LINGER){
         char const *ecp;

         ecp = (cc.cc_error != su_ERR_NOENT) ? su_err_doc(cc.cc_error)
               : _("executable not found (adjust *mta* variable)");
         n_err(_("Cannot start %s: %s\n"), n_shexp_quote_cp(mta, FAL0), ecp);
      }
jstop:
      savedeadletter(sbp->sb_input, TRU1);
      n_err(_("... message not sent\n"));
      _sendout_error = TRU1;
   }else if(cc.cc_pid == 0)
      goto jkid;
   else if(dowait){
      /* TODO Now with SPAWN_CONTROL we could actually (1) handle $DEAD only
       * TODO in the parent, and (2) report the REAL child error status!! */
      rv = (mx_child_wait(&cc) && cc.cc_exit_status == 0);
      if(!rv)
         goto jstop;
   }else{
      mx_child_forget(&cc);
      rv = TRU1;
   }

jleave:
   NYD_OU;
   return rv;

jkid:
   mx_child_in_child_setup(&cc);

#ifdef mx_HAVE_SMTP
   if(rv == TRU1){
      if(mx_smtp_mta(sbp))
         _exit(n_EXIT_OK);
      savedeadletter(sbp->sb_input, TRU1);
      if(!dowait)
         n_err(_("... message not sent\n"));
   }else
#endif
        {
      execv(mta, n_UNCONST(args));
      mx_child_in_child_exec_failed(&cc, su_err_no());
   }
   for(;;)
      _exit(n_EXIT_ERR);
}

static char const **
a_sendout_mta_file_args(struct mx_name *to, struct header *hp)
{
   uz vas_cnt, i, j;
   char **vas;
   char const **args, *cp, *cp_v15compat;
   boole snda;
   NYD_IN;

   if((cp_v15compat = ok_vlook(sendmail_arguments)) != NULL)
      n_OBSOLETE(_("please use *mta-arguments*, not *sendmail-arguments*"));
   if((cp = ok_vlook(mta_arguments)) == NULL)
      cp = cp_v15compat;
   if ((cp /* TODO v15: = ok_vlook(mta_arguments)*/) == NULL) {
      vas_cnt = 0;
      vas = NULL;
   } else {
      /* Don't assume anything on the content but do allocate exactly j slots;
       * like this getrawlist will never overflow (and return -1) */
      j = su_cs_len(cp);
      vas = n_lofi_alloc(sizeof(*vas) * j);
      vas_cnt = (uz)getrawlist(TRU1, vas, j, cp, j);
   }

   i = 4 + n_smopts_cnt + vas_cnt + 4 + 1 + count(to) + 1;
   args = n_autorec_alloc(i * sizeof(char*));

   if((cp_v15compat = ok_vlook(sendmail_progname)) != NULL)
      n_OBSOLETE(_("please use *mta-argv0*, not *sendmail-progname*"));
   cp = ok_vlook(mta_argv0);
   if(cp_v15compat != NULL && !su_cs_cmp(cp, VAL_MTA_ARGV0))
      cp = cp_v15compat;
   args[0] = cp/* TODO v15 only : = ok_vlook(mta_argv0) */;

   if ((snda = ok_blook(sendmail_no_default_arguments)))
      n_OBSOLETE(_("please use *mta-no-default-arguments*, "
         "not *sendmail-no-default-arguments*"));
   snda |= ok_blook(mta_no_default_arguments);
   if ((snda /* TODO v15: = ok_blook(mta_no_default_arguments)*/))
      i = 1;
   else {
      args[1] = "-i";
      i = 2;
      if (ok_blook(metoo))
         args[i++] = "-m";
      if (n_poption & n_PO_V)
         args[i++] = "-v";
   }

   for (j = 0; j < n_smopts_cnt; ++j, ++i)
      args[i] = n_smopts[j];

   for (j = 0; j < vas_cnt; ++j, ++i)
      args[i] = vas[j];

   /* -r option?  In conjunction with -t we act compatible to postfix(1) and
    * ignore it (it is -f / -F there) if the message specified From:/Sender:.
    * The interdependency with -t has been resolved in n_puthead() */
   if (!snda && ((n_poption & n_PO_r_FLAG) || ok_blook(r_option_implicit))) {
      struct mx_name const *np;

      if (hp != NULL && (np = hp->h_from) != NULL) {
         /* However, what wasn't resolved there was the case that the message
          * specified multiple From: addresses and a Sender: */
         if((n_poption & n_PO_t_FLAG) && hp->h_sender != NULL)
            np = hp->h_sender;

         if (np->n_fullextra != NULL) {
            args[i++] = "-F";
            args[i++] = np->n_fullextra;
         }
         cp = np->n_name;
      } else {
         ASSERT(n_poption_arg_r == NULL);
         cp = skin(myorigin(NULL));
      }

      if (cp != NULL) {
         args[i++] = "-f";
         args[i++] = cp;
      }
   }

   /* Terminate option list to avoid false interpretation of system-wide
    * aliases that start with hyphen */
   if (!snda)
      args[i++] = "--";

   /* Receivers follow */
   if(!ok_blook(mta_no_receiver_arguments))
      for (; to != NULL; to = to->n_flink)
         if (!(to->n_type & GDEL))
            args[i++] = to->n_name;
   args[i] = NULL;

   if(vas != NULL)
      n_lofi_free(vas);
   NYD_OU;
   return args;
}

static void
a_sendout_mta_file_debug(struct sendbundle *sbp, char const *mta,
   char const **args)
{
   uz cnt, bufsize, llen;
   char *buf;
   NYD_IN;

   n_err(_(">>> MTA: %s, arguments:"), n_shexp_quote_cp(mta, FAL0));
   for (; *args != NULL; ++args)
      n_err(" %s", n_shexp_quote_cp(*args, FAL0));
   n_err("\n");

   fflush_rewind(sbp->sb_input);

   cnt = fsize(sbp->sb_input);
   buf = NULL;
   bufsize = 0;
   while (fgetline(&buf, &bufsize, &cnt, &llen, sbp->sb_input, TRU1) != NULL) {
      buf[--llen] = '\0';
      n_err(">>> %s\n", buf);
   }
   if (buf != NULL)
      n_free(buf);
   NYD_OU;
}

static boole
a_sendout_mta_test(struct sendbundle *sbp, char const *mta)
{
   enum{
      a_OK = 0,
      a_ERR = 1u<<0,
      a_MAFC = 1u<<1,
      a_ANY = 1u<<2,
      a_LASTNL = 1u<<3
   };
   uz cnt, bufsize, llen;
   FILE *fp;
   s32 f;
   char *buf;
   NYD_IN;

   buf = NIL;

   if(*mta == '\0')
      fp = n_stdout;
   else{
      if((fp = mx_fs_open(mta, "W+")) != NIL)
         ;
      else if((fp = mx_fs_open(mta, "r+")) == NIL)
         goto jeno;
      else if(!mx_file_lock(fileno(fp), (mx_FILE_LOCK_TYPE_READ |
            mx_FILE_LOCK_TYPE_WRITE), 0,0, UZ_MAX)){
         f = su_ERR_NOLCK;
         goto jefo;
      }else if((f = n_folder_mbox_prepare_append(fp, NIL)) != su_ERR_NONE)
         goto jefo;
   }

   fflush_rewind(sbp->sb_input);
   cnt = fsize(sbp->sb_input);
   bufsize = 0;
   f = ok_blook(mbox_fcc_and_pcc) ? a_MAFC : a_OK;

   if((f & a_MAFC) &&
         fprintf(fp, "From %s %s", ok_vlook(LOGNAME), time_current.tc_ctime
            ) < 0)
      goto jeno;
   while(fgetline(&buf, &bufsize, &cnt, &llen, sbp->sb_input, TRU1) != NIL){
      if(fwrite(buf, 1, llen, fp) != llen)
         goto jeno;
      if(f & a_MAFC){
         f |= a_ANY;
         if(llen > 0 && buf[llen - 1] == '\0')
            f |= a_LASTNL;
         else
            f &= ~a_LASTNL;
      }
   }
   if((f & (a_ANY | a_LASTNL)) == a_ANY && putc('\n', fp) == EOF)
      goto jeno;

jdone:
   if(buf != NULL)
      n_free(buf);

   if(fp != n_stdout)
      mx_fs_close(fp);
   else
      clearerr(fp);
jleave:
   NYD_OU;
   return ((f & a_ERR) == 0);

jeno:
   f = su_err_no();
jefo:
   n_err(_("test MTA: cannot open/prepare/write: %s: %s\n"),
      n_shexp_quote_cp(mta, FAL0), su_err_doc(f));
   f = a_ERR;
   if(fp != NIL)
      goto jdone;
   goto jleave;
}

static char const *
a_sendout_random_id(struct header *hp, boole msgid)
{
   static u32 reprocnt;
   struct tm *tmp;
   char const *h;
   uz rl, i;
   char *rv, sep;
   NYD_IN;

   rv = NULL;

   if(msgid && hp != NULL && hp->h_message_id != NULL){
      rv = hp->h_message_id->n_name;
      goto jleave;
   }

   if(ok_blook(message_id_disable))
      goto jleave;

   sep = '%';
   rl = 5;
   if((h = __sendout_ident) != NULL)
      goto jgen;
   if(ok_vlook(hostname) != NULL){
      h = n_nodename(TRU1);
      sep = '@';
      rl = 8;
      goto jgen;
   }
   if(hp != NULL && (h = skin(myorigin(hp))) != NULL &&
         su_cs_find_c(h, '@') != NULL)
      goto jgen;
   goto jleave;

jgen:
   tmp = &time_current.tc_gm;
   i = sizeof("%04d%02d%02d%02d%02d%02d.%s%c%s") -1 + rl + su_cs_len(h);
   rv = n_autorec_alloc(i +1);
   snprintf(rv, i, "%04d%02d%02d%02d%02d%02d.%s%c%s",
      tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday,
      tmp->tm_hour, tmp->tm_min, tmp->tm_sec,
      mx_random_create_cp(rl, &reprocnt), sep, h);
   rv[i] = '\0'; /* Because we don't test snprintf(3) return */
jleave:
   NYD_OU;
   return rv;
}

static boole
a_sendout_put_addrline(char const *hname, struct mx_name *np, FILE *fo,
   enum a_sendout_addrline_flags saf)
{
   sz hnlen, col, len;
   enum{
      m_ERROR = 1u<<0,
      m_INIT = 1u<<1,
      m_COMMA = 1u<<2,
      m_NOPF = 1u<<3,
      m_NONAME = 1u<<4,
      m_CSEEN = 1u<<5
   } m;
   NYD_IN;

   m = (saf & GCOMMA) ? m_ERROR | m_COMMA : m_ERROR;

   if((col = hnlen = su_cs_len(hname)) > 0){
#undef _X
#define _X(S)  (col == sizeof(S) -1 && !su_cs_cmp_case(hname, S))
      if (saf & GFILES) {
         ;
      } else if (_X("reply-to:") || _X("mail-followup-to:") ||
            _X("references:") || _X("in-reply-to:") ||
            _X("disposition-notification-to:"))
         m |= m_NOPF | m_NONAME;
      else if (ok_blook(add_file_recipients)) {
         ;
      } else if (_X("to:") || _X("cc:") || _X("bcc:") || _X("resent-to:"))
         m |= m_NOPF;
#undef _X
   }

   for (; np != NULL; np = np->n_flink) {
      if(np->n_type & GDEL)
         continue;
      if(is_addr_invalid(np,
               ((saf & a_SENDOUT_AL_INC_INVADDR ? 0 : EACM_NOLOG) |
                (m & m_NONAME ? EACM_NONAME : EACM_NONE))) &&
            !(saf & a_SENDOUT_AL_INC_INVADDR))
         continue;

      /* File and pipe addresses only printed with set *add-file-recipients* */
      if ((m & m_NOPF) && is_fileorpipe_addr(np))
         continue;

      if ((m & (m_INIT | m_COMMA)) == (m_INIT | m_COMMA)) {
         if (putc(',', fo) == EOF)
            goto jleave;
         m |= m_CSEEN;
         ++col;
      }

      len = su_cs_len(np->n_fullname);
      if (np->n_type & GREF)
         len += 2;
      ++col; /* The separating space */
      if ((m & m_INIT) && /*col > 1 &&*/
            UCMP(z, col + len, >,
               (np->n_type & GREF ? MIME_LINELEN : 72))) {
         if (fputs("\n ", fo) == EOF)
            goto jleave;
         col = 1;
         m &= ~m_CSEEN;
      } else {
         if(!(m & m_INIT) && fwrite(hname, sizeof *hname, hnlen, fo
               ) != sizeof *hname * hnlen)
            goto jleave;
         if(putc(' ', fo) == EOF)
            goto jleave;
      }
      m = (m & ~m_CSEEN) | m_INIT;

      /* C99 */{
         char *hb;

         /* GREF needs to be placed in angle brackets, but which are missing */
         hb = np->n_fullname;
         if(np->n_type & GREF){
            ASSERT(UCMP(z, len, ==, su_cs_len(np->n_fullname) + 2));
            hb = n_lofi_alloc(len +1);
            len -= 2;
            hb[0] = '<';
            hb[len + 1] = '>';
            hb[len + 2] = '\0';
            su_mem_copy(&hb[1], np->n_fullname, len);
            len += 2;
         }
         len = xmime_write(hb, len, fo,
               ((saf & a_SENDOUT_AL_DOMIME) ? CONV_TOHDR_A : CONV_NONE),
               TD_ICONV, NULL, NULL);
         if(np->n_type & GREF)
            n_lofi_free(hb);
      }
      if (len < 0)
         goto jleave;
      col += len;
   }

   if(!(m & m_INIT) || putc('\n', fo) != EOF)
      m ^= m_ERROR;
jleave:
   NYD_OU;
   return ((m & m_ERROR) == 0);
}

static int
infix_resend(FILE *fi, FILE *fo, struct message *mp, struct mx_name *to,
   int add_resent)
{
   uz cnt, c, bufsize = 0;
   char *buf = NULL;
   char const *cp;
   struct mx_name *fromfield = NULL, *senderfield = NULL, *mdn;
   int rv = 1;
   NYD_IN;

   cnt = mp->m_size;

   /* Write the Resent-Fields */
   if (add_resent) {
      fputs("Resent-", fo);
      mkdate(fo, "Date");
      if ((cp = myaddrs(NULL)) != NULL) {
         if (!a_sendout_put_name(cp, GCOMMA, SEND_MBOX, "Resent-From:", fo,
               &fromfield))
            goto jleave;
      }
      /* TODO RFC 5322: Resent-Sender SHOULD NOT be used if it's EQ -From: */
      if ((cp = ok_vlook(sender)) != NULL) {
         if (!a_sendout_put_name(cp, GCOMMA | GNOT_A_LIST, SEND_MBOX,
               "Resent-Sender:", fo, &senderfield))
            goto jleave;
      }
      if (!a_sendout_put_addrline("Resent-To:", to, fo, a_SENDOUT_AL_COMMA))
         goto jleave;
      if (((cp = ok_vlook(stealthmua)) == NULL || !su_cs_cmp(cp, "noagent")) &&
            (cp = a_sendout_random_id(NULL, TRU1)) != NULL &&
            fprintf(fo, "Resent-Message-ID: <%s>\n", cp) < 0)
         goto jleave;
   }

   if ((mdn = n_UNCONST(check_from_and_sender(fromfield, senderfield))) == NULL)
      goto jleave;
   if (!_check_dispo_notif(mdn, NULL, fo))
      goto jleave;

   /* Write the original headers */
   while (cnt > 0) {
      if (fgetline(&buf, &bufsize, &cnt, &c, fi, 0) == NULL)
         break;
      if (su_cs_cmp_case_n("status:", buf, 7) &&
            su_cs_cmp_case_n("disposition-notification-to:", buf, 28) &&
            !is_head(buf, c, FAL0))
         fwrite(buf, sizeof *buf, c, fo);
      if (cnt > 0 && *buf == '\n')
         break;
   }

   /* Write the message body */
   while (cnt > 0) {
      if (fgetline(&buf, &bufsize, &cnt, &c, fi, 0) == NULL)
         break;
      if (cnt == 0 && *buf == '\n')
         break;
      fwrite(buf, sizeof *buf, c, fo);
   }
   if (buf != NULL)
      n_free(buf);
   if (ferror(fo)) {
      n_perr(_("infix_resend: temporary mail file"), 0);
      goto jleave;
   }
   rv = 0;
jleave:
   NYD_OU;
   return rv;
}

FL boole
mx_sendout_mta_url(struct mx_url *urlp){
   boole rv;
   char const *smtp, *proto;
   NYD_IN;

   if((smtp = ok_vlook(smtp)) == NIL){ /* TODO v15 url_creat(,ok_vlook(mta)*/
      /* *smtp* OBSOLETE message in mta_start() */
      if((proto = mx_url_servbyname(smtp = ok_vlook(mta), NIL, NIL)) == NIL ||
            *proto == '\0'){
         rv = TRUM1;
         goto jleave;
      }
   }

#ifdef mx_HAVE_NET
   rv = mx_url_parse(urlp, CPROTO_SMTP, smtp);
#else
   UNUSED(urlp);
   rv = FAL0;
#endif

jleave:
   NYD_OU;
   return rv;
}

FL int
n_mail(enum n_mailsend_flags msf, struct mx_name *to, struct mx_name *cc,
   struct mx_name *bcc, char const *subject, struct attachment *attach,
   char const *quotefile)
{
   struct header head;
   struct str in, out;
   boole fullnames;
   NYD_IN;

   su_mem_set(&head, 0, sizeof head);

   /* The given subject may be in RFC1522 format. */
   if (subject != NULL) {
      in.s = n_UNCONST(subject);
      in.l = su_cs_len(subject);
      mime_fromhdr(&in, &out, /* TODO ??? TD_ISPR |*/ TD_ICONV);
      head.h_subject = out.s;
   }

   fullnames = ok_blook(fullnames);

   head.h_mailx_command = "mail";
   if((head.h_to = to) != NULL){
      if(!fullnames)
         head.h_to = to = a_sendout_fullnames_cleanup(to);
      head.h_mailx_raw_to = n_namelist_dup(to, to->n_type);
   }
   if((head.h_cc = cc) != NULL){
      if(!fullnames)
         head.h_cc = cc = a_sendout_fullnames_cleanup(cc);
      head.h_mailx_raw_cc = n_namelist_dup(cc, cc->n_type);
   }
   if((head.h_bcc = bcc) != NULL){
      if(!fullnames)
         head.h_bcc = bcc = a_sendout_fullnames_cleanup(bcc);
      head.h_mailx_raw_bcc = n_namelist_dup(bcc, bcc->n_type);
   }

   head.h_attach = attach;

   /* TODO n_exit_status only!!?? */n_mail1(msf, &head, NULL, quotefile);

   if (subject != NULL)
      n_free(out.s);
   NYD_OU;
   return 0;
}

FL int
c_sendmail(void *v)
{
   int rv;
   NYD_IN;

   rv = a_sendout_sendmail(v, n_MAILSEND_NONE);
   NYD_OU;
   return rv;
}

FL int
c_Sendmail(void *v)
{
   int rv;
   NYD_IN;

   rv = a_sendout_sendmail(v, n_MAILSEND_RECORD_RECIPIENT);
   NYD_OU;
   return rv;
}

FL enum okay
n_mail1(enum n_mailsend_flags msf, struct header *hp, struct message *quote,
   char const *quotefile)
{
   struct n_sigman sm;
   struct mx_cred_ctx cc;
   struct mx_url url;
   struct sendbundle sb;
   struct mx_name *to;
   boole dosign, mta_isexe;
   FILE * volatile mtf, *nmtf;
   enum okay volatile rv;
   NYD_IN;

   _sendout_error = FAL0;
   __sendout_ident = NULL;
   n_pstate_err_no = su_ERR_INVAL;
   rv = STOP;
   mtf = NULL;

   n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL) {
   case 0:
      break;
   default:
      goto jleave;
   }

   /* Update some globals we likely need first */
   time_current_update(&time_current, TRU1);

   /* Collect user's mail from standard input.  Get the result as mtf */
   mtf = n_collect(msf, hp, quote, quotefile, &_sendout_error);
   if (mtf == NULL)
      goto jleave;
   /* TODO All custom headers should be joined here at latest
    * TODO In fact that should happen before we enter compose mode, so that the
    * TODO -C headers can be managed (removed etc.) via ~^, too, but the
    * TODO *customhdr* ones are fixated at this very place here, no sooner! */

   dosign = TRUM1;

   /* */
   if(n_psonce & n_PSO_INTERACTIVE){
      if(ok_blook(asksign))
         dosign = mx_tty_yesorno(_("Sign this message"), TRU1);
   }

   if(fsize(mtf) == 0){
      if(n_poption & n_PO_E_FLAG){
         n_pstate_err_no = su_ERR_NONE;
         rv = OKAY;
         goto jleave;
      }

      if(hp->h_subject == NULL)
         n_err(_("No message, no subject; hope that's ok\n"));
      else if(ok_blook(bsdcompat) || ok_blook(bsdmsgs))
         n_err(_("Null message body; hope that's ok\n"));
   }

   if (dosign == TRUM1)
      dosign = ok_blook(smime_sign); /* TODO USER@HOST <-> *from* +++!!! */
#ifndef mx_HAVE_SMIME
   if (dosign) {
      n_err(_("No S/MIME support compiled in\n"));
      goto jleave;
   }
#endif

   /* XXX Update time_current again; once n_collect() offers editing of more
    * XXX headers, including Date:, this must only happen if Date: is the
    * XXX same that it was before n_collect() (e.g., postponing etc.).
    * XXX But *do* update otherwise because the mail seems to be backdated
    * XXX if the user edited some time, which looks odd and it happened
    * XXX to me that i got mis-dated response mails due to that... */
   time_current_update(&time_current, TRU1);

   /* TODO hrmpf; the MIME/send layer rewrite MUST address the init crap:
    * TODO setup the header ONCE; note this affects edit.c, collect.c ...,
    * TODO but: offer a hook that rebuilds/expands/checks/fixates all
    * TODO header fields ONCE, call that ONCE after user editing etc. has
    * TODO completed (one edit cycle) */

   if(!(mta_isexe = mx_sendout_mta_url(&url)))
      goto jleave;
   mta_isexe = (mta_isexe != TRU1);

   /* Take the user names from the combined to and cc lists and do all the
    * alias processing.  The POSIX standard says:
    *   The names shall be substituted when alias is used as a recipient
    *   address specified by the user in an outgoing message (that is,
    *   other recipients addressed indirectly through the reply command
    *   shall not be substituted in this manner).
    * XXX S-nail thus violates POSIX, as has been pointed out correctly by
    * XXX Martin Neitzel, but logic and usability of POSIX standards is
    * XXX sometimes disputable: go for user friendliness */
   to = n_namelist_vaporise_head(hp, TRU1, ((quote != NULL &&
            (msf & n_MAILSEND_IS_FWD) == 0) || !ok_blook(posix)),
         (EACM_NORMAL | EACM_DOMAINCHECK |
            (mta_isexe ? EACM_NONE : EACM_NONAME | EACM_NONAME_OR_FAIL)),
         &_sendout_error);

   if(_sendout_error < 0){
      n_err(_("Some addressees were classified as \"hard error\"\n"));
      n_pstate_err_no = su_ERR_PERM;
      goto jfail_dead;
   }
   if(to == NULL){
      n_err(_("No recipients specified\n"));
      n_pstate_err_no = su_ERR_DESTADDRREQ;
      goto jfail_dead;
   }

   /* */
   su_mem_set(&sb, 0, sizeof sb);
   sb.sb_hp = hp;
   sb.sb_to = to;
   sb.sb_input = mtf;
   sb.sb_urlp = mta_isexe ? NIL : &url;
   sb.sb_credp = &cc;

   if((dosign || count_nonlocal(to) > 0) &&
         !_sendbundle_setup_creds(&sb, (dosign > 0))){
      /* TODO saving $DEAD and recovering etc is not yet well defined */
      n_pstate_err_no = su_ERR_INVAL;
      goto jfail_dead;
   }

   /* 'Bit ugly kind of control flow until we find a charset that does it */
   /* C99 */{
      boole any;

      for(any = FAL0, charset_iter_reset(hp->h_charset);;
            any = TRU1, charset_iter_next()){
         int err;
         boole volatile force;

         force = FAL0;
         if(!charset_iter_is_valid() &&
               (!any || !(force = ok_blook(mime_force_sendout))))
            err = su_ERR_NOENT;
         else if((nmtf = a_sendout_infix(hp, mtf, dosign, force)) != NULL)
            break;
#ifdef mx_HAVE_ICONV
         else if((err = n_iconv_err_no) == su_ERR_ILSEQ ||
               err == su_ERR_INVAL || err == su_ERR_NOENT){
            rewind(mtf);
            continue;
         }
#endif

         n_perr(_("Cannot find a usable character set to encode message"),
            err);
         n_pstate_err_no = su_ERR_NOTSUP;
         goto jfail_dead;
      }
   }
   mtf = nmtf;

   /*  */
#ifdef mx_HAVE_SMIME
   if (dosign) {
      if ((nmtf = smime_sign(mtf, sb.sb_signer.s)) == NULL)
         goto jfail_dead;
      mx_fs_close(mtf);
      mtf = nmtf;
   }
#endif

   /* TODO truly - i still don't get what follows: (1) we deliver file
    * TODO and pipe addressees, (2) we mightrecord() and (3) we transfer
    * TODO even if (1) savedeadletter() etc.  To me this doesn't make sense? */

   /* C99 */{
      u32 cnt;
      boole b;

      /* Deliver pipe and file addressees */
      b = (ok_blook(record_files) && count(to) > 0);
      to = a_sendout_file_a_pipe(to, mtf, &_sendout_error);

      if (_sendout_error)
         savedeadletter(mtf, FAL0);

      to = elide(to); /* XXX only to drop GDELs due a_sendout_file_a_pipe()! */
      cnt = count(to);

      if (((msf & n_MAILSEND_RECORD_RECIPIENT) || b || cnt > 0) &&
            !mightrecord(mtf, (msf & n_MAILSEND_RECORD_RECIPIENT ? to : NULL),
               FAL0))
         goto jleave;
      if (cnt > 0) {
         sb.sb_hp = hp;
         sb.sb_to = to;
         sb.sb_input = mtf;
         b = FAL0;
         if(a_sendout_transfer(&sb, &b))
            rv = OKAY;
         else if(b && _sendout_error == 0){
            _sendout_error = b;
            savedeadletter(mtf, FAL0);
         }
      } else if (!_sendout_error)
         rv = OKAY;
   }

   n_sigman_cleanup_ping(&sm);
jleave:
   if(mtf != NIL){
      char const *cp;

      mx_fs_close(mtf);

      if((cp = ok_vlook(on_compose_cleanup)) != NULL)
         temporary_compose_mode_hook_call(cp, NULL, NULL);
   }

   temporary_compose_mode_hook_unroll();

   if (_sendout_error)
      n_exit_status |= n_EXIT_SEND_ERROR;
   if(rv == OKAY)
      n_pstate_err_no = su_ERR_NONE;
   NYD_OU;
   n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
   return rv;

jfail_dead:
   _sendout_error = TRU1;
   savedeadletter(mtf, TRU1);
   n_err(_("... message not sent\n"));
   goto jleave;
}

FL int
mkdate(FILE *fo, char const *field)
{
   struct tm tmpgm, *tmptr;
   int tzdiff, tzdiff_hour, tzdiff_min, rv;
   NYD_IN;

   su_mem_copy(&tmpgm, &time_current.tc_gm, sizeof tmpgm);
   tzdiff = time_current.tc_time - mktime(&tmpgm);
   tzdiff_hour = (int)(tzdiff / 60);
   tzdiff_min = tzdiff_hour % 60;
   tzdiff_hour /= 60;
   tmptr = &time_current.tc_local;
   if (tmptr->tm_isdst > 0)
      ++tzdiff_hour;
   rv = fprintf(fo, "%s: %s, %02d %s %04d %02d:%02d:%02d %+05d\n",
         field,
         n_weekday_names[tmptr->tm_wday],
         tmptr->tm_mday, n_month_names[tmptr->tm_mon],
         tmptr->tm_year + 1900, tmptr->tm_hour,
         tmptr->tm_min, tmptr->tm_sec,
         tzdiff_hour * 100 + tzdiff_min);
   NYD_OU;
   return rv;
}

FL boole
n_puthead(boole nosend_msg, struct header *hp, FILE *fo, enum gfield w,
   enum sendaction action, enum conversion convert, char const *contenttype,
   char const *charset)
{
#define a_PUT_CC_BCC_FCC()   \
do {\
   if ((w & GCC) && (hp->h_cc != NULL || nosend_msg == TRUM1)) {\
      if (!a_sendout_put_addrline("Cc:", hp->h_cc, fo, saf))\
         goto jleave;\
      ++gotcha;\
   }\
   if ((w & GBCC) && (hp->h_bcc != NULL || nosend_msg == TRUM1)) {\
      if (!a_sendout_put_addrline("Bcc:", hp->h_bcc, fo, saf))\
         goto jleave;\
      ++gotcha;\
   }\
   if((w & GBCC_IS_FCC) && nosend_msg){\
      for(np = hp->h_fcc; np != NULL; np = np->n_flink){\
         if(fprintf(fo, "Fcc: %s\n", np->n_name) < 0)\
            goto jleave;\
         ++gotcha;\
      }\
   }\
} while (0)

   char const *addr;
   uz gotcha;
   int stealthmua;
   boole nodisp;
   enum a_sendout_addrline_flags saf;
   struct mx_name *np, *fromasender, *mft, **mftp;
   boole rv;
   NYD_IN;

   mftp = NIL;
   fromasender = mft = NIL;
   rv = FAL0;

   if(nosend_msg == TRUM1 &&
         fputs(_("# Message will be discarded unless file is saved\n"),
            fo) == EOF)
      goto jleave;

   if ((addr = ok_vlook(stealthmua)) != NULL)
      stealthmua = !su_cs_cmp(addr, "noagent") ? -1 : 1;
   else
      stealthmua = 0;
   gotcha = 0;
   nodisp = (action != SEND_TODISP);
   saf = (w & (GCOMMA | GFILES)) | (nodisp ? a_SENDOUT_AL_DOMIME : 0);
   if(nosend_msg)
      saf |= a_SENDOUT_AL_INC_INVADDR;

   if (w & GDATE)
      mkdate(fo, "Date"), ++gotcha;
   if (w & GIDENT) {
      if (hp->h_from == NULL || hp->h_sender == NULL)
         setup_from_and_sender(hp);

      if (hp->h_from != NULL) {
         if (!a_sendout_put_addrline("From:", hp->h_from, fo, saf))
            goto jleave;
         ++gotcha;
      }

      if (hp->h_sender != NULL) {
         if (!a_sendout_put_addrline("Sender:", hp->h_sender, fo, saf))
            goto jleave;
         ++gotcha;
      }

      fromasender = n_UNCONST(check_from_and_sender(hp->h_from, hp->h_sender));
      if (fromasender == NULL)
         goto jleave;
      /* Note that fromasender is (NULL,) 0x1 or real sender here */
   }

   /* M-F-T: check this now, and possibly place us in Cc: */
   if((w & GIDENT) && !nosend_msg){
      /* Mail-Followup-To: TODO factor out this huge block of code.
       * TODO Also, this performs multiple expensive list operations, which
       * TODO hopefully can be heavily optimized later on! */
      /* Place ourselfs in there if any non-subscribed list is an addressee */
      if((hp->h_flags & HF_LIST_REPLY) || hp->h_mft != NIL ||
            ok_blook(followup_to)){
         enum{
            a_HADMFT = 1u<<(HF__NEXT_SHIFT + 0),
            a_WASINMFT = 1u<<(HF__NEXT_SHIFT + 1),
            a_ANYLIST = 1u<<(HF__NEXT_SHIFT + 2),
            a_OTHER = 1u<<(HF__NEXT_SHIFT + 3)
         };
         struct mx_name *x;
         u32 f;

         f = hp->h_flags | (hp->h_mft != NIL ? a_HADMFT : 0);
         if(f & a_HADMFT){
            /* Detect whether we were part of the former MFT:.
             * Throw away MFT: if we were the sole member (kidding) */
            hp->h_mft = mft = elide(hp->h_mft);
            mft = mx_alternates_remove(n_namelist_dup(mft, GNONE), FAL0);
            if(mft == NIL)
               f ^= a_HADMFT;
            else for(x = hp->h_mft; x != NIL;
                  x = x->n_flink, mft = mft->n_flink){
               if(mft == NIL){
                  f |= a_WASINMFT;
                  break;
               }
            }
         }

         /* But for that, we have to remove all incarnations of ourselfs first.
          * TODO It is total crap that we have alternates_remove(), is_myname()
          * TODO or whatever; these work only with variables, not with data
          * TODO that is _currently_ in some header fields!!!  v15.0: complete
          * TODO rewrite, object based, lazy evaluated, on-the-fly marked.
          * TODO then this should be a really cheap thing in here... */
         np = elide(mx_alternates_remove(cat(
               n_namelist_dup(hp->h_to, GEXTRA | GFULL),
               n_namelist_dup(hp->h_cc, GEXTRA | GFULL)), FAL0));
         addr = hp->h_list_post;
         mft = NIL;
         mftp = &mft;

         while((x = np) != NIL){
            s8 ml;

            np = np->n_flink;

            /* Automatically make MLIST_KNOWN List-Post: address */
            /* XXX mx_mlist_query_mp()?? */
            if((ml = mx_mlist_query(x->n_name, FAL0)) == mx_MLIST_OTHER &&
                  addr != NIL && !su_cs_cmp_case(addr, x->n_name))
               ml = mx_MLIST_KNOWN;

            /* Any non-subscribed list?  Add ourselves */
            switch(ml){
            case mx_MLIST_KNOWN:
               f |= HF_MFT_SENDER;
               /* FALLTHRU */
            case mx_MLIST_SUBSCRIBED:
               f |= a_ANYLIST;
               goto j_mft_add;
            case mx_MLIST_OTHER:
               f |= a_OTHER;
               if(!(f & HF_LIST_REPLY)){
j_mft_add:
                  if(!is_addr_invalid(x,
                        EACM_STRICT | EACM_NOLOG | EACM_NONAME)){
                     x->n_blink = *mftp;
                     x->n_flink = NIL;
                     *mftp = x;
                     mftp = &x->n_flink;
                  } /* XXX write some warning?  if verbose?? */
                  continue;
               }
               /* And if this is a reply that honoured a M-F-T: header then
                * we'll also add all members of the original M-F-T: that are
                * still addressed by us, regardless of other circumstances */
               /* TODO If the user edited this list, then we should include
                * TODO whatever she did therewith, even if _LIST_REPLY! */
               else if(f & a_HADMFT){
                  struct mx_name *ox;

                  for(ox = hp->h_mft; ox != NIL; ox = ox->n_flink)
                     if(!su_cs_cmp_case(ox->n_name, x->n_name))
                        goto j_mft_add;
               }
               break;
            }
         }

         if((f & (a_ANYLIST | a_HADMFT)) && mft != NIL){
            if(((f & HF_MFT_SENDER) ||
                     ((f & (a_ANYLIST | a_HADMFT)) == a_HADMFT)) &&
                  (np = fromasender) != NIL && np != R(struct mx_name*,0x1)){
               *mftp = ndup(np, (np->n_type & ~GMASK) | GEXTRA | GFULL);

               /* Place ourselfs in the Cc: if we will be a member of M-F-T:,
                * and we are not subscribed (and are no addressee yet)? */
               /* TODO This entire block is much to expensive and should
                * TODO be somewhere else (like name_unite(), or so) */
               if(ok_blook(followup_to_add_cc)){
                  struct mx_name **npp;

                  np = ndup(np, (np->n_type & ~GMASK) | GCC | GFULL);
                  np = cat(cat(hp->h_to, hp->h_cc), np);
                  np = mx_alternates_remove(np, TRU1);
                  np = elide(np);
                  hp->h_to = hp->h_cc = NIL;
                  for(; np != NIL; np = np->n_flink){
                     switch(np->n_type & (GDEL | GMASK)){
                     case GTO: npp = &hp->h_to; break;
                     case GCC: npp = &hp->h_cc; break;
                     default: continue;
                     }
                     *npp = cat(*npp, ndup(np, np->n_type | GFULL));
                  }
               }
            }
         }else
            mft = NIL;
      }
   }

   if(nosend_msg == TRUM1 &&
         fputs(_("# To:, Cc: and Bcc: support a ?single modifier: "
            "To?: exa, \n"), fo) == EOF)
      goto jleave;

#if 1
   if ((w & GTO) && (hp->h_to != NULL || nosend_msg == TRUM1)) {
      if (!a_sendout_put_addrline("To:", hp->h_to, fo, saf))
         goto jleave;
      ++gotcha;
   }
#else
   /* TODO Thought about undisclosed recipients:;, but would be such a fake
    * TODO given that we cannot handle group addresses.  Ridiculous */
   if (w & GTO) {
      struct mx_name *xto;

      if ((xto = hp->h_to) != NULL) {
         char const ud[] = "To: Undisclosed recipients:;\n" /* TODO groups */;

         if (count_nonlocal(xto) != 0 || ok_blook(add_file_recipients) ||
               (hp->h_cc != NULL && count_nonlocal(hp->h_cc) > 0))
            goto jto_fmt;
         if (fwrite(ud, 1, sizeof(ud) -1, fo) != sizeof(ud) -1)
            goto jleave;
         ++gotcha;
      } else if (nosend_msg == TRUM1) {
jto_fmt:
         if (!a_sendout_put_addrline("To:", hp->h_to, fo, saf))
            goto jleave;
         ++gotcha;
      }
   }
#endif

   if(!ok_blook(bsdcompat) && !ok_blook(bsdorder))
      a_PUT_CC_BCC_FCC();

   if ((w & GSUBJECT) && (hp->h_subject != NULL || nosend_msg == TRUM1)) {
      if (fwrite("Subject: ", sizeof(char), 9, fo) != 9)
         goto jleave;
      if (hp->h_subject != NULL) {
         uz sublen;
         char const *sub;

         sublen = su_cs_len(sub = subject_re_trim(hp->h_subject));

         /* Trimmed something, (re-)add Re: */
         if (sub != hp->h_subject) {
            if (fwrite("Re: ", 1, 4, fo) != 4) /* RFC mandates eng. "Re: " */
               goto jleave;
            if (sublen > 0 &&
                  xmime_write(sub, sublen, fo,
                     (!nodisp ? CONV_NONE : CONV_TOHDR),
                     (!nodisp ? TD_ISPR | TD_ICONV : TD_ICONV), NULL, NULL) < 0)
               goto jleave;
         }
         /* This may be, e.g., a Fwd: XXX yes, unfortunately we do like that */
         else if (*sub != '\0') {
            if (xmime_write(sub, sublen, fo, (!nodisp ? CONV_NONE : CONV_TOHDR),
                  (!nodisp ? TD_ISPR | TD_ICONV : TD_ICONV), NULL, NULL) < 0)
               goto jleave;
         }
      }
      ++gotcha;
      putc('\n', fo);
   }

   if (ok_blook(bsdcompat) || ok_blook(bsdorder))
      a_PUT_CC_BCC_FCC();

   if ((w & GMSGID) && stealthmua <= 0 &&
         (addr = a_sendout_random_id(hp, TRU1)) != NULL) {
      if (fprintf(fo, "Message-ID: <%s>\n", addr) < 0)
         goto jleave;
      ++gotcha;
   }

   if(w & (GREF | GREF_IRT)){
      if((np = hp->h_in_reply_to) == NULL)
         hp->h_in_reply_to = np = n_header_setup_in_reply_to(hp);
      if(np != NULL){
         if(nosend_msg == TRUM1 &&
               fputs(_("# Removing or modifying In-Reply-To: "
                     "breaks the old, and starts a new thread.\n"
                  "# Assigning hyphen-minus - creates a thread of only the "
                     "replied-to message\n"), fo) == EOF)
            goto jleave;
         if(!a_sendout_put_addrline("In-Reply-To:", np, fo, 0))
            goto jleave;
         ++gotcha;
      }

      if((w & GREF) && (np = hp->h_ref) != NULL){
         if(!a_sendout_put_addrline("References:", np, fo, 0))
            goto jleave;
         ++gotcha;
      }
   }

   if (w & GIDENT) {
      /* Reply-To:.  Be careful not to destroy a possible user input, duplicate
       * the list first.. TODO it is a terrible codebase.. */
      if((np = hp->h_reply_to) != NULL)
         np = n_namelist_dup(np, np->n_type);
      else{
         char const *v15compat;

         if((v15compat = ok_vlook(replyto)) != NULL)
            n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
         if((addr = ok_vlook(reply_to)) == NULL)
            addr = v15compat;
         np = lextract(addr, GEXTRA |
               (ok_blook(fullnames) ? GFULL | GSKIN : GSKIN));
      }
      if (np != NULL &&
            (np = elide(
               checkaddrs(usermap(np, TRU1), EACM_STRICT | EACM_NOLOG,
                  NULL))) != NULL) {
         if (!a_sendout_put_addrline("Reply-To:", np, fo, saf))
            goto jleave;
         ++gotcha;
      }
   }

   if((w & GIDENT) && !nosend_msg){
      if(mft != NIL){
         if(!a_sendout_put_addrline("Mail-Followup-To:", mft, fo, saf))
            goto jleave;
         ++gotcha;
      }

      if(!_check_dispo_notif(fromasender, hp, fo))
         goto jleave;
   }

   if ((w & GUA) && stealthmua == 0) {
      if (fprintf(fo, "User-Agent: %s %s\n", n_uagent,
            (su_state_has(su_STATE_REPRODUCIBLE)
               ? su_reproducible_build : ok_vlook(version))) < 0)
         goto jleave;
      ++gotcha;
   }

   /* Custom headers, as via -C and *customhdr* TODO JOINED AFTER COMPOSE! */
   if(!nosend_msg){
      struct n_header_field *chlp[2], *hfp;
      u32 i;

      chlp[0] = n_poption_arg_C;
      chlp[1] = n_customhdr_list;

      for(i = 0; i < NELEM(chlp); ++i)
         if((hfp = chlp[i]) != NULL){
            if(!_sendout_header_list(fo, hfp, nodisp))
               goto jleave;
            ++gotcha;
         }
   }

   /* The user may have placed headers when editing */
   if(1){
      struct n_header_field *hfp;

      if((hfp = hp->h_user_headers) != NULL){
         if(!_sendout_header_list(fo, hfp, nodisp))
            goto jleave;
         ++gotcha;
      }
   }

   /* We don't need MIME unless.. we need MIME?! */
   if ((w & GMIME) && ((n_pstate & n_PS_HEADER_NEEDED_MIME) ||
         hp->h_attach != NULL ||
         ((n_poption & n_PO_Mm_FLAG) && n_poption_arg_Mm != NULL) ||
         convert != CONV_7BIT || !n_iconv_name_is_ascii(charset))) {
      ++gotcha;
      if (fputs("MIME-Version: 1.0\n", fo) == EOF)
         goto jleave;
      if (hp->h_attach != NULL) {
         _sendout_boundary = mime_param_boundary_create();/*TODO carrier*/
         if (fprintf(fo,
               "Content-Type: multipart/mixed;\n boundary=\"%s\"\n",
               _sendout_boundary) < 0)
            goto jleave;
      } else {
         if(a_sendout_put_ct(fo, contenttype, charset) < 0 ||
               a_sendout_put_cte(fo, convert) < 0)
            goto jleave;
      }
   }

   if (gotcha && (w & GNL))
      if (putc('\n', fo) == EOF)
         goto jleave;
   rv = TRU1;
jleave:
   NYD_OU;
   return rv;
#undef a_PUT_CC_BCC_FCC
}

FL enum okay
n_resend_msg(struct message *mp, struct mx_url *urlp, struct header *hp,
   boole add_resent)
{
   struct n_sigman sm;
   struct mx_cred_ctx cc;
   struct sendbundle sb;
   FILE * volatile ibuf, *nfo, * volatile nfi;
   struct mx_fs_tmp_ctx *fstcp;
   struct mx_name *to;
   enum okay volatile rv;
   NYD_IN;

   _sendout_error = FAL0;
   __sendout_ident = NULL;
   n_pstate_err_no = su_ERR_INVAL;

   rv = STOP;
   to = hp->h_to;
   ASSERT(hp->h_cc == NIL);
   ASSERT(hp->h_bcc == NIL);
   nfi = ibuf = NULL;

   n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL) {
   case 0:
      break;
   default:
      goto jleave;
   }

   /* Update some globals we likely need first */
   time_current_update(&time_current, TRU1);

   if((nfo = mx_fs_tmp_open("resend", (mx_FS_O_WRONLY | mx_FS_O_HOLDSIGS |
            mx_FS_O_REGISTER), &fstcp)) == NIL){
      _sendout_error = TRU1;
      n_perr(_("resend_msg: temporary mail file"), 0);
      n_pstate_err_no = su_ERR_IO;
      goto jleave;
   }

   if((nfi = mx_fs_open(fstcp->fstc_filename, "r")) == NIL){
      n_perr(fstcp->fstc_filename, 0);
      n_pstate_err_no = su_ERR_IO;
   }

   mx_fs_tmp_release(fstcp);

   if(nfi == NIL)
      goto jerr_o;

   if((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL){
      n_pstate_err_no = su_ERR_IO;
      goto jerr_io;
   }

   /* C99 */{
      char const *cp;

      if((cp = ok_vlook(on_resend_enter)) != NULL){
         /*setup_from_and_sender(hp);*/
         temporary_compose_mode_hook_call(cp, &n_temporary_compose_hook_varset,
            hp);
      }
   }

   su_mem_set(&sb, 0, sizeof sb);
   sb.sb_to = to;
   sb.sb_input = nfi;
   sb.sb_urlp = urlp;
   sb.sb_credp = &cc;

   if(!_sendout_error &&
         count_nonlocal(to) > 0 && !_sendbundle_setup_creds(&sb, FAL0)){
      /* ..wait until we can write DEAD */
      n_pstate_err_no = su_ERR_INVAL;
      _sendout_error = -1;
   }

   if(infix_resend(ibuf, nfo, mp, to, add_resent) != 0){
jfail_dead:
      savedeadletter(nfi, TRU1);
      n_err(_("... message not sent\n"));
jerr_io:
      mx_fs_close(nfi);
      nfi = NIL;
jerr_o:
      mx_fs_close(nfo);
      _sendout_error = TRU1;
      goto jleave;
   }

   if(_sendout_error < 0)
      goto jfail_dead;

   mx_fs_close(nfo);
   rewind(nfi);

   /* C99 */{
      boole b, c;

      /* Deliver pipe and file addressees */
      b = (ok_blook(record_files) && count(to) > 0);
      to = a_sendout_file_a_pipe(to, nfi, &_sendout_error);

      if(_sendout_error)
         savedeadletter(nfi, FAL0);

      to = elide(to); /* XXX only to drop GDELs due a_sendout_file_a_pipe()! */
      c = (count(to) > 0);

      if(b || c){
         if(!ok_blook(record_resent) || mightrecord(nfi, NULL, TRU1)){
            sb.sb_to = to;
            /*sb.sb_input = nfi;*/
            b = FAL0;
            if(!c || a_sendout_transfer(&sb, &b))
               rv = OKAY;
            else if(b && _sendout_error == 0){
               _sendout_error = b;
               savedeadletter(nfi, FAL0);
            }
         }
      }else if(!_sendout_error)
         rv = OKAY;
   }

   n_sigman_cleanup_ping(&sm);
jleave:
   if(nfi != NIL){
      char const *cp;

      mx_fs_close(nfi);

      if(ibuf != NULL){
         if((cp = ok_vlook(on_resend_cleanup)) != NULL)
            temporary_compose_mode_hook_call(cp, NULL, NULL);

         temporary_compose_mode_hook_unroll();
      }
   }

   if (_sendout_error)
      n_exit_status |= n_EXIT_SEND_ERROR;
   if(rv == OKAY)
      n_pstate_err_no = su_ERR_NONE;
   NYD_OU;
   n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
   return rv;
}

FL void
savedeadletter(FILE *fp, boole fflush_rewind_first){
   struct n_string line;
   int c;
   enum {a_NONE, a_INIT = 1<<0, a_BODY = 1<<1, a_NL = 1<<2} flags;
   ul bytes, lines;
   FILE *dbuf;
   char const *cp, *cpq;
   NYD_IN;

   if(!ok_blook(save))
      goto jleave;

   if(fflush_rewind_first){
      fflush(fp);
      rewind(fp);
   }
   if(fsize(fp) == 0)
      goto jleave;

   cp = n_getdeadletter();
   cpq = n_shexp_quote_cp(cp, FAL0);

   if(n_poption & n_PO_D){
      n_err(_(">>> Would (try to) write $DEAD %s\n"), cpq);
      goto jleave;
   }

   if((dbuf = mx_fs_open(cp, "w")) == NIL){
      n_perr(_("Cannot save to $DEAD"), 0);
      goto jleave;
   }
   /* XXX Natomic */
   mx_file_lock(fileno(dbuf), mx_FILE_LOCK_TYPE_WRITE, 0,0, UZ_MAX);

   fprintf(n_stdout, "%s ", cpq);
   fflush(n_stdout);

   /* TODO savedeadletter() non-conforming: should check whether we have any
    * TODO headers, if not we need to place "something", anything will do.
    * TODO MIME is completely missing, we use MBOXO quoting!!  Yuck.
    * TODO I/O error handling missing.  Yuck! */
   n_string_reserve(n_string_creat_auto(&line), 2 * SEND_LINESIZE);
   bytes = (ul)fprintf(dbuf, "From %s %s",
         ok_vlook(LOGNAME), time_current.tc_ctime);
   lines = 1;
   for(flags = a_NONE, c = '\0'; c != EOF; bytes += line.s_len, ++lines){
      n_string_trunc(&line, 0);
      while((c = getc(fp)) != EOF && c != '\n')
         n_string_push_c(&line, c);

      /* TODO It may be that we have only some plain text.  It may be that we
       * TODO have a complete MIME encoded message.  We don't know, and we
       * TODO have no usable mechanism to dig it!!  We need v15! */
      if(!(flags & a_INIT)){
         uz i;

         /* Throw away leading empty lines! */
         if(line.s_len == 0)
            continue;
         for(i = 0; i < line.s_len; ++i){
            if(fieldnamechar(line.s_dat[i]))
               continue;
            if(line.s_dat[i] == ':'){
               flags |= a_INIT;
               break;
            }else{
               /* We have no headers, this is already a body line! */
               flags |= a_INIT | a_BODY;
               break;
            }
         }
         /* Well, i had to check whether the RFC allows this.  Assume we've
          * passed the headers, too, then! */
         if(i == line.s_len)
            flags |= a_INIT | a_BODY;
      }
      if(flags & a_BODY){
         if(line.s_len >= 5 && !su_mem_cmp(line.s_dat, "From ", 5))
            n_string_unshift_c(&line, '>');
      }
      if(line.s_len == 0)
         flags |= a_BODY | a_NL;
      else
         flags &= ~a_NL;

      n_string_push_c(&line, '\n');
      fwrite(line.s_dat, sizeof *line.s_dat, line.s_len, dbuf);
   }
   if(!(flags & a_NL)){
      putc('\n', dbuf);
      ++bytes;
      ++lines;
   }
   n_string_gut(&line);

   mx_fs_close(dbuf);
   fprintf(n_stdout, "%lu/%lu\n", lines, bytes);
   fflush(n_stdout);

   rewind(fp);
jleave:
   NYD_OU;
}

#undef SEND_LINESIZE

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/mx/shexp.c000066400000000000000000002102741352610246600155770ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ Shell "word", file- and other name expansions, incl. file globbing.
 *@ TODO v15: peek signal states while opendir/readdir/etc.
 *@ TODO "Magic solidus" used as path separator.
 *
 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: BSD-3-Clause TODO ISC
 */
/*
 * Copyright (c) 1980, 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.
 */
#undef su_FILE
#define su_FILE shexp
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

#include 

#ifdef mx_HAVE_FNMATCH
# include 
# include 
#endif

#include 
#include 
#include 
#include 

#include "mx/cmd-shortcut.h"
#include "mx/iconv.h"
#include "mx/ui-str.h"

/* TODO fake */
#include "su/code-in.h"

/* POSIX says
 *   Environment variable names used by the utilities in the Shell and
 *   Utilities volume of POSIX.1-2008 consist solely of uppercase
 *   letters, digits, and the  ('_') from the characters
 *   defined in Portable Character Set and do not begin with a digit.
 *   Other characters may be permitted by an implementation;
 *   applications shall tolerate the presence of such names.
 * We do support the hyphen-minus "-" (except in last position for ${x[:]-y}).
 * We support some special parameter names for one-letter(++) variable names;
 * these have counterparts in the code that manages internal variables,
 * and some more special treatment below! */
#define a_SHEXP_ISVARC(C) (su_cs_is_alnum(C) || (C) == '_' || (C) == '-')
#define a_SHEXP_ISVARC_BAD1ST(C) (su_cs_is_digit(C)) /* (Assumed below!) */
#define a_SHEXP_ISVARC_BADNST(C) ((C) == '-')

enum a_shexp_quote_flags{
   a_SHEXP_QUOTE_NONE,
   a_SHEXP_QUOTE_ROUNDTRIP = 1u<<0, /* Result won't be consumed immediately */

   a_SHEXP_QUOTE_T_REVSOL = 1u<<8,  /* Type: by reverse solidus */
   a_SHEXP_QUOTE_T_SINGLE = 1u<<9,  /* Type: single-quotes */
   a_SHEXP_QUOTE_T_DOUBLE = 1u<<10, /* Type: double-quotes */
   a_SHEXP_QUOTE_T_DOLLAR = 1u<<11, /* Type: dollar-single-quotes */
   a_SHEXP_QUOTE_T_MASK = a_SHEXP_QUOTE_T_REVSOL | a_SHEXP_QUOTE_T_SINGLE |
         a_SHEXP_QUOTE_T_DOUBLE | a_SHEXP_QUOTE_T_DOLLAR,

   a_SHEXP_QUOTE__FREESHIFT = 16u
};

#ifdef mx_HAVE_FNMATCH
struct a_shexp_glob_ctx{
   char const *sgc_patdat; /* Remaining pattern (at and below level) */
   uz sgc_patlen;
   struct n_string *sgc_outer; /* Resolved path up to this level */
   u32 sgc_flags;
   u8 sgc__pad[4];
};

struct a_shexp_glob_one_ctx{
   struct a_shexp_glob_ctx *sgoc_sgcp;
   struct a_shexp_glob_ctx *sgoc_new_sgcp;
   struct n_strlist **sgoc_slpp;
   uz sgoc_old_outer_len;
   sz sgoc_dt_type; /* Can be -1 even if mx_HAVE_DIRENT_TYPE */
   char const *sgoc_name;
};
#endif

struct a_shexp_quote_ctx{
   struct n_string *sqc_store;   /* Result storage */
   struct str sqc_input;         /* Input data, topmost level */
   u32 sqc_cnt_revso;
   u32 sqc_cnt_single;
   u32 sqc_cnt_double;
   u32 sqc_cnt_dollar;
   enum a_shexp_quote_flags sqc_flags;
   u8 sqc__dummy[4];
};

struct a_shexp_quote_lvl{
   struct a_shexp_quote_lvl *sql_link; /* Outer level */
   struct str sql_dat;                 /* This level (has to) handle(d) */
   enum a_shexp_quote_flags sql_flags;
   u8 sql__dummy[4];
};

/* Locate the user's mailbox file (where new, unread mail is queued) */
static char *a_shexp_findmail(char const *user, boole force);

/* Expand ^~/? and ^~USER/? constructs.
 * Returns the completely resolved (maybe empty or identical to input)
 * n_autorec_alloc()ed string */
static char *a_shexp_tilde(char const *s);

/* Perform fnmatch(3).  May return NULL on error */
static char *a_shexp_globname(char const *name, enum fexp_mode fexpm);
#ifdef mx_HAVE_FNMATCH
static boole a_shexp__glob(struct a_shexp_glob_ctx *sgcp,
               struct n_strlist **slpp);
static char const *a_shexp__glob_one(struct a_shexp_glob_one_ctx *sgocp);
static su_sz a_shexp__globsort(void const *cvpa, void const *cvpb);
#endif

/* Parse an input string and create a sh(1)ell-quoted result */
static void a_shexp__quote(struct a_shexp_quote_ctx *sqcp,
               struct a_shexp_quote_lvl *sqlp);

static char *
a_shexp_findmail(char const *user, boole force){
   char *rv;
   char const *cp;
   NYD2_IN;

   if(!force){
      if((cp = ok_vlook(inbox)) != NULL && *cp != '\0'){
         /* _NFOLDER extra introduced to avoid % recursion loops */
         if((rv = fexpand(cp, FEXP_NSPECIAL | FEXP_NFOLDER | FEXP_NSHELL)
               ) != NULL)
            goto jleave;
         n_err(_("*inbox* expansion failed, using $MAIL/built-in: %s\n"), cp);
      }
      /* Heirloom compatibility: an IMAP *folder* becomes "%" */
#ifdef mx_HAVE_IMAP
      else if(cp == NULL && !su_cs_cmp(user, ok_vlook(LOGNAME)) &&
            which_protocol(cp = n_folder_query(), FAL0, FAL0, NULL)
               == PROTO_IMAP){
         /* TODO Compat handling of *folder* with IMAP! */
         n_OBSOLETE("no more expansion of *folder* in \"%\": "
            "please set *inbox*");
         rv = savestr(cp);
         goto jleave;
      }
#endif

      if((cp = ok_vlook(MAIL)) != NULL){
         rv = savestr(cp);
         goto jleave;
      }
   }

   /* C99 */{
      uz ulen, i;

      ulen = su_cs_len(user) +1;
      i = sizeof(VAL_MAIL) -1 + 1 + ulen;

      rv = n_autorec_alloc(i);
      su_mem_copy(rv, VAL_MAIL, (i = sizeof(VAL_MAIL) -1));
      rv[i] = '/';
      su_mem_copy(&rv[++i], user, ulen);
   }
jleave:
   NYD2_OU;
   return rv;
}

static char *
a_shexp_tilde(char const *s){
   struct passwd *pwp;
   uz nl, rl;
   char const *rp, *np;
   char *rv;
   NYD2_IN;

   if(*(rp = &s[1]) == '/' || *rp == '\0'){
      np = ok_vlook(HOME);
      rl = su_cs_len(rp);
   }else{
      if((rp = su_cs_find_c(np = rp, '/')) != NULL){
         nl = P2UZ(rp - np);
         np = savestrbuf(np, nl);
         rl = su_cs_len(rp);
      }else
         rl = 0;

      if((pwp = getpwnam(np)) == NULL){
         rv = savestr(s);
         goto jleave;
      }
      np = pwp->pw_dir;
   }

   nl = su_cs_len(np);
   rv = n_autorec_alloc(nl + 1 + rl +1);
   su_mem_copy(rv, np, nl);
   if(rl > 0){
      su_mem_copy(rv + nl, rp, rl);
      nl += rl;
   }
   rv[nl] = '\0';
jleave:
   NYD2_OU;
   return rv;
}

static char *
a_shexp_globname(char const *name, enum fexp_mode fexpm){
#ifdef mx_HAVE_FNMATCH
   struct a_shexp_glob_ctx sgc;
   struct n_string outer;
   struct n_strlist *slp;
   char *cp;
   void *lofi_snap;
   NYD_IN;

   lofi_snap = n_lofi_snap_create();

   su_mem_set(&sgc, 0, sizeof sgc);
   /* C99 */{
      uz i;

      sgc.sgc_patlen = i = su_cs_len(name);
      sgc.sgc_patdat = cp = n_lofi_alloc(++i);
      su_mem_copy(cp, name, i);
      sgc.sgc_outer = n_string_book(n_string_creat(&outer), i);
   }
   sgc.sgc_flags = ((fexpm & FEXP_SILENT) != 0); /* a_shexp__glob():a_SILENT */

   slp = NULL;
   if(a_shexp__glob(&sgc, &slp))
      cp = (char*)0x1;
   else
      cp = NULL;

   n_string_gut(&outer);

   if(cp == NULL)
      goto jleave;

   if(slp == NULL){
      cp = UNCONST(char*,N_("File pattern does not match"));
      goto jerr;
   }else if(slp->sl_next == NULL)
      cp = savestrbuf(slp->sl_dat, slp->sl_len);
   else if(fexpm & FEXP_MULTIOK){
      struct n_strlist **sorta, *xslp;
      uz i, no, l;

      no = l = 0;
      for(xslp = slp; xslp != NULL; xslp = xslp->sl_next){
         ++no;
         l += xslp->sl_len + 1;
      }

      sorta = n_lofi_alloc(sizeof(*sorta) * no);

      no = 0;
      for(xslp = slp; xslp != NULL; xslp = xslp->sl_next)
         sorta[no++] = xslp;
      su_sort_shell_vpp(su_S(void const**,sorta), no, &a_shexp__globsort);

      cp = n_autorec_alloc(++l);
      l = 0;
      for(i = 0; i < no; ++i){
         xslp = sorta[i];
         su_mem_copy(&cp[l], xslp->sl_dat, xslp->sl_len);
         l += xslp->sl_len;
         cp[l++] = '\0';
      }
      cp[l] = '\0';

      /*n_lofi_free(sorta);*/
      n_pstate |= n_PS_EXPAND_MULTIRESULT;
   }else{
      cp = UNCONST(char*,N_("File pattern matches multiple results"));
      goto jerr;
   }

jleave:
   n_lofi_snap_unroll(lofi_snap);
   NYD_OU;
   return cp;

jerr:
   if(!(fexpm & FEXP_SILENT)){
      name = n_shexp_quote_cp(name, FAL0);
      n_err("%s: %s\n", V_(cp), name);
   }
   cp = NULL;
   goto jleave;

#else /* mx_HAVE_FNMATCH */
   UNUSED(fexpm);

   if(!(fexpm & FEXP_SILENT))
      n_err(_("No filename pattern support (fnmatch(3) not available)\n"));
   return savestr(name);
#endif
}

#ifdef mx_HAVE_FNMATCH
static boole
a_shexp__glob(struct a_shexp_glob_ctx *sgcp, struct n_strlist **slpp){
   /* a_SILENT == a_shexp_globname():((fexpm & FEXP_SILENT) != 0) */
   enum{a_SILENT = 1<<0, a_DEEP=1<<1};

   struct a_shexp_glob_ctx nsgc;
   struct a_shexp_glob_one_ctx sgoc;
   struct dirent *dep;
   DIR *dp;
   char const *ccp, *myp;
   NYD2_IN;

   /* We need some special treatment for the outermost level.
    * All along our way, normalize path separators */
   if(!(sgcp->sgc_flags & a_DEEP)){
      if(sgcp->sgc_patlen > 0 && sgcp->sgc_patdat[0] == '/'){
         myp = n_string_cp(n_string_push_c(sgcp->sgc_outer, '/'));
         do
            ++sgcp->sgc_patdat;
         while(--sgcp->sgc_patlen > 0 && sgcp->sgc_patdat[0] == '/');
      }else
         myp = "./";
   }else
      myp = n_string_cp(sgcp->sgc_outer);

   sgoc.sgoc_sgcp = sgcp;
   sgoc.sgoc_new_sgcp = &nsgc;
   sgoc.sgoc_slpp = slpp;
   sgoc.sgoc_old_outer_len = sgcp->sgc_outer->s_len;

   /* Separate current directory/pattern level from any possible remaining
    * pattern in order to be able to use it for fnmatch(3) */
   if((ccp = su_mem_find(sgcp->sgc_patdat, '/', sgcp->sgc_patlen)) == NULL)
      nsgc.sgc_patlen = 0;
   else{
      nsgc = *sgcp;
      nsgc.sgc_flags |= a_DEEP;
      sgcp->sgc_patlen = P2UZ((nsgc.sgc_patdat = &ccp[1]) -
            &sgcp->sgc_patdat[0]);
      nsgc.sgc_patlen -= sgcp->sgc_patlen;

      /* Trim solidus, everywhere */
      if(sgcp->sgc_patlen > 0){
         ASSERT(sgcp->sgc_patdat[sgcp->sgc_patlen -1] == '/');
         UNCONST(char*,sgcp->sgc_patdat)[--sgcp->sgc_patlen] = '\0';
      }
      while(nsgc.sgc_patlen > 0 && nsgc.sgc_patdat[0] == '/'){
         --nsgc.sgc_patlen;
         ++nsgc.sgc_patdat;
      }
   }

   /* Quickshot: cannot be a fnmatch(3) pattern? */
   if(sgcp->sgc_patlen == 0 ||
         su_cs_first_of(sgcp->sgc_patdat, "?*[") == su_UZ_MAX){
      dp = NULL;
      sgoc.sgoc_dt_type = -1;
      sgoc.sgoc_name = sgcp->sgc_patdat;
      if((ccp = a_shexp__glob_one(&sgoc)) == su_NIL ||
            ccp == R(char*,-1))
         goto jleave;
      goto jerr;
   }

   if((dp = opendir(myp)) == NULL){
      int err;

      switch((err = su_err_no())){
      case su_ERR_NOTDIR:
         ccp = N_("cannot access paths under non-directory");
         goto jerr;
      case su_ERR_NOENT:
         ccp = N_("path component of (sub)pattern non-existent");
         goto jerr;
      case su_ERR_NFILE:
      case su_ERR_MFILE:
         ccp = N_("file descriptor limit reached, cannot open directory");
         goto jerr;
      case su_ERR_ACCES:
         /* Special case: an intermediate directory may not be read, but we
          * possibly could dive into it? */
         if(sgcp->sgc_patlen > 0 &&
               su_cs_first_of(sgcp->sgc_patdat, "?*[") == su_UZ_MAX){
            sgoc.sgoc_dt_type = -1;
            sgoc.sgoc_name = sgcp->sgc_patdat;
            if((ccp = a_shexp__glob_one(&sgoc)) == su_NIL ||
                  ccp == R(char*,-1))
               goto jleave;
            goto jerr;
         }
         ccp = N_("file permission for file (sub)pattern denied");
         goto jerr;
      default:
         ccp = N_("cannot open path component as directory");
         goto jerr;
      }
   }

   /* As necessary, quote bytes in the current pattern TODO This will not
    * TODO truly work out in case the user would try to quote a character
    * TODO class, for example: in "\[a-z]" the "\" would be doubled!  For that
    * TODO to work out, we need the original user input or the shell-expression
    * TODO parse tree, otherwise we do not know what is desired! */
   /* C99 */{
      char *ncp;
      uz i;
      boole need;

      for(need = FAL0, i = 0, myp = sgcp->sgc_patdat; *myp != '\0'; ++myp)
         switch(*myp){
         case '\'': case '"': case '\\': case '$':
         case ' ': case '\t':
            need = TRU1;
            ++i;
            /* FALLTHRU */
         default:
            ++i;
            break;
         }

      if(need){
         ncp = n_lofi_alloc(i +1);
         for(i = 0, myp = sgcp->sgc_patdat; *myp != '\0'; ++myp)
            switch(*myp){
            case '\'': case '"': case '\\': case '$':
            case ' ': case '\t':
               ncp[i++] = '\\';
               /* FALLTHRU */
            default:
               ncp[i++] = *myp;
               break;
            }
         ncp[i] = '\0';
         myp = ncp;
      }else
         myp = sgcp->sgc_patdat;
   }

   while((dep = readdir(dp)) != su_NIL){
      switch(fnmatch(myp, dep->d_name, FNM_PATHNAME | FNM_PERIOD)){
      case 0:
         sgoc.sgoc_dt_type =
#ifdef mx_HAVE_DIRENT_TYPE
               dep->d_type
#else
               -1
#endif
               ;
         sgoc.sgoc_name = dep->d_name;
         if((ccp = a_shexp__glob_one(&sgoc)) != su_NIL){
            if(ccp == R(char*,-1))
               goto jleave;
            goto jerr;
         }
         break;
      case FNM_NOMATCH:
         break;
      default:
         ccp = N_("fnmatch(3) cannot handle file (sub)pattern");
         goto jerr;
      }
   }

   ccp = NULL;
jleave:
   if(dp != NULL)
      closedir(dp);
   NYD2_OU;
   return (ccp == NULL);

jerr:
   if(!(sgcp->sgc_flags & a_SILENT)){
      char const *s2, *s3;

      if(sgcp->sgc_outer->s_len > 0){
         s2 = n_shexp_quote_cp(n_string_cp(sgcp->sgc_outer), FAL0);
         s3 = "/";
      }else
         s2 = s3 = n_empty;

      n_err("%s: %s%s%s\n", V_(ccp), s2, s3,
         n_shexp_quote_cp(sgcp->sgc_patdat, FAL0));
   }
   goto jleave;
}

static char const *
a_shexp__glob_one(struct a_shexp_glob_one_ctx *sgocp){
   char const *rv;
   struct n_string *ousp;
   NYD2_IN;

   ousp = sgocp->sgoc_sgcp->sgc_outer;

   /* A match expresses the desire to recurse if there is more pattern */
   if(sgocp->sgoc_new_sgcp->sgc_patlen > 0){
      boole isdir;

      if(ousp->s_len > 0 && (ousp->s_len > 1 || ousp->s_dat[0] != '/'))
         ousp = n_string_push_c(ousp, '/');
      n_string_push_cp(ousp, sgocp->sgoc_name);

      isdir = FAL0;
      if(sgocp->sgoc_dt_type == -1)
#ifdef mx_HAVE_DIRENT_TYPE
Jstat:
#endif
      {
         struct stat sb;

         if(stat(n_string_cp(ousp), &sb)){
            rv = N_("I/O error when querying file status");
            goto jleave;
         }else if(S_ISDIR(sb.st_mode))
            isdir = TRU1;
      }
#ifdef mx_HAVE_DIRENT_TYPE
      else if(sgocp->sgoc_dt_type == DT_DIR)
         isdir = TRU1;
      else if(sgocp->sgoc_dt_type == DT_LNK ||
            sgocp->sgoc_dt_type == DT_UNKNOWN)
         goto Jstat;
#endif

      /* TODO Recurse with current dir FD open, which could E[MN]FILE!
       * TODO Instead save away a list of such n_string's for later */
      if(isdir && !a_shexp__glob(sgocp->sgoc_new_sgcp, sgocp->sgoc_slpp))
         rv = R(char*,-1);
      else{
         n_string_trunc(ousp, sgocp->sgoc_old_outer_len);
         rv = su_NIL;
      }
   }else{
      struct n_strlist *slp;
      uz i, j;

      i = su_cs_len(sgocp->sgoc_name);
      j = (sgocp->sgoc_old_outer_len > 0)
            ? sgocp->sgoc_old_outer_len + 1 + i : i;
      slp = n_STRLIST_LOFI_ALLOC(j);
      *sgocp->sgoc_slpp = slp;
      sgocp->sgoc_slpp = &slp->sl_next;
      slp->sl_next = NULL;
      if((j = sgocp->sgoc_old_outer_len) > 0){
         su_mem_copy(&slp->sl_dat[0], ousp->s_dat, j);
         if(slp->sl_dat[j -1] != '/')
            slp->sl_dat[j++] = '/';
      }
      su_mem_copy(&slp->sl_dat[j], sgocp->sgoc_name, i);
      slp->sl_dat[j += i] = '\0';
      slp->sl_len = j;
      rv = su_NIL;
   }

jleave:
   NYD2_OU;
   return rv;
}

static su_sz
a_shexp__globsort(void const *cvpa, void const *cvpb){
   su_sz rv;
   struct n_strlist const *slpa, *slpb;
   NYD2_IN;

   slpa = cvpa;
   slpb = cvpb;
   rv = su_cs_cmp_case(slpa->sl_dat, slpb->sl_dat);
   NYD2_OU;
   return rv;
}
#endif /* mx_HAVE_FNMATCH */

static void
a_shexp__quote(struct a_shexp_quote_ctx *sqcp, struct a_shexp_quote_lvl *sqlp){
   /* XXX Because of the problems caused by ISO C multibyte interface we cannot
    * XXX use the recursive implementation because of stateful encodings.
    * XXX I.e., if a quoted substring cannot be self-contained - the data after
    * XXX the quote relies on "the former state", then this doesn't make sense.
    * XXX Therefore this is not fully programmed out but instead only detects
    * XXX the "most fancy" quoting necessary, and directly does that.
    * XXX As a result of this, T_REVSOL and T_DOUBLE are not even considered.
    * XXX Otherwise we rather have to convert to wide first and act on that,
    * XXX e.g., call visual_info(n_VISUAL_INFO_WOUT_CREATE) on entire input */
#undef a_SHEXP_QUOTE_RECURSE /* XXX (Needs complete revisit, then) */
#ifdef a_SHEXP_QUOTE_RECURSE
# define jrecurse jrecurse
   struct a_shexp_quote_lvl sql;
#else
# define jrecurse jstep
#endif
   struct n_visual_info_ctx vic;
   union {struct a_shexp_quote_lvl *head; struct n_string *store;} u;
   u32 flags;
   uz il;
   char const *ib, *ib_base;
   NYD2_IN;

   ib_base = ib = sqlp->sql_dat.s;
   il = sqlp->sql_dat.l;
   flags = sqlp->sql_flags;

   /* Iterate over the entire input, classify characters and type of quotes
    * along the way.  Whenever a quote change has to be applied, adjust flags
    * for the new situation -, setup sql.* and recurse- */
   while(il > 0){
      char c;

      c = *ib;
      if(su_cs_is_cntrl(c)){
         if(flags & a_SHEXP_QUOTE_T_DOLLAR)
            goto jstep;
         if(c == '\t' && (flags & (a_SHEXP_QUOTE_T_REVSOL |
               a_SHEXP_QUOTE_T_SINGLE | a_SHEXP_QUOTE_T_DOUBLE)))
            goto jstep;
#ifdef a_SHEXP_QUOTE_RECURSE
         ++sqcp->sqc_cnt_dollar;
#endif
         flags = (flags & ~a_SHEXP_QUOTE_T_MASK) | a_SHEXP_QUOTE_T_DOLLAR;
         goto jrecurse;
      }else if(su_cs_is_space(c) || c == '|' || c == '&' || c == ';' ||
            /* Whereas we don't support those, quote them for the sh(1)ell */
            c == '(' || c == ')' || c == '<' || c == '>' ||
            c == '"' || c == '$'){
         if(flags & a_SHEXP_QUOTE_T_MASK)
            goto jstep;
#ifdef a_SHEXP_QUOTE_RECURSE
         ++sqcp->sqc_cnt_single;
#endif
         flags = (flags & ~a_SHEXP_QUOTE_T_MASK) | a_SHEXP_QUOTE_T_SINGLE;
         goto jrecurse;
      }else if(c == '\''){
         if(flags & (a_SHEXP_QUOTE_T_MASK & ~a_SHEXP_QUOTE_T_SINGLE))
            goto jstep;
#ifdef a_SHEXP_QUOTE_RECURSE
         ++sqcp->sqc_cnt_dollar;
#endif
         flags = (flags & ~a_SHEXP_QUOTE_T_MASK) | a_SHEXP_QUOTE_T_DOLLAR;
         goto jrecurse;
      }else if(c == '\\' || (c == '#' && ib == ib_base)){
         if(flags & a_SHEXP_QUOTE_T_MASK)
            goto jstep;
#ifdef a_SHEXP_QUOTE_RECURSE
         ++sqcp->sqc_cnt_single;
#endif
         flags = (flags & ~a_SHEXP_QUOTE_T_MASK) | a_SHEXP_QUOTE_T_SINGLE;
         goto jrecurse;
      }else if(!su_cs_is_ascii(c)){
         /* Need to keep together multibytes */
#ifdef a_SHEXP_QUOTE_RECURSE
         su_mem_set(&vic, 0, sizeof vic);
         vic.vic_indat = ib;
         vic.vic_inlen = il;
         n_visual_info(&vic,
            n_VISUAL_INFO_ONE_CHAR | n_VISUAL_INFO_SKIP_ERRORS);
#endif
         /* xxx check whether resulting \u would be ASCII */
         if(!(flags & a_SHEXP_QUOTE_ROUNDTRIP) ||
               (flags & a_SHEXP_QUOTE_T_DOLLAR)){
#ifdef a_SHEXP_QUOTE_RECURSE
            ib = vic.vic_oudat;
            il = vic.vic_oulen;
            continue;
#else
            goto jstep;
#endif
         }
#ifdef a_SHEXP_QUOTE_RECURSE
         ++sqcp->sqc_cnt_dollar;
#endif
         flags = (flags & ~a_SHEXP_QUOTE_T_MASK) | a_SHEXP_QUOTE_T_DOLLAR;
         goto jrecurse;
      }else
jstep:
         ++ib, --il;
   }
   sqlp->sql_flags = flags;

   /* Level made the great and completed processing input.  Reverse the list of
    * levels, detect the "most fancy" quote type needed along this way */
   /* XXX Due to restriction as above very crude */
   for(flags = 0, il = 0, u.head = NULL; sqlp != NULL;){
      struct a_shexp_quote_lvl *tmp;

      tmp = sqlp->sql_link;
      sqlp->sql_link = u.head;
      u.head = sqlp;
      il += sqlp->sql_dat.l;
      if(sqlp->sql_flags & a_SHEXP_QUOTE_T_MASK)
         il += (sqlp->sql_dat.l >> 1);
      flags |= sqlp->sql_flags;
      sqlp = tmp;
   }
   sqlp = u.head;

   /* Finally work the substrings in the correct order, adjusting quotes along
    * the way as necessary.  Start off with the "most fancy" quote, so that
    * the user sees an overall boundary she can orientate herself on.
    * We do it like that to be able to give the user some "encapsulation
    * experience", to address what strikes me is a problem of sh(1)ell quoting:
    * different to, e.g., perl(1), where you see at a glance where a string
    * starts and ends, sh(1) quoting occurs at the "top level", disrupting the
    * visual appearance of "a string" as such */
   u.store = n_string_reserve(sqcp->sqc_store, il);

   if(flags & a_SHEXP_QUOTE_T_DOLLAR){
      u.store = n_string_push_buf(u.store, "$'", sizeof("$'") -1);
      flags = (flags & ~a_SHEXP_QUOTE_T_MASK) | a_SHEXP_QUOTE_T_DOLLAR;
   }else if(flags & a_SHEXP_QUOTE_T_DOUBLE){
      u.store = n_string_push_c(u.store, '"');
      flags = (flags & ~a_SHEXP_QUOTE_T_MASK) | a_SHEXP_QUOTE_T_DOUBLE;
   }else if(flags & a_SHEXP_QUOTE_T_SINGLE){
      u.store = n_string_push_c(u.store, '\'');
      flags = (flags & ~a_SHEXP_QUOTE_T_MASK) | a_SHEXP_QUOTE_T_SINGLE;
   }else /*if(flags & a_SHEXP_QUOTE_T_REVSOL)*/
      flags &= ~a_SHEXP_QUOTE_T_MASK;

   /* Work all the levels */
   for(; sqlp != NULL; sqlp = sqlp->sql_link){
      /* As necessary update our mode of quoting */
#ifdef a_SHEXP_QUOTE_RECURSE
      il = 0;

      switch(sqlp->sql_flags & a_SHEXP_QUOTE_T_MASK){
      case a_SHEXP_QUOTE_T_DOLLAR:
         if(!(flags & a_SHEXP_QUOTE_T_DOLLAR))
            il = a_SHEXP_QUOTE_T_DOLLAR;
         break;
      case a_SHEXP_QUOTE_T_DOUBLE:
         if(!(flags & (a_SHEXP_QUOTE_T_DOUBLE | a_SHEXP_QUOTE_T_DOLLAR)))
            il = a_SHEXP_QUOTE_T_DOLLAR;
         break;
      case a_SHEXP_QUOTE_T_SINGLE:
         if(!(flags & (a_SHEXP_QUOTE_T_REVSOL | a_SHEXP_QUOTE_T_SINGLE |
               a_SHEXP_QUOTE_T_DOUBLE | a_SHEXP_QUOTE_T_DOLLAR)))
            il = a_SHEXP_QUOTE_T_SINGLE;
         break;
      default:
      case a_SHEXP_QUOTE_T_REVSOL:
         if(!(flags & (a_SHEXP_QUOTE_T_REVSOL | a_SHEXP_QUOTE_T_SINGLE |
               a_SHEXP_QUOTE_T_DOUBLE | a_SHEXP_QUOTE_T_DOLLAR)))
            il = a_SHEXP_QUOTE_T_REVSOL;
         break;
      }

      if(il != 0){
         if(flags & (a_SHEXP_QUOTE_T_SINGLE | a_SHEXP_QUOTE_T_DOLLAR))
            u.store = n_string_push_c(u.store, '\'');
         else if(flags & a_SHEXP_QUOTE_T_DOUBLE)
            u.store = n_string_push_c(u.store, '"');
         flags &= ~a_SHEXP_QUOTE_T_MASK;

         flags |= (u32)il;
         if(flags & a_SHEXP_QUOTE_T_DOLLAR)
            u.store = n_string_push_buf(u.store, "$'", sizeof("$'") -1);
         else if(flags & a_SHEXP_QUOTE_T_DOUBLE)
            u.store = n_string_push_c(u.store, '"');
         else if(flags & a_SHEXP_QUOTE_T_SINGLE)
            u.store = n_string_push_c(u.store, '\'');
      }
#endif /* a_SHEXP_QUOTE_RECURSE */

      /* Work the level's substring */
      ib = sqlp->sql_dat.s;
      il = sqlp->sql_dat.l;

      while(il > 0){
         char c2, c;

         c = *ib;

         if(su_cs_is_cntrl(c)){
            ASSERT(c == '\t' || (flags & a_SHEXP_QUOTE_T_DOLLAR));
            ASSERT((flags & (a_SHEXP_QUOTE_T_REVSOL | a_SHEXP_QUOTE_T_SINGLE |
               a_SHEXP_QUOTE_T_DOUBLE | a_SHEXP_QUOTE_T_DOLLAR)));
            switch((c2 = c)){
            case 0x07: c = 'a'; break;
            case 0x08: c = 'b'; break;
            case 0x0A: c = 'n'; break;
            case 0x0B: c = 'v'; break;
            case 0x0C: c = 'f'; break;
            case 0x0D: c = 'r'; break;
            case 0x1B: c = 'E'; break;
            default: break;
            case 0x09:
               if(flags & a_SHEXP_QUOTE_T_DOLLAR){
                  c = 't';
                  break;
               }
               if(flags & a_SHEXP_QUOTE_T_REVSOL)
                  u.store = n_string_push_c(u.store, '\\');
               goto jpush;
            }
            u.store = n_string_push_c(u.store, '\\');
            if(c == c2){
               u.store = n_string_push_c(u.store, 'c');
               c ^= 0x40;
            }
            goto jpush;
         }else if(su_cs_is_space(c) || c == '|' || c == '&' || c == ';' ||
               /* Whereas we do not support those, quote them for sh(1)ell */
               c == '(' || c == ')' || c == '<' || c == '>' ||
               c == '"' || c == '$'){
            if(flags & (a_SHEXP_QUOTE_T_SINGLE | a_SHEXP_QUOTE_T_DOLLAR))
               goto jpush;
            ASSERT(flags & (a_SHEXP_QUOTE_T_REVSOL | a_SHEXP_QUOTE_T_DOUBLE));
            u.store = n_string_push_c(u.store, '\\');
            goto jpush;
         }else if(c == '\''){
            if(flags & a_SHEXP_QUOTE_T_DOUBLE)
               goto jpush;
            ASSERT(!(flags & a_SHEXP_QUOTE_T_SINGLE));
            u.store = n_string_push_c(u.store, '\\');
            goto jpush;
         }else if(c == '\\' || (c == '#' && ib == ib_base)){
            if(flags & a_SHEXP_QUOTE_T_SINGLE)
               goto jpush;
            ASSERT(flags & (a_SHEXP_QUOTE_T_REVSOL | a_SHEXP_QUOTE_T_DOUBLE |
               a_SHEXP_QUOTE_T_DOLLAR));
            u.store = n_string_push_c(u.store, '\\');
            goto jpush;
         }else if(su_cs_is_ascii(c)){
            /* Shorthand: we can simply push that thing out */
jpush:
            u.store = n_string_push_c(u.store, c);
            ++ib, --il;
         }else{
            /* Not an ASCII character, take care not to split up multibyte
             * sequences etc.  For the sake of compile testing, don't enwrap in
             * mx_HAVE_ALWAYS_UNICODE_LOCALE || mx_HAVE_NATCH_CHAR */
            if(n_psonce & n_PSO_UNICODE){
               u32 unic;
               char const *ib2;
               uz il2, il3;

               ib2 = ib;
               il2 = il;
               if((unic = su_utf8_to_32(&ib2, &il2)) != U32_MAX){
                  char itoa[32];
                  char const *cp;

                  il2 = P2UZ(&ib2[0] - &ib[0]);
                  if((flags & a_SHEXP_QUOTE_ROUNDTRIP) || unic == 0xFFFDu){
                     /* Use padding to make ambiguities impossible */
                     il3 = snprintf(itoa, sizeof itoa, "\\%c%0*X",
                           (unic > 0xFFFFu ? 'U' : 'u'),
                           (int)(unic > 0xFFFFu ? 8 : 4), unic);
                     cp = itoa;
                  }else{
                     il3 = il2;
                     cp = &ib[0];
                  }
                  u.store = n_string_push_buf(u.store, cp, il3);
                  ib += il2, il -= il2;
                  continue;
               }
            }

            su_mem_set(&vic, 0, sizeof vic);
            vic.vic_indat = ib;
            vic.vic_inlen = il;
            n_visual_info(&vic,
               n_VISUAL_INFO_ONE_CHAR | n_VISUAL_INFO_SKIP_ERRORS);

            /* Work this substring as sensitive as possible */
            il -= vic.vic_oulen;
            if(!(flags & a_SHEXP_QUOTE_ROUNDTRIP))
               u.store = n_string_push_buf(u.store, ib, il);
#ifdef mx_HAVE_ICONV
            else if((vic.vic_indat = n_iconv_onetime_cp(n_ICONV_NONE,
                  "utf-8", ok_vlook(ttycharset), savestrbuf(ib, il))) != NULL){
               u32 unic;
               char const *ib2;
               uz il2, il3;

               il2 = su_cs_len(ib2 = vic.vic_indat);
               if((unic = su_utf8_to_32(&ib2, &il2)) != U32_MAX){
                  char itoa[32];

                  il2 = P2UZ(&ib2[0] - &vic.vic_indat[0]);
                  /* Use padding to make ambiguities impossible */
                  il3 = snprintf(itoa, sizeof itoa, "\\%c%0*X",
                        (unic > 0xFFFFu ? 'U' : 'u'),
                        (int)(unic > 0xFFFFu ? 8 : 4), unic);
                  u.store = n_string_push_buf(u.store, itoa, il3);
               }else
                  goto Jxseq;
            }
#endif
            else
#ifdef mx_HAVE_ICONV
                 Jxseq:
#endif
                        while(il-- > 0){
               u.store = n_string_push_buf(u.store, "\\xFF",
                     sizeof("\\xFF") -1);
               n_c_to_hex_base16(&u.store->s_dat[u.store->s_len - 2], *ib++);
            }

            ib = vic.vic_oudat;
            il = vic.vic_oulen;
         }
      }
   }

   /* Close an open quote */
   if(flags & (a_SHEXP_QUOTE_T_SINGLE | a_SHEXP_QUOTE_T_DOLLAR))
      u.store = n_string_push_c(u.store, '\'');
   else if(flags & a_SHEXP_QUOTE_T_DOUBLE)
      u.store = n_string_push_c(u.store, '"');
#ifdef a_SHEXP_QUOTE_RECURSE
jleave:
#endif
   NYD2_OU;
   return;

#ifdef a_SHEXP_QUOTE_RECURSE
jrecurse:
   sqlp->sql_dat.l -= il;

   sql.sql_link = sqlp;
   sql.sql_dat.s = UNCONST(char*,ib);
   sql.sql_dat.l = il;
   sql.sql_flags = flags;
   a_shexp__quote(sqcp, &sql);
   goto jleave;
#endif

#undef jrecurse
#undef a_SHEXP_QUOTE_RECURSE
}

FL char *
fexpand(char const *name, enum fexp_mode fexpm) /* TODO in parts: -> URL::!! */
{
   struct str proto, s;
   char const *res, *cp;
   boole dyn, haveproto;
   NYD_IN;

   n_pstate &= ~n_PS_EXPAND_MULTIRESULT;
   dyn = FAL0;

   /* The order of evaluation is "%" and "#" expand into constants.
    * "&" can expand into "+".  "+" can expand into shell meta characters.
    * Shell meta characters expand into constants.
    * This way, we make no recursive expansion */
   if((fexpm & FEXP_NSHORTCUT) || (res = mx_shortcut_expand(name)) == NULL)
      res = name;

jprotonext:
   UNINIT(proto.s, NULL), UNINIT(proto.l, 0);
   haveproto = FAL0;
   for(cp = res; *cp && *cp != ':'; ++cp)
      if(!su_cs_is_alnum(*cp))
         goto jnoproto;
   if(cp[0] == ':' && cp[1] == '/' && cp[2] == '/'){
      haveproto = TRU1;
      proto.s = UNCONST(char*,res);
      cp += 3;
      proto.l = P2UZ(cp - res);
      res = cp;
   }

jnoproto:
   if(!(fexpm & FEXP_NSPECIAL)){
jnext:
      dyn = FAL0;
      switch(*res){
      case '%':
         if(res[1] == ':' && res[2] != '\0'){
            res = &res[2];
            goto jprotonext;
         }else{
            boole force;

            force = (res[1] != '\0');
            res = a_shexp_findmail((force ? &res[1] : ok_vlook(LOGNAME)),
                  force);
            if(force)
               goto jislocal;
         }
         goto jnext;
      case '#':
         if (res[1] != '\0')
            break;
         if (prevfile[0] == '\0') {
            n_err(_("No previous file\n"));
            res = NULL;
            goto jleave;
         }
         res = prevfile;
         goto jislocal;
      case '&':
         if (res[1] == '\0')
            res = ok_vlook(MBOX);
         break;
      default:
         break;
      }
   }

#ifdef mx_HAVE_IMAP
   if(res[0] == '@' && which_protocol(mailname, FAL0, FAL0, NULL)
         == PROTO_IMAP){
      res = str_concat_csvl(&s, protbase(mailname), "/", &res[1], NULL)->s;
      dyn = TRU1;
   }
#endif

   /* POSIX: if *folder* unset or null, "+" shall be retained */
   if(!(fexpm & FEXP_NFOLDER) && *res == '+' &&
         *(cp = n_folder_query()) != '\0'){
      res = str_concat_csvl(&s, cp, &res[1], NULL)->s;
      dyn = TRU1;
   }

   /* Do some meta expansions */
   if((fexpm & (FEXP_NSHELL | FEXP_NVAR)) != FEXP_NVAR &&
         ((fexpm & FEXP_NSHELL) ? (su_cs_find_c(res, '$') != NULL)
          : (su_cs_first_of(res, "{}[]*?$") != su_UZ_MAX))){
      boole doexp;

      if(fexpm & FEXP_NOPROTO)
         doexp = TRU1;
      else{
         cp = haveproto ? savecat(savestrbuf(proto.s, proto.l), res) : res;

         switch(which_protocol(cp, TRU1, FAL0, NULL)){
         case PROTO_FILE:
         case PROTO_MAILDIR:
            doexp = TRU1;
            break;
         default:
            doexp = FAL0;
            break;
         }
      }

      if(doexp){
         struct str shin;
         struct n_string shou, *shoup;

         shin.s = UNCONST(char*,res);
         shin.l = UZ_MAX;
         shoup = n_string_creat_auto(&shou);
         for(;;){
            enum n_shexp_state shs;

            /* TODO shexp: take care: not include backtick eval once avail! */
            shs = n_shexp_parse_token((n_SHEXP_PARSE_LOG_D_V |
                  n_SHEXP_PARSE_QUOTE_AUTO_FIXED |
                  n_SHEXP_PARSE_QUOTE_AUTO_DQ |
                  n_SHEXP_PARSE_QUOTE_AUTO_CLOSE), shoup, &shin, NULL);
            if(shs & n_SHEXP_STATE_STOP)
               break;
         }
         res = n_string_cp(shoup);
         /*shoup = n_string_drop_ownership(shoup);*/
         dyn = TRU1;

         if(res[0] == '~')
            res = a_shexp_tilde(res);

         if(!(fexpm & FEXP_NSHELL) &&
               (res = a_shexp_globname(res, fexpm)) == NULL)
            goto jleave;
         dyn = TRU1;
      }/* else no tilde */
   }else if(res[0] == '~'){
      res = a_shexp_tilde(res);
      dyn = TRU1;
   }

jislocal:
   if(res != NULL && haveproto){
      res = savecat(savestrbuf(proto.s, proto.l), res);
      dyn = TRU1;
   }

   if(fexpm & (FEXP_LOCAL | FEXP_LOCAL_FILE)){
      switch (which_protocol(res, FAL0, FAL0, &cp)) {
      case PROTO_MAILDIR:
         if(!(fexpm & FEXP_LOCAL_FILE)){
         /* FALLTHRU */
      case PROTO_FILE:
            if(fexpm & FEXP_LOCAL_FILE){
               res = cp;
               dyn = FAL0;
            }
            break;
         }
         /* FALLTHRU */
      default:
         n_err(_("Not a local file or directory: %s\n"),
            n_shexp_quote_cp(name, FAL0));
         res = NULL;
         break;
      }
   }

jleave:
   if(res != NULL && !dyn)
      res = savestr(res);
   NYD_OU;
   return UNCONST(char*,res);
}

FL enum n_shexp_state
n_shexp_parse_token(enum n_shexp_parse_flags flags, struct n_string *store,
      struct str *input, void const **cookie){
   /* TODO shexp_parse_token: WCHAR
    * TODO This needs to be rewritten in order to support $(( )) and $( )
    * TODO and ${xyYZ} and the possibly infinite recursion they bring along,
    * TODO too.  We need a carrier struct, then, and can nicely split this
    * TODO big big thing up in little pieces!
    * TODO This means it should produce a tree of objects, so that callees
    * TODO can recognize whether something happened inside single/double etc.
    * TODO quotes; e.g., to requote "'[a-z]'" to, e.g., "\[a-z]", etc.!
    * TODO Also, it should be possible to "yield" this, e.g., like this
    * TODO we would not need to be wired to variable handling for positional
    * TODO parameters, instead these should be fields of the carrier, and
    * TODO once we need them we should yield saying we need them, and if
    * TODO we are reentered we simply access the fields directly.
    * TODO That is: do that, also for normal variables: like this the shell
    * TODO expression parser can be made entirely generic and placed in SU! */
   u32 last_known_meta_trim_len;
   char c2, c, quotec, utf[8];
   enum n_shexp_state rv;
   uz i, il;
   char const *ifs, *ifs_ws, *ib_save, *ib;
   enum{
      a_NONE = 0,
      a_SKIPQ = 1u<<0,     /* Skip rest of this quote (\u0 ..) */
      a_SKIPT = 1u<<1,     /* Skip entire token (\c@) */
      a_SKIPMASK = a_SKIPQ | a_SKIPT,
      a_SURPLUS = 1u<<2,   /* Extended sequence interpretation */
      a_NTOKEN = 1u<<3,    /* "New token": e.g., comments are possible */
      a_BRACE = 1u<<4,     /* Variable substitution: brace enclosed */
      a_DIGIT1 = 1u<<5,    /* ..first character was digit */
      a_NONDIGIT = 1u<<6,  /* ..has seen any non-digits */
      a_VARSUBST_MASK = su_BITENUM_MASK(4, 6),

      a_ROUND_MASK = a_SKIPT | (int)~su_BITENUM_MASK(0, 7),
      a_COOKIE = 1u<<8,
      a_EXPLODE = 1u<<9,
      /* Remove one more byte from the input after pushing data to output */
      a_CHOP_ONE = 1u<<10,
      a_TMP = 1u<<30
   } state;
   NYD2_IN;

   ASSERT((flags & n_SHEXP_PARSE_DRYRUN) || store != NULL);
   ASSERT(input != NULL);
   ASSERT(input->l == 0 || input->s != NULL);
   ASSERT(!(flags & n_SHEXP_PARSE_LOG) || !(flags & n_SHEXP_PARSE_LOG_D_V));
   ASSERT(!(flags & n_SHEXP_PARSE_IFS_ADD_COMMA) ||
      !(flags & n_SHEXP_PARSE_IFS_IS_COMMA));
   ASSERT(!(flags & n_SHEXP_PARSE_QUOTE_AUTO_FIXED) ||
      (flags & n__SHEXP_PARSE_QUOTE_AUTO_MASK));

   if((flags & n_SHEXP_PARSE_LOG_D_V) && (n_poption & n_PO_D_V))
      flags |= n_SHEXP_PARSE_LOG;
   if(flags & n_SHEXP_PARSE_QUOTE_AUTO_FIXED)
      flags |= n_SHEXP_PARSE_QUOTE_AUTO_CLOSE;

   if((flags & n_SHEXP_PARSE_TRUNC) && store != NULL)
      store = n_string_trunc(store, 0);

   if(flags & (n_SHEXP_PARSE_IFS_VAR | n_SHEXP_PARSE_TRIM_IFSSPACE)){
      ifs = ok_vlook(ifs);
      ifs_ws = ok_vlook(ifs_ws);
   }else{
      UNINIT(ifs, n_empty);
      UNINIT(ifs_ws, n_empty);
   }

   state = a_NONE;
   ib = input->s;
   if((il = input->l) == UZ_MAX)
      input->l = il = su_cs_len(ib);
   UNINIT(c, '\0');

   if(cookie != NULL && *cookie != NULL){
      ASSERT(!(flags & n_SHEXP_PARSE_DRYRUN));
      state |= a_COOKIE;
   }

   rv = n_SHEXP_STATE_NONE;
jrestart_empty:
   rv &= n_SHEXP_STATE_WS_LEAD;
   state &= a_ROUND_MASK;

   /* In cookie mode, the next ARGV entry is the token already, unchanged,
    * since it has already been expanded before! */
   if(state & a_COOKIE){
      char const * const *xcookie, *cp;

      i = store->s_len;
      xcookie = *cookie;
      if((store = n_string_push_cp(store, *xcookie))->s_len > 0)
         rv |= n_SHEXP_STATE_OUTPUT;
      if(*++xcookie == NULL){
         *cookie = NULL;
         state &= ~a_COOKIE;
         flags |= n_SHEXP_PARSE_QUOTE_AUTO_DQ; /* ..why we are here! */
      }else
         *cookie = UNCONST(void*,xcookie);

      for(cp = &n_string_cp(store)[i]; (c = *cp++) != '\0';)
         if(su_cs_is_cntrl(c)){
            rv |= n_SHEXP_STATE_CONTROL;
            break;
         }

      /* The last exploded cookie will join with the yielded input token, so
       * simply fall through in this case */
      if(state & a_COOKIE)
         goto jleave_quick;
   }else{
jrestart:
      if(flags & n_SHEXP_PARSE_TRIM_SPACE){
         for(; il > 0; ++ib, --il){
            if(!su_cs_is_space(*ib))
               break;
            rv |= n_SHEXP_STATE_WS_LEAD;
         }
      }

      if(flags & n_SHEXP_PARSE_TRIM_IFSSPACE){
         for(; il > 0; ++ib, --il){
            if(su_cs_find_c(ifs_ws, *ib) == NULL)
               break;
            rv |= n_SHEXP_STATE_WS_LEAD;
         }
      }

      input->s = UNCONST(char*,ib);
      input->l = il;
   }

   if(il == 0){
      rv |= n_SHEXP_STATE_STOP;
      goto jleave;
   }

   if(store != NULL)
      store = n_string_reserve(store, MIN(il, 32)); /* XXX */

   switch(flags & n__SHEXP_PARSE_QUOTE_AUTO_MASK){
   case n_SHEXP_PARSE_QUOTE_AUTO_SQ:
      quotec = '\'';
      rv |= n_SHEXP_STATE_QUOTE;
      break;
   case n_SHEXP_PARSE_QUOTE_AUTO_DQ:
      quotec = '"';
      if(0){
   case n_SHEXP_PARSE_QUOTE_AUTO_DSQ:
         quotec = '\'';
      }
      rv |= n_SHEXP_STATE_QUOTE;
      state |= a_SURPLUS;
      break;
   default:
      quotec = '\0';
      state |= a_NTOKEN;
      break;
   }

   /* TODO n_SHEXP_PARSE_META_SEMICOLON++, well, hack: we are not the shell,
    * TODO we are not a language, and therefore the general *ifs-ws* and normal
    * TODO whitespace trimming that input lines undergo (in a_go_evaluate())
    * TODO has already happened, our result will be used *as is*, and therefore
    * TODO we need to be aware of and remove trailing unquoted WS that would
    * TODO otherwise remain, after we have seen a semicolon sequencer.
    * By sheer luck we only need to track this in non-quote-mode */
   last_known_meta_trim_len = U32_MAX;

   while(il > 0){ /* {{{ */
      --il, c = *ib++;

      /* If no quote-mode active.. */
      if(quotec == '\0'){
         if(c == '"' || c == '\''){
            quotec = c;
            if(c == '"')
               state |= a_SURPLUS;
            else
               state &= ~a_SURPLUS;
            state &= ~a_NTOKEN;
            last_known_meta_trim_len = U32_MAX;
            rv |= n_SHEXP_STATE_QUOTE;
            continue;
         }else if(c == '$'){
            if(il > 0){
               state &= ~a_NTOKEN;
               last_known_meta_trim_len = U32_MAX;
               if(*ib == '\''){
                  --il, ++ib;
                  quotec = '\'';
                  state |= a_SURPLUS;
                  rv |= n_SHEXP_STATE_QUOTE;
                  continue;
               }else
                  goto J_var_expand;
            }
         }else if(c == '\\'){
            /* Outside of quotes this just escapes any next character, but
             * a sole  at EOS is left unchanged */
             if(il > 0)
               --il, c = *ib++;
            state &= ~a_NTOKEN;
            last_known_meta_trim_len = U32_MAX;
         }
         /* A comment may it be if no token has yet started */
         else if(c == '#' && (state & a_NTOKEN)){
            rv |= n_SHEXP_STATE_STOP;
            /*last_known_meta_trim_len = U32_MAX;*/
            goto jleave;
         }
         /* Metacharacters that separate tokens must be turned on explicitly */
         else if(c == '|' && (flags & n_SHEXP_PARSE_META_VERTBAR)){
            rv |= n_SHEXP_STATE_META_VERTBAR;

            /* The parsed sequence may be _the_ output, so ensure we don't
             * include the metacharacter, then. */
            if(flags & (n_SHEXP_PARSE_DRYRUN | n_SHEXP_PARSE_META_KEEP))
               ++il, --ib;
            /*last_known_meta_trim_len = U32_MAX;*/
            break;
         }else if(c == '&' && (flags & n_SHEXP_PARSE_META_AMPERSAND)){
            rv |= n_SHEXP_STATE_META_AMPERSAND;

            /* The parsed sequence may be _the_ output, so ensure we don't
             * include the metacharacter, then. */
            if(flags & (n_SHEXP_PARSE_DRYRUN | n_SHEXP_PARSE_META_KEEP)){
               if(!(flags & n_SHEXP_PARSE_META_KEEP))
                  state |= a_CHOP_ONE;
               ++il, --ib;
            }
            /*last_known_meta_trim_len = U32_MAX;*/
            break;
         }else if(c == ';' && (flags & n_SHEXP_PARSE_META_SEMICOLON)){
            rv |= n_SHEXP_STATE_META_SEMICOLON | n_SHEXP_STATE_STOP;
            if(!(flags & n_SHEXP_PARSE_DRYRUN) &&
                  (rv & n_SHEXP_STATE_OUTPUT) &&
                  last_known_meta_trim_len != U32_MAX)
               store = n_string_trunc(store, last_known_meta_trim_len);

            /* The parsed sequence may be _the_ output, so ensure we don't
             * include the metacharacter, then. */
            /*if(flags & (n_SHEXP_PARSE_DRYRUN | n_SHEXP_PARSE_META_KEEP)){*/
               if(!(flags & n_SHEXP_PARSE_META_KEEP))
                  state |= a_CHOP_ONE;
               ++il, --ib;
          /*  }*/
            /*last_known_meta_trim_len = U32_MAX;*/
            break;
         }else if(c == ',' && (flags &
               (n_SHEXP_PARSE_IFS_ADD_COMMA | n_SHEXP_PARSE_IFS_IS_COMMA))){
            /* The parsed sequence may be _the_ output, so ensure we don't
             * include the metacharacter, then. */
            if(flags & (n_SHEXP_PARSE_DRYRUN | n_SHEXP_PARSE_META_KEEP)){
               if(!(flags & n_SHEXP_PARSE_META_KEEP))
                  state |= a_CHOP_ONE;
               ++il, --ib;
            }
            /*last_known_meta_trim_len = U32_MAX;*/
            break;
         }else{
            u8 blnk;

            blnk = su_cs_is_blank(c) ? 1 : 0;
            blnk |= ((flags & (n_SHEXP_PARSE_IFS_VAR |
                     n_SHEXP_PARSE_TRIM_IFSSPACE)) &&
                  su_cs_find_c(ifs_ws, c) != NULL) ? 2 : 0;

            if((!(flags & n_SHEXP_PARSE_IFS_VAR) && (blnk & 1)) ||
                  ((flags & n_SHEXP_PARSE_IFS_VAR) &&
                     ((blnk & 2) || su_cs_find_c(ifs, c) != NULL))){
               if(!(flags & n_SHEXP_PARSE_IFS_IS_COMMA)){
                  /* The parsed sequence may be _the_ output, so ensure we do
                   * not include the metacharacter, then. */
                  if(flags & (n_SHEXP_PARSE_DRYRUN | n_SHEXP_PARSE_META_KEEP)){
                     if(!(flags & n_SHEXP_PARSE_META_KEEP))
                        state |= a_CHOP_ONE;
                     ++il, --ib;
                  }
                  /*last_known_meta_trim_len = U32_MAX;*/
                  break;
               }
               state |= a_NTOKEN;
            }else
               state &= ~a_NTOKEN;

            if(blnk && store != NULL){
               if(last_known_meta_trim_len == U32_MAX)
                  last_known_meta_trim_len = store->s_len;
            }else
               last_known_meta_trim_len = U32_MAX;
         }
      }else{
         /* Quote-mode */
         ASSERT(!(state & a_NTOKEN));
         if(c == quotec && !(flags & n_SHEXP_PARSE_QUOTE_AUTO_FIXED)){
            state &= a_ROUND_MASK;
            quotec = '\0';
            /* Users may need to recognize the presence of empty quotes */
            rv |= n_SHEXP_STATE_OUTPUT;
            continue;
         }else if(c == '\\' && (state & a_SURPLUS)){
            ib_save = ib - 1;
            /* A sole  at EOS is treated as-is!  This is ok
             * since the "closing quote" error will occur next, anyway */
            if(il == 0)
               ;
            else if((c2 = *ib) == quotec){
               --il, ++ib;
               c = quotec;
            }else if(quotec == '"'){
               /* Double quotes, POSIX says:
                *    The  shall retain its special meaning as an
                *    escape character (see Section 2.2.1) only when followed
                *    by one of the following characters when considered
                *    special: $ ` " \  */
               switch(c2){
               case '$':
               case '`':
               /* case '"': already handled via c2 == quotec */
               case '\\':
                  --il, ++ib;
                  c = c2;
                  /* FALLTHRU */
               default:
                  break;
               }
            }else{
               /* Dollar-single-quote */
               --il, ++ib;
               switch(c2){
               case '"':
               /* case '\'': already handled via c2 == quotec */
               case '\\':
                  c = c2;
                  break;

               case 'b': c = '\b'; break;
               case 'f': c = '\f'; break;
               case 'n': c = '\n'; break;
               case 'r': c = '\r'; break;
               case 't': c = '\t'; break;
               case 'v': c = '\v'; break;

               case 'E':
               case 'e': c = '\033'; break;

               /* Control character */
               case 'c':
                  if(il == 0)
                     goto j_dollar_ungetc;
                  --il, c2 = *ib++;
                  if(state & a_SKIPMASK)
                     continue;
                  /* ASCII C0: 0..1F, 7F <- @.._ (+ a-z -> A-Z), ? */
                  c = su_cs_to_upper(c2) ^ 0x40;
                  if((u8)c > 0x1F && c != 0x7F){
                     if(flags & n_SHEXP_PARSE_LOG)
                        n_err(_("Invalid \\c notation: %.*s: %.*s\n"),
                           (int)input->l, input->s,
                           (int)P2UZ(ib - ib_save), ib_save);
                     rv |= n_SHEXP_STATE_ERR_CONTROL;
                  }
                  /* As an implementation-defined extension, support \c@
                   * EQ printf(1) alike \c */
                  if(c == '\0'){
                     state |= a_SKIPT;
                     continue;
                  }
                  break;

               /* Octal sequence: 1 to 3 octal bytes */
               case '0':
                  /* As an extension (dependent on where you look, echo(1), or
                   * awk(1)/tr(1) etc.), allow leading "0" octal indicator */
                  if(il > 0 && (c = *ib) >= '0' && c <= '7'){
                     c2 = c;
                     --il, ++ib;
                  }
                  /* FALLTHRU */
               case '1': case '2': case '3':
               case '4': case '5': case '6': case '7':
                  c2 -= '0';
                  if(il > 0 && (c = *ib) >= '0' && c <= '7'){
                     c2 = (c2 << 3) | (c - '0');
                     --il, ++ib;
                  }
                  if(il > 0 && (c = *ib) >= '0' && c <= '7'){
                     if(!(state & a_SKIPMASK) && (u8)c2 > 0x1F){
                        rv |= n_SHEXP_STATE_ERR_NUMBER;
                        --il, ++ib;
                        if(flags & n_SHEXP_PARSE_LOG)
                           n_err(_("\\0 argument exceeds byte: %.*s: %.*s\n"),
                              (int)input->l, input->s,
                              (int)P2UZ(ib - ib_save), ib_save);
                        /* Write unchanged */
jerr_ib_save:
                        rv |= n_SHEXP_STATE_OUTPUT;
                        if(!(flags & n_SHEXP_PARSE_DRYRUN))
                           store = n_string_push_buf(store, ib_save,
                                 P2UZ(ib - ib_save));
                        continue;
                     }
                     c2 = (c2 << 3) | (c -= '0');
                     --il, ++ib;
                  }
                  if(state & a_SKIPMASK)
                     continue;
                  if((c = c2) == '\0'){
                     state |= a_SKIPQ;
                     continue;
                  }
                  break;

               /* ISO 10646 / Unicode sequence, 8 or 4 hexadecimal bytes */
               case 'U':
                  i = 8;
                  if(0){
                  /* FALLTHRU */
               case 'u':
                     i = 4;
                  }
                  if(il == 0)
                     goto j_dollar_ungetc;
                  if(0){
                     /* FALLTHRU */

               /* Hexadecimal sequence, 1 or 2 hexadecimal bytes */
               case 'X':
               case 'x':
                     if(il == 0)
                        goto j_dollar_ungetc;
                     i = 2;
                  }
                  /* C99 */{
                     static u8 const hexatoi[] = { /* XXX uses ASCII */
                        0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
                     };
                     uz no, j;

                     i = MIN(il, i);
                     for(no = j = 0; i-- > 0; --il, ++ib, ++j){
                        c = *ib;
                        if(su_cs_is_xdigit(c)){
                           no <<= 4;
                           no += hexatoi[(u8)((c) - ((c) <= '9' ? 48
                                 : ((c) <= 'F' ? 55 : 87)))];
                        }else if(j == 0){
                           if(state & a_SKIPMASK)
                              break;
                           c2 = (c2 == 'U' || c2 == 'u') ? 'u' : 'x';
                           if(flags & n_SHEXP_PARSE_LOG)
                              n_err(_("Invalid \\%c notation: %.*s: %.*s\n"),
                                 c2, (int)input->l, input->s,
                                 (int)P2UZ(ib - ib_save), ib_save);
                           rv |= n_SHEXP_STATE_ERR_NUMBER;
                           goto jerr_ib_save;
                        }else
                           break;
                     }

                     /* Unicode massage */
                     if((c2 != 'U' && c2 != 'u') || su_cs_is_ascii(no)){
                        if((c = (char)no) == '\0')
                           state |= a_SKIPQ;
                     }else if(no == 0)
                        state |= a_SKIPQ;
                     else if(!(state & a_SKIPMASK)){
                        if(!(flags & n_SHEXP_PARSE_DRYRUN))
                           store = n_string_reserve(store, MAX(j, 4));

                        if(no > 0x10FFFF){ /* XXX magic; CText */
                           if(flags & n_SHEXP_PARSE_LOG)
                              n_err(_("\\U argument exceeds 0x10FFFF: %.*s: "
                                    "%.*s\n"),
                                 (int)input->l, input->s,
                                 (int)P2UZ(ib - ib_save), ib_save);
                           rv |= n_SHEXP_STATE_ERR_NUMBER;
                           /* But normalize the output anyway */
                           goto Jerr_uni_norm;
                        }

                        j = su_utf32_to_8(no, utf);

                        if(n_psonce & n_PSO_UNICODE){
                           rv |= n_SHEXP_STATE_OUTPUT | n_SHEXP_STATE_UNICODE;
                           if(!(flags & n_SHEXP_PARSE_DRYRUN))
                              store = n_string_push_buf(store, utf, j);
                           continue;
                        }
#ifdef mx_HAVE_ICONV
                        else{
                           char *icp;

                           icp = n_iconv_onetime_cp(n_ICONV_NONE,
                                 NULL, NULL, utf);
                           if(icp != NULL){
                              rv |= n_SHEXP_STATE_OUTPUT;
                              if(!(flags & n_SHEXP_PARSE_DRYRUN))
                                 store = n_string_push_cp(store, icp);
                              continue;
                           }
                        }
#endif
                        if(!(flags & n_SHEXP_PARSE_DRYRUN)) Jerr_uni_norm:{
                           char itoa[32];

                           rv |= n_SHEXP_STATE_OUTPUT |
                                 n_SHEXP_STATE_ERR_UNICODE;
                           i = snprintf(itoa, sizeof itoa, "\\%c%0*X",
                                 (no > 0xFFFFu ? 'U' : 'u'),
                                 (int)(no > 0xFFFFu ? 8 : 4), (u32)no);
                           store = n_string_push_buf(store, itoa, i);
                        }
                        continue;
                     }
                     if(state & a_SKIPMASK)
                        continue;
                  }
                  break;

               /* Extension: \$ can be used to expand a variable.
                * B(ug|ad) effect: if conversion fails, not written "as-is" */
               case '$':
                  if(il == 0)
                     goto j_dollar_ungetc;
                  goto J_var_expand;

               default:
j_dollar_ungetc:
                  /* Follow bash(1) behaviour, print sequence unchanged */
                  ++il, --ib;
                  break;
               }
            }
         }else if(c == '$' && quotec == '"' && il > 0) J_var_expand:{
            state &= ~a_VARSUBST_MASK;
            if(*ib == '{')
               state |= a_BRACE;

            /* Scan variable name */
            if(!(state & a_BRACE) || il > 1){
               char const *cp, *vp;

               ib_save = ib - 1;
               if(state & a_BRACE)
                  --il, ++ib;
               vp = ib;
               state &= ~a_EXPLODE;

               for(i = 0; il > 0; --il, ++ib, ++i){
                  /* We have some special cases regarding special parameters,
                   * so ensure these don't cause failure.  This code has
                   * counterparts in code that manages internal variables! */
                  c = *ib;
                  if(!a_SHEXP_ISVARC(c)){
                     if(i == 0){
                        /* Simply skip over multiplexer */
                        if(c == '^')
                           continue;
                        if(c == '*' || c == '@' || c == '#' || c == '?' ||
                              c == '!'){
                           if(c == '@'){
                              if(quotec == '"')
                                 state |= a_EXPLODE;
                           }
                           --il, ++ib;
                           ++i;
                        }
                     }
                     break;
                  }else if(a_SHEXP_ISVARC_BAD1ST(c)){
                     if(i == 0)
                        state |= a_DIGIT1;
                  }else
                     state |= a_NONDIGIT;
               }

               /* In skip mode, be easy and.. skip over */
               if(state & a_SKIPMASK){
                  if((state & a_BRACE) && il > 0 && *ib == '}')
                     --il, ++ib;
                  continue;
               }

               /* Handle the scan error cases */
               if((state & (a_DIGIT1 | a_NONDIGIT)) ==
                     (a_DIGIT1 | a_NONDIGIT)){
                  if(state & a_BRACE){
                     if(il > 0 && *ib == '}')
                        --il, ++ib;
                     else
                        rv |= n_SHEXP_STATE_ERR_GROUPOPEN;
                  }
                  if(flags & n_SHEXP_PARSE_LOG)
                     n_err(_("Invalid identifier for ${}: %.*s: %.*s\n"),
                        (int)input->l, input->s,
                        (int)P2UZ(ib - ib_save), ib_save);
                  rv |= n_SHEXP_STATE_ERR_IDENTIFIER;
                  goto jerr_ib_save;
               }else if(i == 0){
                  if(state & a_BRACE){
                     if(il == 0 || *ib != '}'){
                        if(flags & n_SHEXP_PARSE_LOG)
                           n_err(_("No closing brace for ${}: %.*s: %.*s\n"),
                              (int)input->l, input->s,
                              (int)P2UZ(ib - ib_save), ib_save);
                        rv |= n_SHEXP_STATE_ERR_GROUPOPEN;
                        goto jerr_ib_save;
                     }
                     --il, ++ib;

                     if(i == 0){
                        if(flags & n_SHEXP_PARSE_LOG)
                           n_err(_("Bad substitution for ${}: %.*s: %.*s\n"),
                              (int)input->l, input->s,
                              (int)P2UZ(ib - ib_save), ib_save);
                        rv |= n_SHEXP_STATE_ERR_BADSUB;
                        goto jerr_ib_save;
                     }
                  }
                  /* Simply write dollar as-is? */
                  c = '$';
               }else{
                  if(state & a_BRACE){
                     if(il == 0 || *ib != '}'){
                        if(flags & n_SHEXP_PARSE_LOG)
                           n_err(_("No closing brace for ${}: %.*s: %.*s\n"),
                              (int)input->l, input->s,
                              (int)P2UZ(ib - ib_save), ib_save);
                        rv |= n_SHEXP_STATE_ERR_GROUPOPEN;
                        goto jerr_ib_save;
                     }
                     --il, ++ib;

                     if(i == 0){
                        if(flags & n_SHEXP_PARSE_LOG)
                           n_err(_("Bad substitution for ${}: %.*s: %.*s\n"),
                              (int)input->l, input->s,
                              (int)P2UZ(ib - ib_save), ib_save);
                        rv |= n_SHEXP_STATE_ERR_BADSUB;
                        goto jerr_ib_save;
                     }
                  }

                  if(flags & n_SHEXP_PARSE_DRYRUN)
                     continue;

                  /* We may shall explode "${@}" to a series of successive,
                   * properly quoted tokens (instead).  The first exploded
                   * cookie will join with the current token */
                  if(UNLIKELY(state & a_EXPLODE) &&
                        !(flags & n_SHEXP_PARSE_DRYRUN) && cookie != NULL){
                     if(n_var_vexplode(cookie))
                        state |= a_COOKIE;
                     /* On the other hand, if $@ expands to nothing and is the
                      * sole content of this quote then act like the shell does
                      * and throw away the entire atxplode construct */
                     else if(!(rv & n_SHEXP_STATE_OUTPUT) &&
                           il == 1 && *ib == '"' &&
                           ib_save == &input->s[1] && ib_save[-1] == '"')
                        ++ib, --il;
                     else
                        continue;
                     input->s = n_UNCONST(ib);
                     input->l = il;
                     goto jrestart_empty;
                  }

                  /* Check getenv(3) shall no internal variable exist!
                   * XXX We have some common idioms, avoid memory for them
                   * XXX Even better would be var_vlook_buf()! */
                  if(i == 1){
                     switch(*vp){
                     case '?': vp = n_qm; break;
                     case '!': vp = n_em; break;
                     case '*': vp = n_star; break;
                     case '@': vp = n_at; break;
                     case '#': vp = n_ns; break;
                     default: goto j_var_look_buf;
                     }
                  }else
j_var_look_buf:
                     vp = savestrbuf(vp, i);

                  if((cp = n_var_vlook(vp, TRU1)) != NULL){
                     rv |= n_SHEXP_STATE_OUTPUT;
                     store = n_string_push_cp(store, cp);
                     for(; (c = *cp) != '\0'; ++cp)
                        if(su_cs_is_cntrl(c)){
                           rv |= n_SHEXP_STATE_CONTROL;
                           break;
                        }
                  }
                  continue;
               }
            }
         }else if(c == '`' && quotec == '"' && il > 0){ /* TODO sh command */
            continue;
         }
      }

      if(!(state & a_SKIPMASK)){
         rv |= n_SHEXP_STATE_OUTPUT;
         if(su_cs_is_cntrl(c))
            rv |= n_SHEXP_STATE_CONTROL;
         if(!(flags & n_SHEXP_PARSE_DRYRUN))
            store = n_string_push_c(store, c);
      }
   } /* }}} */

   if(quotec != '\0' && !(flags & n_SHEXP_PARSE_QUOTE_AUTO_CLOSE)){
      if(flags & n_SHEXP_PARSE_LOG)
         n_err(_("No closing quote: %.*s\n"), (int)input->l, input->s);
      rv |= n_SHEXP_STATE_ERR_QUOTEOPEN;
   }

jleave:
   ASSERT(!(state & a_COOKIE));
   if((flags & n_SHEXP_PARSE_DRYRUN) && store != NULL){
      store = n_string_push_buf(store, input->s, P2UZ(ib - input->s));
      rv |= n_SHEXP_STATE_OUTPUT;
   }

   if(state & a_CHOP_ONE)
      ++ib, --il;

   if(flags & n_SHEXP_PARSE_TRIM_SPACE){
      for(; il > 0; ++ib, --il){
         if(!su_cs_is_space(*ib))
            break;
         rv |= n_SHEXP_STATE_WS_TRAIL;
      }
   }

   if(flags & n_SHEXP_PARSE_TRIM_IFSSPACE){
      for(; il > 0; ++ib, --il){
         if(su_cs_find_c(ifs_ws, *ib) == NULL)
            break;
         rv |= n_SHEXP_STATE_WS_TRAIL;
      }
   }

   input->l = il;
   input->s = UNCONST(char*,ib);

   if(!(rv & n_SHEXP_STATE_STOP)){
      if(!(rv & (n_SHEXP_STATE_OUTPUT | n_SHEXP_STATE_META_MASK)) &&
            (flags & n_SHEXP_PARSE_IGNORE_EMPTY) && il > 0)
         goto jrestart_empty;
      if(/*!(rv & n_SHEXP_STATE_OUTPUT) &&*/ il == 0)
         rv |= n_SHEXP_STATE_STOP;
   }

   if((state & a_SKIPT) && !(rv & n_SHEXP_STATE_STOP) &&
         (flags & n_SHEXP_PARSE_META_MASK))
      goto jrestart;
jleave_quick:
   ASSERT((rv & n_SHEXP_STATE_OUTPUT) || !(rv & n_SHEXP_STATE_UNICODE));
   ASSERT((rv & n_SHEXP_STATE_OUTPUT) || !(rv & n_SHEXP_STATE_CONTROL));
   NYD2_OU;
   return rv;
}

FL char *
n_shexp_parse_token_cp(enum n_shexp_parse_flags flags, char const **cp){
   struct str input;
   struct n_string sou, *soup;
   char *rv;
   enum n_shexp_state shs;
   NYD2_IN;

   ASSERT(cp != NULL);

   input.s = UNCONST(char*,*cp);
   input.l = UZ_MAX;
   soup = n_string_creat_auto(&sou);

   shs = n_shexp_parse_token(flags, soup, &input, NULL);
   if(shs & n_SHEXP_STATE_ERR_MASK){
      soup = n_string_assign_cp(soup, *cp);
      *cp = NULL;
   }else
      *cp = input.s;

   rv = n_string_cp(soup);
   /*n_string_gut(n_string_drop_ownership(soup));*/
   NYD2_OU;
   return rv;
}

FL struct n_string *
n_shexp_quote(struct n_string *store, struct str const *input, boole rndtrip){
   struct a_shexp_quote_lvl sql;
   struct a_shexp_quote_ctx sqc;
   NYD2_IN;

   ASSERT(store != NULL);
   ASSERT(input != NULL);
   ASSERT(input->l == 0 || input->s != NULL);

   su_mem_set(&sqc, 0, sizeof sqc);
   sqc.sqc_store = store;
   sqc.sqc_input.s = input->s;
   if((sqc.sqc_input.l = input->l) == UZ_MAX)
      sqc.sqc_input.l = su_cs_len(input->s);
   sqc.sqc_flags = rndtrip ? a_SHEXP_QUOTE_ROUNDTRIP : a_SHEXP_QUOTE_NONE;

   if(sqc.sqc_input.l == 0)
      store = n_string_push_buf(store, "''", sizeof("''") -1);
   else{
      su_mem_set(&sql, 0, sizeof sql);
      sql.sql_dat = sqc.sqc_input;
      sql.sql_flags = sqc.sqc_flags;
      a_shexp__quote(&sqc, &sql);
   }
   NYD2_OU;
   return store;
}

FL char *
n_shexp_quote_cp(char const *cp, boole rndtrip){
   struct n_string store;
   struct str input;
   char *rv;
   NYD2_IN;

   ASSERT(cp != NULL);

   input.s = UNCONST(char*,cp);
   input.l = UZ_MAX;
   rv = n_string_cp(n_shexp_quote(n_string_creat_auto(&store), &input,
         rndtrip));
   n_string_gut(n_string_drop_ownership(&store));
   NYD2_OU;
   return rv;
}

FL boole
n_shexp_is_valid_varname(char const *name){
   char lc, c;
   boole rv;
   NYD2_IN;

   rv = FAL0;

   for(lc = '\0'; (c = *name++) != '\0'; lc = c)
      if(!a_SHEXP_ISVARC(c))
         goto jleave;
      else if(lc == '\0' && a_SHEXP_ISVARC_BAD1ST(c))
         goto jleave;
   if(a_SHEXP_ISVARC_BADNST(lc))
      goto jleave;

   rv = TRU1;
jleave:
   NYD2_OU;
   return rv;
}

FL int
c_shcodec(void *vp){
   struct str in;
   struct n_string sou_b, *soup;
   s32 nerrn;
   uz alen;
   boole norndtrip;
   char const **argv, *varname, *act, *cp;
   NYD_IN;

   soup = n_string_creat_auto(&sou_b);
   argv = vp;
   varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;

   act = *argv;
   for(cp = act; *cp != '\0' && !su_cs_is_space(*cp); ++cp)
      ;
   if((norndtrip = (*act == '+')))
      ++act;
   if(act == cp)
      goto jesynopsis;
   alen = P2UZ(cp - act);
   if(*cp != '\0')
      ++cp;

   in.l = su_cs_len(in.s = UNCONST(char*,cp));
   nerrn = su_ERR_NONE;

   if(su_cs_starts_with_case_n("encode", act, alen))
      soup = n_shexp_quote(soup, &in, !norndtrip);
   else if(!norndtrip && su_cs_starts_with_case_n("decode", act, alen)){
      for(;;){
         enum n_shexp_state shs;

         shs = n_shexp_parse_token((n_SHEXP_PARSE_LOG |
               n_SHEXP_PARSE_IGNORE_EMPTY), soup, &in, NULL);
         if(shs & n_SHEXP_STATE_ERR_MASK){
            soup = n_string_assign_cp(soup, cp);
            nerrn = su_ERR_CANCELED;
            vp = NULL;
            break;
         }
         if(shs & n_SHEXP_STATE_STOP)
            break;
      }
   }else
      goto jesynopsis;

   if(varname != NULL){
      cp = n_string_cp(soup);
      if(!n_var_vset(varname, (up)cp)){
         nerrn = su_ERR_NOTSUP;
         vp = NULL;
      }
   }else{
      struct str out;

      in.s = n_string_cp(soup);
      in.l = soup->s_len;
      makeprint(&in, &out);
      if(fprintf(n_stdout, "%s\n", out.s) < 0){
         nerrn = su_err_no();
         vp = NULL;
      }
      n_free(out.s);
   }

jleave:
   n_pstate_err_no = nerrn;
   NYD_OU;
   return (vp != NULL ? 0 : 1);
jesynopsis:
   n_err(_("Synopsis: shcodec: <[+]e[ncode]|d[ecode]> \n"));
   nerrn = su_ERR_INVAL;
   vp = NULL;
   goto jleave;
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/mx/sigs.c000066400000000000000000000357301352610246600154170ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ Implementation of sigs.h.
 *
 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: BSD-3-Clause TODO ISC (better: drop)
 */
/*
 * Copyright (c) 1980, 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.
 */
#undef su_FILE
#define su_FILE sigs
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

#include 

#include 
#include 
#include 

/* TODO fake */
#include "mx/sigs.h"
#include "su/code-in.h"

/*
 * TODO At the beginning of November 2015 -- for v14.9 -- i've tried for one
 * TODO and a half week to convert this codebase to SysV style signal handling,
 * TODO meaning no SA_RESTART and EINTR in a lot of places and error reporting
 * TODO up the chain.   I failed miserably, not only because S/MIME / SSL but
 * TODO also because of general frustration.  Directly after v14.9 i will strip
 * TODO ANYTHING off the codebase (socket stuff etc.) and keep only the very
 * TODO core, doing namespace and type cleanup and convert this core to a clean
 * TODO approach, from which i plan to start this thing anew.
 * TODO For now i introduced the n_sigman, yet another hack, to be used in a
 * TODO few places.
 * TODO The real solution:
 * TODO - No SA_RESTART.  Just like for my C++ library: encapsulate EINTR in
 * TODO   userspace at the systemcall (I/O rewrite: drop stdio), normal
 * TODO   interface auto-restarts, special _intr() series return EINTR.
 * TODO   Do n_sigman_poll()/peek()/whatever whenever desired for the former,
 * TODO   report errors up the call chain, have places where operations can be
 * TODO   "properly" aborted.
 * TODO - We save the initial signal settings upon program startup.
 * TODO - We register our sigman handlers once, at program startup.
 * TODO   Maximally, and most likely only due to lack of atomic CAS, ignore
 * TODO   or block some signals temporarily.  Best if not.
 * TODO   The signal handlers only set a flag.  Block all signals for handler
 * TODO   execution, like this we are safe to "set the first signal was x".
 * TODO - In interactive context, ignore SIGTERM.
 * TODO   I.e., see the POSIX standard for what a shell does.
 * TODO - In non-interactive context, don't know anything about job control!?!
 * TODO - Place child processes in their own process group.  Restore the signal
 * TODO   mask back to the saved original one for them, before exec.
 * TODO - Except for job control related (<-> interactive) ignore any signals
 * TODO   while we are "behind" a child that occupies the terminal.  For those,
 * TODO   perform proper terminal attribute handling.  For childs that don't
 * TODO   occupy the terminal we "are the shell" and should therefore manage
 * TODO   them accordingly, including termination request as necessary.
 * TODO   And if we have a worker in a pipeline, we need to manage it and deal
 * TODO   with it properly, WITHOUT temporary signal overwrites.
 * TODO - No more jumps.
 * TODO - (When sockets will be reintroduced, non-blocking.)
 * TODO - (When SSL is reintroduced, memory BIO.  It MAY be necessary to
 * TODO   temporarily block signals during a few SSL functions?  Read SSL docu!
 * TODO   But i prefer blocking since it's a single syscall, not temporary
 * TODO   SA_RESTART setting, since that has to be done for every signal.)
 */

/* signal_all_ */
static uz volatile a_sigs_all_depth;
static sigset_t a_sigs_all_nset, a_sigs_all_oset;

/* {hold,rele}_sigs() */
static uz           _hold_sigdepth;
static sigset_t         _hold_nset, _hold_oset;

/* */
static void a_sigs_dummyhdl(int sig);

static void
a_sigs_dummyhdl(int sig){
   UNUSED(sig);
}

FL int
c_sleep(void *v){ /* XXX installs sighdl+ due to outer jumps and SA_RESTART! */
   sigset_t nset, oset;
   struct sigaction nact, oact;
   boole ignint;
   uz sec, msec;
   char **argv;
   NYD_IN;

   argv = v;

   if((su_idec_uz_cp(&sec, argv[0], 0, NULL) &
         (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
         ) != su_IDEC_STATE_CONSUMED)
      goto jesyn;

   if(argv[1] == NULL){
      msec = 0;
      ignint = FAL0;
   }else if((su_idec_uz_cp(&msec, argv[1], 0, NULL) &
         (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
         ) != su_IDEC_STATE_CONSUMED)
      goto jesyn;
   else
      ignint = (argv[2] != NULL);

   if(UZ_MAX / n_DATE_MILLISSEC < sec)
      goto jeover;
   sec *= n_DATE_MILLISSEC;

   if(UZ_MAX - sec < msec)
      goto jeover;
   msec += sec;

   /* XXX This requires a terrible mess of signal handling:
    * - we usually have our SA_RESTART handler, that must be replaced
    * - we most often have a sigsetjmp() to overcome SA_RESTART
    * - TODO for now signal_all_hold() most often on in robot mode,
    *   TODO therefore we also need sigprocmask(), to block anything
    *   TODO except SIGINT, and to unblock SIGINT, thus! */
   su_mem_set(&nact, 0, sizeof nact);
   nact.sa_handler = &a_sigs_dummyhdl;
   sigemptyset(&nact.sa_mask);
   sigaddset(&nact.sa_mask, SIGINT);
   sigaction(SIGINT, &nact, &oact);

   sigfillset(&nset);
   sigdelset(&nset, SIGINT);
   sigprocmask(SIG_BLOCK, &nset, &oset);
   sigemptyset(&nset);
   sigaddset(&nset, SIGINT);
   sigprocmask(SIG_UNBLOCK, &nset, NULL);

   n_pstate_err_no = (n_msleep(msec, ignint) > 0) ? su_ERR_INTR : su_ERR_NONE;

   sigprocmask(SIG_SETMASK, &oset, NULL);
   sigaction(SIGINT, &oact, NULL);
jleave:
   NYD_OU;
   return (argv == NULL);
jeover:
   n_err(_("`sleep': argument(s) overflow(s) datatype\n"));
   n_pstate_err_no = su_ERR_OVERFLOW;
   argv = NULL;
   goto jleave;
jesyn:
   n_err(_("Synopsis: sleep:  [] [uninterruptible]\n"));
   n_pstate_err_no = su_ERR_INVAL;
   argv = NULL;
   goto jleave;
}

FL void
n_raise(int signo)
{
   NYD2_IN;
   if(n_pid == 0)
      n_pid = getpid();
   kill(n_pid, signo);
   NYD2_OU;
}

FL n_sighdl_t
safe_signal(int signum, n_sighdl_t handler)
{
   struct sigaction nact, oact;
   n_sighdl_t rv;
   NYD2_IN;

   nact.sa_handler = handler;
   sigfillset(&nact.sa_mask);
   nact.sa_flags = SA_RESTART;
   rv = (sigaction(signum, &nact, &oact) != 0) ? SIG_ERR : oact.sa_handler;
   NYD2_OU;
   return rv;
}

FL n_sighdl_t
n_signal(int signo, n_sighdl_t hdl){
   struct sigaction nact, oact;
   NYD2_IN;

   nact.sa_handler = hdl;
   sigfillset(&nact.sa_mask);
   nact.sa_flags = 0;
   hdl = (sigaction(signo, &nact, &oact) != 0) ? SIG_ERR : oact.sa_handler;
   NYD2_OU;
   return hdl;
}

FL void
mx_sigs_all_hold(s32 sigadjust, ...){
   sigset_t unbl, *ossp;
   boole nounbl, anyunbl;

   sigemptyset(&unbl);

   if((nounbl = (a_sigs_all_depth++ == 0))){
      sigfillset(&a_sigs_all_nset);
      sigdelset(&a_sigs_all_nset, SIGABRT);
#ifdef SIGBUS
      sigdelset(&a_sigs_all_nset, SIGBUS);
#endif
      sigdelset(&a_sigs_all_nset, SIGFPE);
      sigdelset(&a_sigs_all_nset, SIGILL);
      sigdelset(&a_sigs_all_nset, SIGSEGV);

      sigdelset(&a_sigs_all_nset, SIGCHLD);

      sigdelset(&a_sigs_all_nset, SIGKILL);
      sigdelset(&a_sigs_all_nset, SIGSTOP);

      ossp = &a_sigs_all_oset;
   }else
      ossp = NIL;

   anyunbl = FAL0;
   if(sigadjust != 0){
      va_list val;

      va_start(val, sigadjust);

      do if(sigadjust > 0)
         sigaddset(&a_sigs_all_nset, sigadjust);
      else{
         sigadjust = -sigadjust;
         sigdelset(&a_sigs_all_nset, sigadjust);
         sigaddset(&unbl, sigadjust);
         anyunbl = TRU1;
      }while((sigadjust = va_arg(val, s32)) != 0);

      va_end(val);
   }

   sigprocmask(SIG_BLOCK, &a_sigs_all_nset, ossp);
   if(!nounbl && anyunbl)
      sigprocmask(SIG_UNBLOCK, &unbl, NIL);
}

FL void
mx_sigs_all_rele(void){
   if(--a_sigs_all_depth == 0)
      sigprocmask(SIG_SETMASK, &a_sigs_all_oset, NIL);
}

FL void
hold_sigs(void)
{
   NYD2_IN;
   if (_hold_sigdepth++ == 0) {
      sigemptyset(&_hold_nset);
      sigaddset(&_hold_nset, SIGHUP);
      sigaddset(&_hold_nset, SIGINT);
      sigaddset(&_hold_nset, SIGQUIT);
      sigprocmask(SIG_BLOCK, &_hold_nset, &_hold_oset);
   }
   NYD2_OU;
}

FL void
rele_sigs(void)
{
   NYD2_IN;
   if (--_hold_sigdepth == 0)
      sigprocmask(SIG_SETMASK, &_hold_oset, NULL);
   NYD2_OU;
}

/* TODO This is temporary gracyness */
static struct n_sigman *n__sigman;
static void n__sigman_hdl(int signo);
static void
n__sigman_hdl(int signo){
   NYD; /* Signal handler */
   n__sigman->sm_signo = signo;
   siglongjmp(n__sigman->sm_jump, 1);
}

FL int
n__sigman_enter(struct n_sigman *self, int flags){
   /* TODO no error checking when installing sighdls */
   int rv;
   NYD2_IN;

   if((int)flags >= 0){
      self->sm_flags = (enum n_sigman_flags)flags;
      self->sm_signo = 0;
      self->sm_outer = n__sigman;
      if(flags & n_SIGMAN_HUP)
         self->sm_ohup = safe_signal(SIGHUP, &n__sigman_hdl);
      if(flags & n_SIGMAN_INT)
         self->sm_oint = safe_signal(SIGINT, &n__sigman_hdl);
      if(flags & n_SIGMAN_QUIT)
         self->sm_oquit = safe_signal(SIGQUIT, &n__sigman_hdl);
      if(flags & n_SIGMAN_PIPE)
         self->sm_opipe = safe_signal(SIGPIPE, &n__sigman_hdl);
      n__sigman = self;
      rv = 0;
   }else{
      flags = self->sm_flags;

      /* Just in case of a race (signal while holding and ignoring? really?) */
      if(!(flags & n__SIGMAN_PING)){
         if(flags & n_SIGMAN_HUP)
            safe_signal(SIGHUP, SIG_IGN);
         if(flags & n_SIGMAN_INT)
            safe_signal(SIGINT, SIG_IGN);
         if(flags & n_SIGMAN_QUIT)
            safe_signal(SIGQUIT, SIG_IGN);
         if(flags & n_SIGMAN_PIPE)
            safe_signal(SIGPIPE, SIG_IGN);
      }
      rv = self->sm_signo;
      /* The signal mask has been restored, but of course rele_sigs() has
       * already been called: account for restoration due to jump */
      ++_hold_sigdepth;
   }
   rele_sigs();
   NYD2_OU;
   return rv;
}

FL void
n_sigman_cleanup_ping(struct n_sigman *self){
   u32 f;
   NYD2_IN;

   hold_sigs();

   f = self->sm_flags;
   f |= n__SIGMAN_PING;
   self->sm_flags = f;

   if(f & n_SIGMAN_HUP)
      safe_signal(SIGHUP, SIG_IGN);
   if(f & n_SIGMAN_INT)
      safe_signal(SIGINT, SIG_IGN);
   if(f & n_SIGMAN_QUIT)
      safe_signal(SIGQUIT, SIG_IGN);
   if(f & n_SIGMAN_PIPE)
      safe_signal(SIGPIPE, SIG_IGN);

   rele_sigs();
   NYD2_OU;
}

FL void
n_sigman_leave(struct n_sigman *self,
      enum n_sigman_flags reraise_flags){
   u32 f;
   int sig;
   NYD2_IN;

   hold_sigs();
   n__sigman = self->sm_outer;

   f = self->sm_flags;
   if(f & n_SIGMAN_HUP)
      safe_signal(SIGHUP, self->sm_ohup);
   if(f & n_SIGMAN_INT)
      safe_signal(SIGINT, self->sm_oint);
   if(f & n_SIGMAN_QUIT)
      safe_signal(SIGQUIT, self->sm_oquit);
   if(f & n_SIGMAN_PIPE)
      safe_signal(SIGPIPE, self->sm_opipe);

   rele_sigs();

   sig = 0;
   switch(self->sm_signo){
   case SIGPIPE:
      if((reraise_flags & n_SIGMAN_PIPE) ||
            ((reraise_flags & n_SIGMAN_NTTYOUT_PIPE) &&
             !(n_psonce & n_PSO_TTYOUT)))
         sig = SIGPIPE;
      break;
   case SIGHUP:
      if(reraise_flags & n_SIGMAN_HUP)
         sig = SIGHUP;
      break;
   case SIGINT:
      if(reraise_flags & n_SIGMAN_INT)
         sig = SIGINT;
      break;
   case SIGQUIT:
      if(reraise_flags & n_SIGMAN_QUIT)
         sig = SIGQUIT;
      break;
   default:
      break;
   }

   NYD2_OU;
   if(sig != 0){
      sigset_t cset;

      sigemptyset(&cset);
      sigaddset(&cset, sig);
      sigprocmask(SIG_UNBLOCK, &cset, NULL);
      n_raise(sig);
   }
}

FL int
n_sigman_peek(void){
   int rv;
   NYD2_IN;
   rv = 0;
   NYD2_OU;
   return rv;
}

FL void
n_sigman_consume(void){
   NYD2_IN;
   NYD2_OU;
}

#if su_DVLOR(1, 0)
static void a_sigs_nyd__dump(su_up cookie, char const *buf, su_uz blen);
static void
a_sigs_nyd__dump(su_up cookie, char const *buf, su_uz blen){
   write((int)cookie, buf, blen);
}

FL void
mx__nyd_oncrash(int signo)
{
   char pathbuf[PATH_MAX], s2ibuf[32], *cp;
   struct sigaction xact;
   sigset_t xset;
   int fd;
   uz i, fnl;
   char const *tmpdir;

   su_nyd_stop();

   LCTA(sizeof("./") -1 + sizeof(VAL_UAGENT) -1 + sizeof(".dat") < PATH_MAX,
      "System limits too low for fixed-size buffer operation");

   xact.sa_handler = SIG_DFL;
   sigemptyset(&xact.sa_mask);
   xact.sa_flags = 0;
   sigaction(signo, &xact, NULL);

   i = su_cs_len(tmpdir = ok_vlook(TMPDIR));
   fnl = sizeof(VAL_UAGENT) -1;

   if (i + 1 + fnl + 1 + sizeof(".dat") > sizeof(pathbuf)) {
      (cp = pathbuf)[0] = '.';
      i = 1;
   } else
      su_mem_copy(cp = pathbuf, tmpdir, i);
   cp[i++] = '/'; /* xxx pathsep */
   su_mem_copy(cp += i, VAL_UAGENT, fnl);
   i += fnl;
   su_mem_copy(cp += fnl, ".dat", sizeof(".dat"));
   fnl = i + sizeof(".dat") -1;

   if ((fd = open(pathbuf, O_WRONLY | O_CREAT | O_EXCL, 0666)) == -1)
      fd = STDERR_FILENO;

# undef _X
# define _X(X) (X), sizeof(X) -1
   write(fd, _X("\n\nNYD: program dying due to signal "));

   cp = s2ibuf + sizeof(s2ibuf) -1;
   *cp = '\0';
   i = signo;
   do {
      *--cp = "0123456789"[i % 10];
      i /= 10;
   } while (i != 0);
   write(fd, cp, P2UZ((s2ibuf + sizeof(s2ibuf) -1) - cp));

   write(fd, _X(":\n"));

   su_nyd_dump(&a_sigs_nyd__dump, (su_uz)(su_u32)fd);

   write(fd, _X("----------\nCome up to the lab and see what's on the slab\n"));

   if (fd != STDERR_FILENO) {
      write(STDERR_FILENO, _X("Crash NYD listing written to "));
      write(STDERR_FILENO, pathbuf, fnl);
      write(STDERR_FILENO, _X("\n"));
# undef _X

      close(fd);
   }

   sigemptyset(&xset);
   sigaddset(&xset, signo);
   sigprocmask(SIG_UNBLOCK, &xset, NULL);
   n_raise(signo);
   for (;;)
      _exit(n_EXIT_ERR);
}
#endif /* DVLOR(1,0) */

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/mx/spam.c000066400000000000000000000572241352610246600154140ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ Spam related facilities.
 *
 * Copyright (c) 2013 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE spam
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

su_EMPTY_FILE()
#ifdef mx_HAVE_SPAM

#include 
#include 
#include 

#include "mx/child.h"
#include "mx/file-streams.h"
#include "mx/random.h"
#include "mx/sigs.h"

/* TODO fake */
#include "su/code-in.h"

/* This is chosen rather arbitrarily.
 * It must be able to swallow the first line of a rate response */
#if BUFFER_SIZE < 1024
# error *spam-interface* BUFFER_SIZE constraints are not matched
#endif

#ifdef mx_HAVE_SPAM_FILTER
  /* NELEM() of regmatch_t groups */
# define SPAM_FILTER_MATCHES  32u
#endif

enum spam_action {
   _SPAM_RATE,
   _SPAM_HAM,
   _SPAM_SPAM,
   _SPAM_FORGET
};

#if defined mx_HAVE_SPAM_SPAMC || defined mx_HAVE_SPAM_FILTER
struct spam_cf{
   char const *cf_cmd;
   char *cf_result; /* _SPAM_RATE: first response line */
   s32 cf_exit_status;
   u8 cf__pad[3];
   boole cf_useshell;
   /* .cf_cmd may be adjusted for each call (`spamforget')... */
   char const *cf_acmd;
   char const *cf_a0;
   char const *cf_env[4];
   n_sighdl_t cf_otstp;
   n_sighdl_t cf_ottin;
   n_sighdl_t cf_ottou;
   n_sighdl_t cf_ohup;
   n_sighdl_t cf_opipe;
   n_sighdl_t cf_oint;
   n_sighdl_t cf_oquit;
};
#endif

#ifdef mx_HAVE_SPAM_SPAMC
struct spam_spamc{
   struct spam_cf c_super;
   char const *c_cmd_arr[9];
};
#endif

#ifdef mx_HAVE_SPAM_FILTER
struct spam_filter {
   struct spam_cf    f_super;
   char const        *f_cmd_nospam; /* Working relative to current message.. */
   char const        *f_cmd_noham;
# ifdef mx_HAVE_REGEX
   u8             __pad[4];
   u32            f_score_grpno; /* 0 for not set */
   regex_t           f_score_regex;
# endif
};
#endif

struct spam_vc {
   enum spam_action  vc_action;
   boole            vc_verbose;    /* Verbose output */
   boole            vc_progress;   /* "Progress meter" (mutual verbose) */
   u8             __pad[2];
   boole            (*vc_act)(struct spam_vc *);
   void              (*vc_dtor)(struct spam_vc *);
   char              *vc_buffer;    /* I/O buffer, BUFFER_SIZE bytes */
   uz            vc_mno;        /* Current message number */
   struct message    *vc_mp;        /* Current message */
   FILE              *vc_ifp;       /* Input stream on .vc_mp */
   union {
#ifdef mx_HAVE_SPAM_SPAMC
      struct spam_spamc    spamc;
#endif
#ifdef mx_HAVE_SPAM_FILTER
      struct spam_filter   filter;
#endif
#if defined mx_HAVE_SPAM_SPAMC || defined mx_HAVE_SPAM_FILTER
   struct spam_cf          cf;
#endif
   }                 vc_t;
   char const        *vc_esep;      /* Error separator for progress mode */
};

/* Indices according to enum spam_action */
static char const _spam_cmds[][16] = {
   "spamrate", "spamham", "spamspam", "spamforget"
};

/* Shared action setup */
static boole  _spam_action(enum spam_action sa, int *ip);

/* *spam-interface*=spamc: initialize, communicate */
#ifdef mx_HAVE_SPAM_SPAMC
static boole  _spamc_setup(struct spam_vc *vcp);
static boole  _spamc_interact(struct spam_vc *vcp);
static void    _spamc_dtor(struct spam_vc *vcp);
#endif

/* *spam-interface*=filter: initialize, communicate */
#ifdef mx_HAVE_SPAM_FILTER
static boole  _spamfilter_setup(struct spam_vc *vcp);
static boole  _spamfilter_interact(struct spam_vc *vcp);
static void    _spamfilter_dtor(struct spam_vc *vcp);
#endif

/* *spam-interface*=(spamc|filter): create child + communication */
#if defined mx_HAVE_SPAM_SPAMC || defined mx_HAVE_SPAM_FILTER
static void    _spam_cf_setup(struct spam_vc *vcp, boole useshell);
static boole  _spam_cf_interact(struct spam_vc *vcp);
#endif

/* Convert a floating-point spam rate into message.m_spamscore */
#if defined mx_HAVE_SPAM_SPAMC ||\
   (defined mx_HAVE_SPAM_FILTER && defined mx_HAVE_REGEX)
static void    _spam_rate2score(struct spam_vc *vcp, char *buf);
#endif

static boole
_spam_action(enum spam_action sa, int *ip)
{
   struct spam_vc vc;
   uz maxsize, skipped, cnt, curr;
   char const *cp;
   boole ok = FAL0;
   NYD_IN;

   su_mem_set(&vc, 0, sizeof vc);
   vc.vc_action = sa;
   vc.vc_verbose = ((n_poption & n_PO_V) != 0);
   vc.vc_progress = (!vc.vc_verbose && ((n_psonce & n_PSO_INTERACTIVE) != 0));
   vc.vc_esep = vc.vc_progress ? "\n" : n_empty;

   /* Check and setup the desired spam interface */
   if ((cp = ok_vlook(spam_interface)) == NULL) {
      n_err(_("`%s': no *spam-interface* set\n"), _spam_cmds[sa]);
      goto jleave;
#ifdef mx_HAVE_SPAM_SPAMC
   } else if (!su_cs_cmp_case(cp, "spamc")) {
       if (!_spamc_setup(&vc))
         goto jleave;
#endif
#ifdef mx_HAVE_SPAM_FILTER
   } else if (!su_cs_cmp_case(cp, "filter")) {
      if (!_spamfilter_setup(&vc))
         goto jleave;
#endif
   } else {
      n_err(_("`%s': unknown / unsupported *spam-interface*: %s\n"),
         _spam_cmds[sa], cp);
      goto jleave;
   }

   /* *spam-maxsize* we do handle ourselfs instead */
   if ((cp = ok_vlook(spam_maxsize)) == NULL ||
         (su_idec_u32_cp(&maxsize, cp, 0, NULL), maxsize) == 0)
      maxsize = SPAM_MAXSIZE;

   /* Finally get an I/O buffer */
   vc.vc_buffer = n_autorec_alloc(BUFFER_SIZE);

   skipped = cnt = 0;
   if (vc.vc_progress) {
      while (ip[cnt] != 0)
         ++cnt;
   }
   for (curr = 0, ok = TRU1; *ip != 0; --cnt, ++curr, ++ip) {
      vc.vc_mno = (uz)*ip;
      vc.vc_mp = message + vc.vc_mno - 1;
      if (sa == _SPAM_RATE)
         vc.vc_mp->m_spamscore = 0;

      if (vc.vc_mp->m_size > maxsize) {
         if (vc.vc_verbose)
            n_err(_("`%s': message %lu exceeds maxsize (%lu > %lu), skip\n"),
               _spam_cmds[sa], (ul)vc.vc_mno, (ul)(uz)vc.vc_mp->m_size,
               (ul)maxsize);
         else if (vc.vc_progress) {
            fprintf(n_stdout, "\r%s: !%-6" PRIuZ " %6" PRIuZ "/%-6" PRIuZ,
               _spam_cmds[sa], vc.vc_mno, cnt, curr);
            fflush(n_stdout);
         }
         ++skipped;
      } else {
         if (vc.vc_verbose)
            n_err(_("`%s': message %lu\n"), _spam_cmds[sa], (ul)vc.vc_mno);
         else if (vc.vc_progress) {
            fprintf(n_stdout, "\r%s: .%-6" PRIuZ " %6" PRIuZ "/%-6" PRIuZ,
               _spam_cmds[sa], vc.vc_mno, cnt, curr);
            fflush(n_stdout);
         }

         setdot(vc.vc_mp);
         if ((vc.vc_ifp = setinput(&mb, vc.vc_mp, NEED_BODY)) == NULL) {
            n_err(_("%s`%s': cannot load message %lu: %s\n"),
               vc.vc_esep, _spam_cmds[sa], (ul)vc.vc_mno,
               su_err_doc(su_err_no()));
            ok = FAL0;
            break;
         }

         if (!(ok = (*vc.vc_act)(&vc)))
            break;
      }
   }
   if (vc.vc_progress) {
      if (curr > 0)
         fprintf(n_stdout, _(" %s (%" PRIuZ "/%" PRIuZ " all/skipped)\n"),
            (ok ? _("done") : V_(n_error)), curr, skipped);
      fflush(n_stdout);
   }

   if (vc.vc_dtor != NULL)
      (*vc.vc_dtor)(&vc);
jleave:
   NYD_OU;
   return !ok;
}

#ifdef mx_HAVE_SPAM_SPAMC
static boole
_spamc_setup(struct spam_vc *vcp)
{
   struct spam_spamc *sscp;
   struct str str;
   char const **args, *cp;
   boole rv = FAL0;
   NYD2_IN;

   sscp = &vcp->vc_t.spamc;
   args = sscp->c_cmd_arr;

   if ((cp = ok_vlook(spamc_command)) == NULL) {
# ifdef SPAM_SPAMC_PATH
      cp = SPAM_SPAMC_PATH;
# else
      n_err(_("`%s': *spamc-command* is not set\n"),
         _spam_cmds[vcp->vc_action]);
      goto jleave;
# endif
   }
   *args++ = cp;

   switch (vcp->vc_action) {
   case _SPAM_RATE:
      *args = "-c";
      break;
   case _SPAM_HAM:
      args[1] = "ham";
      goto jlearn;
   case _SPAM_SPAM:
      args[1] = "spam";
      goto jlearn;
   case _SPAM_FORGET:
      args[1] = "forget";
jlearn:
      *args = "-L";
      ++args;
      break;
   }
   ++args;

   *args++ = "-l"; /* --log-to-stderr */
   *args++ = "-x"; /* No "safe callback", we need to react on errors! */

   if ((cp = ok_vlook(spamc_arguments)) != NULL)
      *args++ = cp;

   if ((cp = ok_vlook(spamc_user)) != NULL) {
      if (*cp == '\0')
         cp = ok_vlook(LOGNAME);
      *args++ = "-u";
      *args++ = cp;
   }
   ASSERT(P2UZ(args - sscp->c_cmd_arr) <= NELEM(sscp->c_cmd_arr));

   *args = NULL;
   sscp->c_super.cf_cmd = str_concat_cpa(&str, sscp->c_cmd_arr, " ")->s;
   if (vcp->vc_verbose)
      n_err(_("spamc(1) via %s\n"),
         n_shexp_quote_cp(sscp->c_super.cf_cmd, FAL0));

   _spam_cf_setup(vcp, FAL0);

   vcp->vc_act = &_spamc_interact;
   vcp->vc_dtor = &_spamc_dtor;
   rv = TRU1;
# ifndef SPAM_SPAMC_PATH
jleave:
# endif
   NYD2_OU;
   return rv;
}

static boole
_spamc_interact(struct spam_vc *vcp)
{
   boole rv;
   NYD2_IN;

   if (!(rv = _spam_cf_interact(vcp)))
      goto jleave;

   vcp->vc_mp->m_flag &= ~(MSPAM | MSPAMUNSURE);
   if (vcp->vc_action != _SPAM_RATE) {
      if (vcp->vc_action == _SPAM_SPAM)
         vcp->vc_mp->m_flag |= MSPAM;
   } else {
      char *buf, *cp;

      switch(vcp->vc_t.spamc.c_super.cf_exit_status){
      case 1:
         vcp->vc_mp->m_flag |= MSPAM;
         /* FALLTHRU */
      case 0:
         break;
      default:
         rv = FAL0;
         goto jleave;
      }

      if ((cp = su_cs_find_c(buf = vcp->vc_t.spamc.c_super.cf_result, '/')
            ) != NULL)
         buf[P2UZ(cp - buf)] = '\0';
      _spam_rate2score(vcp, buf);
   }
jleave:
   NYD2_OU;
   return rv;
}

static void
_spamc_dtor(struct spam_vc *vcp)
{
   NYD2_IN;
   if (vcp->vc_t.spamc.c_super.cf_result != NULL)
      n_free(vcp->vc_t.spamc.c_super.cf_result);
   NYD2_OU;
}
#endif /* mx_HAVE_SPAM_SPAMC */

#ifdef mx_HAVE_SPAM_FILTER
static boole
_spamfilter_setup(struct spam_vc *vcp)
{
   struct spam_filter *sfp;
   char const *cp, *var;
   boole rv = FAL0;
   NYD2_IN;

   sfp = &vcp->vc_t.filter;

   switch (vcp->vc_action) {
   case _SPAM_RATE:
      cp = ok_vlook(spamfilter_rate);
      var = "spam-filter-rate";
      goto jonecmd;
   case _SPAM_HAM:
      cp = ok_vlook(spamfilter_ham);
      var = "spam-filter-ham";
      goto jonecmd;
   case _SPAM_SPAM:
      cp = ok_vlook(spamfilter_spam);
      var = "spam-filter-spam";
jonecmd:
      if (cp == NULL) {
jecmd:
         n_err(_("`%s': *%s* is not set\n"), _spam_cmds[vcp->vc_action], var);
         goto jleave;
      }
      sfp->f_super.cf_cmd = savestr(cp);
      break;
   case _SPAM_FORGET:
      var = "spam-filter-nospam";
      if ((cp =  ok_vlook(spamfilter_nospam)) == NULL)
         goto jecmd;
      sfp->f_cmd_nospam = savestr(cp);
      if ((cp =  ok_vlook(spamfilter_noham)) == NULL)
         goto jecmd;
      sfp->f_cmd_noham = savestr(cp);
      break;
   }

# ifdef mx_HAVE_REGEX
   if (vcp->vc_action == _SPAM_RATE &&
         (cp = ok_vlook(spamfilter_rate_scanscore)) != NULL) {
      int s;
      char const *bp;

      var = su_cs_find_c(cp, ';');
      if (var == NULL) {
         n_err(_("`%s': *spamfilter-rate-scanscore*: missing semicolon;: %s\n"),
            _spam_cmds[vcp->vc_action], cp);
         goto jleave;
      }
      bp = &var[1];

      if((su_idec(&sfp->f_score_grpno, cp, P2UZ(var - cp), 0,
                  su_IDEC_MODE_LIMIT_32BIT, NULL
               ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
            ) != su_IDEC_STATE_CONSUMED){
         n_err(_("`%s': *spamfilter-rate-scanscore*: bad group: %s\n"),
            _spam_cmds[vcp->vc_action], cp);
         goto jleave;
      }
      if (sfp->f_score_grpno >= SPAM_FILTER_MATCHES) {
         n_err(_("`%s': *spamfilter-rate-scanscore*: "
            "group %u excesses limit %u\n"),
            _spam_cmds[vcp->vc_action], sfp->f_score_grpno,
            SPAM_FILTER_MATCHES);
         goto jleave;
      }

      if ((s = regcomp(&sfp->f_score_regex, bp, REG_EXTENDED | REG_ICASE))
            != 0) {
         n_err(_("`%s': invalid *spamfilter-rate-scanscore* regex: %s: %s\n"),
            _spam_cmds[vcp->vc_action], n_shexp_quote_cp(cp, FAL0),
            n_regex_err_to_doc(NULL, s));
         goto jleave;
      }
      if (sfp->f_score_grpno > sfp->f_score_regex.re_nsub) {
         regfree(&sfp->f_score_regex);
         n_err(_("`%s': *spamfilter-rate-scanscore*: no group %u: %s\n"),
            _spam_cmds[vcp->vc_action], sfp->f_score_grpno, cp);
         goto jleave;
      }
   }
# endif /* mx_HAVE_REGEX */

   _spam_cf_setup(vcp, TRU1);

   vcp->vc_act = &_spamfilter_interact;
   vcp->vc_dtor = &_spamfilter_dtor;
   rv = TRU1;
jleave:
   NYD2_OU;
   return rv;
}

static boole
_spamfilter_interact(struct spam_vc *vcp)
{
# ifdef mx_HAVE_REGEX
   regmatch_t rem[SPAM_FILTER_MATCHES], *remp;
   struct spam_filter *sfp;
   char *cp;
# endif
   boole rv;
   NYD2_IN;

   if (vcp->vc_action == _SPAM_FORGET)
      vcp->vc_t.cf.cf_cmd = (vcp->vc_mp->m_flag & MSPAM)
            ? vcp->vc_t.filter.f_cmd_nospam : vcp->vc_t.filter.f_cmd_noham;

   if (!(rv = _spam_cf_interact(vcp)))
      goto jleave;

   vcp->vc_mp->m_flag &= ~(MSPAM | MSPAMUNSURE);
   if (vcp->vc_action != _SPAM_RATE) {
      if (vcp->vc_action == _SPAM_SPAM)
         vcp->vc_mp->m_flag |= MSPAM;
      goto jleave;
   }else switch(vcp->vc_t.filter.f_super.cf_exit_status){
   case 2:
      vcp->vc_mp->m_flag |= MSPAMUNSURE;
      /* FALLTHRU */
   case 1:
      break;
   case 0:
      vcp->vc_mp->m_flag |= MSPAM;
      break;
   default:
      rv = FAL0;
      goto jleave;
   }

# ifdef mx_HAVE_REGEX
   sfp = &vcp->vc_t.filter;

   if (sfp->f_score_grpno == 0)
      goto jleave;
   if (sfp->f_super.cf_result == NULL) {
      n_err(_("`%s': *spamfilter-rate-scanscore*: filter does not "
         "produce output!\n"));
      goto jleave;
   }

   remp = rem + sfp->f_score_grpno;

   if (regexec(&sfp->f_score_regex, sfp->f_super.cf_result, NELEM(rem), rem,
         0) == REG_NOMATCH || (remp->rm_so | remp->rm_eo) < 0) {
      n_err(_("`%s': *spamfilter-rate-scanscore* "
         "does not match filter output!\n"),
         _spam_cmds[vcp->vc_action]);
      sfp->f_score_grpno = 0;
      goto jleave;
   }

   cp = sfp->f_super.cf_result;
   cp[remp->rm_eo] = '\0';
   cp += remp->rm_so;
   _spam_rate2score(vcp, cp);
# endif /* mx_HAVE_REGEX */

jleave:
   NYD2_OU;
   return rv;
}

static void
_spamfilter_dtor(struct spam_vc *vcp)
{
   struct spam_filter *sfp;
   NYD2_IN;

   sfp = &vcp->vc_t.filter;

   if (sfp->f_super.cf_result != NULL)
      n_free(sfp->f_super.cf_result);
# ifdef mx_HAVE_REGEX
   if (sfp->f_score_grpno > 0)
      regfree(&sfp->f_score_regex);
# endif
   NYD2_OU;
}
#endif /* mx_HAVE_SPAM_FILTER */

#if defined mx_HAVE_SPAM_SPAMC || defined mx_HAVE_SPAM_FILTER
static void
_spam_cf_setup(struct spam_vc *vcp, boole useshell)
{
   struct str s;
   char const *cp;
   struct spam_cf *scfp;
   NYD2_IN;
   LCTA(2 < NELEM(scfp->cf_env), "Preallocated buffer too small");

   scfp = &vcp->vc_t.cf;

   if ((scfp->cf_useshell = useshell)) {
      scfp->cf_acmd = ok_vlook(SHELL);
      scfp->cf_a0 = "-c";
   }

   /* MAILX_FILENAME_GENERATED *//* TODO pathconf NAME_MAX; but user can create
    * TODO a file wherever he wants!  *Do* create a zero-size temporary file
    * TODO and give *that* path as MAILX_FILENAME_TEMPORARY, clean it up once
    * TODO the pipe returns?  Like this we *can* verify path/name issues! */
   cp = mx_random_create_cp(MIN(NAME_MAX / 4, 16), NIL);
   scfp->cf_env[0] = str_concat_csvl(&s,
         n_PIPEENV_FILENAME_GENERATED, "=", cp, NULL)->s;
   /* v15 compat NAIL_ environments vanish! */
   scfp->cf_env[1] = str_concat_csvl(&s,
         "NAIL_FILENAME_GENERATED", "=", cp, NULL)->s;
   scfp->cf_env[2] = NULL;
   NYD2_OU;
}

static sigjmp_buf    __spam_cf_actjmp; /* TODO someday, we won't need it */
static int volatile  __spam_cf_sig; /* TODO someday, we won't need it */
static void
__spam_cf_onsig(int sig) /* TODO someday, we won't need it no more */
{
   NYD; /* Signal handler */
   __spam_cf_sig = sig;
   siglongjmp(__spam_cf_actjmp, 1);
}

static boole
_spam_cf_interact(struct spam_vc *vcp)
{
   struct mx_child_ctx cc;
   struct spam_cf *scfp;
   sz p2c[2], c2p[2];
   sigset_t cset;
   char const *cp;
   uz size;
   enum {
      _NONE    = 0,
      _SIGHOLD = 1<<0,
      _P2C_0   = 1<<1,
      _P2C_1   = 1<<2,
      _P2C     = _P2C_0 | _P2C_1,
      _C2P_0   = 1<<3,
      _C2P_1   = 1<<4,
      _C2P     = _C2P_0 | _C2P_1,
      _JUMPED  = 1<<5,
      _RUNNING = 1<<6,
      _GOODRUN = 1<<7,
      _ERRORS  = 1<<8
   } volatile state = _NONE;
   NYD2_IN;

   scfp = &vcp->vc_t.cf;
   if (scfp->cf_result != NULL) {
      n_free(scfp->cf_result);
      scfp->cf_result = NULL;
   }

   /* TODO Avoid that we jump away; yet necessary signal mess */
   /*__spam_cf_sig = 0;*/
   hold_sigs();
   state |= _SIGHOLD;
   scfp->cf_otstp = safe_signal(SIGTSTP, SIG_DFL);
   scfp->cf_ottin = safe_signal(SIGTTIN, SIG_DFL);
   scfp->cf_ottou = safe_signal(SIGTTOU, SIG_DFL);
   scfp->cf_opipe = safe_signal(SIGPIPE, SIG_IGN);
   scfp->cf_ohup = safe_signal(SIGHUP, &__spam_cf_onsig);
   scfp->cf_oint = safe_signal(SIGINT, &__spam_cf_onsig);
   scfp->cf_oquit = safe_signal(SIGQUIT, &__spam_cf_onsig);
   /* Keep sigs blocked */

   if(!mx_fs_pipe_cloexec(p2c)){
      n_err(_("%s`%s': cannot create parent communication pipe: %s\n"),
         vcp->vc_esep, _spam_cmds[vcp->vc_action], su_err_doc(su_err_no()));
      goto jtail;
   }
   state |= _P2C;

   if(!mx_fs_pipe_cloexec(c2p)){
      n_err(_("%s`%s': cannot create child pipe: %s\n"),
         vcp->vc_esep, _spam_cmds[vcp->vc_action], su_err_doc(su_err_no()));
      goto jtail;
   }
   state |= _C2P;

   if (sigsetjmp(__spam_cf_actjmp, 1)) {
      if (*vcp->vc_esep != '\0')
         n_err(vcp->vc_esep);
      state |= _JUMPED;
      goto jtail;
   }
   rele_sigs();
   state &= ~_SIGHOLD;

   /* Start our command as requested */
   sigemptyset(&cset);
   mx_child_ctx_setup(&cc);
   cc.cc_mask = &cset;
   cc.cc_fds[mx_CHILD_FD_IN] = p2c[0];
   cc.cc_fds[mx_CHILD_FD_OUT] = c2p[1];
   cc.cc_cmd = (scfp->cf_acmd != NULL ? scfp->cf_acmd : scfp->cf_cmd);
   cc.cc_args[0] = scfp->cf_a0;
   if(scfp->cf_acmd != NIL)
      cc.cc_args[1] = scfp->cf_cmd;
   cc.cc_env_addon = scfp->cf_env;

   if(!mx_child_run(&cc)){
      state |= _ERRORS;
      goto jtail;
   }
   state |= _RUNNING;
   close(p2c[0]);
   state &= ~_P2C_0;

   /* Yes, we could sendmp(SEND_MBOX), but simply passing through the MBOX
    * content does the same in effect, however much more efficiently.
    * XXX NOTE: this may mean we pass a message without From_ line! */
   for (size = vcp->vc_mp->m_size; size > 0;) {
      uz i;

      i = fread(vcp->vc_buffer, 1, MIN(size, BUFFER_SIZE), vcp->vc_ifp);
      if (i == 0) {
         if (ferror(vcp->vc_ifp))
            state |= _ERRORS;
         break;
      }
      size -= i;
      if(i != S(uz,write(S(int,p2c[1]), vcp->vc_buffer, i))){
         state |= _ERRORS;
         break;
      }
   }

jtail:
   /* TODO Quite racy -- block anything for a while? */
   if (state & _SIGHOLD) {
      state &= ~_SIGHOLD;
      rele_sigs();
   }

   if(state & _P2C_0){
      state &= ~_P2C_0;
      close(S(int,p2c[0]));
   }
   if(state & _C2P_1){
      state &= ~_C2P_1;
      close(S(int,c2p[1]));
   }
   /* And cause EOF for the reader */
   if(state & _P2C_1){
      state &= ~_P2C_1;
      close(S(int,p2c[1]));
   }

   if (state & _RUNNING) {
      if (!(state & _ERRORS) &&
            vcp->vc_action == _SPAM_RATE && !(state & (_JUMPED | _ERRORS))) {
         sz i = read(S(int,c2p[0]), vcp->vc_buffer, BUFFER_SIZE - 1);
         if (i > 0) {
            vcp->vc_buffer[i] = '\0';
            if ((cp = su_cs_find_c(vcp->vc_buffer, NETNL[0])) == NULL &&
                  (cp = su_cs_find_c(vcp->vc_buffer, NETNL[1])) == NULL) {
               n_err(_("%s`%s': program generates too much output: %s\n"),
                  vcp->vc_esep, _spam_cmds[vcp->vc_action],
                  n_shexp_quote_cp(scfp->cf_cmd, FAL0));
               state |= _ERRORS;
            } else {
               scfp->cf_result = su_cs_dup_cbuf(vcp->vc_buffer,
                     P2UZ(cp - vcp->vc_buffer), 0);
/* FIXME consume child output until EOF??? */
            }
         } else if (i != 0)
            state |= _ERRORS;
      }

      state &= ~_RUNNING;
      if(mx_child_wait(&cc) && (scfp->cf_exit_status = cc.cc_exit_status) >= 0)
         state |= _GOODRUN;
   }

   if (state & _C2P_0) {
      state &= ~_C2P_0;
      close(S(int,c2p[0]));
   }

   safe_signal(SIGQUIT, scfp->cf_oquit);
   safe_signal(SIGINT, scfp->cf_oint);
   safe_signal(SIGHUP, scfp->cf_ohup);
   safe_signal(SIGPIPE, scfp->cf_opipe);
   safe_signal(SIGTSTP, scfp->cf_otstp);
   safe_signal(SIGTTIN, scfp->cf_ottin);
   safe_signal(SIGTTOU, scfp->cf_ottou);

   NYD2_OU;
   if (state & _JUMPED) {
      ASSERT(vcp->vc_dtor != NULL);
      (*vcp->vc_dtor)(vcp);

      sigemptyset(&cset);
      sigaddset(&cset, __spam_cf_sig);
      sigprocmask(SIG_UNBLOCK, &cset, NULL);
      n_raise(__spam_cf_sig);
   }
   return !(state & (_JUMPED | _ERRORS));
}
#endif /* mx_HAVE_SPAM_SPAMC || mx_HAVE_SPAM_FILTER */

#if defined mx_HAVE_SPAM_SPAMC ||\
   (defined mx_HAVE_SPAM_FILTER && defined mx_HAVE_REGEX)
static void
_spam_rate2score(struct spam_vc *vcp, char *buf){
   u32 m, s;
   enum su_idec_state ids;
   NYD2_IN;

   /* C99 */{ /* Overcome ISO C / compiler weirdness */
      char const *cp;

      cp = buf;
      ids = su_idec_u32_cp(&m, buf, 10, &cp);
      if((ids & su_IDEC_STATE_EMASK) & ~su_IDEC_STATE_EBASE)
         goto jleave;
      buf = n_UNCONST(cp);
   }

   s = 0;
   if(!(ids & su_IDEC_STATE_CONSUMED)){
      /* Floating-point rounding for non-mathematicians */
      char c1, c2, c3;

      ++buf; /* '.' */
      if((c1 = buf[0]) != '\0' && (c2 = buf[1]) != '\0' &&
            (c3 = buf[2]) != '\0'){
         buf[2] = '\0';
         if(c3 >= '5'){
            if(c2 == '9'){
               if(c1 == '9'){
                  ++m;
                  goto jscore_ok;
               }else
                  buf[0] = ++c1;
               c2 = '0';
            }else
               ++c2;
            buf[1] = c2;
         }
      }

      ids = su_idec_u32_cp(&s, buf, 10, NULL);
      if((ids & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
            ) != su_IDEC_STATE_CONSUMED)
         goto jleave;
   }

jscore_ok:
   vcp->vc_mp->m_spamscore = (m << 8) | s;
jleave:
   NYD2_OU;
}
#endif /* _SPAM_SPAMC || (_SPAM_FILTER && mx_HAVE_REGEX) */

FL int
c_spam_clear(void *v)
{
   int *ip;
   NYD_IN;

   for (ip = v; *ip != 0; ++ip)
      message[(uz)*ip - 1].m_flag &= ~(MSPAM | MSPAMUNSURE);
   NYD_OU;
   return 0;
}

FL int
c_spam_set(void *v)
{
   int *ip;
   NYD_IN;

   for (ip = v; *ip != 0; ++ip) {
      message[(uz)*ip - 1].m_flag &= ~(MSPAM | MSPAMUNSURE);
      message[(uz)*ip - 1].m_flag |= MSPAM;
   }
   NYD_OU;
   return 0;
}

FL int
c_spam_forget(void *v)
{
   int rv;
   NYD_IN;

   rv = _spam_action(_SPAM_FORGET, (int*)v) ? OKAY : STOP;
   NYD_OU;
   return rv;
}

FL int
c_spam_ham(void *v)
{
   int rv;
   NYD_IN;

   rv = _spam_action(_SPAM_HAM, (int*)v) ? OKAY : STOP;
   NYD_OU;
   return rv;
}

FL int
c_spam_rate(void *v)
{
   int rv;
   NYD_IN;

   rv = _spam_action(_SPAM_RATE, (int*)v) ? OKAY : STOP;
   NYD_OU;
   return rv;
}

FL int
c_spam_spam(void *v)
{
   int rv;
   NYD_IN;

   rv = _spam_action(_SPAM_SPAM, (int*)v) ? OKAY : STOP;
   NYD_OU;
   return rv;
}

#include "su/code-ou.h"
#endif /* mx_HAVE_SPAM */
/* s-it-mode */
s-nail-14.9.15/src/mx/strings.c000066400000000000000000000370531352610246600161430ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ String support routines.
 *
 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: BSD-3-Clause
 */
/*
 * Copyright (c) 1980, 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.
 */
#undef su_FILE
#define su_FILE strings
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

#ifdef mx_HAVE_C90AMEND1
# include 
#endif

#include 
#include 

/* TODO fake */
#include "su/code-in.h"

FL char *
(savestr)(char const *str  su_DBG_LOC_ARGS_DECL)
{
   uz size;
   char *news;
   NYD_IN;

   size = su_cs_len(str);
   news = su_MEM_BAG_SELF_AUTO_ALLOC_LOCOR(size +1,  su_DBG_LOC_ARGS_ORUSE);
   if(size > 0)
      su_mem_copy(news, str, size);
   news[size] = '\0';
   NYD_OU;
   return news;
}

FL char *
(savestrbuf)(char const *sbuf, uz sbuf_len  su_DBG_LOC_ARGS_DECL)
{
   char *news;
   NYD_IN;

   news = su_MEM_BAG_SELF_AUTO_ALLOC_LOCOR(sbuf_len +1, su_DBG_LOC_ARGS_ORUSE);
   if(sbuf_len > 0)
      su_mem_copy(news, sbuf, sbuf_len);
   news[sbuf_len] = 0;
   NYD_OU;
   return news;
}

FL char *
(savecatsep)(char const *s1, char sep, char const *s2  su_DBG_LOC_ARGS_DECL)
{
   uz l1, l2;
   char *news;
   NYD_IN;

   l1 = (s1 != NULL) ? su_cs_len(s1) : 0;
   l2 = su_cs_len(s2);
   news = su_MEM_BAG_SELF_AUTO_ALLOC_LOCOR(l1 + (sep != '\0') + l2 +1,
         su_DBG_LOC_ARGS_ORUSE);
   if (l1 > 0) {
      su_mem_copy(news + 0, s1, l1);
      if (sep != '\0')
         news[l1++] = sep;
   }
   if(l2 > 0)
      su_mem_copy(news + l1, s2, l2);
   news[l1 + l2] = '\0';
   NYD_OU;
   return news;
}

/*
 * Support routines, auto-reclaimed storage
 */

FL struct str *
str_concat_csvl(struct str *self, ...) /* XXX onepass maybe better here */
{
   va_list vl;
   uz l;
   char const *cs;
   NYD_IN;

   va_start(vl, self);
   for (l = 0; (cs = va_arg(vl, char const*)) != NULL;)
      l += su_cs_len(cs);
   va_end(vl);

   self->l = l;
   self->s = n_autorec_alloc(l +1);

   va_start(vl, self);
   for (l = 0; (cs = va_arg(vl, char const*)) != NULL;) {
      uz i;

      i = su_cs_len(cs);
      if(i > 0){
         su_mem_copy(self->s + l, cs, i);
         l += i;
      }
   }
   self->s[l] = '\0';
   va_end(vl);
   NYD_OU;
   return self;
}

FL struct str *
(str_concat_cpa)(struct str *self, char const * const *cpa,
   char const *sep_o_null  su_DBG_LOC_ARGS_DECL)
{
   uz sonl, l;
   char const * const *xcpa;
   NYD_IN;

   sonl = (sep_o_null != NULL) ? su_cs_len(sep_o_null) : 0;

   for (l = 0, xcpa = cpa; *xcpa != NULL; ++xcpa)
      l += su_cs_len(*xcpa) + sonl;

   self->l = l;
   self->s = su_MEM_BAG_SELF_AUTO_ALLOC_LOCOR(l +1, su_DBG_LOC_ARGS_ORUSE);

   for (l = 0, xcpa = cpa; *xcpa != NULL; ++xcpa) {
      uz i;

      i = su_cs_len(*xcpa);
      if(i > 0){
         su_mem_copy(self->s + l, *xcpa, i);
         l += i;
      }
      if (sonl > 0) {
         su_mem_copy(self->s + l, sep_o_null, sonl);
         l += sonl;
      }
   }
   self->s[l] = '\0';
   NYD_OU;
   return self;
}

/*
 * Routines that are not related to auto-reclaimed storage follow.
 */

FL char *
string_quote(char const *v) /* TODO too simpleminded (getrawlist(), +++ ..) */
{
   char const *cp;
   uz i;
   char c, *rv;
   NYD2_IN;

   for (i = 0, cp = v; (c = *cp) != '\0'; ++i, ++cp)
      if (c == '"' || c == '\\')
         ++i;
   rv = n_autorec_alloc(i +1);

   for (i = 0, cp = v; (c = *cp) != '\0'; rv[i++] = c, ++cp)
      if (c == '"' || c == '\\')
         rv[i++] = '\\';
   rv[i] = '\0';
   NYD2_OU;
   return rv;
}

FL void
makelow(char *cp) /* TODO isn't that crap? --> */
{
      NYD_IN;
#ifdef mx_HAVE_C90AMEND1
   if (n_mb_cur_max > 1) {
      char *tp = cp;
      wchar_t wc;
      int len;

      while (*cp != '\0') {
         len = mbtowc(&wc, cp, n_mb_cur_max);
         if (len < 0)
            *tp++ = *cp++;
         else {
            wc = towlower(wc);
            if (wctomb(tp, wc) == len)
               tp += len, cp += len;
            else
               *tp++ = *cp++; /* <-- at least here */
         }
      }
   } else
#endif
          for(;; ++cp){
      char c;

      *cp = su_cs_to_lower(c = *cp);
      if(c == '\0')
         break;
   }
   NYD_OU;
}

FL boole
substr(char const *str, char const *sub)
{
   char const *cp, *backup;
   NYD_IN;

   cp = sub;
   backup = str;
   while (*str != '\0' && *cp != '\0') {
#ifdef mx_HAVE_C90AMEND1
      if (n_mb_cur_max > 1) {
         wchar_t c, c2;
         int i;

         if ((i = mbtowc(&c, cp, n_mb_cur_max)) == -1)
            goto Jsinglebyte;
         cp += i;
         if ((i = mbtowc(&c2, str, n_mb_cur_max)) == -1)
            goto Jsinglebyte;
         str += i;
         c = towupper(c);
         c2 = towupper(c2);
         if (c != c2) {
            if ((i = mbtowc(&c, backup, n_mb_cur_max)) > 0) {
               backup += i;
               str = backup;
            } else
               str = ++backup;
            cp = sub;
         }
      } else
Jsinglebyte:
#endif
      {
         int c, c2;

         c = *cp++ & 0377;
         c = su_cs_to_upper(c);
         c2 = *str++ & 0377;
         c2 = su_cs_to_upper(c2);
         if (c != c2) {
            str = ++backup;
            cp = sub;
         }
      }
   }
   NYD_OU;
   return (*cp == '\0');
}

FL struct str *
(n_str_assign_buf)(struct str *self, char const *buf, uz buflen
      su_DBG_LOC_ARGS_DECL){
   NYD_IN;
   if(buflen == UZ_MAX)
      buflen = (buf == NULL) ? 0 : su_cs_len(buf);

   ASSERT(buflen == 0 || buf != NULL);

   if(LIKELY(buflen > 0)){
      self->s = su_MEM_REALLOC_LOCOR(self->s, (self->l = buflen) +1,
            su_DBG_LOC_ARGS_ORUSE);
      su_mem_copy(self->s, buf, buflen);
      self->s[buflen] = '\0';
   }else
      self->l = 0;
   NYD_OU;
   return self;
}

FL struct str *
(n_str_add_buf)(struct str *self, char const *buf, uz buflen
      su_DBG_LOC_ARGS_DECL){
   NYD_IN;
   if(buflen == UZ_MAX)
      buflen = (buf == NULL) ? 0 : su_cs_len(buf);

   ASSERT(buflen == 0 || buf != NULL);

   if(buflen > 0) {
      uz osl = self->l, nsl = osl + buflen;

      self->s = su_MEM_REALLOC_LOCOR(self->s, (self->l = nsl) +1,
            su_DBG_LOC_ARGS_ORUSE);
      su_mem_copy(self->s + osl, buf, buflen);
      self->s[nsl] = '\0';
   }
   NYD_OU;
   return self;
}

FL struct str *
n_str_trim(struct str *self, enum n_str_trim_flags stf){
   uz l;
   char const *cp;
   NYD2_IN;

   cp = self->s;

   if((l = self->l) > 0 && (stf & n_STR_TRIM_FRONT)){
      while(su_cs_is_space(*cp)){
         ++cp;
         if(--l == 0)
            break;
      }
      self->s = n_UNCONST(cp);
   }

   if(l > 0 && (stf & n_STR_TRIM_END)){
      for(cp += l -1; su_cs_is_space(*cp); --cp)
         if(--l == 0)
            break;
   }
   self->l = l;

   NYD2_OU;
   return self;
}

FL struct str *
n_str_trim_ifs(struct str *self, boole dodefaults){
   char s, t, n, c;
   char const *ifs, *cp;
   uz l, i;
   NYD2_IN;

   if((l = self->l) == 0)
      goto jleave;

   ifs = ok_vlook(ifs_ws);
   cp = self->s;
   s = t = n = '\0';

   /* Check whether we can go fast(er) path */
   for(i = 0; (c = ifs[i]) != '\0'; ++i){
      switch(c){
      case ' ': s = c; break;
      case '\t': t = c; break;
      case '\n': n = c; break;
      default:
         /* Need to go the slow path */
         while(su_cs_find_c(ifs, *cp) != NULL){
            ++cp;
            if(--l == 0)
               break;
         }
         self->s = n_UNCONST(cp);

         if(l > 0){
            for(cp += l -1; su_cs_find_c(ifs, *cp) != NULL;){
               if(--l == 0)
                  break;
               /* An uneven number of reverse solidus escapes last WS! */
               else if(*--cp == '\\'){
                  sz j;

                  for(j = 1; l - (uz)j > 0 && cp[-j] == '\\'; ++j)
                     ;
                  if(j & 1){
                     ++l;
                     break;
                  }
               }
            }
         }
         self->l = l;

         if(!dodefaults)
            goto jleave;
         cp = self->s;
         ++i;
         break;
      }
   }

   /* No ifs-ws?  No more data?  No trimming */
   if(l == 0 || (i == 0 && !dodefaults))
      goto jleave;

   if(dodefaults){
      s = ' ';
      t = '\t';
      n = '\n';
   }

   if(l > 0){
      while((c = *cp) != '\0' && (c == s || c == t || c == n)){
         ++cp;
         if(--l == 0)
            break;
      }
      self->s = n_UNCONST(cp);
   }

   if(l > 0){
      for(cp += l -1; (c = *cp) != '\0' && (c == s || c == t || c == n);){
         if(--l == 0)
            break;
         /* An uneven number of reverse solidus escapes last WS! */
         else if(*--cp == '\\'){
            sz j;

            for(j = 1; l - (uz)j > 0 && cp[-j] == '\\'; ++j)
               ;
            if(j & 1){
               ++l;
               break;
            }
         }
      }
   }
   self->l = l;
jleave:
   NYD2_OU;
   return self;
}

/*
 * struct n_string TODO extend, optimize
 */

FL struct n_string *
n__string_clear(struct n_string *self){
   NYD_IN;
   ASSERT(self != NULL);

   if(self->s_size != 0){
      if(!self->s_auto)
         su_MEM_FREE(self->s_dat);
      self->s_len = self->s_auto = self->s_size = 0;
      self->s_dat = NIL;
   }
   NYD_OU;
   return self;
}

FL struct n_string *
(n_string_reserve)(struct n_string *self, uz noof  su_DBG_LOC_ARGS_DECL){
   u32 i, l, s;
   NYD_IN;
   ASSERT(self != NULL);

   s = self->s_size;
   l = self->s_len;
   if((uz)S32_MAX - Z_ALIGN(1) - l <= noof)
      n_panic(_("Memory allocation too large"));

   if((i = s - l) <= ++noof){
      i += l + (u32)noof;
      i = Z_ALIGN(i);
      self->s_size = i -1;

      if(!self->s_auto)
         self->s_dat = su_MEM_REALLOC_LOCOR(self->s_dat, i,
               su_DBG_LOC_ARGS_ORUSE);
      else{
         char *ndat;

         ndat = su_MEM_BAG_SELF_AUTO_ALLOC_LOCOR(i, su_DBG_LOC_ARGS_ORUSE);
         if(l > 0)
            su_mem_copy(ndat, self->s_dat, l);
         self->s_dat = ndat;
      }
   }
   NYD_OU;
   return self;
}

FL struct n_string *
(n_string_resize)(struct n_string *self, uz nlen  su_DBG_LOC_ARGS_DECL){
   NYD_IN;
   ASSERT(self != NULL);

   if(UCMP(z, S32_MAX, <=, nlen))
      n_panic(_("Memory allocation too large"));

   if(self->s_len < nlen)
      self = (n_string_reserve)(self, nlen  su_DBG_LOC_ARGS_USE);
   self->s_len = (u32)nlen;
   NYD_OU;
   return self;
}

FL struct n_string *
(n_string_push_buf)(struct n_string *self, char const *buf, uz buflen
      su_DBG_LOC_ARGS_DECL){
   NYD_IN;

   ASSERT(self != NULL);
   ASSERT(buflen == 0 || buf != NULL);

   if(buflen == UZ_MAX)
      buflen = (buf == NULL) ? 0 : su_cs_len(buf);

   if(buflen > 0){
      u32 i;

      self = (n_string_reserve)(self, buflen  su_DBG_LOC_ARGS_USE);
      su_mem_copy(&self->s_dat[i = self->s_len], buf, buflen);
      self->s_len = (i += (u32)buflen);
   }
   NYD_OU;
   return self;
}

FL struct n_string *
(n_string_push_c)(struct n_string *self, char c  su_DBG_LOC_ARGS_DECL){
   NYD_IN;

   ASSERT(self != NULL);

   if(self->s_len + 1 >= self->s_size)
      self = (n_string_reserve)(self, 1  su_DBG_LOC_ARGS_USE);
   self->s_dat[self->s_len++] = c;
   NYD_OU;
   return self;
}

FL struct n_string *
(n_string_unshift_buf)(struct n_string *self, char const *buf, uz buflen
      su_DBG_LOC_ARGS_DECL){
   NYD_IN;

   ASSERT(self != NULL);
   ASSERT(buflen == 0 || buf != NULL);

   if(buflen == UZ_MAX)
      buflen = (buf == NULL) ? 0 : su_cs_len(buf);

   if(buflen > 0){
      self = (n_string_reserve)(self, buflen  su_DBG_LOC_ARGS_USE);
      if(self->s_len > 0)
         su_mem_move(&self->s_dat[buflen], self->s_dat, self->s_len);
      su_mem_copy(self->s_dat, buf, buflen);
      self->s_len += (u32)buflen;
   }
   NYD_OU;
   return self;
}

FL struct n_string *
(n_string_unshift_c)(struct n_string *self, char c  su_DBG_LOC_ARGS_DECL){
   NYD_IN;

   ASSERT(self != NULL);

   if(self->s_len + 1 >= self->s_size)
      self = (n_string_reserve)(self, 1  su_DBG_LOC_ARGS_USE);
   if(self->s_len > 0)
      su_mem_move(&self->s_dat[1], self->s_dat, self->s_len);
   self->s_dat[0] = c;
   ++self->s_len;
   NYD_OU;
   return self;
}

FL struct n_string *
(n_string_insert_buf)(struct n_string *self, uz idx,
      char const *buf, uz buflen  su_DBG_LOC_ARGS_DECL){
   NYD_IN;

   ASSERT(self != NULL);
   ASSERT(buflen == 0 || buf != NULL);
   ASSERT(idx <= self->s_len);

   if(buflen == UZ_MAX)
      buflen = (buf == NULL) ? 0 : su_cs_len(buf);

   if(buflen > 0){
      self = (n_string_reserve)(self, buflen  su_DBG_LOC_ARGS_USE);
      if(self->s_len > 0)
         su_mem_move(&self->s_dat[idx + buflen], &self->s_dat[idx],
            self->s_len - idx);
      su_mem_copy(&self->s_dat[idx], buf, buflen);
      self->s_len += (u32)buflen;
   }
   NYD_OU;
   return self;
}

FL struct n_string *
(n_string_insert_c)(struct n_string *self, uz idx,
      char c  su_DBG_LOC_ARGS_DECL){
   NYD_IN;

   ASSERT(self != NULL);
   ASSERT(idx <= self->s_len);

   if(self->s_len + 1 >= self->s_size)
      self = (n_string_reserve)(self, 1  su_DBG_LOC_ARGS_USE);
   if(self->s_len > 0)
      su_mem_move(&self->s_dat[idx + 1], &self->s_dat[idx], self->s_len - idx);
   self->s_dat[idx] = c;
   ++self->s_len;
   NYD_OU;
   return self;
}

FL struct n_string *
n_string_cut(struct n_string *self, uz idx, uz len){
   NYD_IN;

   ASSERT(self != NULL);
   ASSERT(UZ_MAX - idx > len);
   ASSERT(S32_MAX >= idx + len);
   ASSERT(idx + len <= self->s_len);

   if(len > 0)
      su_mem_move(&self->s_dat[idx], &self->s_dat[idx + len],
         (self->s_len -= len) - idx);
   NYD_OU;
   return self;
}

FL char *
(n_string_cp)(struct n_string *self  su_DBG_LOC_ARGS_DECL){
   char *rv;
   NYD2_IN;

   ASSERT(self != NULL);

   if(self->s_size == 0)
      self = (n_string_reserve)(self, 1  su_DBG_LOC_ARGS_USE);

   (rv = self->s_dat)[self->s_len] = '\0';
   NYD2_OU;
   return rv;
}

FL char const *
n_string_cp_const(struct n_string const *self){
   char const *rv;
   NYD2_IN;

   ASSERT(self != NULL);

   if(self->s_size != 0){
      ((struct n_string*)n_UNCONST(self))->s_dat[self->s_len] = '\0';
      rv = self->s_dat;
   }else
      rv = n_empty;
   NYD2_OU;
   return rv;
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/mx/termcap.c000066400000000000000000000720671352610246600161110ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ Implementation of termcap.h.
 *@ For encapsulation purposes provide a basic foundation even without
 *@ HOWTO add a new non-dynamic command or query:
 *@ - add an entry to enum mx_termcap_{cmd,query}
 *@ - run make-tcap-map.pl
 *@ - update the *termcap* member documentation on changes!
 *@ Bug: in case of clashes of two-letter names terminfo(5) wins.
 *
 * Copyright (c) 2016 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE termcap
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

su_EMPTY_FILE()
#include "mx/termcap.h"
#ifdef mx_HAVE_TCAP

/* If available, curses.h must be included before term.h! */
#ifdef mx_HAVE_TERMCAP
# ifdef mx_HAVE_TERMCAP_CURSES
#  include 
# endif
# include 
#endif

#include 
#include 
#include 

#include "mx/termios.h"
#include "mx/tty.h"

/* Already: #include "mx/termcap.h"*/
/* TODO fake */
#include "su/code-in.h"

/*
 * xxx We are not really compatible with very old and strange terminals since
 * we don't care at all for circumstances indicated by terminal flags: if we
 * find a capability we use it and assume it works.  E.g., if "Co" indicates
 * colours we simply use ISO 6429 also for font attributes etc.  That is,
 * we don't use the ncurses/terminfo interface with all its internal logic.
 */

/* Unless mx_HAVE_TERMINFO or mx_HAVE_TGETENT_NULL_BUF are defined we use this
 * value to space the buffer we pass through to tgetent(3).
 * Since for (such) elder non-emulated terminals really weird things will
 * happen if an entry would require more than 1024 bytes, don't really mind.
 * Use a u16 for storage */
#define a_TERMCAP_ENTRYSIZE_MAX ((2668 + 128) & ~127) /* As of ncurses 6.0 */

CTA(a_TERMCAP_ENTRYSIZE_MAX < U16_MAX,
   "Chosen buffer size exceeds datatype capability");

/* For simplicity we store commands and queries in single continuous control
 * and entry structure arrays: to index queries one has to add
 * mx__TERMCAP_CMD_MAX1 first!  And don't confound with ENTRYSIZE_MAX! */
enum{
   a_TERMCAP_ENT_MAX1 = mx__TERMCAP_CMD_MAX1 + mx__TERMCAP_QUERY_MAX1
};

enum a_termcap_flags{
   a_TERMCAP_F_NONE,
   /* enum mx_termcap_captype values stored here.
    * Note presence of a type in an a_termcap_ent signals initialization */
   a_TERMCAP_F_TYPE_MASK = (1u<<4) - 1,

   a_TERMCAP_F_QUERY = 1u<<4, /* A query rather than a command */
   a_TERMCAP_F_DISABLED = 1u<<5, /* User explicitly disabled command/query */
   a_TERMCAP_F_ALTERN = 1u<<6, /* Not available, but has alternative */
   a_TERMCAP_F_NOENT = 1u<<7, /* Not available */

   /* _cmd() argument interpretion (_T_STR) */
   a_TERMCAP_F_ARG_IDX1 = 1u<<11, /* Argument 1 used, and is an index */
   a_TERMCAP_F_ARG_IDX2 = 1u<<12,
   a_TERMCAP_F_ARG_CNT = 1u<<13, /* .., and is a count */

   a_TERMCAP_F__LAST = a_TERMCAP_F_ARG_CNT
};
CTA(S(u32,mx__TERMCAP_CAPTYPE_MAX1) <= S(u32,a_TERMCAP_F_TYPE_MASK),
   "enum mx_termcap_captype exceeds bit range of a_termcap_flags");

struct a_termcap_control{
   u16 tc_flags;
   /* Offset base into a_termcap_namedat[], which stores the two-letter
    * termcap(5) name directly followed by a NUL terminated terminfo(5) name.
    * A termcap(5) name may consist of two NULs meaning ERR_NOENT,
    * a terminfo(5) name may be empty for the same purpose */
   u16 tc_off;
};
CTA(a_TERMCAP_F__LAST <= U16_MAX,
   "a_termcap_flags exceed storage datatype in a_termcap_control");

struct a_termcap_ent{
   u16 te_flags;
   u16 te_off;    /* in a_termcap_g->tg_dat / value for T_BOOL and T_NUM */
};
CTA(a_TERMCAP_F__LAST <= U16_MAX,
   "a_termcap_flags exceed storage datatype in a_termcap_ent");

/* Structure for extended queries, which don't have an entry constant in
 * mx_termcap_query (to allow free query/binding of keycodes) */
struct a_termcap_ext_ent{
   struct a_termcap_ent tee_super;
   u8 tee__dummy[4];
   struct a_termcap_ext_ent *tee_next;
   /* Resolvable termcap(5)/terminfo(5) name as given by user; the actual data
    * is stored just like for normal queries */
   char tee_name[VFIELD_SIZE(0)];
};

struct a_termcap_g{
   struct a_termcap_ext_ent *tg_ext_ents; /* List of extended queries */
   struct a_termcap_ent tg_ents[a_TERMCAP_ENT_MAX1];
   struct n_string tg_dat; /* Storage for resolved caps */
# if !defined mx_HAVE_TGETENT_NULL_BUF && !defined mx_HAVE_TERMINFO
   char tg_lib_buf[a_TERMCAP_ENTRYSIZE_MAX];
# endif
};

/* Include the constant make-tcap-map.pl output */
#include "mx/gen-tcaps.h" /* $(MX_SRCDIR) */
CTA(sizeof a_termcap_namedat <= U16_MAX,
   "Termcap command and query name data exceed storage datatype");
CTA(a_TERMCAP_ENT_MAX1 == NELEM(a_termcap_control),
   "Control array does not match command/query array to be controlled");

static struct a_termcap_g *a_termcap_g;

/* Query *termcap*, parse it and incorporate into a_termcap_g */
static void a_termcap_init_var(struct str const *termvar);

/* Expand ^CNTRL, \[Ee] and \OCT.  False for parse error and empty results */
static boole a_termcap__strexp(struct n_string *store, char const *ibuf);

/* Initialize any _ent for which we have _F_ALTERN and which isn't yet set */
static void a_termcap_init_altern(void);

#ifdef mx_HAVE_TERMCAP
/* Setup the library we use to work with term */
static boole a_termcap_load(char const *term);

/* Query the capability tcp and fill in tep (upon success) */
static boole a_termcap_ent_query(struct a_termcap_ent *tep,
               char const *cname, u16 cflags);
su_SINLINE boole a_termcap_ent_query_tcp(struct a_termcap_ent *tep,
                  struct a_termcap_control const *tcp);

/* Output PTF for both, termcap(5) and terminfo(5) */
static int a_termcap_putc(int c);
#endif

/* Get mx_termcap_cmd or mx_termcap_query constant belonging to (nlen bytes of)
 * name, -1 if not found.  min and max have to be used to cramp the result */
static s32 a_termcap_enum_for_name(char const *name, uz nlen,
               s32 min, s32 max);
#define a_termcap_cmd_for_name(NB,NL) \
   a_termcap_enum_for_name(NB, NL, 0, mx__TERMCAP_CMD_MAX1)
#define a_termcap_query_for_name(NB,NL) \
   a_termcap_enum_for_name(NB, NL, mx__TERMCAP_CMD_MAX1, a_TERMCAP_ENT_MAX1)

static void
a_termcap_init_var(struct str const *termvar){
   char *cbp_base, *cbp;
   uz i;
   char const *ccp;
   NYD2_IN;

   if(termvar->l >= U16_MAX){
      n_err(_("*termcap*: length excesses internal limit, skipping\n"));
      goto j_leave;
   }

   ASSERT(termvar->s[termvar->l] == '\0');
   i = termvar->l +1;
   cbp_base = n_autorec_alloc(i);
   su_mem_copy(cbp = cbp_base, termvar->s, i);

   for(; (ccp = su_cs_sep_c(&cbp, ',', TRU1)) != NULL;){
      struct a_termcap_ent *tep;
      uz kl;
      char const *v;
      u16 f;

      /* Separate key/value, if any */
      if(/* no empties ccp[0] == '\0' ||*/ ccp[1] == '\0'){
jeinvent:
         n_err(_("*termcap*: invalid entry: %s\n"), ccp);
         continue;
      }
      for(kl = 2, v = &ccp[2];; ++kl, ++v){
         char c = *v;

         if(c == '\0'){
            f = mx_TERMCAP_CAPTYPE_BOOL;
            break;
         }else if(c == '#'){
            f = mx_TERMCAP_CAPTYPE_NUMERIC;
            ++v;
            break;
         }else if(c == '='){
            f = mx_TERMCAP_CAPTYPE_STRING;
            ++v;
            break;
         }
      }

      /* Do we know about this one? */
      /* C99 */{
         struct a_termcap_control const *tcp;
         s32 tci;

         tci = a_termcap_enum_for_name(ccp, kl, 0, a_TERMCAP_ENT_MAX1);
         if(tci < 0){
            /* For key binding purposes, save any given string */
#ifdef mx_HAVE_KEY_BINDINGS
            if((f & a_TERMCAP_F_TYPE_MASK) == mx_TERMCAP_CAPTYPE_STRING){
               struct a_termcap_ext_ent *teep;

               teep = n_alloc(VSTRUCT_SIZEOF(struct a_termcap_ext_ent,
                     tee_name) + kl +1);
               teep->tee_next = a_termcap_g->tg_ext_ents;
               a_termcap_g->tg_ext_ents = teep;
               su_mem_copy(teep->tee_name, ccp, kl);
               teep->tee_name[kl] = '\0';

               tep = &teep->tee_super;
               tep->te_flags = mx_TERMCAP_CAPTYPE_STRING | a_TERMCAP_F_QUERY;
               tep->te_off = (u16)a_termcap_g->tg_dat.s_len;
               if(!a_termcap__strexp(&a_termcap_g->tg_dat, v))
                  tep->te_flags |= a_TERMCAP_F_DISABLED;
               goto jlearned;
            }else
#endif /* mx_HAVE_KEY_BINDINGS */
                  if(n_poption & n_PO_D_V)
               n_err(_("*termcap*: unknown capability: %s\n"), ccp);
            continue;
         }
         i = (uz)tci;

         tcp = &a_termcap_control[i];
         if((tcp->tc_flags & a_TERMCAP_F_TYPE_MASK) != f){
            n_err(_("*termcap*: entry type mismatch: %s\n"), ccp);
            break;
         }
         tep = &a_termcap_g->tg_ents[i];
         tep->te_flags = tcp->tc_flags;
         tep->te_off = (u16)a_termcap_g->tg_dat.s_len;
      }

      if((f & a_TERMCAP_F_TYPE_MASK) == mx_TERMCAP_CAPTYPE_BOOL)
         ;
      else if(*v == '\0')
         tep->te_flags |= a_TERMCAP_F_DISABLED;
      else if((f & a_TERMCAP_F_TYPE_MASK) == mx_TERMCAP_CAPTYPE_NUMERIC){
         if((su_idec_u16_cp(&tep->te_off, v, 0, NULL
                  ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
               ) != su_IDEC_STATE_CONSUMED)
            goto jeinvent;
      }else if(!a_termcap__strexp(&a_termcap_g->tg_dat, v))
         tep->te_flags |= a_TERMCAP_F_DISABLED;
#ifdef mx_HAVE_KEY_BINDINGS
jlearned:
#endif
      if(n_poption & n_PO_D_VV)
         n_err(_("*termcap*: learned %.*s: %s\n"), (int)kl, ccp,
            (tep->te_flags & a_TERMCAP_F_DISABLED ? ""
             : (f & a_TERMCAP_F_TYPE_MASK) == mx_TERMCAP_CAPTYPE_BOOL ? "true"
               : v));
   }
   su_DBG( if(n_poption & n_PO_D_VV)
      n_err("*termcap* parsed: buffer used=%lu\n",
         (ul)a_termcap_g->tg_dat.s_len) );

   /* Catch some inter-dependencies the user may have triggered */
#ifdef mx_HAVE_TERMCAP
   if(a_termcap_g->tg_ents[mx_TERMCAP_CMD_te].te_flags & a_TERMCAP_F_DISABLED)
      a_termcap_g->tg_ents[mx_TERMCAP_CMD_ti].te_flags = a_TERMCAP_F_DISABLED;
   else if(a_termcap_g->tg_ents[mx_TERMCAP_CMD_ti].te_flags &
         a_TERMCAP_F_DISABLED)
      a_termcap_g->tg_ents[mx_TERMCAP_CMD_te].te_flags = a_TERMCAP_F_DISABLED;
#endif

j_leave:
   NYD2_OU;
}

static boole
a_termcap__strexp(struct n_string *store, char const *ibuf){ /* XXX ASCII */
   char c;
   char const *oibuf;
   uz olen;
   NYD2_IN;

   olen = store->s_len;

   for(oibuf = ibuf; (c = *ibuf) != '\0';){
      if(c == '\\'){
         if((c = ibuf[1]) == '\0')
            goto jebsseq;

         if(c == 'E'){
            c = '\033';
            ibuf += 2;
            goto jpush;
         }

         if(su_cs_is_digit(c) && c <= '7'){
            char c2, c3;

            if((c2 = ibuf[2]) == '\0' || !su_cs_is_digit(c2) || c2 > '7' ||
                  (c3 = ibuf[3]) == '\0' || !su_cs_is_digit(c3) || c3 > '7'){
               n_err(_("*termcap*: invalid octal sequence: %s\n"), oibuf);
               goto jerr;
            }
            c -= '0', c2 -= '0', c3 -= '0';
            c <<= 3, c |= c2;
            if((u8)c > 0x1F){
               n_err(_("*termcap*: octal number too large: %s\n"), oibuf);
               goto jerr;
            }
            c <<= 3, c |= c3;
            ibuf += 4;
            goto jpush;
         }
jebsseq:
         n_err(_("*termcap*: invalid reverse solidus \\ sequence: %s\n"),
            oibuf);
         goto jerr;
      }else if(c == '^'){
         if((c = ibuf[1]) == '\0'){
            n_err(_("*termcap*: incomplete ^CNTRL sequence: %s\n"), oibuf);
            goto jerr;
         }
         c = su_cs_to_upper(c) ^ 0x40;
         if((u8)c > 0x1F && c != 0x7F){ /* ASCII C0: 0..1F, 7F */
            n_err(_("*termcap*: invalid ^CNTRL sequence: %s\n"), oibuf);
            goto jerr;
         }
         ibuf += 2;
      }else
         ++ibuf;

jpush:
      store = n_string_push_c(store, c);
   }

   c = (store->s_len != olen) ? '\1' : '\0';
jleave:
   n_string_push_c(store, '\0');
   NYD2_OU;
   return (c != '\0');
jerr:
   store = n_string_trunc(store, olen);
   c = '\0';
   goto jleave;
}

static void
a_termcap_init_altern(void){
   /* We silently ignore user _F_DISABLED requests for those entries for which
    * we have fallback entries, and which we need to ensure proper functioning.
    * I.e., this allows users to explicitly disable some termcap(5) capability
    * and enforce usage of the built-in fallback */
   /* xxx Use table-based approach for fallback strategies */
#define a_OK(CMD) a_OOK(&a_termcap_g->tg_ents[CMD])
#define a_OOK(TEP) \
   ((TEP)->te_flags != 0 && !((TEP)->te_flags & a_TERMCAP_F_NOENT))
#define a_SET(TEP,CMD,ALT) \
   (TEP)->te_flags = a_termcap_control[CMD].tc_flags |\
      ((ALT) ? a_TERMCAP_F_ALTERN : 0)

   struct a_termcap_ent *tep;
   NYD2_IN;
   UNUSED(tep);

   /* For simplicity in the rest of this file null flags of disabled commands,
    * as we won't check and try to lazy query any command */
   /* C99 */{
      uz i;

      for(i = mx__TERMCAP_CMD_MAX1;;){
         if(i-- == 0)
            break;
         if((tep = &a_termcap_g->tg_ents[i])->te_flags & a_TERMCAP_F_DISABLED)
            tep->te_flags = 0;
      }
   }

#ifdef mx_HAVE_MLE
   /* ce == ch + [:SPACE:] (start column specified by argument) */
   tep = &a_termcap_g->tg_ents[mx_TERMCAP_CMD_ce];
   if(!a_OOK(tep))
      a_SET(tep, mx_TERMCAP_CMD_ce, TRU1);

   /* ch == cr[\r] + nd[:\033C:] */
   tep = &a_termcap_g->tg_ents[mx_TERMCAP_CMD_ch];
   if(!a_OOK(tep))
      a_SET(tep, mx_TERMCAP_CMD_ch, TRU1);

   /* cr == \r */
   tep = &a_termcap_g->tg_ents[mx_TERMCAP_CMD_cr];
   if(!a_OOK(tep)){
      a_SET(tep, mx_TERMCAP_CMD_cr, FAL0);
      tep->te_off = (u16)a_termcap_g->tg_dat.s_len;
      n_string_push_c(n_string_push_c(&a_termcap_g->tg_dat, '\r'), '\0');
   }

   /* le == \b */
   tep = &a_termcap_g->tg_ents[mx_TERMCAP_CMD_le];
   if(!a_OOK(tep)){
      a_SET(tep, mx_TERMCAP_CMD_le, FAL0);
      tep->te_off = (u16)a_termcap_g->tg_dat.s_len;
      n_string_push_c(n_string_push_c(&a_termcap_g->tg_dat, '\b'), '\0');
   }

   /* nd == \033[C (we may not fail, anyway, so use xterm sequence default) */
   tep = &a_termcap_g->tg_ents[mx_TERMCAP_CMD_nd];
   if(!a_OOK(tep)){
      a_SET(tep, mx_TERMCAP_CMD_nd, FAL0);
      tep->te_off = (u16)a_termcap_g->tg_dat.s_len;
      n_string_push_buf(&a_termcap_g->tg_dat, "\033[C", sizeof("\033[C"));
   }

# ifdef mx_HAVE_TERMCAP
   /* cl == ho+cd */
   tep = &a_termcap_g->tg_ents[mx_TERMCAP_CMD_cl];
   if(!a_OOK(tep)){
      if(a_OK(mx_TERMCAP_CMD_cd) && a_OK(mx_TERMCAP_CMD_ho))
         a_SET(tep, mx_TERMCAP_CMD_cl, TRU1);
   }
# endif
#endif /* mx_HAVE_MLE */

   NYD2_OU;
#undef a_OK
#undef a_OOK
#undef a_SET
}

#ifdef mx_HAVE_TERMCAP
# ifdef mx_HAVE_TERMINFO
static boole
a_termcap_load(char const *term){
   boole rv;
   int err;
   NYD2_IN;

   if(!(rv = (setupterm(term, fileno(mx_tty_fp), &err) == OK)))
      n_err(_("Unknown ${TERM}inal, using only *termcap*: %s\n"), term);
   NYD2_OU;
   return rv;
}

static boole
a_termcap_ent_query(struct a_termcap_ent *tep,
      char const *cname, u16 cflags){
   boole rv;
   NYD2_IN;
   ASSERT(!(n_psonce & n_PSO_TERMCAP_DISABLE));

   if(UNLIKELY(*cname == '\0'))
      rv = FAL0;
   else switch((tep->te_flags = cflags) & a_TERMCAP_F_TYPE_MASK){
   case mx_TERMCAP_CAPTYPE_BOOL:
      if(!(rv = (tigetflag(cname) > 0)))
         tep->te_flags |= a_TERMCAP_F_NOENT;
      tep->te_off = rv;
      break;
   case mx_TERMCAP_CAPTYPE_NUMERIC:{
      int r = tigetnum(cname);

      if((rv = (r >= 0)))
         tep->te_off = (u16)MIN(U16_MAX, r);
      else
         tep->te_flags |= a_TERMCAP_F_NOENT;
      }break;
   default:
   case mx_TERMCAP_CAPTYPE_STRING:{
      char *cp;

      cp = tigetstr(cname);
      if((rv = (cp != NULL && cp != (char*)-1))){
         tep->te_off = (u16)a_termcap_g->tg_dat.s_len;
         n_string_push_buf(&a_termcap_g->tg_dat, cp, su_cs_len(cp) +1);
      }else
         tep->te_flags |= a_TERMCAP_F_NOENT;
      }break;
   }
   NYD2_OU;
   return rv;
}

su_SINLINE boole
a_termcap_ent_query_tcp(struct a_termcap_ent *tep,
      struct a_termcap_control const *tcp){
   ASSERT(!(n_psonce & n_PSO_TERMCAP_DISABLE));
   return a_termcap_ent_query(tep, &a_termcap_namedat[tcp->tc_off] + 2,
      tcp->tc_flags);
}

# else /* mx_HAVE_TERMINFO */
static boole
a_termcap_load(char const *term){
   boole rv;
   NYD2_IN;

   /* ncurses may return -1 */
# ifndef mx_HAVE_TGETENT_NULL_BUF
#  define a_BUF &a_termcap_g->tg_lib_buf[0]
# else
#  define a_BUF NULL
# endif
   if(!(rv = tgetent(a_BUF, term) > 0))
      n_err(_("Unknown ${TERM}inal, using only *termcap*: %s\n"), term);
# undef a_BUF
   NYD2_OU;
   return rv;
}

static boole
a_termcap_ent_query(struct a_termcap_ent *tep,
      char const *cname, u16 cflags){
   boole rv;
   NYD2_IN;
   ASSERT(!(n_psonce & n_PSO_TERMCAP_DISABLE));

   if(UNLIKELY(*cname == '\0'))
      rv = FAL0;
   else switch((tep->te_flags = cflags) & a_TERMCAP_F_TYPE_MASK){
   case mx_TERMCAP_CAPTYPE_BOOL:
      if(!(rv = (tgetflag(cname) > 0)))
         tep->te_flags |= a_TERMCAP_F_NOENT;
      tep->te_off = rv;
      break;
   case mx_TERMCAP_CAPTYPE_NUMERIC:{
      int r = tgetnum(cname);

      if((rv = (r >= 0)))
         tep->te_off = (u16)MIN(U16_MAX, r);
      else
         tep->te_flags |= a_TERMCAP_F_NOENT;
      }break;
   default:
   case mx_TERMCAP_CAPTYPE_STRING:{
# ifndef mx_HAVE_TGETENT_NULL_BUF
      char buf_base[a_TERMCAP_ENTRYSIZE_MAX], *buf = &buf_base[0];
#  define a_BUF &buf
# else
#  define a_BUF NULL
# endif
      char *cp;

      if((rv = ((cp = tgetstr(cname, a_BUF)) != NULL))){
         tep->te_off = (u16)a_termcap_g->tg_dat.s_len;
         n_string_push_buf(&a_termcap_g->tg_dat, cp, su_cs_len(cp) +1);
# undef a_BUF
      }else
         tep->te_flags |= a_TERMCAP_F_NOENT;
      }break;
   }
   NYD2_OU;
   return rv;
}

su_SINLINE boole
a_termcap_ent_query_tcp(struct a_termcap_ent *tep,
      struct a_termcap_control const *tcp){
   ASSERT(!(n_psonce & n_PSO_TERMCAP_DISABLE));
   return a_termcap_ent_query(tep, &a_termcap_namedat[tcp->tc_off],
      tcp->tc_flags);
}
# endif /* !mx_HAVE_TERMINFO */

static int
a_termcap_putc(int c){
   return putc(c, mx_tty_fp);
}
#endif /* mx_HAVE_TERMCAP */

static s32
a_termcap_enum_for_name(char const *name, uz nlen, s32 min, s32 max){
   struct a_termcap_control const *tcp;
   char const *cnam;
   s32 rv;
   NYD2_IN;

   /* Prefer terminfo(5) names */
   for(rv = max;;){
      if(rv-- == min){
         rv = -1;
         break;
      }

      tcp = &a_termcap_control[(u32)rv];
      cnam = &a_termcap_namedat[tcp->tc_off];
      if(cnam[2] != '\0'){
         char const *xcp = cnam + 2;

         if(nlen == su_cs_len(xcp) && !su_mem_cmp(xcp, name, nlen))
            break;
      }
      if(nlen == 2 && cnam[0] == name[0] && cnam[1] == name[1])
         break;
   }
   NYD2_OU;
   return rv;
}

void
mx_termcap_init(void){
   struct mx_termcap_value tv;
   struct str termvar;
   char const *ccp;
   NYD_IN;

   ASSERT(n_psonce & n_PSO_TTYANY);

   a_termcap_g = n_alloc(sizeof *a_termcap_g);
   a_termcap_g->tg_ext_ents = NULL;
   su_mem_set(&a_termcap_g->tg_ents[0], 0, sizeof(a_termcap_g->tg_ents));
   if((ccp = ok_vlook(termcap)) != NULL)
      termvar.l = su_cs_len(termvar.s = n_UNCONST(ccp));
   else
      /*termvar.s = NULL,*/ termvar.l = 0;
   n_string_reserve(n_string_creat(&a_termcap_g->tg_dat),
      ((termvar.l + (256 - 64)) & ~127));

   if(termvar.l > 0)
      a_termcap_init_var(&termvar);

   if(ok_blook(termcap_disable))
      n_psonce |= n_PSO_TERMCAP_DISABLE;
#ifdef mx_HAVE_TERMCAP
   else if((ccp = ok_vlook(TERM)) == NULL){
      n_err(_("Environment variable $TERM not set, using only *termcap*\n"));
      n_psonce |= n_PSO_TERMCAP_DISABLE;
   }else if(!a_termcap_load(ccp))
      n_psonce |= n_PSO_TERMCAP_DISABLE;
   else{
      /* Query termcap(5) for each command slot that is not yet set */
      struct a_termcap_ent *tep;
      uz i;

      for(i = mx__TERMCAP_CMD_MAX1;;){
         if(i-- == 0)
            break;
         if((tep = &a_termcap_g->tg_ents[i])->te_flags == 0)
            a_termcap_ent_query_tcp(tep, &a_termcap_control[i]);
      }
   }
#endif /* mx_HAVE_TERMCAP */

   a_termcap_init_altern();

#ifdef mx_HAVE_TERMCAP
   if(a_termcap_g->tg_ents[mx_TERMCAP_CMD_te].te_flags != 0 &&
         ok_blook(termcap_ca_mode))
      n_psonce |= n_PSO_TERMCAP_CA_MODE;
#endif

   /* TODO We do not handle !mx_TERMCAP_QUERY_sam in this software! */
   if(!mx_termcap_query(mx_TERMCAP_QUERY_am, &tv) ||
         mx_termcap_query(mx_TERMCAP_QUERY_xenl, &tv)){
      n_psonce |= n_PSO_TERMCAP_FULLWIDTH;

      /* Since termcap was not initialized when we did TERMIOS_SETUP_TERMSIZE
       * we need/should adjust the found setting to reality (without causing
       * a synthesized SIGWINCH or something even more expensive that is) */
      if(mx_termios_dimen.tiosd_width > 0)
         ++mx_termios_dimen.tiosd_width;
   }

   mx_TERMCAP_RESUME(TRU1);
   NYD_OU;
}

void
mx_termcap_destroy(void){
   NYD_IN;
   ASSERT(a_termcap_g != NULL);

   mx_TERMCAP_SUSPEND(TRU1);

#ifdef mx_HAVE_DEBUG
   /* C99 */{
      struct a_termcap_ext_ent *tmp;

      while((tmp = a_termcap_g->tg_ext_ents) != NULL){
         a_termcap_g->tg_ext_ents = tmp->tee_next;
         n_free(tmp);
      }
   }
   n_string_gut(&a_termcap_g->tg_dat);
   n_free(a_termcap_g);
   a_termcap_g = NULL;
#endif
   NYD_OU;
}

#ifdef mx_HAVE_TERMCAP
void
mx_termcap_resume(boole complete){
   NYD_IN;
   if(a_termcap_g != NULL && !(n_psonce & n_PSO_TERMCAP_DISABLE)){
      if(complete && (n_psonce & n_PSO_TERMCAP_CA_MODE))
         mx_termcap_cmdx(mx_TERMCAP_CMD_ti);
      mx_termcap_cmdx(mx_TERMCAP_CMD_ks);
      fflush(mx_tty_fp);
   }
   NYD_OU;
}

void
mx_termcap_suspend(boole complete){
   NYD_IN;
   if(a_termcap_g != NULL && !(n_psonce & n_PSO_TERMCAP_DISABLE)){
      mx_termcap_cmdx(mx_TERMCAP_CMD_ke);
      if(complete && (n_psonce & n_PSO_TERMCAP_CA_MODE))
         mx_termcap_cmdx(mx_TERMCAP_CMD_te);
      fflush(mx_tty_fp);
   }
   NYD_OU;
}
#endif /* mx_HAVE_TERMCAP */

sz
mx_termcap_cmd(enum mx_termcap_cmd cmd, sz a1, sz a2){
   /* Commands are not lazy queried */
   struct a_termcap_ent const *tep;
   enum a_termcap_flags flags;
   sz rv;
   NYD2_IN;
   UNUSED(a1);
   UNUSED(a2);

   rv = FAL0;
   if(a_termcap_g == NULL)
      goto jleave;

   flags = cmd & ~mx__TERMCAP_CMD_MASK;
   cmd &= mx__TERMCAP_CMD_MASK;
   tep = a_termcap_g->tg_ents;

   if((flags & mx_TERMCAP_CMD_FLAG_CA_MODE) &&
         !(n_psonce & n_PSO_TERMCAP_CA_MODE))
      rv = TRU1;
   else if((tep += cmd)->te_flags == 0 || (tep->te_flags & a_TERMCAP_F_NOENT))
      rv = TRUM1;
   else if(!(tep->te_flags & a_TERMCAP_F_ALTERN)){
      char const *cp;

      ASSERT((tep->te_flags & a_TERMCAP_F_TYPE_MASK) ==
         mx_TERMCAP_CAPTYPE_STRING);

      cp = &a_termcap_g->tg_dat.s_dat[tep->te_off];

#ifdef mx_HAVE_TERMCAP
      if(tep->te_flags & (a_TERMCAP_F_ARG_IDX1 | a_TERMCAP_F_ARG_IDX2)){
         if(n_psonce & n_PSO_TERMCAP_DISABLE){
            if(n_poption & n_PO_D_V){
               char const *cnam = &a_termcap_namedat[
                     a_termcap_control[cmd].tc_off];

               if(cnam[2] != '\0')
                  cnam += 2;
               n_err(_("*termcap-disable*d (/$TERM not set/unknown): "
                  "can't perform CAP: %s\n"), cnam);
            }
            goto jleave;
         }

         /* Follow Thomas Dickey's advise on pre-va_arg prototypes, add 0s */
# ifdef mx_HAVE_TERMINFO
         if((cp = tparm(cp, a1, a2, 0,0,0,0,0,0,0)) == NULL)
            goto jleave;
# else
         /* curs_termcap.3:
          * The \fBtgoto\fP function swaps the order of parameters.
          * It does this also for calls requiring only a single parameter.
          * In that case, the first parameter is merely a placeholder. */
         if(!(tep->te_flags & a_TERMCAP_F_ARG_IDX2)){
            a2 = a1;
            a1 = (u32)-1;
         }
         if((cp = tgoto(cp, (int)a1, (int)a2)) == NULL)
            goto jleave;
# endif
      }
#endif /* mx_HAVE_TERMCAP */

      for(;;){
#ifdef mx_HAVE_TERMCAP
         if(!(n_psonce & n_PSO_TERMCAP_DISABLE)){
            if(tputs(cp, 1, &a_termcap_putc) != OK)
               break;
         }else
#endif
               if(fputs(cp, mx_tty_fp) == EOF)
            break;
         if(!(tep->te_flags & a_TERMCAP_F_ARG_CNT) || --a1 <= 0){
            rv = TRU1;
            break;
         }
      }
      goto jflush;
   }else{
      switch(cmd){
      default:
         rv = TRUM1;
         break;

#ifdef mx_HAVE_MLE
      case mx_TERMCAP_CMD_ce: /* ce == ch + [:SPACE:] */
         if(a1 > 0)
            --a1;
         if((rv = mx_termcap_cmd(mx_TERMCAP_CMD_ch, a1, 0)) > 0){
            for(a2 = mx_termios_dimen.tiosd_width - a1 - 1; a2 > 0; --a2)
               if(putc(' ', mx_tty_fp) == EOF){
                  rv = FAL0;
                  break;
               }
            if(rv && mx_termcap_cmd(mx_TERMCAP_CMD_ch, a1, -1) != TRU1)
               rv = FAL0;
         }
         break;
      case mx_TERMCAP_CMD_ch: /* ch == cr + nd */
         rv = mx_termcap_cmdx(mx_TERMCAP_CMD_cr);
         if(rv > 0 && a1 > 0){
            rv = mx_termcap_cmd(mx_TERMCAP_CMD_nd, a1, -1);
         }
         break;
# ifdef mx_HAVE_TERMCAP
      case mx_TERMCAP_CMD_cl: /* cl = ho + cd */
         rv = mx_termcap_cmdx(mx_TERMCAP_CMD_ho);
         if(rv > 0)
            rv = mx_termcap_cmdx(mx_TERMCAP_CMD_cd | flags);
         break;
# endif
#endif /* mx_HAVE_MLE */
      }

jflush:
      if(flags & mx_TERMCAP_CMD_FLAG_FLUSH)
         fflush(mx_tty_fp);
      if(ferror(mx_tty_fp))
         rv = FAL0;
   }

jleave:
   NYD2_OU;
   return rv;
}

boole
mx_termcap_query(enum mx_termcap_query query, struct mx_termcap_value *tvp){
   /* Queries are lazy queried upon request */
   /* XXX mx_termcap_query(): boole handling suboptimal, tvp used on success */
   struct a_termcap_ent const *tep;
   boole rv;
   NYD2_IN;

   ASSERT(tvp != NULL);

   rv = FAL0;
   if(a_termcap_g == NULL)
      goto jleave;

   /* Is it a built-in query? */
   if(query != mx__TERMCAP_QUERY_MAX1){
      tep = &a_termcap_g->tg_ents[mx__TERMCAP_CMD_MAX1 + query];

      if(tep->te_flags == 0
#ifdef mx_HAVE_TERMCAP
            && ((n_psonce & n_PSO_TERMCAP_DISABLE) ||
               !a_termcap_ent_query_tcp(n_UNCONST(tep),
                  &a_termcap_control[mx__TERMCAP_CMD_MAX1 + query]))
#endif
      )
         goto jleave;
   }else{
#ifdef mx_HAVE_TERMCAP
      uz nlen;
#endif
      struct a_termcap_ext_ent *teep;
      char const *ndat = tvp->tv_data.tvd_string;

      for(teep = a_termcap_g->tg_ext_ents; teep != NULL; teep = teep->tee_next)
         if(!su_cs_cmp(teep->tee_name, ndat)){
            tep = &teep->tee_super;
            goto jextok;
         }

#ifdef mx_HAVE_TERMCAP
      if(n_psonce & n_PSO_TERMCAP_DISABLE)
#endif
         goto jleave;
#ifdef mx_HAVE_TERMCAP
      nlen = su_cs_len(ndat) +1;
      teep = n_alloc(VSTRUCT_SIZEOF(struct a_termcap_ext_ent, tee_name) +
            nlen);
      tep = &teep->tee_super;
      teep->tee_next = a_termcap_g->tg_ext_ents;
      a_termcap_g->tg_ext_ents = teep;
      su_mem_copy(teep->tee_name, ndat, nlen);

      if(!a_termcap_ent_query(n_UNCONST(tep), ndat,
               mx_TERMCAP_CAPTYPE_STRING | a_TERMCAP_F_QUERY))
         goto jleave;
#endif
jextok:;
   }

   if(tep->te_flags & a_TERMCAP_F_NOENT)
      goto jleave;

   rv = (tep->te_flags & a_TERMCAP_F_ALTERN) ? TRUM1 : TRU1;

   switch((tvp->tv_captype = tep->te_flags & a_TERMCAP_F_TYPE_MASK)){
   case mx_TERMCAP_CAPTYPE_BOOL:
      tvp->tv_data.tvd_bool = (boole)tep->te_off;
      break;
   case mx_TERMCAP_CAPTYPE_NUMERIC:
      tvp->tv_data.tvd_numeric = (u32)tep->te_off;
      break;
   default:
   case mx_TERMCAP_CAPTYPE_STRING:
      tvp->tv_data.tvd_string = a_termcap_g->tg_dat.s_dat + tep->te_off;
      break;
   }
jleave:
   NYD2_OU;
   return rv;
}

#ifdef mx_HAVE_KEY_BINDINGS
s32
mx_termcap_query_for_name(char const *name, enum mx_termcap_captype type){
   s32 rv;
   NYD2_IN;

   if((rv = a_termcap_query_for_name(name, su_cs_len(name))) >= 0){
      struct a_termcap_control const *tcp = &a_termcap_control[(u32)rv];

      if(type != mx_TERMCAP_CAPTYPE_NONE &&
            (tcp->tc_flags & a_TERMCAP_F_TYPE_MASK) != type)
         rv = -2;
      else
         rv -= mx__TERMCAP_CMD_MAX1;
   }
   NYD2_OU;
   return rv;
}

char const *
mx_termcap_name_of_query(enum mx_termcap_query query){
   char const *rv;
   NYD2_IN;

   rv = &a_termcap_namedat[
         a_termcap_control[mx__TERMCAP_CMD_MAX1 + query].tc_off + 2];
   NYD2_OU;
   return rv;
}
#endif /* mx_HAVE_KEY_BINDINGS */

#include "su/code-ou.h"
#endif /* mx_HAVE_TCAP */
/* s-it-mode */
s-nail-14.9.15/src/mx/termios.c000066400000000000000000000474011352610246600161320ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ Implementation of termios.h.
 *@ FIXME everywhere: tcsetattr() generates SIGTTOU when we're not in
 *@ FIXME foreground pgrp, and can fail with EINTR!!
 *@ TODO . SIGINT during HANDS_OFF reaches us nonetheless.
 *@ TODO . _HANDS_OFF as well as the stack based approach as such is nonsense.
 *@ TODO   It might work well for this MUA, but in general termios_ctx should
 *@ TODO   be a public struct with a lifetime, and activate/suspend methods.
 *@ TODO   It shall be up to the callers how this is managed, we can emit some
 *@ TODO   state events to let them decide for good.
 *@ TODO   Handling childs which take over terminal via HANDS_OFF is bad:
 *@ TODO   instead, we need to have a notion of background and foreground,
 *@ TODO   and ensure the terminal is in normal mode when going backward.
 *@ TODO   What the childs do is up to them, managing them in stack: impossible
 *
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE termios
#define mx_SOURCE
#define mx_SOURCE_TERMIOS

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

#include 

#include 

#include 
#include 

#include "mx/sigs.h"
#include "mx/tty.h"

#include "mx/termios.h"
#include "su/code-in.h"

/* Problem: VAL_ configuration values are strings, we need numbers */
#define a_TERMIOS_DEFAULT_HEIGHT \
   (VAL_HEIGHT[1] == '\0' ? (VAL_HEIGHT[0] - '0') \
   : (VAL_HEIGHT[2] == '\0' \
      ? ((VAL_HEIGHT[0] - '0') * 10 + (VAL_HEIGHT[1] - '0')) \
      : (((VAL_HEIGHT[0] - '0') * 10 + (VAL_HEIGHT[1] - '0')) * 10 + \
         (VAL_HEIGHT[2] - '0'))))
#define a_TERMIOS_DEFAULT_WIDTH \
   (VAL_WIDTH[1] == '\0' ? (VAL_WIDTH[0] - '0') \
   : (VAL_WIDTH[2] == '\0' \
      ? ((VAL_WIDTH[0] - '0') * 10 + (VAL_WIDTH[1] - '0')) \
      : (((VAL_WIDTH[0] - '0') * 10 + (VAL_WIDTH[1] - '0')) * 10 + \
         (VAL_WIDTH[2] - '0'))))

#ifdef SIGWINCH
# define a_TERMIOS_SIGWINCH SIGWINCH
#else
# define a_TERMIOS_SIGWINCH -1
#endif

struct a_termios_env{
   struct a_termios_env *tiose_prev;
   u8 tiose_cmd;
   u8 tiose_a1;
   boole tiose_suspended;
   u8 tiose__pad[1];
   /*s32 tiose_pgrp;*/ /* In HANDS_OFF mode */
   su_64( u8 tiose__pad2[4]; )
   mx_termios_on_state_change tiose_on_state_change;
   up tiose_osc_cookie;
   struct termios tiose_state;
};

struct a_termios_g{
   struct a_termios_env *tiosg_envp;
   /* If outermost == normal state; used as init switch, too */
   struct a_termios_env *tiosg_normal;
   struct a_termios_env *tiosg_pend_free;
   /*s32 tiosg_pgrp;
    *u8 tiosg__pad[4];*/
   n_sighdl_t tiosg_otstp;
   n_sighdl_t tiosg_ottin;
   n_sighdl_t tiosg_ottou;
   n_sighdl_t tiosg_ocont;
#if a_TERMIOS_SIGWINCH != -1
   n_sighdl_t tiosg_owinch;
#endif
   /* Remaining signals only when in password/raw mode */
   n_sighdl_t tiosg_ohup;
   n_sighdl_t tiosg_oint;
   n_sighdl_t tiosg_oquit;
   n_sighdl_t tiosg_oterm;
   struct a_termios_env tiosg_env_base;
};

static struct a_termios_g a_termios_g;

struct mx_termios_dimension mx_termios_dimen;

/* */
static void a_termios_sig_adjust(boole condome);

/* */
static void a_termios_onsig(int sig);

/* */
SINLINE boole a_termios_norm_query(void);

/* Do the system-dependent dance on getting used to terminal dimension */
static void a_termios_dimen_query(struct mx_termios_dimension *tiosdp);

static void
a_termios_sig_adjust(boole condome){
   NYD2_IN;

   if(condome){
      a_termios_g.tiosg_ohup = safe_signal(SIGHUP, &a_termios_onsig);
      a_termios_g.tiosg_oint = safe_signal(SIGINT, &a_termios_onsig);
      a_termios_g.tiosg_oquit = safe_signal(SIGQUIT, &a_termios_onsig);
      a_termios_g.tiosg_oterm = safe_signal(SIGTERM, &a_termios_onsig);
   }else{
      safe_signal(SIGHUP, a_termios_g.tiosg_ohup);
      safe_signal(SIGINT, a_termios_g.tiosg_oint);
      safe_signal(SIGQUIT, a_termios_g.tiosg_oquit);
      safe_signal(SIGTERM, a_termios_g.tiosg_oterm);
   }
   NYD2_OU;
}

SINLINE boole
a_termios_norm_query(void){
   boole rv;
   /*NYD2_IN;*/

   rv = (tcgetattr(fileno(mx_tty_fp),
         &a_termios_g.tiosg_normal->tiose_state) == 0);
   /* XXX always set ECHO and ICANON in our "normal" canonical state */
   a_termios_g.tiosg_normal->tiose_state.c_lflag |= ECHO | ICANON;
   /*NYD2_OU;*/
   return rv;
}

static void
a_termios_onsig(int sig){
   n_sighdl_t oact, myact;
   sigset_t nset;
   struct a_termios_env *tiosep;
   boole jobsig, dopop;
   NYD; /* Signal handler */

   if(sig == a_TERMIOS_SIGWINCH)
      goto jsigwinch;

#define a_X(N,X,Y) \
   case SIG ## N: oact = a_termios_g.tiosg_o ## X; jobsig = Y; break;

   switch(sig){
   default:
   a_X(TSTP, tstp, TRU1)
   a_X(TTIN, ttin, TRU1)
   a_X(TTOU, ttou, TRU1)
   a_X(CONT, cont, TRU1)
   a_X(HUP, hup, FAL0)
   a_X(INT, int, FAL0)
   a_X(QUIT, quit, FAL0)
   a_X(TERM, term, FAL0)
   }

#undef a_X

   dopop = FAL0;
   tiosep = a_termios_g.tiosg_envp;

   if(!jobsig || sig != SIGCONT){
      if(!tiosep->tiose_suspended){
         tiosep->tiose_suspended = TRU1;

         if(tiosep->tiose_on_state_change != NIL){
            dopop = (*tiosep->tiose_on_state_change)(
                  tiosep->tiose_osc_cookie,
                  (mx_TERMIOS_STATE_SUSPEND | mx_TERMIOS_STATE_SIGNAL |
                   (jobsig ? mx_TERMIOS_STATE_JOB_SIGNAL : 0)), sig);
            if(dopop)
               a_termios_g.tiosg_envp = tiosep->tiose_prev;
         }

         if(tiosep->tiose_cmd != mx_TERMIOS_CMD_NORMAL)
            (void)tcsetattr(fileno(mx_tty_fp), TCSAFLUSH,
               &a_termios_g.tiosg_normal->tiose_state);
      }
   }

   if(jobsig || (tiosep->tiose_cmd != mx_TERMIOS_CMD_HANDS_OFF &&
            oact != SIG_DFL && oact != SIG_IGN && oact != SIG_ERR)){
      myact = safe_signal(sig, oact);

      sigemptyset(&nset);
      sigaddset(&nset, sig);
      sigprocmask(SIG_UNBLOCK, &nset, NIL);
      n_raise(sig);
      sigprocmask(SIG_BLOCK, &nset, NIL);

      safe_signal(sig, myact);

      /* When we come here we shall continue */
      if(!dopop && tiosep->tiose_suspended){
         tiosep->tiose_suspended = FAL0;

         if(tiosep->tiose_cmd != mx_TERMIOS_CMD_HANDS_OFF){
            /* Requery our notion of what is "normal", so that possible user
             * adjustments which happened in the meantime are kept */
            a_termios_norm_query();

            if(tiosep->tiose_cmd != mx_TERMIOS_CMD_NORMAL)
               (void)tcsetattr(fileno(mx_tty_fp), TCSADRAIN,
                  &tiosep->tiose_state);
         }

         if(tiosep->tiose_on_state_change != NIL)
            (*tiosep->tiose_on_state_change)(tiosep->tiose_osc_cookie,
               (mx_TERMIOS_STATE_RESUME | mx_TERMIOS_STATE_SIGNAL |
                (jobsig ? mx_TERMIOS_STATE_JOB_SIGNAL : 0)), sig);
      }

#if a_TERMIOS_SIGWINCH == -1
      if(sig == SIGCONT)
         goto jsigwinch;
#endif
   }

   /* If we shall pop this level link context in a list for later freeing in
    * a more regular context */
   if(dopop){
      tiosep->tiose_prev = a_termios_g.tiosg_pend_free;
      a_termios_g.tiosg_pend_free = tiosep;
   }

jleave:
   return;

jsigwinch:
   if(n_psonce & n_PSO_INTERACTIVE){
      a_termios_dimen_query(&mx_termios_dimen);
      if(mx_termios_dimen.tiosd_width > 1 &&
            !(n_psonce & n_PSO_TERMCAP_FULLWIDTH))
         --mx_termios_dimen.tiosd_width;
      n_pstate |= n_PS_SIGWINCH_PEND;
   }
   goto jleave;
}

static void
a_termios_dimen_query(struct mx_termios_dimension *tiosdp){
   struct termios tbuf;
#if defined mx_HAVE_TCGETWINSIZE || defined TIOCGWINSZ
   struct winsize ws;
#elif defined TIOCGSIZE
   struct ttysize ts;
#else
# error One of TCGETWINSIZE, TIOCGWINSZ and TIOCGSIZE
#endif
   /*NYD2_IN;*/

#ifdef mx_HAVE_TCGETWINSIZE
   if(tcgetwinsize(fileno(mx_tty_fp), &ws) == -1)
      ws.ws_col = ws.ws_row = 0;
#elif defined TIOCGWINSZ
   if(ioctl(fileno(mx_tty_fp), TIOCGWINSZ, &ws) == -1)
      ws.ws_col = ws.ws_row = 0;
#elif defined TIOCGSIZE
   if(ioctl(fileno(mx_tty_fp), TIOCGSIZE, &ws) == -1)
      ts.ts_lines = ts.ts_cols = 0;
#endif

#if defined mx_HAVE_TCGETWINSIZE || defined TIOCGWINSZ
   if(ws.ws_row != 0)
      tiosdp->tiosd_height = tiosdp->tiosd_real_height = ws.ws_row;
#elif defined TIOCGSIZE
   if(ts.ts_lines != 0)
      tiosdp->tiosd_height = tiosdp->tiosd_real_height = ts.ts_lines;
#endif
   else{
      speed_t ospeed;

      /* We use the following algorithm for the fallback height:
       * If baud rate < 1200, use  9
       * If baud rate = 1200, use 14
       * If baud rate > 1200, use VAL_HEIGHT */
      ospeed = ((tcgetattr(fileno(mx_tty_fp), &tbuf) == -1)
            ? B9600 : cfgetospeed(&tbuf));

      if(ospeed < B1200)
         tiosdp->tiosd_height = 9;
      else if(ospeed == B1200)
         tiosdp->tiosd_height = 14;
      else
         tiosdp->tiosd_height = a_TERMIOS_DEFAULT_HEIGHT;

      tiosdp->tiosd_real_height = a_TERMIOS_DEFAULT_HEIGHT;
   }

   if(0 == (
#if defined mx_HAVE_TCGETWINSIZE || defined TIOCGWINSZ
       tiosdp->tiosd_width = tiosdp->tiosd_real_width = ws.ws_col
#elif defined TIOCGSIZE
       tiosdp->tiosd_width = tiosdp->tiosd_real_width = ts.ts_cols
#endif
         ))
      tiosdp->tiosd_width = tiosdp->tiosd_real_width = a_TERMIOS_DEFAULT_WIDTH;

   /*NYD2_OU;*/
}

void
mx_termios_controller_setup(enum mx_termios_setup what){
   sigset_t nset, oset;
   NYD_IN;

   if(what == mx_TERMIOS_SETUP_STARTUP){
      a_termios_g.tiosg_envp = &a_termios_g.tiosg_env_base;

      sigfillset(&nset);
      sigprocmask(SIG_BLOCK, &nset, &oset);

      a_termios_g.tiosg_otstp = safe_signal(SIGTSTP, &a_termios_onsig);
      a_termios_g.tiosg_ottin = safe_signal(SIGTTIN, &a_termios_onsig);
      a_termios_g.tiosg_ottou = safe_signal(SIGTTOU, &a_termios_onsig);
      a_termios_g.tiosg_ocont = safe_signal(SIGCONT, &a_termios_onsig);

      /* This assumes oset contains nothing but SIGCHLD, so to say */
      sigdelset(&oset, SIGTSTP);
      sigdelset(&oset, SIGTTIN);
      sigdelset(&oset, SIGTTOU);
      sigdelset(&oset, SIGCONT);
      sigprocmask(SIG_SETMASK, &oset, NIL);
   }else{
      /* Semantics are a bit hairy and cast in stone in the manual, and
       * scattered all over the place, at least $COLUMNS, $LINES, -# */
      if(!su_state_has(su_STATE_REPRODUCIBLE) &&
            ((n_psonce & n_PSO_INTERACTIVE) ||
               ((n_psonce & n_PSO_TTYANY) && (n_poption & n_PO_BATCH_FLAG)))){
         /* (Also) POSIX: LINES and COLUMNS always override.  These variables
          * are ensured to be positive numbers, so no checking */
         u32 l, c;
         char const *cp;
         boole hadl, hadc;

         ASSERT(mx_termios_dimen.tiosd_height == 0);
         ASSERT(mx_termios_dimen.tiosd_real_height == 0);
         ASSERT(mx_termios_dimen.tiosd_width == 0);
         ASSERT(mx_termios_dimen.tiosd_real_width == 0);
         if(n_psonce & n_PSO_INTERACTIVE){
            /* XXX Yet WINCH after WINCH/CONT, but see POSIX TOSTOP flag */
#if a_TERMIOS_SIGWINCH != -1
            if(safe_signal(SIGWINCH, SIG_IGN) != SIG_IGN)
               a_termios_g.tiosg_owinch = safe_signal(SIGWINCH,
                     &a_termios_onsig);
#endif
         }

         l = c = 0;
         if((hadl = ((cp = ok_vlook(LINES)) != NIL)))
            su_idec_u32_cp(&l, cp, 0, NIL);
         if((hadc = ((cp = ok_vlook(COLUMNS)) != NIL)))
            su_idec_u32_cp(&c, cp, 0, NIL);

         if(l == 0 || c == 0){
            /* In non-interactive mode, stop now, except for the documented case
             * that both are set but not both have been usable */
            if(!(n_psonce & n_PSO_INTERACTIVE) &&
                  !((n_psonce & n_PSO_TTYANY) &&
                     (n_poption & n_PO_BATCH_FLAG)) &&
                  (!hadl || !hadc))
               goto jtermsize_default;

            a_termios_dimen_query(&mx_termios_dimen);
         }

         if(l != 0)
            mx_termios_dimen.tiosd_real_height =
                  mx_termios_dimen.tiosd_height = l;
         if(c != 0)
            mx_termios_dimen.tiosd_width =
                  mx_termios_dimen.tiosd_real_width = c;
      }else{
jtermsize_default:
         /* $COLUMNS and $LINES defaults as documented in the manual! */
         mx_termios_dimen.tiosd_height =
               mx_termios_dimen.tiosd_real_height = a_TERMIOS_DEFAULT_HEIGHT;
         mx_termios_dimen.tiosd_width =
               mx_termios_dimen.tiosd_real_width = a_TERMIOS_DEFAULT_WIDTH;
      }

      /* Note: for this first invocation this will always trigger.
       * If we have termcap support then termcap_init() will undo this if
       * FULLWIDTH is set after termcap is initialized.
       * We have to evaluate it now since cmds may run pre-termcap ... */
      if(mx_termios_dimen.tiosd_width > 1)
         --mx_termios_dimen.tiosd_width;
      n_pstate |= n_PS_SIGWINCH_PEND;
   }

   NYD_OU;
}

void
mx_termios_on_state_change_set(mx_termios_on_state_change tiossc, up cookie){
   NYD2_IN;
   ASSERT(a_termios_g.tiosg_envp->tiose_prev != NIL); /* Not in base level */

   a_termios_g.tiosg_envp->tiose_on_state_change = tiossc;
   a_termios_g.tiosg_envp->tiose_osc_cookie = cookie;
   NYD2_OU;
}

boole
mx_termios_cmd(u32 tiosc, uz a1){
   /* xxx tcsetattr not correct says manual: would need to requery and check
    * whether all desired changes made it instead! */
   boole rv;
   struct a_termios_env *tiosep_2free, *tiosep;
   NYD_IN;

   tiosep_2free = NIL;

   ASSERT_NYD_EXEC((tiosc & mx__TERMIOS_CMD_CTL_MASK) ||
       tiosc == mx_TERMIOS_CMD_RESET ||
      /*(tiosc == mx_TERMIOS_CMD_SET_PGRP &&
       *  a_termios_g.tiosg_envp->tiose_cmd == mx_TERMIOS_CMD_HANDS_OFF) ||*/
      (a_termios_g.tiosg_envp->tiose_prev != NIL &&
       ((tiosc & mx__TERMIOS_CMD_ACT_MASK) == mx_TERMIOS_CMD_RAW ||
        (tiosc & mx__TERMIOS_CMD_ACT_MASK) == mx_TERMIOS_CMD_RAW_TIMEOUT) &&
       (a_termios_g.tiosg_envp->tiose_cmd == mx_TERMIOS_CMD_RAW ||
        a_termios_g.tiosg_envp->tiose_cmd == mx_TERMIOS_CMD_RAW_TIMEOUT)),
      rv = FAL0);
   ASSERT_NYD_EXEC(!(tiosc & mx_TERMIOS_CMD_POP) ||
      a_termios_g.tiosg_envp->tiose_prev != NIL, rv = FAL0);
   ASSERT_NYD_EXEC((tiosc & mx__TERMIOS_CMD_ACT_MASK) ||
      (tiosc == mx_TERMIOS_CMD_RESET /*|| tiosc == mx_TERMIOS_CMD_SET_PGRP*/),
      rv = FAL0);

   if(a_termios_g.tiosg_normal == NIL){
      a_termios_g.tiosg_normal = a_termios_g.tiosg_envp;
      a_termios_g.tiosg_normal->tiose_cmd = mx_TERMIOS_CMD_NORMAL;
      /*rv =*/ a_termios_norm_query();
   }

   /* Note: RESET only called with signals blocked in main loop handler */
   if(tiosc == mx_TERMIOS_CMD_RESET){
      boole first;

      if((tiosep = a_termios_g.tiosg_envp)->tiose_prev == NIL){
         rv = TRU1;
         goto jleave;
      }
      rv = (tcsetattr(fileno(mx_tty_fp), TCSADRAIN, &tiosep->tiose_state
            ) == 0);

      for(first = TRU1;; first = FAL0){
         a_termios_g.tiosg_envp = tiosep->tiose_prev;

         if(first || !tiosep->tiose_suspended){
            if(tiosep->tiose_on_state_change != NIL)
               (*tiosep->tiose_on_state_change)(tiosep->tiose_osc_cookie,
                  (first ? mx_TERMIOS_STATE_SUSPEND : 0
                      | mx_TERMIOS_STATE_POP), 0);
         }

         su_FREE(tiosep);

         if((tiosep = a_termios_g.tiosg_envp)->tiose_prev == NIL)
            break;
      }

      a_termios_sig_adjust(FAL0);
      goto jleave;
#if 0
   }else if(tiosc == mx_TERMIOS_CMD_SET_PGRP){
      if(a_termios_g.tiosg_pgrp == 0)
         a_termios_g.tiosg_pgrp = getpgrp();
      rv = (tcsetpgrp(fileno(mx_tty_fp), S(pid_t,a1)) == 0);
      goto jleave;
#endif
   }else if(a_termios_g.tiosg_envp->tiose_cmd ==
            (tiosc & mx__TERMIOS_CMD_ACT_MASK) &&
         !(tiosc & mx__TERMIOS_CMD_CTL_MASK)){
      rv = TRU1;
      goto jleave;
   }

   if(tiosc & mx_TERMIOS_CMD_PUSH){
      tiosep = su_TCALLOC(struct a_termios_env, 1);
      tiosep->tiose_cmd = (tiosc & mx__TERMIOS_CMD_ACT_MASK);
   }

   mx_sigs_all_holdx();

   if(tiosc & mx_TERMIOS_CMD_PUSH){
      if((tiosep->tiose_prev = a_termios_g.tiosg_envp)->tiose_prev == NIL)
         a_termios_sig_adjust(TRU1);
      else{
         if(!a_termios_g.tiosg_envp->tiose_suspended){
            a_termios_g.tiosg_envp->tiose_suspended = TRU1;
            if(a_termios_g.tiosg_envp->tiose_on_state_change != NIL)
               (*a_termios_g.tiosg_envp->tiose_on_state_change)(
                  a_termios_g.tiosg_envp->tiose_osc_cookie,
                  mx_TERMIOS_STATE_SUSPEND, 0);
         }
      }

      a_termios_g.tiosg_envp = tiosep;
   }else if(tiosc & mx_TERMIOS_CMD_POP){
      tiosep_2free = tiosep = a_termios_g.tiosg_envp;
      a_termios_g.tiosg_envp = tiosep->tiose_prev;
      tiosep->tiose_prev = NIL;

      if(!tiosep->tiose_suspended && tiosep->tiose_on_state_change != NIL)
         (*tiosep->tiose_on_state_change)(tiosep->tiose_osc_cookie,
            (mx_TERMIOS_STATE_SUSPEND | mx_TERMIOS_STATE_POP), 0);

      if((tiosep = a_termios_g.tiosg_envp)->tiose_prev == NIL)
         a_termios_sig_adjust(FAL0);

      tiosc = tiosep->tiose_cmd | mx_TERMIOS_CMD_POP;
      a1 = tiosep->tiose_a1;
   }else
      tiosep = a_termios_g.tiosg_envp;

   if(tiosep->tiose_prev != NIL)
      su_mem_copy(&tiosep->tiose_state, &a_termios_g.tiosg_normal->tiose_state,
         sizeof(tiosep->tiose_state));
   else
      ASSERT(tiosep->tiose_cmd == mx_TERMIOS_CMD_NORMAL);

   switch((tiosep->tiose_cmd = (tiosc & mx__TERMIOS_CMD_ACT_MASK))){
   default:
   case mx_TERMIOS_CMD_HANDS_OFF:
      if(!(tiosc & mx_TERMIOS_CMD_PUSH)){
         rv = TRU1;
         break;
      }
      /* FALLTHRU */
   case mx_TERMIOS_CMD_NORMAL:
      rv = (tcsetattr(fileno(mx_tty_fp), TCSADRAIN, &tiosep->tiose_state
            ) == 0);
      break;
   case mx_TERMIOS_CMD_PASSWORD:
      tiosep->tiose_state.c_iflag &= ~(ISTRIP);
      tiosep->tiose_state.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
      rv = (tcsetattr(fileno(mx_tty_fp), TCSADRAIN, &tiosep->tiose_state
            ) == 0);
      break;
   case mx_TERMIOS_CMD_RAW:
   case mx_TERMIOS_CMD_RAW_TIMEOUT:
      a1 = MIN(U8_MAX, a1);
      tiosep->tiose_a1 = S(u8,a1);
      if((tiosc & mx__TERMIOS_CMD_ACT_MASK) == mx_TERMIOS_CMD_RAW){
         tiosep->tiose_state.c_cc[VMIN] = S(u8,a1);
         tiosep->tiose_state.c_cc[VTIME] = 0;
      }else{
         tiosep->tiose_state.c_cc[VMIN] = 0;
         tiosep->tiose_state.c_cc[VTIME] = S(u8,a1);
      }
      tiosep->tiose_state.c_iflag &= ~(ISTRIP | IGNCR | IXON | IXOFF);
      tiosep->tiose_state.c_lflag &= ~(ECHO /*| ECHOE | ECHONL */|
            ICANON | IEXTEN | ISIG);
      rv = (tcsetattr(fileno(mx_tty_fp), TCSADRAIN, &tiosep->tiose_state
            ) == 0);
      break;
   }

   if(/*!(tiosc & mx__TERMIOS_CMD_CTL_MASK) &&*/ tiosep->tiose_suspended &&
         tiosep->tiose_on_state_change != NIL)
      (*tiosep->tiose_on_state_change)(tiosep->tiose_osc_cookie,
         mx_TERMIOS_STATE_RESUME, 0);

   /* XXX if(rv)*/
      tiosep->tiose_suspended = FAL0;

   if(tiosep_2free != NIL)
      tiosep_2free->tiose_prev = a_termios_g.tiosg_pend_free;
   else
      tiosep_2free = a_termios_g.tiosg_pend_free;
   a_termios_g.tiosg_pend_free = NIL;

   mx_sigs_all_rele();

jleave:
   while((tiosep = tiosep_2free) != NIL){
      tiosep_2free = tiosep->tiose_prev;
      su_FREE(tiosep);
   }

   NYD_OU;
   return rv;
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/mx/thread.c000066400000000000000000000537451352610246600157270ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ Message threading. TODO thread handling needs rewrite, m_collapsed must go
 *
 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: BSD-4-Clause
 */
/*
 * Copyright (c) 2004
 * Gunnar Ritter.  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 Gunnar Ritter
 *    and his contributors.
 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER 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 GUNNAR RITTER 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.
 */
#undef su_FILE
#define su_FILE thread
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

#include 
#include 
#include 

#include "mx/names.h"

/* TODO fake */
#include "su/code-in.h"

/* Open addressing is used for Message-IDs because the maximum number of
 * messages in the table is known in advance (== msgCount) */
struct mitem {
   struct message *mi_data;
   char           *mi_id;
};
#define NOT_AN_ID ((struct mitem*)-1)

struct msort {
   union {
#ifdef mx_HAVE_SPAM
      u32   ms_ui;
#endif
      long     ms_long;
      char     *ms_char;
   }           ms_u;
   int         ms_n;
};

/* Return the hash value for a message id modulo mprime, or mprime if the
 * passed string does not look like a message-id */
static u32           _mhash(char const *cp, u32 mprime);

/* Look up a message id. Returns NOT_AN_ID if the passed string does not look
 * like a message-id */
static struct mitem *   _mlook(char *id, struct mitem *mt,
                           struct message *mdata, u32 mprime);

/* Child is to be adopted by parent.  A thread tree is structured as follows:
 *
 *  ------       m_child       ------        m_child
 *  |    |-------------------->|    |------------------------> . . .
 *  |    |<--------------------|    |<-----------------------  . . .
 *  ------      m_parent       ------       m_parent
 *     ^^                       |  ^
 *     | \____        m_younger |  |
 *     |      \                 |  |
 *     |       ----             |  |
 *     |           \            |  | m_elder
 *     |   m_parent ----        |  |
 *                      \       |  |
 *                       ----   |  |
 *                           \  +  |
 *                             ------        m_child
 *                             |    |------------------------> . . .
 *                             |    |<-----------------------  . . .
 *                             ------       m_parent
 *                              |  ^
 *                              . . .
 *
 * The base message of a thread does not have a m_parent link.  Elements
 * connected by m_younger/m_elder links are replies to the same message, which
 * is connected to them by m_parent links.  The first reply to a message gets
 * the m_child link */
static void             _adopt(struct message *parent, struct message *child,
                           int dist);

/* Connect all msgs on the lowest thread level with m_younger/m_elder links */
static struct message * _interlink(struct message *m, u32 cnt, int nmail);

static void             _finalize(struct message *mp);

/* Several sort comparison PTFs */
#ifdef mx_HAVE_SPAM
static int              _mui32lt(void const *a, void const *b);
#endif
static int              _mlonglt(void const *a, void const *b);
static int              _mcharlt(void const *a, void const *b);

static void             _lookup(struct message *m, struct mitem *mi,
                           u32 mprime);
static void             _makethreads(struct message *m, u32 cnt, int nmail);
static int              _colpt(int *msgvec, int cl);
static void             _colps(struct message *b, int cl);
static void             _colpm(struct message *m, int cl, int *cc, int *uc);

static u32
_mhash(char const *cp, u32 mprime)
{
   u32 h = 0, g, at = 0;
   NYD2_IN;

   for (--cp; *++cp != '\0';) {
      /* Pay attention not to hash characters which are irrelevant for
       * Message-ID semantics */
      if (*cp == '(') {
         cp = skip_comment(cp + 1) - 1;
         continue;
      }
      if (*cp == '"' || *cp == '\\')
         continue;
      if (*cp == '@')
         ++at;
      /* TODO torek hash */
      h = ((h << 4) & 0xffffffff) + su_cs_to_lower(*cp);
      if ((g = h & 0xf0000000) != 0) {
         h = h ^ (g >> 24);
         h = h ^ g;
      }
   }
   NYD2_OU;
   return (at ? h % mprime : mprime);
}

static struct mitem *
_mlook(char *id, struct mitem *mt, struct message *mdata, u32 mprime)
{
   struct mitem *mp = NULL;
   u32 h, c, n = 0;
   NYD2_IN;

   if (id == NULL) {
      if ((id = hfield1("message-id", mdata)) == NULL)
         goto jleave;
      /* Normalize, what hfield1() doesn't do (TODO should now GREF, too!!) */
      if (id[0] == '<') {
         id[su_cs_len(id) -1] = '\0';
         if (*id != '\0')
            ++id;
      }
   }

   if (mdata != NULL && mdata->m_idhash)
      h = ~mdata->m_idhash;
   else {
      h = _mhash(id, mprime);
      if (h == mprime) {
         mp = NOT_AN_ID;
         goto jleave;
      }
   }

   mp = mt + (c = h);
   while (mp->mi_id != NULL) {
      if (!msgidcmp(mp->mi_id, id))
         break;
      c += (n & 1) ? -((n+1)/2) * ((n+1)/2) : ((n+1)/2) * ((n+1)/2);
      ++n;
      if ((s32)c < 0)
         c = 0;
      else while (c >= mprime)
         c -= mprime;
      mp = mt + c;
   }

   if (mdata != NULL && mp->mi_id == NULL) {
      mp->mi_id = su_cs_dup(id, 0);
      mp->mi_data = mdata;
      mdata->m_idhash = ~h;
   }
   if (mp->mi_id == NULL)
      mp = NULL;
jleave:
   NYD2_OU;
   return mp;
}

static void
_adopt(struct message *parent, struct message *child, int dist)
{
   struct message *mp, *mq;
   NYD2_IN;

   for (mp = parent; mp != NULL; mp = mp->m_parent)
      if (mp == child)
         goto jleave;

   child->m_level = dist; /* temporarily store distance */
   child->m_parent = parent;

   if (parent->m_child != NULL) {
      mq = NULL;
      for (mp = parent->m_child; mp != NULL; mp = mp->m_younger) {
         if (mp->m_date >= child->m_date) {
            if (mp->m_elder != NULL)
               mp->m_elder->m_younger = child;
            child->m_elder = mp->m_elder;
            mp->m_elder = child;
            child->m_younger = mp;
            if (mp == parent->m_child)
               parent->m_child = child;
            goto jleave;
         }
         mq = mp;
      }
      mq->m_younger = child;
      child->m_elder = mq;
   } else
      parent->m_child = child;
jleave:
   NYD2_OU;
}

static struct message *
_interlink(struct message *m, u32 cnt, int nmail)
{
   struct message *root;
   u32 n;
   struct msort *ms;
   int i, autocollapse;
   NYD2_IN;

   autocollapse = (!nmail && !(n_pstate & n_PS_HOOK_NEWMAIL) &&
         ok_blook(autocollapse));
   ms = n_alloc(sizeof *ms * cnt);

   for (n = 0, i = 0; UCMP(32, i, <, cnt); ++i) {
      if (m[i].m_parent == NULL) {
         if (autocollapse)
            _colps(m + i, 1);
         ms[n].ms_u.ms_long = m[i].m_date;
         ms[n].ms_n = i;
         ++n;
      }
   }

   if (n > 0) {
      qsort(ms, n, sizeof *ms, &_mlonglt);
      root = m + ms[0].ms_n;
      for (i = 1; UCMP(32, i, <, n); ++i) {
         m[ms[i-1].ms_n].m_younger = m + ms[i].ms_n;
         m[ms[i].ms_n].m_elder = m + ms[i - 1].ms_n;
      }
   } else
      root = NULL;

   n_free(ms);
   NYD2_OU;
   return root;
}

static void
_finalize(struct message *mp)
{
   long n;
   NYD2_IN;

   for (n = 0; mp; mp = next_in_thread(mp)) {
      mp->m_threadpos = ++n;
      mp->m_level = mp->m_parent ? mp->m_level + mp->m_parent->m_level : 0;
   }
   NYD2_OU;
}

#ifdef mx_HAVE_SPAM
static int
_mui32lt(void const *a, void const *b)
{
   struct msort const *xa = a, *xb = b;
   int i;
   NYD2_IN;

   i = (int)(xa->ms_u.ms_ui - xb->ms_u.ms_ui);
   if (i == 0)
      i = xa->ms_n - xb->ms_n;
   NYD2_OU;
   return i;
}
#endif

static int
_mlonglt(void const *a, void const *b)
{
   struct msort const *xa = a, *xb = b;
   int i;
   NYD2_IN;

   i = (int)(xa->ms_u.ms_long - xb->ms_u.ms_long);
   if (i == 0)
      i = xa->ms_n - xb->ms_n;
   NYD2_OU;
   return i;
}

static int
_mcharlt(void const *a, void const *b)
{
   struct msort const *xa = a, *xb = b;
   int i;
   NYD2_IN;

   i = strcoll(xa->ms_u.ms_char, xb->ms_u.ms_char);
   if (i == 0)
      i = xa->ms_n - xb->ms_n;
   NYD2_OU;
   return i;
}

static void
_lookup(struct message *m, struct mitem *mi, u32 mprime)
{
   struct mx_name *np;
   struct mitem *ip;
   char *cp;
   long dist;
   NYD2_IN;

   if (m->m_flag & MHIDDEN)
      goto jleave;

   dist = 1;
   if ((cp = hfield1("in-reply-to", m)) != NULL) {
      if ((np = extract(cp, GREF)) != NULL)
         do {
            if ((ip = _mlook(np->n_name, mi, NULL, mprime)) != NULL &&
                  ip != NOT_AN_ID) {
               _adopt(ip->mi_data, m, 1);
               goto jleave;
            }
         } while ((np = np->n_flink) != NULL);
   }

   if ((cp = hfield1("references", m)) != NULL) {
      if ((np = extract(cp, GREF)) != NULL) {
         while (np->n_flink != NULL)
            np = np->n_flink;
         do {
            if ((ip = _mlook(np->n_name, mi, NULL, mprime)) != NULL) {
               if (ip == NOT_AN_ID)
                  continue; /* skip dist++ */
               _adopt(ip->mi_data, m, dist);
               goto jleave;
            }
            ++dist;
         } while ((np = np->n_blink) != NULL);
      }
   }
jleave:
   NYD2_OU;
}

static void
_makethreads(struct message *m, u32 cnt, int nmail)
{
   struct mitem *mt;
   char *cp;
   u32 i, mprime;
   NYD2_IN;

   if (cnt == 0)
      goto jleave;

   /* It is performance crucial to space this large enough in order to minimize
    * bucket sharing */
   mprime = su_prime_lookup_next((cnt < U32_MAX >> 3) ? cnt << 2 : cnt);
   mt = n_calloc(mprime, sizeof *mt);

   srelax_hold();

   for (i = 0; i < cnt; ++i) {
      if (!(m[i].m_flag & MHIDDEN)) {
         _mlook(NULL, mt, m + i, mprime);
         if (m[i].m_date == 0) {
            if ((cp = hfield1("date", m + i)) != NULL)
               m[i].m_date = rfctime(cp);
         }
      }
      m[i].m_child = m[i].m_younger = m[i].m_elder = m[i].m_parent = NULL;
      m[i].m_level = 0;
      if (!nmail && !(n_pstate & n_PS_HOOK_NEWMAIL))
         m[i].m_collapsed = 0;
      srelax();
   }

   /* Most folders contain the eldest messages first.  Traversing them in
    * descending order makes it more likely that younger brothers are found
    * first, so elder ones can be prepended to the brother list, which is
    * faster.  The worst case is still in O(n^2) and occurs when all but one
    * messages in a folder are replies to the one message, and are sorted such
    * that youngest messages occur first */
   for (i = cnt; i > 0; --i) {
      _lookup(m + i - 1, mt, mprime);
      srelax();
   }

   srelax_rele();

   threadroot = _interlink(m, cnt, nmail);
   _finalize(threadroot);

   for (i = 0; i < mprime; ++i)
      if (mt[i].mi_id != NULL)
         n_free(mt[i].mi_id);

   n_free(mt);
   mb.mb_threaded = 1;
jleave:
   NYD2_OU;
}

static int
_colpt(int *msgvec, int cl)
{
   int *ip, rv;
   NYD2_IN;

   if (mb.mb_threaded != 1) {
      fputs("Not in threaded mode.\n", n_stdout);
      rv = 1;
   } else {
      for (ip = msgvec; *ip != 0; ++ip)
         _colps(message + *ip - 1, cl);
      rv = 0;
   }
   NYD2_OU;
   return rv;
}

static void
_colps(struct message *b, int cl)
{
   struct message *m;
   int cc = 0, uncc = 0;
   NYD2_IN;

   if (cl && (b->m_collapsed > 0 || (b->m_flag & (MNEW | MREAD)) == MNEW))
      goto jleave;

   if (b->m_child != NULL) {
      m = b->m_child;
      _colpm(m, cl, &cc, &uncc);
      for (m = m->m_younger; m != NULL; m = m->m_younger)
         _colpm(m, cl, &cc, &uncc);
   }

   if (cl) {
      b->m_collapsed = -cc;
      for (m = b->m_parent; m != NULL; m = m->m_parent)
         if (m->m_collapsed <= -uncc) {
            m->m_collapsed += uncc;
            break;
         }
   } else {
      if (b->m_collapsed > 0) {
         b->m_collapsed = 0;
         ++uncc;
      }
      for (m = b; m != NULL; m = m->m_parent)
         if (m->m_collapsed <= -uncc) {
            m->m_collapsed += uncc;
            break;
         }
   }
jleave:
   NYD2_OU;
}

static void
_colpm(struct message *m, int cl, int *cc, int *uncc)
{
   NYD2_IN;
   if (cl) {
      if (m->m_collapsed > 0)
         ++(*uncc);
      if ((m->m_flag & (MNEW | MREAD)) != MNEW || m->m_collapsed < 0)
         m->m_collapsed = 1;
      if (m->m_collapsed > 0)
         ++(*cc);
   } else {
      if (m->m_collapsed > 0) {
         m->m_collapsed = 0;
         ++(*uncc);
      }
   }

   if (m->m_child != NULL) {
      m = m->m_child;
      _colpm(m, cl, cc, uncc);
      for (m = m->m_younger; m != NULL; m = m->m_younger)
         _colpm(m, cl, cc, uncc);
   }
   NYD2_OU;
}

FL int
c_thread(void *vp)
{
   int rv;
   NYD_IN;

   if (mb.mb_threaded != 1 || vp == NULL || vp == (void*)-1) {
#ifdef mx_HAVE_IMAP
      if (mb.mb_type == MB_IMAP)
         imap_getheaders(1, msgCount);
#endif
      _makethreads(message, msgCount, (vp == (void*)-1));
      if (mb.mb_sorted != NULL)
         n_free(mb.mb_sorted);
      mb.mb_sorted = su_cs_dup("thread", 0);
   }

   if (vp != NULL && vp != (void*)-1 && !(n_pstate & n_PS_HOOK_MASK) &&
         ok_blook(header))
      rv = print_header_group(vp);
   else
      rv = 0;
   NYD_OU;
   return rv;
}

FL int
c_unthread(void *vp)
{
   struct message *m;
   int rv;
   NYD_IN;

   mb.mb_threaded = 0;
   if (mb.mb_sorted != NULL)
      n_free(mb.mb_sorted);
   mb.mb_sorted = NULL;

   for (m = message; PCMP(m, <, message + msgCount); ++m)
      m->m_collapsed = 0;

   if (vp && !(n_pstate & n_PS_HOOK_MASK) && ok_blook(header))
      rv = print_header_group(vp);
   else
      rv = 0;
   NYD_OU;
   return rv;
}

FL struct message *
next_in_thread(struct message *mp)
{
   struct message *rv;
   NYD2_IN;

   if ((rv = mp->m_child) != NULL)
      goto jleave;
   if ((rv = mp->m_younger) != NULL)
      goto jleave;

   while ((rv = mp->m_parent) != NULL) {
      mp = rv;
      if ((rv = rv->m_younger) != NULL)
         goto jleave;
   }
jleave:
   NYD2_OU;
   return rv;
}

FL struct message *
prev_in_thread(struct message *mp)
{
   struct message *rv;
   NYD2_IN;

   if ((rv = mp->m_elder) != NULL) {
      for (mp = rv; (rv = mp->m_child) != NULL;) {
         mp = rv;
         while ((rv = mp->m_younger) != NULL)
            mp = rv;
      }
      rv = mp;
      goto jleave;
   }
   rv = mp->m_parent;
jleave:
   NYD2_OU;
   return rv;
}

FL struct message *
this_in_thread(struct message *mp, long n)
{
   struct message *rv;
   NYD2_IN;

   if (n == -1) { /* find end of thread */
      while (mp != NULL) {
         if ((rv = mp->m_younger) != NULL) {
            mp = rv;
            continue;
         }
         rv = next_in_thread(mp);
         if (rv == NULL || rv->m_threadpos < mp->m_threadpos) {
            rv = mp;
            goto jleave;
         }
         mp = rv;
      }
      rv = mp;
      goto jleave;
   }

   while (mp != NULL && mp->m_threadpos < n) {
      if ((rv = mp->m_younger) != NULL && rv->m_threadpos <= n) {
         mp = rv;
         continue;
      }
      mp = next_in_thread(mp);
   }
   rv = (mp != NULL && mp->m_threadpos == n) ? mp : NULL;
jleave:
   NYD2_OU;
   return rv;
}

FL int
c_sort(void *vp)
{
   enum method {SORT_SUBJECT, SORT_DATE, SORT_STATUS, SORT_SIZE, SORT_FROM,
      SORT_TO, SORT_SPAM, SORT_THREAD} method;
   struct {
      char const *me_name;
      enum method me_method;
      int         (*me_func)(void const *, void const *);
   } const methnames[] = {
      {"date", SORT_DATE, &_mlonglt},
      {"from", SORT_FROM, &_mcharlt},
      {"to", SORT_TO, &_mcharlt},
      {"subject", SORT_SUBJECT, &_mcharlt},
      {"size", SORT_SIZE, &_mlonglt},
#ifdef mx_HAVE_SPAM
      {"spam", SORT_SPAM, &_mui32lt},
#endif
      {"status", SORT_STATUS, &_mlonglt},
      {"thread", SORT_THREAD, NULL}
   };

   struct str in, out;
   char *_args[2], *cp, **args = vp;
   int msgvec[2], i, n;
   int (*func)(void const *, void const *);
   struct msort *ms;
   struct message *mp;
   boole showname;
   NYD_IN;

   if (vp == NULL || vp == (void*)-1) {
      _args[0] = savestr((mb.mb_sorted != NULL) ? mb.mb_sorted : "unsorted");
      _args[1] = NULL;
      args = _args;
   } else if (args[0] == NULL) {
      fprintf(n_stdout, "Current sorting criterion is: %s\n",
            (mb.mb_sorted != NULL) ? mb.mb_sorted : "unsorted");
      i = 0;
      goto jleave;
   }

   i = 0;
   for (;;) {
      if (*args[0] != '\0' && su_cs_starts_with(methnames[i].me_name, args[0]))
         break;
      if (UCMP(z, ++i, >=, NELEM(methnames))) {
         n_err(_("Unknown sorting method: %s\n"), args[0]);
         i = 1;
         goto jleave;
      }
   }

   if (mb.mb_sorted != NULL)
      n_free(mb.mb_sorted);
   mb.mb_sorted = su_cs_dup(args[0], 0);

   method = methnames[i].me_method;
   func = methnames[i].me_func;
   msgvec[0] = (int)P2UZ(dot - message + 1);
   msgvec[1] = 0;

   if (method == SORT_THREAD) {
      i = c_thread((vp != NULL && vp != (void*)-1) ? msgvec : vp);
      goto jleave;
   }

   showname = ok_blook(showname);
   ms = n_lofi_alloc(sizeof *ms * msgCount);
#ifdef mx_HAVE_IMAP
   switch (method) {
   case SORT_SUBJECT:
   case SORT_DATE:
   case SORT_FROM:
   case SORT_TO:
      if (mb.mb_type == MB_IMAP)
         imap_getheaders(1, msgCount);
      break;
   default:
      break;
   }
#endif

   srelax_hold();
   for (n = 0, i = 0; i < msgCount; ++i) {
      mp = message + i;
      if (!(mp->m_flag & MHIDDEN)) {
         switch (method) {
         case SORT_DATE:
            if (mp->m_date == 0 && (cp = hfield1("date", mp)) != NULL)
               mp->m_date = rfctime(cp);
            ms[n].ms_u.ms_long = mp->m_date;
            break;
         case SORT_STATUS:
            if (mp->m_flag & MDELETED)
               ms[n].ms_u.ms_long = 1;
            else if ((mp->m_flag & (MNEW | MREAD)) == MNEW)
               ms[n].ms_u.ms_long = 90;
            else if (mp->m_flag & MFLAGGED)
               ms[n].ms_u.ms_long = 85;
            else if ((mp->m_flag & (MNEW | MBOX)) == MBOX)
               ms[n].ms_u.ms_long = 70;
            else if (mp->m_flag & MNEW)
               ms[n].ms_u.ms_long = 80;
            else if (mp->m_flag & MREAD)
               ms[n].ms_u.ms_long = 40;
            else
               ms[n].ms_u.ms_long = 60;
            break;
         case SORT_SIZE:
            ms[n].ms_u.ms_long = mp->m_xsize;
            break;
#ifdef mx_HAVE_SPAM
         case SORT_SPAM:
            ms[n].ms_u.ms_ui = mp->m_spamscore;
            break;
#endif
         case SORT_FROM:
         case SORT_TO:
            if ((cp = hfield1((method == SORT_FROM ?  "from" : "to"), mp)
                  ) != NULL) {
               ms[n].ms_u.ms_char = su_cs_dup((showname ? realname(cp)
                     : skin(cp)), 0);
               makelow(ms[n].ms_u.ms_char);
            } else
               ms[n].ms_u.ms_char = su_cs_dup(n_empty, 0);
            break;
         default:
         case SORT_SUBJECT:
            if ((cp = hfield1("subject", mp)) != NULL) {
               in.s = cp;
               in.l = su_cs_len(in.s);
               mime_fromhdr(&in, &out, TD_ICONV);
               ms[n].ms_u.ms_char = su_cs_dup(subject_re_trim(out.s), 0);
               n_free(out.s);
               makelow(ms[n].ms_u.ms_char);
            } else
               ms[n].ms_u.ms_char = su_cs_dup(n_empty, 0);
            break;
         }
         ms[n++].ms_n = i;
      }
      mp->m_child = mp->m_younger = mp->m_elder = mp->m_parent = NULL;
      mp->m_level = 0;
      mp->m_collapsed = 0;
      srelax();
   }
   srelax_rele();

   if (n > 0) {
      qsort(ms, n, sizeof *ms, func);
      threadroot = message + ms[0].ms_n;
      for (i = 1; i < n; ++i) {
         message[ms[i - 1].ms_n].m_younger = message + ms[i].ms_n;
         message[ms[i].ms_n].m_elder = message + ms[i - 1].ms_n;
      }
   } else
      threadroot = NULL;

   _finalize(threadroot);
   mb.mb_threaded = 2;

   switch (method) {
   case SORT_FROM:
   case SORT_TO:
   case SORT_SUBJECT:
      for (i = 0; i < n; ++i)
         n_free(ms[i].ms_u.ms_char);
      /* FALLTHRU */
   default:
      break;
   }
   n_lofi_free(ms);

   i = ((vp != NULL && vp != (void*)-1 && !(n_pstate & n_PS_HOOK_MASK) &&
      ok_blook(header)) ? print_header_group(msgvec) : 0);
jleave:
   NYD_OU;
   return i;
}

FL int
c_collapse(void *v)
{
   int rv;
   NYD_IN;

   rv = _colpt(v, 1);
   NYD_OU;
   return rv;
}

FL int
c_uncollapse(void *v)
{
   int rv;
   NYD_IN;

   rv = _colpt(v, 0);
   NYD_OU;
   return rv;
}

FL void
uncollapse1(struct message *mp, int always)
{
   NYD_IN;
   if (mb.mb_threaded == 1 && (always || mp->m_collapsed > 0))
      _colps(mp, 0);
   NYD_OU;
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/mx/tls.c000066400000000000000000000322341352610246600152500ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ Generic TLS / S/MIME commands.
 *
 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: BSD-4-Clause TODO ISC (is taken from book!)
 */
/*
 * Copyright (c) 2002
 * Gunnar Ritter.  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 Gunnar Ritter
 * and his contributors.
 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER 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 GUNNAR RITTER 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.
 */
#undef su_FILE
#define su_FILE tls
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

su_EMPTY_FILE()
#ifdef mx_HAVE_TLS
#include 
#include 

#include "mx/file-streams.h"
#include "mx/net-socket.h"
#include "mx/tty.h"
#include "mx/url.h"

/* TODO fake */
#include "su/code-in.h"

struct a_tls_verify_levels{
   char const tv_name[8];
   enum n_tls_verify_level tv_level;
};

/* Supported SSL/TLS verification methods: update manual on change! */
static struct a_tls_verify_levels const a_tls_verify_levels[] = {
   {"strict", n_TLS_VERIFY_STRICT},
   {"ask", n_TLS_VERIFY_ASK},
   {"warn", n_TLS_VERIFY_WARN},
   {"ignore", n_TLS_VERIFY_IGNORE}
};

FL void
n_tls_set_verify_level(struct mx_url const *urlp){
   uz i;
   char const *cp;
   NYD2_IN;

   n_tls_verify_level = n_TLS_VERIFY_ASK;

   if((cp = xok_vlook(tls_verify, urlp, OXM_ALL)) != NULL ||
         (cp = xok_vlook(ssl_verify, urlp, OXM_ALL)) != NULL){
      for(i = 0;;)
         if(!su_cs_cmp_case(a_tls_verify_levels[i].tv_name, cp)){
            n_tls_verify_level = a_tls_verify_levels[i].tv_level;
            break;
         }else if(++i >= NELEM(a_tls_verify_levels)){
            n_err(_("Invalid value of *tls-verify*: %s\n"), cp);
            break;
         }
   }
   NYD2_OU;
}

FL boole
n_tls_verify_decide(void){
   boole rv;
   NYD2_IN;

   switch(n_tls_verify_level){
   default:
   case n_TLS_VERIFY_STRICT:
      rv = FAL0;
      break;
   case n_TLS_VERIFY_ASK:
      rv = mx_tty_yesorno(NIL, FAL0);
      break;
   case n_TLS_VERIFY_WARN:
   case n_TLS_VERIFY_IGNORE:
      rv = TRU1;
      break;
   }
   NYD2_OU;
   return rv;
}

FL enum okay
smime_split(FILE *ip, FILE **hp, FILE **bp, long xcount, int keep)
{
   struct myline {
      struct myline  *ml_next;
      uz         ml_len;
      char           ml_buf[VFIELD_SIZE(0)];
   } *head, *tail;
   char *buf;
   uz bufsize, buflen, cnt;
   int c;
   enum okay rv = STOP;
   NYD_IN;

   if((*hp = mx_fs_tmp_open("smimeh", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
            mx_FS_O_REGISTER), NIL)) == NIL)
      goto jetmp;
   if((*bp = mx_fs_tmp_open("smimeb", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
            mx_FS_O_REGISTER), NIL)) == NIL){
      mx_fs_close(*hp);
jetmp:
      n_perr(_("tempfile"), 0);
      goto jleave;
   }

   head = tail = NULL;
   buf = n_alloc(bufsize = LINESIZE);
   cnt = (xcount < 0) ? fsize(ip) : xcount;

   while (fgetline(&buf, &bufsize, &cnt, &buflen, ip, 0) != NULL &&
         *buf != '\n') {
      if (!su_cs_cmp_case_n(buf, "content-", 8)) {
         if (keep)
            fputs("X-Encoded-", *hp);
         for (;;) {
            struct myline *ml = n_alloc(VSTRUCT_SIZEOF(struct myline, ml_buf
                  ) + buflen +1);
            if (tail != NULL)
               tail->ml_next = ml;
            else
               head = ml;
            tail = ml;
            ml->ml_next = NULL;
            ml->ml_len = buflen;
            su_mem_copy(ml->ml_buf, buf, buflen +1);
            if (keep)
               fwrite(buf, sizeof *buf, buflen, *hp);
            c = getc(ip);
            ungetc(c, ip);
            if (!su_cs_is_blank(c))
               break;
            fgetline(&buf, &bufsize, &cnt, &buflen, ip, 0);
         }
         continue;
      }
      fwrite(buf, sizeof *buf, buflen, *hp);
   }
   fflush_rewind(*hp);

   while (head != NULL) {
      fwrite(head->ml_buf, sizeof *head->ml_buf, head->ml_len, *bp);
      tail = head;
      head = head->ml_next;
      n_free(tail);
   }
   putc('\n', *bp);
   while (fgetline(&buf, &bufsize, &cnt, &buflen, ip, 0) != NULL)
      fwrite(buf, sizeof *buf, buflen, *bp);
   fflush_rewind(*bp);

   n_free(buf);
   rv = OKAY;
jleave:
   NYD_OU;
   return rv;
}

FL FILE *
smime_sign_assemble(FILE *hp, FILE *bp, FILE *tsp, char const *message_digest)
{
   char *boundary;
   int c, lastc = EOF;
   FILE *op;
   NYD_IN;

   if((op = mx_fs_tmp_open("smimea", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
            mx_FS_O_REGISTER), NIL)) == NIL){
      n_perr(_("tempfile"), 0);
      goto jleave;
   }

   while ((c = getc(hp)) != EOF) {
      if (c == '\n' && lastc == '\n')
         break;
      putc(c, op);
      lastc = c;
   }

   boundary = mime_param_boundary_create();
   fprintf(op, "Content-Type: multipart/signed;\n"
      " protocol=\"application/pkcs7-signature\"; micalg=%s;\n"
      " boundary=\"%s\"\n\n", message_digest, boundary);
   fprintf(op, "This is a S/MIME signed message.\n\n--%s\n", boundary);
   while ((c = getc(bp)) != EOF)
      putc(c, op);

   fprintf(op, "\n--%s\n", boundary);
   fputs("Content-Type: application/pkcs7-signature; name=\"smime.p7s\"\n"
      "Content-Transfer-Encoding: base64\n"
      "Content-Disposition: attachment; filename=\"smime.p7s\"\n"
      "Content-Description: S/MIME digital signature\n\n", op);
   while ((c = getc(tsp)) != EOF) {
      if (c == '-') {
         while ((c = getc(tsp)) != EOF && c != '\n');
         continue;
      }
      putc(c, op);
   }

   fprintf(op, "\n--%s--\n", boundary);

   mx_fs_close(hp);
   mx_fs_close(bp);
   mx_fs_close(tsp);

   fflush(op);
   if (ferror(op)) {
      n_perr(_("signed output data"), 0);
      mx_fs_close(op);
      op = NULL;
      goto jleave;
   }
   rewind(op);
jleave:
   NYD_OU;
   return op;
}

FL FILE *
smime_encrypt_assemble(FILE *hp, FILE *yp)
{
   FILE *op;
   int c, lastc = EOF;
   NYD_IN;

   if((op = mx_fs_tmp_open("smimee", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
            mx_FS_O_REGISTER), NIL)) == NIL){
      n_perr(_("tempfile"), 0);
      goto jleave;
   }

   while ((c = getc(hp)) != EOF) {
      if (c == '\n' && lastc == '\n')
         break;
      putc(c, op);
      lastc = c;
   }

   fputs("Content-Type: application/pkcs7-mime; name=\"smime.p7m\"\n"
      "Content-Transfer-Encoding: base64\n"
      "Content-Disposition: attachment; filename=\"smime.p7m\"\n"
      "Content-Description: S/MIME encrypted message\n\n", op);
   while ((c = getc(yp)) != EOF) {
      if (c == '-') {
         while ((c = getc(yp)) != EOF && c != '\n');
         continue;
      }
      putc(c, op);
   }

   mx_fs_close(hp);
   mx_fs_close(yp);

   fflush(op);
   if (ferror(op)) {
      n_perr(_("encrypted output data"), 0);
      mx_fs_close(op);
      op = NULL;
      goto jleave;
   }
   rewind(op);
jleave:
   NYD_OU;
   return op;
}

FL struct message *
smime_decrypt_assemble(struct message *m, FILE *hp, FILE *bp)
{
   u32 lastnl = 0;
   int binary = 0;
   char *buf = NULL;
   uz bufsize = 0, buflen, cnt;
   long lns = 0, octets = 0;
   struct message *x;
   off_t offset;
   NYD_IN;

   x = n_autorec_alloc(sizeof *x);
   *x = *m;
   fflush(mb.mb_otf);
   fseek(mb.mb_otf, 0L, SEEK_END);
   offset = ftell(mb.mb_otf);

   cnt = fsize(hp);
   while (fgetline(&buf, &bufsize, &cnt, &buflen, hp, 0) != NULL) {
      char const *cp;
      if (buf[0] == '\n')
         break;
      if ((cp = n_header_get_field(buf, "content-transfer-encoding", su_NIL)
            ) != NULL)
         if (!su_cs_cmp_case_n(cp, "binary", 7))
            binary = 1;
      fwrite(buf, sizeof *buf, buflen, mb.mb_otf);
      octets += buflen;
      ++lns;
   }

   {  struct time_current save = time_current;
      time_current_update(&time_current, TRU1);
      octets += mkdate(mb.mb_otf, "X-Decoding-Date");
      time_current = save;
   }
   ++lns;

   cnt = fsize(bp);
   while (fgetline(&buf, &bufsize, &cnt, &buflen, bp, 0) != NULL) {
      lns++;
      if (!binary && buf[buflen - 1] == '\n' && buf[buflen - 2] == '\r')
         buf[--buflen - 1] = '\n';
      fwrite(buf, sizeof *buf, buflen, mb.mb_otf);
      octets += buflen;
      if (buf[0] == '\n')
         ++lastnl;
      else if (buf[buflen - 1] == '\n')
         lastnl = 1;
      else
         lastnl = 0;
   }

   while (!binary && lastnl < 2) {
      putc('\n', mb.mb_otf);
      ++lns;
      ++octets;
      ++lastnl;
   }

   mx_fs_close(hp);
   mx_fs_close(bp);
   n_free(buf);

   fflush(mb.mb_otf);
   if (ferror(mb.mb_otf)) {
      n_perr(_("decrypted output data"), 0);
      x = NULL;
   }else{
      x->m_size = x->m_xsize = octets;
      x->m_lines = x->m_xlines = lns;
      x->m_block = mailx_blockof(offset);
      x->m_offset = mailx_offsetof(offset);
   }
   NYD_OU;
   return x;
}

FL int
c_certsave(void *vp){
   FILE *fp;
   int *msgvec, *ip;
   struct n_cmd_arg_ctx *cacp;
   NYD_IN;

   cacp = vp;
   ASSERT(cacp->cac_no == 2);

   msgvec = cacp->cac_arg->ca_arg.ca_msglist;
   /* C99 */{
      char *file, *cp;

      file = cacp->cac_arg->ca_next->ca_arg.ca_str.s;
      if((cp = fexpand(file, FEXP_LOCAL_FILE | FEXP_NOPROTO)) == NULL ||
            *cp == '\0'){
         n_err(_("`certsave': file expansion failed: %s\n"),
            n_shexp_quote_cp(file, FAL0));
         vp = NULL;
         goto jleave;
      }
      file = cp;

      if((fp = mx_fs_open(file, "a")) == NIL){
         n_perr(file, 0);
         vp = NULL;
         goto jleave;
      }
   }

   for(ip = msgvec; *ip != 0; ++ip)
      if(smime_certsave(&message[*ip - 1], *ip, fp) != OKAY)
         vp = NULL;

   mx_fs_close(fp);

   if(vp != NULL)
      fprintf(n_stdout, "Certificate(s) saved\n");
jleave:
   NYD_OU;
   return (vp != NULL);
}

FL boole
n_tls_rfc2595_hostname_match(char const *host, char const *pattern){
   boole rv;
   NYD_IN;

   if(pattern[0] == '*' && pattern[1] == '.'){
      ++pattern;
      while(*host && *host != '.')
         ++host;
   }
   rv = (su_cs_cmp_case(host, pattern) == 0);
   NYD_OU;
   return rv;
}

FL int
c_tls(void *vp){
   uz i;
   char const **argv, *varname, *varres, *cp;
   NYD_IN;

   argv = vp;
   vp = NULL; /* -> return value (boolean) */
   varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
   varres = n_empty;

   if((cp = argv[0])[0] == '\0')
      goto jesubcmd;
   else if(su_cs_starts_with_case("fingerprint", cp)){
#ifndef mx_HAVE_NET
      n_err(_("`tls': fingerprint: no +sockets in *features*\n"));
      n_pstate_err_no = su_ERR_OPNOTSUPP;
      goto jleave;
#else
      struct mx_socket so;
      struct mx_url url;

      if(argv[1] == NULL || argv[2] != NULL)
         goto jesynopsis;
      if((i = su_cs_len(*++argv)) >= U32_MAX)
         goto jeoverflow; /* TODO generic for ALL commands!! */
      if(!mx_url_parse(&url, CPROTO_CERTINFO, *argv))
         goto jeinval;
      if(!mx_socket_open(&so, &url)){ /* auto-close 4 CPROTO_CERTINFO if ok */
         n_pstate_err_no = su_err_no();
         goto jleave;
      }
      if(so.s_tls_finger == NULL)
         goto jeinval;
      varres = so.s_tls_finger;
#endif /* mx_HAVE_NET */
   }else
      goto jesubcmd;

   n_pstate_err_no = su_ERR_NONE;
   vp = (char*)-1;
jleave:
   if(varname == NULL){
      if(fprintf(n_stdout, "%s\n", varres) < 0){
         n_pstate_err_no = su_err_no();
         vp = NULL;
      }
   }else if(!n_var_vset(varname, (up)varres)){
      n_pstate_err_no = su_ERR_NOTSUP;
      vp = NULL;
   }
   NYD_OU;
   return (vp == NULL);

jeoverflow:
   n_err(_("`tls': string length or offset overflows datatype\n"));
   n_pstate_err_no = su_ERR_OVERFLOW;
   goto jleave;

jesubcmd:
   n_err(_("`tls': invalid subcommand: %s\n"),
      n_shexp_quote_cp(*argv, FAL0));
jesynopsis:
   n_err(_("Synopsis: tls:  [<:argument:>]\n"));
jeinval:
   n_pstate_err_no = su_ERR_INVAL;
   goto jleave;
}

#include "su/code-ou.h"
#endif /* mx_HAVE_TLS */
/* s-it-mode */
s-nail-14.9.15/src/mx/tty.c000066400000000000000000004266751352610246600153060ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ TTY (command line) editing interaction.
 *@ Because we have (had) multiple line-editor implementations, including our
 *@ own M(ailx) L(ine) E(ditor), change the file layout a bit and place those
 *@ one after the other below the other externals.
 *
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE tty
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

#include 

#if defined mx_HAVE_MLE
# include 
# include 

# ifdef mx_HAVE_KEY_BINDINGS
#  include 
# endif
#endif

#include "mx/file-locks.h"
#include "mx/file-streams.h"
#include "mx/sigs.h"
#include "mx/termios.h"
#include "mx/ui-str.h"

#ifdef mx_HAVE_MLE
# include "mx/colour.h"
# include "mx/termcap.h"
#endif

#include "mx/tty.h"
/* TODO fake */
#include "su/code-in.h"

FILE *mx_tty_fp; /* Our terminal output TODO input channel */

boole
mx_tty_yesorno(char const * volatile prompt, boole noninteract_default){
   boole rv;
   NYD_IN;

   if(!(n_psonce & n_PSO_INTERACTIVE) || (n_pstate & n_PS_ROBOT))
      rv = noninteract_default;
   else{
      uz lsize;
      char *ldat;
      char const *quest;

      rv = FAL0;

      quest = noninteract_default ? _("[yes]/no? ") : _("[no]/yes? ");
      if(prompt == NIL)
         prompt = _("Continue");
      prompt = savecatsep(prompt, ' ', quest);

      mx_fs_linepool_aquire(&ldat, &lsize);
      while(n_go_input(n_GO_INPUT_CTX_DEFAULT | n_GO_INPUT_NL_ESC, prompt,
               &ldat, &lsize, NIL,NIL) >= 0){
         boole x;

         x = n_boolify(ldat, UZ_MAX, noninteract_default);
         if(x >= FAL0){
            rv = x;
            break;
         }
      }
      mx_fs_linepool_release(ldat, lsize);
   }

   NYD_OU;
   return rv;
}

#ifdef mx_HAVE_NET
char *
mx_tty_getuser(char const * volatile query) /* TODO v15-compat obsolete */
{
   uz lsize;
   char *ldat, *user;
   NYD_IN;

   if (query == NULL)
      query = _("User: ");

   mx_fs_linepool_aquire(&ldat, &lsize);
   if(n_go_input(n_GO_INPUT_CTX_DEFAULT | n_GO_INPUT_NL_ESC, query,
         &ldat, &lsize, NIL, NIL) >= 0)
      user = savestr(ldat);
   else
      user = NIL;
   mx_fs_linepool_release(ldat, lsize);

   NYD_OU;
   return user;
}

char *
mx_tty_getpass(char const *query){
   uz lsize;
   char *ldat, *pass;
   NYD_IN;

   pass = NIL;

   if(n_psonce & n_PSO_TTYANY){
      if(query == NIL)
         query = _("Password: ");

      mx_termios_cmdx(mx_TERMIOS_CMD_PUSH | mx_TERMIOS_CMD_PASSWORD);

      fputs(query, mx_tty_fp);
      fflush(mx_tty_fp);

      mx_fs_linepool_aquire(&ldat, &lsize);
      if(readline_restart(mx_tty_fp, &ldat, &lsize, 0) >= 0)
         pass = savestr(ldat);
      mx_fs_linepool_release(ldat, lsize);

      mx_termios_cmdx(mx_TERMIOS_CMD_POP | mx_TERMIOS_CMD_PASSWORD);

      putc('\n', mx_tty_fp);
   }

   NYD_OU;
   return pass;
}
#endif /* mx_HAVE_NET */

u32
mx_tty_create_prompt(struct n_string *store, char const *xprompt,
      enum n_go_input_flags gif){
   struct n_visual_info_ctx vic;
   struct str in, out;
   u32 pwidth, poff;
   char const *cp;
   NYD2_IN;
   ASSERT(n_psonce & n_PSO_INTERACTIVE);

   /* Prompt creation indicates that prompt printing is directly ahead, so take
    * this opportunity of UI-in-a-known-state and advertise the error ring */
#ifdef mx_HAVE_ERRORS
   if(!(n_psonce & n_PSO_ERRORS_NOTED) && (n_pstate & n_PS_ERRORS_PROMPT)){
      n_psonce |= n_PSO_ERRORS_NOTED;
      n_err(_("There are new messages in the error message ring "
         "(denoted by %s),\n"
         "  which can be managed with the `errors' command\n"),
         V_(n_error));
   }
#endif

jredo:
   n_string_trunc(store, 0);

   if(gif & n_GO_INPUT_PROMPT_NONE){
      pwidth = poff = 0;
      goto jleave;
   }

   /* Possible error/info prefix? */
#ifdef mx_HAVE_ERRORS
   if(n_pstate & n_PS_ERRORS_PROMPT){
      n_pstate &= ~n_PS_ERRORS_PROMPT;
      store = n_string_push_cp(store, V_(n_error));
   }
#endif

   if(!(gif & n_GO_INPUT_NL_FOLLOW) && n_cnd_if_is_skip()){
      if(store->s_len != 0)
         store = n_string_push_c(store, '#');
      store = n_string_push_cp(store, _("WHITEOUT: NEED `endif'"));
   }

   if((poff = store->s_len) != 0){
      ++poff;
      store = n_string_push_c(store, '#');
      store = n_string_push_c(store, ' ');
   }

   cp = (gif & n_GO_INPUT_PROMPT_EVAL)
         ? (gif & n_GO_INPUT_NL_FOLLOW ? ok_vlook(prompt2) : ok_vlook(prompt))
         : xprompt;
   if(cp != NULL && *cp != '\0'){
      enum n_shexp_state shs;

      store = n_string_push_cp(store, cp);
      in.s = n_string_cp(store);
      in.l = store->s_len;
      out = in;
      store = n_string_drop_ownership(store);

      shs = n_shexp_parse_token((n_SHEXP_PARSE_LOG |
            n_SHEXP_PARSE_IGNORE_EMPTY | n_SHEXP_PARSE_QUOTE_AUTO_FIXED |
            n_SHEXP_PARSE_QUOTE_AUTO_DSQ), store, &in, NULL);
      if((shs & n_SHEXP_STATE_ERR_MASK) || !(shs & n_SHEXP_STATE_STOP)){
         store = n_string_clear(store);
         store = n_string_take_ownership(store, out.s, out.l +1, out.l);
jeeval:
         n_err(_("*prompt2?* evaluation failed, actively unsetting it\n"));
         if(gif & n_GO_INPUT_NL_FOLLOW)
            ok_vclear(prompt2);
         else
            ok_vclear(prompt);
         goto jredo;
      }

      if(!store->s_auto)
         n_free(out.s);
   }

   /* Make all printable TODO not know, we want to pass through ESC/CSI! */
#if 0
   in.s = n_string_cp(store);
   in.l = store->s_len;
   makeprint(&in, &out);
   store = n_string_assign_buf(store, out.s, out.l);
   n_free(out.s);
#endif

   /* We need the visual width.. */
   su_mem_set(&vic, 0, sizeof vic);
   vic.vic_indat = n_string_cp(store);
   vic.vic_inlen = store->s_len;
   for(pwidth = 0; vic.vic_inlen > 0;){
      /* but \[ .. \] is not taken into account */
      if(vic.vic_indat[0] == '\\' && vic.vic_inlen > 1 &&
            vic.vic_indat[1] == '['){
         uz i;

         i = P2UZ(vic.vic_indat - store->s_dat);
         store = n_string_cut(store, i, 2);
         cp = &n_string_cp(store)[i];
         i = store->s_len - i;
         for(;; ++cp, --i){
            if(i < 2){
               n_err(_("Open \\[ sequence not closed in *prompt2?*\n"));
               goto jeeval;
            }
            if(cp[0] == '\\' && cp[1] == ']')
               break;
         }
         i = P2UZ(cp - store->s_dat);
         store = n_string_cut(store, i, 2);
         vic.vic_indat = &n_string_cp(store)[i];
         vic.vic_inlen = store->s_len - i;
      }else if(!n_visual_info(&vic, n_VISUAL_INFO_WIDTH_QUERY |
            n_VISUAL_INFO_ONE_CHAR)){
         n_err(_("Character set error in evaluation of *prompt2?*\n"));
         goto jeeval;
      }else{
         pwidth += (u32)vic.vic_vi_width;
         vic.vic_indat = vic.vic_oudat;
         vic.vic_inlen = vic.vic_oulen;
      }
   }

   /* And there may be colour support, too */
#ifdef mx_HAVE_COLOUR
   if(mx_COLOUR_IS_ACTIVE()){
      struct mx_colour_pen *ccp;
      struct str const *rsp, *psp, *esp;

      psp = NIL;
      if((rsp = mx_colour_reset_to_str()) != NIL &&
         (ccp = mx_colour_pen_create(mx_COLOUR_ID_MLE_PROMPT, NIL)) != NIL &&
            (psp = mx_colour_pen_to_str(ccp)) != NIL){
         store = n_string_insert_buf(store, poff, psp->s, psp->l);
         store = n_string_push_buf(store, rsp->s, rsp->l);
      }

      if(poff > 0 && rsp != NIL &&
            (((ccp = mx_colour_pen_create(mx_COLOUR_ID_MLE_ERROR, NIL)
               ) != NIL &&
             (esp = mx_colour_pen_to_str(ccp)) != NIL) || (esp = psp) != NIL)){
         store = n_string_insert_buf(store, poff, rsp->s, rsp->l);
         store = n_string_unshift_buf(store, esp->s, esp->l);
      }
   }
#endif /* mx_HAVE_COLOUR */

jleave:
   NYD2_OU;
   return pwidth;
}

/*
 * MLE: the Mailx-Line-Editor, our homebrew editor
 * (inspired from NetBSDs sh(1) and dash(1)s hetio.c).
 *
 * Only used in interactive mode.
 * TODO . This code should be splitted in funs/raw input/bind modules.
 * TODO . We work with wide characters, but not for buffer takeovers and
 * TODO   cell2save()ings.  This should be changed.  For the former the buffer
 * TODO   thus needs to be converted to wide first, and then simply be fed in.
 * TODO . We repaint too much.  To overcome this use the same approach that my
 * TODO   terminal library uses, add a true "virtual screen line" that stores
 * TODO   the actually visible content, keep a notion of "first modified slot"
 * TODO   and "last modified slot" (including "unknown" and "any" specials),
 * TODO   update that virtual instead, then synchronize what has truly changed.
 * TODO   I.e., add an indirection layer.
 * TODO .. This also has an effect on our signal hook (as long as this codebase
 * TODO    uses SA_RESTART), which currently does a tremendous amount of work.
 * TODO    With double-buffer, it could simply write through the prepared one.
 * TODO . No BIDI support.
 * TODO . `bind': we currently use only one lookup tree.
 * TODO   For graceful behaviour (in conjunction with mx_HAVE_TERMCAP) we
 * TODO   need a lower level tree, which possibly combines bytes into "symbolic
 * TODO   wchar_t values", into "keys" that is, as applicable, and an upper
 * TODO   layer which only works on "keys" in order to possibly combine them
 * TODO   into key sequences.  We can reuse existent tree code for that.
 * TODO   We need an additional hashmap which maps termcap/terminfo names to
 * TODO   (their byte representations and) a dynamically assigned unique
 * TODO   "symbolic wchar_t value".  This implies we may have incompatibilities
 * TODO   when __STDC_ISO_10646__ is not defined.  Also we do need takeover-
 * TODO   bytes storage, but it can be a string_creat_auto in the line struct.
 * TODO   Until then we can run into ambiguities; in rare occasions.
 */
#ifdef mx_HAVE_MLE
/* To avoid memory leaks etc. with the current codebase that simply longjmp(3)s
 * we're forced to use the very same buffer--the one that is passed through to
 * us from the outside--to store anything we need, i.e., a "struct cell[]", and
 * convert that on-the-fly back to the plain char* result once we're done.
 * To simplify our live, use savestr() buffers for all other needed memory */

# ifdef mx_HAVE_KEY_BINDINGS
   /* Default *bind-timeout* key-sequence continuation timeout, in tenths of
    * a second.  Must fit in 8-bit!  Update the manual upon change! */
#  define a_TTY_BIND_TIMEOUT 2
#  define a_TTY_BIND_TIMEOUT_MAX S8_MAX

CTAV(a_TTY_BIND_TIMEOUT_MAX <= U8_MAX);

   /* We have a chicken-and-egg problem with `bind' and our termcap layer,
    * because we may not initialize the latter automatically to allow users to
    * specify *termcap-disable* and let it mean exactly that.
    * On the other hand users can be expected to use `bind' in resources.
    * Therefore bindings which involve termcap/terminfo sequences, and which
    * are defined before n_PSO_STARTED signals usability of termcap/terminfo,
    * will be (partially) delayed until tty_init() is called.
    * And we preallocate space for the expansion of the resolved capability */
#  define a_TTY_BIND_CAPNAME_MAX 15
#  define a_TTY_BIND_CAPEXP_ROUNDUP 16

CTAV(IS_POW2(a_TTY_BIND_CAPEXP_ROUNDUP));
CTA(a_TTY_BIND_CAPEXP_ROUNDUP <= S8_MAX / 2, "Variable must fit in 6-bit");
CTA(a_TTY_BIND_CAPEXP_ROUNDUP >= 8, "Variable too small");

   /* Bind lookup trees organized in (wchar_t indexed) hashmaps */
#  define a_TTY_PRIME 0xBu
# endif /* mx_HAVE_KEY_BINDINGS */

# ifdef mx_HAVE_HISTORY
   /* The first line of the history file is used as a marker after >v14.9.6 */
#  define a_TTY_HIST_MARKER "@s-mailx history v2"
# endif

/* The maximum size (of a_tty_cell's) in a line */
# define a_TTY_LINE_MAX S32_MAX

/* (Some more CTAs around) */
CTA(a_TTY_LINE_MAX <= S32_MAX,
   "a_TTY_LINE_MAX larger than S32_MAX, but the MLE uses 32-bit arithmetic");

/* When shall the visual screen be scrolled, in % of usable screen width */
# define a_TTY_SCROLL_MARGIN_LEFT 15
# define a_TTY_SCROLL_MARGIN_RIGHT 10

/* fexpand() flags for expand-on-tab */
# define a_TTY_TAB_FEXP_FL \
   (FEXP_NOPROTO | FEXP_FULL | FEXP_SILENT | FEXP_MULTIOK)

/* Columns to ripoff: position indicator.
 * Should be >= 4 to dig the position indicator that we place (if there is
 * sufficient space) */
# define a_TTY_WIDTH_RIPOFF 4

/* The implementation of the MLE functions always exists, and is based upon
 * the a_TTY_BIND_FUN_* constants, so most of this enum is always necessary */
enum a_tty_bind_flags{
# ifdef mx_HAVE_KEY_BINDINGS
   a_TTY_BIND_RESOLVE = 1u<<8, /* Term cap. yet needs to be resolved */
   a_TTY_BIND_DEFUNCT = 1u<<9, /* Unicode/term cap. used but not avail. */
   a_TTY__BIND_MASK = a_TTY_BIND_RESOLVE | a_TTY_BIND_DEFUNCT,
   /* MLE fun assigned to a one-byte-sequence: this may be used for special
    * key-sequence bypass processing */
   a_TTY_BIND_MLE1CNTRL = 1u<<10,
   a_TTY_BIND_NOCOMMIT = 1u<<11, /* Expansion shall be editable */
# endif

   /* MLE internal commands */
   a_TTY_BIND_FUN_INTERNAL = 1u<<15,
   a_TTY__BIND_FUN_SHIFT = 16u,
   a_TTY__BIND_FUN_SHIFTMAX = 24u,
   a_TTY__BIND_FUN_MASK = ((1u << a_TTY__BIND_FUN_SHIFTMAX) - 1) &
         ~((1u << a_TTY__BIND_FUN_SHIFT) - 1),
# define a_TTY_BIND_FUN_REDUCE(X) \
   (((u32)(X) & a_TTY__BIND_FUN_MASK) >> a_TTY__BIND_FUN_SHIFT)
# define a_TTY_BIND_FUN_EXPAND(X) \
   (((u32)(X) & (a_TTY__BIND_FUN_MASK >> a_TTY__BIND_FUN_SHIFT)) << \
      a_TTY__BIND_FUN_SHIFT)
# undef a_X
# define a_X(N,I)\
   a_TTY_BIND_FUN_ ## N = a_TTY_BIND_FUN_EXPAND(I),

   a_X(BELL, 0)
   a_X(GO_BWD, 1) a_X(GO_FWD, 2)
   a_X(GO_WORD_BWD, 3) a_X(GO_WORD_FWD, 4)
   a_X(GO_SCREEN_BWD, 5) a_X(GO_SCREEN_FWD, 6)
   a_X(GO_HOME, 7) a_X(GO_END, 8)
   a_X(DEL_BWD, 9) a_X(DEL_FWD, 10)
   a_X(SNARF_WORD_BWD, 11) a_X(SNARF_WORD_FWD, 12)
   a_X(SNARF_END, 13) a_X(SNARF_LINE, 14)
   a_X(HIST_BWD, 15) a_X(HIST_FWD, 16)
   a_X(HIST_SRCH_BWD, 17) a_X(HIST_SRCH_FWD, 18)
   a_X(REPAINT, 19)
   a_X(QUOTE_RNDTRIP, 20)
   a_X(PROMPT_CHAR, 21)
   a_X(COMPLETE, 22)
   a_X(PASTE, 23)
   a_X(CLEAR_SCREEN, 24)

   a_X(RAISE_INT, 25)
   a_X(RAISE_QUIT, 26)
   a_X(RAISE_TSTP, 27)

   a_X(CANCEL, 28)
   a_X(RESET, 29)
   a_X(FULLRESET, 30)

   a_X(COMMIT, 31) /* Must be last one (else adjust CTAs)! */
# undef a_X

   a_TTY__BIND_LAST = 1u<<28
};
# ifdef mx_HAVE_KEY_BINDINGS
CTA((u32)a_TTY_BIND_RESOLVE >= (u32)n__GO_INPUT_CTX_MAX1,
   "Bit carrier lower boundary must be raised to avoid value sharing");
# endif
CTA(a_TTY_BIND_FUN_EXPAND(a_TTY_BIND_FUN_COMMIT) <
      (1u << a_TTY__BIND_FUN_SHIFTMAX),
   "Bit carrier range must be expanded to represent necessary bits");
CTA(a_TTY__BIND_LAST >= (1u << a_TTY__BIND_FUN_SHIFTMAX),
   "Bit carrier upper boundary must be raised to avoid value sharing");
CTA(UCMP(64, a_TTY__BIND_LAST, <=, S32_MAX),
   "Flag bits excess storage datatype" /* And we need one bit free */);

enum a_tty_fun_status{
   a_TTY_FUN_STATUS_OK, /* Worked, next character */
   a_TTY_FUN_STATUS_COMMIT, /* Line done */
   a_TTY_FUN_STATUS_RESTART, /* Complete restart, reset multibyte etc. */
   a_TTY_FUN_STATUS_END /* End, return EOF */
};

# ifdef mx_HAVE_HISTORY
enum a_tty_hist_flags{
   a_TTY_HIST_CTX_DEFAULT = n_GO_INPUT_CTX_DEFAULT,
   a_TTY_HIST_CTX_COMPOSE = n_GO_INPUT_CTX_COMPOSE,
   a_TTY_HIST_CTX_MASK = n__GO_INPUT_CTX_MASK,
   /* Cannot use enum n_go_input_flags for the rest, need to stay in 8-bit */
   a_TTY_HIST_GABBY = 1u<<7,
   a_TTY_HIST__MAX = a_TTY_HIST_GABBY
};
CTA(a_TTY_HIST_CTX_MASK < a_TTY_HIST_GABBY, "Enumeration value overlap");
# endif

enum a_tty_visual_flags{
   a_TTY_VF_NONE,
   a_TTY_VF_MOD_CURSOR = 1u<<0, /* Cursor moved */
   a_TTY_VF_MOD_CONTENT = 1u<<1, /* Content modified */
   a_TTY_VF_MOD_DIRTY = 1u<<2, /* Needs complete repaint */
   a_TTY_VF_MOD_SINGLE = 1u<<3, /* TODO Drop if indirection as above comes */
   a_TTY_VF_REFRESH = a_TTY_VF_MOD_DIRTY | a_TTY_VF_MOD_CURSOR |
         a_TTY_VF_MOD_CONTENT | a_TTY_VF_MOD_SINGLE,
   a_TTY_VF_BELL = 1u<<8, /* Ring the bell */
   a_TTY_VF_SYNC = 1u<<9, /* Flush/Sync I/O channel */

   a_TTY_VF_ALL_MASK = a_TTY_VF_REFRESH | a_TTY_VF_BELL | a_TTY_VF_SYNC,
   a_TTY__VF_LAST = a_TTY_VF_SYNC
};

# ifdef mx_HAVE_KEY_BINDINGS
struct a_tty_bind_ctx{
   struct a_tty_bind_ctx *tbc_next;
   char *tbc_seq; /* quence as given (poss. re-quoted), in .tb__buf */
   char *tbc_exp; /* ansion, in .tb__buf */
   /* The .tbc_seq'uence with any terminal capabilities resolved; in fact an
    * array of structures, the first entry of which is {s32 buf_len_iscap;}
    * where the signed bit indicates whether the buffer is a resolved terminal
    * capability instead of a (possibly multibyte) character.  In .tbc__buf */
   char *tbc_cnv;
   u32 tbc_seq_len;
   u32 tbc_exp_len;
   u32 tbc_cnv_len;
   u32 tbc_flags;
   char tbc__buf[VFIELD_SIZE(0)];
};
# endif /* mx_HAVE_KEY_BINDINGS */

struct a_tty_bind_builtin_tuple{
   boole tbbt_iskey;  /* Whether this is a control key; else termcap query */
   char tbbt_ckey; /* Control code */
   u16 tbbt_query; /* enum mx_termcap_query (instead) */
   char tbbt_exp[12]; /* String or [0]=NUL/[1]=BIND_FUN_REDUCE() */
};
CTA(mx__TERMCAP_QUERY_MAX1 <= U16_MAX,
   "Enumeration cannot be stored in datatype");

# ifdef mx_HAVE_KEY_BINDINGS
struct a_tty_bind_parse_ctx{
   char const *tbpc_cmd; /* Command which parses */
   char const *tbpc_in_seq; /* In: key sequence */
   struct str tbpc_exp; /* In/Out: expansion (or NULL) */
   struct a_tty_bind_ctx *tbpc_tbcp; /* Out: if yet existent */
   struct a_tty_bind_ctx *tbpc_ltbcp; /* Out: the one before .tbpc_tbcp */
   char *tbpc_seq; /* Out: normalized sequence */
   char *tbpc_cnv; /* Out: sequence when read(2)ing it */
   u32 tbpc_seq_len;
   u32 tbpc_cnv_len;
   u32 tbpc_cnv_align_mask; /* For creating a_tty_bind_ctx.tbc_cnv */
   u32 tbpc_flags; /* n_go_input_flags | a_tty_bind_flags */
};

/* Input character tree */
struct a_tty_bind_tree{
   struct a_tty_bind_tree *tbt_sibling; /* s at same level */
   struct a_tty_bind_tree *tbt_childs; /* Sequence continues.. here */
   struct a_tty_bind_tree *tbt_parent;
   struct a_tty_bind_ctx *tbt_bind; /* NULL for intermediates */
   wchar_t tbt_char; /* acter this level represents */
   boole tbt_isseq; /* Belongs to multibyte sequence */
   boole tbt_isseq_trail; /* ..is trailing byte of it */
   u8 tbt__dummy[2];
};
# endif /* mx_HAVE_KEY_BINDINGS */

struct a_tty_cell{
   wchar_t tc_wc;
   u16 tc_count; /* ..of bytes */
   u8 tc_width; /* Visual width; TAB==U8_MAX! */
   boole tc_novis; /* Don't display visually as such (control character) */
   char tc_cbuf[MB_LEN_MAX * 2]; /* .. plus reset shift sequence */
};

struct a_tty_global{
   struct a_tty_line *tg_line; /* To be able to access it from signal hdl */
# ifdef mx_HAVE_HISTORY
   struct a_tty_hist *tg_hist;
   struct a_tty_hist *tg_hist_tail;
   uz tg_hist_size;
   uz tg_hist_size_max;
# endif
# ifdef mx_HAVE_KEY_BINDINGS
   u32 tg_bind_cnt; /* Overall number of bindings */
   boole tg_bind_isdirty;
   boole tg_bind_isbuild;
#  define a_TTY_SHCUT_MAX (3 +1) /* Note: update manual on change! */
   u8 tg_bind__dummy[2];
   char tg_bind_shcut_cancel[n__GO_INPUT_CTX_MAX1][a_TTY_SHCUT_MAX];
   char tg_bind_shcut_prompt_char[n__GO_INPUT_CTX_MAX1][a_TTY_SHCUT_MAX];
   struct a_tty_bind_ctx *tg_bind[n__GO_INPUT_CTX_MAX1];
   struct a_tty_bind_tree *tg_bind_tree[n__GO_INPUT_CTX_MAX1][a_TTY_PRIME];
# endif
};
# ifdef mx_HAVE_KEY_BINDINGS
CTA(n__GO_INPUT_CTX_MAX1 == 3 && a_TTY_SHCUT_MAX == 4 &&
   su_FIELD_SIZEOF(struct a_tty_global, tg_bind__dummy) == 2,
   "Value results in array sizes that results in bad structure layout");
CTA(a_TTY_SHCUT_MAX > 1,
   "Users need at least one shortcut, plus NUL terminator");
# endif

# ifdef mx_HAVE_HISTORY
struct a_tty_hist{
   struct a_tty_hist *th_older;
   struct a_tty_hist *th_younger;
   u32 th_len;
   u8 th_flags; /* enum a_tty_hist_flags */
   char th_dat[VFIELD_SIZE(3)];
};
CTA(U8_MAX >= a_TTY_HIST__MAX, "Value exceeds datatype storage");
# endif

#if defined mx_HAVE_KEY_BINDINGS || defined mx_HAVE_HISTORY
struct a_tty_input_ctx_map{
   enum n_go_input_flags ticm_ctx;
   char const ticm_name[12]; /* Name of `bind' context */
};
#endif

struct a_tty_line{
   /* Caller pointers */
   char **tl_x_buf;
   uz *tl_x_bufsize;
   /* Input processing */
# ifdef mx_HAVE_KEY_BINDINGS
   wchar_t tl_bind_takeover; /* Leftover byte to consume next */
   u8 tl_bind_timeout; /* In-seq. inter-byte-timer, in 1/10th secs */
   u8 tl__bind_pad1[3];
   char (*tl_bind_shcut_cancel)[a_TTY_SHCUT_MAX]; /* Special _CANCEL control */
   char (*tl_bind_shcut_prompt_char)[a_TTY_SHCUT_MAX]; /* ..for _PROMPT_CHAR */
   struct a_tty_bind_tree *(*tl_bind_tree_hmap)[a_TTY_PRIME]; /* Lookup tree */
   struct a_tty_bind_tree *tl_bind_tree;
# endif
   /* Line data / content handling */
   u32 tl_count; /* ..of a_tty_cell's (<= a_TTY_LINE_MAX) */
   u32 tl_cursor; /* Current a_tty_cell insertion point */
   union{
      char *cbuf; /* *.tl_x_buf */
      struct a_tty_cell *cells;
   } tl_line;
   struct str tl_defc; /* Current default content */
   uz tl_defc_cursor_byte; /* Desired cursor position after takeover */
   struct str tl_savec; /* Saved default content */
   struct str tl_pastebuf; /* Last snarfed data */
# ifdef mx_HAVE_HISTORY
   struct a_tty_hist *tl_hist; /* History cursor */
# endif
   u32 tl_count_max; /* ..before buffer needs to grow */
   /* Visual data representation handling */
   u32 tl_vi_flags; /* enum a_tty_visual_flags */
   u32 tl_lst_count; /* .tl_count after last sync */
   u32 tl_lst_cursor; /* .tl_cursor after last sync */
   /* TODO Add another indirection layer by adding a tl_phy_line of
    * TODO a_tty_cell objects, incorporate changes in visual layer,
    * TODO then check what _really_ has changed, sync those changes only */
   struct a_tty_cell const *tl_phy_start; /* First visible cell, left border */
   u32 tl_phy_cursor; /* Physical cursor position */
   boole tl_quote_rndtrip; /* For _kht() expansion */
   u8 tl__pad2[3];
   u32 tl_goinflags; /* enum n_go_input_flags */
   u32 tl_prompt_length; /* Preclassified (TODO needed as tty_cell) */
   u32 tl_prompt_width;
   su_64( u8 tl__pad3[4]; )
   char const *tl_prompt; /* Preformatted prompt (including colours) */
   /* .tl_pos_buf is a hack */
# ifdef mx_HAVE_COLOUR
   char *tl_pos_buf; /* mle-position colour-on, [4], reset seq. */
   char *tl_pos; /* Address of the [4] */
# endif
};

# if defined mx_HAVE_KEY_BINDINGS || defined mx_HAVE_HISTORY
/* C99: use [INDEX]={} */
CTAV(n_GO_INPUT_CTX_BASE == 0);
CTAV(n_GO_INPUT_CTX_DEFAULT == 1);
CTAV(n_GO_INPUT_CTX_COMPOSE == 2);
static struct a_tty_input_ctx_map const
      a_tty_input_ctx_maps[n__GO_INPUT_CTX_MAX1] = {
   FIELD_INITI(n_GO_INPUT_CTX_BASE){n_GO_INPUT_CTX_BASE, "base"},
   FIELD_INITI(n_GO_INPUT_CTX_DEFAULT){n_GO_INPUT_CTX_DEFAULT, "default"},
   FIELD_INITI(n_GO_INPUT_CTX_COMPOSE){n_GO_INPUT_CTX_COMPOSE, "compose"}
};
#endif

# ifdef mx_HAVE_KEY_BINDINGS
/* Special functions which our MLE provides internally.
 * Update the manual upon change! */
static char const a_tty_bind_fun_names[][24] = {
#  undef a_X
#  define a_X(I,N) \
   FIELD_INITI(a_TTY_BIND_FUN_REDUCE(a_TTY_BIND_FUN_ ## I)) "mle-" N "\0",

   a_X(BELL, "bell")
   a_X(GO_BWD, "go-bwd") a_X(GO_FWD, "go-fwd")
   a_X(GO_WORD_BWD, "go-word-bwd") a_X(GO_WORD_FWD, "go-word-fwd")
   a_X(GO_SCREEN_BWD, "go-screen-bwd") a_X(GO_SCREEN_FWD, "go-screen-fwd")
   a_X(GO_HOME, "go-home") a_X(GO_END, "go-end")
   a_X(DEL_BWD, "del-bwd") a_X(DEL_FWD, "del-fwd")
   a_X(SNARF_WORD_BWD, "snarf-word-bwd") a_X(SNARF_WORD_FWD, "snarf-word-fwd")
   a_X(SNARF_END, "snarf-end") a_X(SNARF_LINE, "snarf-line")
   a_X(HIST_BWD, "hist-bwd") a_X(HIST_FWD, "hist-fwd")
   a_X(HIST_SRCH_BWD, "hist-srch-bwd") a_X(HIST_SRCH_FWD, "hist-srch-fwd")
   a_X(REPAINT, "repaint")
   a_X(QUOTE_RNDTRIP, "quote-rndtrip")
   a_X(PROMPT_CHAR, "prompt-char")
   a_X(COMPLETE, "complete")
   a_X(PASTE, "paste")
   a_X(CLEAR_SCREEN, "clear-screen")

   a_X(RAISE_INT, "raise-int")
   a_X(RAISE_QUIT, "raise-quit")
   a_X(RAISE_TSTP, "raise-tstp")

   a_X(CANCEL, "cancel")
   a_X(RESET, "reset")
   a_X(FULLRESET, "fullreset")
   a_X(COMMIT, "commit")

#  undef a_X
};
# endif /* mx_HAVE_KEY_BINDINGS */

/* The default key bindings (unless disallowed).  Update manual upon change!
 * A logical subset of this table is also used if !mx_HAVE_KEY_BINDINGS (more
 * expensive than a switch() on control codes directly, but less redundant).
 * The table for the "base" context */
static struct a_tty_bind_builtin_tuple const a_tty_bind_base_tuples[] = {
# undef a_X
# define a_X(K,S) \
   {TRU1, K, 0, {'\0', (char)a_TTY_BIND_FUN_REDUCE(a_TTY_BIND_FUN_ ## S),}},

   a_X('A', GO_HOME)
   a_X('B', GO_BWD)
   a_X('C', RAISE_INT)
   a_X('D', DEL_FWD)
   a_X('E', GO_END)
   a_X('F', GO_FWD)
   a_X('G', RESET)
   a_X('H', DEL_BWD)
   a_X('I', COMPLETE)
   a_X('J', COMMIT)
   a_X('K', SNARF_END)
   a_X('L', REPAINT)
   /* M: same as J */
   a_X('N', HIST_FWD)
   /* O: below */
   a_X('P', HIST_BWD)
   a_X('Q', QUOTE_RNDTRIP)
   a_X('R', HIST_SRCH_BWD)
   a_X('S', HIST_SRCH_FWD)
   a_X('T', PASTE)
   a_X('U', SNARF_LINE)
   a_X('V', PROMPT_CHAR)
   a_X('W', SNARF_WORD_BWD)
   a_X('X', GO_WORD_FWD)
   a_X('Y', GO_WORD_BWD)
   a_X('Z', RAISE_TSTP)

   a_X('[', CANCEL)
   /* \: below */
   /* ]: below */
   /* ^: below */
   a_X('_', SNARF_WORD_FWD)

   a_X('?', DEL_BWD)

# undef a_X
# define a_X(K,S) {TRU1, K, 0, {S}},

   /* The remains only if we have `bind' functionality available */
# ifdef mx_HAVE_KEY_BINDINGS
#  undef a_X
#  define a_X(Q,S) \
   {FAL0, '\0', mx_TERMCAP_QUERY_ ## Q,\
      {'\0', (char)a_TTY_BIND_FUN_REDUCE(a_TTY_BIND_FUN_ ## S),}},

   a_X(key_backspace, DEL_BWD) a_X(key_dc, DEL_FWD)
   a_X(key_eol, SNARF_END)
   a_X(key_home, GO_HOME) a_X(key_end, GO_END)
   a_X(key_left, GO_BWD) a_X(key_right, GO_FWD)
   a_X(xkey_aleft, GO_WORD_BWD) a_X(xkey_aright, GO_WORD_FWD)
   a_X(xkey_cleft, GO_SCREEN_BWD) a_X(xkey_cright, GO_SCREEN_FWD)
   a_X(key_sleft, GO_HOME) a_X(key_sright, GO_END)
   a_X(key_up, HIST_BWD) a_X(key_down, HIST_FWD)
# endif /* mx_HAVE_KEY_BINDINGS */
};

/* The table for the "default" context */
static struct a_tty_bind_builtin_tuple const a_tty_bind_default_tuples[] = {
# undef a_X
# define a_X(K,S) \
   {TRU1, K, 0, {'\0', (char)a_TTY_BIND_FUN_REDUCE(a_TTY_BIND_FUN_ ## S),}},

# undef a_X
# define a_X(K,S) {TRU1, K, 0, {S}},

   a_X('O', "dt")

   a_X('\\', "z+")
   a_X(']', "z$")
   a_X('^', "z0")

   /* The remains only if we have `bind' functionality available */
# ifdef mx_HAVE_KEY_BINDINGS
#  undef a_X
#  define a_X(Q,S) {FAL0, '\0', mx_TERMCAP_QUERY_ ## Q, {S}},

   a_X(key_shome, "z0") a_X(key_send, "z$")
   a_X(xkey_sup, "z0") a_X(xkey_sdown, "z$")
   a_X(key_ppage, "z-") a_X(key_npage, "z+")
   a_X(xkey_cup, "dotmove-") a_X(xkey_cdown, "dotmove+")
# endif /* mx_HAVE_KEY_BINDINGS */
};
# undef a_X

static struct a_tty_global a_tty;

/* */
static boole a_tty_on_state_change(up cookie, u32 tiossc, s32 sig);

# ifdef mx_HAVE_HISTORY
/* Load and save the history file, respectively */
static boole a_tty_hist_load(void);
static boole a_tty_hist_save(void);

/* Initialize .tg_hist_size_max and return desired history file, or NULL */
static char const *a_tty_hist__query_config(void);

/* (Definetely) Add an entry TODO yet assumes sigs_all_hild() is held! */
static void a_tty_hist_add(char const *s, enum n_go_input_flags gif);
# endif

/* Adjust an active raw mode to use / not use a timeout */
# ifdef mx_HAVE_KEY_BINDINGS
static void a_tty_term_rawmode_timeout(struct a_tty_line *tlp, boole enable);
# endif

/* 0-X (2), U8_MAX == \t / HT */
static u8 a_tty_wcwidth(wchar_t wc);

/* Memory / cell / word generics */
static void a_tty_check_grow(struct a_tty_line *tlp, u32 no
               su_DBG_LOC_ARGS_DECL);
static sz a_tty_cell2dat(struct a_tty_line *tlp);
static void a_tty_cell2save(struct a_tty_line *tlp);

/* Save away data bytes of given range (max = non-inclusive) */
static void a_tty_copy2paste(struct a_tty_line *tlp, struct a_tty_cell *tcpmin,
               struct a_tty_cell *tcpmax);

/* Ask user for hexadecimal number, interpret as UTF-32 */
static wchar_t a_tty_vinuni(struct a_tty_line *tlp);

/* Visual screen synchronization */
static boole a_tty_vi_refresh(struct a_tty_line *tlp);

static boole a_tty_vi__paint(struct a_tty_line *tlp);

/* Search for word boundary, starting at tl_cursor, in "dir"ection (<> 0).
 * Return <0 when moving is impossible (backward direction but in position 0,
 * forward direction but in outermost column), and relative distance to
 * tl_cursor otherwise */
static s32 a_tty_wboundary(struct a_tty_line *tlp, s32 dir);

/* Most function implementations */
static void a_tty_khome(struct a_tty_line *tlp, boole dobell);
static void a_tty_kend(struct a_tty_line *tlp);
static void a_tty_kbs(struct a_tty_line *tlp);
static void a_tty_ksnarf(struct a_tty_line *tlp, boole cplline,boole dobell);
static s32 a_tty_kdel(struct a_tty_line *tlp);
static void a_tty_kleft(struct a_tty_line *tlp);
static void a_tty_kright(struct a_tty_line *tlp);
static void a_tty_ksnarfw(struct a_tty_line *tlp, boole fwd);
static void a_tty_kgow(struct a_tty_line *tlp, s32 dir);
static void a_tty_kgoscr(struct a_tty_line *tlp, s32 dir);
static boole a_tty_kother(struct a_tty_line *tlp, wchar_t wc);
static u32 a_tty_kht(struct a_tty_line *tlp);

# ifdef mx_HAVE_HISTORY
/* Return U32_MAX on "exhaustion" */
static u32 a_tty_khist(struct a_tty_line *tlp, boole fwd);
static u32 a_tty_khist_search(struct a_tty_line *tlp, boole fwd);

static u32 a_tty__khist_shared(struct a_tty_line *tlp,
                  struct a_tty_hist *thp);
# endif

/* Handle a function */
static enum a_tty_fun_status a_tty_fun(struct a_tty_line *tlp,
                              enum a_tty_bind_flags tbf, uz *len);

/* Readline core */
static sz a_tty_readline(struct a_tty_line *tlp, uz len,
                  boole *histok_or_null  su_DBG_LOC_ARGS_DECL);

# ifdef mx_HAVE_KEY_BINDINGS
/* Find context or -1 */
static enum n_go_input_flags a_tty_bind_ctx_find(char const *name);

/* Create (or replace, if allowed) a binding */
static boole a_tty_bind_create(struct a_tty_bind_parse_ctx *tbpcp,
               boole replace);

/* Shared implementation to parse `bind' and `unbind' "key-sequence" and
 * "expansion" command line arguments into something that we can work with */
static boole a_tty_bind_parse(boole isbindcmd,
               struct a_tty_bind_parse_ctx *tbpcp);

/* Lazy resolve a termcap(5)/terminfo(5) (or *termcap*!) capability */
static void a_tty_bind_resolve(struct a_tty_bind_ctx *tbcp);

/* Delete an existing binding */
static void a_tty_bind_del(struct a_tty_bind_parse_ctx *tbpcp);

/* Life cycle of all input node trees */
static void a_tty_bind_tree_build(void);
static void a_tty_bind_tree_teardown(void);

static void a_tty__bind_tree_add(u32 hmap_idx,
               struct a_tty_bind_tree *store[a_TTY_PRIME],
               struct a_tty_bind_ctx *tbcp);
static struct a_tty_bind_tree *a_tty__bind_tree_add_wc(
               struct a_tty_bind_tree **treep, struct a_tty_bind_tree *parentp,
               wchar_t wc, boole isseq);
static void a_tty__bind_tree_free(struct a_tty_bind_tree *tbtp);
# endif /* mx_HAVE_KEY_BINDINGS */

static boole
a_tty_on_state_change(up cookie, u32 tiossc, s32 sig){
   boole rv;
   NYD; /* Signal handler */
   UNUSED(cookie);
   UNUSED(sig);

   rv = FAL0;

   if(tiossc & mx_TERMIOS_STATE_SUSPEND){
      mx_COLOUR( mx_colour_env_gut(); ) /* TODO NO SIMPLE SUSP POSSIBLE.. */
      mx_TERMCAP_SUSPEND((tiossc & mx_TERMIOS_STATE_POP) == 0 &&
         !(tiossc & mx_TERMIOS_STATE_SIGNAL));
      if((tiossc & (mx_TERMIOS_STATE_SIGNAL | mx_TERMIOS_STATE_JOB_SIGNAL)
            ) == mx_TERMIOS_STATE_SIGNAL)
         rv = TRU1;
   }else if(tiossc & mx_TERMIOS_STATE_RESUME){
      /* TODO THEREFORE NEED TO _GUT() .. _CREATE() ENTIRE ENVS!! */
      mx_COLOUR( mx_colour_env_create(mx_COLOUR_CTX_MLE, mx_tty_fp, FAL0); )
      mx_TERMCAP_RESUME(TRU1);
      a_tty.tg_line->tl_vi_flags |= a_TTY_VF_MOD_DIRTY;
      /* TODO Due to SA_RESTART we need to refresh the line in here! */
      a_tty_vi_refresh(a_tty.tg_line);
   }

   return rv;
}

# ifdef mx_HAVE_HISTORY
static boole
a_tty_hist_load(void){
   u8 version;
   uz lsize, cnt, llen;
   char *lbuf, *cp;
   FILE *f;
   char const *v;
   boole rv;
   NYD_IN;

   rv = TRU1;

   if((v = a_tty_hist__query_config()) == NULL ||
         a_tty.tg_hist_size_max == 0)
      goto jleave;

   mx_sigs_all_holdx(); /* TODO too heavy, yet we may jump even here!? */
   f = fopen(v, "r");
   if(f == NULL){
      int e;

      e = errno;
      n_err(_("Cannot read *history-file*=%s: %s\n"),
         n_shexp_quote_cp(v, FAL0), su_err_doc(e));
      rv = FAL0;
      goto jrele;
   }
   (void)mx_file_lock(fileno(f), mx_FILE_LOCK_TYPE_READ, 0,0, UZ_MAX);

   /* Clear old history */
   /* C99 */{
      struct a_tty_hist *thp;

      while((thp = a_tty.tg_hist) != NULL){
         a_tty.tg_hist = thp->th_older;
         n_free(thp);
      }
      a_tty.tg_hist_tail = NULL;
      a_tty.tg_hist_size = 0;
   }

   mx_fs_linepool_aquire(&lbuf, &lsize);

   cnt = (uz)fsize(f);
   version = 0;

   while(fgetline(&lbuf, &lsize, &cnt, &llen, f, FAL0) != NULL){
      cp = lbuf;
      /* Hand-edited history files may have this, probably */
      while(llen > 0 && su_cs_is_space(cp[0])){
         ++cp;
         --llen;
      }
      if(llen > 0 && cp[llen - 1] == '\n')
         cp[--llen] = '\0';

      /* Skip empty and comment lines */
      if(llen == 0 || cp[0] == '#')
         continue;

      if(UNLIKELY(version == 0) &&
            (version = su_cs_cmp(cp, a_TTY_HIST_MARKER) ? 1 : 2) != 1)
         continue;

      /* C99 */{
         enum n_go_input_flags gif;

         if(version == 2){
            if(llen <= 2){
               /* XXX n_err(_("Skipped invalid *history-file* entry: %s\n"),
                * XXX  n_shexp_quote_cp(cp));*/
               continue;
            }
            switch(*cp++){
            default:
            case 'd':
               gif = n_GO_INPUT_CTX_DEFAULT; /* == a_TTY_HIST_CTX_DEFAULT */
               break;
            case 'c':
               gif = n_GO_INPUT_CTX_COMPOSE; /* == a_TTY_HIST_CTX_COMPOSE */
               break;
            }

            if(*cp++ == '*')
               gif |= n_GO_INPUT_HIST_GABBY;

            while(*cp == ' ')
               ++cp;
         }else{
            gif = n_GO_INPUT_CTX_DEFAULT;
            if(cp[0] == '*'){
               ++cp;
               gif |= n_GO_INPUT_HIST_GABBY;
            }
         }

         a_tty_hist_add(cp, gif);
      }
   }

   mx_fs_linepool_release(lbuf, lsize);

   fclose(f);
jrele:
   mx_sigs_all_rele(); /* XXX remove jumps */
jleave:
   NYD_OU;
   return rv;
}

static boole
a_tty_hist_save(void){
   uz i;
   struct a_tty_hist *thp;
   FILE *f;
   char const *v;
   boole rv, dogabby;
   NYD_IN;

   rv = TRU1;

   if((v = a_tty_hist__query_config()) == NULL ||
         a_tty.tg_hist_size_max == 0)
      goto jleave;

   dogabby = ok_blook(history_gabby_persist);

   if((thp = a_tty.tg_hist) != NULL)
      for(i = a_tty.tg_hist_size_max; thp->th_older != NULL;
            thp = thp->th_older)
         if((dogabby || !(thp->th_flags & a_TTY_HIST_GABBY)) && --i == 0)
            break;

   mx_sigs_all_holdx(); /* TODO too heavy, yet we may jump even here!? */
   if((f = fopen(v, "w")) == NULL){ /* TODO temporary + rename?! */
      int e;

      e = errno;
      n_err(_("Cannot write *history-file*=%s: %s\n"),
         n_shexp_quote_cp(v, FAL0), su_err_doc(e));
      rv = FAL0;
      goto jrele;
   }
   (void)mx_file_lock(fileno(f), mx_FILE_LOCK_TYPE_WRITE, 0,0, UZ_MAX);

   if(fwrite(a_TTY_HIST_MARKER "\n", sizeof *a_TTY_HIST_MARKER,
         sizeof(a_TTY_HIST_MARKER "\n") -1, f) !=
         sizeof(a_TTY_HIST_MARKER "\n") -1)
      goto jioerr;
   else for(; thp != NULL; thp = thp->th_younger){
      if(dogabby || !(thp->th_flags & a_TTY_HIST_GABBY)){
         char c;

         switch(thp->th_flags & a_TTY_HIST_CTX_MASK){
         default:
         case a_TTY_HIST_CTX_DEFAULT:
            c = 'd';
            break;
         case a_TTY_HIST_CTX_COMPOSE:
            c = 'c';
            break;
         }
         if(putc(c, f) == EOF)
            goto jioerr;

         if((thp->th_flags & a_TTY_HIST_GABBY) && putc('*', f) == EOF)
            goto jioerr;

         if(putc(' ', f) == EOF ||
               fwrite(thp->th_dat, sizeof *thp->th_dat, thp->th_len, f) !=
                  sizeof(*thp->th_dat) * thp->th_len ||
               putc('\n', f) == EOF){
jioerr:
            n_err(_("I/O error while writing *history-file* %s\n"),
               n_shexp_quote_cp(v, FAL0));
            rv = FAL0;
            break;
         }
      }
   }

   fclose(f);
jrele:
   mx_sigs_all_rele(); /* XXX remove jumps */
jleave:
   NYD_OU;
   return rv;
}

static char const *
a_tty_hist__query_config(void){
   char const *rv, *cp;
   NYD2_IN;

   if((cp = ok_vlook(NAIL_HISTSIZE)) != NULL)
      n_OBSOLETE(_("please use *history-size* instead of *NAIL_HISTSIZE*"));
   if((rv = ok_vlook(history_size)) == NULL)
      rv = cp;
   if(rv == NULL)
      a_tty.tg_hist_size_max = UZ_MAX;
   else
      (void)su_idec_uz_cp(&a_tty.tg_hist_size_max, rv, 10, NULL);

   if((cp = ok_vlook(NAIL_HISTFILE)) != NULL)
      n_OBSOLETE(_("please use *history-file* instead of *NAIL_HISTFILE*"));
   if((rv = ok_vlook(history_file)) == NULL)
      rv = cp;
   if(rv != NULL)
      rv = fexpand(rv, FEXP_LOCAL | FEXP_NSHELL);
   NYD2_OU;
   return rv;
}

static void
a_tty_hist_add(char const *s, enum n_go_input_flags gif){
   u32 l;
   struct a_tty_hist *thp, *othp, *ythp;
   NYD2_IN;

   l = (u32)su_cs_len(s); /* xxx simply do not store if >= S32_MAX */

   /* Eliminating duplicates is expensive, but simply inacceptable so
    * during the load of a potentially large history file! */
   if(n_psonce & n_PSO_LINE_EDITOR_INIT)
      for(thp = a_tty.tg_hist; thp != NULL; thp = thp->th_older)
         if(thp->th_len == l && !su_cs_cmp(thp->th_dat, s)){
            thp->th_flags = (gif & a_TTY_HIST_CTX_MASK) |
                  (gif & n_GO_INPUT_HIST_GABBY ? a_TTY_HIST_GABBY : 0);
            othp = thp->th_older;
            ythp = thp->th_younger;
            if(othp != NULL)
               othp->th_younger = ythp;
            else
               a_tty.tg_hist_tail = ythp;
            if(ythp != NULL)
               ythp->th_older = othp;
            else
               a_tty.tg_hist = othp;
            goto jleave;
         }

   if(LIKELY(a_tty.tg_hist_size <= a_tty.tg_hist_size_max))
      ++a_tty.tg_hist_size;
   else{
      --a_tty.tg_hist_size;
      if((thp = a_tty.tg_hist_tail) != NULL){
         if((a_tty.tg_hist_tail = thp->th_younger) == NULL)
            a_tty.tg_hist = NULL;
         else
            a_tty.tg_hist_tail->th_older = NULL;
         n_free(thp);
      }
   }

   thp = n_alloc(VSTRUCT_SIZEOF(struct a_tty_hist, th_dat) + l +1);
   thp->th_len = l;
   thp->th_flags = (gif & a_TTY_HIST_CTX_MASK) |
         (gif & n_GO_INPUT_HIST_GABBY ? a_TTY_HIST_GABBY : 0);
   su_mem_copy(thp->th_dat, s, l +1);
jleave:
   if((thp->th_older = a_tty.tg_hist) != NULL)
      a_tty.tg_hist->th_younger = thp;
   else
      a_tty.tg_hist_tail = thp;
   thp->th_younger = NULL;
   a_tty.tg_hist = thp;
   NYD2_OU;
}
# endif /* mx_HAVE_HISTORY */

# ifdef mx_HAVE_KEY_BINDINGS
static void
a_tty_term_rawmode_timeout(struct a_tty_line *tlp, boole enable){
   u32 tiosc;
   u8 i;
   NYD2_IN;

   if(enable){
      tiosc = mx_TERMIOS_CMD_RAW_TIMEOUT;
      if((i = tlp->tl_bind_timeout) == 0)
         i = a_TTY_BIND_TIMEOUT;
   }else{
      tiosc = mx_TERMIOS_CMD_RAW;
      i = 1;
   }

   mx_termios_cmd(tiosc, i);
   NYD2_OU;
}
# endif /* mx_HAVE_KEY_BINDINGS */

static u8
a_tty_wcwidth(wchar_t wc){
   u8 rv;
   NYD2_IN;

   /* Special case the reverse solidus at first */
   if(wc == '\t')
      rv = U8_MAX;
   else{
      int i;

# ifdef mx_HAVE_WCWIDTH
      rv = ((i = wcwidth(wc)) > 0) ? (u8)i : 0;
# else
      rv = iswprint(wc) ? 1 + (wc >= 0x1100u) : 0; /* TODO use S-CText */
# endif
   }
   NYD2_OU;
   return rv;
}

static void
a_tty_check_grow(struct a_tty_line *tlp, u32 no  su_DBG_LOC_ARGS_DECL){
   u32 cmax;
   NYD2_IN;

   if(UNLIKELY((cmax = tlp->tl_count + no) > tlp->tl_count_max)){
      uz i;

      i = cmax * sizeof(struct a_tty_cell) + 2 * sizeof(struct a_tty_cell);
      if(LIKELY(i >= *tlp->tl_x_bufsize)){
         mx_sigs_all_holdx(); /* XXX v15 drop */
         i <<= 1;
         tlp->tl_line.cbuf =
         *tlp->tl_x_buf = su_MEM_REALLOC_LOCOR(*tlp->tl_x_buf, i,
               su_DBG_LOC_ARGS_ORUSE);
         mx_sigs_all_rele(); /* XXX v15 drop */
      }
      tlp->tl_count_max = cmax;
      *tlp->tl_x_bufsize = i;
   }
   NYD2_OU;
}

static sz
a_tty_cell2dat(struct a_tty_line *tlp){
   uz len, i;
   NYD2_IN;

   len = 0;

   if(LIKELY((i = tlp->tl_count) > 0)){
      struct a_tty_cell const *tcap;

      tcap = tlp->tl_line.cells;
      do{
         su_mem_copy(tlp->tl_line.cbuf + len, tcap->tc_cbuf, tcap->tc_count);
         len += tcap->tc_count;
      }while(++tcap, --i > 0);
   }

   tlp->tl_line.cbuf[len] = '\0';
   NYD2_OU;
   return (sz)len;
}

static void
a_tty_cell2save(struct a_tty_line *tlp){
   uz len, i;
   struct a_tty_cell *tcap;
   NYD2_IN;

   tlp->tl_savec.s = NULL;
   tlp->tl_savec.l = 0;

   if(UNLIKELY(tlp->tl_count == 0))
      goto jleave;

   for(tcap = tlp->tl_line.cells, len = 0, i = tlp->tl_count; i > 0;
         ++tcap, --i)
      len += tcap->tc_count;

   tlp->tl_savec.s = n_autorec_alloc((tlp->tl_savec.l = len) +1);

   for(tcap = tlp->tl_line.cells, len = 0, i = tlp->tl_count; i > 0;
         ++tcap, --i){
      su_mem_copy(tlp->tl_savec.s + len, tcap->tc_cbuf, tcap->tc_count);
      len += tcap->tc_count;
   }
   tlp->tl_savec.s[len] = '\0';
jleave:
   NYD2_OU;
}

static void
a_tty_copy2paste(struct a_tty_line *tlp, struct a_tty_cell *tcpmin,
      struct a_tty_cell *tcpmax){
   char *cp;
   struct a_tty_cell *tcp;
   uz l;
   NYD2_IN;

   l = 0;
   for(tcp = tcpmin; tcp < tcpmax; ++tcp)
      l += tcp->tc_count;

   tlp->tl_pastebuf.s = cp = n_autorec_alloc((tlp->tl_pastebuf.l = l) +1);

   for(tcp = tcpmin; tcp < tcpmax; cp += l, ++tcp)
      su_mem_copy(cp, tcp->tc_cbuf, l = tcp->tc_count);
   *cp = '\0';
   NYD2_OU;
}

static wchar_t
a_tty_vinuni(struct a_tty_line *tlp){
   char buf[16];
   uz i;
   wchar_t wc;
   NYD2_IN;

   wc = '\0';

   if(!mx_termcap_cmdx(mx_TERMCAP_CMD_cr) ||
         !mx_termcap_cmd(mx_TERMCAP_CMD_ce, 0, -1))
      goto jleave;

   /* C99 */{
      struct str const *cpre, *csuf;

      cpre = csuf = NULL;
#ifdef mx_HAVE_COLOUR
      if(mx_COLOUR_IS_ACTIVE()){
         struct mx_colour_pen *cpen;

         cpen = mx_colour_pen_create(mx_COLOUR_ID_MLE_PROMPT, NULL);
         if((cpre = mx_colour_pen_to_str(cpen)) != NULL)
            csuf = mx_colour_reset_to_str();
      }
#endif
      fprintf(mx_tty_fp, _("%sPlease enter Unicode code point:%s "),
         (cpre != NIL ? cpre->s : n_empty), (csuf != NIL ? csuf->s : n_empty));
   }
   fflush(mx_tty_fp);

   buf[sizeof(buf) -1] = '\0';
   for(i = 0;;){
      if(read(STDIN_FILENO, &buf[i], 1) != 1){
         if(su_err_no() == su_ERR_INTR) /* xxx #if !SA_RESTART ? */
            continue;
         goto jleave;
      }
      if(buf[i] == '\n')
         break;
      if(!su_cs_is_xdigit(buf[i])){
         char const emsg[] = "[0-9a-fA-F]";

         LCTA(sizeof emsg <= sizeof(buf), "Preallocated buffer too small");
         su_mem_copy(buf, emsg, sizeof emsg);
         goto jerr;
      }

      putc(buf[i], mx_tty_fp);
      fflush(mx_tty_fp);
      if(++i == sizeof buf)
         goto jerr;
   }
   buf[i] = '\0';

   if((su_idec_uz_cp(&i, buf, 16, NULL
            ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
         ) != su_IDEC_STATE_CONSUMED || i > 0x10FFFF/* XXX magic; CText */){
jerr:
      n_err(_("\nInvalid input: %s\n"), buf);
      goto jleave;
   }

   wc = (wchar_t)i;
jleave:
   tlp->tl_vi_flags |= a_TTY_VF_MOD_DIRTY | (wc == '\0' ? a_TTY_VF_BELL : 0);
   NYD2_OU;
   return wc;
}

static boole
a_tty_vi_refresh(struct a_tty_line *tlp){
   boole rv;
   NYD2_IN;

   if(tlp->tl_vi_flags & a_TTY_VF_BELL){
      tlp->tl_vi_flags |= a_TTY_VF_SYNC;
      if(putc('\a', mx_tty_fp) == EOF)
         goto jerr;
   }

   if(tlp->tl_vi_flags & a_TTY_VF_REFRESH){
      /* kht may want to restore a cursor position after inserting some
       * data somewhere */
      if(tlp->tl_defc_cursor_byte > 0){
         uz i, j;
         sz k;

         a_tty_khome(tlp, FAL0);

         i = tlp->tl_defc_cursor_byte;
         tlp->tl_defc_cursor_byte = 0;
         for(j = 0; tlp->tl_cursor < tlp->tl_count; ++j){
            a_tty_kright(tlp);
            if((k = tlp->tl_line.cells[j].tc_count) > i)
               break;
            i -= k;
         }
      }

      if(!a_tty_vi__paint(tlp))
         goto jerr;
   }

   if(tlp->tl_vi_flags & a_TTY_VF_SYNC){
      tlp->tl_vi_flags &= ~a_TTY_VF_SYNC;
      if(fflush(mx_tty_fp))
         goto jerr;
   }

   rv = TRU1;
jleave:
   tlp->tl_vi_flags &= ~a_TTY_VF_ALL_MASK;
   NYD2_OU;
   return rv;

jerr:
   clearerr(mx_tty_fp); /* xxx I/O layer rewrite */
   n_err(_("Visual refresh failed!  Is $TERM set correctly?\n"
      "  Setting *line-editor-disable* to get us through!\n"));
   ok_bset(line_editor_disable);
   rv = FAL0;
   goto jleave;
}

static boole
a_tty_vi__paint(struct a_tty_line *tlp){
   enum{
      a_TRUE_RV = a_TTY__VF_LAST<<1,         /* Return value bit */
      a_HAVE_PROMPT = a_TTY__VF_LAST<<2,     /* Have a prompt */
      a_SHOW_PROMPT = a_TTY__VF_LAST<<3,     /* Shall print the prompt */
      a_MOVE_CURSOR = a_TTY__VF_LAST<<4,     /* Move visual cursor for user! */
      a_LEFT_MIN = a_TTY__VF_LAST<<5,        /* On left boundary */
      a_RIGHT_MAX = a_TTY__VF_LAST<<6,
      a_HAVE_POSITION = a_TTY__VF_LAST<<7,   /* Print the position indicator */

      /* We carry some flags over invocations (not worth a specific field) */
      a_VISIBLE_PROMPT = a_TTY__VF_LAST<<8,  /* The prompt is on the screen */
      a_PERSIST_MASK = a_VISIBLE_PROMPT,
      a__LAST = a_PERSIST_MASK
   };

   u32 f, w, phy_wid_base, phy_wid, phy_base, phy_cur, cnt,
      DBG(lstcur su_COMMA) cur,
      vi_left, /*vi_right,*/ phy_nxtcur;
   struct a_tty_cell const *tccp, *tcp_left, *tcp_right, *tcxp;
   NYD2_IN;
   LCTA(UCMP(64, a__LAST, <, U32_MAX), "Flag bits excess storage type");

   f = tlp->tl_vi_flags;
   tlp->tl_vi_flags = (f & ~(a_TTY_VF_REFRESH | a_PERSIST_MASK)) |
         a_TTY_VF_SYNC;
   f |= a_TRUE_RV;
   if((w = tlp->tl_prompt_width) > 0)
      f |= a_HAVE_PROMPT;
   f |= a_HAVE_POSITION;

   /* XXX We do not have a OnTerminalResize event (see termios) yet, so we need
    * XXX to reevaluate our circumstances over and over again */
   /* Do not display prompt or position indicator on very small screens */
   if((phy_wid_base = mx_termios_dimen.tiosd_width) <= a_TTY_WIDTH_RIPOFF)
      f &= ~(a_HAVE_PROMPT | a_HAVE_POSITION);
   else{
      phy_wid_base -= a_TTY_WIDTH_RIPOFF;

      /* Disable the prompt if the screen is too small; due to lack of some
       * indicator simply add a second ripoff */
      if((f & a_HAVE_PROMPT) && w + a_TTY_WIDTH_RIPOFF >= phy_wid_base)
         f &= ~a_HAVE_PROMPT;
   }

   phy_wid = phy_wid_base;
   phy_base = 0;
   phy_cur = tlp->tl_phy_cursor;
   cnt = tlp->tl_count;
   su_DBG( lstcur = tlp->tl_lst_cursor; )

   /* XXX Assume dirty screen if shrunk */
   if(cnt < tlp->tl_lst_count)
      f |= a_TTY_VF_MOD_DIRTY;

   /* TODO Without mx_HAVE_TERMCAP, it would likely be much cheaper to simply
    * TODO always "cr + paint + ce + ch", since ce is simulated via spaces.. */

   /* Quickshot: if the line is empty, possibly print prompt and out */
   if(cnt == 0){
      /* In that special case dirty anything if it seems better */
      if((f & a_TTY_VF_MOD_CONTENT) || tlp->tl_lst_count > 0)
         f |= a_TTY_VF_MOD_DIRTY;

      if((f & a_TTY_VF_MOD_DIRTY) && phy_cur != 0){
         if(!mx_termcap_cmdx(mx_TERMCAP_CMD_cr))
            goto jerr;
         phy_cur = 0;
      }

      if((f & (a_TTY_VF_MOD_DIRTY | a_HAVE_PROMPT)) ==
            (a_TTY_VF_MOD_DIRTY | a_HAVE_PROMPT)){
         if(fputs(tlp->tl_prompt, mx_tty_fp) == EOF)
            goto jerr;
         phy_cur = tlp->tl_prompt_width + 1;
      }

      /* May need to clear former line content */
      if((f & a_TTY_VF_MOD_DIRTY) &&
            !mx_termcap_cmd(mx_TERMCAP_CMD_ce, phy_cur, -1))
         goto jerr;

      tlp->tl_phy_start = tlp->tl_line.cells;
      goto jleave;
   }

   /* Try to get an idea of the visual window */

   /* Find the left visual boundary */
   phy_wid = (phy_wid >> 1) + (phy_wid >> 2);
   if((cur = tlp->tl_cursor) == cnt)
      --cur;

   w = (tcp_left = tccp = tlp->tl_line.cells + cur)->tc_width;
   if(w == U8_MAX) /* TODO yet HT == SPACE */
      w = 1;
   while(tcp_left > tlp->tl_line.cells){
      u16 cw = tcp_left[-1].tc_width;

      if(cw == U8_MAX) /* TODO yet HT == SPACE */
         cw = 1;
      if(w + cw >= phy_wid)
         break;
      w += cw;
      --tcp_left;
   }
   vi_left = w;

   /* If the left hand side of our visual viewpoint consumes less than half
    * of the screen width, show the prompt */
   if(tcp_left == tlp->tl_line.cells)
      f |= a_LEFT_MIN;

   if((f & (a_LEFT_MIN | a_HAVE_PROMPT)) == (a_LEFT_MIN | a_HAVE_PROMPT) &&
         w + tlp->tl_prompt_width < phy_wid){
      phy_base = tlp->tl_prompt_width;
      f |= a_SHOW_PROMPT;
   }

   /* Then search for right boundary.  Dependent upon n_PSO_FULLWIDTH (termcap
    * am/xn) We leave the rightmost column empty because some terminals
    * [cw]ould wrap the line if we write into that, or not.
    * TODO We do not deal with !mx_TERMCAP_QUERY_sam */
   phy_wid = phy_wid_base - phy_base;
   tcp_right = tlp->tl_line.cells + cnt;

   while(&tccp[1] < tcp_right){
      u16 cw = tccp[1].tc_width;
      u32 i;

      if(cw == U8_MAX) /* TODO yet HT == SPACE */
         cw = 1;
      i = w + cw;
      if(i > phy_wid)
         break;
      w = i;
      ++tccp;
   }
   /*vi_right = w - vi_left;*/

   /* If the complete line including prompt fits on the screen, show prompt */
   if(--tcp_right == tccp){
      f |= a_RIGHT_MAX;

      /* Since we did brute-force walk also for the left boundary we may end up
       * in a situation were anything effectively fits on the screen, including
       * the prompt that is, but where we don't recognize this since we
       * restricted the search to fit in some visual viewpoint.  Therefore try
       * again to extend the left boundary to overcome that */
      if(!(f & a_LEFT_MIN)){
         struct a_tty_cell const *tc1p = tlp->tl_line.cells;
         u32 vil1 = vi_left;

         ASSERT(!(f & a_SHOW_PROMPT));
         w += tlp->tl_prompt_width;
         for(tcxp = tcp_left;;){
            u32 i = tcxp[-1].tc_width;

            if(i == U8_MAX) /* TODO yet HT == SPACE */
               i = 1;
            vil1 += i;
            i += w;
            if(i > phy_wid)
               break;
            w = i;
            if(--tcxp == tc1p){
               tcp_left = tc1p;
               /*vi_left = vil1;*/
               f |= a_LEFT_MIN;
               break;
            }
         }
         /*w -= tlp->tl_prompt_width;*/
      }
   }
   tcp_right = tccp;
   tccp = tlp->tl_line.cells + cur;

   if((f & (a_LEFT_MIN | a_RIGHT_MAX | a_HAVE_PROMPT | a_SHOW_PROMPT)) ==
            (a_LEFT_MIN | a_RIGHT_MAX | a_HAVE_PROMPT) &&
         w + tlp->tl_prompt_width <= phy_wid){
      phy_wid -= (phy_base = tlp->tl_prompt_width);
      f |= a_SHOW_PROMPT;
   }

   /* Try to avoid repainting the complete line - this is possible if the
    * cursor "did not leave the screen" and the prompt status hasn't changed.
    * I.e., after clamping virtual viewpoint, compare relation to physical */
   if((f & (a_TTY_VF_MOD_SINGLE/*FIXME*/ |
            a_TTY_VF_MOD_CONTENT/* xxx */ | a_TTY_VF_MOD_DIRTY)) ||
         (tcxp = tlp->tl_phy_start) == NULL ||
         tcxp > tccp || tcxp <= tcp_right)
         f |= a_TTY_VF_MOD_DIRTY;
   else{
         f |= a_TTY_VF_MOD_DIRTY;
#if 0
      struct a_tty_cell const *tcyp;
      s32 cur_displace;
      u32 phy_lmargin, phy_rmargin, fx, phy_displace;

      phy_lmargin = (fx = phy_wid) / 100;
      phy_rmargin = fx - (phy_lmargin * a_TTY_SCROLL_MARGIN_RIGHT);
      phy_lmargin *= a_TTY_SCROLL_MARGIN_LEFT;
      fx = (f & (a_SHOW_PROMPT | a_VISIBLE_PROMPT));

      if(fx == 0 || fx == (a_SHOW_PROMPT | a_VISIBLE_PROMPT)){
      }
#endif
   }
   goto jpaint;

   /* We know what we have to paint, start synchronizing */
jpaint:
   ASSERT(phy_cur == tlp->tl_phy_cursor);
   ASSERT(phy_wid == phy_wid_base - phy_base);
   ASSERT(cnt == tlp->tl_count);
   ASSERT(cnt > 0);
   ASSERT(lstcur == tlp->tl_lst_cursor);
   ASSERT(tccp == tlp->tl_line.cells + cur);

   phy_nxtcur = phy_base; /* FIXME only if repaint cpl. */

   /* Quickshot: is it only cursor movement within the visible screen? */
   if((f & a_TTY_VF_REFRESH) == a_TTY_VF_MOD_CURSOR){
      f |= a_MOVE_CURSOR;
      goto jcursor;
   }

   /* To be able to apply some quick jump offs, clear line if possible */
   if(f & a_TTY_VF_MOD_DIRTY){
      /* Force complete clearance and cursor reinitialization */
      if(!mx_termcap_cmdx(mx_TERMCAP_CMD_cr) ||
            !mx_termcap_cmd(mx_TERMCAP_CMD_ce, 0, -1))
         goto jerr;
      tlp->tl_phy_start = tcp_left;
      phy_cur = 0;
   }

   if((f & (a_TTY_VF_MOD_DIRTY | a_SHOW_PROMPT)) && phy_cur != 0){
      if(!mx_termcap_cmdx(mx_TERMCAP_CMD_cr))
         goto jerr;
      phy_cur = 0;
   }

   if(f & a_SHOW_PROMPT){
      ASSERT(phy_base == tlp->tl_prompt_width);
      if(fputs(tlp->tl_prompt, mx_tty_fp) == EOF)
         goto jerr;
      phy_cur = phy_nxtcur;
      f |= a_VISIBLE_PROMPT;
   }else
      f &= ~a_VISIBLE_PROMPT;

/* FIXME reposition cursor for paint */
   for(w = phy_nxtcur; tcp_left <= tcp_right; ++tcp_left){
      u16 cw;

      cw = tcp_left->tc_width;

      if(LIKELY(!tcp_left->tc_novis)){
         if(fwrite(tcp_left->tc_cbuf, sizeof *tcp_left->tc_cbuf,
               tcp_left->tc_count, mx_tty_fp) != tcp_left->tc_count)
            goto jerr;
      }else{ /* XXX Shouldn't be here <-> CText, ui_str.c */
         char wbuf[8]; /* XXX magic */

         if(n_psonce & n_PSO_UNICODE){
            u32 wc;

            wc = (u32)tcp_left->tc_wc;
            if((wc & ~0x1Fu) == 0)
               wc |= 0x2400;
            else if(wc == 0x7F)
               wc = 0x2421;
            else
               wc = 0x2426;
            su_utf32_to_8(wc, wbuf);
         }else
            wbuf[0] = '?', wbuf[1] = '\0';

         if(fputs(wbuf, mx_tty_fp) == EOF)
            goto jerr;
         cw = 1;
      }

      if(cw == U8_MAX) /* TODO yet HT == SPACE */
         cw = 1;
      w += cw;
      if(tcp_left == tccp)
         phy_nxtcur = w;
      phy_cur += cw;
   }

   /* Write something position marker alike if it does not fit on screen */
   if((f & a_HAVE_POSITION) &&
         ((f & (a_LEFT_MIN | a_RIGHT_MAX)) != (a_LEFT_MIN | a_RIGHT_MAX) /*||
          ((f & a_HAVE_PROMPT) && !(f & a_SHOW_PROMPT))*/)){
# ifdef mx_HAVE_COLOUR
      char *posbuf = tlp->tl_pos_buf, *pos = tlp->tl_pos;
# else
      char posbuf[5], *pos = posbuf;

      pos[4] = '\0';
# endif

      if(phy_cur != (w = phy_wid_base) &&
            !mx_termcap_cmd(mx_TERMCAP_CMD_ch, phy_cur = w, 0))
         goto jerr;

      *pos++ = '|';
      if(f & a_LEFT_MIN)
         su_mem_copy(pos, "^.+", 3);
      else if(f & a_RIGHT_MAX)
         su_mem_copy(pos, ".+$", 3);
      else{
         /* Theoretical line length limit a_TTY_LINE_MAX, choose next power of
          * ten (10 ** 10) to represent 100 percent; we do not have a macro
          * that generates a constant, and i do not trust the standard "u type
          * suffix automatically scales": calculate the large number */
         static char const itoa[] = "0123456789";

         u64 const fact100 = (u64)0x3B9ACA00u * 10u,
               fact = fact100 / 100;
         u32 i = (u32)(((fact100 / cnt) * tlp->tl_cursor) / fact);
         LCTA(a_TTY_LINE_MAX <= S32_MAX, "a_TTY_LINE_MAX too large");

         if(i < 10)
            pos[0] = ' ', pos[1] = itoa[i];
         else
            pos[1] = itoa[i % 10], pos[0] = itoa[i / 10];
         pos[2] = '%';
      }

      if(fputs(posbuf, mx_tty_fp) == EOF)
         goto jerr;
      phy_cur += 4;
   }

   /* Users are used to see the cursor right of the point of interest, so we
    * need some further adjustments unless in special conditions.  Be aware
    * that we may have adjusted cur at the beginning, too */
   if((cur = tlp->tl_cursor) == 0)
      phy_nxtcur = phy_base;
   else if(cur != cnt){
      u16 cw = tccp->tc_width;

      if(cw == U8_MAX) /* TODO yet HT == SPACE */
         cw = 1;
      phy_nxtcur -= cw;
   }

jcursor:
   if(((f & a_MOVE_CURSOR) || phy_nxtcur != phy_cur) &&
         !mx_termcap_cmd(mx_TERMCAP_CMD_ch, phy_cur = phy_nxtcur, 0))
      goto jerr;

jleave:
   tlp->tl_vi_flags |= (f & a_PERSIST_MASK);
   tlp->tl_lst_count = tlp->tl_count;
   tlp->tl_lst_cursor = tlp->tl_cursor;
   tlp->tl_phy_cursor = phy_cur;

   NYD2_OU;
   return ((f & a_TRUE_RV) != 0);
jerr:
   f &= ~a_TRUE_RV;
   goto jleave;
}

static s32
a_tty_wboundary(struct a_tty_line *tlp, s32 dir){/* TODO shell token-wise */
   boole anynon;
   struct a_tty_cell *tcap;
   u32 cur, cnt;
   s32 rv;
   NYD2_IN;

   ASSERT(dir == 1 || dir == -1);

   rv = -1;
   cnt = tlp->tl_count;
   cur = tlp->tl_cursor;

   if(dir < 0){
      if(cur == 0)
         goto jleave;
   }else if(cur + 1 >= cnt)
      goto jleave;
   else
      --cnt, --cur; /* xxx Unsigned wrapping may occur (twice), then */

   for(rv = 0, tcap = tlp->tl_line.cells, anynon = FAL0;;){
      wchar_t wc;

      wc = tcap[cur += (u32)dir].tc_wc;
      if(/*TODO not everywhere iswblank(wc)*/ wc == L' ' || wc == L'\t' ||
            iswpunct(wc)){
         if(anynon)
            break;
      }else
         anynon = TRU1;

      ++rv;

      if(dir < 0){
         if(cur == 0)
            break;
      }else if(cur + 1 >= cnt){
         ++rv;
         break;
      }
   }
jleave:
   NYD2_OU;
   return rv;
}

static void
a_tty_khome(struct a_tty_line *tlp, boole dobell){
   u32 f;
   NYD2_IN;

   if(LIKELY(tlp->tl_cursor > 0)){
      tlp->tl_cursor = 0;
      f = a_TTY_VF_MOD_CURSOR;
   }else if(dobell)
      f = a_TTY_VF_BELL;
   else
      f = a_TTY_VF_NONE;

   tlp->tl_vi_flags |= f;
   NYD2_OU;
}

static void
a_tty_kend(struct a_tty_line *tlp){
   u32 f;
   NYD2_IN;

   if(LIKELY(tlp->tl_cursor < tlp->tl_count)){
      tlp->tl_cursor = tlp->tl_count;
      f = a_TTY_VF_MOD_CURSOR;
   }else
      f = a_TTY_VF_BELL;

   tlp->tl_vi_flags |= f;
   NYD2_OU;
}

static void
a_tty_kbs(struct a_tty_line *tlp){
   u32 f, cur, cnt;
   NYD2_IN;

   cur = tlp->tl_cursor;
   cnt = tlp->tl_count;

   if(LIKELY(cur > 0)){
      tlp->tl_cursor = --cur;
      tlp->tl_count = --cnt;

      if((cnt -= cur) > 0){
         struct a_tty_cell *tcap;

         tcap = tlp->tl_line.cells + cur;
         su_mem_move(tcap, &tcap[1], cnt *= sizeof(*tcap));
      }
      f = a_TTY_VF_MOD_CURSOR | a_TTY_VF_MOD_CONTENT;
   }else
      f = a_TTY_VF_BELL;

   tlp->tl_vi_flags |= f;
   NYD2_OU;
}

static void
a_tty_ksnarf(struct a_tty_line *tlp, boole cplline, boole dobell){
   u32 i, f;
   NYD2_IN;

   f = a_TTY_VF_NONE;
   i = tlp->tl_cursor;

   if(cplline && i > 0){
      tlp->tl_cursor = i = 0;
      f = a_TTY_VF_MOD_CURSOR;
   }

   if(LIKELY(i < tlp->tl_count)){
      struct a_tty_cell *tcap;

      tcap = &tlp->tl_line.cells[0];
      a_tty_copy2paste(tlp, &tcap[i], &tcap[tlp->tl_count]);
      tlp->tl_count = i;
      f = a_TTY_VF_MOD_CONTENT;
   }else if(dobell)
      f |= a_TTY_VF_BELL;

   tlp->tl_vi_flags |= f;
   NYD2_OU;
}

static s32
a_tty_kdel(struct a_tty_line *tlp){
   u32 cur, cnt, f;
   s32 i;
   NYD2_IN;

   cur = tlp->tl_cursor;
   cnt = tlp->tl_count;
   i = (s32)(cnt - cur);

   if(LIKELY(i > 0)){
      tlp->tl_count = --cnt;

      if(LIKELY(--i > 0)){
         struct a_tty_cell *tcap;

         tcap = &tlp->tl_line.cells[cur];
         su_mem_move(tcap, &tcap[1], (u32)i * sizeof(*tcap));
      }
      f = a_TTY_VF_MOD_CONTENT;
   }else if(cnt == 0 && !ok_blook(ignoreeof)){
      putc('^', mx_tty_fp);
      putc('D', mx_tty_fp);
      i = -1;
      f = a_TTY_VF_NONE;
   }else{
      i = 0;
      f = a_TTY_VF_BELL;
   }

   tlp->tl_vi_flags |= f;
   NYD2_OU;
   return i;
}

static void
a_tty_kleft(struct a_tty_line *tlp){
   u32 f;
   NYD2_IN;

   if(LIKELY(tlp->tl_cursor > 0)){
      --tlp->tl_cursor;
      f = a_TTY_VF_MOD_CURSOR;
   }else
      f = a_TTY_VF_BELL;

   tlp->tl_vi_flags |= f;
   NYD2_OU;
}

static void
a_tty_kright(struct a_tty_line *tlp){
   u32 i;
   NYD2_IN;

   if(LIKELY((i = tlp->tl_cursor + 1) <= tlp->tl_count)){
      tlp->tl_cursor = i;
      i = a_TTY_VF_MOD_CURSOR;
   }else
      i = a_TTY_VF_BELL;

   tlp->tl_vi_flags |= i;
   NYD2_OU;
}

static void
a_tty_ksnarfw(struct a_tty_line *tlp, boole fwd){
   struct a_tty_cell *tcap;
   u32 cnt, cur, f;
   s32 i;
   NYD2_IN;

   if(UNLIKELY((i = a_tty_wboundary(tlp, (fwd ? +1 : -1))) <= 0)){
      f = (i < 0) ? a_TTY_VF_BELL : a_TTY_VF_NONE;
      goto jleave;
   }

   cnt = tlp->tl_count - (u32)i;
   cur = tlp->tl_cursor;
   if(!fwd)
      cur -= (u32)i;
   tcap = &tlp->tl_line.cells[cur];

   a_tty_copy2paste(tlp, &tcap[0], &tcap[i]);

   if((tlp->tl_count = cnt) != (tlp->tl_cursor = cur)){
      cnt -= cur;
      su_mem_move(&tcap[0], &tcap[i], cnt * sizeof(*tcap)); /* FIXME*/
   }

   f = a_TTY_VF_MOD_CURSOR | a_TTY_VF_MOD_CONTENT;
jleave:
   tlp->tl_vi_flags |= f;
   NYD2_OU;
}

static void
a_tty_kgow(struct a_tty_line *tlp, s32 dir){
   u32 f;
   s32 i;
   NYD2_IN;

   if(UNLIKELY((i = a_tty_wboundary(tlp, dir)) <= 0))
      f = (i < 0) ? a_TTY_VF_BELL : a_TTY_VF_NONE;
   else{
      if(dir < 0)
         i = -i;
      tlp->tl_cursor += (u32)i;
      f = a_TTY_VF_MOD_CURSOR;
   }

   tlp->tl_vi_flags |= f;
   NYD2_OU;
}

static void
a_tty_kgoscr(struct a_tty_line *tlp, s32 dir){
   u32 sw, i, cur, f, cnt;
   NYD2_IN;

   if((sw = mx_termios_dimen.tiosd_width) > 2)
      sw -= 2;
   if(sw > (i = tlp->tl_prompt_width))
      sw -= i;
   cur = tlp->tl_cursor;
   f = a_TTY_VF_BELL;

   if(dir > 0){
      for(cnt = tlp->tl_count; cur < cnt && sw > 0; ++cur){
         i = tlp->tl_line.cells[cur].tc_width;
         i = MIN(sw, i);
         sw -= i;
      }
   }else{
       while(cur > 0 && sw > 0){
         i = tlp->tl_line.cells[--cur].tc_width;
         i = MIN(sw, i);
         sw -= i;
      }
   }
   if(cur != tlp->tl_cursor){
      tlp->tl_cursor = cur;
      f = a_TTY_VF_MOD_CURSOR;
   }

   tlp->tl_vi_flags |= f;
   NYD2_OU;
}

static boole
a_tty_kother(struct a_tty_line *tlp, wchar_t wc){
   /* Append if at EOL, insert otherwise;
    * since we may move around character-wise, always use a fresh ps */
   mbstate_t ps;
   struct a_tty_cell tc, *tcap;
   u32 f, cur, cnt;
   boole rv;
   NYD2_IN;

   rv = FAL0;
   f = a_TTY_VF_NONE;

   LCTA(a_TTY_LINE_MAX <= S32_MAX, "a_TTY_LINE_MAX too large");
   if(tlp->tl_count + 1 >= a_TTY_LINE_MAX){
      n_err(_("Stop here, we can't extend line beyond size limit\n"));
      goto jleave;
   }

   /* First init a cell and see whether we'll really handle this wc */
   su_mem_set(&ps, 0, sizeof ps);
   /* C99 */{
      uz l;

      l = wcrtomb(tc.tc_cbuf, tc.tc_wc = wc, &ps);
      if(UNLIKELY(l > MB_LEN_MAX)){
jemb:
         n_err(_("wcrtomb(3) error: too many multibyte character bytes\n"));
         goto jleave;
      }
      tc.tc_count = (u16)l;

      if(UNLIKELY((n_psonce & n_PSO_ENC_MBSTATE) != 0)){
         l = wcrtomb(&tc.tc_cbuf[l], L'\0', &ps);
         if(LIKELY(l == 1))
            /* Only NUL terminator */;
         else if(LIKELY(--l < MB_LEN_MAX))
            tc.tc_count += (u16)l;
         else
            goto jemb;
      }
   }

   /* Yes, we will!  Place it in the array */
   tc.tc_novis = (iswprint(wc) == 0);
   tc.tc_width = a_tty_wcwidth(wc);
   /* TODO if(tc.tc_novis && tc.tc_width > 0) */

   cur = tlp->tl_cursor++;
   cnt = tlp->tl_count++ - cur;
   tcap = &tlp->tl_line.cells[cur];
   if(cnt >= 1){
      su_mem_move(&tcap[1], tcap, cnt * sizeof(*tcap));
      f = a_TTY_VF_MOD_CONTENT;
   }else
      f = a_TTY_VF_MOD_SINGLE;
   su_mem_copy(tcap, &tc, sizeof *tcap);

   f |= a_TTY_VF_MOD_CURSOR;
   rv = TRU1;
jleave:
   if(!rv)
      f |= a_TTY_VF_BELL;
   tlp->tl_vi_flags |= f;
   NYD2_OU;
   return rv;
}

static u32
a_tty_kht(struct a_tty_line *tlp){
   struct su_mem_bag *membag, *membag_persist, membag__buf[1];
   struct stat sb;
   struct str orig, bot, topp, sub, exp, preexp;
   struct n_string shou, *shoup;
   struct a_tty_cell *ctop, *cx;
   boole wedid, set_savec;
   u32 rv, f;
   NYD2_IN;

   /* Get plain line data; if this is the first expansion/xy, update the
    * very original content so that ^G gets the origin back */
   orig = tlp->tl_savec;
   a_tty_cell2save(tlp);
   exp = tlp->tl_savec;
   if(orig.s != NULL){
      /*tlp->tl_savec = orig;*/
      set_savec = FAL0;
   }else
      set_savec = TRU1;
   orig = exp;

   membag = su_mem_bag_create(&membag__buf[0], 0);
   membag_persist = su_mem_bag_top(n_go_data->gdc_membag);
   su_mem_bag_push(n_go_data->gdc_membag, membag);

   shoup = n_string_creat_auto(&shou);
   f = a_TTY_VF_NONE;

   /* C99 */{
      uz max;
      struct a_tty_cell *cword;

      /* Find the word to be expanded */
      cword = tlp->tl_line.cells;
      ctop = &cword[tlp->tl_cursor];
      cx = &cword[tlp->tl_count];

      /* topp: separate data right of cursor */
      if(cx > ctop){
         for(rv = 0; ctop < cx; ++ctop)
            rv += ctop->tc_count;
         topp.l = rv;
         topp.s = orig.s + orig.l - rv;
         ctop = cword + tlp->tl_cursor;
      }else
         topp.s = NULL, topp.l = 0;

      /* Find the shell token that corresponds to the cursor position */
      max = 0;
      if(ctop > cword){
         for(; cword < ctop; ++cword)
            max += cword->tc_count;
      }
      bot = sub = orig;
      bot.l = 0;
      sub.l = max;

      if(max > 0){
         for(;;){
            enum n_shexp_state shs;

            exp = sub;
            shs = n_shexp_parse_token((n_SHEXP_PARSE_DRYRUN |
                  n_SHEXP_PARSE_TRIM_SPACE | n_SHEXP_PARSE_IGNORE_EMPTY |
                  n_SHEXP_PARSE_QUOTE_AUTO_CLOSE), NULL, &sub, NULL);
            if(sub.l != 0){
               uz x;

               ASSERT(max >= sub.l);
               x = max - sub.l;
               bot.l += x;
               max -= x;
               continue;
            }
            if(shs & n_SHEXP_STATE_ERR_MASK){
               n_err(_("Invalid completion pattern: %.*s\n"),
                  (int)exp.l, exp.s);
               f |= a_TTY_VF_BELL;
               goto jnope;
            }

            /* All WS?  Trailing WS that has been "jumped over"? */
            if(exp.l == 0 || (shs & n_SHEXP_STATE_WS_TRAIL))
               break;

            n_shexp_parse_token((n_SHEXP_PARSE_TRIM_SPACE |
                  n_SHEXP_PARSE_IGNORE_EMPTY | n_SHEXP_PARSE_QUOTE_AUTO_CLOSE),
                  shoup, &exp, NULL);
            break;
         }

         sub.s = n_string_cp(shoup);
         sub.l = shoup->s_len;
      }
   }

   /* Leave room for "implicit asterisk" expansion, as below */
   if(sub.l == 0){
      sub.s = n_UNCONST(n_star);
      sub.l = sizeof(n_star) -1;
   }

   preexp.s = n_UNCONST(n_empty);
   preexp.l = sizeof(n_empty) -1;
   wedid = FAL0;
jredo:
   /* TODO Super-Heavy-Metal: block all sigs, avoid leaks on jump */
   mx_sigs_all_holdx();
   exp.s = fexpand(sub.s, a_TTY_TAB_FEXP_FL);
   mx_sigs_all_rele();

   if(exp.s == NULL || (exp.l = su_cs_len(exp.s)) == 0){
      if(wedid < FAL0)
         goto jnope;

      /* No.  But maybe the users' desire was to complete only a part of the
       * shell token of interest!  TODO This can be improved, we would need to
       * TODO have shexp_parse to create a DOM structure of parsed snippets, so
       * TODO that we can tell for each snippet which quote is active and
       * TODO whether we may cross its boundary and/or apply expansion for it!
       * TODO Only like that we would be able to properly requote user input
       * TODO like "'['a-z]" to e.g. "\[a-z]" for glob purposes!
       * TODO Then, honour *line-editor-word-breaks* from ground up! */
      if(wedid == TRU1){
         uz i, li;
         char const *word_breaks;

         wedid = TRUM1;

         word_breaks = ok_vlook(line_editor_cpl_word_breaks);
         li = UZ_MAX;
         i = sub.l;

         while(i-- != 0){
            char c;

            c = sub.s[i];
            if(su_cs_is_space(c))
               break;
            else if((i != sub.l - 1 || c != '*') &&
                  su_cs_find_c(word_breaks, c) != NIL){
               li = i + 1;
               break;
            }
         }
         if(li == UZ_MAX)
            goto jnope;

         preexp = sub;
         preexp.l = li;
         sub.l -= li;
         sub.s += li;
         goto jredo;
      }

      /* A different case is that the user input includes for example character
       * classes: here fexpand() will go over glob, and that will not find any
       * match, thus returning NULL; try to wildcard expand this pattern! */
jaster_check:
      if(sub.s[sub.l - 1] != '*'){
         wedid = TRU1;
         shoup = n_string_push_c(shoup, '*');
         sub.s = n_string_cp(shoup);
         sub.l = shoup->s_len;
         goto jredo;
      }
      goto jnope;
   }

   if(wedid == TRUM1 && preexp.l > 0)
      preexp.s = savestrbuf(preexp.s, preexp.l);

   /* May be multi-return! */
   if(n_pstate & n_PS_EXPAND_MULTIRESULT)
      goto jmulti;

   /* xxx That not really true since the limit counts characters not bytes */
   LCTA(a_TTY_LINE_MAX <= S32_MAX, "a_TTY_LINE_MAX too large");
   if(exp.l >= a_TTY_LINE_MAX - 1 || a_TTY_LINE_MAX - 1 - exp.l < preexp.l){
      n_err(_("Tabulator expansion would extend beyond line size limit\n"));
      f |= a_TTY_VF_BELL;
      goto jnope;
   }

   /* If the expansion equals the original string, assume the user wants what
    * is usually known as tab completion, append `*' and restart */
   if(!wedid && exp.l == sub.l && !su_mem_cmp(exp.s, sub.s, exp.l))
      goto jaster_check;

   if(exp.s[exp.l - 1] != '/'){
      if(!stat(exp.s, &sb) && S_ISDIR(sb.st_mode)){
         shoup = n_string_assign_buf(shoup, exp.s, exp.l);
         shoup = n_string_push_c(shoup, '/');
         exp.s = n_string_cp(shoup);
         goto jset;
      }
   }
   exp.s[exp.l] = '\0';

jset:
   exp.l = su_cs_len(exp.s = n_shexp_quote_cp(exp.s, tlp->tl_quote_rndtrip));
   tlp->tl_defc_cursor_byte = bot.l + preexp.l + exp.l -1;

   orig.l = bot.l + preexp.l + exp.l + topp.l;
   su_mem_bag_push(n_go_data->gdc_membag, membag_persist);
   orig.s = su_MEM_BAG_SELF_AUTO_ALLOC(orig.l + 5 +1);
   su_mem_bag_pop(n_go_data->gdc_membag, membag_persist);
   if((rv = (u32)bot.l) > 0)
      su_mem_copy(orig.s, bot.s, rv);
   if(preexp.l > 0){
      su_mem_copy(&orig.s[rv], preexp.s, preexp.l);
      rv += preexp.l;
   }
   su_mem_copy(&orig.s[rv], exp.s, exp.l);
   rv += exp.l;
   if(topp.l > 0){
      su_mem_copy(&orig.s[rv], topp.s, topp.l);
      rv += topp.l;
   }
   orig.s[rv] = '\0';

   tlp->tl_defc = orig;
   tlp->tl_count = tlp->tl_cursor = 0;
   f |= a_TTY_VF_MOD_DIRTY;
jleave:
   su_mem_bag_pop(n_go_data->gdc_membag, membag);
   su_mem_bag_gut(membag);
   tlp->tl_vi_flags |= f;
   NYD2_OU;
   return rv;

jmulti:{
      struct n_visual_info_ctx vic;
      struct str input;
      wc_t c2, c1;
      boole isfirst;
      char const *lococp;
      uz locolen, scrwid, lnlen, lncnt, prefixlen;
      FILE *fp;

      if((fp = mx_fs_tmp_open("mlecpl", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
               mx_FS_O_REGISTER), NIL)) == NIL){
         n_perr(_("tmpfile"), 0);
         fp = mx_tty_fp;
      }

      /* How long is the result string for real?  Search the NUL NUL
       * terminator.  While here, detect the longest entry to perform an
       * initial allocation of our accumulator string */
      locolen = preexp.l;
      do{
         uz i;

         i = su_cs_len(&exp.s[++exp.l]);
         locolen = MAX(locolen, i);
         exp.l += i;
      }while(exp.s[exp.l + 1] != '\0');

      shoup = n_string_reserve(n_string_trunc(shoup, 0),
            locolen + (locolen >> 1));

      /* Iterate (once again) over all results */
      scrwid = mx_TERMIOS_WIDTH_OF_LISTS();
      lnlen = lncnt = 0;
      UNINIT(prefixlen, 0);
      UNINIT(lococp, NULL);
      UNINIT(c1, '\0');
      for(isfirst = TRU1; exp.l > 0; isfirst = FAL0, c1 = c2){
         uz i;
         char const *fullpath;

         /* Next result */
         sub = exp;
         sub.l = i = su_cs_len(sub.s);
         ASSERT(exp.l >= i);
         if((exp.l -= i) > 0)
            --exp.l;
         exp.s += ++i;

         /* Separate dirname and basename */
         fullpath = sub.s;
         if(isfirst){
            char const *cp;

            if((cp = su_cs_rfind_c(fullpath, '/')) != NULL)
               prefixlen = P2UZ(++cp - fullpath);
            else
               prefixlen = 0;
         }
         if(prefixlen > 0 && prefixlen < sub.l){
            sub.l -= prefixlen;
            sub.s += prefixlen;
         }

         /* We want case-insensitive sort-order */
         su_mem_set(&vic, 0, sizeof vic);
         vic.vic_indat = sub.s;
         vic.vic_inlen = sub.l;
         c2 = n_visual_info(&vic, n_VISUAL_INFO_ONE_CHAR) ? vic.vic_waccu
               : (u8)*sub.s;
#ifdef mx_HAVE_C90AMEND1
         c2 = towlower(c2);
#else
         c2 = su_cs_to_lower(c2);
#endif

         /* Query longest common prefix along the way */
         if(isfirst){
            c1 = c2;
            lococp = sub.s;
            locolen = sub.l;
         }else if(locolen > 0){
            for(i = 0; i < locolen; ++i)
               if(lococp[i] != sub.s[i]){
                  i = field_detect_clip(i, lococp, i);
                  locolen = i;
                  break;
               }
         }

         /* Prepare display */
         input = sub;
         shoup = n_shexp_quote(n_string_trunc(shoup, 0), &input,
               tlp->tl_quote_rndtrip);
         su_mem_set(&vic, 0, sizeof vic);
         vic.vic_indat = shoup->s_dat;
         vic.vic_inlen = shoup->s_len;
         if(!n_visual_info(&vic,
               n_VISUAL_INFO_SKIP_ERRORS | n_VISUAL_INFO_WIDTH_QUERY))
            vic.vic_vi_width = shoup->s_len;

         /* Put on screen.  Indent follow lines of same sort slot.
          * Leave enough room for filename tagging */
         if((c1 = (c1 != c2))){
#ifdef mx_HAVE_C90AMEND1
            c1 = (iswalnum(c2) != 0);
#else
            c1 = (su_cs_is_alnum(c2) != 0);
#endif
         }
         if(isfirst || c1 ||
               scrwid < lnlen || scrwid - lnlen <= vic.vic_vi_width + 2){
            putc('\n', fp);
            if(scrwid < lnlen)
               ++lncnt;
            ++lncnt, lnlen = 0;
            if(!isfirst && !c1)
               goto jsep;
         }else if(lnlen > 0){
jsep:
            fputs("  ", fp);
            lnlen += 2;
         }
         fputs(n_string_cp(shoup), fp);
         lnlen += vic.vic_vi_width;

         /* Support the known filename tagging
          * XXX *line-editor-completion-filetype* or so */
         if(!lstat(fullpath, &sb)){
            char c = '\0';

            if(S_ISDIR(sb.st_mode))
               c = '/';
            else if(S_ISLNK(sb.st_mode))
               c = '@';
# ifdef S_ISFIFO
            else if(S_ISFIFO(sb.st_mode))
               c = '|';
# endif
# ifdef S_ISSOCK
            else if(S_ISSOCK(sb.st_mode))
               c = '=';
# endif
# ifdef S_ISCHR
            else if(S_ISCHR(sb.st_mode))
               c = '%';
# endif
# ifdef S_ISBLK
            else if(S_ISBLK(sb.st_mode))
               c = '#';
# endif

            if(c != '\0'){
               putc(c, fp);
               ++lnlen;
            }
         }
      }
      putc('\n', fp);
      ++lncnt;

      if(fp != mx_tty_fp){
         su_mem_bag_push(n_go_data->gdc_membag, membag_persist);
         page_or_print(fp, lncnt);
         su_mem_bag_pop(n_go_data->gdc_membag, membag_persist);

         mx_fs_close(fp);
      }

      n_string_gut(shoup);

      /* A common prefix of 0 means we cannot provide the user any auto
       * completed characters for the multiple possible results.
       * Otherwise we can, so extend the visual line content by the common
       * prefix (in a reversible way) */
      f |= a_TTY_VF_BELL; /* XXX -> *line-editor-completion-bell*? or so */
      if(locolen > 0){
         (exp.s = n_UNCONST(lococp))[locolen] = '\0';
         exp.s -= prefixlen;
         exp.l = (locolen += prefixlen);
         goto jset;
      }
   }

jnope:
   /* If we've provided a default content, but failed to expand, there is
    * nothing we can "revert to": drop that default again */
   if(set_savec){
      tlp->tl_savec.s = NULL;
      tlp->tl_savec.l = 0;
   }
   f &= a_TTY_VF_BELL; /* XXX -> *line-editor-completion-bell*? or so */
   rv = 0;
   goto jleave;
}

# ifdef mx_HAVE_HISTORY
static u32
a_tty__khist_shared(struct a_tty_line *tlp, struct a_tty_hist *thp){
   u32 f, rv;
   NYD2_IN;

   if(LIKELY((tlp->tl_hist = thp) != NULL)){
      char *cp;
      uz i;

      i = thp->th_len;
      if(tlp->tl_goinflags & n_GO_INPUT_CTX_COMPOSE){
         ++i;
         if(!(thp->th_flags & a_TTY_HIST_CTX_COMPOSE))
            ++i;
      }
      tlp->tl_defc.s = cp = n_autorec_alloc(i +1);
      if(tlp->tl_goinflags & n_GO_INPUT_CTX_COMPOSE){
         if((*cp = ok_vlook(escape)[0]) == '\0')
            *cp = n_ESCAPE[0];
         ++cp;
         if(!(thp->th_flags & a_TTY_HIST_CTX_COMPOSE))
            *cp++ = ':';
      }
      su_mem_copy(cp, thp->th_dat, thp->th_len +1);
      rv = tlp->tl_defc.l = i;

      f = (tlp->tl_count > 0) ? a_TTY_VF_MOD_DIRTY : a_TTY_VF_NONE;
      tlp->tl_count = tlp->tl_cursor = 0;
   }else{
      f = a_TTY_VF_BELL;
      rv = U32_MAX;
   }

   tlp->tl_vi_flags |= f;
   NYD2_OU;
   return rv;
}

static u32
a_tty_khist(struct a_tty_line *tlp, boole fwd){
   struct a_tty_hist *thp;
   u32 rv;
   NYD2_IN;

   /* If we're not in history mode yet, save line content */
   if((thp = tlp->tl_hist) == NULL){
      a_tty_cell2save(tlp);
      if((thp = a_tty.tg_hist) == NULL)
         goto jleave;
      if(fwd)
         while(thp->th_older != NULL)
            thp = thp->th_older;
      goto jleave;
   }

   while((thp = fwd ? thp->th_younger : thp->th_older) != NULL){
      /* Applicable to input context?  Compose mode swallows anything */
      if((tlp->tl_goinflags & n__GO_INPUT_CTX_MASK) == n_GO_INPUT_CTX_COMPOSE)
         break;
      if((thp->th_flags & a_TTY_HIST_CTX_MASK) != a_TTY_HIST_CTX_COMPOSE)
         break;
   }
jleave:
   rv = a_tty__khist_shared(tlp, thp);
   NYD2_OU;
   return rv;
}

static u32
a_tty_khist_search(struct a_tty_line *tlp, boole fwd){
   struct str orig_savec;
   u32 xoff, rv;
   struct a_tty_hist *thp;
   NYD2_IN;

   thp = NULL;

   /* We cannot complete an empty line */
   if(UNLIKELY(tlp->tl_count == 0)){
      /* XXX The upcoming hard reset would restore a set savec buffer,
       * XXX so forcefully reset that.  A cleaner solution would be to
       * XXX reset it whenever a restore is no longer desired */
      tlp->tl_savec.s = NULL;
      tlp->tl_savec.l = 0;
      goto jleave;
   }

   /* xxx It is a bit of a hack, but let's just hard-code the knowledge that
    * xxx in compose mode the first character is *escape* and must be skipped
    * xxx when searching the history.  Alternatively we would need to have
    * xxx context-specific history search hooks which would do the search,
    * xxx which is overkill for our sole special case compose mode */
   if((tlp->tl_goinflags & n__GO_INPUT_CTX_MASK) == n_GO_INPUT_CTX_COMPOSE)
      xoff = 1;
   else
      xoff = 0;

   if((thp = tlp->tl_hist) == NULL){
      a_tty_cell2save(tlp);
      if((thp = a_tty.tg_hist) == NULL) /* TODO Should end "doing nothing"! */
         goto jleave;
      if(fwd)
         while(thp->th_older != NULL)
            thp = thp->th_older;
      orig_savec.s = NULL;
      orig_savec.l = 0; /* silence CC */
   }else{
      while((thp = fwd ? thp->th_younger : thp->th_older) != NULL){
         /* Applicable to input context?  Compose mode swallows anything */
         if(xoff != 0)
            break;
         if((thp->th_flags & a_TTY_HIST_CTX_MASK) != a_TTY_HIST_CTX_COMPOSE)
            break;
      }
      if(thp == NULL)
         goto jleave;

      orig_savec = tlp->tl_savec;
   }

   if(xoff >= tlp->tl_savec.l){
      thp = NULL;
      goto jleave;
   }

   if(orig_savec.s == NULL)
      a_tty_cell2save(tlp);

   for(; thp != NULL; thp = fwd ? thp->th_younger : thp->th_older){
      /* Applicable to input context?  Compose mode swallows anything */
      if(xoff != 1 && (thp->th_flags & a_TTY_HIST_CTX_MASK) ==
            a_TTY_HIST_CTX_COMPOSE)
         continue;
      if(su_cs_starts_with(thp->th_dat, &tlp->tl_savec.s[xoff]))
         break;
   }

   if(orig_savec.s != NULL)
      tlp->tl_savec = orig_savec;
jleave:
   rv = a_tty__khist_shared(tlp, thp);
   NYD2_OU;
   return rv;
}
# endif /* mx_HAVE_HISTORY */

static enum a_tty_fun_status
a_tty_fun(struct a_tty_line *tlp, enum a_tty_bind_flags tbf, uz *len){
   enum a_tty_fun_status rv;
   NYD2_IN;

   rv = a_TTY_FUN_STATUS_OK;
# undef a_X
# define a_X(N) a_TTY_BIND_FUN_REDUCE(a_TTY_BIND_FUN_ ## N)
   switch(a_TTY_BIND_FUN_REDUCE(tbf)){
   case a_X(BELL):
      tlp->tl_vi_flags |= a_TTY_VF_BELL;
      break;
   case a_X(GO_BWD):
      a_tty_kleft(tlp);
      break;
   case a_X(GO_FWD):
      a_tty_kright(tlp);
      break;
   case a_X(GO_WORD_BWD):
      a_tty_kgow(tlp, -1);
      break;
   case a_X(GO_WORD_FWD):
      a_tty_kgow(tlp, +1);
      break;
   case a_X(GO_SCREEN_BWD):
      a_tty_kgoscr(tlp, -1);
      break;
   case a_X(GO_SCREEN_FWD):
      a_tty_kgoscr(tlp, +1);
      break;
   case a_X(GO_HOME):
      a_tty_khome(tlp, TRU1);
      break;
   case a_X(GO_END):
      a_tty_kend(tlp);
      break;
   case a_X(DEL_BWD):
      a_tty_kbs(tlp);
      break;
   case a_X(DEL_FWD):
      if(a_tty_kdel(tlp) < 0)
         rv = a_TTY_FUN_STATUS_END;
      break;
   case a_X(SNARF_WORD_BWD):
      a_tty_ksnarfw(tlp, FAL0);
      break;
   case a_X(SNARF_WORD_FWD):
      a_tty_ksnarfw(tlp, TRU1);
      break;
   case a_X(SNARF_END):
      a_tty_ksnarf(tlp, FAL0, TRU1);
      break;
   case a_X(SNARF_LINE):
      a_tty_ksnarf(tlp, TRU1, (tlp->tl_count == 0));
      break;

   case a_X(HIST_FWD):{
# ifdef mx_HAVE_HISTORY
         boole isfwd = TRU1;

         if(0){
# endif
      /* FALLTHRU */
   case a_X(HIST_BWD):
# ifdef mx_HAVE_HISTORY
            isfwd = FAL0;
         }
         if((*len = a_tty_khist(tlp, isfwd)) != U32_MAX){
            rv = a_TTY_FUN_STATUS_RESTART;
            break;
         }
         goto jreset;
# endif
      }
      tlp->tl_vi_flags |= a_TTY_VF_BELL;
      break;

   case a_X(HIST_SRCH_FWD):{
# ifdef mx_HAVE_HISTORY
      boole isfwd = TRU1;

      if(0){
# endif
      /* FALLTHRU */
   case a_X(HIST_SRCH_BWD):
# ifdef mx_HAVE_HISTORY
         isfwd = FAL0;
      }
      if((*len = a_tty_khist_search(tlp, isfwd)) != U32_MAX){
         rv = a_TTY_FUN_STATUS_RESTART;
         break;
      }
      goto jreset;
# else
      tlp->tl_vi_flags |= a_TTY_VF_BELL;
# endif
      }break;

   case a_X(REPAINT):
      tlp->tl_vi_flags |= a_TTY_VF_MOD_DIRTY;
      break;
   case a_X(QUOTE_RNDTRIP):
      tlp->tl_quote_rndtrip = !tlp->tl_quote_rndtrip;
      break;
   case a_X(PROMPT_CHAR):{
      wchar_t wc;

      if((wc = a_tty_vinuni(tlp)) > 0)
         a_tty_kother(tlp, wc);
      }break;
   case a_X(COMPLETE):
      if((*len = a_tty_kht(tlp)) > 0)
         rv = a_TTY_FUN_STATUS_RESTART;
      break;

   case a_X(PASTE):
      if(tlp->tl_pastebuf.l > 0)
         *len = (tlp->tl_defc = tlp->tl_pastebuf).l;
      else
         tlp->tl_vi_flags |= a_TTY_VF_BELL;
      break;

   case a_X(CLEAR_SCREEN):
      tlp->tl_vi_flags |= (mx_termcap_cmdx(mx_TERMCAP_CMD_cl) == TRU1)
            ? a_TTY_VF_MOD_DIRTY : a_TTY_VF_BELL;
      break;

   case a_X(RAISE_INT):
#ifdef SIGINT
      n_raise(SIGINT);
#endif
      break;
   case a_X(RAISE_QUIT):
#ifdef SIGTSTP
      n_raise(SIGQUIT);
#endif
      break;
   case a_X(RAISE_TSTP):
#ifdef SIGTSTP
      n_raise(SIGTSTP);
#endif
      break;

   case a_X(CANCEL):
      /* Normally this just causes a restart and thus resets the state
       * machine  */
      if(tlp->tl_savec.l == 0 && tlp->tl_defc.l == 0){
      }
# ifdef mx_HAVE_KEY_BINDINGS
      tlp->tl_bind_takeover = '\0';
# endif
      tlp->tl_vi_flags |= a_TTY_VF_BELL;
      rv = a_TTY_FUN_STATUS_RESTART;
      break;

   case a_X(RESET):
      if(tlp->tl_count == 0 && tlp->tl_savec.l == 0 && tlp->tl_defc.l == 0){
# ifdef mx_HAVE_KEY_BINDINGS
         tlp->tl_bind_takeover = '\0';
# endif
         tlp->tl_vi_flags |= a_TTY_VF_MOD_DIRTY | a_TTY_VF_BELL;
         break;
      }else if(0){
   case a_X(FULLRESET):
         tlp->tl_savec.s = tlp->tl_defc.s = NULL;
         tlp->tl_savec.l = tlp->tl_defc.l = 0;
         tlp->tl_defc_cursor_byte = 0;
         tlp->tl_vi_flags |= a_TTY_VF_BELL;
      }
jreset:
# ifdef mx_HAVE_KEY_BINDINGS
      tlp->tl_bind_takeover = '\0';
# endif
      tlp->tl_vi_flags |= a_TTY_VF_MOD_DIRTY;
      tlp->tl_cursor = tlp->tl_count = 0;
# ifdef mx_HAVE_HISTORY
      tlp->tl_hist = NULL;
# endif
      if((*len = tlp->tl_savec.l) != 0){
         tlp->tl_defc = tlp->tl_savec;
         tlp->tl_savec.s = NULL;
         tlp->tl_savec.l = 0;
      }else
         *len = tlp->tl_defc.l;
      rv = a_TTY_FUN_STATUS_RESTART;
      break;

   default:
   case a_X(COMMIT):
      rv = a_TTY_FUN_STATUS_COMMIT;
      break;
   }
# undef a_X

   NYD2_OU;
   return rv;
}

static sz
a_tty_readline(struct a_tty_line *tlp, uz len, boole *histok_or_null
      su_DBG_LOC_ARGS_DECL){
   /* We want to save code, yet we may have to incorporate a lines'
    * default content and / or default input to switch back to after some
    * history movement; let "len > 0" mean "have to display some data
    * buffer" -> a_BUFMODE, and only otherwise read(2) it */
   mbstate_t ps[2];
   char cbuf_base[MB_LEN_MAX * 2], *cbuf, *cbufp;
   sz rv;
   struct a_tty_bind_tree *tbtp;
   wchar_t wc;
   enum a_tty_bind_flags tbf;
   enum {a_NONE, a_WAS_HERE = 1<<0, a_BUFMODE = 1<<1, a_MAYBEFUN = 1<<2,
      a_TIMEOUT = 1<<3, a_TIMEOUT_EXPIRED = 1<<4,
         a_TIMEOUT_MASK = a_TIMEOUT | a_TIMEOUT_EXPIRED,
      a_READ_LOOP_MASK = ~(a_WAS_HERE | a_MAYBEFUN | a_TIMEOUT_MASK)
   } flags;
   NYD_IN;

   UNINIT(rv, 0);
# ifdef mx_HAVE_KEY_BINDINGS
   ASSERT(tlp->tl_bind_takeover == '\0');
# endif
jrestart:
   su_mem_set(ps, 0, sizeof ps);
   flags = a_NONE;
   tlp->tl_vi_flags |= a_TTY_VF_REFRESH | a_TTY_VF_SYNC;

jinput_loop:
   for(;;){
      if(len != 0)
         flags |= a_BUFMODE;

      /* Ensure we have valid pointers, and room for grow */
      a_tty_check_grow(tlp, ((flags & a_BUFMODE) ? (u32)len : 1)
         su_DBG_LOC_ARGS_USE);

      /* Handle visual state flags, except in buffer mode */
      if(!(flags & a_BUFMODE) && (tlp->tl_vi_flags & a_TTY_VF_ALL_MASK))
         if(!a_tty_vi_refresh(tlp)){
            rv = -1;
            goto jleave;
         }

      /* Ready for messing around.
       * Normal read(2)?  Else buffer mode: speed this one up */
      if(!(flags & a_BUFMODE)){
         cbufp =
         cbuf = cbuf_base;
      }else{
         ASSERT(tlp->tl_defc.l > 0 && tlp->tl_defc.s != NULL);
         ASSERT(tlp->tl_defc.l >= len);
         cbufp =
         cbuf = tlp->tl_defc.s + (tlp->tl_defc.l - len);
         cbufp += len;
      }

      /* Read in the next complete multibyte character */
      /* C99 */{
# ifdef mx_HAVE_KEY_BINDINGS
         struct a_tty_bind_tree *xtbtp;
         struct inseq{
            struct inseq *last;
            struct inseq *next;
            struct a_tty_bind_tree *tbtp;
         } *isp_head, *isp;

         isp_head = isp = NULL;
# endif

         for(flags &= a_READ_LOOP_MASK;;){
# ifdef mx_HAVE_KEY_BINDINGS
            if(!(flags & a_BUFMODE) && tlp->tl_bind_takeover != '\0'){
               wc = tlp->tl_bind_takeover;
               tlp->tl_bind_takeover = '\0';
            }else
# endif
            {
               if(!(flags & a_BUFMODE)){
                  /* Let me at least once dream of iomon(itor), timer with
                   * one-shot, enwrapped with key_event and key_sequence_event,
                   * all driven by an event_loop */
# ifdef mx_HAVE_KEY_BINDINGS
                  flags &= ~a_TIMEOUT_MASK;
                  if(isp != NULL && (tbtp = isp->tbtp)->tbt_isseq &&
                        !tbtp->tbt_isseq_trail){
                     a_tty_term_rawmode_timeout(tlp, TRU1);
                     flags |= a_TIMEOUT;
                  }
jread_again:
# endif
                  while((rv = read(STDIN_FILENO, cbufp, 1)) == -1){
                     /* TODO Currently a noop due to SA_RESTART */
                     if(su_err_no() != su_ERR_INTR ||
                           ((tlp->tl_vi_flags & a_TTY_VF_MOD_DIRTY) &&
                              !a_tty_vi_refresh(tlp)))
                        break;
                  }

# ifdef mx_HAVE_KEY_BINDINGS
                  if(flags & a_TIMEOUT)
                     a_tty_term_rawmode_timeout(tlp, FAL0);
# endif

                  if(rv < 0)
                     goto jleave;
# ifdef mx_HAVE_KEY_BINDINGS
                  /* Timeout expiration */
                  else if(rv == 0){
                     ASSERT(flags & a_TIMEOUT);
                     ASSERT(isp != NIL);

                     /* Something "atomic" broke.  Maybe the current one can
                      * also be terminated already, itself? xxx really? */
                     if((tbtp = isp->tbtp)->tbt_bind != NIL){
                        tlp->tl_bind_takeover = wc;
                        goto jhave_bind;
                     }

                     /* Or, maybe there is a second path without a timeout;
                      * this should be covered by .tbt_isseq_trail, but then
                      * again a single-layer implementation cannot "know" */
                     for(xtbtp = tbtp; (xtbtp = xtbtp->tbt_sibling) != NIL;)
                        if(xtbtp->tbt_char == tbtp->tbt_char){
                           ASSERT(!xtbtp->tbt_isseq);
                           break;
                        }
                     /* So -- lay down on a blocking read(2)? */
                     if(xtbtp != NIL){
                        flags &= ~a_TIMEOUT;
                        goto jread_again;
                     }
                     goto jtake_over;
                  }
# endif /* mx_HAVE_KEY_BINDINGS */

                  /* As a special case, simulate EOF via EOT (which can happen
                   * via type-ahead as when typing "yes\n^@" during sleep of
                   *    $ sleep 5; mail -s byjove $LOGNAME */
                  if(*cbufp == '\0'){
                     ASSERT((n_psonce & n_PSO_INTERACTIVE) &&
                        !(n_pstate & n_PS_ROBOT));
                     *cbuf = '\x04';
                  }
                  ++cbufp;
               }

               rv = (sz)mbrtowc(&wc, cbuf, P2UZ(cbufp - cbuf), &ps[0]);
               if(rv <= 0){
                  /* Any error during buffer mode can only result in a hard
                   * reset;  Otherwise, if it's a hard error, or if too many
                   * redundant shift sequences overflow our buffer: perform
                   * hard reset */
                  if((flags & a_BUFMODE) || rv == -1 ||
                        sizeof cbuf_base == P2UZ(cbufp - cbuf)){
                     a_tty_fun(tlp, a_TTY_BIND_FUN_FULLRESET, &len);
                     goto jrestart;
                  }
                  /* Otherwise, due to the way we deal with the buffer, we need
                   * to restore the mbstate_t from before this conversion */
                  ps[0] = ps[1];
                  continue;
               }
               cbufp = cbuf;
               ps[1] = ps[0];
            }

            /* Normal read(2)ing is subject to detection of key-bindings */
# ifdef mx_HAVE_KEY_BINDINGS
            if(!(flags & a_BUFMODE)){
               /* Check for special bypass functions before we try to embed
                * this character into the tree */
               if(su_cs_is_ascii(wc)){
                  char c;
                  char const *cp;

                  for(c = (char)wc, cp = &(*tlp->tl_bind_shcut_prompt_char)[0];
                        *cp != '\0'; ++cp){
                     if(c == *cp){
                        wc = a_tty_vinuni(tlp);
                        break;
                     }
                  }
                  if(wc == '\0'){
                     tlp->tl_vi_flags |= a_TTY_VF_BELL;
                     goto jinput_loop;
                  }
               }
               if(su_cs_is_ascii(wc))
                  flags |= a_MAYBEFUN;
               else
                  flags &= ~a_MAYBEFUN;

               /* Search for this character in the bind tree */
               tbtp = (isp != NULL) ? isp->tbtp->tbt_childs
                     : (*tlp->tl_bind_tree_hmap)[wc % a_TTY_PRIME];
               for(; tbtp != NULL; tbtp = tbtp->tbt_sibling){
                  if(tbtp->tbt_char == wc){
                     struct inseq *nisp;

                     /* If this one cannot continue we're likely finished! */
                     if(tbtp->tbt_childs == NULL){
                        ASSERT(tbtp->tbt_bind != NULL);
                        tbf = tbtp->tbt_bind->tbc_flags;
                        goto jmle_fun;
                     }

                     /* This needs to read more characters */
                     nisp = n_autorec_alloc(sizeof *nisp);
                     if((nisp->last = isp) == NULL)
                        isp_head = nisp;
                     else
                        isp->next = nisp;
                     nisp->next = NULL;
                     nisp->tbtp = tbtp;
                     isp = nisp;
                     flags &= ~a_WAS_HERE;
                     break;
                  }
               }
               if(tbtp != NULL)
                  continue;

               /* Was there a binding active, but couldn't be continued? */
               if(isp != NULL){
                  /* A binding had a timeout, it didn't expire, but we saw
                   * something non-expected.  Something "atomic" broke.
                   * Maybe there is a second path without a timeout, that
                   * continues like we've seen it.  I.e., it may just have been
                   * the user, typing too fast.  We definitely want to allow
                   * bindings like \e,d etc. to succeed: users are so used to
                   * them that a timeout cannot be the mechanism to catch up!
                   * A single-layer implementation cannot "know" */
                  if((tbtp = isp->tbtp)->tbt_isseq && (isp->last == NULL ||
                        !(xtbtp = isp->last->tbtp)->tbt_isseq ||
                        xtbtp->tbt_isseq_trail)){
                     for(xtbtp = (tbtp = isp->tbtp);
                           (xtbtp = xtbtp->tbt_sibling) != NULL;)
                        if(xtbtp->tbt_char == tbtp->tbt_char){
                           ASSERT(!xtbtp->tbt_isseq);
                           break;
                        }
                     if(xtbtp != NULL){
                        isp->tbtp = xtbtp;
                        tlp->tl_bind_takeover = wc;
                        continue;
                     }
                  }

                  /* Check for CANCEL shortcut now */
                  if(flags & a_MAYBEFUN){
                     char c;
                     char const *cp;

                     for(c = (char)wc, cp = &(*tlp->tl_bind_shcut_cancel)[0];
                           *cp != '\0'; ++cp)
                        if(c == *cp){
                           tbf = a_TTY_BIND_FUN_INTERNAL |
                                 a_TTY_BIND_FUN_CANCEL;
                           goto jmle_fun;
                        }
                  }

                  /* So: maybe the current sequence can be terminated here? */
                  if((tbtp = isp->tbtp)->tbt_bind != NULL){
jhave_bind:
                     tbf = tbtp->tbt_bind->tbc_flags;
jmle_fun:
                     if(tbf & a_TTY_BIND_FUN_INTERNAL){
                        switch(a_tty_fun(tlp, tbf, &len)){
                        case a_TTY_FUN_STATUS_OK:
                           goto jinput_loop;
                        case a_TTY_FUN_STATUS_COMMIT:
                           goto jdone;
                        case a_TTY_FUN_STATUS_RESTART:
                           goto jrestart;
                        case a_TTY_FUN_STATUS_END:
                           rv = -1;
                           goto jleave;
                        }
                        ASSERT(0);
                     }else if(tbtp->tbt_bind->tbc_flags & a_TTY_BIND_NOCOMMIT){
                        struct a_tty_bind_ctx *tbcp;

                        tbcp = tbtp->tbt_bind;
                        su_mem_copy(tlp->tl_defc.s = n_autorec_alloc(
                              (tlp->tl_defc.l = len = tbcp->tbc_exp_len) +1),
                           tbcp->tbc_exp, tbcp->tbc_exp_len +1);
                        goto jrestart;
                     }else{
                        cbufp = tbtp->tbt_bind->tbc_exp;
                        goto jinject_input;
                     }
                  }
               }

               /* Otherwise take over all chars "as is" */
jtake_over:
               for(; isp_head != NULL; isp_head = isp_head->next)
                  if(a_tty_kother(tlp, isp_head->tbtp->tbt_char)){
                     /* FIXME */
                  }
               /* And the current one too */
               goto jkother;
            }
# endif /* mx_HAVE_KEY_BINDINGS */

            if((flags & a_BUFMODE) && (len -= (uz)rv) == 0){
               /* Buffer mode completed */
               tlp->tl_defc.s = NULL;
               tlp->tl_defc.l = 0;
               flags &= ~a_BUFMODE;
            }
            break;
         }

# ifndef mx_HAVE_KEY_BINDINGS
         /* Don't interpret control bytes during buffer mode.
          * Otherwise, if it's a control byte check whether it is a MLE
          * function.  Remarks: initially a complete duplicate to be able to
          * switch(), later converted to simply iterate over (an #ifdef'd
          * subset of) the MLE base_tuple table in order to have "a SPOF" */
         if(cbuf == cbuf_base && su_cs_is_ascii(wc) &&
               su_cs_is_cntrl((unsigned char)wc)){
            struct a_tty_bind_builtin_tuple const *tbbtp, *tbbtp_max;
            char c;

            c = (char)wc ^ 0x40;
            tbbtp = a_tty_bind_base_tuples;
            tbbtp_max = &tbbtp[NELEM(a_tty_bind_base_tuples)];
jbuiltin_redo:
            for(; tbbtp < tbbtp_max; ++tbbtp){
               /* Assert default_tuple table is properly subset'ed */
               ASSERT(tbbtp->tbdt_iskey);
               if(tbbtp->tbbt_ckey == c){
                  if(tbbtp->tbbt_exp[0] == '\0'){
                     tbf = a_TTY_BIND_FUN_EXPAND((u8)tbbtp->tbbt_exp[1]);
                     switch(a_tty_fun(tlp, tbf, &len)){
                     case a_TTY_FUN_STATUS_OK:
                        goto jinput_loop;
                     case a_TTY_FUN_STATUS_COMMIT:
                        goto jdone;
                     case a_TTY_FUN_STATUS_RESTART:
                        goto jrestart;
                     case a_TTY_FUN_STATUS_END:
                        rv = -1;
                        goto jleave;
                     }
                     ASSERT(0);
                  }else{
                     cbufp = n_UNCONST(tbbtp->tbbt_exp);
                     goto jinject_input;
                  }
               }
            }
            if(tbbtp ==
                  &a_tty_bind_base_tuples[NELEM(a_tty_bind_base_tuples)]){
               tbbtp = a_tty_bind_default_tuples;
               tbbtp_max = &tbbtp[NELEM(a_tty_bind_default_tuples)];
               goto jbuiltin_redo;
            }
         }
#  endif /* !mx_HAVE_KEY_BINDINGS */

# ifdef mx_HAVE_KEY_BINDINGS
jkother:
# endif
         if(a_tty_kother(tlp, wc)){
            /* Don't clear the history during buffer mode.. */
# ifdef mx_HAVE_HISTORY
            if(!(flags & a_BUFMODE) && cbuf == cbuf_base)
               tlp->tl_hist = NULL;
# endif
         }
      }
   }

   /* We have a completed input line, convert the struct cell data to its
    * plain character equivalent */
jdone:
   rv = a_tty_cell2dat(tlp);
jleave:
   putc('\n', mx_tty_fp);
   fflush(mx_tty_fp);
   NYD_OU;
   return rv;

jinject_input:{
   uz i;

   mx_sigs_all_holdx(); /* XXX v15 drop */
   i = a_tty_cell2dat(tlp);
   n_go_input_inject(n_GO_INPUT_INJECT_NONE, tlp->tl_line.cbuf, i);
   i = su_cs_len(cbufp) +1;
   if(i >= *tlp->tl_x_bufsize){
      *tlp->tl_x_buf = su_MEM_REALLOC_LOCOR(*tlp->tl_x_buf, i,
               su_DBG_LOC_ARGS_ORUSE);
      *tlp->tl_x_bufsize = i;
   }
   su_mem_copy(*tlp->tl_x_buf, cbufp, i);
   mx_sigs_all_rele(); /* XXX v15 drop */
   if(histok_or_null != NULL)
      *histok_or_null = FAL0;
   rv = (sz)--i;
   }
   goto jleave;
}

# ifdef mx_HAVE_KEY_BINDINGS
static enum n_go_input_flags
a_tty_bind_ctx_find(char const *name){
   enum n_go_input_flags rv;
   struct a_tty_input_ctx_map const *ticmp;
   NYD2_IN;

   ticmp = a_tty_input_ctx_maps;
   do if(!su_cs_cmp_case(ticmp->ticm_name, name)){
      rv = ticmp->ticm_ctx;
      goto jleave;
   }while(PCMP(++ticmp, <,
      &a_tty_input_ctx_maps[NELEM(a_tty_input_ctx_maps)]));

   rv = (enum n_go_input_flags)-1;
jleave:
   NYD2_OU;
   return rv;
}

static boole
a_tty_bind_create(struct a_tty_bind_parse_ctx *tbpcp, boole replace){
   struct a_tty_bind_ctx *tbcp;
   boole rv;
   NYD2_IN;

   rv = FAL0;

   if(!a_tty_bind_parse(TRU1, tbpcp))
      goto jleave;

   /* Since we use a single buffer for it all, need to replace as such */
   if(tbpcp->tbpc_tbcp != NULL){
      if(!replace)
         goto jleave;
      a_tty_bind_del(tbpcp);
   }else if(a_tty.tg_bind_cnt == U32_MAX){
      n_err(_("`bind': maximum number of bindings already established\n"));
      goto jleave;
   }

   /* C99 */{
      uz i, j;

      tbcp = n_alloc(VSTRUCT_SIZEOF(struct a_tty_bind_ctx, tbc__buf) +
            tbpcp->tbpc_seq_len +1 + tbpcp->tbpc_exp.l +1 +
            tbpcp->tbpc_cnv_align_mask + 1 + tbpcp->tbpc_cnv_len);
      if(tbpcp->tbpc_ltbcp != NULL){
         tbcp->tbc_next = tbpcp->tbpc_ltbcp->tbc_next;
         tbpcp->tbpc_ltbcp->tbc_next = tbcp;
      }else{
         enum n_go_input_flags gif;

         gif = tbpcp->tbpc_flags & n__GO_INPUT_CTX_MASK;
         tbcp->tbc_next = a_tty.tg_bind[gif];
         a_tty.tg_bind[gif] = tbcp;
      }
      su_mem_copy(tbcp->tbc_seq = &tbcp->tbc__buf[0],
         tbpcp->tbpc_seq, i = (tbcp->tbc_seq_len = tbpcp->tbpc_seq_len) +1);
      ASSERT(tbpcp->tbpc_exp.l > 0);
      su_mem_copy(tbcp->tbc_exp = &tbcp->tbc__buf[i],
         tbpcp->tbpc_exp.s, j = (tbcp->tbc_exp_len = tbpcp->tbpc_exp.l) +1);
      i += j;
      i = (i + tbpcp->tbpc_cnv_align_mask) & ~tbpcp->tbpc_cnv_align_mask;
      su_mem_copy(tbcp->tbc_cnv = &tbcp->tbc__buf[i],
         tbpcp->tbpc_cnv, (tbcp->tbc_cnv_len = tbpcp->tbpc_cnv_len));
      tbcp->tbc_flags = tbpcp->tbpc_flags;
   }

   /* Directly resolve any termcap(5) symbol if we are already setup */
   if((n_psonce & n_PSO_STARTED) &&
         (tbcp->tbc_flags & (a_TTY_BIND_RESOLVE | a_TTY_BIND_DEFUNCT)) ==
          a_TTY_BIND_RESOLVE)
      a_tty_bind_resolve(tbcp);

   ++a_tty.tg_bind_cnt;
   /* If this binding is usable invalidate the key input lookup trees */
   if(!(tbcp->tbc_flags & a_TTY_BIND_DEFUNCT))
      a_tty.tg_bind_isdirty = TRU1;
   rv = TRU1;
jleave:
   NYD2_OU;
   return rv;
}

static boole
a_tty_bind_parse(boole isbindcmd, struct a_tty_bind_parse_ctx *tbpcp){
   enum{a_TRUE_RV = a_TTY__BIND_LAST<<1};

   struct n_visual_info_ctx vic;
   struct str shin_save, shin;
   struct n_string shou, *shoup;
   uz i;
   struct kse{
      struct kse *next;
      char *seq_dat;
      wc_t *cnv_dat;
      u32 seq_len;
      u32 cnv_len;      /* High bit set if a termap to be resolved */
      u32 calc_cnv_len; /* Ditto, but aligned etc. */
      u8 kse__dummy[4];
   } *head, *tail;
   u32 f;
   NYD2_IN;
   LCTA(UCMP(64, a_TRUE_RV, <, U32_MAX),
      "Flag bits excess storage datatype");

   f = n_GO_INPUT_NONE;
   shoup = n_string_creat_auto(&shou);
   head = tail = NULL;

   /* Parse the key-sequence */
   for(shin.s = n_UNCONST(tbpcp->tbpc_in_seq), shin.l = UZ_MAX;;){
      struct kse *ep;
      enum n_shexp_state shs;

      shin_save = shin;
      shs = n_shexp_parse_token((n_SHEXP_PARSE_TRUNC |
            n_SHEXP_PARSE_TRIM_SPACE | n_SHEXP_PARSE_IGNORE_EMPTY |
            n_SHEXP_PARSE_IFS_IS_COMMA), shoup, &shin, NULL);
      if(shs & n_SHEXP_STATE_ERR_UNICODE){
         f |= a_TTY_BIND_DEFUNCT;
         if(isbindcmd && (n_poption & n_PO_D_V))
            n_err(_("`%s': \\uNICODE not available in locale: %s\n"),
               tbpcp->tbpc_cmd, tbpcp->tbpc_in_seq);
      }
      if((shs & n_SHEXP_STATE_ERR_MASK) & ~n_SHEXP_STATE_ERR_UNICODE){
         n_err(_("`%s': failed to parse key-sequence: %s\n"),
            tbpcp->tbpc_cmd, tbpcp->tbpc_in_seq);
         goto jleave;
      }
      if((shs & (n_SHEXP_STATE_OUTPUT | n_SHEXP_STATE_STOP)) ==
            n_SHEXP_STATE_STOP)
         break;

      ep = n_autorec_alloc(sizeof *ep);
      if(head == NULL)
         head = ep;
      else
         tail->next = ep;
      tail = ep;
      ep->next = NULL;
      if(!(shs & n_SHEXP_STATE_ERR_UNICODE)){
         i = su_cs_len(ep->seq_dat =
               n_shexp_quote_cp(n_string_cp(shoup), TRU1));
         if(i >= S32_MAX - 1)
            goto jelen;
         ep->seq_len = (u32)i;
      }else{
         /* Otherwise use the original buffer, _we_ can only quote it the wrong
          * way (e.g., an initial $'\u3a' becomes '\u3a'), _then_ */
         if((i = shin_save.l - shin.l) >= S32_MAX - 1)
            goto jelen;
         ep->seq_len = (u32)i;
         ep->seq_dat = savestrbuf(shin_save.s, i);
      }

      su_mem_set(&vic, 0, sizeof vic);
      vic.vic_inlen = shoup->s_len;
      vic.vic_indat = shoup->s_dat;
      if(!n_visual_info(&vic,
            n_VISUAL_INFO_WOUT_CREATE | n_VISUAL_INFO_WOUT_SALLOC)){
         n_err(_("`%s': key-sequence seems to contain invalid "
            "characters: %s: %s\n"),
            tbpcp->tbpc_cmd, n_string_cp(shoup), tbpcp->tbpc_in_seq);
         f |= a_TTY_BIND_DEFUNCT;
         goto jleave;
      }else if(vic.vic_woulen == 0 ||
            vic.vic_woulen >= (S32_MAX - 2) / sizeof(wc_t)){
jelen:
         n_err(_("`%s': length of key-sequence unsupported: %s: %s\n"),
            tbpcp->tbpc_cmd, n_string_cp(shoup), tbpcp->tbpc_in_seq);
         f |= a_TTY_BIND_DEFUNCT;
         goto jleave;
      }
      ep->cnv_dat = vic.vic_woudat;
      ep->cnv_len = (u32)vic.vic_woulen;

      /* A termcap(5)/terminfo(5) identifier? */
      if(ep->cnv_len > 1 && ep->cnv_dat[0] == ':'){
         i = --ep->cnv_len, ++ep->cnv_dat;
#  if 0 /* ndef mx_HAVE_TERMCAP xxx User can, via *termcap*! */
         if(n_poption & n_PO_D_V)
            n_err(_("`%s': no termcap(5)/terminfo(5) support: %s: %s\n"),
               tbpcp->tbpc_cmd, ep->seq_dat, tbpcp->tbpc_in_seq);
         f |= a_TTY_BIND_DEFUNCT;
#  endif
         if(i > a_TTY_BIND_CAPNAME_MAX){
            n_err(_("`%s': termcap(5)/terminfo(5) name too long: %s: %s\n"),
               tbpcp->tbpc_cmd, ep->seq_dat, tbpcp->tbpc_in_seq);
            f |= a_TTY_BIND_DEFUNCT;
         }
         while(i > 0)
            /* (We store it as char[]) */
            if((u32)ep->cnv_dat[--i] & ~0x7Fu){
               n_err(_("`%s': invalid termcap(5)/terminfo(5) name content: "
                  "%s: %s\n"),
                  tbpcp->tbpc_cmd, ep->seq_dat, tbpcp->tbpc_in_seq);
               f |= a_TTY_BIND_DEFUNCT;
               break;
            }
         ep->cnv_len |= S32_MIN; /* Needs resolve */
         f |= a_TTY_BIND_RESOLVE;
      }

      if(shs & n_SHEXP_STATE_STOP)
         break;
   }

   if(head == NULL){
jeempty:
      n_err(_("`%s': effectively empty key-sequence: %s\n"),
         tbpcp->tbpc_cmd, tbpcp->tbpc_in_seq);
      goto jleave;
   }

   if(isbindcmd) /* (Or always, just "1st time init") */
      tbpcp->tbpc_cnv_align_mask = MAX(sizeof(s32), sizeof(wc_t)) - 1;

   /* C99 */{
      struct a_tty_bind_ctx *ltbcp, *tbcp;
      char *cpbase, *cp, *cnv;
      uz seql, cnvl;

      /* Unite the parsed sequence(s) into single string representations */
      for(seql = cnvl = 0, tail = head; tail != NULL; tail = tail->next){
         seql += tail->seq_len + 1;

         if(!isbindcmd)
            continue;

         /* Preserve room for terminal capabilities to be resolved.
          * Above we have ensured the buffer will fit in these calculations */
         if((i = tail->cnv_len) & S32_MIN){
            /* For now
             * struct{s32 buf_len_iscap; s32 cap_len; wc_t name[]+NUL;}
             * later
             * struct{s32 buf_len_iscap; s32 cap_len; char buf[]+NUL;} */
            LCTAV(IS_POW2(a_TTY_BIND_CAPEXP_ROUNDUP));
            LCTA(a_TTY_BIND_CAPEXP_ROUNDUP >= sizeof(wc_t),
               "Aligning on this constant does not properly align wc_t");
            i &= S32_MAX;
            i *= sizeof(wc_t);
            i += sizeof(s32);
            if(i < a_TTY_BIND_CAPEXP_ROUNDUP)
               i = (i + (a_TTY_BIND_CAPEXP_ROUNDUP - 1)) &
                     ~(a_TTY_BIND_CAPEXP_ROUNDUP - 1);
         }else
            /* struct{s32 buf_len_iscap; wc_t buf[]+NUL;} */
            i *= sizeof(wc_t);
         i += sizeof(s32) + sizeof(wc_t); /* (buf_len_iscap, NUL) */
         cnvl += i;
         if(tail->cnv_len & S32_MIN){
            tail->cnv_len &= S32_MAX;
            i |= S32_MIN;
         }
         tail->calc_cnv_len = (u32)i;
      }
      --seql;

      tbpcp->tbpc_seq_len = seql;
      tbpcp->tbpc_cnv_len = cnvl;
      /* C99 */{
         uz j;

         j = i = seql + 1; /* Room for comma separator */
         if(isbindcmd){
            i = (i + tbpcp->tbpc_cnv_align_mask) & ~tbpcp->tbpc_cnv_align_mask;
            j = i;
            i += cnvl;
         }
         tbpcp->tbpc_seq = cp = cpbase = n_autorec_alloc(i);
         tbpcp->tbpc_cnv = cnv = &cpbase[j];
      }

      for(tail = head; tail != NULL; tail = tail->next){
         su_mem_copy(cp, tail->seq_dat, tail->seq_len);
         cp += tail->seq_len;
         *cp++ = ',';

         if(isbindcmd){
            char * const save_cnv = cnv;

            UNALIGN(s32*,cnv)[0] = (s32)(i = tail->calc_cnv_len);
            cnv += sizeof(s32);
            if(i & S32_MIN){
               /* For now
                * struct{s32 buf_len_iscap; s32 cap_len; wc_t name[];}
                * later
                * struct{s32 buf_len_iscap; s32 cap_len; char buf[];} */
               UNALIGN(s32*,cnv)[0] = tail->cnv_len;
               cnv += sizeof(s32);
            }
            i = tail->cnv_len * sizeof(wc_t);
            su_mem_copy(cnv, tail->cnv_dat, i);
            cnv += i;
            *UNALIGN(wc_t*,cnv) = '\0';

            cnv = save_cnv + (tail->calc_cnv_len & S32_MAX);
         }
      }
      *--cp = '\0';

      /* Search for a yet existing identical mapping */
      /* C99 */{
         enum n_go_input_flags gif;

         gif = tbpcp->tbpc_flags & n__GO_INPUT_CTX_MASK;

         for(ltbcp = NULL, tbcp = a_tty.tg_bind[gif]; tbcp != NULL;
               ltbcp = tbcp, tbcp = tbcp->tbc_next)
            if(tbcp->tbc_seq_len == seql &&
                  !su_mem_cmp(tbcp->tbc_seq, cpbase, seql)){
               tbpcp->tbpc_tbcp = tbcp;
               break;
            }
      }
      tbpcp->tbpc_ltbcp = ltbcp;
      tbpcp->tbpc_flags |= (f & a_TTY__BIND_MASK);
   }

   /* Create single string expansion if so desired */
   if(isbindcmd){
      char *exp;

      exp = tbpcp->tbpc_exp.s;

      i = tbpcp->tbpc_exp.l;
      if(i > 0 && exp[i - 1] == '@'){
#if 0 /* xxx no: allow trailing whitespace, as in 'echo du @' .. */
         while(--i > 0)
            if(!su_cs_is_space(exp[i - 1]))
               break;
#else
         --i;
#endif
         if(i == 0)
            goto jeempty;

         exp[tbpcp->tbpc_exp.l = i] = '\0';
         tbpcp->tbpc_flags |= a_TTY_BIND_NOCOMMIT;
      }

      /* Reverse solidus cannot be placed last in expansion to avoid (at the
       * time of this writing) possible problems with newline escaping.
       * Don't care about (un)even number thereof */
      if(i > 0 && exp[i - 1] == '\\'){
         n_err(_("`%s': reverse solidus cannot be last in expansion: %s\n"),
            tbpcp->tbpc_cmd, tbpcp->tbpc_in_seq);
         goto jleave;
      }

      /* TODO `bind': since empty expansions are forbidden it would be nice to
       * TODO be able to say "bind base a,b,c" and see the expansion of only
       * TODO that (just like we do for `alias', `commandalias' etc.!) */
      if(i == 0)
         goto jeempty;

      /* It may map to an internal MLE command! */
      for(i = 0; i < NELEM(a_tty_bind_fun_names); ++i)
         if(!su_cs_cmp_case(exp, a_tty_bind_fun_names[i])){
            tbpcp->tbpc_flags |= a_TTY_BIND_FUN_EXPAND(i) |
                  a_TTY_BIND_FUN_INTERNAL |
                  (head->next == NULL ? a_TTY_BIND_MLE1CNTRL : 0);
            if((n_poption & n_PO_D_V) &&
                  (tbpcp->tbpc_flags & a_TTY_BIND_NOCOMMIT))
               n_err(_("%s: MLE commands cannot be made editable via @: %s\n"),
                  tbpcp->tbpc_cmd, exp);
            tbpcp->tbpc_flags &= ~a_TTY_BIND_NOCOMMIT;
            break;
         }
   }

  f |= a_TRUE_RV; /* TODO because we only now true and false; DEFUNCT.. */
jleave:
   n_string_gut(shoup);
   NYD2_OU;
   return (f & a_TRUE_RV) != 0;
}

static void
a_tty_bind_resolve(struct a_tty_bind_ctx *tbcp){
   char capname[a_TTY_BIND_CAPNAME_MAX +1];
   struct mx_termcap_value tv;
   uz len;
   boole isfirst; /* TODO For now: first char must be control! */
   char *cp, *next;
   NYD2_IN;

   UNINIT(next, NULL);
   for(cp = tbcp->tbc_cnv, isfirst = TRU1, len = tbcp->tbc_cnv_len;
         len > 0; isfirst = FAL0, cp = next){
      /* C99 */{
         s32 i, j;

         i = UNALIGN(s32*,cp)[0];
         j = i & S32_MAX;
         next = &cp[j];
         len -= j;
         if(i == j)
            continue;

         /* struct{s32 buf_len_iscap; s32 cap_len; wc_t name[];} */
         cp += sizeof(s32);
         i = UNALIGN(s32*,cp)[0];
         cp += sizeof(s32);
         for(j = 0; j < i; ++j)
            capname[j] = UNALIGN(wc_t*,cp)[j];
         capname[j] = '\0';
      }

      /* Use generic lookup mechanism if not a known query */
      /* C99 */{
         s32 tq;

         tq = mx_termcap_query_for_name(capname, mx_TERMCAP_CAPTYPE_STRING);
         if(tq == -1){
            tv.tv_data.tvd_string = capname;
            tq = mx__TERMCAP_QUERY_MAX1;
         }

         if(tq < 0 || !mx_termcap_query(tq, &tv)){
            if(n_poption & n_PO_D_V)
               n_err(_("`bind': unknown or unsupported capability: %s: %s\n"),
                  capname, tbcp->tbc_seq);
            tbcp->tbc_flags |= a_TTY_BIND_DEFUNCT;
            break;
         }
      }

      /* struct{s32 buf_len_iscap; s32 cap_len; char buf[]+NUL;} */
      /* C99 */{
         uz i;

         i = su_cs_len(tv.tv_data.tvd_string);
         if(/*i > S32_MAX ||*/ i >= P2UZ(next - cp)){
            if(n_poption & n_PO_D_V)
               n_err(_("`bind': capability expansion too long: %s: %s\n"),
                  capname, tbcp->tbc_seq);
            tbcp->tbc_flags |= a_TTY_BIND_DEFUNCT;
            break;
         }else if(i == 0){
            if(n_poption & n_PO_D_V)
               n_err(_("`bind': empty capability expansion: %s: %s\n"),
                  capname, tbcp->tbc_seq);
            tbcp->tbc_flags |= a_TTY_BIND_DEFUNCT;
            break;
         }else if(isfirst && !su_cs_is_cntrl(*tv.tv_data.tvd_string)){
            if(n_poption & n_PO_D_V)
               n_err(_("`bind': capability expansion does not start with "
                  "control: %s: %s\n"), capname, tbcp->tbc_seq);
            tbcp->tbc_flags |= a_TTY_BIND_DEFUNCT;
            break;
         }
         UNALIGN(s32*,cp)[-1] = (s32)i;
         su_mem_copy(cp, tv.tv_data.tvd_string, i);
         cp[i] = '\0';
      }
   }
   NYD2_OU;
}

static void
a_tty_bind_del(struct a_tty_bind_parse_ctx *tbpcp){
   struct a_tty_bind_ctx *ltbcp, *tbcp;
   NYD2_IN;

   tbcp = tbpcp->tbpc_tbcp;

   if((ltbcp = tbpcp->tbpc_ltbcp) != NULL)
      ltbcp->tbc_next = tbcp->tbc_next;
   else
      a_tty.tg_bind[tbpcp->tbpc_flags & n__GO_INPUT_CTX_MASK] = tbcp->tbc_next;
   n_free(tbcp);

   --a_tty.tg_bind_cnt;
   a_tty.tg_bind_isdirty = TRU1;
   NYD2_OU;
}

static void
a_tty_bind_tree_build(void){
   uz i;
   NYD2_IN;

   for(i = 0; i < n__GO_INPUT_CTX_MAX1; ++i){
      struct a_tty_bind_ctx *tbcp;
      LCTAV(n_GO_INPUT_CTX_BASE == 0);

      /* Somewhat wasteful, but easier to handle: simply clone the entire
       * primary key onto the secondary one, then only modify it */
      for(tbcp = a_tty.tg_bind[n_GO_INPUT_CTX_BASE]; tbcp != NULL;
            tbcp = tbcp->tbc_next)
         if(!(tbcp->tbc_flags & a_TTY_BIND_DEFUNCT))
            a_tty__bind_tree_add(n_GO_INPUT_CTX_BASE,
               &a_tty.tg_bind_tree[i][0], tbcp);

      if(i != n_GO_INPUT_CTX_BASE)
         for(tbcp = a_tty.tg_bind[i]; tbcp != NULL; tbcp = tbcp->tbc_next)
            if(!(tbcp->tbc_flags & a_TTY_BIND_DEFUNCT))
               a_tty__bind_tree_add(i, &a_tty.tg_bind_tree[i][0], tbcp);
   }

   a_tty.tg_bind_isbuild = TRU1;
   NYD2_OU;
}

static void
a_tty_bind_tree_teardown(void){
   uz i, j;
   NYD2_IN;

   su_mem_set(&a_tty.tg_bind_shcut_cancel[0], 0,
      sizeof(a_tty.tg_bind_shcut_cancel));
   su_mem_set(&a_tty.tg_bind_shcut_prompt_char[0], 0,
      sizeof(a_tty.tg_bind_shcut_prompt_char));

   for(i = 0; i < n__GO_INPUT_CTX_MAX1; ++i)
      for(j = 0; j < a_TTY_PRIME; ++j)
         a_tty__bind_tree_free(a_tty.tg_bind_tree[i][j]);
   su_mem_set(&a_tty.tg_bind_tree[0], 0, sizeof(a_tty.tg_bind_tree));

   a_tty.tg_bind_isdirty = a_tty.tg_bind_isbuild = FAL0;
   NYD2_OU;
}

static void
a_tty__bind_tree_add(u32 hmap_idx,
      struct a_tty_bind_tree *store[a_TTY_PRIME], struct a_tty_bind_ctx *tbcp){
   u32 cnvlen;
   char const *cnvdat;
   struct a_tty_bind_tree *ntbtp;
   NYD2_IN;
   UNUSED(hmap_idx);

   ntbtp = NULL;

   for(cnvdat = tbcp->tbc_cnv, cnvlen = tbcp->tbc_cnv_len; cnvlen > 0;){
      union {wchar_t const *wp; char const *cp;} u;
      s32 entlen;

      /* {s32 buf_len_iscap;} */
      entlen = *UNALIGN(s32 const*,cnvdat);

      if(entlen & S32_MIN){
         /* struct{s32 buf_len_iscap; s32 cap_len; char buf[]+NUL;}
          * Note that empty capabilities result in DEFUNCT */
         for(u.cp = (char const*)&UNALIGN(s32 const*,cnvdat)[2];
               *u.cp != '\0'; ++u.cp)
            ntbtp = a_tty__bind_tree_add_wc(store, ntbtp, *u.cp, TRU1);
         ASSERT(ntbtp != NULL);
         ntbtp->tbt_isseq_trail = TRU1;
         entlen &= S32_MAX;
      }else{
         /* struct{s32 buf_len_iscap; wc_t buf[]+NUL;} */
         boole isseq;

         u.wp = (wchar_t const*)&UNALIGN(s32 const*,cnvdat)[1];

         /* May be a special shortcut function? */
         if(ntbtp == NULL && (tbcp->tbc_flags & a_TTY_BIND_MLE1CNTRL)){
            char *cp;
            u32 ctx, fun;

            ctx = tbcp->tbc_flags & n__GO_INPUT_CTX_MASK;
            fun = tbcp->tbc_flags & a_TTY__BIND_FUN_MASK;

            if(fun == a_TTY_BIND_FUN_CANCEL){
               for(cp = &a_tty.tg_bind_shcut_cancel[ctx][0];
                     PCMP(cp, <, &a_tty.tg_bind_shcut_cancel[ctx]
                        [NELEM(a_tty.tg_bind_shcut_cancel[ctx]) - 1]); ++cp)
                  if(*cp == '\0'){
                     *cp = (char)*u.wp;
                     break;
                  }
            }else if(fun == a_TTY_BIND_FUN_PROMPT_CHAR){
               for(cp = &a_tty.tg_bind_shcut_prompt_char[ctx][0];
                     PCMP(cp, <, &a_tty.tg_bind_shcut_prompt_char[ctx]
                        [NELEM(a_tty.tg_bind_shcut_prompt_char[ctx]) - 1]);
                     ++cp)
                  if(*cp == '\0'){
                     *cp = (char)*u.wp;
                     break;
                  }
            }
         }

         isseq = (u.wp[1] != '\0');
         for(; *u.wp != '\0'; ++u.wp)
            ntbtp = a_tty__bind_tree_add_wc(store, ntbtp, *u.wp, isseq);
         if(isseq){
            ASSERT(ntbtp != NULL);
            ntbtp->tbt_isseq_trail = TRU1;
         }
      }

      cnvlen -= entlen;
      cnvdat += entlen;
   }

   /* Should have been rendered defunctional at first instead */
   ASSERT(ntbtp != NULL);
   ntbtp->tbt_bind = tbcp;
   NYD2_OU;
}

static struct a_tty_bind_tree *
a_tty__bind_tree_add_wc(struct a_tty_bind_tree **treep,
      struct a_tty_bind_tree *parentp, wchar_t wc, boole isseq){
   struct a_tty_bind_tree *tbtp, *xtbtp;
   NYD2_IN;

   if(parentp == NULL){
      treep += wc % a_TTY_PRIME;

      /* Having no parent also means that the tree slot is possibly empty */
      for(tbtp = *treep; tbtp != NULL;
            parentp = tbtp, tbtp = tbtp->tbt_sibling){
         if(tbtp->tbt_char != wc)
            continue;
         if(tbtp->tbt_isseq == isseq)
            goto jleave;
         /* isseq MUST be linked before !isseq, so record this "parent"
          * sibling, but continue searching for now.
          * Otherwise it is impossible that we'll find what we look for */
         if(isseq){
#ifdef mx_HAVE_DEBUG
            while((tbtp = tbtp->tbt_sibling) != NULL)
               ASSERT(tbtp->tbt_char != wc);
#endif
            break;
         }
      }

      tbtp = n_alloc(sizeof *tbtp);
      su_mem_set(tbtp, 0, sizeof *tbtp);
      tbtp->tbt_char = wc;
      tbtp->tbt_isseq = isseq;

      if(parentp == NULL){
         tbtp->tbt_sibling = *treep;
         *treep = tbtp;
      }else{
         tbtp->tbt_sibling = parentp->tbt_sibling;
         parentp->tbt_sibling = tbtp;
      }
   }else{
      if((tbtp = *(treep = &parentp->tbt_childs)) != NULL){
         for(;; tbtp = xtbtp){
            if(tbtp->tbt_char == wc){
               if(tbtp->tbt_isseq == isseq)
                  goto jleave;
               /* isseq MUST be linked before, so it is impossible that we'll
                * find what we look for */
               if(isseq){
#ifdef mx_HAVE_DEBUG
                  while((tbtp = tbtp->tbt_sibling) != NULL)
                     ASSERT(tbtp->tbt_char != wc);
#endif
                  tbtp = NULL;
                  break;
               }
            }

            if((xtbtp = tbtp->tbt_sibling) == NULL){
               treep = &tbtp->tbt_sibling;
               break;
            }
         }
      }

      xtbtp = n_alloc(sizeof *xtbtp);
      su_mem_set(xtbtp, 0, sizeof *xtbtp);
      xtbtp->tbt_parent = parentp;
      xtbtp->tbt_char = wc;
      xtbtp->tbt_isseq = isseq;
      tbtp = xtbtp;
      *treep = tbtp;
   }
jleave:
   NYD2_OU;
   return tbtp;
}

static void
a_tty__bind_tree_free(struct a_tty_bind_tree *tbtp){
   NYD2_IN;
   while(tbtp != NULL){
      struct a_tty_bind_tree *tmp;

      if((tmp = tbtp->tbt_childs) != NULL)
         a_tty__bind_tree_free(tmp);

      tmp = tbtp->tbt_sibling;
      n_free(tbtp);
      tbtp = tmp;
   }
   NYD2_OU;
}
# endif /* mx_HAVE_KEY_BINDINGS */

void
mx_tty_init(void){
   NYD_IN;

   if(ok_blook(line_editor_disable))
      goto jleave;

   /* Load the history file */
# ifdef mx_HAVE_HISTORY
   a_tty_hist_load();
# endif

   /* Force immediate resolve for anything which follows */
   n_psonce |= n_PSO_LINE_EDITOR_INIT;

# ifdef mx_HAVE_KEY_BINDINGS
   /* `bind's (and `unbind's) done from within resource files couldn't be
    * performed for real since our termcap driver wasn't yet loaded, and we
    * can't perform automatic init since the user may have disallowed so */
   /* C99 */{ /* TODO outsource into own file */
      struct a_tty_bind_ctx *tbcp;
      enum n_go_input_flags gif;

      for(gif = 0; gif < n__GO_INPUT_CTX_MAX1; ++gif)
         for(tbcp = a_tty.tg_bind[gif]; tbcp != NULL; tbcp = tbcp->tbc_next)
            if((tbcp->tbc_flags & (a_TTY_BIND_RESOLVE | a_TTY_BIND_DEFUNCT)) ==
                  a_TTY_BIND_RESOLVE)
               a_tty_bind_resolve(tbcp);
   }

   /* And we want to (try to) install some default key bindings */
   if(!ok_blook(line_editor_no_defaults)){
      char buf[8];
      struct a_tty_bind_parse_ctx tbpc;
      struct a_tty_bind_builtin_tuple const *tbbtp, *tbbtp_max;
      u32 flags;

      buf[0] = '$', buf[1] = '\'', buf[2] = '\\', buf[3] = 'c',
         buf[5] = '\'', buf[6] = '\0';

      tbbtp = a_tty_bind_base_tuples;
      tbbtp_max = &tbbtp[NELEM(a_tty_bind_base_tuples)];
      flags = n_GO_INPUT_CTX_BASE;
jbuiltin_redo:
      for(; tbbtp < tbbtp_max; ++tbbtp){
         su_mem_set(&tbpc, 0, sizeof tbpc);
         tbpc.tbpc_cmd = "bind";
         if(tbbtp->tbbt_iskey){
            buf[4] = tbbtp->tbbt_ckey;
            tbpc.tbpc_in_seq = buf;
         }else
            tbpc.tbpc_in_seq = savecatsep(":", '\0',
               mx_termcap_name_of_query(tbbtp->tbbt_query));
         tbpc.tbpc_exp.s = n_UNCONST(tbbtp->tbbt_exp[0] == '\0'
               ? a_tty_bind_fun_names[(u8)tbbtp->tbbt_exp[1]]
               : tbbtp->tbbt_exp);
         tbpc.tbpc_exp.l = su_cs_len(tbpc.tbpc_exp.s);
         tbpc.tbpc_flags = flags;
         /* ..but don't want to overwrite any user settings */
         a_tty_bind_create(&tbpc, FAL0);
      }
      if(flags == n_GO_INPUT_CTX_BASE){
         tbbtp = a_tty_bind_default_tuples;
         tbbtp_max = &tbbtp[NELEM(a_tty_bind_default_tuples)];
         flags = n_GO_INPUT_CTX_DEFAULT;
         goto jbuiltin_redo;
      }
   }
# endif /* mx_HAVE_KEY_BINDINGS */

jleave:
   NYD_OU;
}

void
mx_tty_destroy(boole xit_fastpath){
   NYD_IN;

   if(!(n_psonce & n_PSO_LINE_EDITOR_INIT))
      goto jleave;

  /* Be aware of identical code for `exit' command! */
#ifdef mx_HAVE_TCAP
   if((n_psonce & n_PSO_INTERACTIVE) && !(n_poption & n_PO_QUICKRUN_MASK))
      mx_termcap_destroy();
#endif

   /* Write the history file */
# ifdef mx_HAVE_HISTORY
   if(!xit_fastpath)
      a_tty_hist_save();
# endif

# if defined mx_HAVE_KEY_BINDINGS && defined mx_HAVE_DEBUG
   if(!xit_fastpath)
      n_go_command(n_GO_INPUT_NONE, "unbind * *");
# endif

# ifdef mx_HAVE_DEBUG
   su_mem_set(&a_tty, 0, sizeof a_tty);

   n_psonce &= ~n_PSO_LINE_EDITOR_INIT;
# endif
jleave:
   NYD_OU;
}

int
(mx_tty_readline)(enum n_go_input_flags gif, char const *prompt,
      char **linebuf, uz *linesize, uz n, boole *histok_or_nil
      su_DBG_LOC_ARGS_DECL){
   struct a_tty_line tl;
   struct n_string xprompt;
# ifdef mx_HAVE_COLOUR
   char *posbuf, *pos;
# endif
   sz nn;
   NYD_IN;

   ASSERT(!ok_blook(line_editor_disable));
   if(!(n_psonce & n_PSO_LINE_EDITOR_INIT))
      mx_tty_init();
   ASSERT(n_psonce & n_PSO_LINE_EDITOR_INIT);

# ifdef mx_HAVE_COLOUR
   mx_colour_env_create(mx_COLOUR_CTX_MLE, mx_tty_fp, FAL0);

   /* .tl_pos_buf is a hack */
   posbuf = pos = NULL;

   if(mx_COLOUR_IS_ACTIVE()){
      char const *ccol;
      struct mx_colour_pen *ccp;
      struct str const *s;

      if((ccp = mx_colour_pen_create(mx_COLOUR_ID_MLE_POSITION, NULL)
            ) != NIL && (s = mx_colour_pen_to_str(ccp)) != NIL){
         ccol = s->s;
         if((s = mx_colour_reset_to_str()) != NULL){
            uz l1, l2;

            l1 = su_cs_len(ccol);
            l2 = su_cs_len(s->s);
            posbuf = n_autorec_alloc(l1 + 4 + l2 +1);
            su_mem_copy(posbuf, ccol, l1);
            pos = &posbuf[l1];
            su_mem_copy(&pos[4], s->s, ++l2);
         }
      }
   }

   if(posbuf == NULL){
      posbuf = pos = n_autorec_alloc(4 +1);
      pos[4] = '\0';
   }
# endif /* mx_HAVE_COLOUR */

   su_mem_set(&tl, 0, sizeof tl);
   tl.tl_goinflags = gif;

# ifdef mx_HAVE_KEY_BINDINGS
   /* C99 */{
      char const *cp;

      if((cp = ok_vlook(bind_timeout)) != NULL){
         u64 uib;

         su_idec_u64_cp(&uib, cp, 0, NULL);

         if(uib > 0 &&
               /* Convert to tenths of a second, unfortunately */
               (uib = (uib + 99) / 100) <= a_TTY_BIND_TIMEOUT_MAX)
            tl.tl_bind_timeout = (u8)uib;
         else if(n_poption & n_PO_D_V)
            n_err(_("Ignoring invalid *bind-timeout*: %s\n"), cp);
      }
   }

   if(a_tty.tg_bind_isdirty)
      a_tty_bind_tree_teardown();
   if(a_tty.tg_bind_cnt > 0 && !a_tty.tg_bind_isbuild)
      a_tty_bind_tree_build();
   tl.tl_bind_tree_hmap = &a_tty.tg_bind_tree[gif & n__GO_INPUT_CTX_MASK];
   tl.tl_bind_shcut_cancel =
         &a_tty.tg_bind_shcut_cancel[gif & n__GO_INPUT_CTX_MASK];
   tl.tl_bind_shcut_prompt_char =
         &a_tty.tg_bind_shcut_prompt_char[gif & n__GO_INPUT_CTX_MASK];
# endif /* mx_HAVE_KEY_BINDINGS */

# ifdef mx_HAVE_COLOUR
   tl.tl_pos_buf = posbuf;
   tl.tl_pos = pos;
# endif

   if(!(gif & n_GO_INPUT_PROMPT_NONE)){
      n_string_creat_auto(&xprompt);

      if((tl.tl_prompt_width = mx_tty_create_prompt(&xprompt, prompt,
            gif)) > 0){
         tl.tl_prompt = n_string_cp_const(&xprompt);
         tl.tl_prompt_length = S(u32,xprompt.s_len);
      }
   }

   tl.tl_line.cbuf = *linebuf;
   if(n != 0){
      tl.tl_defc.s = savestrbuf(*linebuf, n);
      tl.tl_defc.l = n;
   }
   tl.tl_x_buf = linebuf;
   tl.tl_x_bufsize = linesize;

   a_tty.tg_line = &tl;
   mx_termios_cmd(mx_TERMIOS_CMD_PUSH | mx_TERMIOS_CMD_RAW, 1);
   mx_termios_on_state_change_set(&a_tty_on_state_change, S(up,NIL));
   mx_TERMCAP_RESUME(FAL0);
   nn = a_tty_readline(&tl, n, histok_or_nil  su_DBG_LOC_ARGS_USE);
   /*mx_COLOUR( mx_colour_env_gut(); )
    *mx_TERMCAP_SUSPEND(FAL0);*/
   mx_termios_cmdx(mx_TERMIOS_CMD_POP | mx_TERMIOS_CMD_RAW);
   a_tty.tg_line = NIL;

   NYD_OU;
   return (int)nn;
}

void
mx_tty_addhist(char const *s, enum n_go_input_flags gif){
   NYD_IN;
   UNUSED(s);
   UNUSED(gif);

# ifdef mx_HAVE_HISTORY
   if(*s != '\0' && (n_psonce & n_PSO_LINE_EDITOR_INIT) &&
         a_tty.tg_hist_size_max > 0 &&
         (!(gif & n_GO_INPUT_HIST_GABBY) || ok_blook(history_gabby)) &&
          !ok_blook(line_editor_disable)){
      struct a_tty_input_ctx_map const *ticmp;

      ticmp = &a_tty_input_ctx_maps[gif & a_TTY_HIST_CTX_MASK];

      /* TODO *on-history-addition*: a future version will give the expanded
       * TODO command name as the third argument, followed by the tokenized
       * TODO command line as parsed in the remaining arguments, the first of
       * TODO which is the original unexpanded command name; i.e., one may do
       * TODO "shift 4" and access the arguments normal via $#, $@ etc. */
      if(temporary_addhist_hook(ticmp->ticm_name,
            ((gif & n_GO_INPUT_HIST_GABBY) != 0), s)){
         mx_sigs_all_holdx();
         a_tty_hist_add(s, gif);
         mx_sigs_all_rele();
      }
   }
# endif
   NYD_OU;
}

# ifdef mx_HAVE_HISTORY
int
c_history(void *v){
   sz entry;
   struct a_tty_hist *thp;
   char **argv;
   NYD_IN;

   if(ok_blook(line_editor_disable)){
      n_err(_("history: *line-editor-disable* is set\n"));
      goto jerr;
   }

   if(!(n_psonce & n_PSO_LINE_EDITOR_INIT)){
      mx_tty_init();
      ASSERT(n_psonce & n_PSO_LINE_EDITOR_INIT);
   }

   if(*(argv = v) == NULL)
      goto jlist;
   if(argv[1] != NULL)
      goto jerr;
   if(!su_cs_cmp_case(*argv, "show"))
      goto jlist;
   if(!su_cs_cmp_case(*argv, "clear"))
      goto jclear;

   if(!su_cs_cmp_case(*argv, "load")){
      if(!a_tty_hist_load())
         v = NULL;
      goto jleave;
   }
   if(!su_cs_cmp_case(*argv, "save")){
      if(!a_tty_hist_save())
         v = NULL;
      goto jleave;
   }

   if((su_idec_sz_cp(&entry, *argv, 10, NULL
            ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
         ) == su_IDEC_STATE_CONSUMED)
      goto jentry;
jerr:
   n_err(_("Synopsis: history: %s\n"),
      /* Same string as in cmd-tab.h, still hoping...) */
      _(" or select history "));
   v = NULL;
jleave:
   NYD_OU;
   return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */

jlist:{
   uz no, l, b;
   FILE *fp;

   if(a_tty.tg_hist == NIL)
      goto jleave;

   if((fp = mx_fs_tmp_open("hist", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
            mx_FS_O_REGISTER), NIL)) == NIL){
      n_perr(_("tmpfile"), 0);
      v = NIL;
      goto jleave;
   }

   no = a_tty.tg_hist_size;
   l = b = 0;

   for(thp = a_tty.tg_hist; thp != NULL;
         --no, ++l, thp = thp->th_older){
      char c1, c2;

      b += thp->th_len;

      switch(thp->th_flags & a_TTY_HIST_CTX_MASK){
      default:
      case a_TTY_HIST_CTX_DEFAULT:
         c1 = 'd';
         break;
      case a_TTY_HIST_CTX_COMPOSE:
         c1 = 'c';
         break;
      }
      c2 = (thp->th_flags & a_TTY_HIST_GABBY) ? '*' : ' ';

      if(n_poption & n_PO_D_V)
         fprintf(fp, "# Length +%" PRIu32 ", total %" PRIuZ "\n",
            thp->th_len, b);
      fprintf(fp, "%c%c%4" PRIuZ "\t%s\n", c1, c2, no, thp->th_dat);
   }

   page_or_print(fp, l);
   mx_fs_close(fp);
   }
   goto jleave;

jclear:
   while((thp = a_tty.tg_hist) != NULL){
      a_tty.tg_hist = thp->th_older;
      n_free(thp);
   }
   a_tty.tg_hist_tail = NULL;
   a_tty.tg_hist_size = 0;
   goto jleave;

jentry:{
   sz ep;

   ep = (entry < 0) ? -entry : entry;

   if(ep != 0 && UCMP(z, ep, <=, a_tty.tg_hist_size)){
      if(ep != entry)
         --ep;
      else
         ep = (sz)a_tty.tg_hist_size - ep;
      for(thp = a_tty.tg_hist;; thp = thp->th_older){
         ASSERT(thp != NULL);
         if(ep-- == 0){
            n_go_input_inject((n_GO_INPUT_INJECT_COMMIT |
               n_GO_INPUT_INJECT_HISTORY), v = thp->th_dat, thp->th_len);
            break;
         }
      }
   }else{
      n_err(_("`history': no such entry: %" PRIdZ "\n"), entry);
      v = NULL;
   }
   }
   goto jleave;
}
# endif /* mx_HAVE_HISTORY */

# ifdef mx_HAVE_KEY_BINDINGS
int
c_bind(void *v){
   /* TODO `bind': since empty expansions are forbidden it would be nice to
    * TODO be able to say "bind base a,b,c" and see the expansion of only
    * TODO that (just like we do for `alias', `commandalias' etc.!) */
   struct a_tty_bind_ctx *tbcp;
   union {char const *cp; char *p; char c;} c;
   boole show, aster;
   enum n_go_input_flags gif;
   struct n_cmd_arg_ctx *cacp;
   NYD_IN;

   cacp = v;
   gif = 0;

   if(cacp->cac_no <= 1)
      aster = show = TRU1;
   else{
      c.cp = cacp->cac_arg->ca_arg.ca_str.s;
      show = !su_cs_cmp_case(cacp->cac_arg->ca_next->ca_arg.ca_str.s, "show");
      aster = FAL0;

      if((gif = a_tty_bind_ctx_find(c.cp)) == (enum n_go_input_flags)-1){
         if(!(aster = n_is_all_or_aster(c.cp)) || !show){
            n_err(_("`bind': invalid context: %s\n"), c.cp);
            v = NULL;
            goto jleave;
         }
         gif = 0;
      }
   }

   if(show){
      u32 lns;
      FILE *fp;

      if((fp = mx_fs_tmp_open("bind", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
               mx_FS_O_REGISTER), NIL)) == NIL){
         n_perr(_("tmpfile"), 0);
         v = NULL;
         goto jleave;
      }

      lns = 0;
      for(;;){
         for(tbcp = a_tty.tg_bind[gif]; tbcp != NULL;
               ++lns, tbcp = tbcp->tbc_next){
            /* Print the bytes of resolved terminal capabilities, then */
            if((n_poption & n_PO_D_V) &&
                  (tbcp->tbc_flags & (a_TTY_BIND_RESOLVE | a_TTY_BIND_DEFUNCT)
                  ) == a_TTY_BIND_RESOLVE){
               char cbuf[8];
               union {wchar_t const *wp; char const *cp;} u;
               s32 entlen;
               u32 cnvlen;
               char const *cnvdat, *bsep, *cbufp;

               putc('#', fp);
               putc(' ', fp);

               cbuf[0] = '=', cbuf[2] = '\0';
               for(cnvdat = tbcp->tbc_cnv, cnvlen = tbcp->tbc_cnv_len;
                     cnvlen > 0;){
                  if(cnvdat != tbcp->tbc_cnv)
                     putc(',', fp);

                  /* {s32 buf_len_iscap;} */
                  entlen = *UNALIGN(s32 const*,cnvdat);
                  if(entlen & S32_MIN){
                     /* struct{s32 buf_len_iscap; s32 cap_len;
                      * char buf[]+NUL;} */
                     for(bsep = n_empty,
                              u.cp = (char const*)
                                    &UNALIGN(s32 const*,cnvdat)[2];
                           (c.c = *u.cp) != '\0'; ++u.cp){
                        if(su_cs_is_ascii(c.c) && !su_cs_is_cntrl(c.c))
                           cbuf[1] = c.c, cbufp = cbuf;
                        else
                           cbufp = n_empty;
                        fprintf(fp, "%s\\x%02X%s",
                           bsep, (u32)(u8)c.c, cbufp);
                        bsep = " ";
                     }
                     entlen &= S32_MAX;
                  }else
                     putc('-', fp);

                  cnvlen -= entlen;
                  cnvdat += entlen;
               }

               fputs("\n  ", fp);
               ++lns;
            }

            fprintf(fp, "%sbind %s %s %s%s%s\n",
               ((tbcp->tbc_flags & a_TTY_BIND_DEFUNCT)
               /* I18N: `bind' sequence not working, either because it is
                * I18N: using Unicode and that is not available in the locale,
                * I18N: or a termcap(5)/terminfo(5) sequence won't work out */
                  ? _("#  ") : n_empty),
               a_tty_input_ctx_maps[gif].ticm_name, tbcp->tbc_seq,
               n_shexp_quote_cp(tbcp->tbc_exp, TRU1),
               (tbcp->tbc_flags & a_TTY_BIND_NOCOMMIT ? n_at : n_empty),
               (!(n_poption & n_PO_D_VV) ? n_empty
                  : (tbcp->tbc_flags & a_TTY_BIND_FUN_INTERNAL
                     ? _(" # MLE internal") : n_empty))
               );
         }
         if(!aster || ++gif >= n__GO_INPUT_CTX_MAX1)
            break;
      }
      page_or_print(fp, lns);

      mx_fs_close(fp);
   }else{
      struct a_tty_bind_parse_ctx tbpc;
      struct n_cmd_arg *cap;

      su_mem_set(&tbpc, 0, sizeof tbpc);
      tbpc.tbpc_cmd = cacp->cac_desc->cad_name;
      tbpc.tbpc_in_seq = (cap = cacp->cac_arg->ca_next)->ca_arg.ca_str.s;
      if((cap = cap->ca_next) != NULL){
         tbpc.tbpc_exp.s = cap->ca_arg.ca_str.s;
         tbpc.tbpc_exp.l = cap->ca_arg.ca_str.l;
      }
      tbpc.tbpc_flags = gif;
      if(!a_tty_bind_create(&tbpc, TRU1))
         v = NULL;
   }
jleave:
   NYD_OU;
   return (v != NULL) ? n_EXIT_OK : n_EXIT_ERR;
}

int
c_unbind(void *v){
   struct a_tty_bind_parse_ctx tbpc;
   struct a_tty_bind_ctx *tbcp;
   enum n_go_input_flags gif;
   boole aster;
   union {char const *cp; char *p;} c;
   struct n_cmd_arg_ctx *cacp;
   NYD_IN;

   cacp = v;
   c.cp = cacp->cac_arg->ca_arg.ca_str.s;
   aster = FAL0;

   if((gif = a_tty_bind_ctx_find(c.cp)) == (enum n_go_input_flags)-1){
      if(!(aster = n_is_all_or_aster(c.cp))){
         n_err(_("`unbind': invalid context: %s\n"), c.cp);
         v = NULL;
         goto jleave;
      }
      gif = 0;
   }

   c.cp = cacp->cac_arg->ca_next->ca_arg.ca_str.s;
jredo:
   if(n_is_all_or_aster(c.cp)){
      while((tbcp = a_tty.tg_bind[gif]) != NULL){
         su_mem_set(&tbpc, 0, sizeof tbpc);
         tbpc.tbpc_tbcp = tbcp;
         tbpc.tbpc_flags = gif;
         a_tty_bind_del(&tbpc);
      }
   }else{
      su_mem_set(&tbpc, 0, sizeof tbpc);
      tbpc.tbpc_cmd = cacp->cac_desc->cad_name;
      tbpc.tbpc_in_seq = c.cp;
      tbpc.tbpc_flags = gif;

      if(UNLIKELY(!a_tty_bind_parse(FAL0, &tbpc)))
         v = NULL;
      else if(UNLIKELY((tbcp = tbpc.tbpc_tbcp) == NULL)){
         n_err(_("`unbind': no such `bind'ing: %s  %s\n"),
            a_tty_input_ctx_maps[gif].ticm_name, c.cp);
         v = NULL;
      }else
         a_tty_bind_del(&tbpc);
   }

   if(aster && ++gif < n__GO_INPUT_CTX_MAX1)
      goto jredo;
jleave:
   NYD_OU;
   return (v != NULL) ? n_EXIT_OK : n_EXIT_ERR;
}
# endif /* mx_HAVE_KEY_BINDINGS */

#else /* mx_HAVE_MLE */
/*
 * The really-nothing-at-all implementation
 */

# if 0
void
mx_tty_init(void){
   NYD_IN;
   NYD_OU;
}

void
mx_tty_destroy(boole xit_fastpath){
   NYD_IN;
   UNUSED(xit_fastpath);
   NYD_OU;
}
# endif /* 0 */

int
(mx_tty_readline)(enum n_go_input_flags gif, char const *prompt,
      char **linebuf, uz *linesize, uz n, boole *histok_or_nil
      su_DBG_LOC_ARGS_DECL){
   struct n_string xprompt;
   int rv;
   NYD_IN;
   UNUSED(histok_or_nil);

   if(!(gif & n_GO_INPUT_PROMPT_NONE)){
      if(mx_tty_create_prompt(n_string_creat_auto(&xprompt), prompt, gif) > 0){
         fwrite(xprompt.s_dat, 1, xprompt.s_len, mx_tty_fp);
         fflush(mx_tty_fp);
      }
   }

   rv = (readline_restart)(n_stdin, linebuf, linesize, n  su_DBG_LOC_ARGS_USE);
   NYD_OU;
   return rv;
}

void
mx_tty_addhist(char const *s, enum n_go_input_flags gif){
   NYD_IN;
   UNUSED(s);
   UNUSED(gif);
   NYD_OU;
}
#endif /* nothing at all */

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/mx/ui-str.c000066400000000000000000000363341352610246600156760ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ Visual strings, string classification/preparation for the user interface.
 *
 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: BSD-3-Clause TODO ISC
 */
/*
 * 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.
 */
#undef su_FILE
#define su_FILE ui_str
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

#ifdef mx_HAVE_NL_LANGINFO
# include 
#endif
#ifdef mx_HAVE_SETLOCALE
# include 
#endif

#include 
#include 
#include 

#include "mx/ui-str.h"

/* TODO fake */
#include "su/code-in.h"

#ifdef mx_HAVE_NATCH_CHAR
struct a_uis_bidi_info{
   struct str bi_start; /* Start of (possibly) bidirectional text */
   struct str bi_end;   /* End of ... */
   uz bi_pad;           /* No of visual columns to reserve for BIDI pad */
};
#endif

#ifdef mx_HAVE_NATCH_CHAR
/* Check whether bidirectional info maybe needed for blen bytes of bdat */
static boole a_uis_bidi_info_needed(char const *bdat, uz blen);

/* Create bidirectional text encapsulation info; without mx_HAVE_NATCH_CHAR
 * the strings are always empty */
static void a_uis_bidi_info_create(struct a_uis_bidi_info *bip);
#endif

#ifdef mx_HAVE_NATCH_CHAR
static boole
a_uis_bidi_info_needed(char const *bdat, uz blen)
{
   boole rv = FAL0;
   NYD_IN;

   if (n_psonce & n_PSO_UNICODE)
      while (blen > 0) {
         /* TODO Checking for BIDI character: use S-CText fromutf8
          * TODO plus isrighttoleft (or whatever there will be)! */
         u32 c = su_utf8_to_32(&bdat, &blen);
         if (c == U32_MAX)
            break;

         if (c <= 0x05BE)
            continue;

         /* (Very very fuzzy, awaiting S-CText for good) */
         if ((c >= 0x05BE && c <= 0x08E3) ||
               (c >= 0xFB1D && c <= 0xFE00) /* No: variation selectors */ ||
               (c >= 0xFE70 && c <= 0xFEFC) ||
               (c >= 0x10800 && c <= 0x10C48) ||
               (c >= 0x1EE00 && c <= 0x1EEF1)) {
            rv = TRU1;
            break;
         }
      }
   NYD_OU;
   return rv;
}

static void
a_uis_bidi_info_create(struct a_uis_bidi_info *bip)
{
   /* Unicode: how to isolate RIGHT-TO-LEFT scripts via *headline-bidi*
    * 1.1 (Jun 1993): U+200E (E2 80 8E) LEFT-TO-RIGHT MARK
    * 6.3 (Sep 2013): U+2068 (E2 81 A8) FIRST STRONG ISOLATE,
    *                 U+2069 (E2 81 A9) POP DIRECTIONAL ISOLATE
    * Worse results seen for: U+202D "\xE2\x80\xAD" U+202C "\xE2\x80\xAC" */
   n_NATCH_CHAR( char const *hb; )
   NYD_IN;

   su_mem_set(bip, 0, sizeof *bip);
   bip->bi_start.s = bip->bi_end.s = n_UNCONST(n_empty);

   if ((n_psonce & n_PSO_UNICODE) && (hb = ok_vlook(headline_bidi)) != NULL) {
      switch (*hb) {
      case '3':
         bip->bi_pad = 2;
         /* FALLTHRU */
      case '2':
         bip->bi_start.s = bip->bi_end.s = n_UNCONST("\xE2\x80\x8E");
         break;
      case '1':
         bip->bi_pad = 2;
         /* FALLTHRU */
      default:
         bip->bi_start.s = n_UNCONST("\xE2\x81\xA8");
         bip->bi_end.s = n_UNCONST("\xE2\x81\xA9");
         break;
      }
      bip->bi_start.l = bip->bi_end.l = 3;
   }
   NYD_OU;
}
#endif /* mx_HAVE_NATCH_CHAR */

void
n_locale_init(void){
   NYD2_IN;

   n_psonce &= ~(n_PSO_UNICODE | n_PSO_ENC_MBSTATE);

#ifndef mx_HAVE_SETLOCALE
   n_mb_cur_max = 1;
#else
   setlocale(LC_ALL, n_empty);
   n_mb_cur_max = MB_CUR_MAX;
# ifdef mx_HAVE_NL_LANGINFO
   /* C99 */{
      char const *cp;

      if((cp = nl_langinfo(CODESET)) != NULL)
         /* (Will log during startup if user set that via -S) */
         ok_vset(ttycharset, cp);
   }
# endif /* mx_HAVE_SETLOCALE */

# ifdef mx_HAVE_C90AMEND1
   if(n_mb_cur_max > 1){
#  ifdef mx_HAVE_ALWAYS_UNICODE_LOCALE
      n_psonce |= n_PSO_UNICODE;
#  else
      wchar_t wc;
      if(mbtowc(&wc, "\303\266", 2) == 2 && wc == 0xF6 &&
            mbtowc(&wc, "\342\202\254", 3) == 3 && wc == 0x20AC)
         n_psonce |= n_PSO_UNICODE;
      /* Reset possibly messed up state; luckily this also gives us an
       * indication whether the encoding has locking shift state sequences */
      if(mbtowc(&wc, NULL, n_mb_cur_max))
         n_psonce |= n_PSO_ENC_MBSTATE;
#  endif
   }
# endif
#endif /* mx_HAVE_C90AMEND1 */
   NYD2_OU;
}

boole
n_visual_info(struct n_visual_info_ctx *vicp, enum n_visual_info_flags vif){
#ifdef mx_HAVE_C90AMEND1
   mbstate_t *mbp;
#endif
   uz il;
   char const *ib;
   boole rv;
   NYD2_IN;

   ASSERT(vicp != NULL);
   ASSERT(vicp->vic_inlen == 0 || vicp->vic_indat != NULL);
   ASSERT(!(vif & n__VISUAL_INFO_FLAGS) || !(vif & n_VISUAL_INFO_ONE_CHAR));

   rv = TRU1;
   ib = vicp->vic_indat;
   if((il = vicp->vic_inlen) == UZ_MAX)
      il = vicp->vic_inlen = su_cs_len(ib);

   if((vif & (n_VISUAL_INFO_WIDTH_QUERY | n_VISUAL_INFO_WOUT_PRINTABLE)) ==
         n_VISUAL_INFO_WOUT_PRINTABLE)
      vif |= n_VISUAL_INFO_WIDTH_QUERY;

   vicp->vic_chars_seen = vicp->vic_bytes_seen = vicp->vic_vi_width = 0;
   if(vif & n_VISUAL_INFO_WOUT_CREATE){
      if(vif & n_VISUAL_INFO_WOUT_SALLOC)
         vicp->vic_woudat =
               n_autorec_alloc(sizeof(*vicp->vic_woudat) * (il +1));
      vicp->vic_woulen = 0;
   }
#ifdef mx_HAVE_C90AMEND1
   if((mbp = vicp->vic_mbstate) == NULL)
      mbp = &vicp->vic_mbs_def;
#endif

   if(il > 0){
      do/* while(!(vif & n_VISUAL_INFO_ONE_CHAR) && il > 0) */{
#ifdef mx_HAVE_C90AMEND1
         uz i = mbrtowc(&vicp->vic_waccu, ib, il, mbp);

         if(i == (uz)-2){
            rv = FAL0;
            break;
         }else if(i == (uz)-1){
            if(!(vif & n_VISUAL_INFO_SKIP_ERRORS)){
               rv = FAL0;
               break;
            }
            su_mem_set(mbp, 0, sizeof *mbp);
            vicp->vic_waccu = (n_psonce & n_PSO_UNICODE) ? 0xFFFD : '?';
            i = 1;
         }else if(i == 0){
            il = 0;
            break;
         }

         ++vicp->vic_chars_seen;
         vicp->vic_bytes_seen += i;
         ib += i;
         il -= i;

         if(vif & n_VISUAL_INFO_WIDTH_QUERY){
            int w;
            wchar_t wc = vicp->vic_waccu;

# ifdef mx_HAVE_WCWIDTH
            w = (wc == '\t' ? 1 : wcwidth(wc));
# else
            if(wc == '\t' || iswprint(wc))
               w = 1 + (wc >= 0x1100u); /* S-CText isfullwidth() */
            else
               w = -1;
# endif
            if(w > 0)
               vicp->vic_vi_width += w;
            else if(vif & n_VISUAL_INFO_WOUT_PRINTABLE)
               continue;
         }
#else /* mx_HAVE_C90AMEND1 */
         char c = *ib;

         if(c == '\0'){
            il = 0;
            break;
         }

         ++vicp->vic_chars_seen;
         ++vicp->vic_bytes_seen;
         vicp->vic_waccu = c;
         if(vif & n_VISUAL_INFO_WIDTH_QUERY)
            vicp->vic_vi_width += (c == '\t' || su_cs_is_print(c)); /* XXX */

         ++ib;
         --il;
#endif

         if(vif & n_VISUAL_INFO_WOUT_CREATE)
            vicp->vic_woudat[vicp->vic_woulen++] = vicp->vic_waccu;
      }while(!(vif & n_VISUAL_INFO_ONE_CHAR) && il > 0);
   }

   if(vif & n_VISUAL_INFO_WOUT_CREATE)
      vicp->vic_woudat[vicp->vic_woulen] = L'\0';
   vicp->vic_oudat = ib;
   vicp->vic_oulen = il;
   vicp->vic_flags = vif;
   NYD2_OU;
   return rv;
}

uz
field_detect_clip(uz maxlen, char const *buf, uz blen)/*TODO mbrtowc()*/
{
   uz rv;
   NYD_IN;

#ifdef mx_HAVE_NATCH_CHAR
   maxlen = MIN(maxlen, blen);
   for (rv = 0; maxlen > 0;) {
      int ml = mblen(buf, maxlen);
      if (ml <= 0) {
         mblen(NULL, 0);
         break;
      }
      buf += ml;
      rv += ml;
      maxlen -= ml;
   }
#else
   rv = MIN(blen, maxlen);
#endif
   NYD_OU;
   return rv;
}

char *
colalign(char const *cp, int col, int fill, int *cols_decr_used_or_nil)
{
   n_NATCH_CHAR( struct a_uis_bidi_info bi; )
   int col_orig = col, n, size;
   boole isbidi, isuni, istab, isrepl;
   char *nb, *np;
   NYD_IN;

   /* Bidi only on request and when there is 8-bit data */
   isbidi = isuni = FAL0;
#ifdef mx_HAVE_NATCH_CHAR
   isuni = ((n_psonce & n_PSO_UNICODE) != 0);
   a_uis_bidi_info_create(&bi);
   if (bi.bi_start.l == 0)
      goto jnobidi;
   if (!(isbidi = a_uis_bidi_info_needed(cp, su_cs_len(cp))))
      goto jnobidi;

   if (S(uz,col) >= bi.bi_pad)
      col -= bi.bi_pad;
   else
      col = 0;
jnobidi:
#endif

   np = nb = n_autorec_alloc(n_mb_cur_max * su_cs_len(cp) +
         ((fill ? col : 0)
         n_NATCH_CHAR( + (isbidi ? bi.bi_start.l + bi.bi_end.l : 0) )
         +1));

#ifdef mx_HAVE_NATCH_CHAR
   if (isbidi) {
      su_mem_copy(np, bi.bi_start.s, bi.bi_start.l);
      np += bi.bi_start.l;
   }
#endif

   while (*cp != '\0') {
      istab = FAL0;
#ifdef mx_HAVE_C90AMEND1
      if (n_mb_cur_max > 1) {
         wchar_t  wc;

         n = 1;
         isrepl = TRU1;
         if ((size = mbtowc(&wc, cp, n_mb_cur_max)) == -1)
            size = 1;
         else if (wc == L'\t') {
            cp += size - 1; /* Silly, charset unknown (.. until S-Ctext) */
            isrepl = FAL0;
            istab = TRU1;
         } else if (iswprint(wc)) {
# ifndef mx_HAVE_WCWIDTH
            n = 1 + (wc >= 0x1100u); /* TODO use S-CText isfullwidth() */
# else
            if ((n = wcwidth(wc)) == -1)
               n = 1;
            else
# endif
               isrepl = FAL0;
         }
      } else
#endif
      {
         n = size = 1;
         istab = (*cp == '\t');
         isrepl = !(istab || su_cs_is_print((uc)*cp));
      }

      if (n > col)
         break;
      col -= n;

      if (isrepl) {
         if (isuni) {
            /* Contained in n_mb_cur_max, then */
            su_mem_copy(np, su_utf8_replacer,
               sizeof(su_utf8_replacer) -1);
            np += sizeof(su_utf8_replacer) -1;
         } else
            *np++ = '?';
         cp += size;
      } else if (istab || (size == 1 && su_cs_is_space(*cp))) {
         *np++ = ' ';
         ++cp;
      } else
         while (size--)
            *np++ = *cp++;
   }

   if (fill && col != 0) {
      if (fill > 0) {
         su_mem_move(nb + col, nb, P2UZ(np - nb));
         su_mem_set(nb, ' ', col);
      } else
         su_mem_set(np, ' ', col);
      np += col;
      col = 0;
   }

#ifdef mx_HAVE_NATCH_CHAR
   if (isbidi) {
      su_mem_copy(np, bi.bi_end.s, bi.bi_end.l);
      np += bi.bi_end.l;
   }
#endif

   *np = '\0';
   if (cols_decr_used_or_nil != NIL)
      *cols_decr_used_or_nil -= col_orig - col;
   NYD_OU;
   return nb;
}

void
makeprint(struct str const *in, struct str *out) /* TODO <-> TTYCHARSET!! */
{
   /* TODO: makeprint() should honour *ttycharset*.  This of course does not
    * TODO work with ISO C / POSIX since mbrtowc() do know about locales, not
    * TODO charsets, and ditto iswprint() etc. do work with the locale too.
    * TODO I hope S-CText can do something about that, and/or otherwise add
    * TODO some special treatment for UTF-8 (take it from S-CText too then) */
   char const *inp, *maxp;
   char *outp;
   DBG( uz msz; )
   NYD_IN;

   out->s =
   outp = n_alloc(su_DBG( msz = ) in->l*n_mb_cur_max + 2u*n_mb_cur_max +1);
   inp = in->s;
   maxp = inp + in->l;

#ifdef mx_HAVE_NATCH_CHAR
   if (n_mb_cur_max > 1) {
      char mbb[MB_LEN_MAX + 1];
      wchar_t wc;
      int i, n;
      boole isuni = ((n_psonce & n_PSO_UNICODE) != 0);

      out->l = 0;
      while (inp < maxp) {
         if (*inp & 0200)
            n = mbtowc(&wc, inp, P2UZ(maxp - inp));
         else {
            wc = *inp;
            n = 1;
         }
         if (n == -1) {
            /* FIXME Why mbtowc() resetting here?
             * FIXME what about ISO 2022-JP plus -- those
             * FIXME will loose shifts, then!
             * FIXME THUS - we'd need special "known points"
             * FIXME to do so - say, after a newline!!
             * FIXME WE NEED TO CHANGE ALL USES +MBLEN! */
            mbtowc(&wc, NULL, n_mb_cur_max);
            wc = isuni ? 0xFFFD : '?';
            n = 1;
         } else if (n == 0)
            n = 1;
         inp += n;
         if (!iswprint(wc) && wc != '\n' /*&& wc != '\r' && wc != '\b'*/ &&
               wc != '\t') {
            if ((wc & ~(wchar_t)037) == 0)
               wc = isuni ? 0x2400 | wc : '?';
            else if (wc == 0177)
               wc = isuni ? 0x2421 : '?';
            else
               wc = isuni ? 0x2426 : '?';
         }else if(isuni){ /* TODO ctext */
            /* Need to filter out L-TO-R and R-TO-R marks TODO ctext */
            if(wc == 0x200E || wc == 0x200F || (wc >= 0x202A && wc <= 0x202E))
               continue;
            /* And some zero-width messes */
            if(wc == 0x00AD || (wc >= 0x200B && wc <= 0x200D))
               continue;
            /* Oh about the ISO C wide character interfaces, baby! */
            if(wc == 0xFEFF)
               continue;
         }
         if ((n = wctomb(mbb, wc)) <= 0)
            continue;
         out->l += n;
         ASSERT(out->l < msz);
         for (i = 0; i < n; ++i)
            *outp++ = mbb[i];
      }
   } else
#endif /* NATCH_CHAR */
   {
      int c;
      while (inp < maxp) {
         c = *inp++ & 0377;
         if (!su_cs_is_print(c) &&
               c != '\n' && c != '\r' && c != '\b' && c != '\t')
            c = '?';
         *outp++ = c;
      }
      out->l = in->l;
   }
   out->s[out->l] = '\0';
   NYD_OU;
}

uz
delctrl(char *cp, uz len)
{
   uz x, y;
   NYD_IN;

   for (x = y = 0; x < len; ++x)
      if (!su_cs_is_cntrl(cp[x]))
         cp[y++] = cp[x];
   cp[y] = '\0';
   NYD_OU;
   return y;
}

char *
prstr(char const *s)
{
   struct str in, out;
   char *rp;
   NYD_IN;

   in.s = n_UNCONST(s);
   in.l = su_cs_len(s);
   makeprint(&in, &out);
   rp = savestrbuf(out.s, out.l);
   n_free(out.s);
   NYD_OU;
   return rp;
}

int
prout(char const *s, uz size, FILE *fp)
{
   struct str in, out;
   int n;
   NYD_IN;

   in.s = n_UNCONST(s);
   in.l = size;
   makeprint(&in, &out);
   n = fwrite(out.s, 1, out.l, fp);
   n_free(out.s);
   NYD_OU;
   return n;
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/mx/url.c000066400000000000000000000456571352610246600152650ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ Implementation of url.h.
 *
 * Copyright (c) 2014 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE url
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

#include 
#include 

#ifdef mx_HAVE_NET
# include 
#endif

#include "mx/cred-auth.h"
#include "mx/cred-netrc.h"
#include "mx/file-streams.h"
#include "mx/ui-str.h"

#include "mx/url.h"
/* TODO fake */
#include "su/code-in.h"

/* Find the last @ before a slash
 * TODO Casts off the const but this is ok here; obsolete function! */
#ifdef mx_HAVE_NET /* temporary (we'll have file://..) */
static char *a_url_last_at_before_slash(char const *cp);
#endif

#ifdef mx_HAVE_NET
static char *
a_url_last_at_before_slash(char const *cp){
   char const *xcp;
   char c;
   NYD2_IN;

   for(xcp = cp; (c = *xcp) != '\0'; ++xcp)
      if(c == '/')
         break;
   while(xcp > cp && *--xcp != '@')
      ;
   if(*xcp != '@')
      xcp = NIL;

   NYD2_OU;
   return UNCONST(char*,xcp);
}
#endif

char *
(mx_url_xenc)(char const *cp, boole ispath  su_DBG_LOC_ARGS_DECL){
   char *n, *np, c1;
   NYD2_IN;

   /* C99 */{
      uz i;

      i = su_cs_len(cp);
      if(i >= UZ_MAX / 3){
         n = NIL;
         goto jleave;
      }
      i *= 3;
      ++i;
      np = n = su_MEM_BAG_SELF_AUTO_ALLOC_LOCOR(i, su_DBG_LOC_ARGS_ORUSE);
   }

   for(; (c1 = *cp) != '\0'; ++cp){
      /* (RFC 1738) RFC 3986, 2.3 Unreserved Characters:
       *    ALPHA / DIGIT / "-" / "." / "_" / "~"
       * However add a special is[file]path mode for file-system friendliness */
      if(su_cs_is_alnum(c1) || c1 == '_')
         *np++ = c1;
      else if(!ispath) {
         if(c1 != '-' && c1 != '.' && c1 != '~')
            goto jesc;
         *np++ = c1;
      }else if(PCMP(np, >, n) && (*cp == '-' || *cp == '.')) /* XXX imap */
         *np++ = c1;
      else{
jesc:
         np[0] = '%';
         n_c_to_hex_base16(np + 1, c1);
         np += 3;
      }
   }
   *np = '\0';

jleave:
   NYD2_OU;
   return n;
}

char *
(mx_url_xdec)(char const *cp  su_DBG_LOC_ARGS_DECL){
   char *n, *np;
   s32 c;
   NYD2_IN;

   np = n = su_MEM_BAG_SELF_AUTO_ALLOC_LOCOR(su_cs_len(cp) +1,
         su_DBG_LOC_ARGS_ORUSE);

   while((c = S(uc,*cp++)) != '\0'){
      if(c == '%' && cp[0] != '\0' && cp[1] != '\0'){
         s32 o = c;
         if(LIKELY((c = n_c_from_hex_base16(cp)) >= '\0'))
            cp += 2;
         else
            c = o;
      }
      *np++ = S(char,c);
   }
   *np = '\0';

   NYD2_OU;
   return n;
}

int
c_urlcodec(void *vp){
   boole ispath;
   uz alen;
   char const **argv, *varname, *varres, *act, *cp;
   NYD_IN;

   argv = vp;
   varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NIL;

   act = *argv;
   for(cp = act; *cp != '\0' && !su_cs_is_space(*cp); ++cp)
      ;
   if((ispath = (*act == 'p'))){
      if(!su_cs_cmp_case_n(++act, "ath", 3))
         act += 3;
   }
   if(act >= cp)
      goto jesynopsis;
   alen = P2UZ(cp - act);
   if(*cp != '\0')
      ++cp;

   n_pstate_err_no = su_ERR_NONE;

   if(su_cs_starts_with_case_n("encode", act, alen))
      varres = mx_url_xenc(cp, ispath);
   else if(su_cs_starts_with_case_n("decode", act, alen))
      varres = mx_url_xdec(cp);
   else
      goto jesynopsis;

   if(varres == NIL){
      n_pstate_err_no = su_ERR_CANCELED;
      varres = cp;
      vp = NIL;
   }

   if(varname != NIL){
      if(!n_var_vset(varname, (up)varres)){
         n_pstate_err_no = su_ERR_NOTSUP;
         cp = NIL;
      }
   }else{
      struct str in, out;

      in.l = su_cs_len(in.s = n_UNCONST(varres));
      makeprint(&in, &out);
      if(fprintf(n_stdout, "%s\n", out.s) < 0){
         n_pstate_err_no = su_err_no();
         vp = NIL;
      }
      n_free(out.s);
   }

jleave:
   NYD_OU;
   return (vp != NIL ? 0 : 1);
jesynopsis:
   n_err(_("Synopsis: urlcodec: "
      "<[path]e[ncode]|[path]d[ecode]> \n"));
   n_pstate_err_no = su_ERR_INVAL;
   vp = NIL;
   goto jleave;
}

char *
mx_url_mailto_to_address(char const *mailtop){ /* TODO hack RFC6068 factory? */
   uz i;
   char *rv;
   char const *mailtop_orig;
   NYD_IN;

   if(!su_cs_starts_with(mailtop_orig = mailtop, "mailto:")){
      rv = NIL;
      goto jleave;
   }
   mailtop += sizeof("mailto:") -1;

   /* TODO This is all intermediate, and for now just enough to understand
    * TODO a little bit of a little more advanced List-Post: headers. */
   /* Strip any hfield additions, keep only to addr-spec's */
   if((rv = su_cs_find_c(mailtop, '?')) != NIL)
      rv = savestrbuf(mailtop, i = P2UZ(rv - mailtop));
   else
      rv = savestrbuf(mailtop, i = su_cs_len(mailtop));

   i = su_cs_len(rv);

   /* Simply perform percent-decoding if there is a percent % */
   if(su_mem_find(rv, '%', i) != NIL){
      char *rv_base;
      boole err;

      for(err = FAL0, mailtop = rv_base = rv; i > 0;){
         char c;

         if((c = *mailtop++) == '%'){
            s32 cc;

            if(i < 3 || (cc = n_c_from_hex_base16(mailtop)) < 0){
               if(!err && (err = TRU1, n_poption & n_PO_D_V))
                  n_err(_("Invalid RFC 6068 'mailto' URL: %s\n"),
                     n_shexp_quote_cp(mailtop_orig, FAL0));
               goto jhex_putc;
            }
            *rv++ = S(char,cc);
            mailtop += 2;
            i -= 3;
         }else{
jhex_putc:
            *rv++ = c;
            --i;
         }
      }
      *rv = '\0';
      rv = rv_base;
   }

jleave:
   NYD_OU;
   return rv;
}

char const *
mx_url_servbyname(char const *proto, u16 *port_or_nil, boole *issnd_or_nil){
   static struct{
      char const name[14];
      char const port[7];
      boole issnd;
      u16 portno;
   } const tbl[] = {
      { "smtp", "25", TRU1, 25},
      { "smtps", "465", TRU1, 465},
      { "submission", "587", TRU1, 587},
      { "submissions", "465", TRU1, 465},
      { "pop3", "110", FAL0, 110},
      { "pop3s", "995", FAL0, 995},
      { "imap", "143", FAL0, 143},
      { "imaps", "993", FAL0, 993},
      { "file", "", TRU1, 0},
      { "test", "", TRU1, U16_MAX}
   };
   char const *rv;
   uz l, i;
   NYD2_IN;

   for(rv = proto; *rv != '\0'; ++rv)
      if(*rv == ':')
         break;
   l = P2UZ(rv - proto);

   for(rv = NIL, i = 0; i < NELEM(tbl); ++i)
      if(!su_cs_cmp_case_n(tbl[i].name, proto, l)){
         rv = tbl[i].port;
         if(port_or_nil != NIL)
            *port_or_nil = tbl[i].portno;
         if(issnd_or_nil != NIL)
            *issnd_or_nil = tbl[i].issnd;
         break;
      }
   NYD2_OU;
   return rv;
}

#ifdef mx_HAVE_NET /* Note: not indented for that -- later: file:// etc.! */
boole
mx_url_parse(struct mx_url *urlp, enum cproto cproto, char const *data){
#if defined mx_HAVE_SMTP && defined mx_HAVE_POP3 && defined mx_HAVE_IMAP
# define a_ALLPROTO
#endif
#if defined mx_HAVE_SMTP || defined mx_HAVE_POP3 || defined mx_HAVE_IMAP || \
      defined mx_HAVE_TLS
# define a_ANYPROTO
   char *cp, *x;
#endif
   boole rv;
   NYD_IN;
   UNUSED(data);

   su_mem_set(urlp, 0, sizeof *urlp);
   urlp->url_input = data;
   urlp->url_cproto = cproto;

   rv = FAL0;

   /* Network protocol */
#define a_PROTOX(X,Y,Z) \
   urlp->url_portno = Y;\
   su_mem_copy(urlp->url_proto, X "://\0", sizeof(X "://\0"));\
   urlp->url_proto[sizeof(X) -1] = '\0';\
   urlp->url_proto_len = sizeof(X) -1;\
   do{ Z; }while(0)
#define a_PRIVPROTOX(X,Y,Z) \
   do{ a_PROTOX(X, Y, Z); }while(0)
#define a__IF(X,Y,Z)  \
   if(!su_cs_cmp_case_n(data, X "://", sizeof(X "://") -1)){\
      a_PROTOX(X, Y, Z);\
      data += sizeof(X "://") -1;\
      goto juser;\
   }
#define a_IF(X,Y) a__IF(X, Y, (void)0)
#ifdef mx_HAVE_TLS
# define a_IFS(X,Y) a__IF(X, Y, urlp->url_flags |= mx_URL_TLS_REQUIRED)
# define a_IFs(X,Y) a__IF(X, Y, urlp->url_flags |= mx_URL_TLS_OPTIONAL)
#else
# define a_IFS(X,Y) goto jeproto;
# define a_IFs(X,Y) a_IF(X, Y)
#endif

   switch(cproto){
   case CPROTO_CERTINFO:
      /* The special `tls' certificate info protocol
       * We do allow all protos here, for later getaddrinfo() usage! */
#ifdef mx_HAVE_TLS
      if((cp = su_cs_find(data, "://")) == NIL)
         a_PRIVPROTOX("https", 443, urlp->url_flags |= mx_URL_TLS_REQUIRED);
      else{
         uz i;

         if((i = P2UZ(&cp[sizeof("://") -1] - data)) + 2 >=
               sizeof(urlp->url_proto))
            goto jeproto;
         su_mem_copy(urlp->url_proto, data, i);
         data += i;
         i -= sizeof("://") -1;
         urlp->url_proto[i] = '\0';\
         urlp->url_proto_len = i;
         urlp->url_flags |= mx_URL_TLS_REQUIRED;
      }
      break;
#else
      goto jeproto;
#endif
   case CPROTO_CCRED:
      /* The special S/MIME etc. credential lookup TODO TLS client cert! */
#ifdef mx_HAVE_TLS
      a_PRIVPROTOX("ccred", 0, (void)0);
      break;
#else
      goto jeproto;
#endif
   case CPROTO_SOCKS:
      a_IF("socks5", 1080);
      a_IF("socks", 1080);
      a_PROTOX("socks", 1080, (void)0);
      break;
   case CPROTO_SMTP:
#ifdef mx_HAVE_SMTP
      a_IFS("smtps", 465)
      a_IFs("smtp", 25)
      a_IFs("submission", 587)
      a_IFS("submissions", 465)
      a_PROTOX("smtp", 25, urlp->url_flags |= mx_URL_TLS_OPTIONAL);
      break;
#else
      goto jeproto;
#endif
   case CPROTO_POP3:
#ifdef mx_HAVE_POP3
      a_IFS("pop3s", 995)
      a_IFs("pop3", 110)
      a_PROTOX("pop3", 110, urlp->url_flags |= mx_URL_TLS_OPTIONAL);
      break;
#else
      goto jeproto;
#endif
#ifdef mx_HAVE_IMAP
   case CPROTO_IMAP:
      a_IFS("imaps", 993)
      a_IFs("imap", 143)
      a_PROTOX("imap", 143, urlp->url_flags |= mx_URL_TLS_OPTIONAL);
      break;
#else
      goto jeproto;
#endif
   }

#undef a_PRIVPROTOX
#undef a_PROTOX
#undef a__IF
#undef a_IF
#undef a_IFS
#undef a_IFs

   if(su_cs_find(data, "://") != NIL){
jeproto:
      n_err(_("URL proto:// invalid (protocol or TLS support missing?): %s\n"),
         urlp->url_input);
      goto jleave;
   }
#ifdef a_ANYPROTO

   /* User and password, I */
juser:
   if((cp = a_url_last_at_before_slash(data)) != NIL){
      uz l;
      char const *urlpe, *d;
      char *ub;

      l = P2UZ(cp - data);
      ub = n_lofi_alloc(l +1);
      d = data;
      data = &cp[1];

      /* And also have a password? */
      if((cp = su_mem_find(d, ':', l)) != NIL){
         uz i = P2UZ(cp - d);

         l -= i + 1;
         su_mem_copy(ub, cp + 1, l);
         ub[l] = '\0';

         if((urlp->url_pass.s = mx_url_xdec(ub)) == NIL)
            goto jurlp_err;
         urlp->url_pass.l = su_cs_len(urlp->url_pass.s);
         if((urlpe = mx_url_xenc(urlp->url_pass.s, FAL0)) == NIL)
            goto jurlp_err;
         if(su_cs_cmp(ub, urlpe))
            goto jurlp_err;
         l = i;
      }

      su_mem_copy(ub, d, l);
      ub[l] = '\0';
      if((urlp->url_user.s = mx_url_xdec(ub)) == NIL)
         goto jurlp_err;
      if((urlp->url_user.l = su_cs_len(urlp->url_user.s)) > 0){
         urlp->url_flags |= mx_URL_HAD_USER;

         if((urlp->url_user_enc.s = mx_url_xenc(urlp->url_user.s, FAL0)
               ) == NIL)
            goto jurlp_err;
         urlp->url_user_enc.l = su_cs_len(urlp->url_user_enc.s);

         if(urlp->url_user_enc.l != l ||
               su_mem_cmp(urlp->url_user_enc.s, ub, l)){
jurlp_err:
            n_err(_("Incorrect URL percent encoding: %s\n"), ub);
            d = NIL;
         }
      }else
         urlp->url_user.s = NIL;

      n_lofi_free(ub);
      if(d == NIL)
         goto jleave;
   }

   /* Servername and port -- and possible path suffix */
   if((cp = su_cs_find_c(data, ':')) != NIL){ /* TODO URL: use IPAddress! */
      urlp->url_port = x = savestr(x = &cp[1]);
      if((x = su_cs_find_c(x, '/')) != NIL){
         *x = '\0';
         while(*++x == '/')
            ;
      }

      if((su_idec_u16_cp(&urlp->url_portno, urlp->url_port, 10, NIL
               ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
            ) != su_IDEC_STATE_CONSUMED || urlp->url_portno == 0){
         n_err(_("URL with invalid port number: %s\n"), urlp->url_input);
         goto jleave;
      }
   }else{
      if((x = su_cs_find_c(data, '/')) != NIL){
         data = savestrbuf(data, P2UZ(x - data));
         while(*++x == '/')
            ;
      }
      cp = n_UNCONST(data + su_cs_len(data));
   }

   /* A (non-empty) path may only occur with IMAP */
   if(x != NIL && *x != '\0'){
      /* Take care not to count adjacent solidus for real, on either end */
      char *x2;
      uz i;
      boole trailsol;

      for(trailsol = FAL0, x2 = savestrbuf(x, i = su_cs_len(x)); i > 0;
            trailsol = TRU1, --i)
         if(x2[i - 1] != '/')
            break;
      x2[i] = '\0';

      if(i > 0){
         if(cproto != CPROTO_IMAP){
            n_err(_("URL protocol doesn't support paths: \"%s\"\n"),
               urlp->url_input);
            goto jleave;
         }
# ifdef mx_HAVE_IMAP
         if(trailsol){
            urlp->url_path.s = n_autorec_alloc(i + sizeof("/INBOX"));
            su_mem_copy(urlp->url_path.s, x, i);
            su_mem_copy(&urlp->url_path.s[i], "/INBOX", sizeof("/INBOX"));
            urlp->url_path.l = (i += sizeof("/INBOX") -1);
         }else
# endif
            urlp->url_path.l = i, urlp->url_path.s = x2;
      }
   }
# ifdef mx_HAVE_IMAP
   if(cproto == CPROTO_IMAP && urlp->url_path.s == NIL)
      urlp->url_path.s = savestrbuf("INBOX",
            urlp->url_path.l = sizeof("INBOX") -1);
# endif

   urlp->url_host.s = savestrbuf(data, urlp->url_host.l = P2UZ(cp - data));
   /* C99 */{
      uz i;

      for(cp = urlp->url_host.s, i = urlp->url_host.l; i != 0; ++cp, --i)
         *cp = su_cs_to_lower(*cp);
   }
# ifdef mx_HAVE_IDNA
   if(!ok_blook(idna_disable)){
      struct n_string idna;

      if(!n_idna_to_ascii(n_string_creat_auto(&idna), urlp->url_host.s,
               urlp->url_host.l)){
         n_err(_("URL host fails IDNA conversion: %s\n"), urlp->url_input);
         goto jleave;
      }
      urlp->url_host.s = n_string_cp(&idna);
      urlp->url_host.l = idna.s_len;
   }
# endif /* mx_HAVE_IDNA */

   /* .url_h_p: HOST:PORT */
   /* C99 */{
      uz upl, i;
      struct str *s = &urlp->url_h_p;

      upl = (urlp->url_port == NIL) ? 0 : 1u + su_cs_len(urlp->url_port);
      s->s = n_autorec_alloc(urlp->url_host.l + upl +1);
      su_mem_copy(s->s, urlp->url_host.s, i = urlp->url_host.l);
      if(upl > 0){
         s->s[i++] = ':';
         su_mem_copy(&s->s[i], urlp->url_port, --upl);
         i += upl;
      }
      s->s[s->l = i] = '\0';
   }

   /* User, II
    * If there was no user in the URL, do we have *user-HOST* or *user*? */
   if(!(urlp->url_flags & mx_URL_HAD_USER)){
      /* *user* guaranteed non-empty */
      if((urlp->url_user.s = xok_vlook(user, urlp, OXM_PLAIN | OXM_H_P)
            ) == NIL){
         /* No, check whether .netrc lookup is desired */
# ifdef mx_HAVE_NETRC
         if(ok_vlook(v15_compat) == NIL ||
               !xok_blook(netrc_lookup, urlp, OXM_PLAIN | OXM_H_P) ||
               !mx_netrc_lookup(urlp, FAL0) || urlp->url_user.s[0] == '\0')
# endif
            urlp->url_user.s = UNCONST(char*,ok_vlook(LOGNAME));
      }

      urlp->url_user.l = su_cs_len(urlp->url_user.s);
      urlp->url_user.s = savestrbuf(urlp->url_user.s, urlp->url_user.l);
      if((urlp->url_user_enc.s = mx_url_xenc(urlp->url_user.s, FAL0)) == NIL){
         n_err(_("Cannot URL encode %s\n"), urlp->url_user.s);
         goto jleave;
      }
      urlp->url_user_enc.l = su_cs_len(urlp->url_user_enc.s);
   }

   /* And then there are a lot of prebuild string combinations TODO do lazy */

   /* .url_u_h: .url_user@.url_host
    * For SMTP we apply ridiculously complicated *v15-compat* plus
    * *smtp-hostname* / *hostname* dependent rules */
   /* C99 */{
      struct str h, *s;
      uz i;

      if(cproto == CPROTO_SMTP && ok_vlook(v15_compat) != NIL &&
            (cp = ok_vlook(smtp_hostname)) != NIL){
         if(*cp == '\0')
            cp = n_nodename(TRU1);
         h.s = savestrbuf(cp, h.l = su_cs_len(cp));
      }else
         h = urlp->url_host;

      s = &urlp->url_u_h;
      i = urlp->url_user.l;

      s->s = n_autorec_alloc(i + 1 + h.l +1);
      if(i > 0){
         su_mem_copy(s->s, urlp->url_user.s, i);
         s->s[i++] = '@';
      }
      su_mem_copy(s->s + i, h.s, h.l +1);
      i += h.l;
      s->l = i;
   }

   /* .url_u_h_p: .url_user@.url_host[:.url_port] */
   /* C99 */{
      struct str *s = &urlp->url_u_h_p;
      uz i = urlp->url_user.l;

      s->s = n_autorec_alloc(i + 1 + urlp->url_h_p.l +1);
      if(i > 0){
         su_mem_copy(s->s, urlp->url_user.s, i);
         s->s[i++] = '@';
      }
      su_mem_copy(s->s + i, urlp->url_h_p.s, urlp->url_h_p.l +1);
      i += urlp->url_h_p.l;
      s->l = i;
   }

   /* .url_eu_h_p: .url_user_enc@.url_host[:.url_port] */
   /* C99 */{
      struct str *s = &urlp->url_eu_h_p;
      uz i = urlp->url_user_enc.l;

      s->s = n_autorec_alloc(i + 1 + urlp->url_h_p.l +1);
      if(i > 0){
         su_mem_copy(s->s, urlp->url_user_enc.s, i);
         s->s[i++] = '@';
      }
      su_mem_copy(s->s + i, urlp->url_h_p.s, urlp->url_h_p.l +1);
      i += urlp->url_h_p.l;
      s->l = i;
   }

   /* .url_p_u_h_p: .url_proto://.url_u_h_p */
   /* C99 */{
      uz i;
      char *ud;

      ud = n_autorec_alloc((i = urlp->url_proto_len + sizeof("://") -1 +
            urlp->url_u_h_p.l) +1);
      urlp->url_proto[urlp->url_proto_len] = ':';
      su_mem_copy(su_cs_pcopy(ud, urlp->url_proto), urlp->url_u_h_p.s,
         urlp->url_u_h_p.l +1);
      urlp->url_proto[urlp->url_proto_len] = '\0';

      urlp->url_p_u_h_p = ud;
   }

   /* .url_p_eu_h_p, .url_p_eu_h_p_p: .url_proto://.url_eu_h_p[/.url_path] */
   /* C99 */{
      uz i;
      char *ud;

      ud = n_autorec_alloc((i = urlp->url_proto_len + sizeof("://") -1 +
            urlp->url_eu_h_p.l) + 1 + urlp->url_path.l +1);
      urlp->url_proto[urlp->url_proto_len] = ':';
      su_mem_copy(su_cs_pcopy(ud, urlp->url_proto), urlp->url_eu_h_p.s,
         urlp->url_eu_h_p.l +1);
      urlp->url_proto[urlp->url_proto_len] = '\0';

      if(urlp->url_path.l == 0)
         urlp->url_p_eu_h_p = urlp->url_p_eu_h_p_p = ud;
      else{
         urlp->url_p_eu_h_p = savestrbuf(ud, i);
         urlp->url_p_eu_h_p_p = ud;
         ud += i;
         *ud++ = '/';
         su_mem_copy(ud, urlp->url_path.s, urlp->url_path.l +1);
      }
   }

   rv = TRU1;
#endif /* a_ANYPROTO */
jleave:
   NYD_OU;
   return rv;
#undef a_ANYPROTO
#undef a_ALLPROTO
}
#endif /* mx_HAVE_NET */

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/mx/xtls.c000066400000000000000000002150111352610246600154340ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ OpenSSL client implementation according to: John Viega, Matt Messier,
 *@ Pravir Chandra: Network Security with OpenSSL. Sebastopol, CA 2002.
 *@ TODO This needs an overhaul -- there _are_ stack leaks!?
 *
 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: BSD-4-Clause TODO ISC
 */
/*
 * Copyright (c) 2002
 * Gunnar Ritter.  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 Gunnar Ritter
 *    and his contributors.
 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER 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 GUNNAR RITTER 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.
 */
#undef su_FILE
#define su_FILE xtls
#define mx_SOURCE

#ifndef mx_HAVE_AMALGAMATION
# include "mx/nail.h"
#endif

su_EMPTY_FILE()
#ifdef mx_HAVE_XTLS
#include 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#ifdef mx_HAVE_XTLS_CONFIG
# include 
#endif

#if defined X509_V_FLAG_CRL_CHECK && defined X509_V_FLAG_CRL_CHECK_ALL
# include 
#endif

#include 
#include 

#include "mx/cred-auth.h"
#include "mx/file-streams.h"
#include "mx/names.h"
#include "mx/net-socket.h"
#include "mx/random.h"
#include "mx/tty.h"
#include "mx/url.h"

/* TODO fake */
#include "su/code-in.h"

/* Compatibility shims which assume 0/-1 cannot really happen */
#ifndef mx_HAVE_XTLS_CONF_CTX
# ifndef SSL_OP_NO_SSLv2
#  define SSL_OP_NO_SSLv2 0
# endif
# ifndef SSL_OP_NO_SSLv3
#  define SSL_OP_NO_SSLv3 0
# endif
# ifndef SSL_OP_NO_TLSv1
#  define SSL_OP_NO_TLSv1 0
# endif
# ifndef SSL_OP_NO_TLSv1_1
#  define SSL_OP_NO_TLSv1_1 0
# endif
# ifndef SSL_OP_NO_TLSv1_2
#  define SSL_OP_NO_TLSv1_2 0
# endif
# ifndef SSL_OP_NO_TLSv1_3
#  define SSL_OP_NO_TLSv1_3 0
# endif
  /* SSL_CONF_CTX and _OP_NO_SSL_MASK were both introduced with 1.0.2!?! */
# ifndef SSL_OP_NO_SSL_MASK
#  define SSL_OP_NO_SSL_MASK \
   (SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |\
   SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2 |\
   SSL_OP_NO_TLSv1_3)
# endif

# ifndef SSL2_VERSION
#  define SSL2_VERSION 0
# endif
# ifndef SSL3_VERSION
#  define SSL3_VERSION 0
# endif
# ifndef TLS1_VERSION
#  define TLS1_VERSION 0
# endif
# ifndef TLS1_1_VERSION
#  define TLS1_1_VERSION 0
# endif
# ifndef TLS1_2_VERSION
#  define TLS1_2_VERSION 0
# endif
# ifndef TLS1_3_VERSION
#  define TLS1_3_VERSION 0
# endif
#endif

#ifdef mx_HAVE_XTLS_STACK_OF
# define n_XTLS_STACKOF(X) STACK_OF(X)
#else
# define n_XTLS_STACKOF(X) /*X*/STACK
#endif

#ifdef mx_HAVE_TLS_RAND_FILE
# if OPENSSL_VERSION_NUMBER + 0 >= 0x0090581fL
#  define a_XTLS_RAND_LOAD_FILE_MAXBYTES -1
# else
#  define a_XTLS_RAND_LOAD_FILE_MAXBYTES 1024
# endif
#endif

/* Compatibility sighs (that sigh is _really_ a cute one) */
#if mx_HAVE_XTLS_OPENSSL >= 0x10100
# define a_xtls_X509_get_notBefore X509_get0_notBefore
# define a_xtls_X509_get_notAfter X509_get0_notAfter
#else
# define a_xtls_X509_get_notBefore X509_get_notBefore
# define a_xtls_X509_get_notAfter X509_get_notAfter
#endif

/* X509_STORE_set_flags */
#undef a_XTLS_X509_V_ANY
#ifndef X509_V_FLAG_NO_ALT_CHAINS
# define X509_V_FLAG_NO_ALT_CHAINS -1
#else
# undef a_XTLS_X509_V_ANY
# define a_XTLS_X509_V_ANY
#endif
#ifndef X509_V_FLAG_NO_CHECK_TIME
# define X509_V_FLAG_NO_CHECK_TIME -1
#else
# undef a_XTLS_X509_V_ANY
# define a_XTLS_X509_V_ANY
#endif
#ifndef X509_V_FLAG_PARTIAL_CHAIN
# define X509_V_FLAG_PARTIAL_CHAIN -1
#else
# undef a_XTLS_X509_V_ANY
# define a_XTLS_X509_V_ANY
#endif
#ifndef X509_V_FLAG_X509_STRICT
# define X509_V_FLAG_X509_STRICT -1
#else
# undef a_XTLS_X509_V_ANY
# define a_XTLS_X509_V_ANY
#endif
#ifndef X509_V_FLAG_TRUSTED_FIRST
# define X509_V_FLAG_TRUSTED_FIRST -1
#else
# undef a_XTLS_X509_V_ANY
# define a_XTLS_X509_V_ANY
#endif

enum a_xtls_state{
   a_XTLS_S_INIT = 1u<<0,
   a_XTLS_S_RAND_DRBG_INIT = 1u<<1,
   a_XTLS_S_RAND_INIT = 1u<<2,
   a_XTLS_S_CONF_LOAD = 1u<<3,

#if mx_HAVE_XTLS_OPENSSL < 0x10100
   a_XTLS_S_EXIT_HDL = 1u<<8,
   a_XTLS_S_ALGO_LOAD = 1u<<9,
#endif

   a_XTLS_S_VERIFY_ERROR = 1u<<16
};

struct ssl_method { /* TODO v15 obsolete */
   char const  sm_name[8];
   char const  sm_map[16];
};

struct a_xtls_protocol{
   char const xp_name[8];
   sl xp_op_no;               /* SSL_OP_NO_* bit */
   u16 xp_version;            /* *_VERSION number */
   boole xp_ok_minmaxproto;   /* Valid for {Min,Max}Protocol= */
   boole xp_ok_proto;         /* Valid for Protocol= */
   boole xp_last;
   u8 xp__dummy[3];
};

struct a_xtls_cipher{
   char const xc_name[8];
   EVP_CIPHER const *(*xc_fun)(void);
};

struct a_xtls_digest{
   char const xd_name[16];
   EVP_MD const *(*xd_fun)(void);
};

struct a_xtls_x509_v_flags{
   char const xxvf_name[20];
   s32 xxvf_flag;
};

/* Supported SSL/TLS methods: update manual on change! */
static struct ssl_method const _ssl_methods[] = { /* TODO obsolete */
   {"auto",    "ALL,-SSLv2"},
   {"ssl3",    "-ALL,SSLv3"},
   {"tls1",    "-ALL,TLSv1"},
   {"tls1.1",  "-ALL,TLSv1.1"},
   {"tls1.2",  "-ALL,TLSv1.2"}
};

/* Update manual on change!
 * Ensure array size by adding \0 to longest entry.
 * Strictly to be sorted new/up to old/down, [0]=ALL, [x-1]=None! */
static struct a_xtls_protocol const a_xtls_protocols[] = {
   {"ALL", SSL_OP_NO_SSL_MASK, 0, FAL0, TRU1, FAL0, {0}},
   {"TLSv1.3\0", SSL_OP_NO_TLSv1_3, TLS1_3_VERSION, TRU1, TRU1, FAL0, {0}},
   {"TLSv1.2", SSL_OP_NO_TLSv1_2, TLS1_2_VERSION, TRU1, TRU1, FAL0, {0}},
   {"TLSv1.1", SSL_OP_NO_TLSv1_1, TLS1_1_VERSION, TRU1, TRU1, FAL0, {0}},
   {"TLSv1", SSL_OP_NO_TLSv1, TLS1_VERSION, TRU1, TRU1, FAL0, {0}},
   {"SSLv3", SSL_OP_NO_SSLv3, SSL3_VERSION, TRU1, TRU1, FAL0, {0}},
   {"SSLv2", SSL_OP_NO_SSLv2, SSL2_VERSION, TRU1, TRU1, FAL0, {0}},
   {"None", SSL_OP_NO_SSL_MASK, 0, TRU1, FAL0, TRU1, {0}}
};

/* Supported S/MIME cipher algorithms */
static struct a_xtls_cipher const a_xtls_ciphers[] = { /*Manual!*/
#ifndef OPENSSL_NO_AES
# define a_XTLS_SMIME_DEFAULT_CIPHER EVP_aes_128_cbc /* According RFC 5751 */
   {"AES128", &EVP_aes_128_cbc},
   {"AES256", &EVP_aes_256_cbc},
   {"AES192", &EVP_aes_192_cbc},
#endif
#ifndef OPENSSL_NO_DES
# ifndef a_XTLS_SMIME_DEFAULT_CIPHER
#  define a_XTLS_SMIME_DEFAULT_CIPHER EVP_des_ede3_cbc
# endif
   {"DES3", &EVP_des_ede3_cbc},
   {"DES", &EVP_des_cbc},
#endif
};
#ifndef a_XTLS_SMIME_DEFAULT_CIPHER
# error Your OpenSSL library does not include the necessary
# error cipher algorithms that are required to support S/MIME
#endif

#ifndef OPENSSL_NO_AES
/* TODO obsolete a_xtls_smime_ciphers_obs */
static struct a_xtls_cipher const a_xtls_smime_ciphers_obs[] = {
   {"AES-128", &EVP_aes_128_cbc},
   {"AES-256", &EVP_aes_256_cbc},
   {"AES-192", &EVP_aes_192_cbc}
};
#endif

/* Supported S/MIME message digest algorithms.
 * Update manual on default changes! */
static struct a_xtls_digest const a_xtls_digests[] = { /*Manual!*/
#ifdef mx_HAVE_XTLS_BLAKE2
   {"BLAKE2b512\0", &EVP_blake2b512},
   {"BLAKE2s256", &EVP_blake2s256},
# ifndef a_XTLS_FINGERPRINT_DEFAULT_DIGEST
#  define a_XTLS_FINGERPRINT_DEFAULT_DIGEST EVP_blake2s256
#  define a_XTLS_FINGERPRINT_DEFAULT_DIGEST_S "BLAKE2s256"
# endif
#endif

#ifdef mx_HAVE_XTLS_SHA3
   {"SHA3-512\0", &EVP_sha3_512},
   {"SHA3-384", &EVP_sha3_384},
   {"SHA3-256", &EVP_sha3_256},
   {"SHA3-224", &EVP_sha3_224},
#endif

#ifndef OPENSSL_NO_SHA512
   {"SHA512\0", &EVP_sha512},
   {"SHA384", &EVP_sha384},
# ifndef a_XTLS_SMIME_DEFAULT_DIGEST
#  define a_XTLS_SMIME_DEFAULT_DIGEST EVP_sha512
#  define a_XTLS_SMIME_DEFAULT_DIGEST_S "SHA512"
# endif
#endif

#ifndef OPENSSL_NO_SHA256
   {"SHA256\0", &EVP_sha256},
   {"SHA224", &EVP_sha224},
# ifndef a_XTLS_SMIME_DEFAULT_DIGEST
#  define a_XTLS_SMIME_DEFAULT_DIGEST EVP_sha256
#  define a_XTLS_SMIME_DEFAULT_DIGEST_S "SHA256"
# endif
# ifndef a_XTLS_FINGERPRINT_DEFAULT_DIGEST
#  define a_XTLS_FINGERPRINT_DEFAULT_DIGEST EVP_sha256
#  define a_XTLS_FINGERPRINT_DEFAULT_DIGEST_S "SHA256"
# endif
#endif

#ifndef OPENSSL_NO_SHA
   {"SHA1\0", &EVP_sha1},
# ifndef a_XTLS_SMIME_DEFAULT_DIGEST
#  define a_XTLS_SMIME_DEFAULT_DIGEST EVP_sha1
#  define a_XTLS_SMIME_DEFAULT_DIGEST_S "SHA1"
# endif
# ifndef a_XTLS_FINGERPRINT_DEFAULT_DIGEST
#  define a_XTLS_FINGERPRINT_DEFAULT_DIGEST EVP_sha1
#  define a_XTLS_FINGERPRINT_DEFAULT_DIGEST_S "SHA1"
# endif
#endif

#ifndef OPENSSL_NO_MD5
   {"MD5\0", &EVP_md5},
#endif
};

#if !defined a_XTLS_SMIME_DEFAULT_DIGEST || \
      !defined a_XTLS_FINGERPRINT_DEFAULT_DIGEST
# error Not enough supported message digest algorithms available
#endif

/* X509_STORE_set_flags() for *{smime,ssl}-ca-flags* */
static struct a_xtls_x509_v_flags const a_xtls_x509_v_flags[] = { /* Manual! */
   {"no-alt-chains", X509_V_FLAG_NO_ALT_CHAINS},
   {"no-check-time", X509_V_FLAG_NO_CHECK_TIME},
   {"partial-chain", X509_V_FLAG_PARTIAL_CHAIN},
   {"strict", X509_V_FLAG_X509_STRICT},
   {"trusted-first", X509_V_FLAG_TRUSTED_FIRST},
};

static uz a_xtls_state;
static uz a_xtls_msgno;

/* Special pre-PRNG PRNG init */
#ifdef a_XTLS_S_RAND_DRBG_INIT
su_SINLINE void a_xtls_rand_drbg_init(void);
#else
# define a_xtls_rand_drbg_init() \
   do {a_xtls_state |= a_XTLS_S_RAND_DRBG_INIT;} while(0)
#endif

/* PRNG init */
#ifdef mx_HAVE_TLS_RAND_FILE
static void a_xtls_rand_init(void);
#else
# define a_xtls_rand_init() \
   do {a_xtls_state |= a_XTLS_S_RAND_INIT;} while(0)
#endif

/* Library init */
static void a_xtls_init(void);

#if mx_HAVE_XTLS_OPENSSL < 0x10100
# ifdef mx_HAVE_TLS_ALL_ALGORITHMS
static void a_xtls__load_algos(void);
#  define a_xtls_load_algos a_xtls__load_algos
# endif
# if defined mx_HAVE_XTLS_CONFIG || defined mx_HAVE_TLS_ALL_ALGORITHMS
static void a_xtls_atexit(void);
# endif
#endif
#ifndef a_xtls_load_algos
# define a_xtls_load_algos() do{;}while(0)
#endif

static boole a_xtls_parse_asn1_time(ASN1_TIME const *atp,
               char *bdat, uz blen);
static int a_xtls_verify_cb(int success, X509_STORE_CTX *store);

static boole a_xtls_digest_find(char const *name, EVP_MD const **mdp,
               char const **normalized_name_or_null);

/* *smime-ca-flags*, *tls-ca-flags* */
static void a_xtls_ca_flags(X509_STORE *store, char const *flags);

/* SSL_CTX configuration; the latter always NULLs *confp */
static void *a_xtls_conf_setup(SSL_CTX *ctxp, struct mx_url const *urlp);
static boole a_xtls_conf(void *confp, char const *cmd, char const *value);
static boole a_xtls_conf_finish(void **confp, boole error);

static boole a_xtls_obsolete_conf_vars(void *confp, struct mx_url const *urlp);
static boole a_xtls_config_pairs(void *confp, struct mx_url const *urlp);
static boole a_xtls_load_verifications(SSL_CTX *ctxp,
      struct mx_url const *urlp);

static boole a_xtls_check_host(struct mx_socket *sp, X509 *peercert,
      struct mx_url const *urlp);

static int        smime_verify(struct message *m, int n,
                     n_XTLS_STACKOF(X509) *chain, X509_STORE *store);
static EVP_CIPHER const * _smime_cipher(char const *name);
static int        ssl_password_cb(char *buf, int size, int rwflag,
                     void *userdata);
static FILE *     smime_sign_cert(char const *xname, char const *xname2,
                     boole dowarn, char const **match);
static char const * _smime_sign_include_certs(char const *name);
static boole     _smime_sign_include_chain_creat(n_XTLS_STACKOF(X509) **chain,
                     char const *cfiles, char const *addr);
static EVP_MD const *a_xtls_smime_sign_digest(char const *name,
                        char const **digname);
#if defined X509_V_FLAG_CRL_CHECK && defined X509_V_FLAG_CRL_CHECK_ALL
static enum okay  load_crl1(X509_STORE *store, char const *name);
#endif
static enum okay  load_crls(X509_STORE *store, enum okeys fok, enum okeys dok);

#ifdef a_XTLS_S_RAND_DRBG_INIT
su_SINLINE void
a_xtls_rand_drbg_init(void){
   (void)RAND_DRBG_set_reseed_defaults(0, 0, 0, 0); /* (does not fail here) */
   a_xtls_state |= a_XTLS_S_RAND_DRBG_INIT;
}
#endif

#ifdef mx_HAVE_TLS_RAND_FILE
static void
a_xtls_rand_init(void){
# define a_XTLS_RAND_ENTROPY 32
   char b64buf[a_XTLS_RAND_ENTROPY * 5 +1], *randfile;
   char const *cp, *x;
   boole err;
   NYD2_IN;

   a_xtls_rand_drbg_init();
   a_xtls_state |= a_XTLS_S_RAND_INIT;

# ifdef mx_HAVE_XTLS_CONFIG
   if(!(a_xtls_state & a_XTLS_S_INIT))
      a_xtls_init();
# endif

   err = TRU1;
   randfile = NULL;

   /* Prefer possible user setting */
   if((cp = ok_vlook(tls_rand_file)) != NULL ||
         (cp = ok_vlook(ssl_rand_file)) != NULL){
      x = NULL;
      if(*cp != '\0'){
         if((x = fexpand(cp, FEXP_LOCAL | FEXP_NOPROTO)) == NULL)
            n_err(_("*tls-rand-file*: expansion of %s failed "
                  "(using default)\n"),
               n_shexp_quote_cp(cp, FAL0));
      }
      cp = x;
   }
   if(cp == NULL){
      randfile = n_lofi_alloc(PATH_MAX);
      if((cp = RAND_file_name(randfile, PATH_MAX)) == NULL){
         n_err(_("*tls-rand-file*: no TLS entropy file, can't seed PRNG\n"));
         goto jleave;
      }
   }

   (void)RAND_load_file(cp, a_XTLS_RAND_LOAD_FILE_MAXBYTES);

   /* And feed in some data, then write the updated file.
    * While this rather feeds the PRNG with itself in the RANDOM_IMPL_TLS
    * case, let us stir the buffer a little bit.
    * Estimate a low but likely still too high number of entropy bytes, use
    * 20%: base64 uses 3 input = 4 output bytes relation, and the base64
    * alphabet is a 6 bit one */
   for(x = (char*)-1;;){
      RAND_add(mx_random_create_buf(b64buf, sizeof(b64buf) -1, NIL),
         sizeof(b64buf) -1, a_XTLS_RAND_ENTROPY);
      if((x = (char*)((up)x >> (1
# if mx_HAVE_RANDOM == mx_RANDOM_IMPL_TLS
         + 3
# endif
            ))) == NULL){
         err = (RAND_status() == 0);
         break;
      }
# if mx_HAVE_RANDOM != mx_RANDOM_IMPL_TLS
      if(!(err = (RAND_status() == 0)))
         break;
# endif
   }

   if(!err)
      err = (RAND_write_file(cp) == -1);

jleave:
   if(randfile != NULL)
      n_lofi_free(randfile);
   if(err)
      n_panic(_("Cannot seed the *TLS PseudoRandomNumberGenerator, "
            "RAND_status() is 0!\n"
         "  Please set *tls-rand-file* to a file with sufficient entropy.\n"
         "  On a machine with entropy: "
            "\"$ dd if=/dev/urandom of=FILE bs=1024 count=1\"\n"));
   NYD2_OU;
}
#endif /* mx_HAVE_TLS_RAND_FILE */

static void
a_xtls_init(void){
#ifdef mx_HAVE_XTLS_CONFIG
   char const *cp;
#endif
   NYD2_IN;

   if(a_xtls_state & a_XTLS_S_INIT)
      goto jleave;

#if mx_HAVE_XTLS_OPENSSL >= 0x10100
   OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS |
      OPENSSL_INIT_LOAD_CRYPTO_STRINGS
# ifdef mx_HAVE_TLS_ALL_ALGORITHMS
         | OPENSSL_INIT_ADD_ALL_CIPHERS | OPENSSL_INIT_ADD_ALL_DIGESTS
# endif
      , NULL);
#else
   SSL_load_error_strings();
   SSL_library_init();
   a_xtls_load_algos();
#endif
   a_xtls_state |= a_XTLS_S_INIT;

   a_xtls_rand_drbg_init();

   /* Load openssl.cnf or whatever was given in *tls-config-file* */
#ifdef mx_HAVE_XTLS_CONFIG
   if((cp = ok_vlook(tls_config_file)) != NULL ||
         (cp = ok_vlook(ssl_config_file)) != NULL){
      char const *msg;
      ul flags;

      if(*cp == '\0'){
         msg = "[default]";
         cp = NULL;
         flags = CONF_MFLAGS_IGNORE_MISSING_FILE;
      }else if((msg = cp, cp = fexpand(cp, FEXP_LOCAL | FEXP_NOPROTO)) != NULL)
         flags = 0;
      else{
         n_err(_("*tls-config-file*: file expansion failed: %s\n"),
            n_shexp_quote_cp(msg, FAL0));
         goto jefile;
      }

      if(CONF_modules_load_file(cp, n_uagent, flags) == 1){
         a_xtls_state |= a_XTLS_S_CONF_LOAD;
# if mx_HAVE_XTLS_OPENSSL < 0x10100
         if(!(a_xtls_state & a_XTLS_S_EXIT_HDL)){
            a_xtls_state |= a_XTLS_S_EXIT_HDL;
            atexit(&a_xtls_atexit); /* TODO generic program-wide event mech. */
         }
# endif
         if(n_poption & n_PO_D_V)
            n_err(_("Loaded TLS configuration for %s from %s\n"), n_uagent,
               n_shexp_quote_cp(msg, FAL0));
jefile:;
      }else
         ssl_gen_err(_("TLS CONF_modules_load_file() load error"));
   }
#endif /* mx_HAVE_XTLS_CONFIG */

   if(!(a_xtls_state & a_XTLS_S_RAND_INIT))
      a_xtls_rand_init();
jleave:
   NYD2_OU;
}

#if mx_HAVE_XTLS_OPENSSL < 0x10100
# ifdef mx_HAVE_TLS_ALL_ALGORITHMS
static void
a_xtls__load_algos(void){
   NYD2_IN;
   if(!(a_xtls_state & a_XTLS_S_ALGO_LOAD)){
      a_xtls_state |= a_XTLS_S_ALGO_LOAD;
      OpenSSL_add_all_algorithms();

      if(!(a_xtls_state & a_XTLS_S_EXIT_HDL)){
         a_xtls_state |= a_XTLS_S_EXIT_HDL;
         atexit(&a_xtls_atexit); /* TODO generic program-wide event mech. */
      }
   }
   NYD2_OU;
}
# endif

# if defined mx_HAVE_XTLS_CONFIG || defined mx_HAVE_TLS_ALL_ALGORITHMS
static void
a_xtls_atexit(void){
   NYD2_IN;
#  ifdef mx_HAVE_XTLS_CONFIG
   if(a_xtls_state & a_XTLS_S_CONF_LOAD)
      CONF_modules_free();
#  endif

#  ifdef mx_HAVE_TLS_ALL_ALGORITHMS
   if(a_xtls_state & a_XTLS_S_ALGO_LOAD)
      EVP_cleanup();
#  endif
   NYD2_OU;
}
# endif
#endif /* mx_HAVE_XTLS_OPENSSL < 0x10100 */

static boole
a_xtls_parse_asn1_time(ASN1_TIME const *atp, char *bdat, uz blen)
{
   BIO *mbp;
   char *mcp;
   long l;
   NYD_IN;

   mbp = BIO_new(BIO_s_mem());

   if (ASN1_TIME_print(mbp, atp) && (l = BIO_get_mem_data(mbp, &mcp)) > 0)
      snprintf(bdat, blen, "%.*s", (int)l, mcp);
   else {
      snprintf(bdat, blen, _("Bogus certificate date: %.*s"),
         /*is (int)*/atp->length, (char const*)atp->data);
      mcp = NULL;
   }

   BIO_free(mbp);
   NYD_OU;
   return (mcp != NULL);
}

static int
a_xtls_verify_cb(int success, X509_STORE_CTX *store)
{
   char data[256];
   X509 *cert;
   int rv = TRU1;
   NYD_IN;

   if (success && !(n_poption & n_PO_D_V))
      goto jleave;

   if (a_xtls_msgno != 0) {
      n_err(_("Message %lu:\n"), (ul)a_xtls_msgno);
      a_xtls_msgno = 0;
   }
   n_err(_(" Certificate depth %d %s\n"),
      X509_STORE_CTX_get_error_depth(store), (success ? n_empty : V_(n_error)));

   if ((cert = X509_STORE_CTX_get_current_cert(store)) != NULL) {
      X509_NAME_oneline(X509_get_subject_name(cert), data, sizeof data);
      n_err(_("  subject = %s\n"), data);

      a_xtls_parse_asn1_time(a_xtls_X509_get_notBefore(cert),
         data, sizeof data);
      n_err(_("  notBefore = %s\n"), data);

      a_xtls_parse_asn1_time(a_xtls_X509_get_notAfter(cert),
         data, sizeof data);
      n_err(_("  notAfter = %s\n"), data);

      X509_NAME_oneline(X509_get_issuer_name(cert), data, sizeof data);
      n_err(_("  issuer = %s\n"), data);
   }

   if (!success) {
      int err = X509_STORE_CTX_get_error(store);

      n_err(_("  err %i: %s\n"), err, X509_verify_cert_error_string(err));
      a_xtls_state |= a_XTLS_S_VERIFY_ERROR;
   }

   if(!success)
      rv = n_tls_verify_decide();
jleave:
   NYD_OU;
   return rv;
}

static boole
a_xtls_digest_find(char const *name,
      EVP_MD const **mdp, char const **normalized_name_or_null){
   uz i;
   char *nn;
   NYD2_IN;

   /* C99 */{
      char *cp, c;

      i = su_cs_len(name);
      nn = cp = n_lofi_alloc(i +1);
      while((c = *name++) != '\0')
         *cp++ = su_cs_to_upper(c);
      *cp = '\0';

      if(normalized_name_or_null != NULL)
         *normalized_name_or_null = savestrbuf(nn, P2UZ(cp - nn));
   }

   for(i = 0; i < NELEM(a_xtls_digests); ++i)
      if(!su_cs_cmp(a_xtls_digests[i].xd_name, nn)){
         *mdp = (*a_xtls_digests[i].xd_fun)();
         goto jleave;
      }

   /* Not a built-in algorithm, but we may have dynamic support for more */
#ifdef mx_HAVE_TLS_ALL_ALGORITHMS
   if((*mdp = EVP_get_digestbyname(nn)) != NULL)
      goto jleave;
#endif

   n_err(_("Invalid message digest: %s\n"), n_shexp_quote_cp(nn, FAL0));
   *mdp = NULL;
jleave:
   n_lofi_free(nn);

   NYD2_OU;
   return (*mdp != NULL);
}

static void
a_xtls_ca_flags(X509_STORE *store, char const *flags){
   NYD2_IN;
   if(flags != NULL){
      char *iolist, *cp;

      iolist = savestr(flags);
jouter:
      while((cp = su_cs_sep_c(&iolist, ',', TRU1)) != NULL){
         struct a_xtls_x509_v_flags const *xvfp;

         for(xvfp = &a_xtls_x509_v_flags[0];
               xvfp < &a_xtls_x509_v_flags[NELEM(a_xtls_x509_v_flags)];
               ++xvfp)
            if(!su_cs_cmp_case(cp, xvfp->xxvf_name)){
               if(xvfp->xxvf_flag != -1){
#ifdef a_XTLS_X509_V_ANY
                  X509_STORE_set_flags(store, xvfp->xxvf_flag);
#endif
               }else if(n_poption & n_PO_D_V)
                  n_err(_("*{smime,tls}-ca-flags*: "
                     "directive not supported: %s\n"), cp);
               goto jouter;
            }
         n_err(_("*{smime,tls}-ca-flags*: invalid directive: %s\n"), cp);
      }
   }
   NYD2_OU;
}

#ifdef mx_HAVE_XTLS_CONF_CTX
static void *
a_xtls_conf_setup(SSL_CTX *ctxp, struct mx_url const *urlp){
   char const *cp;
   SSL_CONF_CTX *sccp;
   NYD2_IN;

   sccp = NULL;

   if((cp = xok_vlook(tls_config_module, urlp, OXM_ALL)) != NULL ||
         (cp = xok_vlook(ssl_config_module, urlp, OXM_ALL)) != NULL){
# ifdef mx_HAVE_XTLS_CTX_CONFIG
      if(!(a_xtls_state & a_XTLS_S_CONF_LOAD)){
         n_err(_("*tls-config-module*: no *tls-config-file* loaded: %s\n"),
            n_shexp_quote_cp(cp, FAL0));
         goto jleave;
      }else if(!SSL_CTX_config(ctxp, cp)){
         ssl_gen_err(_("*tls-config-module*: load error for %s, section [%s]"),
               n_uagent, n_shexp_quote_cp(cp, FAL0));
         goto jleave;
      }
# else
      n_err(_("*tls-config-module*: set but not supported: %s\n"),
         n_shexp_quote_cp(cp, FAL0));
      goto jleave;
# endif
   }

   if((sccp = SSL_CONF_CTX_new()) != NULL){
      SSL_CONF_CTX_set_flags(sccp,
         SSL_CONF_FLAG_FILE | SSL_CONF_FLAG_CLIENT |
         SSL_CONF_FLAG_CERTIFICATE | SSL_CONF_FLAG_SHOW_ERRORS);

      SSL_CONF_CTX_set_ssl_ctx(sccp, ctxp);
   }else
      ssl_gen_err(_("SSL_CONF_CTX_new() failed"));
jleave:
   NYD2_OU;
   return sccp;
}

static boole
a_xtls_conf(void *confp, char const *cmd, char const *value){
   int rv;
   SSL_CONF_CTX *sccp;
   NYD2_IN;

   if(n_poption & n_PO_D_V)
      n_err(_("TLS: applying config: %s = %s\n"),
            n_shexp_quote_cp(cmd, FAL0), n_shexp_quote_cp(value, FAL0));

   rv = SSL_CONF_cmd(sccp = confp, cmd, value);
   if(rv == 2)
      rv = 0;
   else{
      cmd = n_shexp_quote_cp(cmd, FAL0);
      value = n_shexp_quote_cp(value, FAL0);
      if(rv == 0)
         ssl_gen_err(_("TLS: config failure: %s = %s"), cmd, value);
      else{
         char const *err;

         switch(rv){
         case -2: err = N_("TLS: config command not recognized"); break;
         case -3: err = N_("TLS: missing required config argument"); break;
         default: err = N_("TLS: unspecified config error"); break;
         }
         err = V_(err);
         n_err(_("%s (%d): %s = %s\n"), err, rv, cmd, value);
      }
      rv = 1;
   }
   NYD2_OU;
   return (rv == 0);
}

static boole
a_xtls_conf_finish(void **confp, boole error){
   SSL_CONF_CTX *sccp;
   boole rv;
   NYD2_IN;

   sccp = (SSL_CONF_CTX*)*confp;
   *confp = NULL;

   if(!(rv = error))
      rv = (SSL_CONF_CTX_finish(sccp) != 0);

   SSL_CONF_CTX_free(sccp);
   NYD2_OU;
   return rv;
}

#else /* mx_HAVE_XTLS_CONF_CTX */
# ifdef mx_HAVE_XTLS_CTX_CONFIG
#  error SSL_CTX_config(3) support unexpected without SSL_CONF_CTX support
# endif

static void *
a_xtls_conf_setup(SSL_CTX* ctxp, struct mx_url const *urlp){
   char const *cp;
   NYD2_IN;

   if((cp = xok_vlook(tls_config_module, urlp, OXM_ALL)) != NULL ||
         (cp = xok_vlook(ssl_config_module, urlp, OXM_ALL)) != NULL){
      n_err(_("*tls-config-module*: set but not supported: %s\n"),
         n_shexp_quote_cp(cp, FAL0));
      ctxp = NULL;
   }
   NYD2_OU;
   return ctxp;
}

static boole
a_xtls_conf(void *confp, char const *cmd, char const *value){
   char const *xcmd, *emsg;
   SSL_CTX *ctxp;
   NYD2_IN;

   if(n_poption & n_PO_D_V)
      n_err(_("TLS: applying config: %s = %s\n"),
            n_shexp_quote_cp(cmd, FAL0), n_shexp_quote_cp(value, FAL0));

   ctxp = confp;

   if(!su_cs_cmp_case(cmd, xcmd = "Certificate")){
      if(SSL_CTX_use_certificate_chain_file(ctxp, value) != 1){
         emsg = N_("TLS: %s: cannot load from file %s\n");
         goto jerr;
      }
   }else if(!su_cs_cmp_case(cmd, xcmd = "CipherString") ||
         !su_cs_cmp_case(cmd, xcmd = "CipherList")/* XXX bad bug in past! */){
      if(SSL_CTX_set_cipher_list(ctxp, value) != 1){
         emsg = N_("TLS: %s: invalid: %s\n");
         goto jerr;
      }
   }else if(!su_cs_cmp_case(cmd, xcmd = "Ciphersuites")){
# ifdef mx_HAVE_XTLS_SET_CIPHERSUITES
      if(SSL_CTX_set_ciphersuites(ctxp, value) != 1){
         emsg = N_("TLS: %s: invalid: %s\n");
         goto jerr;
      }
# else
      value = NULL;
      emsg = N_("TLS: %s: directive not supported\n");
      goto jxerr;
# endif
   }else if(!su_cs_cmp_case(cmd, xcmd = "Curves")){
# ifdef SSL_CTRL_SET_CURVES_LIST
      if(SSL_CTX_set1_curves_list(ctxp, n_UNCONST(value)) != 1){
         emsg = N_("TLS: %s: invalid: %s\n");
         goto jerr;
      }
# else
      value = NULL;
      emsg = N_("TLS: %s: directive not supported\n");
      goto jxerr;
# endif
   }else if((emsg = NULL, !su_cs_cmp_case(cmd, xcmd = "MaxProtocol")) ||
         (emsg = (char*)-1, !su_cs_cmp_case(cmd, xcmd = "MinProtocol"))){
# ifndef mx_HAVE_XTLS_SET_MIN_PROTO_VERSION
      value = NULL;
      emsg = N_("TLS: %s: directive not supported\n");
      goto jxerr;
# else
      struct a_xtls_protocol const *xpp;

      for(xpp = &a_xtls_protocols[1] /* [0] == ALL */;;)
         if(xpp->xp_ok_minmaxproto && !su_cs_cmp_case(value, xpp->xp_name))
            break;
         else if((++xpp)->xp_last){
            emsg = N_("TLS: %s: unsupported element: %s\n");
            goto jxerr;
         }

      if((emsg == NULL ? SSL_CTX_set_max_proto_version(ctxp, xpp->xp_version)
            : SSL_CTX_set_min_proto_version(ctxp, xpp->xp_version)) != 1){
         emsg = N_("TLS: %s: invalid protocol: %s\n");
         goto jerr;
      }
# endif /* !mx_HAVE_XTLS_SET_MIN_PROTO_VERSION */
   }else if(!su_cs_cmp_case(cmd, xcmd = "Options")){
      if(su_cs_cmp_case(value, "Bugs")){
         emsg = N_("TLS: %s: fallback only supports value \"Bugs\": %s\n");
         goto jxerr;
      }
      SSL_CTX_set_options(ctxp, SSL_OP_ALL);
   }else if(!su_cs_cmp_case(cmd, xcmd = "PrivateKey")){
      if(SSL_CTX_use_PrivateKey_file(ctxp, value, SSL_FILETYPE_PEM) != 1){
         emsg = N_("%s: cannot load from file %s\n");
         goto jerr;
      }
   }else if(!su_cs_cmp_case(cmd, xcmd = "Protocol")){
      struct a_xtls_protocol const *xpp;
      char *iolist, *cp, addin;
      sl opts;

      opts = 0;

      for(iolist = cp = savestr(value);
            (cp = su_cs_sep_c(&iolist, ',', FAL0)) != NULL;){
         if(*cp == '\0'){
            value = NULL;
            emsg = N_("TLS: %s: empty elements are not supported\n");
            goto jxerr;
         }

         addin = TRU1;
         switch(cp[0]){
         case '-': addin = FAL0; /* FALLTHRU */
         case '+': ++cp; /* FALLTHRU */
         default : break;
         }

         for(xpp = &a_xtls_protocols[0];;){
            if(xpp->xp_ok_proto && !su_cs_cmp_case(cp, xpp->xp_name)){
               /* We need to inverse the meaning of the _NO_s */
               if(!addin)
                  opts |= xpp->xp_op_no;
               else
                  opts &= ~xpp->xp_op_no;
               break;
            }else if((++xpp)->xp_last){
               emsg = N_("TLS: %s: unsupported element: %s\n");
               goto jxerr;
            }
         }
      }

      SSL_CTX_clear_options(ctxp, SSL_OP_NO_SSL_MASK);
      SSL_CTX_set_options(ctxp, opts);
   }else{
      xcmd = n_shexp_quote_cp(cmd, FAL0);
      emsg = N_("TLS: unsupported directive: %s: value: %s\n");
      goto jxerr;
   }

jleave:
   NYD2_OU;
   return (confp != NULL);
jerr:
   ssl_gen_err(V_(emsg), xcmd, n_shexp_quote_cp(value, FAL0));
   confp = NULL;
   goto jleave;
jxerr:
   if(value != NULL)
      value = n_shexp_quote_cp(value, FAL0);
   n_err(V_(emsg), xcmd, value);
   confp = NULL;
   goto jleave;
}

static boole
a_xtls_conf_finish(void **confp, boole error){
   UNUSED(confp);
   UNUSED(error);
   *confp = NULL;
   return TRU1;
}
#endif /* !mx_HAVE_XTLS_CONF_CTX */

static boole
a_xtls_obsolete_conf_vars(void *confp, struct mx_url const *urlp){
   char const *cp, *cp_base, *certchain;
   boole rv;
   NYD2_IN;

   rv = FAL0;

   /* Certificate via ssl-cert */
   if((certchain = cp = xok_vlook(ssl_cert, urlp, OXM_ALL)) != NULL){
      n_OBSOLETE(_("please use *tls-config-pairs* instead of *ssl-cert*"));
      if((cp_base = fexpand(cp, FEXP_LOCAL | FEXP_NOPROTO)) == NULL){
         n_err(_("*ssl-cert* value expansion failed: %s\n"),
            n_shexp_quote_cp(cp, FAL0));
         goto jleave;
      }
      if(!a_xtls_conf(confp, "Certificate", certchain = cp_base))
         goto jleave;
   }

   /* CipherString via ssl-ciper-list */
   if((cp = xok_vlook(ssl_cipher_list, urlp, OXM_ALL)) != NULL){
      n_OBSOLETE(_("please use *tls-config-pairs* instead of "
         "*ssl-cipher-list*"));
      if(!a_xtls_conf(confp, "CipherString", cp))
         goto jleave;
   }

   /* Curves via ssl-curves */
   if((cp = xok_vlook(ssl_curves, urlp, OXM_ALL)) != NULL){
      n_OBSOLETE(_("please use *tls-config-pairs* instead of *ssl-curves*"));
      if(!a_xtls_conf(confp, "Curves", cp))
         goto jleave;
   }

   /* PrivateKey via ssl-key */
   if((cp = xok_vlook(ssl_key, urlp, OXM_ALL)) != NULL){
      n_OBSOLETE(_("please use *tls-config-pairs* instead of *ssl-key*"));
      if((cp_base = fexpand(cp, FEXP_LOCAL | FEXP_NOPROTO)) == NULL){
         n_err(_("*ssl-key* value expansion failed: %s\n"),
            n_shexp_quote_cp(cp, FAL0));
         goto jleave;
      }
      cp = cp_base;
      if(certchain == NULL){
         n_err(_("*ssl-key* can only be used together with *ssl-cert*! "
            "And use *ssl-config-pairs*!\n"));
         goto jleave;
      }
   }
   if((cp != NULL || (cp = certchain) != NULL) &&
         !a_xtls_conf(confp, "PrivateKey", cp))
      goto jleave;

   /* Protocol via ssl-method or ssl-protocol */
   if((cp = xok_vlook(ssl_method, urlp, OXM_ALL)) != NULL){
      uz i;

      n_OBSOLETE(_("please use *tls-config-pairs* instead of *ssl-method*"));
      for(i = 0;;){
         if(!su_cs_cmp_case(_ssl_methods[i].sm_name, cp)){
            cp = _ssl_methods[i].sm_map;
            break;
         }
         if(++i == NELEM(_ssl_methods)){
            n_err(_("Unsupported SSL method: %s\n"), cp);
            goto jleave;
         }
      }
   }
   if((cp_base = xok_vlook(ssl_protocol, urlp, OXM_ALL)) != NULL){
      n_OBSOLETE(_("please use *tls-config-pairs* instead of *ssl-protocol*"));
      if(cp != NULL && (n_poption & n_PO_D_V))
         n_err(_("*ssl-protocol* overrides *ssl-method*! "
            "And please use *tls-config-pairs* instead!\n"));
      cp = cp_base;
   }
   if(cp != NULL && !a_xtls_conf(confp, "Protocol", cp))
      goto jleave;

   rv = TRU1;
jleave:
   NYD2_OU;
   return rv;
}

static boole
a_xtls_config_pairs(void *confp, struct mx_url const *urlp){
   /* Due to interdependencies some commands have to be delayed a bit */
   static char const cmdcert[] = "Certificate", cmdprivkey[] = "PrivateKey";
   char const *valcert, *valprivkey;
   char *pairs, *cp, *cmd, *val;
   NYD2_IN;

   if((pairs = n_UNCONST(xok_vlook(tls_config_pairs, urlp, OXM_ALL))
         ) == NULL &&
         (pairs = n_UNCONST(xok_vlook(ssl_config_pairs, urlp, OXM_ALL))
         ) == NULL)
      goto jleave;
   pairs = savestr(pairs);

   valcert = valprivkey = NULL;

   while((cp = su_cs_sep_escable_c(&pairs, ',', FAL0)) != NULL){
      char c;
      enum{
         a_NONE,
         a_EXPAND = 1u<<0,
         a_CERT = 1u<<1,
         a_PRIVKEY = 1u<<2,
         a_EXPAND_MASK = a_EXPAND | a_CERT | a_PRIVKEY
      } f;

      /* Directive, space trimmed */
      if((cmd = su_cs_find_c(cp, '=')) == NULL){
jenocmd:
         if(pairs == NULL)
            pairs = n_UNCONST(n_empty);
         n_err(_("*tls-config-pairs*: missing directive: %s; rest: %s\n"),
            n_shexp_quote_cp(cp, FAL0), n_shexp_quote_cp(pairs, FAL0));
         goto jleave;
      }
      val = &cmd[1];

      if((cmd > cp && cmd[-1] == '*')){
         --cmd;
         f = a_EXPAND;
      }else
         f = a_NONE;
      while(cmd > cp && (c = cmd[-1], su_cs_is_space(c)))
         --cmd;
      if(cmd == cp)
         goto jenocmd;
      *cmd = '\0';
      cmd = cp;

      /* Command with special treatment? */
      if(!su_cs_cmp_case(cmd, cmdcert))
         f |= a_CERT;
      else if(!su_cs_cmp_case(cmd, cmdprivkey))
         f |= a_PRIVKEY;

      /* Value, space trimmed */
      while((c = *val) != '\0' && su_cs_is_space(c))
         ++val;
      cp = &val[su_cs_len(val)];
      while(cp > val && (c = cp[-1], su_cs_is_space(c)))
         --cp;
      *cp = '\0';
      if(cp == val){
         if(pairs == NULL)
            pairs = n_UNCONST(n_empty);
         n_err(_("*tls-config-pairs*: missing value: %s; rest: %s\n"),
            n_shexp_quote_cp(cmd, FAL0), n_shexp_quote_cp(pairs, FAL0));
         goto jleave;
      }

      /* Filename transformations to be applied? */
      if(f & a_EXPAND_MASK){
         if((cp = fexpand(val, FEXP_LOCAL | FEXP_NOPROTO)) == NULL){
            if(pairs == NULL)
               pairs = n_UNCONST(n_empty);
            n_err(_("*tls-config-pairs*: value expansion failed: %s: %s; "
                  "rest: %s\n"),
               n_shexp_quote_cp(cmd, FAL0), n_shexp_quote_cp(val, FAL0),
               n_shexp_quote_cp(pairs, FAL0));
            goto jleave;
         }
         val = cp;
      }

      /* Some things have to be delayed */
      if(f & a_CERT)
         valcert = val;
      else if(f & a_PRIVKEY)
         valprivkey = val;
      else if(!a_xtls_conf(confp, cmd, val)){
         pairs = n_UNCONST(n_empty);
         goto jleave;
      }
   }

   /* Work the delayed ones */
   if((valcert != NULL && !a_xtls_conf(confp, cmdcert, valcert)) ||
         ((valprivkey != NULL || (valprivkey = valcert) != NULL) &&
          !a_xtls_conf(confp, cmdprivkey, valprivkey)))
      pairs = n_UNCONST(n_empty);

jleave:
   NYD2_OU;
   return (pairs == NULL);
}

static boole
a_xtls_load_verifications(SSL_CTX *ctxp, struct mx_url const *urlp){
   char *ca_dir, *ca_file;
   X509_STORE *store;
   boole rv;
   NYD2_IN;

   if(n_tls_verify_level == n_TLS_VERIFY_IGNORE){
      rv = TRU1;
      goto jleave;
   }
   rv = FAL0;

   if((ca_dir = xok_vlook(tls_ca_dir, urlp, OXM_ALL)) != NULL ||
         (ca_dir = xok_vlook(ssl_ca_dir, urlp, OXM_ALL)) != NULL)
      ca_dir = fexpand(ca_dir, FEXP_LOCAL | FEXP_NOPROTO);
   if((ca_file = xok_vlook(tls_ca_file, urlp, OXM_ALL)) != NULL ||
         (ca_file = xok_vlook(ssl_ca_file, urlp, OXM_ALL)) != NULL)
      ca_file = fexpand(ca_file, FEXP_LOCAL | FEXP_NOPROTO);

   if((ca_dir != NULL || ca_file != NULL) &&
         SSL_CTX_load_verify_locations(ctxp, ca_file, ca_dir) != 1){
      char const *m1, *m2, *m3;

      if(ca_dir != NULL){
         m1 = ca_dir;
         m2 = (ca_file != NULL) ? _(" or ") : n_empty;
      }else
         m1 = m2 = n_empty;
      m3 = (ca_file != NULL) ? ca_file : n_empty;
      ssl_gen_err(_("Error loading %s%s%s\n"), m1, m2, m3);
      goto jleave;
   }

   /* C99 */{
      boole xv15;

      if((xv15 = ok_blook(ssl_no_default_ca)))
         n_OBSOLETE(_("please use *tls-ca-no-defaults*, "
            "not *ssl-no-default-ca*"));
      if(!xok_blook(tls_ca_no_defaults, urlp, OXM_ALL) &&
            !xok_blook(ssl_ca_no_defaults, urlp, OXM_ALL) && !xv15 &&
            SSL_CTX_set_default_verify_paths(ctxp) != 1) {
         ssl_gen_err(_("Error loading built-in default CA locations\n"));
         goto jleave;
      }
   }

   a_xtls_state &= ~a_XTLS_S_VERIFY_ERROR;
   a_xtls_msgno = 0;
   SSL_CTX_set_verify(ctxp, SSL_VERIFY_PEER, &a_xtls_verify_cb);
   store = SSL_CTX_get_cert_store(ctxp);
   load_crls(store, ok_v_tls_crl_file, ok_v_tls_crl_dir);
   a_xtls_ca_flags(store, xok_vlook(tls_ca_flags, urlp, OXM_ALL));
      a_xtls_ca_flags(store, xok_vlook(ssl_ca_flags, urlp, OXM_ALL));

   rv = TRU1;
jleave:
   NYD2_OU;
   return rv;
}

static boole
a_xtls_check_host(struct mx_socket *sop, X509 *peercert,
      struct mx_url const *urlp){
   char data[256];
   n_XTLS_STACKOF(GENERAL_NAME) *gens;
   GENERAL_NAME *gen;
   X509_NAME *subj;
   boole rv;
   NYD_IN;
   UNUSED(sop);

   rv = FAL0;

   if((gens = X509_get_ext_d2i(peercert, NID_subject_alt_name, NULL, NULL)
         ) != NULL){
      int i;

      for(i = 0; i < sk_GENERAL_NAME_num(gens); ++i){
         gen = sk_GENERAL_NAME_value(gens, i);
         if(gen->type == GEN_DNS){
            if(n_poption & n_PO_D_V)
               n_err(_("Comparing subject_alt_name: need<%s> is<%s>\n"),
                  urlp->url_host.s, (char*)gen->d.ia5->data);
            if((rv = n_tls_rfc2595_hostname_match(urlp->url_host.s,
                  (char*)gen->d.ia5->data)))
               goto jleave;
         }
      }
   }

   if((subj = X509_get_subject_name(peercert)) != NULL &&
         X509_NAME_get_text_by_NID(subj, NID_commonName, data, sizeof data
            ) > 0){
      data[sizeof data - 1] = '\0';
      if(n_poption & n_PO_D_V)
         n_err(_("Comparing commonName: need<%s> is<%s>\n"),
            urlp->url_host.s, data);
      rv = n_tls_rfc2595_hostname_match(urlp->url_host.s, data);
   }
jleave:
   NYD_OU;
   return rv;
}

static int
smime_verify(struct message *m, int n, n_XTLS_STACKOF(X509) *chain,
   X509_STORE *store)
{
   char data[LINESIZE], *sender, *to, *cc, *cnttype;
   int rv, c, i, j;
   struct message *x;
   FILE *fp, *ip;
   off_t size;
   BIO *fb, *pb;
   PKCS7 *pkcs7;
   n_XTLS_STACKOF(X509) *certs;
   n_XTLS_STACKOF(GENERAL_NAME) *gens;
   X509 *cert;
   X509_NAME *subj;
   GENERAL_NAME *gen;
   NYD_IN;

   rv = 1;
   fp = NULL;
   fb = pb = NULL;
   pkcs7 = NULL;
   certs = NULL;
   a_xtls_state &= ~a_XTLS_S_VERIFY_ERROR;
   a_xtls_msgno = (uz)n;

   for (;;) {
      sender = getsender(m);
      to = hfield1("to", m);
      cc = hfield1("cc", m);
      cnttype = hfield1("content-type", m);

#undef _X
#undef _Y
#define _X     (sizeof("application/") -1)
#define _Y(X)  X, sizeof(X) -1
      if (cnttype && su_cs_starts_with_case(cnttype, "application/") &&
            (!su_cs_cmp_case_n(cnttype + _X, _Y("pkcs7-mime")) ||
             !su_cs_cmp_case_n(cnttype + _X, _Y("x-pkcs7-mime")))) {
#undef _Y
#undef _X
         if ((x = smime_decrypt(m, to, cc, 1)) == NULL)
            goto jleave;
         if (x != (struct message*)-1) {
            m = x;
            continue;
         }
      }

      if ((ip = setinput(&mb, m, NEED_BODY)) == NULL)
         goto jleave;
      size = m->m_size;
      break;
   }

   if((fp = mx_fs_tmp_open("smimever", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
            mx_FS_O_REGISTER), NIL)) == NIL){
      n_perr(_("tempfile"), 0);
      goto jleave;
   }
   while (size-- > 0) {
      c = getc(ip);
      putc(c, fp);
   }
   fflush_rewind(fp);

   if ((fb = BIO_new_fp(fp, BIO_NOCLOSE)) == NULL) {
      ssl_gen_err(_(
         "Error creating BIO verification object for message %d"), n);
      goto jleave;
   }

   if ((pkcs7 = SMIME_read_PKCS7(fb, &pb)) == NULL) {
      ssl_gen_err(_("Error reading PKCS#7 object for message %d"), n);
      goto jleave;
   }
   if (PKCS7_verify(pkcs7, chain, store, pb, NULL, 0) != 1) {
      ssl_gen_err(_("Error verifying message %d"), n);
      goto jleave;
   }

   if (sender == NULL) {
      n_err(_("Warning: Message %d has no sender\n"), n);
      rv = 0;
      goto jleave;
   }

   certs = PKCS7_get0_signers(pkcs7, chain, 0);
   if (certs == NULL) {
      n_err(_("No certificates found in message %d\n"), n);
      goto jleave;
   }

   for (i = 0; i < sk_X509_num(certs); ++i) {
      cert = sk_X509_value(certs, i);
      gens = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
      if (gens != NULL) {
         for (j = 0; j < sk_GENERAL_NAME_num(gens); ++j) {
            gen = sk_GENERAL_NAME_value(gens, j);
            if (gen->type == GEN_EMAIL) {
               if (n_poption & n_PO_D_V)
                  n_err(_("Comparing subject_alt_name: need<%s> is<%s>)\n"),
                     sender, (char*)gen->d.ia5->data);
               if (!su_cs_cmp_case((char*)gen->d.ia5->data, sender))
                  goto jfound;
            }
         }
      }

      if ((subj = X509_get_subject_name(cert)) != NULL &&
            X509_NAME_get_text_by_NID(subj, NID_pkcs9_emailAddress,
               data, sizeof data) > 0) {
         data[sizeof data -1] = '\0';
         if (n_poption & n_PO_D_V)
            n_err(_("Comparing emailAddress: need<%s> is<%s>\n"),
               sender, data);
         if (!su_cs_cmp_case(data, sender))
            goto jfound;
      }
   }
   n_err(_("Message %d: certificate does not match <%s>\n"), n, sender);
   goto jleave;
jfound:
   rv = ((a_xtls_state & a_XTLS_S_VERIFY_ERROR) != 0);
   if (!rv)
      fprintf(n_stdout, _("Message %d was verified successfully\n"), n);
jleave:
   if (certs != NULL)
      sk_X509_free(certs);
   if (pb != NULL)
      BIO_free(pb);
   if (fb != NULL)
      BIO_free(fb);
   if (pkcs7 != NULL)
      PKCS7_free(pkcs7);
   if(fp != NIL)
      mx_fs_close(fp);
   NYD_OU;
   return rv;
}

static EVP_CIPHER const *
_smime_cipher(char const *name)
{
   EVP_CIPHER const *cipher;
   char *vn;
   char const *cp;
   uz i;
   NYD_IN;

   vn = n_lofi_alloc(i = su_cs_len(name) + sizeof("smime-cipher-") -1 +1);
   snprintf(vn, (int)i, "smime-cipher-%s", name);
   cp = n_var_vlook(vn, FAL0);
   n_lofi_free(vn);

   if (cp == NULL && (cp = ok_vlook(smime_cipher)) == NULL) {
      cipher = a_XTLS_SMIME_DEFAULT_CIPHER();
      goto jleave;
   }
   cipher = NULL;

   for(i = 0; i < NELEM(a_xtls_ciphers); ++i)
      if(!su_cs_cmp_case(a_xtls_ciphers[i].xc_name, cp)){
         cipher = (*a_xtls_ciphers[i].xc_fun)();
         goto jleave;
      }
#ifndef OPENSSL_NO_AES
   for (i = 0; i < NELEM(a_xtls_smime_ciphers_obs); ++i) /* TODO obsolete */
      if (!su_cs_cmp_case(a_xtls_smime_ciphers_obs[i].xc_name, cp)) {
         n_OBSOLETE2(_("*smime-cipher* names with hyphens will vanish"), cp);
         cipher = (*a_xtls_smime_ciphers_obs[i].xc_fun)();
         goto jleave;
      }
#endif

   /* Not a built-in algorithm, but we may have dynamic support for more */
#ifdef mx_HAVE_TLS_ALL_ALGORITHMS
   if((cipher = EVP_get_cipherbyname(cp)) != NULL)
      goto jleave;
#endif

   n_err(_("Invalid S/MIME cipher(s): %s\n"), cp);
jleave:
   NYD_OU;
   return cipher;
}

static int
ssl_password_cb(char *buf, int size, int rwflag, void *userdata)
{
   char *pass;
   uz len;
   NYD_IN;
   UNUSED(rwflag);
   UNUSED(userdata);

   /* New-style */
   if(userdata != NULL){
      struct mx_url url;
      struct mx_cred_ctx cred;

      if(mx_url_parse(&url, CPROTO_CCRED, userdata)){
         if(mx_cred_auth_lookup(&cred, &url)){
            char *end;

            if((end = su_cs_pcopy_n(buf, cred.cc_pass.s, size)) != NULL){
               size = (int)P2UZ(end - buf);
               goto jleave;
            }
         }
         size = 0;
         goto jleave;
      }
   }

   /* Old-style */
   if((pass = mx_tty_getpass("PEM pass phrase:")) != NIL){
      len = su_cs_len(pass);
      if (UCMP(z, len, >=, size))
         len = size -1;
      su_mem_copy(buf, pass, len);
      buf[len] = '\0';
      size = (int)len;
   } else
      size = 0;
jleave:
   NYD_OU;
   return size;
}

static FILE *
smime_sign_cert(char const *xname, char const *xname2, boole dowarn,
   char const **match)
{
   char *vn;
   int vs;
   struct mx_name *np;
   char const *name = xname, *name2 = xname2, *cp;
   FILE *fp = NULL;
   NYD_IN;

jloop:
   if (name) {
      np = lextract(name, GTO | GSKIN);
      while (np != NULL) {
         /* This needs to be more intelligent since it will currently take the
          * first name for which a private key is available regardless of
          * whether it is the right one for the message */
         vn = n_lofi_alloc(vs = su_cs_len(np->n_name) + 30);
         snprintf(vn, vs, "smime-sign-cert-%s", np->n_name);
         cp = n_var_vlook(vn, FAL0);
         n_lofi_free(vn);
         if (cp != NULL) {
            if (match != NULL)
               *match = np->n_name;
            goto jopen;
         }
         np = np->n_flink;
      }
      if (name2 != NULL) {
         name = name2;
         name2 = NULL;
         goto jloop;
      }
   }

   if ((cp = ok_vlook(smime_sign_cert)) == NULL)
      goto jerr;
   if(match != NULL)
      *match = NULL;
jopen:
   if ((cp = fexpand(cp, FEXP_LOCAL | FEXP_NOPROTO)) == NULL)
      goto jleave;
   if((fp = mx_fs_open(cp, "r")) == NIL)
      n_perr(cp, 0);
jleave:
   NYD_OU;
   return fp;
jerr:
   if (dowarn)
      n_err(_("Could not find a certificate for %s%s%s\n"),
         xname, (xname2 != NULL ? _("or ") : n_empty),
         (xname2 != NULL ? xname2 : n_empty));
   goto jleave;
}

static char const *
_smime_sign_include_certs(char const *name)
{
   char const *rv;
   NYD_IN;

   /* See comments in smime_sign_cert() for algorithm pitfalls */
   if (name != NULL) {
      struct mx_name *np;

      for (np = lextract(name, GTO | GSKIN); np != NULL; np = np->n_flink) {
         int vs;
         char *vn;

         vn = n_lofi_alloc(vs = su_cs_len(np->n_name) + 30);
         snprintf(vn, vs, "smime-sign-include-certs-%s", np->n_name);
         rv = n_var_vlook(vn, FAL0);
         n_lofi_free(vn);
         if (rv != NULL)
            goto jleave;
      }
   }
   rv = ok_vlook(smime_sign_include_certs);
jleave:
   NYD_OU;
   return rv;
}

static boole
_smime_sign_include_chain_creat(n_XTLS_STACKOF(X509) **chain,
   char const *cfiles, char const *addr)
{
   X509 *tmp;
   FILE *fp;
   char *nfield, *cfield, *x;
   NYD_IN;

   *chain = sk_X509_new_null();

   for (nfield = savestr(cfiles);
         (cfield = su_cs_sep_c(&nfield, ',', TRU1)) != NULL;) {
      if ((x = fexpand(cfield, FEXP_LOCAL | FEXP_NOPROTO)) == NULL ||
            (fp = mx_fs_open(cfield = x, "r")) == NIL){
         n_perr(cfiles, 0);
         goto jerr;
      }
      if ((tmp = PEM_read_X509(fp, NULL, &ssl_password_cb, n_UNCONST(addr))
            ) == NULL) {
         ssl_gen_err(_("Error reading certificate from %s"),
            n_shexp_quote_cp(cfield, FAL0));
         mx_fs_close(fp);
         goto jerr;
      }
      sk_X509_push(*chain, tmp);
      mx_fs_close(fp);
   }

   if (sk_X509_num(*chain) == 0) {
      n_err(_("*smime-sign-include-certs* defined but empty\n"));
      goto jerr;
   }
jleave:
   NYD_OU;
   return (*chain != NULL);
jerr:
   sk_X509_pop_free(*chain, X509_free);
   *chain = NULL;
   goto jleave;
}

static EVP_MD const *
a_xtls_smime_sign_digest(char const *name, char const **digname){
   EVP_MD const *digest;
   char const *cp;
   NYD2_IN;

   /* See comments in smime_sign_cert() for algorithm pitfalls */
   if(name != NULL){
      struct mx_name *np;

      for(np = lextract(name, GTO | GSKIN); np != NULL; np = np->n_flink){
         int vs;
         char *vn;

         vn = n_lofi_alloc(vs = su_cs_len(np->n_name) + 30);
         snprintf(vn, vs, "smime-sign-digest-%s", np->n_name);
         if((cp = n_var_vlook(vn, FAL0)) == NULL){
            snprintf(vn, vs, "smime-sign-message-digest-%s",np->n_name);/*v15*/
            cp = n_var_vlook(vn, FAL0);
         }
         n_lofi_free(vn);
         if(cp != NULL)
            goto jhave_name;
      }
   }

   if((cp = ok_vlook(smime_sign_digest)) != NULL ||
         (cp = ok_vlook(smime_sign_message_digest)/* v15 */) != NULL)
jhave_name:
      if(a_xtls_digest_find(cp, &digest, digname))
         goto jleave;

   digest = a_XTLS_SMIME_DEFAULT_DIGEST();
   *digname = a_XTLS_SMIME_DEFAULT_DIGEST_S;
jleave:
   NYD2_OU;
   return digest;
}

#if defined X509_V_FLAG_CRL_CHECK && defined X509_V_FLAG_CRL_CHECK_ALL
static enum okay
load_crl1(X509_STORE *store, char const *name)
{
   X509_LOOKUP *lookup;
   enum okay rv = STOP;
   NYD_IN;

   if (n_poption & n_PO_D_V)
      n_err(_("Loading CRL from %s\n"), n_shexp_quote_cp(name, FAL0));
   if ((lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file())) == NULL) {
      ssl_gen_err(_("Error creating X509 lookup object"));
      goto jleave;
   }
   if (X509_load_crl_file(lookup, name, X509_FILETYPE_PEM) != 1) {
      ssl_gen_err(_("Error loading CRL from %s"),
         n_shexp_quote_cp(name, FAL0));
      goto jleave;
   }
   rv = OKAY;
jleave:
   NYD_OU;
   return rv;
}
#endif /* new OpenSSL */

static enum okay
load_crls(X509_STORE *store, enum okeys fok, enum okeys dok)/*TODO nevertried*/
{
   char *crl_file, *crl_dir;
#if defined X509_V_FLAG_CRL_CHECK && defined X509_V_FLAG_CRL_CHECK_ALL
   DIR *dirp;
   struct dirent *dp;
   char *fn = NULL;
   int fs = 0, ds, es;
#endif
   boole any;
   enum okay rv;
   NYD_IN;

   rv = STOP;
   any = FAL0;

jredo_v15:
   if ((crl_file = n_var_oklook(fok)) != NULL) {
#if defined X509_V_FLAG_CRL_CHECK && defined X509_V_FLAG_CRL_CHECK_ALL
      if ((crl_file = fexpand(crl_file, FEXP_LOCAL | FEXP_NOPROTO)) == NULL ||
            load_crl1(store, crl_file) != OKAY)
         goto jleave;
      any = TRU1;
#else
      n_err(_("This OpenSSL version is too old to use CRLs\n"));
      goto jleave;
#endif
   }

   if ((crl_dir = n_var_oklook(dok)) != NULL) {
#if defined X509_V_FLAG_CRL_CHECK && defined X509_V_FLAG_CRL_CHECK_ALL
      char *x;
      if ((x = fexpand(crl_dir, FEXP_LOCAL | FEXP_NOPROTO)) == NULL ||
            (dirp = opendir(crl_dir = x)) == NULL) {
         n_perr(crl_dir, 0);
         goto jleave;
      }

      ds = su_cs_len(crl_dir);
      fn = n_alloc(fs = ds + 20);
      su_mem_copy(fn, crl_dir, ds);
      fn[ds] = '/';
      while ((dp = readdir(dirp)) != NULL) {
         if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' ||
               (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
            continue;
         if (dp->d_name[0] == '.')
            continue;
         if (ds + (es = su_cs_len(dp->d_name)) + 2 < fs)
            fn = n_realloc(fn, fs = ds + es + 20);
         su_mem_copy(fn + ds + 1, dp->d_name, es + 1);
         if (load_crl1(store, fn) != OKAY) {
            closedir(dirp);
            n_free(fn);
            goto jleave;
         }
         any = TRU1;
      }
      closedir(dirp);
      n_free(fn);
#else /* old OpenSSL */
      n_err(_("This OpenSSL version is too old to use CRLs\n"));
      goto jleave;
#endif
   }

   if(fok == ok_v_tls_crl_file){
      fok = ok_v_ssl_crl_file;
      dok = ok_v_ssl_crl_dir;
      goto jredo_v15;
   }
#if defined X509_V_FLAG_CRL_CHECK && defined X509_V_FLAG_CRL_CHECK_ALL
   if(any)
      X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK |
         X509_V_FLAG_CRL_CHECK_ALL);
#endif
   rv = OKAY;
jleave:
   NYD_OU;
   return rv;
}

#if mx_HAVE_RANDOM == mx_RANDOM_IMPL_TLS
FL void
mx_tls_rand_bytes(void *buf, uz blen){
   NYD2_IN;
   if(!(a_xtls_state & a_XTLS_S_RAND_INIT))
      a_xtls_rand_init();

   while(blen > 0){
      s32 i;

      switch(RAND_bytes(buf, i = MIN(S32_MAX, blen))){
      default:
         /* LibreSSL always succeeds, i think it aborts otherwise.
          * With elder OpenSSL we ensure via RAND_status() in
          * a_xtls_rand_init() that the PRNG is seeded, so it does not fail.
          *
          * With newer OpenSSL we disable automatic reseeding, but do not
          * ASSERT RAND_status() ("Since you always have to check RAND_bytes's
          * return value now, RAND_status is mostly useless.",
          * 20190104180735.GA25041@roeckx.be), so we have not that many options
          * on what to do.  Since OSs will try hard to serve, a simple sleep
          * may be it, so do that */
#if !defined mx_HAVE_XTLS_RESSL && !defined mx_HAVE_TLS_RAND_FILE
         n_err(_("TLS RAND_bytes(3ssl) failed (missing entropy?), "
            "waiting a bit\n"));
         n_msleep(250, FAL0);
         continue;
#endif
      case 1:
         break;
      }
      blen -= i;
      buf = (u8*)buf + i;
   }
   NYD2_OU;
}
#endif /* HAVE_RANDOM == RANDOM_IMPL_TLS */

FL boole
n_tls_open(struct mx_url *urlp, struct mx_socket *sop){
   void *confp;
   SSL_CTX *ctxp;
   const EVP_MD *fprnt_mdp;
   char const *fprnt, *fprnt_namep;
   NYD_IN;

   a_xtls_init();
   n_tls_set_verify_level(urlp); /* TODO should come in via URL! */

   sop->s_tls = NULL;
   if(urlp->url_cproto != CPROTO_CERTINFO)
      fprnt = xok_vlook(tls_fingerprint, urlp, OXM_ALL);
   else
      fprnt = NULL;
   fprnt_namep = NULL;
   fprnt_mdp = NULL;

   if(fprnt != NULL || urlp->url_cproto == CPROTO_CERTINFO ||
         (n_poption & n_PO_D_V)){
      if((fprnt_namep = xok_vlook(tls_fingerprint_digest, urlp,
            OXM_ALL)) == NULL ||
            !a_xtls_digest_find(fprnt_namep, &fprnt_mdp, &fprnt_namep)){
         fprnt_mdp = a_XTLS_FINGERPRINT_DEFAULT_DIGEST();
         fprnt_namep = a_XTLS_FINGERPRINT_DEFAULT_DIGEST_S;
      }
   }

   if((ctxp = SSL_CTX_new(n_XTLS_CLIENT_METHOD())) == NULL){
      ssl_gen_err(_("SSL_CTX_new() failed"));
      goto j_leave;
   }

   /* Available with OpenSSL 0.9.6 or later */
#ifdef SSL_MODE_AUTO_RETRY
   SSL_CTX_set_mode(ctxp, SSL_MODE_AUTO_RETRY);
#endif

   if((confp = a_xtls_conf_setup(ctxp, urlp)) == NULL)
      goto jleave;

   if(!a_xtls_obsolete_conf_vars(confp, urlp))
      goto jerr1;
   if(!a_xtls_config_pairs(confp, urlp))
      goto jerr1;
   if((fprnt == NULL || urlp->url_cproto == CPROTO_CERTINFO) &&
         !a_xtls_load_verifications(ctxp, urlp))
      goto jerr1;

   /* Done with context setup, create our new per-connection structure */
   if(!a_xtls_conf_finish(&confp, FAL0))
      goto jleave;
   ASSERT(confp == NULL);

   if((sop->s_tls = SSL_new(ctxp)) == NULL){
      ssl_gen_err(_("SSL_new() failed"));
      goto jleave;
   }

   /* Try establish SNI extension; even though this is a TLS extension the
    * protocol isn't checked by OpenSSL once the host name is set, and
    * therefore i refrained from changing so much code just to check out
    * whether we are using SSLv3, which should become more and more rare */
#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
   if((urlp->url_flags & mx_URL_TLS_MASK) &&
         (urlp->url_flags & mx_URL_HOST_IS_NAME)){
      if(!SSL_set_tlsext_host_name(sop->s_tls, urlp->url_host.s) &&
            (n_poption & n_PO_D_V))
         n_err(_("Hostname cannot be used with ServerNameIndication "
               "TLS extension: %s\n"),
            n_shexp_quote_cp(urlp->url_host.s, FAL0));
   }
#endif

   SSL_set_fd(sop->s_tls, sop->s_fd);

   if(SSL_connect(sop->s_tls) < 0){
      ssl_gen_err(_("could not initiate TLS connection"));
      goto jerr2;
   }

   if(fprnt != NULL || urlp->url_cproto == CPROTO_CERTINFO ||
         n_tls_verify_level != n_TLS_VERIFY_IGNORE){
      boole stay;
      X509 *peercert;

      if((peercert = SSL_get_peer_certificate(sop->s_tls)) == NULL){
         n_err(_("TLS: no certificate from peer: %s\n"), urlp->url_h_p.s);
         goto jerr2;
      }

      stay = FAL0;

      if(fprnt == NULL){
         if(!a_xtls_check_host(sop, peercert, urlp)){
            n_err(_("TLS certificate does not match: %s\n"), urlp->url_h_p.s);
            stay = n_tls_verify_decide();
         }else{
            if(n_poption & n_PO_D_V)
               n_err(_("TLS certificate ok\n"));
            stay = TRU1;
         }
      }

      if(fprnt != NULL || urlp->url_cproto == CPROTO_CERTINFO ||
            (n_poption & n_PO_D_V)){
         char fpmdhexbuf[EVP_MAX_MD_SIZE * 3], *cp;
         unsigned char fpmdbuf[EVP_MAX_MD_SIZE], *ucp;
         unsigned int fpmdlen;

         if(!X509_digest(peercert, fprnt_mdp, fpmdbuf, &fpmdlen)){
            ssl_gen_err(_("TLS %s fingerprint creation failed"), fprnt_namep);
            goto jpeer_leave;
         }
         ASSERT(fpmdlen <= EVP_MAX_MD_SIZE);

         for(cp = fpmdhexbuf, ucp = fpmdbuf; fpmdlen > 0; --fpmdlen){
            n_c_to_hex_base16(cp, (char)*ucp++);
            cp[2] = ':';
            cp += 3;
         }
         cp[-1] = '\0';

         if(n_poption & n_PO_D_V)
            n_err(_("TLS %s fingerprint: %s\n"), fprnt_namep, fpmdhexbuf);
         if(fprnt != NULL){
            if(!(stay = !su_cs_cmp(fprnt, fpmdhexbuf))){
               n_err(_("TLS fingerprint does not match: %s\n"
                     "  Expected: %s\n  Detected: %s\n"),
                  urlp->url_h_p.s, fprnt, fpmdhexbuf);
               stay = n_tls_verify_decide();
            }else if(n_poption & n_PO_D_V)
               n_err(_("TLS fingerprint ok\n"));
            goto jpeer_leave;
         }else if(urlp->url_cproto == CPROTO_CERTINFO)
            sop->s_tls_finger = savestrbuf(fpmdhexbuf,
                  P2UZ(cp - fpmdhexbuf));
      }

jpeer_leave:
      X509_free(peercert);
      if(!stay)
         goto jerr2;
   }

   if(n_poption & n_PO_D_V){
      struct a_xtls_protocol const *xpp;
      int ver;

      ver = SSL_version(sop->s_tls);
      for(xpp = &a_xtls_protocols[1] /* [0] == ALL */;; ++xpp)
         if(xpp->xp_version == ver || xpp->xp_last){
            n_err(_("TLS connection using %s / %s\n"),
               xpp->xp_name, SSL_get_cipher(sop->s_tls));
            break;
         }
   }

   sop->s_use_tls = 1;
jleave:
   /* We're fully setup: since we don't reuse the SSL_CTX (pooh) keep it local
    * and free it right now -- it is reference counted by sp->s_tls.. */
   SSL_CTX_free(ctxp);
j_leave:
   NYD_OU;
   return (sop->s_tls != NULL);
jerr2:
   SSL_free(sop->s_tls);
   sop->s_tls = NULL;
jerr1:
   if(confp != NULL)
      a_xtls_conf_finish(&confp, TRU1);
   goto jleave;
}

FL void
ssl_gen_err(char const *fmt, ...)
{
   va_list ap;
   NYD_IN;

   va_start(ap, fmt);
   n_verr(fmt, ap);
   va_end(ap);

   n_err(_(": %s\n"), ERR_error_string(ERR_get_error(), NULL));
   NYD_OU;
}

FL int
c_verify(void *vp)
{
   int *msgvec = vp, *ip, ec = 0, rv = 1;
   X509_STORE *store = NULL;
   char *ca_dir, *ca_file;
   NYD_IN;

   a_xtls_init();

   n_tls_verify_level = n_TLS_VERIFY_STRICT;
   if ((store = X509_STORE_new()) == NULL) {
      ssl_gen_err(_("Error creating X509 store"));
      goto jleave;
   }
   X509_STORE_set_verify_cb_func(store, &a_xtls_verify_cb);

   if ((ca_dir = ok_vlook(smime_ca_dir)) != NULL)
      ca_dir = fexpand(ca_dir, FEXP_LOCAL | FEXP_NOPROTO);
   if ((ca_file = ok_vlook(smime_ca_file)) != NULL)
      ca_file = fexpand(ca_file, FEXP_LOCAL | FEXP_NOPROTO);

   if((ca_dir != NULL || ca_file != NULL) &&
         X509_STORE_load_locations(store, ca_file, ca_dir) != 1){
      char const *m1, *m2, *m3;

      if(ca_dir != NULL){
         m1 = ca_dir;
         m2 = (ca_file != NULL) ? _(" or ") : n_empty;
      }else
         m1 = m2 = n_empty;
      m3 = (ca_file != NULL) ? ca_file : n_empty;
      ssl_gen_err(_("Error loading %s%s%s\n"), m1, m2, m3);
      goto jleave;
   }

   /* C99 */{
      boole xv15;

      if((xv15 = ok_blook(smime_no_default_ca)))
         n_OBSOLETE(_("please use *smime-ca-no-defaults*, "
            "not *smime-no-default-ca*"));
      if(!ok_blook(smime_ca_no_defaults) && !xv15 &&
            X509_STORE_set_default_paths(store) != 1) {
         ssl_gen_err(_("Error loading built-in default CA locations\n"));
         goto jleave;
      }
   }

   if (load_crls(store, ok_v_smime_crl_file, ok_v_smime_crl_dir) != OKAY)
      goto jleave;

   a_xtls_ca_flags(store, ok_vlook(smime_ca_flags));

   srelax_hold();
   for (ip = msgvec; *ip != 0; ++ip) {
      struct message *mp = message + *ip - 1;
      setdot(mp);
      ec |= smime_verify(mp, *ip, NULL, store);
      srelax();
   }
   srelax_rele();

   if ((rv = ec) != 0)
      n_exit_status |= n_EXIT_ERR;
jleave:
   if (store != NULL)
      X509_STORE_free(store);
   NYD_OU;
   return rv;
}

FL FILE *
smime_sign(FILE *ip, char const *addr)
{
   FILE *rv, *sfp, *fp, *bp, *hp;
   X509 *cert = NULL;
   n_XTLS_STACKOF(X509) *chain = NULL;
   EVP_PKEY *pkey = NULL;
   BIO *bb, *sb;
   PKCS7 *pkcs7;
   EVP_MD const *md;
   char const *name;
   boole bail = FAL0;
   NYD_IN;

   ASSERT(addr != NULL);
   rv = sfp = fp = bp = hp = NULL;

   a_xtls_init();

   if (addr == NULL) {
      n_err(_("No *from* address for signing specified\n"));
      goto jleave;
   }
   if ((fp = smime_sign_cert(addr, NULL, 1, NULL)) == NULL)
      goto jleave;

   if ((pkey = PEM_read_PrivateKey(fp, NULL, &ssl_password_cb,
         savecat(addr, ".smime-cert-key"))) == NULL) {
      ssl_gen_err(_("Error reading private key from"));
      goto jleave;
   }

   rewind(fp);
   if ((cert = PEM_read_X509(fp, NULL, &ssl_password_cb,
         savecat(addr, ".smime-cert-cert"))) == NULL) {
      ssl_gen_err(_("Error reading signer certificate from"));
      goto jleave;
   }
   mx_fs_close(fp);
   fp = NULL;

   if ((name = _smime_sign_include_certs(addr)) != NULL &&
         !_smime_sign_include_chain_creat(&chain, name,
            savecat(addr, ".smime-include-certs")))
      goto jleave;

   name = NULL;
   if ((md = a_xtls_smime_sign_digest(addr, &name)) == NULL)
      goto jleave;

   if((sfp = mx_fs_tmp_open("smimesign", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
            mx_FS_O_REGISTER), NIL)) == NIL){
      n_perr(_("tempfile"), 0);
      goto jleave;
   }

   rewind(ip);
   if (smime_split(ip, &hp, &bp, -1, 0) == STOP)
      goto jleave;

   sb = NULL;
   pkcs7 = NULL;

   if ((bb = BIO_new_fp(bp, BIO_NOCLOSE)) == NULL ||
         (sb = BIO_new_fp(sfp, BIO_NOCLOSE)) == NULL) {
      ssl_gen_err(_("Error creating BIO signing objects"));
      bail = TRU1;
      goto jerr;
   }

#undef _X
#define _X  PKCS7_DETACHED | PKCS7_PARTIAL
   if ((pkcs7 = PKCS7_sign(NULL, NULL, chain, bb, _X)) == NULL) {
      ssl_gen_err(_("Error creating the PKCS#7 signing object"));
      bail = TRU1;
      goto jerr;
   }
   if (PKCS7_sign_add_signer(pkcs7, cert, pkey, md, _X) == NULL) {
      ssl_gen_err(_("Error setting PKCS#7 signing object signer"));
      bail = TRU1;
      goto jerr;
   }
   if (!PKCS7_final(pkcs7, bb, _X)) {
      ssl_gen_err(_("Error finalizing the PKCS#7 signing object"));
      bail = TRU1;
      goto jerr;
   }
#undef _X

   if (PEM_write_bio_PKCS7(sb, pkcs7) == 0) {
      ssl_gen_err(_("Error writing signed S/MIME data"));
      bail = TRU1;
      /*goto jerr*/
   }
jerr:
   if (pkcs7 != NULL)
      PKCS7_free(pkcs7);
   if (sb != NULL)
      BIO_free(sb);
   if (bb != NULL)
      BIO_free(bb);
   if (!bail) {
      rewind(bp);
      fflush_rewind(sfp);
      rv = smime_sign_assemble(hp, bp, sfp, name);
      hp = bp = sfp = NULL;
   }

jleave:
   if (chain != NULL)
      sk_X509_pop_free(chain, X509_free);
   if (cert != NULL)
      X509_free(cert);
   if (pkey != NULL)
      EVP_PKEY_free(pkey);
   if(fp != NIL)
      mx_fs_close(fp);
   if(hp != NIL)
      mx_fs_close(hp);
   if(bp != NIL)
      mx_fs_close(bp);
   if(sfp != NIL)
      mx_fs_close(sfp);
   NYD_OU;
   return rv;
}

FL FILE *
smime_encrypt(FILE *ip, char const *xcertfile, char const *to)
{
   FILE *rv, *yp, *fp, *bp, *hp;
   X509 *cert;
   PKCS7 *pkcs7;
   BIO *bb, *yb;
   n_XTLS_STACKOF(X509) *certs;
   EVP_CIPHER const *cipher;
   char *certfile;
   boole bail;
   NYD_IN;

   bail = FAL0;
   rv = yp = fp = bp = hp = NULL;

   if ((certfile = fexpand(xcertfile, FEXP_LOCAL | FEXP_NOPROTO)) == NULL)
      goto jleave;

   a_xtls_init();

   if ((cipher = _smime_cipher(to)) == NULL)
      goto jleave;

   if((fp = mx_fs_open(certfile, "r")) == NIL){
      n_perr(certfile, 0);
      goto jleave;
   }
   if ((cert = PEM_read_X509(fp, NULL, &ssl_password_cb, NULL)) == NULL) {
      ssl_gen_err(_("Error reading encryption certificate from %s"),
         n_shexp_quote_cp(certfile, FAL0));
      bail = TRU1;
   }
   if (bail)
      goto jleave;
   mx_fs_close(fp);
   fp = NULL;
   bail = FAL0;

   certs = sk_X509_new_null();
   sk_X509_push(certs, cert);

   if((yp = mx_fs_tmp_open("smimeenc", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
            mx_FS_O_REGISTER), NIL)) == NIL){
      n_perr(_("tempfile"), 0);
      goto jerr1;
   }

   rewind(ip);
   if (smime_split(ip, &hp, &bp, -1, 0) == STOP)
      goto jerr1;

   yb = NULL;
   if ((bb = BIO_new_fp(bp, BIO_NOCLOSE)) == NULL ||
         (yb = BIO_new_fp(yp, BIO_NOCLOSE)) == NULL) {
      ssl_gen_err(_("Error creating BIO encryption objects"));
      bail = TRU1;
      goto jerr2;
   }
   if ((pkcs7 = PKCS7_encrypt(certs, bb, cipher, 0)) == NULL) {
      ssl_gen_err(_("Error creating the PKCS#7 encryption object"));
      bail = TRU1;
      goto jerr2;
   }
   if (PEM_write_bio_PKCS7(yb, pkcs7) == 0) {
      ssl_gen_err(_("Error writing encrypted S/MIME data"));
      bail = TRU1;
      /* goto jerr2 */
   }
   PKCS7_free(pkcs7);

jerr2:
   if(bb != NIL)
      BIO_free(bb);
   if(yb != NIL)
      BIO_free(yb);
   mx_fs_close(bp);
   bp = NIL;
   if(!bail){
      fflush_rewind(yp);
      rv = smime_encrypt_assemble(hp, yp);
      hp = yp = NIL;
   }
jerr1:
   sk_X509_pop_free(certs, X509_free);

jleave:
   if(yp != NIL)
      mx_fs_close(yp);
   if(fp != NIL)
      mx_fs_close(fp);
   if(bp != NIL)
      mx_fs_close(bp);
   if(hp != NIL)
      mx_fs_close(hp);
   NYD_OU;
   return rv;
}

FL struct message *
smime_decrypt(struct message *m, char const *to, char const *cc,
   boole signcall)
{
   char const *myaddr;
   long size;
   struct message *rv;
   FILE *bp, *hp, *op;
   PKCS7 *pkcs7;
   BIO *ob, *bb, *pb;
   X509 *cert;
   EVP_PKEY *pkey;
   FILE *yp;
   NYD_IN;

   pkey = NULL;
   cert = NULL;
   ob = bb = pb = NULL;
   pkcs7 = NULL;
   bp = hp = op = NULL;
   rv = NULL;
   size = m->m_size;

   if((yp = setinput(&mb, m, NEED_BODY)) == NULL)
      goto jleave;

   a_xtls_init();

   if((op = smime_sign_cert(to, cc, 0, &myaddr)) != NULL){
      pkey = PEM_read_PrivateKey(op, NULL, &ssl_password_cb,
            savecat(myaddr, ".smime-cert-key"));
      if(pkey == NULL){
         ssl_gen_err(_("Error reading private key"));
         goto jleave;
      }

      rewind(op);
      if((cert = PEM_read_X509(op, NULL, &ssl_password_cb,
            savecat(myaddr, ".smime-cert-cert"))) == NULL){
         ssl_gen_err(_("Error reading decryption certificate"));
         goto jleave;
      }

      mx_fs_close(op);
      op = NULL;
   }

   if((op = mx_fs_tmp_open("smimed", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
            mx_FS_O_REGISTER), NIL)) == NIL){
      n_perr(_("tempfile"), 0);
      goto jleave;
   }

   if(smime_split(yp, &hp, &bp, size, 1) == STOP)
      goto jleave;

   if((ob = BIO_new_fp(op, BIO_NOCLOSE)) == NULL ||
         (bb = BIO_new_fp(bp, BIO_NOCLOSE)) == NULL){
      ssl_gen_err(_("Error creating BIO decryption objects"));
      goto jleave;
   }

   if((pkcs7 = SMIME_read_PKCS7(bb, &pb)) == NULL){
      ssl_gen_err(_("Error reading PKCS#7 object"));
      goto jleave;
   }

   if(PKCS7_type_is_signed(pkcs7)){
      if(signcall){
         setinput(&mb, m, NEED_BODY);
         rv = (struct message*)-1;
         goto jleave;
      }
      if(PKCS7_verify(pkcs7, NULL, NULL, NULL, ob,
            PKCS7_NOVERIFY | PKCS7_NOSIGS) != 1)
         goto jerr;
      fseek(hp, 0L, SEEK_END);
      fprintf(hp, "X-Encryption-Cipher: none\n");
      fflush_rewind(hp);
   }else if(pkey == NULL){
      n_err(_("No appropriate private key found\n"));
      goto jleave;
   }else if(cert == NULL){
      n_err(_("No appropriate certificate found\n"));
      goto jleave;
   }else if(PKCS7_decrypt(pkcs7, pkey, cert, ob, 0) != 1){
jerr:
      ssl_gen_err(_("Error decrypting PKCS#7 object"));
      goto jleave;
   }
   fflush_rewind(op);

   mx_fs_close(bp);
   bp = NIL;

   rv = smime_decrypt_assemble(m, hp, op);
   hp = op = NIL; /* xxx closed by decrypt_assemble */
jleave:
   if(op != NIL)
      mx_fs_close(op);
   if(hp != NIL)
      mx_fs_close(hp);
   if(bp != NIL)
      mx_fs_close(bp);
   if(bb != NIL)
      BIO_free(bb);
   if(ob != NIL)
      BIO_free(ob);
   if(pkcs7 != NIL)
      PKCS7_free(pkcs7);
   if(cert != NIL)
      X509_free(cert);
   if(pkey != NIL)
      EVP_PKEY_free(pkey);
   NYD_OU;
   return rv;
}

FL enum okay
smime_certsave(struct message *m, int n, FILE *op)
{
   struct message *x;
   char *to, *cc, *cnttype;
   int c, i;
   FILE *fp, *ip;
   off_t size;
   BIO *fb, *pb;
   PKCS7 *pkcs7;
   n_XTLS_STACKOF(X509) *certs, *chain = NULL;
   X509 *cert;
   enum okay rv = STOP;
   NYD_IN;

   pkcs7 = NULL;

   a_xtls_msgno = (uz)n;
jloop:
   to = hfield1("to", m);
   cc = hfield1("cc", m);
   cnttype = hfield1("content-type", m);

   if ((ip = setinput(&mb, m, NEED_BODY)) == NULL)
      goto jleave;

#undef _X
#undef _Y
#define _X     (sizeof("application/") -1)
#define _Y(X)  X, sizeof(X) -1
   if (cnttype && su_cs_starts_with_case(cnttype, "application/") &&
         (!su_cs_cmp_case_n(cnttype + _X, _Y("pkcs7-mime")) ||
          !su_cs_cmp_case_n(cnttype + _X, _Y("x-pkcs7-mime")))) {
#undef _Y
#undef _X
      if ((x = smime_decrypt(m, to, cc, 1)) == NULL)
         goto jleave;
      if (x != (struct message*)-1) {
         m = x;
         goto jloop;
      }
   }
   size = m->m_size;

   if((fp = mx_fs_tmp_open("smimecert", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
            mx_FS_O_REGISTER), NIL)) == NIL){
      n_perr(_("tempfile"), 0);
      goto jleave;
   }

   while (size-- > 0) {
      c = getc(ip);
      putc(c, fp);
   }
   fflush(fp);

   rewind(fp);
   if ((fb = BIO_new_fp(fp, BIO_NOCLOSE)) == NULL) {
      ssl_gen_err("Error creating BIO object for message %d", n);
      mx_fs_close(fp);
      goto jleave;
   }

   if ((pkcs7 = SMIME_read_PKCS7(fb, &pb)) == NULL) {
      ssl_gen_err(_("Error reading PKCS#7 object for message %d"), n);
      BIO_free(fb);
      mx_fs_close(fp);
      goto jleave;
   }
   BIO_free(fb);
   mx_fs_close(fp);

   certs = PKCS7_get0_signers(pkcs7, chain, 0);
   if (certs == NULL) {
      n_err(_("No certificates found in message %d\n"), n);
      goto jleave;
   }

   for (i = 0; i < sk_X509_num(certs); ++i) {
      cert = sk_X509_value(certs, i);
      if (X509_print_fp(op, cert) == 0 || PEM_write_X509(op, cert) == 0) {
         ssl_gen_err(_("Error writing certificate %d from message %d"),
            i, n);
         goto jleave;
      }
   }
   rv = OKAY;
jleave:
   if(pkcs7 != NULL)
      PKCS7_free(pkcs7);
   NYD_OU;
   return rv;
}

#include "su/code-ou.h"
#endif /* mx_HAVE_XTLS */
/* s-it-mode */
s-nail-14.9.15/src/ps-dotlock/000077500000000000000000000000001352610246600157315ustar00rootroot00000000000000s-nail-14.9.15/src/ps-dotlock/main.c000066400000000000000000000176701352610246600170340ustar00rootroot00000000000000/*@ S-nail - a mail user agent derived from Berkeley Mail.
 *@ Privilege-separated dot file lock program (OPT_DOTLOCK=yes)
 *@ that is capable of calling setuid(2), and change its user identity
 *@ to the VAL_PS_DOTLOCK_USER (usually "root") in order to create a
 *@ dotlock file with the same UID/GID as the mailbox to be locked.
 *@ It should be started when chdir(2)d to the lock file's directory,
 *@ with a symlink-resolved target and with SIGPIPE being ignored.
 *
 * Copyright (c) 2015 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE ps_dotlock_main
#define mx_SOURCE
#define mx_SOURCE_PS_DOTLOCK_MAIN

#define su_ASSERT_EXPAND_NOTHING

#include "mx/nail.h"

#include 
#include 

#if defined mx_HAVE_PRCTL_DUMPABLE
# include 
#elif defined mx_HAVE_PTRACE_DENY
# include 
#elif defined mx_HAVE_SETPFLAGS_PROTECT
# include 
#endif

#include "mx/file-locks.h"

/* TODO fake */
#include "su/code-in.h"

/* TODO Avoid linkage errors, instantiate what is needed;
 * TODO SU needs to be available as a library to overcome this,
 * TODO or a compiler capable of inlining can only be used */
uz su__state;
#ifdef su_MEM_ALLOC_DEBUG
boole su__mem_check(su_DBG_LOC_ARGS_DECL_SOLE) {return FAL0;}
boole su__mem_trace(su_DBG_LOC_ARGS_DECL_SOLE) {return FAL0;}
#endif
#define su_err_no() errno
#define su_err_set_no(X) (errno = X)

static void _ign_signal(int signum);
static uz n_msleep(uz millis, boole ignint);

#include "mx/file-dotlock.h" /* $(PS_DOTLOCK_SRCDIR) */

static void
_ign_signal(int signum){
   struct sigaction nact, oact;

   nact.sa_handler = SIG_IGN;
   sigemptyset(&nact.sa_mask);
   nact.sa_flags = 0;
   sigaction(signum, &nact, &oact);
}

static uz
n_msleep(uz millis, boole ignint){
   uz rv;

#ifdef mx_HAVE_NANOSLEEP
   /* C99 */{
      struct timespec ts, trem;
      int i;

      ts.tv_sec = millis / 1000;
      ts.tv_nsec = (millis %= 1000) * 1000 * 1000;

      while((i = nanosleep(&ts, &trem)) != 0 && ignint)
         ts = trem;
      rv = (i == 0) ? 0
            : (trem.tv_sec * 1000) + (trem.tv_nsec / (1000 * 1000));
   }

#elif defined mx_HAVE_SLEEP
   if((millis /= 1000) == 0)
      millis = 1;
   while((rv = sleep(S(ui,millis))) != 0 && ignint)
      millis = rv;
#else
# error Configuration should have detected a function for sleeping.
#endif
   return rv;
}

int
main(int argc, char **argv){
   char hostbuf[64];
   struct mx_file_dotlock_info fdi;
   struct stat stb;
   sigset_t nset, oset;
   enum mx_file_dotlock_state fdls;

   /* We're a dumb helper, ensure as much as we can noone else uses us */
   if(argc != 12 ||
         strcmp(argv[ 0], VAL_PS_DOTLOCK) ||
         (argv[1][0] != 'r' && argv[1][0] != 'w') ||
         strcmp(argv[ 1] + 1, "dotlock") ||
         strcmp(argv[ 2], "mailbox") ||
         strchr(argv[ 3], '/') != NULL /* Seal path injection.. */ ||
         strcmp(argv[ 4], "name") ||
         strcmp(argv[ 6], "hostname") ||
         strcmp(argv[ 8], "randstr") ||
         strchr(argv[ 9], '/') != NULL /* ..attack vector */ ||
         strcmp(argv[10], "pollmsecs") ||
         fstat(STDIN_FILENO, &stb) == -1 || !S_ISFIFO(stb.st_mode) ||
         fstat(STDOUT_FILENO, &stb) == -1 || !S_ISFIFO(stb.st_mode)){
jeuse:
      fprintf(stderr,
         "This is a helper program of " VAL_BINDIR "/" VAL_UAGENT ".\n"
         "  It is capable of gaining more privileges than " VAL_UAGENT "\n"
         "  and will be used to create lock files.\n"
         "  The sole purpose is outsourcing of high privileges into\n"
         "  fewest lines of code in order to reduce attack surface.\n"
         "  This program cannot be run by itself.\n");
      exit(n_EXIT_USE);
   }else{
      /* Prevent one more path injection attack vector, but be friendly */
      char const *ccp;
      uz i;
      char *cp, c;

      for(ccp = argv[7], cp = hostbuf, i = 0; (c = *ccp) != '\0'; ++cp, ++ccp){
         *cp = (c == '/' ? '_' : c);
         if(++i == sizeof(hostbuf) -1)
            break;
      }
      *cp = '\0';
      if(cp == hostbuf)
         goto jeuse;
      argv[7] = hostbuf;
   }

   fdi.fdi_file_name = argv[3];
   fdi.fdi_lock_name = argv[5];
   fdi.fdi_hostname = argv[7];
   fdi.fdi_randstr = argv[9];
   fdi.fdi_pollmsecs = S(uz,strtoul(argv[11], NULL, 10)); /* XXX SU */

   /* Ensure the lock name and the file name are identical */
   /* C99 */{
      uz i;

      i = strlen(fdi.fdi_file_name);
      if(i == 0 || strncmp(fdi.fdi_file_name, fdi.fdi_lock_name, i) ||
            fdi.fdi_lock_name[i] == '\0' ||
            strcmp(fdi.fdi_lock_name + i, ".lock"))
         goto jeuse;
   }

   /* Ensure that we got some random string, and some hostname.
    * a_dotlock_create() will later ensure that it will produce some string
    * not-equal to .fdi_lock_name if it is called by us */
   if(fdi.fdi_hostname[0] == '\0' || fdi.fdi_randstr[0] == '\0')
      goto jeuse;

   close(STDERR_FILENO);

   /* In order to prevent stale lock files at all cost block any signals until
    * we have unlinked the lock file.
    * It is still not safe because we may be SIGKILLed and may linger around
    * because we have been SIGSTOPped, but unfortunately the standard doesn't
    * give any option, e.g. atcrash() or open(O_TEMPORARY_KEEP_NAME) or so, ---
    * and then again we should not unlink(2) the lock file unless our parent
    * has finalized the synchronization!  While at it, let me rant about the
    * default action of realtime signals, program termination */
   _ign_signal(SIGPIPE); /* (Inherited, though) */
   sigfillset(&nset);
   sigdelset(&nset, SIGCONT); /* (Rather redundant, though) */
   sigprocmask(SIG_BLOCK, &nset, &oset);

   fdls = mx_FILE_DOTLOCK_STATE_NOPERM | mx_FILE_DOTLOCK_STATE_ABANDON;

   /* First of all: we only dotlock when the executing user has the necessary
    * rights to access the mailbox */
   if(access(fdi.fdi_file_name, (argv[1][0] == 'r' ? R_OK : R_OK | W_OK)))
      goto jmsg;

   /* We need UID and GID information about the mailbox to lock */
   if(stat(fdi.fdi_file_name, fdi.fdi_stb = &stb) == -1)
      goto jmsg;

   fdls = mx_FILE_DOTLOCK_STATE_PRIVFAILED | mx_FILE_DOTLOCK_STATE_ABANDON;

   /* We are SETUID and do not want to become traced or being attached to */
#if defined mx_HAVE_PRCTL_DUMPABLE
   if(prctl(PR_SET_DUMPABLE, 0))
      goto jmsg;
#elif defined mx_HAVE_PTRACE_DENY
   if(ptrace(PT_DENY_ATTACH, 0, 0, 0) == -1)
      goto jmsg;
#elif defined mx_HAVE_SETPFLAGS_PROTECT
   if(setpflags(__PROC_PROTECT, 1))
      goto jmsg;
#endif

   /* This helper is only executed when really needed, it thus doesn't make
    * sense to try to continue with initial privileges */
   if(setuid(geteuid()))
      goto jmsg;

   fdls = a_file_lock_dotlock_create(&fdi);

   /* Finally: notify our parent about the actual lock state.. */
jmsg:
   write(STDOUT_FILENO, &fdls, sizeof fdls);
   close(STDOUT_FILENO);

   /* ..then eventually wait until we shall remove the lock again, which will
    * be notified via the read returning */
   if(fdls == mx_FILE_DOTLOCK_STATE_NONE){
      read(STDIN_FILENO, &fdls, sizeof fdls);

      unlink(fdi.fdi_lock_name);
   }

   sigprocmask(SIG_SETMASK, &oset, NULL);
   return (fdls == mx_FILE_DOTLOCK_STATE_NONE ? n_EXIT_OK : n_EXIT_ERR);
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/su/000077500000000000000000000000001352610246600143015ustar00rootroot00000000000000s-nail-14.9.15/src/su/.main.cc000066400000000000000000000264711352610246600156240ustar00rootroot00000000000000// Very primitive C++ compile and run instantiator for SU C++ wrappers.

// Sometimes we need a loop limit, e.g., when putting elements in containers
#define a_LOOP_NO 1000

// Call funs which produce statistical output
#define a_STATS(X) //X

// Memory trace on program exit?
//#define a_TRACE

#include 
su_USECASE_MX_DISABLED

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
NSPC_USE(su)

#define a_ERR() \
      do {log::write(log::alert, "%u\n", __LINE__); ++a_errors;} while(0)

static uz a_errors;

//static void a_cs(void); FIXME
static void a_cs_dict(void);
   static void a__cs_dict(u32 addflags);
   static void a__cs_dict_case(cs_dict *cdp, char const *k[3]);
static void a_icodec(void);
static void a_mem_bag(void);
static void a_prime(void);
static void a_sort(void);
static void a_utf(void);

int main(void){
   state::set_program("SU/C++");
   state::set(state::debug);

   log::set_level(log::debug);
   if(log::get_show_level())
      a_ERR();
   log::set_show_level(TRU1);
   if(!log::get_show_level())
      a_ERR();

   if(log::get_show_pid())
      a_ERR();
   log::set_show_pid(TRU1);
   if(!log::get_show_pid())
      a_ERR();

   log::write(log::info, "Redemption songs\n");

   /// Basics (isolated)

   a_prime();
   a_utf();

   /// Basics (building upon other basics)

   a_icodec();
   a_mem_bag();
   a_sort();

   /// Extended

   a_cs_dict();

#ifdef a_TRACE
   mem::trace();
#endif
   log::write(log::info, (a_errors == 0 ? "These songs of freedom\n"
      : "Not to be heard\n"));
   return (a_errors != 0);
}

static void
a_cs_dict(void){
   a__cs_dict(cs_dict::f_none);
   a__cs_dict(cs_dict::f_pow2_spaced);
}

static void
a__cs_dict(u32 addflags){
   {
      cs_dict cd(NIL, addflags);
      char const *k[3];

      k[0] = "k1";
      k[1] = "k2";
      k[2] = "k3";

      a__cs_dict_case(&cd, k);
   }
   {
      cs_dict cd(NIL, cd.f_case | addflags);
      char const *k[3];

      k[0] = "K1";
      k[1] = "K2";
      k[2] = "K3";

      a__cs_dict_case(&cd, k);
   }

   /// Let's do some flag stuff and "big data"
   u32 u32;

   char buf[ienc::buffer_size], *cp;

   cs_dict cdu(NIL, addflags);
   cs_dict cdo(auto_type_toolbox::get_instance());

   cdo.set_treshold_shift(4).add_flags(cdo.f_head_resort | addflags);

   for(u32 = 0; u32++ < a_LOOP_NO;){
      if((cp = ienc::convert(buf, u32)) == NIL){
         a_ERR();
         break;
      }
      if(cdu.insert(cp, R(NSPC(su)up*,u32)) != 0)
         a_ERR();
      if(cdo.insert(cp, cp) != 0)
         a_ERR();
   }
   if(cdu.count() != a_LOOP_NO)
      a_ERR();
   if(cdo.count() != a_LOOP_NO)
      a_ERR();

   // (value really duped?)
   u32 = 0;
   for(cs_dict::view cdov(cdo); cdov; ++u32, ++cdov){
      if(cs::cmp(cdov.key(), cdov.data()))
         a_ERR();
      if(!cdu.has_key(cdov.key()))
         a_ERR();
      else if((cp = ienc::convert(buf, R(NSPC(su)up,cdu.lookup(cdov.key())))
            ) == NIL)
         a_ERR();
      else if(cs::cmp(cdov.key(), cp))
         a_ERR();
   }
   if(u32 != a_LOOP_NO)
      a_ERR();

   a_STATS( cdo.statistics(); )

   cdo.clear().add_flags(cdu.f_frozen);
   for(u32 = 0; u32++ < a_LOOP_NO;){
      if((cp = ienc::convert(buf, u32)) == NIL){
         a_ERR();
         break;
      }
      if(cdo.insert(cp, cp) != 0)
         a_ERR();
   }
   if(cdo.count() != a_LOOP_NO)
      a_ERR();

   u32 = 0;
   for(cs_dict::view cdov(cdo); cdov; ++u32, ++cdov)
      if(cs::cmp(cdov.key(), cdov.data()))
         a_ERR();
   if(u32 != a_LOOP_NO)
      a_ERR();

   a_STATS( cdo.statistics(); )

   if(cdo.set_treshold_shift(2).balance().count() != a_LOOP_NO)
      a_ERR();

   a_STATS( cdo.statistics(); )

   u32 = 0;
   for(cs_dict::view cdov(cdo); cdov; ++u32, ++cdov)
      if(cs::cmp(cdov.key(), cdov.data()))
         a_ERR();
   if(u32 != a_LOOP_NO)
      a_ERR();

   {
      cs_dict cdo2(cdo);
      if(cdo2.count() != cdo.count())
         a_ERR();
   }
   {
      static type_toolbox const xtb = su_TYPE_TOOLBOX_I9R(
            (type_toolbox::clone_fun)0x1,
            (type_toolbox::delete_fun)0x2,
            (type_toolbox::assign_fun)0x3,
            NIL, NIL);
      typedef cs_dict csd;

      csd *cdo2 = su_NEW(csd)(&xtb);
      if(cdo2->assign(cdo) != 0)
         a_ERR();
      if(cdo2->count() != cdo.count())
         a_ERR();
      su_DEL(cdo2);
   }
   {
      cs_dict cdo2(auto_type_toolbox::get_instance());
      if(cdo2.assign_elems(cdo) != 0)
         a_ERR();
      if(cdo2.count() != cdo.count())
         a_ERR();
   }
}

static void
a__cs_dict_case(cs_dict *cdp, char const *k[3]){
   // basics
   if(!cdp->is_empty())
      a_ERR();
   if(cdp->toolbox() != NIL)
      a_ERR();

   s32 s32 = cdp->insert(k[0], "v1");
   if(s32 != 0)
      a_ERR();
      s32 = cdp->insert(k[0], "v1-no");
      if(s32 != -1)
         a_ERR();
      s32 = cdp->replace("k1", "v1-yes");
      if(s32 != -1)
         a_ERR();
   s32 = cdp->insert(k[1], "v2");
   if(s32 != 0)
      a_ERR();
   s32 = cdp->insert(k[2], "v3");
   if(s32 != 0)
      a_ERR();

   if(cdp->count() != 3)
      a_ERR();
   if(cdp->is_empty())
      a_ERR();

   if(!cdp->remove(k[1]))
      a_ERR();

   if(!cdp->has_key(k[0]))
      a_ERR();
      char const *ccp = cdp->lookup(k[0]);
      if(ccp == NIL)
         a_ERR();
      else if(cs::cmp(ccp, "v1-yes"))
         a_ERR();
   if(cdp->has_key(k[1]))
      a_ERR();
      ccp = cdp->lookup("k2");
      if(ccp != NIL)
         a_ERR();
   if(!cdp->has_key(k[2]))
      a_ERR();
      ccp = cdp->lookup("k3");
      if(ccp == NIL)
         a_ERR();
      else if(cs::cmp(ccp, "v3"))
         a_ERR();

   {
      cs_dict cd2(*cdp);

      if(cd2.count() != 2)
         a_ERR();
      if(!cdp->clear_elems().is_empty())
         a_ERR();

      if(!cd2.has_key(k[0]))
         a_ERR();
         ccp = cd2.lookup("k1");
         if(ccp == NIL)
            a_ERR();
         else if(cs::cmp(ccp, "v1-yes"))
            a_ERR();

      if(!cd2.has_key(k[2]))
         a_ERR();
         ccp = cd2.lookup("k3");
         if(ccp == NIL)
            a_ERR();
         else if(cs::cmp(ccp, "v3"))
            a_ERR();

      if(cdp->assign_elems(cd2) != 0)
         a_ERR();
   }

   s32 = cdp->insert(k[1], "v2");
   if(s32 != 0)
      a_ERR();
      ccp = cdp->lookup("k2");
      if(ccp == NIL)
         a_ERR();
      else if(cs::cmp(ccp, "v2"))
         a_ERR();

   // view
   cs_dict::view cdv(*cdp), cdv2(cdv);
   u32 u32;
   for(u32 = 0; cdv; ++u32, ++cdv2, ++cdv){
      char const *xk, *v;

      if(!cs::cmp(cdv.key(), xk = "k1"))
         v = "v1-yes";
      else if(!cs::cmp(cdv.key(), xk = "k2"))
         v = "v2";
      else if(!cs::cmp(cdv.key(), xk = "k3"))
         v = "v3";
      else{
         a_ERR();
         continue;
      }
      if(cs::cmp(cdv.key(), cdv2.key()))
         a_ERR();

      if(!cdp->has_key(xk))
         a_ERR();

      if(cs::cmp(v, cdv.data()))
         a_ERR();
      if(cs::cmp(cdv.data(), cdv2.data()))
         a_ERR();
   }
   if(cdv2)
      a_ERR();
   if(u32 != 3)
      a_ERR();

   if(!cdv.find(k[1]))
      a_ERR();
   if(cdv.remove().find("k2"))
      a_ERR();

   for(u32 = 0, cdv.begin(); cdv.is_valid(); ++u32, ++cdv){
      char const *xk, *v;

      if(!cs::cmp(cdv.key(), xk = "k1"))
         v = "v1-yes";
      else if(!cs::cmp(cdv.key(), xk = "k3"))
         v = "v3";
      else{
         a_ERR();
         continue;
      }
      if(!cdp->has_key(xk))
         a_ERR();

      if(cs::cmp(v, cdv.data()))
         a_ERR();

      if(!cdv.has_next() && u32 < 1)
         a_ERR();
   }
   if(u32 != 2)
      a_ERR();

   if(!cdv.find(k[2]))
      a_ERR();
   if(cdv.set_data("v3-newnewnew") != 0)
      a_ERR();
   if(cdp->count() != 2)
      a_ERR();
   if(cs::cmp(cdv.data(), "v3-newnewnew"))
      a_ERR();
   if(cs::cmp(*cdv, "v3-newnewnew"))
      a_ERR();
}

static void
a_icodec(void){
   char buf[ienc::buffer_size];

   u32 u32 = 0xAFFEDEADu;
   char const *ccp;
   if((ccp = ienc::convert(buf, u32)) == NIL)
      a_ERR();
   if(cs::cmp(ccp, "2952715949"))
      a_ERR();
   if((idec::convert(&u32, ccp, max::uz, 0, idec::mode_limit_32bit, &ccp
            ) & (idec::state_emask | idec::state_consumed)
         ) != idec::state_consumed)
      a_ERR();
   if(*ccp != '\0')
      a_ERR();
   if(u32 != 0xAFFEDEADu)
      a_ERR();
   if((ccp = ienc::convert(buf, u32, 0x10)) == NIL)
      a_ERR();
   else if(cs::cmp(ccp, "0xAFFEDEAD"))
      a_ERR();
   else if((idec::convert(&u32, ccp, max::uz, 0, idec::mode_limit_32bit, &ccp
            ) & (idec::state_emask | idec::state_consumed)
         ) != idec::state_consumed)
      a_ERR();
   else if(*ccp != '\0')
      a_ERR();
   if(u32 != 0xAFFEDEADu)
      a_ERR();

   u64 u64 = (S(NSPC(su)u64,u32) << 32) | 0xABBABEEF;
   if((ccp = ienc::convert(buf, u64)) == NIL)
      a_ERR();
   else if(cs::cmp(ccp, "12681818438213746415"))
      a_ERR();
   else if((idec::convert(&u64, ccp, max::uz, 0, idec::mode_none, &ccp
            ) & (idec::state_emask | idec::state_consumed)
         ) != idec::state_consumed)
      a_ERR();
   else if(*ccp != '\0')
      a_ERR();
   if(u64 != su_U64_C(0xAFFEDEADABBABEEF))
      a_ERR();
   if((ccp = ienc::convert(buf, u64, 0x10)) == NIL)
      a_ERR();
   else if(cs::cmp(ccp, "0xAFFEDEADABBABEEF"))
      a_ERR();
   else if((idec::convert(&u64, ccp, max::uz, 0, idec::mode_none, &ccp
            ) & (idec::state_emask | idec::state_consumed)
         ) != idec::state_consumed)
      a_ERR();
   else if(*ccp != '\0')
      a_ERR();
   if(u64 != su_U64_C(0xAFFEDEADABBABEEF))
      a_ERR();
}

static void
a_mem_bag(void){ // TODO only instantiation test yet
#ifdef su_HAVE_MEM_BAG
   mem_bag *mb;

   mb = su_NEW(mem_bag);

# ifdef su_HAVE_MEM_BAG_AUTO
   mb->auto_allocate(10);
# endif

# ifdef su_HAVE_MEM_BAG_LOFI
   void *lvp = mb->lofi_allocate(10);

   mb->lofi_free(lvp);
# endif

   su_DEL(&mb->reset());
#endif // su_HAVE_MEM_BAG
}

static void
a_prime(void){
   u32 u32 = prime::lookup_next(0);
   if(u32 != prime::lookup_min)
      a_ERR();
   u64 u64 = prime::get_next(u32);
   if(u32 == u64 || (u32 == 2 && u64 != 3))
      a_ERR();

   u32 = prime::lookup_former(max::u32);
   if(u32 != prime::lookup_max)
      a_ERR();
   u64 = prime::get_former(u32 + 1);
   if(u32 != u64)
      a_ERR();
}

static void
a_sort(void){
   char const *arr_sorted[] = {
         "albert", "berta", "david", "emil",
         "friedrich", "gustav", "heinrich", "isidor"
   }, *arr_mixed[] = {
         "gustav", "david", "isidor", "friedrich",
         "berta", "albert", "heinrich", "emil"
   };

   sort::shell(arr_mixed, NELEM(arr_mixed), &cs::cmp);

   for(uz i = NELEM(arr_sorted); i-- != 0;)
      if(cs::cmp(arr_sorted[i], arr_mixed[i]))
         a_ERR();
}

static void
a_utf(void){
   char buf[utf8::buffer_size];

   char const *ccp = utf8::replacer;
   uz i = sizeof(utf8::replacer) -1;
   u32 u32 = utf8::convert_to_32(&ccp, &i);
   if(u32 != utf32::replacer)
      a_ERR();
   if(i != 0 || *ccp != '\0')
      a_ERR();

   i = utf32::convert_to_8(u32, buf);
   if(i != 3 || buf[i] != '\0')
      a_ERR();
   if(cs::cmp(buf, utf8::replacer))
      a_ERR();
}

#include 
// s-it-mode
s-nail-14.9.15/src/su/.makefile000066400000000000000000000025511352610246600160620ustar00rootroot00000000000000#@ .makefile, solely for creating the C++ .main.cc test program
#@ With CC=tcc, AR=tcc ARFLAGS=-ar!

su_USECASE_MX_DISABLED =

awk?=awk
getconf?=getconf

CXXFLAGS+=-Wall -pedantic -Dsu_HAVE_DEVEL -Dsu_HAVE_DEBUG
CFLAGS+=-Wall -pedantic -Dsu_HAVE_DEVEL -Dsu_HAVE_DEBUG

CSRC = avopt.c core-code.c core-errors.c \
	cs-alloc.c cs-ctype.c cs-dict.c cs-find.c cs-misc.c \
		cs-tbox.c cs-tools.c \
	icodec-dec.c icodec-enc.c \
	mem-alloc.c mem-bag.c mem-tools.c \
	prime.c sort.c utf.c
CXXSRC = cxx-core.cc \
	.main.cc

## 8< >8

.SUFFIXES: .o .c .cc .y
.cc.o:
	$(CXX) -Dsu_USECASE_SU -I../../src -I../../include $(CXXFLAGS) -o $(@) -c $(<)
.c.o:
	$(CC) -Dsu_USECASE_SU -I../../src -I../../include $(CFLAGS) -o $(@) -c $(<)
.cc .c .y: ;

COBJ = $(CSRC:.c=.o)
CXXOBJ = $(CXXSRC:.cc=.o)
OBJ = $(COBJ) $(CXXOBJ)

all: .main
clean:
	rm -f ../../include/su/gen-config.h .main .tmp* .clib.a $(OBJ)

$(COBJ): $(CSRC) ../../include/su/gen-config.h
.clib.a: $(COBJ)
	$(AR) $(ARFLAGS) $(@) $(COBJ)
$(CXXOBJ): $(CLIB) ../../include/su/gen-config.h
.main: $(CXXOBJ) .clib.a
	$(CXX) $(LDFLAGS) -o $(@) $(CXXOBJ) .clib.a

../../include/su/gen-config.h:
	SRCDIR=`dirname \`pwd\``/ TARGET="$(@)" awk="$(awk)" \
		$(SHELL) ../../mk/su-make-errors.sh config > .tmp.c &&\
	$(CC) -o .tmp .tmp.c &&\
	./.tmp > $(@) &&\
	rm -f ./.tmp* &&\
	echo '#define su_PAGE_SIZE '"`$(getconf) PAGESIZE`" >> $(@)

# s-mk-mode
s-nail-14.9.15/src/su/avopt.c000066400000000000000000000313131352610246600155770ustar00rootroot00000000000000/*@ Implementation of avopt.h.
 *
 * Copyright (c) 2001 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE su_avopt
#define su_SOURCE
#define su_SOURCE_AVOPT

#include "su/code.h"

#include "su/cs.h"
#include "su/mem.h"

#include "su/avopt.h"
#include "su/code-in.h"

enum a_avopt_flags{
   a_AVOPT_NONE,
   a_AVOPT_DONE = 1u<<0,
   a_AVOPT_SHORT = 1u<<1,
   a_AVOPT_LONG = 1u<<2,
   a_AVOPT_FOLLOWUP = 1u<<3,
   a_AVOPT_CURR_LONG = 1u<<4,
   a_AVOPT_ERR_OPT = 1u<<5,
   a_AVOPT_ERR_ARG = 1u<<6,
   a_AVOPT_ROUND_MASK = a_AVOPT_CURR_LONG | a_AVOPT_ERR_OPT | a_AVOPT_ERR_ARG
};

char const su_avopt_fmt_err_arg[] = N_("option requires argument: %s\n");
char const su_avopt_fmt_err_opt[] = N_("invalid option: %s\n");

struct su_avopt *
su_avopt_setup(struct su_avopt *self, u32 argc, char const * const *argv,
      char const *opts_short_or_nil, char const * const *opts_long_or_nil){
   NYD_IN;
   ASSERT(self);

   su_mem_set(self, 0, sizeof *self);

   ASSERT_NYD_EXEC(argc == 0 || argv != NIL, self->avo_flags = a_AVOPT_DONE);
   ASSERT_NYD_EXEC(opts_short_or_nil != NIL || opts_long_or_nil != NIL,
      self->avo_flags = a_AVOPT_DONE);

   self->avo_flags = (argc == 0 ? a_AVOPT_DONE : a_AVOPT_NONE) |
         (opts_short_or_nil != NIL ? a_AVOPT_SHORT : a_AVOPT_NONE) |
         (opts_long_or_nil != NIL ? a_AVOPT_LONG : a_AVOPT_NONE);
   self->avo_argc = argc;
   self->avo_argv = argv;
   self->avo_opts_short = opts_short_or_nil;
   self->avo_opts_long = opts_long_or_nil;

   /* Somewhat test the content of the option strings in debug code */
#if DVLOR(1, 0)
   /* C99 */{
      uz i;
      char const *cp;

      if(self->avo_flags & a_AVOPT_SHORT){
         if((cp = self->avo_opts_short)[0] == '\0'){
            self->avo_flags &= ~a_AVOPT_SHORT;
            su_log_write(su_LOG_CRIT, "su_avopt_setup(): empty short option "
               "string is unsupported\n");
         }else do{
            if(!su_cs_is_print(*cp)){
               self->avo_flags &= ~a_AVOPT_SHORT;
               su_log_write(su_LOG_CRIT, "su_avopt_setup(): invalid non-"
                  "printable bytes in short option string: %s\n",
                  self->avo_opts_short);
            }else if(*cp == '-'){
               self->avo_flags &= ~a_AVOPT_SHORT;
               su_log_write(su_LOG_CRIT, "su_avopt_setup(): invalid "
                  "hyphen-minus in short option string: %s\n",
                  self->avo_opts_short);
            }
         }while(*++cp != '\0');
      }

      if(self->avo_flags & a_AVOPT_LONG){
         if(self->avo_opts_long[0] == NIL){
            self->avo_flags &= ~a_AVOPT_LONG;
            su_log_write(su_LOG_CRIT, "su_avopt_setup(): empty long option "
               "array is unsupported\n");
         }else for(i = 0; (cp = self->avo_opts_long[i]) != NIL; ++i){
            if(cp[0] == '\0'){
               self->avo_flags &= ~a_AVOPT_LONG;
               su_log_write(su_LOG_CRIT, "su_avopt_setup(): empty long option "
                  "strings are unsupported\n");
            }else do{
               if(*cp == ';'){
                  /* Empty option, or no equivalence? */
                  if(cp == self->avo_opts_long[i] || cp[1] == '\0'){
jelong_semicolon:
                     self->avo_flags &= ~a_AVOPT_LONG;
                     su_log_write(su_LOG_CRIT, "su_avopt_setup(): invalid "
                        "semicolon usage in long option string: %s\n",
                        self->avo_opts_long[i]);
                  }
                  /* Could only indicate documentation string, too */
                  else if(cp[1] == ';'){
                     ;
                  }
                  /* Otherwise we require an equivalence, optionally followed
                   * by documentation */
                  else if(cp[2] != '\0' && cp[2] != ';'){
                     goto jelong_semicolon;
                  }else if(cp[1] == su_AVOPT_STATE_DONE ||
                        cp[1] == su_AVOPT_STATE_LONG ||
                        cp[1] == su_AVOPT_STATE_ERR_OPT ||
                        cp[1] == su_AVOPT_STATE_ERR_ARG){
                     self->avo_flags &= ~a_AVOPT_LONG;
                     su_log_write(su_LOG_CRIT, "su_avopt_setup(): long option "
                        "equivalence byte shadows enum su_avopt_state: %s\n",
                        self->avo_opts_long[i]);
                  }else if(self->avo_flags & a_AVOPT_SHORT){
                     char const *osp;
                     char sopt;
                     boole wantsopt;

                     wantsopt = (cp[-1] == ':');
                     sopt = cp[1];

                     for(osp = self->avo_opts_short; *osp != '\0'; ++osp){
                        if(*osp != sopt){
                           if(osp[1] == ':')
                              ++osp;
                        }else{
                           if((osp[1] == ':') != wantsopt){
                              self->avo_flags &= ~a_AVOPT_LONG;
                              su_log_write(su_LOG_CRIT, "su_avopt_setup(): "
                                 "long option %s argument, short does%s: %s\n",
                                 (wantsopt ? "wants" : "does not want"),
                                 (wantsopt ? " not" : su_empty),
                                 self->avo_opts_long[i]);
                           }
                           break;
                        }
                     }
                  }
                  break;
               }else if(*cp == ':'){
                  if(cp == self->avo_opts_long[i])
                     goto jelong_colon;
                  if(cp[1] != '\0'){
                     if(cp[1] == ';')
                        continue;
jelong_colon:
                     self->avo_flags &= ~a_AVOPT_LONG;
                     su_log_write(su_LOG_CRIT, "su_avopt_setup(): invalid "
                        "colon in long option string: %s\n",
                        self->avo_opts_long[i]);
                  }
                  break;
               }else if(*cp == '='){
                  self->avo_flags &= ~a_AVOPT_LONG;
                  su_log_write(su_LOG_CRIT, "su_avopt_setup(): invalid "
                     "equal-sign in long option string: %s\n",
                     self->avo_opts_long[i]);
               }else if(!su_cs_is_print(*cp)){
                  self->avo_flags &= ~a_AVOPT_LONG;
                  su_log_write(su_LOG_CRIT, "su_avopt_setup(): invalid non-"
                     "printable bytes in long option string: %s\n",
                     self->avo_opts_long[i]);
               }
            }while(*++cp != '\0');
         }
      }

      if(!(self->avo_flags & (a_AVOPT_SHORT | a_AVOPT_LONG)))
         self->avo_flags |= a_AVOPT_DONE;
   }
#endif /* DVLOR(1,0) */

   NYD_OU;
   return self;
}

s8
su_avopt_parse(struct su_avopt *self){
   u8 flags;
   char const *opt, *curr;
   s8 rv;
   NYD_IN;
   ASSERT(self);

   rv = su_AVOPT_STATE_DONE;
   curr = self->avo_curr;
   if((flags = self->avo_flags) & a_AVOPT_DONE)
      goto jleave;
   flags &= ~(a_AVOPT_ROUND_MASK);

   /* If follow up is set this necessarily is a short option */
   if(flags & a_AVOPT_FOLLOWUP){
      ASSERT(flags & a_AVOPT_SHORT);
      goto jshort;
   }

   /* We need an argv entry */
   if(self->avo_argc == 0){
      flags |= a_AVOPT_DONE;
      goto jleave;
   }

   /* Finished if not - or a plain -, which is to be kept in argc/argv!
    * Otherwise we consume this argc/argv entry */
   curr = *self->avo_argv;
   if(*curr != '-' || *++curr == '\0'){
      flags |= a_AVOPT_DONE;
      goto jleave;
   }
   --self->avo_argc;
   ++self->avo_argv;

   /* -- stops argument processing unless long options are enabled and
    * anything non-NUL follows */
   if(UNLIKELY(*curr == '-')){
      if(!(flags & a_AVOPT_LONG) || *++curr == '\0'){
         flags |= a_AVOPT_DONE;
         goto jleave;
      }else{
         /* This is a long option */
         char const * const *opta, *xcurr;

         flags |= a_AVOPT_CURR_LONG;
         rv = su_AVOPT_STATE_LONG;

         opta = self->avo_opts_long;
         for(;;){
jlong_outer:
            if((opt = *opta++) == NIL)
               goto jerropt;
            if(*opt != *(xcurr = curr))
               continue;

            while(*++opt != '\0')
               if(*opt == ':' || *opt == ';')
                  break;
               else if(*opt != *++xcurr)
                  goto jlong_outer;

            if(*++xcurr == '\0' || (*xcurr == '=' && *opt == ':'))
               break;
         }

         /* Have it: argument? */
         if(*opt == ':'){
            ++opt;
            if(*xcurr == '=')
               ++xcurr;
            else{
               if(self->avo_argc == 0)
                  goto jerrarg;
               --self->avo_argc;
               xcurr = *self->avo_argv++;
            }
         }else
            xcurr = NIL;
         self->avo_current_arg = xcurr;
         self->avo_current_long_idx = P2UZ(opta - self->avo_opts_long) - 1;

         /* Short option equivalence instead of su_AVOPT_STATE_LONG? */
         if(*opt == ';' && *++opt != ';')
            rv = *opt;
      }
   }else if(UNLIKELY((flags & a_AVOPT_SHORT) == 0))
      goto jerropt;
   else{
      /* A short option */
jshort:
      opt = self->avo_opts_short;
      rv = S(s8,*curr++);

      while(UCMP(8, *opt, !=, rv)){
         /* Skip argument spec. */
         if(opt[1] == ':')
            ++opt;
         /* If we do not know about this, skip entire ARGV entry! */
         if(UNLIKELY(*++opt == '\0'))
            goto jerropt;
      }

      /* Does this take an argument? */
      flags &= ~a_AVOPT_FOLLOWUP;
      if(opt[1] != ':'){
         if(*curr != '\0')
            flags |= a_AVOPT_FOLLOWUP;
         opt = NIL;
      }else{
         if(*curr == '\0'){
            if(self->avo_argc == 0)
               goto jerrarg;
            --self->avo_argc;
            curr = *self->avo_argv++;
         }
         opt = curr;
      }
      self->avo_current_arg = opt;
   }

jleave:
   self->avo_curr = curr;
   self->avo_current_opt = rv;
   self->avo_flags = flags;
   NYD_OU;
   return rv;

jerropt:
   if(flags & a_AVOPT_CURR_LONG)
      self->avo_current_err_opt = curr;
   else{
      self->avo_current_err_opt = self->avo__buf;
      self->avo__buf[0] = S(char,rv);
      self->avo__buf[1] = '\0';
   }
   flags &= ~a_AVOPT_FOLLOWUP;
   flags |= a_AVOPT_ERR_OPT;
   rv = su_AVOPT_STATE_ERR_OPT;
   goto jleave;

jerrarg:
   ASSERT(!(flags & a_AVOPT_FOLLOWUP));
   if(flags & a_AVOPT_CURR_LONG)
      self->avo_current_err_opt = curr;
   else{
      self->avo_current_err_opt = self->avo__buf;
      self->avo__buf[0] = S(char,rv);
      self->avo__buf[1] = '\0';
   }
   flags |= a_AVOPT_ERR_ARG;
   rv = su_AVOPT_STATE_ERR_ARG;
   goto jleave;
}

boole
su_avopt_dump_doc(struct su_avopt const *self,
      boole (*ptf)(up cookie, boole has_arg, char const *sopt,
         char const *lopt, char const *doc), up cookie){
   char s_so[8], s_lo[128];
   char const * const *opta, *opt, *cp;
   uz l;
   boole rv;
   NYD_IN;
   ASSERT(self);
   ASSERT_NYD_EXEC(ptf != NIL, rv = TRU1);

   rv = TRU1;

   if(self->avo_flags & a_AVOPT_LONG){
      s_so[2] = '\0';
      s_lo[0] = s_lo[1] = '-';

      for(opta = self->avo_opts_long; (opt = *opta++) != NIL;){
         cp = opt;

         while(*++opt != '\0')
            if(*opt == ':' || *opt == ';')
               break;
         l = P2UZ(opt - cp);
         if(l > sizeof(s_lo) - 2 -1)
            l = sizeof(s_lo) - 2 -1;
         su_mem_copy(&s_lo[2], cp, l);
         s_lo[l += 2] = '\0';

         /* Have it: argument? */
         if((rv = (*opt == ':')))
            ++opt;

         /* (Actual) Short option equivalence? */
         s_so[0] = '\0';
         if(*opt == ';' && *++opt != ';'){
            if(su_cs_is_print(*opt)){
               s_so[0] = '-';
               s_so[1] = *opt;
            }
            ++opt;
         }

         /* Documentation? */
         if(*opt == ';')
            ++opt;
         else
            opt = su_empty;

         rv = (*ptf)(cookie, rv, s_so, s_lo, opt);
      }
   }
   NYD_OU;
   return rv;
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/su/core-code.c000066400000000000000000000220531352610246600163070ustar00rootroot00000000000000/*@ Implementation of code.h: (unavoidable) basics.
 *@ TODO Log: domain should be configurable
 *@ TODO Assert: the C++ lib has per-thread assertion states, s_nolog to
 *@ TODO    suppress log, test_state(), test_and_clear_state(): for unit tests!
 *@ TODO su_program: if set, the PID should be logged, too!
 *
 * Copyright (c) 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE su_core_code
#define su_SOURCE
#define su_SOURCE_CORE_CODE
#define su_MASTER

#include "su/code.h"

#include  /* XXX Grrrr */
#include 
#include  /* TODO Get rid */
#include 
#include  /* TODO POSIX module! */

#include "su/icodec.h"

/*#include "su/code.h"*/
#include "su/code-in.h"

#define a_PRIMARY_DOLOG(LVL) \
   ((S(u32,LVL) /*& su__STATE_LOG_MASK*/) <= \
         (su__state & su__STATE_LOG_MASK) ||\
      (su__state & su__STATE_D_V))

#if defined su_HAVE_DEBUG || defined su_HAVE_DEVEL
struct a_core_nyd_info{
   char const *cni_file;
   char const *cni_fun;
   u32 cni_chirp_line;
   u32 cni_level;
};
#endif

static char const a_core_lvlnames[][8] = {
   FIELD_INITI(su_LOG_EMERG) "emerg",
   FIELD_INITI(su_LOG_ALERT) "alert",
   FIELD_INITI(su_LOG_CRIT) "crit",
   FIELD_INITI(su_LOG_ERR) "error",
   FIELD_INITI(su_LOG_WARN) "warning",
   FIELD_INITI(su_LOG_NOTICE) "notice",
   FIELD_INITI(su_LOG_INFO) "info",
   FIELD_INITI(su_LOG_DEBUG) "debug"
};
/* You can deduce the value from the offset */
CTAV(su_LOG_EMERG == 0);
CTAV(su_LOG_DEBUG == 7);

union su__bom_union const su__bom_little = {{'\xFF', '\xFE'}};
union su__bom_union const su__bom_big = {{'\xFE', '\xFF'}};

#if DVLOR(1, 0)
MT( static uz a_core_glock_recno[su__GLOCK_MAX +1]; )

static u32 a_core_nyd_curr, a_core_nyd_level;
static boole a_core_nyd_skip;
static struct a_core_nyd_info a_core_nyd_infos[su_NYD_ENTRIES];
#endif

uz su__state;

char const su_empty[] = "";
char const su_reproducible_build[] = "reproducible_build";
u16 const su_bom = su_BOM;

char const *su_program;

/* TODO Eventually all the I/O is SU based, then this will vanish!
 * TODO We need some _USECASE_ hook to store readily prepared lines then.
 * TODO Also, our log does not yet prepend "su_progam: " to each output line,
 * TODO because of all that (port FmtEncCtx, use rounds!!) */
su_SINLINE void a_evlog(enum su_log_level lvl, char const *fmt, va_list ap);

/* */
#if DVLOR(1, 0)
static void a_core_nyd_printone(void (*ptf)(up cookie, char const *buf,
      uz blen), up cookie, struct a_core_nyd_info const *cnip);
#endif

su_SINLINE void
a_evlog(enum su_log_level lvl, char const *fmt, va_list ap){
#ifdef su_USECASE_MX
# ifndef mx_HAVE_AMALGAMATION
   /*extern*/ void n_err(char const *fmt, ...);
   /*extern*/ void n_verr(char const *fmt, va_list ap);
# endif
#endif
   char buf[su_IENC_BUFFER_SIZE];
   char const *cp, *xfmt;

#ifdef su_USECASE_MX
   if(lvl != su_LOG_EMERG)
      goto jnostd;
#endif

   /* TODO ensure each line has the prefix */
   if(su_program != NIL){
      if(su_state_has(su_STATE_LOG_SHOW_PID)){
         cp = su_ienc_u32(buf, getpid(), 10);
         xfmt = "%s[%s]: ";
      }else{
         cp = su_empty;
         xfmt = "%s: ";
      }
      fprintf(stderr, xfmt, su_program, cp);
   }

   if(su_state_has(su_STATE_LOG_SHOW_LEVEL))
      fprintf(stderr, "[%s] ",
         a_core_lvlnames[S(u32,lvl) /*& su__STATE_LOG_MASK*/]);

   vfprintf(stderr, fmt, ap);

#ifdef su_USECASE_MX
   goto jnomx;
jnostd:
   n_verr(fmt, ap);
jnomx:
#endif

   if(lvl == su_LOG_EMERG)
      abort(); /* TODO configurable */
}

#if DVLOR(1, 0)
static void
a_core_nyd_printone(void (*ptf)(up cookie, char const *buf, uz blen),
      up cookie, struct a_core_nyd_info const *cnip){
   char buf[80], c;
   union {int i; uz z;} u;
   char const *sep, *cp;

   /* Ensure actual file name can be seen, unless multibyte comes into play */
   sep = su_empty;
   cp = cnip->cni_file;
   for(u.z = 0; (c = cp[u.z]) != '\0'; ++u.z)
      if(S(uc,c) & 0x80){
         u.z = 0;
         break;
      }
   if(u.z > 40){
      cp += -(38 - u.z);
      sep = "..";
   }

   u.i = snprintf(buf, sizeof(buf) - 1,
         "%c [%2" PRIu32 "] %.25s (%s%.40s:%" PRIu32 ")\n",
         "=><"[(cnip->cni_chirp_line >> 29) & 0x3], cnip->cni_level,
         cnip->cni_fun, sep, cp, (cnip->cni_chirp_line & 0x1FFFFFFFu));
   if(u.i > 0){
      u.z = u.i;
      if(u.z >= sizeof(buf) -1){
         buf[sizeof(buf) - 2] = '\n';
         buf[sizeof(buf) - 1] = '\0';
         u.z = sizeof(buf) -1; /* (Skip \0) */
      }
      (*ptf)(cookie, buf, u.z);
   }
}
#endif /* DVLOR(1, 0) */

#ifdef su_HAVE_MT
void
su__glock(enum su__glock_type gt){
   NYD2_IN;

   switch(gt){
   case su__GLOCK_STATE: /* XXX spinlock */
      break;
   case su__GLOCK_LOG: /* XXX mutex */
      break;
   }

# if DVLOR(1, 0)
   ASSERT(a_core_glock_recno[gt] != UZ_MAX);
   ++a_core_glock_recno[gt];
# endif
   NYD2_OU;
}

void
su__gunlock(enum su__glock_type gt){
   NYD2_IN;

   switch(gt){
   case su__GLOCK_STATE: /* XXX spinlock */
      break;
   case su__GLOCK_LOG: /* XXX mutex */
      break;
   }

# if DVLOR(1, 0)
   ASSERT(a_core_glock_recno[gt] > 0);
   --a_core_glock_recno[gt];
# endif
   NYD2_OU;
}
#endif /* su_HAVE_MT */

s32
su_state_err(enum su_state_err_type err, uz state, char const *msg_or_nil){
   static char const intro_nomem[] = N_("Out of memory: %s\n"),
      intro_overflow[] = N_("Datatype overflow: %s\n");

   enum su_log_level lvl;
   char const *introp;
   s32 eno;
   u32 xerr;
   NYD2_IN;

   if(msg_or_nil == NIL)
      msg_or_nil = N_("(no error information)");
   state &= su_STATE_ERR_MASK;

   xerr = err;
   switch(xerr &= su_STATE_ERR_TYPE_MASK){
   default:
      ASSERT(0);
      /* FALLTHRU */
   case su_STATE_ERR_NOMEM:
      eno = su_ERR_NOMEM;
      introp = intro_nomem;
      break;
   case su_STATE_ERR_OVERFLOW:
      eno = su_ERR_OVERFLOW;
      introp = intro_overflow;
      break;
   }

   lvl = su_LOG_EMERG;
   if(state & su_STATE_ERR_NOPASS)
      goto jdolog;
   if(state & su_STATE_ERR_PASS)
      lvl = su_LOG_DEBUG;
   else if((state & xerr) || su_state_has(xerr))
      lvl = su_LOG_ALERT;

   if(a_PRIMARY_DOLOG(lvl))
jdolog:
      su_log_write(lvl, V_(introp), V_(msg_or_nil));

   if(!(state & su_STATE_ERR_NOERRNO))
      su_err_set_no(eno);
   NYD2_OU;
   return eno;
}

s32
su_err_no(void){
   s32 rv;
   rv = errno; /* TODO a_core_eno */
   return rv;
}

s32
su_err_set_no(s32 eno){
   errno = eno; /* TODO a_core_eno; */
   return eno;
}

s32
su_err_no_via_errno(void){
   s32 rv;
   rv = /*TODO a_core_eno =*/errno;
   return rv;
}

void
su_log_write(enum su_log_level lvl, char const *fmt, ...){
   va_list va;
   NYD_IN;

   if(a_PRIMARY_DOLOG(lvl)){
      va_start(va, fmt);
      a_evlog(lvl, fmt, va);
      va_end(va);
   }
   NYD_OU;
}

void
su_log_vwrite(enum su_log_level lvl, char const *fmt, void *vp){
   NYD_IN;

   if(a_PRIMARY_DOLOG(lvl))
      a_evlog(lvl, fmt, *S(va_list*,vp));
   NYD_OU;
}

void
su_assert(char const *expr, char const *file, u32 line, char const *fun,
      boole crash){
   su_log_write((crash ? su_LOG_EMERG : su_LOG_ALERT),
      "SU assert failed: %.60s\n"
      "  File %.60s, line %" PRIu32 "\n"
      "  Function %.142s\n",
      expr, file, line, fun);
}

#if DVLOR(1, 0)
void
su_nyd_chirp(u8 act, char const *file, u32 line, char const *fun){
   if(!a_core_nyd_skip){
      struct a_core_nyd_info *cnip;

      cnip = &a_core_nyd_infos[0];

      if(a_core_nyd_curr != su_NELEM(a_core_nyd_infos))
         cnip += a_core_nyd_curr++;
      else
         a_core_nyd_curr = 1;
      cnip->cni_file = file;
      cnip->cni_fun = fun;
      cnip->cni_chirp_line = (S(u32,act & 0x3) << 29) | (line & 0x1FFFFFFFu);
      cnip->cni_level = ((act == 0) ? a_core_nyd_level /* TODO spinlock */
            : (act == 1) ? ++a_core_nyd_level : a_core_nyd_level--);
   }
}

void
su_nyd_stop(void){
   a_core_nyd_skip = TRU1;
}

void
su_nyd_dump(void (*ptf)(up cookie, char const *buf, uz blen), up cookie){
   uz i;
   struct a_core_nyd_info const *cnip;

   a_core_nyd_skip = TRU1;
   if(a_core_nyd_infos[su_NELEM(a_core_nyd_infos) - 1].cni_file != NULL)
      for(i = a_core_nyd_curr, cnip = &a_core_nyd_infos[i];
            i < su_NELEM(a_core_nyd_infos); ++i)
         a_core_nyd_printone(ptf, cookie, cnip++);
   for(i = 0, cnip = a_core_nyd_infos; i < a_core_nyd_curr; ++i)
      a_core_nyd_printone(ptf, cookie, cnip++);
}
#endif /* DVLOR(1, 0) */

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/su/core-errors.c000066400000000000000000000100101352610246600166770ustar00rootroot00000000000000/*@ Implementation of code.h: error (number) handling.
 *
 * Copyright (c) 2017 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE su_core_errors
#define su_SOURCE
#define su_SOURCE_CORE_ERRORS

#include "su/cs.h"
#include "su/icodec.h"

#include "su/code.h"
#include "su/code-in.h"

struct a_corerr_map{
   u32 cem_hash;     /* Hash of name */
   u32 cem_nameoff;  /* Into a_corerr_names[] */
   u32 cem_docoff;   /* Into a_corerr_docs[] */
   s32 cem_errno;    /* OS errno value for this one */
};

/* Include the constant su-make-errors.sh output */
#include "su/gen-errors.h" /* $(SU_SRCDIR) */

/* And these come in via su/config.h (config-time su-make-errors.sh output) */
static su__ERR_NUMBER_TYPE const a_corerr_no2mapoff[][2] = {
#undef a_X
#define a_X(N,I) {N,I},
su__ERR_NUMBER_TO_MAPOFF
#undef a_X
};

/* Find the descriptive mapping of an error number, or _ERR_INVAL */
static struct a_corerr_map const *a_corerr_map_from_no(s32 eno);

static struct a_corerr_map const *
a_corerr_map_from_no(s32 eno){
   s32 ecmp;
   uz asz;
   su__ERR_NUMBER_TYPE const (*adat)[2], (*tmp)[2];
   struct a_corerr_map const *cemp;
   NYD2_IN;

   cemp = &a_corerr_map[su__ERR_NUMBER_VOIDOFF];

   if(UCMP(z, su_ABS(eno), <=, S(su__ERR_NUMBER_TYPE,-1))){
      for(adat = a_corerr_no2mapoff, asz = su_NELEM(a_corerr_no2mapoff);
            asz != 0; asz >>= 1){
         tmp = &adat[asz >> 1];
         if((ecmp = (s32)(S(su__ERR_NUMBER_TYPE,eno) - (*tmp)[0])) == 0){
            cemp = &a_corerr_map[(*tmp)[1]];
            break;
         }
         if(ecmp > 0){
            adat = &tmp[1];
            --asz;
         }
      }
   }
   NYD2_OU;
   return cemp;
}

char const *
su_err_doc(s32 eno){
   char const *rv;
   struct a_corerr_map const *cemp;
   NYD2_IN;

   cemp = a_corerr_map_from_no(eno);
#ifdef su_HAVE_DOCSTRINGS
   rv = &a_corerr_docs[cemp->cem_docoff];
#else
   rv = &a_corerr_names[cemp->cem_nameoff];
#endif
   NYD2_OU;
   return rv;
}

char const *
su_err_name(s32 eno){
   char const *rv;
   struct a_corerr_map const *cemp;
   NYD2_IN;

   cemp = a_corerr_map_from_no(eno);
   rv = &a_corerr_names[cemp->cem_nameoff];
   NYD2_OU;
   return rv;
}

s32
su_err_from_name(char const *name){
   struct a_corerr_map const *cemp;
   u32 hash, i, j, x;
   s32 rv;
   NYD2_IN;

   hash = su_cs_hash(name) & U32_MAX;

   for(i = hash % a_CORERR_REV_PRIME, j = 0; j <= a_CORERR_REV_LONGEST; ++j){
      if((x = a_corerr_revmap[i]) == a_CORERR_REV_ILL)
         break;

      cemp = &a_corerr_map[x];
      if(cemp->cem_hash == hash &&
            !su_cs_cmp(&a_corerr_names[cemp->cem_nameoff], name)){
         rv = cemp->cem_errno;
         goto jleave;
      }

      if(++i == a_CORERR_REV_PRIME){
#ifdef a_CORERR_REV_WRAPAROUND
         i = 0;
#else
         break;
#endif
      }
   }

   /* Have not found it.  But wait, it could be that the user did, e.g.,
    *    eval echo \$^ERR-$: \$^ERRDOC-$!: \$^ERRNAME-$! */
   if((su_idec_s32_cp(&rv, name, 0, NIL) &
            (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
         ) == su_IDEC_STATE_CONSUMED){
      cemp = a_corerr_map_from_no(rv);
      rv = cemp->cem_errno;
      goto jleave;
   }

   rv = a_corerr_map[su__ERR_NUMBER_VOIDOFF].cem_errno;
jleave:
   NYD2_OU;
   return rv;
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/su/cs-alloc.c000066400000000000000000000042031352610246600161410ustar00rootroot00000000000000/*@ Implementation of cs.h: anything which performs allocations.
 *
 * Copyright (c) 2017 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE su_cs_alloc
#define su_SOURCE
#define su_SOURCE_CS_ALLOC

#include "su/code.h"
#include "su/mem.h"

#include "su/cs.h"
#include "su/code-in.h"

char *
su_cs_dup_cbuf(char const *buf, uz len, u32 estate){
   char *rv;
   NYD_IN;
   ASSERT_EXEC(len == 0 || buf != NIL, len = 0);

   if(len == UZ_MAX)
      len = su_cs_len(buf);
   estate &= su_STATE_ERR_MASK;

   if(LIKELY(len != UZ_MAX)){
      rv = S(char*,su_ALLOCATE(1, len +1, estate));
      if(LIKELY(rv != NIL)){
         if(len > 0)
            su_mem_copy(rv, buf, len);
         rv[len] = '\0';
      }
   }else{
      su_state_err(su_STATE_ERR_OVERFLOW, estate,
            _("SU cs_dup_cbuf: buffer too large"));
      rv = NIL;
   }
   NYD_OU;
   return rv;
}

char *
su_cs_dup(char const *cp, u32 estate){
   char *rv;
   uz l;
   NYD_IN;
   ASSERT_EXEC(cp != NIL, cp = su_empty);

   estate &= su_STATE_ERR_MASK;
   l = su_cs_len(cp);

   if(LIKELY(l != UZ_MAX)){
      ++l;
      if(LIKELY((rv = S(char*,su_ALLOCATE(1, l, estate))) != NIL))
         su_mem_copy(rv, cp, l);
   }else{
      su_state_err(su_STATE_ERR_OVERFLOW, estate,
            _("SU cs_dup: string too long"));
      rv = NIL;
   }
   NYD_OU;
   return rv;
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/su/cs-ctype.c000066400000000000000000000037001352610246600161740ustar00rootroot00000000000000/*@ Implementation of cs.h: character type and case conversion tables, tools.
 *
 * Copyright (c) 2017 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE su_cs_ctype
#define su_SOURCE
#define su_SOURCE_CS_CTYPE

#include "su/code.h"

#include "su/cs.h"
#include "su/code-in.h"

/* Include the constant su-make-cs-ctype.sh output */
#include "su/gen-cs-ctype.h" /* $(SU_SRCDIR) */

sz
su_cs_cmp_case(char const *cp1, char const *cp2){
   sz rv;
   NYD_IN;
   ASSERT_NYD_EXEC(cp1 != NIL, rv = (cp2 == NIL) ? 0 : -1);
   ASSERT_NYD_EXEC(cp2 != NIL, rv = 1);

   for(;;){
      u8 c1, c2;

      c1 = su_cs_to_lower(*cp1++);
      c2 = su_cs_to_lower(*cp2++);
      if((rv = c1 - c2) != 0 || c1 == '\0')
         break;
   }
   NYD_OU;
   return rv;
}

sz
su_cs_cmp_case_n(char const *cp1, char const *cp2, uz n){
   sz rv;
   NYD_IN;
   ASSERT_NYD_EXEC(cp1 != NIL, rv = (cp2 == NIL) ? 0 : -1);
   ASSERT_NYD_EXEC(cp2 != NIL, rv = 1);

   for(rv = 0; n != 0; --n){
      u8 c1, c2;

      c1 = su_cs_to_lower(*cp1++);
      c2 = su_cs_to_lower(*cp2++);
      if((rv = c1 - c2) != 0 || c1 == '\0')
         break;
   }
   NYD_OU;
   return rv;
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/su/cs-dict.c000066400000000000000000000023351352610246600157760ustar00rootroot00000000000000/*@ Implementation of cs-dict.h.
 *
 * Copyright (c) 2001 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE su_cs_dict
#define su_SOURCE
#define su_SOURCE_CS_DICT

#include "su/code.h"

#include "su/cs.h"
#include "su/mem.h"
#include "su/prime.h"

#include "su/cs-dict.h"
#include "su/code-in.h"

#include "su/x-assoc-map.h" /* $(SU_SRCDIR) */
#define a_TYPE a_TYPE_CSDICT
#include 

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/su/cs-find.c000066400000000000000000000120211352610246600157640ustar00rootroot00000000000000/*@ Implementation of cs.h: finding related things.
 *@ TODO Optimize (even asm hooks?)
 *
 * Copyright (c) 2017 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE su_cs_find
#define su_SOURCE
#define su_SOURCE_CS_FIND

#include "su/code.h"

#include "su/bits.h"
#include "su/mem.h"

#include "su/cs.h"
#include "su/code-in.h"

char *
su_cs_find(char const *cp, char const *xp){
   char xc, c;
   NYD_IN;
   ASSERT_NYD(cp != NIL);
   ASSERT_NYD_EXEC(xp != NIL, cp = NIL);

   /* Return cp if x is empty */
   if(LIKELY((xc = *xp) != '\0')){
      for(; (c = *cp) != '\0'; ++cp){
         if(c == xc && su_cs_starts_with(cp, xp))
            goto jleave;
      }
      cp = NIL;
   }
jleave:
   NYD_OU;
   return UNCONST(char*,cp);
}

char *
su_cs_find_c(char const *cp, char xc){
   NYD_IN;
   ASSERT_NYD(cp != NIL);

   for(;; ++cp){
      char c;

      if((c = *cp) == xc)
         break;
      if(c == '\0'){
         cp = NIL;
         break;
      }
   }
   NYD_OU;
   return UNCONST(char*,cp);
}

char *
su_cs_find_case(char const *cp, char const *xp){
   char xc, c;
   NYD_IN;
   ASSERT_NYD(cp != NIL);
   ASSERT_NYD_EXEC(xp != NIL, cp = NIL);

   /* Return cp if xp is empty */
   if(LIKELY((xc = *xp) != '\0')){
      xc = su_cs_to_lower(xc);
      for(; (c = *cp) != '\0'; ++cp){
         c = su_cs_to_lower(c);
         if(c == xc && su_cs_starts_with_case(cp, xp))
            goto jleave;
      }
      cp = NIL;
   }
jleave:
   NYD_OU;
   return UNCONST(char*,cp);
}

uz
su_cs_first_of_cbuf_cbuf(char const *cp, uz cplen, char const *xp, uz xlen){
   /* TODO (first|last)_(not_)?of: */
   uz rv, bs[su_BITS_TO_UZ(U8_MAX + 1)];
   char c;
   NYD_IN;
   ASSERT_NYD_EXEC(cplen == 0 || cp != NIL, rv = UZ_MAX);
   ASSERT_NYD_EXEC(xlen == 0 || xp != NIL, rv = UZ_MAX);

   su_mem_set(bs, 0, sizeof bs);

   /* For all bytes in x, set the bit of value */
   for(rv = P2UZ(xp);; ++xp){
      if(xlen-- == 0 || (c = *xp) == '\0')
         break;
      su_bits_array_set(bs, S(u8,c));
   }
   if(UNLIKELY(rv == P2UZ(xp)))
      goto jnope;

   /* For all bytes in cp, test whether the value bit is set */
   for(xp = cp;; ++cp){
      if(cplen-- == 0 || (c = *cp) == '\0')
         break;
      if(su_bits_array_test(bs, S(u8,c))){
         rv = P2UZ(cp - xp);
         goto jleave;
      }
   }

jnope:
   rv = UZ_MAX;
jleave:
   NYD_OU;
   return rv;
}

boole
su_cs_starts_with(char const *cp, char const *xp){
   boole rv;
   NYD_IN;
   ASSERT_NYD_EXEC(cp != NIL, rv = FAL0);
   ASSERT_NYD_EXEC(xp != NIL, rv = FAL0);

   if(LIKELY(*xp != '\0'))
      for(rv = TRU1;; ++cp, ++xp){
         char xc, c;

         if((xc = *xp) == '\0')
            goto jleave;
         if((c = *cp) != xc)
            break;
      }
   rv = FAL0;
jleave:
   NYD_OU;
   return rv;
}

boole
su_cs_starts_with_n(char const *cp, char const *xp, uz n){
   boole rv;
   NYD_IN;
   ASSERT_NYD_EXEC(n == 0 || cp != NIL, rv = FAL0);
   ASSERT_NYD_EXEC(n == 0 || xp != NIL, rv = FAL0);

   if(LIKELY(n > 0 && *xp != '\0'))
      for(rv = TRU1;; ++cp, ++xp){
         char xc, c;

         if((xc = *xp) == '\0')
            goto jleave;
         if((c = *cp) != xc)
            break;
         if(--n == 0)
            goto jleave;
      }
   rv = FAL0;
jleave:
   NYD_OU;
   return rv;
}

boole
su_cs_starts_with_case(char const *cp, char const *xp){
   boole rv;
   NYD_IN;
   ASSERT_NYD_EXEC(cp != NIL, rv = FAL0);
   ASSERT_NYD_EXEC(xp != NIL, rv = FAL0);

   if(LIKELY(*xp != '\0'))
      for(rv = TRU1;; ++cp, ++xp){
         char xc, c;

         if((xc = *xp) == '\0')
            goto jleave;
         xc = su_cs_to_lower(*xp);
         if((c = su_cs_to_lower(*cp)) != xc)
            break;
      }
   rv = FAL0;
jleave:
   NYD_OU;
   return rv;
}

boole
su_cs_starts_with_case_n(char const *cp, char const *xp, uz n){
   boole rv;
   NYD_IN;
   ASSERT_NYD_EXEC(n == 0 || cp != NIL, rv = FAL0);
   ASSERT_NYD_EXEC(n == 0 || xp != NIL, rv = FAL0);

   if(LIKELY(n > 0 && *xp != '\0'))
      for(rv = TRU1;; ++cp, ++xp){
         char xc, c;

         if((xc = *xp) == '\0')
            goto jleave;
         xc = su_cs_to_lower(*xp);
         if((c = su_cs_to_lower(*cp)) != xc)
            break;
         if(--n == 0)
            goto jleave;
      }
   rv = FAL0;
jleave:
   NYD_OU;
   return rv;
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/su/cs-misc.c000066400000000000000000000037701352610246600160120ustar00rootroot00000000000000/*@ Implementation of cs.h: anything non-specific, like hashing.
 *
 * Copyright (c) 2017 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE su_cs_misc
#define su_SOURCE
#define su_SOURCE_CS_MISC

#include "su/code.h"

#include "su/cs.h"
#include "su/code-in.h"

#define a_CSMISC_HASH(C) \
do{\
   u64 xh = 0;\
\
   if(len == UZ_MAX)\
      for(; (c = *buf++) != '\0';)\
         xh = (xh * 33) + S(u8,C);\
   else\
      while(len-- != 0){ /* XXX Duff's device, unroll 8? */\
         c = *buf++;\
         xh = (xh * 33) + S(u8,C);\
      }\
\
   /* Since mixing matters mostly for pow2 spaced maps, mixing the \
    * lower 32-bit seems to be sufficient (? in practice) */\
   xh += xh << 13;\
   xh ^= xh >> 7;\
   xh += xh << 3;\
   xh ^= xh >> 17;\
   xh += xh << 5;\
   h = S(uz,xh);\
}while(0)

uz
su_cs_hash_cbuf(char const *buf, uz len){
   char c;
   uz h;
   NYD_IN;
   ASSERT_NYD_EXEC(len == 0 || buf != NIL, h = 0);

   a_CSMISC_HASH(c);
   NYD_OU;
   return h;
}

uz
su_cs_hash_case_cbuf(char const *buf, uz len){
   char c;
   uz h;
   NYD_IN;
   ASSERT_NYD_EXEC(len == 0 || buf != NIL, h = 0);

   a_CSMISC_HASH(su_cs_to_lower(c));
   NYD_OU;
   return h;
}

#undef a_CSMISC_HASH

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/su/cs-tbox.c000066400000000000000000000043151352610246600160270ustar00rootroot00000000000000/*@ Implementation of cs.h: the toolboxes.
 *
 * Copyright (c) 2017 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE su_cs_tbox
#define su_SOURCE
#define su_SOURCE_CS_TBOX

#include "su/code.h"

#include "su/mem.h"

#include "su/cs.h"
#include "su/code-in.h"

/**/
#if DVLOR(1, 0)
static void a_cstbox_free(void *t);
#else
# define a_cstbox_free su_mem_free
#endif

/**/
static void *a_cstbox_assign(void *self, void const *t, u32 estate);
static uz a_cstbox_hash(void const *self);
static uz a_cstbox_hash_case(void const *self);

#if DVLOR(1, 0)
static void
a_cstbox_free(void *t){
   NYD2_IN;
   su_FREE(t);
   NYD2_OU;
}
#endif

static void *
a_cstbox_assign(void *self, void const *t, u32 estate){
   char *rv;
   NYD2_IN;

   if((rv = su_cs_dup(S(char const*,t), estate)) != NIL)
      su_FREE(self);
   NYD2_OU;
   return rv;
}

static uz
a_cstbox_hash(void const *self){
   uz rv;
   NYD2_IN;

   rv = su_cs_hash(S(char const*,self));
   NYD2_OU;
   return rv;
}

static uz
a_cstbox_hash_case(void const *self){
   uz rv;
   NYD2_IN;

   rv = su_cs_hash_case(S(char const*,self));
   NYD2_OU;
   return rv;
}

struct su_toolbox const su_cs_toolbox = su_TOOLBOX_I9R(
   &su_cs_dup, &a_cstbox_free, &a_cstbox_assign,
   &su_cs_cmp, &a_cstbox_hash);

struct su_toolbox const su_cs_toolbox_case = su_TOOLBOX_I9R(
   &su_cs_dup, &a_cstbox_free, &a_cstbox_assign,
   &su_cs_cmp_case, &a_cstbox_hash_case);

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/su/cs-tools.c000066400000000000000000000123311352610246600162100ustar00rootroot00000000000000/*@ Implementation of cs.h: basic tools, like copy etc.
 *@ TODO ASM optimization hook (like mem tools).
 *
 * Copyright (c) 2017 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE su_cs_tools
#define su_SOURCE
#define su_SOURCE_CS_TOOLS

#include "su/code.h"

#include "su/cs.h"
#include "su/code-in.h"

sz
su_cs_cmp(char const *cp1, char const *cp2){
   sz rv;
   NYD_IN;
   ASSERT_NYD_EXEC(cp1 != NIL, rv = (cp2 == NIL) ? 0 : -1);
   ASSERT_NYD_EXEC(cp2 != NIL, rv = 1);

   for(;;){
      u8 c1, c2;

      c1 = *cp1++;
      c2 = *cp2++;
      if((rv = c1 - c2) != 0 || c1 == '\0')
         break;
   }
   NYD_OU;
   return rv;
}

sz
su_cs_cmp_n(char const *cp1, char const *cp2, uz n){
   sz rv;
   NYD_IN;
   ASSERT_NYD_EXEC(cp1 != NIL, rv = (cp2 == NIL) ? 0 : -1);
   ASSERT_NYD_EXEC(cp2 != NIL, rv = 1);

   for(rv = 0; n != 0; --n){
      u8 c1, c2;

      c1 = *cp1++;
      c2 = *cp2++;
      if((rv = c1 - c2) != 0 || c1 == '\0')
         break;
   }
   NYD_OU;
   return rv;
}

char *
su_cs_copy_n(char *dst, char const *src, uz n){
   NYD_IN;
   ASSERT_NYD(n == 0 || dst != NIL);
   ASSERT_NYD_EXEC(src != NIL, *dst = '\0');

   if(LIKELY(n > 0)){
      char *cp;

      cp = dst;
      do if((*cp++ = *src++) == '\0')
         goto jleave;
      while(--n > 0);
      *--cp = '\0';
   }
   dst = NIL;
jleave:
   NYD_OU;
   return dst;
}

uz
su_cs_len(char const *cp){
   char const *cp_base;
   NYD_IN;
   ASSERT_NYD_EXEC(cp != NIL, cp_base = cp);

   for(cp_base = cp; *cp != '\0'; ++cp)
      ;
   NYD_OU;
   return P2UZ(cp - cp_base);
}

char *
su_cs_pcopy(char *dst, char const *src){
   NYD_IN;
   ASSERT_NYD(dst != NIL);
   ASSERT_NYD_EXEC(src != NIL, *dst = '\0');

   while((*dst = *src++) != '\0')
      ++dst;
   NYD_OU;
   return dst;
}

char *
su_cs_pcopy_n(char *dst, char const *src, uz n){
   NYD_IN;
   ASSERT_NYD(n == 0 || dst != NIL);
   ASSERT_NYD_EXEC(src != NIL, *dst = '\0');

   if(LIKELY(n > 0)){
      do{
         if((*dst = *src++) == '\0')
            goto jleave;
         ++dst;
      }while(--n > 0);
      *--dst = '\0';
   }
   dst = NIL;
jleave:
   NYD_OU;
   return dst;
}

char *
su_cs_rfind_c(char const *cp, char x){
   char const *match, *tail;
   NYD_IN;
   ASSERT_NYD_EXEC(cp != NIL, match = NIL);

   for(match = NIL, tail = cp;; ++tail){
      char c;

      if((c = *tail) == x)
         match = tail;
      if(c == '\0')
         break;
   }
   NYD_OU;
   return UNCONST(char*,match);
}

char *
su_cs_sep_c(char **iolist, char sep, boole ignore_empty){
   char *base, c, *cp;
   NYD_IN;
   ASSERT_NYD_EXEC(iolist != NIL, base = NIL);

   for(base = *iolist; base != NIL; base = *iolist){
      /* Skip WS */
      while((c = *base) != '\0' && su_cs_is_space(c))
         ++base;

      if((cp = su_cs_find_c(base, sep)) != NIL)
         *iolist = &cp[1];
      else{
         *iolist = NIL;
         cp = &base[su_cs_len(base)];
      }

      /* Chop WS */
      while(cp > base && su_cs_is_space(cp[-1]))
         --cp;
      *cp = '\0';

      if(*base != '\0' || !ignore_empty)
         break;
   }
   NYD_OU;
   return base;
}

char *
su_cs_sep_escable_c(char **iolist, char sep, boole ignore_empty){
   char *cp, c, *base;
   boole isesc, anyesc;
   NYD_IN;
   ASSERT_NYD_EXEC(iolist != NIL, base = NIL);

   for(base = *iolist; base != NIL; base = *iolist){
      /* Skip WS */
      while((c = *base) != '\0' && su_cs_is_space(c))
         ++base;

      /* Do not recognize escaped sep characters, keep track of whether we
       * have seen any such tuple along the way */
      for(isesc = anyesc = FAL0, cp = base;; ++cp){
         if(UNLIKELY((c = *cp) == '\0')){
            *iolist = NIL;
            break;
         }else if(!isesc){
            if(c == sep){
               *iolist = &cp[1];
               break;
            }
            isesc = (c == '\\');
         }else{
            isesc = FAL0;
            anyesc |= (c == sep);
         }
      }

      /* Chop WS */
      while(cp > base && su_cs_is_space(cp[-1]))
         --cp;
      *cp = '\0';

      /* Need to strip reverse solidus escaping sep's? */
      if(*base != '\0' && anyesc){
         char *ins;

         for(ins = cp = base;; ++ins)
            if((c = *cp) == '\\' && cp[1] == sep){
               *ins = sep;
               cp += 2;
            }else if((*ins = c) == '\0')
               break;
            else
               ++cp;
      }

      if(*base != '\0' || !ignore_empty)
         break;
   }

   NYD_OU;
   return base;
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/su/cxx-core.cc000066400000000000000000000036641352610246600163510ustar00rootroot00000000000000/*@ C++ injection point of most things which need it.
 *
 * Copyright (c) 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE su_cxx_core
#define su_SOURCE

#include "su/code.h"
su_USECASE_MX_DISABLED

#include 

#include "su/cs.h"
#include "su/utf.h"

#include "su/code-in.h"
NSPC_USE(su)

// code.h

STA void
log::write(level lvl, char const *fmt, ...){ // XXX unroll
   va_list va;
   NYD_IN;

   va_start(va, fmt);
   su_log_vwrite(S(enum su_log_level,lvl), fmt, &va);
   va_end(va);
   NYD_OU;
}

// cs.h

STA type_toolbox const * const cs::type_toolbox =
      R(NSPC(su)type_toolbox const*,&su_cs_toolbox);
STA type_toolbox const * const cs::const_type_toolbox =
      R(NSPC(su)type_toolbox const*,&su_cs_toolbox);

STA type_toolbox const * const cs::type_toolbox_case =
      R(NSPC(su)type_toolbox const*,&su_cs_toolbox_case);
STA type_toolbox const * const cs::const_type_toolbox_case =
      R(NSPC(su)type_toolbox const*,&su_cs_toolbox_case);

// utf.h

STA char const utf8::replacer[sizeof su_UTF8_REPLACER] = su_UTF8_REPLACER;

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/su/gen-cs-ctype.h000066400000000000000000000236751352610246600167650ustar00rootroot00000000000000/*@ src/su/gen-cs-ctype.h, generated by su-make-cs-ctype.sh.
 *@ See cs-ctype.c for more */

CTAV(su_CS_CTYPE_NONE == 0);
#undef a_X
#define a_X(X) su_CONCAT(su_CS_CTYPE_,X)
u16 const su__cs_ctype[S8_MAX + 1] = {
   /* 0x00=? */ a_X(CNTRL),
   /* 0x01=? */ a_X(CNTRL),
   /* 0x02=? */ a_X(CNTRL),
   /* 0x03=? */ a_X(CNTRL),
   /* 0x04=? */ a_X(CNTRL),
   /* 0x05=? */ a_X(CNTRL),
   /* 0x06=? */ a_X(CNTRL),
   /* 0x07=? */ a_X(CNTRL),
   /* 0x08=? */ a_X(CNTRL),
   /* 0x09=? */ a_X(BLANK) | a_X(CNTRL) | a_X(SPACE) | a_X(WHITE),
   /* 0x0A=? */ a_X(CNTRL) | a_X(SPACE) | a_X(WHITE),
   /* 0x0B=? */ a_X(CNTRL) | a_X(SPACE),
   /* 0x0C=? */ a_X(CNTRL) | a_X(SPACE),
   /* 0x0D=? */ a_X(CNTRL) | a_X(SPACE),
   /* 0x0E=? */ a_X(CNTRL),
   /* 0x0F=? */ a_X(CNTRL),
   /* 0x10=? */ a_X(CNTRL),
   /* 0x11=? */ a_X(CNTRL),
   /* 0x12=? */ a_X(CNTRL),
   /* 0x13=? */ a_X(CNTRL),
   /* 0x14=? */ a_X(CNTRL),
   /* 0x15=? */ a_X(CNTRL),
   /* 0x16=? */ a_X(CNTRL),
   /* 0x17=? */ a_X(CNTRL),
   /* 0x18=? */ a_X(CNTRL),
   /* 0x19=? */ a_X(CNTRL),
   /* 0x1A=? */ a_X(CNTRL),
   /* 0x1B=? */ a_X(CNTRL),
   /* 0x1C=? */ a_X(CNTRL),
   /* 0x1D=? */ a_X(CNTRL),
   /* 0x1E=? */ a_X(CNTRL),
   /* 0x1F=? */ a_X(CNTRL),
   /* 0x20=  */ a_X(BLANK) | a_X(PRINT) | a_X(SPACE) | a_X(WHITE),
   /* 0x21=! */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x22=" */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x23=# */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x24=$ */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x25=% */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x26=& */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x27=' */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x28=( */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x29=) */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x2A=* */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x2B=+ */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x2C=, */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x2D=- */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x2E=. */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x2F=/ */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x30=0 */ a_X(ALNUM) | a_X(DIGIT) | a_X(GRAPH) | a_X(PRINT) | a_X(XDIGIT),
   /* 0x31=1 */ a_X(ALNUM) | a_X(DIGIT) | a_X(GRAPH) | a_X(PRINT) | a_X(XDIGIT),
   /* 0x32=2 */ a_X(ALNUM) | a_X(DIGIT) | a_X(GRAPH) | a_X(PRINT) | a_X(XDIGIT),
   /* 0x33=3 */ a_X(ALNUM) | a_X(DIGIT) | a_X(GRAPH) | a_X(PRINT) | a_X(XDIGIT),
   /* 0x34=4 */ a_X(ALNUM) | a_X(DIGIT) | a_X(GRAPH) | a_X(PRINT) | a_X(XDIGIT),
   /* 0x35=5 */ a_X(ALNUM) | a_X(DIGIT) | a_X(GRAPH) | a_X(PRINT) | a_X(XDIGIT),
   /* 0x36=6 */ a_X(ALNUM) | a_X(DIGIT) | a_X(GRAPH) | a_X(PRINT) | a_X(XDIGIT),
   /* 0x37=7 */ a_X(ALNUM) | a_X(DIGIT) | a_X(GRAPH) | a_X(PRINT) | a_X(XDIGIT),
   /* 0x38=8 */ a_X(ALNUM) | a_X(DIGIT) | a_X(GRAPH) | a_X(PRINT) | a_X(XDIGIT),
   /* 0x39=9 */ a_X(ALNUM) | a_X(DIGIT) | a_X(GRAPH) | a_X(PRINT) | a_X(XDIGIT),
   /* 0x3A=: */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x3B=; */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x3C=< */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x3D== */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x3E=> */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x3F=? */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x40=@ */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x41=A */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(PRINT) | a_X(UPPER) | a_X(XDIGIT),
   /* 0x42=B */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(PRINT) | a_X(UPPER) | a_X(XDIGIT),
   /* 0x43=C */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(PRINT) | a_X(UPPER) | a_X(XDIGIT),
   /* 0x44=D */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(PRINT) | a_X(UPPER) | a_X(XDIGIT),
   /* 0x45=E */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(PRINT) | a_X(UPPER) | a_X(XDIGIT),
   /* 0x46=F */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(PRINT) | a_X(UPPER) | a_X(XDIGIT),
   /* 0x47=G */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(PRINT) | a_X(UPPER),
   /* 0x48=H */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(PRINT) | a_X(UPPER),
   /* 0x49=I */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(PRINT) | a_X(UPPER),
   /* 0x4A=J */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(PRINT) | a_X(UPPER),
   /* 0x4B=K */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(PRINT) | a_X(UPPER),
   /* 0x4C=L */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(PRINT) | a_X(UPPER),
   /* 0x4D=M */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(PRINT) | a_X(UPPER),
   /* 0x4E=N */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(PRINT) | a_X(UPPER),
   /* 0x4F=O */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(PRINT) | a_X(UPPER),
   /* 0x50=P */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(PRINT) | a_X(UPPER),
   /* 0x51=Q */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(PRINT) | a_X(UPPER),
   /* 0x52=R */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(PRINT) | a_X(UPPER),
   /* 0x53=S */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(PRINT) | a_X(UPPER),
   /* 0x54=T */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(PRINT) | a_X(UPPER),
   /* 0x55=U */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(PRINT) | a_X(UPPER),
   /* 0x56=V */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(PRINT) | a_X(UPPER),
   /* 0x57=W */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(PRINT) | a_X(UPPER),
   /* 0x58=X */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(PRINT) | a_X(UPPER),
   /* 0x59=Y */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(PRINT) | a_X(UPPER),
   /* 0x5A=Z */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(PRINT) | a_X(UPPER),
   /* 0x5B=[ */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x5C=\ */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x5D=] */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x5E=^ */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x5F=_ */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x60=` */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x61=a */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(LOWER) | a_X(PRINT) | a_X(XDIGIT),
   /* 0x62=b */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(LOWER) | a_X(PRINT) | a_X(XDIGIT),
   /* 0x63=c */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(LOWER) | a_X(PRINT) | a_X(XDIGIT),
   /* 0x64=d */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(LOWER) | a_X(PRINT) | a_X(XDIGIT),
   /* 0x65=e */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(LOWER) | a_X(PRINT) | a_X(XDIGIT),
   /* 0x66=f */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(LOWER) | a_X(PRINT) | a_X(XDIGIT),
   /* 0x67=g */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(LOWER) | a_X(PRINT),
   /* 0x68=h */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(LOWER) | a_X(PRINT),
   /* 0x69=i */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(LOWER) | a_X(PRINT),
   /* 0x6A=j */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(LOWER) | a_X(PRINT),
   /* 0x6B=k */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(LOWER) | a_X(PRINT),
   /* 0x6C=l */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(LOWER) | a_X(PRINT),
   /* 0x6D=m */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(LOWER) | a_X(PRINT),
   /* 0x6E=n */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(LOWER) | a_X(PRINT),
   /* 0x6F=o */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(LOWER) | a_X(PRINT),
   /* 0x70=p */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(LOWER) | a_X(PRINT),
   /* 0x71=q */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(LOWER) | a_X(PRINT),
   /* 0x72=r */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(LOWER) | a_X(PRINT),
   /* 0x73=s */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(LOWER) | a_X(PRINT),
   /* 0x74=t */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(LOWER) | a_X(PRINT),
   /* 0x75=u */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(LOWER) | a_X(PRINT),
   /* 0x76=v */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(LOWER) | a_X(PRINT),
   /* 0x77=w */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(LOWER) | a_X(PRINT),
   /* 0x78=x */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(LOWER) | a_X(PRINT),
   /* 0x79=y */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(LOWER) | a_X(PRINT),
   /* 0x7A=z */ a_X(ALNUM) | a_X(ALPHA) | a_X(GRAPH) | a_X(LOWER) | a_X(PRINT),
   /* 0x7B={ */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x7C=| */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x7D=} */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x7E=~ */ a_X(GRAPH) | a_X(PRINT) | a_X(PUNCT),
   /* 0x7F=? */ a_X(CNTRL),
};
#undef a_X

u8 const su__cs_tolower[S8_MAX + 1] = {
   '\x00','\x01','\x02','\x03','\x04','\x05','\x06','\x07','\x08','\x09',
   '\x0A','\x0B','\x0C','\x0D','\x0E','\x0F','\x10','\x11','\x12','\x13',
   '\x14','\x15','\x16','\x17','\x18','\x19','\x1A','\x1B','\x1C','\x1D',
   '\x1E','\x1F','\x20','\x21','\x22','\x23','\x24','\x25','\x26','\x27',
   '\x28','\x29','\x2A','\x2B','\x2C','\x2D','\x2E','\x2F','\x30','\x31',
   '\x32','\x33','\x34','\x35','\x36','\x37','\x38','\x39','\x3A','\x3B',
   '\x3C','\x3D','\x3E','\x3F','\x40','\x61','\x62','\x63','\x64','\x65',
   '\x66','\x67','\x68','\x69','\x6A','\x6B','\x6C','\x6D','\x6E','\x6F',
   '\x70','\x71','\x72','\x73','\x74','\x75','\x76','\x77','\x78','\x79',
   '\x7A','\x5B','\x5C','\x5D','\x5E','\x5F','\x60','\x61','\x62','\x63',
   '\x64','\x65','\x66','\x67','\x68','\x69','\x6A','\x6B','\x6C','\x6D',
   '\x6E','\x6F','\x70','\x71','\x72','\x73','\x74','\x75','\x76','\x77',
   '\x78','\x79','\x7A','\x7B','\x7C','\x7D','\x7E','\x7F',
};

u8 const su__cs_toupper[S8_MAX + 1] = {
   '\x00','\x01','\x02','\x03','\x04','\x05','\x06','\x07','\x08','\x09',
   '\x0A','\x0B','\x0C','\x0D','\x0E','\x0F','\x10','\x11','\x12','\x13',
   '\x14','\x15','\x16','\x17','\x18','\x19','\x1A','\x1B','\x1C','\x1D',
   '\x1E','\x1F','\x20','\x21','\x22','\x23','\x24','\x25','\x26','\x27',
   '\x28','\x29','\x2A','\x2B','\x2C','\x2D','\x2E','\x2F','\x30','\x31',
   '\x32','\x33','\x34','\x35','\x36','\x37','\x38','\x39','\x3A','\x3B',
   '\x3C','\x3D','\x3E','\x3F','\x40','\x41','\x42','\x43','\x44','\x45',
   '\x46','\x47','\x48','\x49','\x4A','\x4B','\x4C','\x4D','\x4E','\x4F',
   '\x50','\x51','\x52','\x53','\x54','\x55','\x56','\x57','\x58','\x59',
   '\x5A','\x5B','\x5C','\x5D','\x5E','\x5F','\x60','\x41','\x42','\x43',
   '\x44','\x45','\x46','\x47','\x48','\x49','\x4A','\x4B','\x4C','\x4D',
   '\x4E','\x4F','\x50','\x51','\x52','\x53','\x54','\x55','\x56','\x57',
   '\x58','\x59','\x5A','\x7B','\x7C','\x7D','\x7E','\x7F',
};

s-nail-14.9.15/src/su/gen-errors.h000066400000000000000000000576651352610246600165600ustar00rootroot00000000000000/*@ gen-errors.h, generated by su-make-errors.sh.
 *@ See core-errors.c for more */

#ifndef su_SOURCE /* For compile-time tools only */
static char const * const a_names_alphasort[] = {
   "NONE", "2BIG", "ACCES", "ADDRINUSE", "ADDRNOTAVAIL",
   "AFNOSUPPORT", "AGAIN", "ALREADY", "BADF", "BADMSG", "BUSY",
   "CANCELED", "CHILD", "CONNABORTED", "CONNREFUSED", "CONNRESET",
   "DEADLK", "DESTADDRREQ", "DOM", "DQUOT", "EXIST", "FAULT", "FBIG",
   "HOSTUNREACH", "IDRM", "ILSEQ", "INPROGRESS", "INTR", "INVAL",
   "IO", "ISCONN", "ISDIR", "LOOP", "MFILE", "MLINK", "MSGSIZE",
   "MULTIHOP", "NAMETOOLONG", "NETDOWN", "NETRESET", "NETUNREACH",
   "NFILE", "NOBUFS", "NODATA", "NODEV", "NOENT", "NOEXEC", "NOLCK",
   "NOLINK", "NOMEM", "NOMSG", "NOPROTOOPT", "NOSPC", "NOSR",
   "NOSTR", "NOSYS", "NOTCONN", "NOTDIR", "NOTEMPTY", "NOTOBACCO",
   "NOTSOCK", "NOTSUP", "NOTTY", "NXIO", "OPNOTSUPP", "OVERFLOW",
   "PERM", "PIPE", "PROTO", "PROTONOSUPPORT", "PROTOTYPE", "RANGE",
   "ROFS", "SPIPE", "SRCH", "STALE", "TIME", "TIMEDOUT", "TXTBSY",
   "WOULDBLOCK", "XDEV", NULL
};
#endif /* !su_SOURCE */

#ifdef su_SOURCE
static char const a_corerr_names[] = {
   /* 0. [0]+4 NONE */
   'N','O','N','E','\0',
   /* 1. [5]+4 2BIG */
   '2','B','I','G','\0',
   /* 2. [10]+5 ACCES */
   'A','C','C','E','S','\0',
   /* 3. [16]+9 ADDRINUSE */
   'A','D','D','R','I','N','U','S','E','\0',
   /* 4. [26]+12 ADDRNOTAVAIL */
   'A','D','D','R','N','O','T','A','V','A','I','L','\0',
   /* 5. [39]+11 AFNOSUPPORT */
   'A','F','N','O','S','U','P','P','O','R','T','\0',
   /* 6. [51]+5 AGAIN */
   'A','G','A','I','N','\0',
   /* 7. [57]+7 ALREADY */
   'A','L','R','E','A','D','Y','\0',
   /* 8. [65]+4 BADF */
   'B','A','D','F','\0',
   /* 9. [70]+6 BADMSG */
   'B','A','D','M','S','G','\0',
   /* 10. [77]+4 BUSY */
   'B','U','S','Y','\0',
   /* 11. [82]+8 CANCELED */
   'C','A','N','C','E','L','E','D','\0',
   /* 12. [91]+5 CHILD */
   'C','H','I','L','D','\0',
   /* 13. [97]+11 CONNABORTED */
   'C','O','N','N','A','B','O','R','T','E','D','\0',
   /* 14. [109]+11 CONNREFUSED */
   'C','O','N','N','R','E','F','U','S','E','D','\0',
   /* 15. [121]+9 CONNRESET */
   'C','O','N','N','R','E','S','E','T','\0',
   /* 16. [131]+6 DEADLK */
   'D','E','A','D','L','K','\0',
   /* 17. [138]+11 DESTADDRREQ */
   'D','E','S','T','A','D','D','R','R','E','Q','\0',
   /* 18. [150]+3 DOM */
   'D','O','M','\0',
   /* 19. [154]+5 DQUOT */
   'D','Q','U','O','T','\0',
   /* 20. [160]+5 EXIST */
   'E','X','I','S','T','\0',
   /* 21. [166]+5 FAULT */
   'F','A','U','L','T','\0',
   /* 22. [172]+4 FBIG */
   'F','B','I','G','\0',
   /* 23. [177]+11 HOSTUNREACH */
   'H','O','S','T','U','N','R','E','A','C','H','\0',
   /* 24. [189]+4 IDRM */
   'I','D','R','M','\0',
   /* 25. [194]+5 ILSEQ */
   'I','L','S','E','Q','\0',
   /* 26. [200]+10 INPROGRESS */
   'I','N','P','R','O','G','R','E','S','S','\0',
   /* 27. [211]+4 INTR */
   'I','N','T','R','\0',
   /* 28. [216]+5 INVAL */
   'I','N','V','A','L','\0',
   /* 29. [222]+2 IO */
   'I','O','\0',
   /* 30. [225]+6 ISCONN */
   'I','S','C','O','N','N','\0',
   /* 31. [232]+5 ISDIR */
   'I','S','D','I','R','\0',
   /* 32. [238]+4 LOOP */
   'L','O','O','P','\0',
   /* 33. [243]+5 MFILE */
   'M','F','I','L','E','\0',
   /* 34. [249]+5 MLINK */
   'M','L','I','N','K','\0',
   /* 35. [255]+7 MSGSIZE */
   'M','S','G','S','I','Z','E','\0',
   /* 36. [263]+8 MULTIHOP */
   'M','U','L','T','I','H','O','P','\0',
   /* 37. [272]+11 NAMETOOLONG */
   'N','A','M','E','T','O','O','L','O','N','G','\0',
   /* 38. [284]+7 NETDOWN */
   'N','E','T','D','O','W','N','\0',
   /* 39. [292]+8 NETRESET */
   'N','E','T','R','E','S','E','T','\0',
   /* 40. [301]+10 NETUNREACH */
   'N','E','T','U','N','R','E','A','C','H','\0',
   /* 41. [312]+5 NFILE */
   'N','F','I','L','E','\0',
   /* 42. [318]+6 NOBUFS */
   'N','O','B','U','F','S','\0',
   /* 43. [325]+6 NODATA */
   'N','O','D','A','T','A','\0',
   /* 44. [332]+5 NODEV */
   'N','O','D','E','V','\0',
   /* 45. [338]+5 NOENT */
   'N','O','E','N','T','\0',
   /* 46. [344]+6 NOEXEC */
   'N','O','E','X','E','C','\0',
   /* 47. [351]+5 NOLCK */
   'N','O','L','C','K','\0',
   /* 48. [357]+6 NOLINK */
   'N','O','L','I','N','K','\0',
   /* 49. [364]+5 NOMEM */
   'N','O','M','E','M','\0',
   /* 50. [370]+5 NOMSG */
   'N','O','M','S','G','\0',
   /* 51. [376]+10 NOPROTOOPT */
   'N','O','P','R','O','T','O','O','P','T','\0',
   /* 52. [387]+5 NOSPC */
   'N','O','S','P','C','\0',
   /* 53. [393]+4 NOSR */
   'N','O','S','R','\0',
   /* 54. [398]+5 NOSTR */
   'N','O','S','T','R','\0',
   /* 55. [404]+5 NOSYS */
   'N','O','S','Y','S','\0',
   /* 56. [410]+7 NOTCONN */
   'N','O','T','C','O','N','N','\0',
   /* 57. [418]+6 NOTDIR */
   'N','O','T','D','I','R','\0',
   /* 58. [425]+8 NOTEMPTY */
   'N','O','T','E','M','P','T','Y','\0',
   /* 59. [434]+9 NOTOBACCO */
   'N','O','T','O','B','A','C','C','O','\0',
   /* 60. [444]+7 NOTSOCK */
   'N','O','T','S','O','C','K','\0',
   /* 61. [452]+6 NOTSUP */
   'N','O','T','S','U','P','\0',
   /* 62. [459]+5 NOTTY */
   'N','O','T','T','Y','\0',
   /* 63. [465]+4 NXIO */
   'N','X','I','O','\0',
   /* 64. [470]+9 OPNOTSUPP */
   'O','P','N','O','T','S','U','P','P','\0',
   /* 65. [480]+8 OVERFLOW */
   'O','V','E','R','F','L','O','W','\0',
   /* 66. [489]+4 PERM */
   'P','E','R','M','\0',
   /* 67. [494]+4 PIPE */
   'P','I','P','E','\0',
   /* 68. [499]+5 PROTO */
   'P','R','O','T','O','\0',
   /* 69. [505]+14 PROTONOSUPPORT */
   'P','R','O','T','O','N','O','S','U','P','P','O','R','T','\0',
   /* 70. [520]+9 PROTOTYPE */
   'P','R','O','T','O','T','Y','P','E','\0',
   /* 71. [530]+5 RANGE */
   'R','A','N','G','E','\0',
   /* 72. [536]+4 ROFS */
   'R','O','F','S','\0',
   /* 73. [541]+5 SPIPE */
   'S','P','I','P','E','\0',
   /* 74. [547]+4 SRCH */
   'S','R','C','H','\0',
   /* 75. [552]+5 STALE */
   'S','T','A','L','E','\0',
   /* 76. [558]+4 TIME */
   'T','I','M','E','\0',
   /* 77. [563]+8 TIMEDOUT */
   'T','I','M','E','D','O','U','T','\0',
   /* 78. [572]+6 TXTBSY */
   'T','X','T','B','S','Y','\0',
   /* 79. [579]+10 WOULDBLOCK */
   'W','O','U','L','D','B','L','O','C','K','\0',
   /* 80. [590]+4 XDEV */
   'X','D','E','V','\0',
};

# ifdef su_HAVE_DOCSTRINGS
#  undef a_X
#  define a_X(X)
static char const a_corerr_docs[] = {
   /* 0. [0]+8 NONE */ a_X(N_("No error"))
   'N','o',' ','e','r','r','o','r','\0',
   /* 1. [9]+22 2BIG */ a_X(N_("Argument list too long"))
   'A','r','g','u','m','e','n','t',' ','l','i','s','t',' ','t','o','o',' ','l','o','n','g','\0',
   /* 2. [32]+17 ACCES */ a_X(N_("Permission denied"))
   'P','e','r','m','i','s','s','i','o','n',' ','d','e','n','i','e','d','\0',
   /* 3. [50]+22 ADDRINUSE */ a_X(N_("Address already in use"))
   'A','d','d','r','e','s','s',' ','a','l','r','e','a','d','y',' ','i','n',' ','u','s','e','\0',
   /* 4. [73]+31 ADDRNOTAVAIL */ a_X(N_("Cannot assign requested address"))
   'C','a','n','n','o','t',' ','a','s','s','i','g','n',' ','r','e','q','u','e','s','t','e','d',' ','a','d','d','r','e','s','s','\0',
   /* 5. [105]+47 AFNOSUPPORT */ a_X(N_("Address family not supported by protocol family"))
   'A','d','d','r','e','s','s',' ','f','a','m','i','l','y',' ','n','o','t',' ','s','u','p','p','o','r','t','e','d',' ','b','y',' ','p','r','o','t','o','c','o','l',' ','f','a','m','i','l','y','\0',
   /* 6. [153]+32 AGAIN */ a_X(N_("Resource temporarily unavailable"))
   'R','e','s','o','u','r','c','e',' ','t','e','m','p','o','r','a','r','i','l','y',' ','u','n','a','v','a','i','l','a','b','l','e','\0',
   /* 7. [186]+29 ALREADY */ a_X(N_("Operation already in progress"))
   'O','p','e','r','a','t','i','o','n',' ','a','l','r','e','a','d','y',' ','i','n',' ','p','r','o','g','r','e','s','s','\0',
   /* 8. [216]+19 BADF */ a_X(N_("Bad file descriptor"))
   'B','a','d',' ','f','i','l','e',' ','d','e','s','c','r','i','p','t','o','r','\0',
   /* 9. [236]+11 BADMSG */ a_X(N_("Bad message"))
   'B','a','d',' ','m','e','s','s','a','g','e','\0',
   /* 10. [248]+11 BUSY */ a_X(N_("Device busy"))
   'D','e','v','i','c','e',' ','b','u','s','y','\0',
   /* 11. [260]+18 CANCELED */ a_X(N_("Operation canceled"))
   'O','p','e','r','a','t','i','o','n',' ','c','a','n','c','e','l','e','d','\0',
   /* 12. [279]+18 CHILD */ a_X(N_("No child processes"))
   'N','o',' ','c','h','i','l','d',' ','p','r','o','c','e','s','s','e','s','\0',
   /* 13. [298]+32 CONNABORTED */ a_X(N_("Software caused connection abort"))
   'S','o','f','t','w','a','r','e',' ','c','a','u','s','e','d',' ','c','o','n','n','e','c','t','i','o','n',' ','a','b','o','r','t','\0',
   /* 14. [331]+18 CONNREFUSED */ a_X(N_("Connection refused"))
   'C','o','n','n','e','c','t','i','o','n',' ','r','e','f','u','s','e','d','\0',
   /* 15. [350]+24 CONNRESET */ a_X(N_("Connection reset by peer"))
   'C','o','n','n','e','c','t','i','o','n',' ','r','e','s','e','t',' ','b','y',' ','p','e','e','r','\0',
   /* 16. [375]+25 DEADLK */ a_X(N_("Resource deadlock avoided"))
   'R','e','s','o','u','r','c','e',' ','d','e','a','d','l','o','c','k',' ','a','v','o','i','d','e','d','\0',
   /* 17. [401]+28 DESTADDRREQ */ a_X(N_("Destination address required"))
   'D','e','s','t','i','n','a','t','i','o','n',' ','a','d','d','r','e','s','s',' ','r','e','q','u','i','r','e','d','\0',
   /* 18. [430]+32 DOM */ a_X(N_("Numerical argument out of domain"))
   'N','u','m','e','r','i','c','a','l',' ','a','r','g','u','m','e','n','t',' ','o','u','t',' ','o','f',' ','d','o','m','a','i','n','\0',
   /* 19. [463]+19 DQUOT */ a_X(N_("Disc quota exceeded"))
   'D','i','s','c',' ','q','u','o','t','a',' ','e','x','c','e','e','d','e','d','\0',
   /* 20. [483]+11 EXIST */ a_X(N_("File exists"))
   'F','i','l','e',' ','e','x','i','s','t','s','\0',
   /* 21. [495]+11 FAULT */ a_X(N_("Bad address"))
   'B','a','d',' ','a','d','d','r','e','s','s','\0',
   /* 22. [507]+14 FBIG */ a_X(N_("File too large"))
   'F','i','l','e',' ','t','o','o',' ','l','a','r','g','e','\0',
   /* 23. [522]+16 HOSTUNREACH */ a_X(N_("No route to host"))
   'N','o',' ','r','o','u','t','e',' ','t','o',' ','h','o','s','t','\0',
   /* 24. [539]+18 IDRM */ a_X(N_("Identifier removed"))
   'I','d','e','n','t','i','f','i','e','r',' ','r','e','m','o','v','e','d','\0',
   /* 25. [558]+21 ILSEQ */ a_X(N_("Illegal byte sequence"))
   'I','l','l','e','g','a','l',' ','b','y','t','e',' ','s','e','q','u','e','n','c','e','\0',
   /* 26. [580]+25 INPROGRESS */ a_X(N_("Operation now in progress"))
   'O','p','e','r','a','t','i','o','n',' ','n','o','w',' ','i','n',' ','p','r','o','g','r','e','s','s','\0',
   /* 27. [606]+23 INTR */ a_X(N_("Interrupted system call"))
   'I','n','t','e','r','r','u','p','t','e','d',' ','s','y','s','t','e','m',' ','c','a','l','l','\0',
   /* 28. [630]+16 INVAL */ a_X(N_("Invalid argument"))
   'I','n','v','a','l','i','d',' ','a','r','g','u','m','e','n','t','\0',
   /* 29. [647]+18 IO */ a_X(N_("Input/output error"))
   'I','n','p','u','t','/','o','u','t','p','u','t',' ','e','r','r','o','r','\0',
   /* 30. [666]+27 ISCONN */ a_X(N_("Socket is already connected"))
   'S','o','c','k','e','t',' ','i','s',' ','a','l','r','e','a','d','y',' ','c','o','n','n','e','c','t','e','d','\0',
   /* 31. [694]+14 ISDIR */ a_X(N_("Is a directory"))
   'I','s',' ','a',' ','d','i','r','e','c','t','o','r','y','\0',
   /* 32. [709]+33 LOOP */ a_X(N_("Too many levels of symbolic links"))
   'T','o','o',' ','m','a','n','y',' ','l','e','v','e','l','s',' ','o','f',' ','s','y','m','b','o','l','i','c',' ','l','i','n','k','s','\0',
   /* 33. [743]+19 MFILE */ a_X(N_("Too many open files"))
   'T','o','o',' ','m','a','n','y',' ','o','p','e','n',' ','f','i','l','e','s','\0',
   /* 34. [763]+14 MLINK */ a_X(N_("Too many links"))
   'T','o','o',' ','m','a','n','y',' ','l','i','n','k','s','\0',
   /* 35. [778]+16 MSGSIZE */ a_X(N_("Message too long"))
   'M','e','s','s','a','g','e',' ','t','o','o',' ','l','o','n','g','\0',
   /* 36. [795]+18 MULTIHOP */ a_X(N_("Multihop attempted"))
   'M','u','l','t','i','h','o','p',' ','a','t','t','e','m','p','t','e','d','\0',
   /* 37. [814]+18 NAMETOOLONG */ a_X(N_("File name too long"))
   'F','i','l','e',' ','n','a','m','e',' ','t','o','o',' ','l','o','n','g','\0',
   /* 38. [833]+15 NETDOWN */ a_X(N_("Network is down"))
   'N','e','t','w','o','r','k',' ','i','s',' ','d','o','w','n','\0',
   /* 39. [849]+35 NETRESET */ a_X(N_("Network dropped connection on reset"))
   'N','e','t','w','o','r','k',' ','d','r','o','p','p','e','d',' ','c','o','n','n','e','c','t','i','o','n',' ','o','n',' ','r','e','s','e','t','\0',
   /* 40. [885]+22 NETUNREACH */ a_X(N_("Network is unreachable"))
   'N','e','t','w','o','r','k',' ','i','s',' ','u','n','r','e','a','c','h','a','b','l','e','\0',
   /* 41. [908]+29 NFILE */ a_X(N_("Too many open files in system"))
   'T','o','o',' ','m','a','n','y',' ','o','p','e','n',' ','f','i','l','e','s',' ','i','n',' ','s','y','s','t','e','m','\0',
   /* 42. [938]+25 NOBUFS */ a_X(N_("No buffer space available"))
   'N','o',' ','b','u','f','f','e','r',' ','s','p','a','c','e',' ','a','v','a','i','l','a','b','l','e','\0',
   /* 43. [964]+17 NODATA */ a_X(N_("No data available"))
   'N','o',' ','d','a','t','a',' ','a','v','a','i','l','a','b','l','e','\0',
   /* 44. [982]+33 NODEV */ a_X(N_("Operation not supported by device"))
   'O','p','e','r','a','t','i','o','n',' ','n','o','t',' ','s','u','p','p','o','r','t','e','d',' ','b','y',' ','d','e','v','i','c','e','\0',
   /* 45. [1016]+32 NOENT */ a_X(N_("No such entry, file or directory"))
   'N','o',' ','s','u','c','h',' ','e','n','t','r','y',',',' ','f','i','l','e',' ','o','r',' ','d','i','r','e','c','t','o','r','y','\0',
   /* 46. [1049]+17 NOEXEC */ a_X(N_("Exec format error"))
   'E','x','e','c',' ','f','o','r','m','a','t',' ','e','r','r','o','r','\0',
   /* 47. [1067]+18 NOLCK */ a_X(N_("No locks available"))
   'N','o',' ','l','o','c','k','s',' ','a','v','a','i','l','a','b','l','e','\0',
   /* 48. [1086]+21 NOLINK */ a_X(N_("Link has been severed"))
   'L','i','n','k',' ','h','a','s',' ','b','e','e','n',' ','s','e','v','e','r','e','d','\0',
   /* 49. [1108]+22 NOMEM */ a_X(N_("Cannot allocate memory"))
   'C','a','n','n','o','t',' ','a','l','l','o','c','a','t','e',' ','m','e','m','o','r','y','\0',
   /* 50. [1131]+26 NOMSG */ a_X(N_("No message of desired type"))
   'N','o',' ','m','e','s','s','a','g','e',' ','o','f',' ','d','e','s','i','r','e','d',' ','t','y','p','e','\0',
   /* 51. [1158]+22 NOPROTOOPT */ a_X(N_("Protocol not available"))
   'P','r','o','t','o','c','o','l',' ','n','o','t',' ','a','v','a','i','l','a','b','l','e','\0',
   /* 52. [1181]+23 NOSPC */ a_X(N_("No space left on device"))
   'N','o',' ','s','p','a','c','e',' ','l','e','f','t',' ','o','n',' ','d','e','v','i','c','e','\0',
   /* 53. [1205]+23 NOSR */ a_X(N_("Out of streams resource"))
   'O','u','t',' ','o','f',' ','s','t','r','e','a','m','s',' ','r','e','s','o','u','r','c','e','\0',
   /* 54. [1229]+19 NOSTR */ a_X(N_("Device not a stream"))
   'D','e','v','i','c','e',' ','n','o','t',' ','a',' ','s','t','r','e','a','m','\0',
   /* 55. [1249]+24 NOSYS */ a_X(N_("Function not implemented"))
   'F','u','n','c','t','i','o','n',' ','n','o','t',' ','i','m','p','l','e','m','e','n','t','e','d','\0',
   /* 56. [1274]+23 NOTCONN */ a_X(N_("Socket is not connected"))
   'S','o','c','k','e','t',' ','i','s',' ','n','o','t',' ','c','o','n','n','e','c','t','e','d','\0',
   /* 57. [1298]+15 NOTDIR */ a_X(N_("Not a directory"))
   'N','o','t',' ','a',' ','d','i','r','e','c','t','o','r','y','\0',
   /* 58. [1314]+19 NOTEMPTY */ a_X(N_("Directory not empty"))
   'D','i','r','e','c','t','o','r','y',' ','n','o','t',' ','e','m','p','t','y','\0',
   /* 59. [1334]+36 NOTOBACCO */ a_X(N_("No tobacco, snorkeling on empty pipe"))
   'N','o',' ','t','o','b','a','c','c','o',',',' ','s','n','o','r','k','e','l','i','n','g',' ','o','n',' ','e','m','p','t','y',' ','p','i','p','e','\0',
   /* 60. [1371]+30 NOTSOCK */ a_X(N_("Socket operation on non-socket"))
   'S','o','c','k','e','t',' ','o','p','e','r','a','t','i','o','n',' ','o','n',' ','n','o','n','-','s','o','c','k','e','t','\0',
   /* 61. [1402]+23 NOTSUP */ a_X(N_("Operation not supported"))
   'O','p','e','r','a','t','i','o','n',' ','n','o','t',' ','s','u','p','p','o','r','t','e','d','\0',
   /* 62. [1426]+30 NOTTY */ a_X(N_("Inappropriate ioctl for device"))
   'I','n','a','p','p','r','o','p','r','i','a','t','e',' ','i','o','c','t','l',' ','f','o','r',' ','d','e','v','i','c','e','\0',
   /* 63. [1457]+21 NXIO */ a_X(N_("Device not configured"))
   'D','e','v','i','c','e',' ','n','o','t',' ','c','o','n','f','i','g','u','r','e','d','\0',
   /* 64. [1479]+23 OPNOTSUPP */ a_X(N_("Operation not supported"))
   'O','p','e','r','a','t','i','o','n',' ','n','o','t',' ','s','u','p','p','o','r','t','e','d','\0',
   /* 65. [1503]+41 OVERFLOW */ a_X(N_("Value too large to be stored in data type"))
   'V','a','l','u','e',' ','t','o','o',' ','l','a','r','g','e',' ','t','o',' ','b','e',' ','s','t','o','r','e','d',' ','i','n',' ','d','a','t','a',' ','t','y','p','e','\0',
   /* 66. [1545]+23 PERM */ a_X(N_("Operation not permitted"))
   'O','p','e','r','a','t','i','o','n',' ','n','o','t',' ','p','e','r','m','i','t','t','e','d','\0',
   /* 67. [1569]+11 PIPE */ a_X(N_("Broken pipe"))
   'B','r','o','k','e','n',' ','p','i','p','e','\0',
   /* 68. [1581]+14 PROTO */ a_X(N_("Protocol error"))
   'P','r','o','t','o','c','o','l',' ','e','r','r','o','r','\0',
   /* 69. [1596]+22 PROTONOSUPPORT */ a_X(N_("Protocol not supported"))
   'P','r','o','t','o','c','o','l',' ','n','o','t',' ','s','u','p','p','o','r','t','e','d','\0',
   /* 70. [1619]+30 PROTOTYPE */ a_X(N_("Protocol wrong type for socket"))
   'P','r','o','t','o','c','o','l',' ','w','r','o','n','g',' ','t','y','p','e',' ','f','o','r',' ','s','o','c','k','e','t','\0',
   /* 71. [1650]+16 RANGE */ a_X(N_("Result too large"))
   'R','e','s','u','l','t',' ','t','o','o',' ','l','a','r','g','e','\0',
   /* 72. [1667]+20 ROFS */ a_X(N_("Read-only filesystem"))
   'R','e','a','d','-','o','n','l','y',' ','f','i','l','e','s','y','s','t','e','m','\0',
   /* 73. [1688]+12 SPIPE */ a_X(N_("Invalid seek"))
   'I','n','v','a','l','i','d',' ','s','e','e','k','\0',
   /* 74. [1701]+15 SRCH */ a_X(N_("No such process"))
   'N','o',' ','s','u','c','h',' ','p','r','o','c','e','s','s','\0',
   /* 75. [1717]+21 STALE */ a_X(N_("Stale NFS file handle"))
   'S','t','a','l','e',' ','N','F','S',' ','f','i','l','e',' ','h','a','n','d','l','e','\0',
   /* 76. [1739]+13 TIME */ a_X(N_("Timer expired"))
   'T','i','m','e','r',' ','e','x','p','i','r','e','d','\0',
   /* 77. [1753]+19 TIMEDOUT */ a_X(N_("Operation timed out"))
   'O','p','e','r','a','t','i','o','n',' ','t','i','m','e','d',' ','o','u','t','\0',
   /* 78. [1773]+14 TXTBSY */ a_X(N_("Text file busy"))
   'T','e','x','t',' ','f','i','l','e',' ','b','u','s','y','\0',
   /* 79. [1788]+21 WOULDBLOCK */ a_X(N_("Operation would block"))
   'O','p','e','r','a','t','i','o','n',' ','w','o','u','l','d',' ','b','l','o','c','k','\0',
   /* 80. [1810]+17 XDEV */ a_X(N_("Cross-device link"))
   'C','r','o','s','s','-','d','e','v','i','c','e',' ','l','i','n','k','\0',
};
#  undef a_X
# endif /* su_HAVE_DOCSTRINGS */

# undef a_X
# ifndef __CREATE_ERRORS_SH
#  define a_X(X) X
# else
#  define a_X(X) 0
# endif
static struct a_corerr_map const a_corerr_map[] = {
   {233011709u, 0u, 0u, a_X(su_ERR_NONE)},
   {338119313u, 5u, 9u, a_X(su_ERR_2BIG)},
   {2760110381u, 10u, 32u, a_X(su_ERR_ACCES)},
   {3973621059u, 16u, 50u, a_X(su_ERR_ADDRINUSE)},
   {4228849030u, 26u, 73u, a_X(su_ERR_ADDRNOTAVAIL)},
   {1437911301u, 39u, 105u, a_X(su_ERR_AFNOSUPPORT)},
   {1876217389u, 51u, 153u, a_X(su_ERR_AGAIN)},
   {2638797879u, 57u, 186u, a_X(su_ERR_ALREADY)},
   {1628681718u, 65u, 216u, a_X(su_ERR_BADF)},
   {3563543249u, 70u, 236u, a_X(su_ERR_BADMSG)},
   {332518208u, 77u, 248u, a_X(su_ERR_BUSY)},
   {1622129131u, 82u, 260u, a_X(su_ERR_CANCELED)},
   {3508732648u, 91u, 279u, a_X(su_ERR_CHILD)},
   {3696001689u, 97u, 298u, a_X(su_ERR_CONNABORTED)},
   {1535510636u, 109u, 331u, a_X(su_ERR_CONNREFUSED)},
   {323416381u, 121u, 350u, a_X(su_ERR_CONNRESET)},
   {265908508u, 131u, 375u, a_X(su_ERR_DEADLK)},
   {1947834517u, 138u, 401u, a_X(su_ERR_DESTADDRREQ)},
   {689055206u, 150u, 430u, a_X(su_ERR_DOM)},
   {2103109256u, 154u, 463u, a_X(su_ERR_DQUOT)},
   {690634405u, 160u, 483u, a_X(su_ERR_EXIST)},
   {1325100503u, 166u, 495u, a_X(su_ERR_FAULT)},
   {191906582u, 172u, 507u, a_X(su_ERR_FBIG)},
   {3136618891u, 177u, 522u, a_X(su_ERR_HOSTUNREACH)},
   {597011636u, 189u, 539u, a_X(su_ERR_IDRM)},
   {3837265952u, 194u, 558u, a_X(su_ERR_ILSEQ)},
   {164315250u, 200u, 580u, a_X(su_ERR_INPROGRESS)},
   {295637505u, 211u, 606u, a_X(su_ERR_INTR)},
   {4001353557u, 216u, 630u, a_X(su_ERR_INVAL)},
   {1727311571u, 222u, 647u, a_X(su_ERR_IO)},
   {1873054828u, 225u, 666u, a_X(su_ERR_ISCONN)},
   {2112607642u, 232u, 694u, a_X(su_ERR_ISDIR)},
   {409311535u, 238u, 709u, a_X(su_ERR_LOOP)},
   {3633286620u, 243u, 743u, a_X(su_ERR_MFILE)},
   {2195249641u, 249u, 763u, a_X(su_ERR_MLINK)},
   {4096082673u, 255u, 778u, a_X(su_ERR_MSGSIZE)},
   {3526377641u, 263u, 795u, a_X(su_ERR_MULTIHOP)},
   {663564256u, 272u, 814u, a_X(su_ERR_NAMETOOLONG)},
   {2628143458u, 284u, 833u, a_X(su_ERR_NETDOWN)},
   {2913781409u, 292u, 849u, a_X(su_ERR_NETRESET)},
   {3902971798u, 301u, 885u, a_X(su_ERR_NETUNREACH)},
   {459859315u, 312u, 908u, a_X(su_ERR_NFILE)},
   {3446690680u, 318u, 938u, a_X(su_ERR_NOBUFS)},
   {4178159620u, 325u, 964u, a_X(su_ERR_NODATA)},
   {4238350791u, 332u, 982u, a_X(su_ERR_NODEV)},
   {1268523872u, 338u, 1016u, a_X(su_ERR_NOENT)},
   {2076180286u, 344u, 1049u, a_X(su_ERR_NOEXEC)},
   {1161014149u, 351u, 1067u, a_X(su_ERR_NOLCK)},
   {1880207780u, 357u, 1086u, a_X(su_ERR_NOLINK)},
   {2297471775u, 364u, 1108u, a_X(su_ERR_NOMEM)},
   {288687459u, 370u, 1131u, a_X(su_ERR_NOMSG)},
   {3588403549u, 376u, 1158u, a_X(su_ERR_NOPROTOOPT)},
   {2608265568u, 387u, 1181u, a_X(su_ERR_NOSPC)},
   {1014226748u, 393u, 1205u, a_X(su_ERR_NOSR)},
   {2306425920u, 398u, 1229u, a_X(su_ERR_NOSTR)},
   {2157612882u, 404u, 1249u, a_X(su_ERR_NOSYS)},
   {1106981846u, 410u, 1274u, a_X(su_ERR_NOTCONN)},
   {3361152810u, 418u, 1298u, a_X(su_ERR_NOTDIR)},
   {1159928508u, 425u, 1314u, a_X(su_ERR_NOTEMPTY)},
   {1391828806u, 434u, 1334u, a_X(su_ERR_NOTOBACCO)},
   {3263554828u, 444u, 1371u, a_X(su_ERR_NOTSOCK)},
   {151164965u, 452u, 1402u, a_X(su_ERR_NOTSUP)},
   {3870455042u, 459u, 1426u, a_X(su_ERR_NOTTY)},
   {273388482u, 465u, 1457u, a_X(su_ERR_NXIO)},
   {727228813u, 470u, 1479u, a_X(su_ERR_OPNOTSUPP)},
   {2142687439u, 480u, 1503u, a_X(su_ERR_OVERFLOW)},
   {1727042864u, 489u, 1545u, a_X(su_ERR_PERM)},
   {1752074821u, 494u, 1569u, a_X(su_ERR_PIPE)},
   {385670576u, 499u, 1581u, a_X(su_ERR_PROTO)},
   {3066850062u, 505u, 1596u, a_X(su_ERR_PROTONOSUPPORT)},
   {1908988597u, 520u, 1619u, a_X(su_ERR_PROTOTYPE)},
   {833145061u, 530u, 1650u, a_X(su_ERR_RANGE)},
   {2868169750u, 536u, 1667u, a_X(su_ERR_ROFS)},
   {2514504004u, 541u, 1688u, a_X(su_ERR_SPIPE)},
   {1572477186u, 547u, 1701u, a_X(su_ERR_SRCH)},
   {1954937632u, 552u, 1717u, a_X(su_ERR_STALE)},
   {3112234335u, 558u, 1739u, a_X(su_ERR_TIME)},
   {1794618926u, 563u, 1753u, a_X(su_ERR_TIMEDOUT)},
   {1586215649u, 572u, 1773u, a_X(su_ERR_TXTBSY)},
   {1014021045u, 579u, 1788u, a_X(su_ERR_WOULDBLOCK)},
   {1748691063u, 590u, 1810u, a_X(su_ERR_XDEV)},
};
# undef a_X
#endif /* su_SOURCE */

#ifdef su_SOURCE /* Lock-out compile-time-tools */
# define a_CORERR_REV_ILL 81u
# define a_CORERR_REV_PRIME 149u
# define a_CORERR_REV_LONGEST 4u
# define a_CORERR_REV_WRAPAROUND 0
static u8 const a_corerr_revmap[a_CORERR_REV_PRIME] = {
   81u, 70u, 6u, 81u, 63u, 36u, 45u, 52u, 81u, 81u,
   34u, 81u, 81u, 58u, 81u, 81u, 29u, 4u, 81u, 41u,
   1u, 68u, 81u, 81u, 48u, 74u, 81u, 81u, 81u, 81u,
   81u, 81u, 42u, 19u, 81u, 20u, 81u, 81u, 81u, 12u,
   47u, 81u, 32u, 64u, 51u, 81u, 81u, 81u, 44u, 27u,
   62u, 69u, 77u, 81u, 56u, 55u, 79u, 37u, 81u, 81u,
   81u, 72u, 5u, 67u, 81u, 81u, 81u, 81u, 81u, 13u,
   81u, 81u, 81u, 81u, 23u, 24u, 14u, 81u, 81u, 81u,
   60u, 81u, 57u, 81u, 81u, 81u, 81u, 18u, 54u, 40u,
   71u, 65u, 78u, 81u, 80u, 22u, 9u, 81u, 49u, 81u,
   81u, 81u, 81u, 11u, 38u, 66u, 3u, 81u, 50u, 43u,
   15u, 2u, 35u, 76u, 25u, 30u, 31u, 8u, 75u, 73u,
   81u, 81u, 81u, 10u, 81u, 81u, 81u, 81u, 16u, 28u,
   81u, 7u, 17u, 46u, 81u, 81u, 26u, 81u, 39u, 53u,
   59u, 81u, 81u, 33u, 21u, 0u, 61u, 81u, 81u
};
#endif /* su_SOURCE */
s-nail-14.9.15/src/su/icodec-dec.c000066400000000000000000000240171352610246600164300ustar00rootroot00000000000000/*@ Implementation of icodec.h: idec.
 *
 * Copyright (c) 2017 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE su_icodec_dec
#define su_SOURCE
#define su_SOURCE_ICODEC_DEC

#include "su/code.h"

#include "su/cs.h"

#include "su/icodec.h"
#include "su/code-in.h"

static u8 const a_icod_atoi[256] = {
   0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
   0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
   0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
   0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
   0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x01,
   0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0xFF,0xFF,
   0xFF,0xFF,0xFF,0xFF,0xFF,0x0A,0x0B,0x0C,0x0D,0x0E,
   0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,
   0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,
   0x23,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x0A,0x0B,0x0C,
   0x0D,0x0E,0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,
   0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,
   0x21,0x22,0x23,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
   0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
   0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
   0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
   0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
   0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
   0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
   0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
   0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
   0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
   0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
   0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
   0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
   0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
};

#define a_X(X) (U64_MAX / (X))
static u64 const a_icod_cutlimit[35] = {
   a_X( 2), a_X( 3), a_X( 4), a_X( 5), a_X( 6), a_X( 7), a_X( 8),
   a_X( 9), a_X(10), a_X(11), a_X(12), a_X(13), a_X(14), a_X(15),
   a_X(16), a_X(17), a_X(18), a_X(19), a_X(20), a_X(21), a_X(22),
   a_X(23), a_X(24), a_X(25), a_X(26), a_X(27), a_X(28), a_X(29),
   a_X(30), a_X(31), a_X(32), a_X(33), a_X(34), a_X(35), a_X(36)
};
#undef a_X

u32
su_idec(void *resp, char const *cbuf, uz clen, u8 base, u32 idec_mode,
      char const **endptr_or_nil){
   /* XXX Brute simple and */
   u8 currc;
   u64 res, cut;
   u32 rv;
   NYD_IN;
   ASSERT(resp != NIL);
   ASSERT_EXEC(cbuf != NIL || clen == 0, clen = 0);

   idec_mode &= su__IDEC_MODE_MASK;
   rv = su_IDEC_STATE_NONE | idec_mode;
   res = 0;

   if(clen == UZ_MAX){
      if(*cbuf == '\0')
         goto jeinval;
   }else if(clen == 0)
      goto jeinval;

   ASSERT(base != 1 && base <= 36); /* XXX _RET_VAL! */
   /*if(base == 1 || base > 36)
    *   goto jeinval;*/

jnumber_sign_rescan:
   /* Leading WS */
   while(su_cs_is_space(*cbuf))
      if(*++cbuf == '\0' || --clen == 0)
         goto jeinval;

   /* Check sign */
   switch(*cbuf){
   case '-':
      rv |= su_IDEC_STATE_SEEN_MINUS;
      /* FALLTHROUGH */
   case '+':
      if(*++cbuf == '\0' || --clen == 0)
         goto jeinval;
      break;
   }

   /* Base detection/skip */
   if(*cbuf != '0'){
      if(base == 0){
         base = 10;

         /* Support BASE#number prefix, where BASE is decimal 2-36 XXX ASCII */
         if(clen > 1){
            char c1, c2, c3;

            if(((c1 = cbuf[0]) >= '0' && c1 <= '9') &&
                  (((c2 = cbuf[1]) == '#') ||
                   (c2 >= '0' && c2 <= '9' && clen > 2 && cbuf[2] == '#'))){
               base = a_icod_atoi[S(u8,c1)];
               if(c2 == '#')
                  c3 = cbuf[2];
               else{
                  c3 = cbuf[3];
                  base *= 10; /* xxx Inline atoi decimal base */
                  base += a_icod_atoi[S(u8,c2)];
               }

               /* We do not interpret this as BASE#number at all if either we
                * did not get a valid base or if the first char is not valid
                * according to base, to comply to the latest interpretion of
                * "prefix", see comment for standard prefixes below */
               if(base < 2 || base > 36 ||
                     (a_icod_atoi[S(u8,c3)] >= base &&
                        !(rv & su_IDEC_MODE_BASE0_NUMBER_SIGN_RESCAN)))
                  base = 10;
               else{
                  if(c2 == '#')
                     clen -= 2, cbuf += 2;
                  else
                     clen -= 3, cbuf += 3;

                  if(rv & su_IDEC_MODE_BASE0_NUMBER_SIGN_RESCAN)
                     goto jnumber_sign_rescan;
               }
            }
         }
      }

      /* Character must be valid for base */
      currc = a_icod_atoi[S(u8,*cbuf)];
      if(currc >= base)
         goto jeinval;
   }else{
      /* 0 always valid as is, fallback base 10 */
      if(*++cbuf == '\0' || --clen == 0)
         goto jleave;

      /* Base "detection" */
      if(base == 0 || base == 2 || base == 16){
         switch(*cbuf){
         case 'x':
         case 'X':
            if((base & 2) == 0){
               base = 0x10;
               goto jprefix_skip;
            }
            break;
         case 'b':
         case 'B':
            if((base & 16) == 0){
               base = 2; /* 0b10 */
               /* Char after prefix must be valid.  However, after some error
                * in the tor software all libraries (which had to) turned to
                * an interpretation of the C standard which says that the
                * prefix may optionally precede an otherwise valid sequence,
                * which means that "0x" is not a STATE_INVAL error but gives
                * a "0" result with a "STATE_BASE" error and a rest of "x" */
jprefix_skip:
#if 1
               if(clen > 1 && a_icod_atoi[S(u8,cbuf[1])] < base)
                  --clen, ++cbuf;
#else
               if(*++cbuf == '\0' || --clen == 0)
                  goto jeinval;

               /* Character must be valid for base, invalid otherwise */
               currc = a_icod_atoi[S(u8,*cbuf)];
               if(currc >= base)
                  goto jeinval;
#endif
            }
            break;
         default:
            if(base == 0)
               base = 010;
            break;
         }
      }

      /* Character must be valid for base, _EBASE otherwise */
      currc = a_icod_atoi[S(u8,*cbuf)];
      if(currc >= base)
         goto jebase;
   }

   for(cut = a_icod_cutlimit[base - 2];;){
      if(res >= cut){
         if(res == cut){
            res *= base;
            if(res > U64_MAX - currc)
               goto jeover;
            res += currc;
         }else
            goto jeover;
      }else{
         res *= base;
         res += currc;
      }

      if(*++cbuf == '\0' || --clen == 0)
         break;

      currc = a_icod_atoi[S(u8,*cbuf)];
      if(currc >= base)
         goto jebase;
   }

jleave:
   do{
      u64 umask;

      switch(rv & su__IDEC_MODE_LIMIT_MASK){
      case su_IDEC_MODE_LIMIT_8BIT: umask = U8_MAX; break;
      case su_IDEC_MODE_LIMIT_16BIT: umask = U16_MAX; break;
      case su_IDEC_MODE_LIMIT_32BIT: umask = U32_MAX; break;
      default: umask = U64_MAX; break;
      }
      if((rv & su_IDEC_MODE_SIGNED_TYPE) &&
            (!(rv & su_IDEC_MODE_POW2BASE_UNSIGNED) || !IS_POW2(base)))
         umask >>= 1;

      if(res & ~umask){
         if((rv & (su_IDEC_MODE_SIGNED_TYPE | su_IDEC_STATE_SEEN_MINUS)
               ) == (su_IDEC_MODE_SIGNED_TYPE | su_IDEC_STATE_SEEN_MINUS)){
            if(res > umask + 1){
               res = umask << 1;
               res &= ~umask;
            }else{
               res = -res;
               break;
            }
         }else
            res = umask;
         rv |= su_IDEC_STATE_EOVERFLOW;
      }else if(rv & su_IDEC_STATE_SEEN_MINUS)
         res = -res;
   }while(0);

   switch(rv & su__IDEC_MODE_LIMIT_MASK){
   case su_IDEC_MODE_LIMIT_8BIT:
      if(rv & su_IDEC_MODE_SIGNED_TYPE)
         *S(s8*,resp) = S(s8,res);
      else
         *S(u8*,resp) = S(u8,res);
      break;
   case su_IDEC_MODE_LIMIT_16BIT:
      if(rv & su_IDEC_MODE_SIGNED_TYPE)
         *S(s16*,resp) = S(s16,res);
      else
         *S(u16*,resp) = S(u16,res);
      break;
   case su_IDEC_MODE_LIMIT_32BIT:
      if(rv & su_IDEC_MODE_SIGNED_TYPE)
         *S(s32*,resp) = S(s32,res);
      else
         *S(u32*,resp) = S(u32,res);
      break;
   default:
      if(rv & su_IDEC_MODE_SIGNED_TYPE)
         *S(s64*,resp) = S(s64,res);
      else
         *S(u64*,resp) = S(u64,res);
      break;
   }
   if(rv & su_IDEC_MODE_LIMIT_NOERROR)
      rv &= ~su_IDEC_STATE_EOVERFLOW;

   if(endptr_or_nil != NIL)
      *endptr_or_nil = cbuf;
   if(*cbuf == '\0' || clen == 0)
      rv |= su_IDEC_STATE_CONSUMED;
   NYD_OU;
   return rv;

jeinval:
   rv |= su_IDEC_STATE_EINVAL;
   goto j_maxval;
jebase:
   /* Not a base error for terminator and whitespace! */
   if(*cbuf != '\0' && !su_cs_is_space(*cbuf))
      rv |= su_IDEC_STATE_EBASE;
   goto jleave;

jeover:
   /* Overflow error: consume input until bad character or length out */
   for(;;){
      if(*++cbuf == '\0' || --clen == 0)
         break;
      currc = a_icod_atoi[S(u8,*cbuf)];
      if(currc >= base)
         break;
   }

   rv |= su_IDEC_STATE_EOVERFLOW;
j_maxval:
   if(rv & su_IDEC_MODE_SIGNED_TYPE)
      res = (rv & su_IDEC_STATE_SEEN_MINUS) ? S(u64,S64_MIN) : S(u64,S64_MAX);
   else
      res = U64_MAX;
   rv &= ~su_IDEC_STATE_SEEN_MINUS;
   goto jleave;
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/su/icodec-enc.c000066400000000000000000000070631352610246600164440ustar00rootroot00000000000000/*@ Implementation of icodec.h: enc.
 *
 * Copyright (c) 2017 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE su_icodec_enc
#define su_SOURCE
#define su_SOURCE_ICODEC_ENC

#include "su/code.h"

#include "su/cs.h"

#include "su/icodec.h"
#include "su/code-in.h"

/* "Is power-of-two" table, and if, shift (indexed by base-2) */
static u8 const a_icoe_shifts[35] = {
         1, 0, 2, 0, 0, 0, 3, 0,   /*  2 ..  9 */
   0, 0, 0, 0, 0, 0, 4, 0, 0, 0,   /* 10 .. 19 */
   0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   /* 20 .. 29 */
   0, 0, 5, 0, 0, 0, 0             /* 30 .. 36 */
};

/* XXX itoa byte maps not locale aware.. */
static char const a_icoe_upper[36 +1] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
static char const a_icoe_lower[36 +1] = "0123456789abcdefghijklmnopqrstuvwxyz";

char *
su_ienc(char cbuf[su_IENC_BUFFER_SIZE], u64 value, u8 base, u32 ienc_mode){
   enum{a_ISNEG = 1u< 36)){
      rv = NIL;
      goto jleave;
   }

   ienc_mode &= su__IENC_MODE_MASK;
   *(rv = &cbuf[su_IENC_BUFFER_SIZE -1]) = '\0';
   itoa = (ienc_mode & su_IENC_MODE_LOWERCASE) ? a_icoe_lower : a_icoe_upper;

   if(S(s64,value) < 0){
      ienc_mode |= a_ISNEG;
      if(ienc_mode & su_IENC_MODE_SIGNED_TYPE){
         /* self->is_negative = TRU1; */
         value = -value;
      }
   }

   if((shiftmodu = a_icoe_shifts[base - 2]) != 0){
      --base; /* convert to mask */
      do{
         *--rv = itoa[value & base];
         value >>= shiftmodu;
      }while(value != 0);

      if(!(ienc_mode & su_IENC_MODE_NO_PREFIX)){
         /* self->before_prefix = cp; */
         if(shiftmodu == 4)
            *--rv = 'x';
         else if(shiftmodu == 1)
            *--rv = 'b';
         else if(shiftmodu != 3){
            ++base; /* Reconvert from mask */
            goto jnumber_sign_prefix;
         }
         *--rv = '0';
      }
   }else{
      do{
         shiftmodu = value % base;
         value /= base;
         *--rv = itoa[shiftmodu];
      }while(value != 0);

      if(!(ienc_mode & su_IENC_MODE_NO_PREFIX) && base != 10){
jnumber_sign_prefix:
         value = base;
         base = 10;
         *--rv = '#';
         do{
            shiftmodu = value % base;
            value /= base;
            *--rv = itoa[shiftmodu];
         }while(value != 0);
      }

      if(ienc_mode & su_IENC_MODE_SIGNED_TYPE){
         char c;

         if(ienc_mode & a_ISNEG)
            c = '-';
         else if(ienc_mode & su_IENC_MODE_SIGNED_PLUS)
            c = '+';
         else if(ienc_mode & su_IENC_MODE_SIGNED_SPACE)
            c = ' ';
         else
            c = '\0';

         if(c != '\0')
            *--rv = c;
      }
   }

jleave:
   NYD_OU;
   return rv;
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/su/mem-alloc.c000066400000000000000000000516051352610246600163220ustar00rootroot00000000000000/*@ Implementation of mem.h: allocation functions.
 *@ TODO - flux memory: pool specific (like _auto_ and _lofi_), but normal
 *@ TODO   heap beside, which can be free()d in random order etc.
 *@ TODO - dump,trace,etc. should take a log::domain object; if NIL: builtin.
 *@ TODO   (we need a logdom_detach() or so which ensures all resources are
 *@ TODO   initialized [better than asserting it is device-based dom]).
 *@ TODO - port C++ memcache
 *
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE su_mem_alloc
#define su_SOURCE
#define su_SOURCE_MEM_ALLOC

#include "su/code.h"

#include  /* TODO -> port C++ cache */

#include "su/mem.h"
#include "su/code-in.h"

#ifndef su_MEM_ALLOC_DEBUG
# define a_MEMA_DBG(X)
# define a_MEMA_HOPE_SIZE_ADD 0
#else
# define a_MEMA_DBG(X) X
# define a_MEMA_HOPE_SIZE (2 * 8 * sizeof(u8))
# define a_MEMA_HOPE_INC(P) (P) += 8 * sizeof(u8)
# define a_MEMA_HOPE_DEC(P) (P) -= 8 * sizeof(u8)
# define a_MEMA_HOPE_SIZE_ADD \
   (a_MEMA_HOPE_SIZE + sizeof(struct a_mema_heap_chunk))

   /* We use address-induced canary values, inspiration (but he did not invent)
    * and primes from maxv@netbsd.org, src/sys/kern/subr_kmem.c */
# define a_MEMA_HOPE_LOWER(M,P) \
do{\
   u64 __h__ = R(up,P);\
   __h__ *= (S(u64,0x9E37FFFFu) << 32) | 0xFFFC0000u;\
   __h__ >>= 56;\
   (M) = S(u8,__h__);\
}while(0)

# define a_MEMA_HOPE_UPPER(M,P) \
do{\
   u32 __i__;\
   u64 __x__, __h__ = R(up,P);\
   __h__ *= (S(u64,0x9E37FFFFu) << 32) | 0xFFFC0000u;\
   for(__i__ = 56; __i__ != 0; __i__ -= 8)\
      if((__x__ = (__h__ >> __i__)) != 0){\
         (M) = S(u8,__x__);\
         break;\
      }\
   if(__i__ == 0)\
      (M) = 0xAAu;\
}while(0)

# define a_MEMA_HOPE_SET(T,C) \
do{\
   union a_mema_ptr __xp;\
   struct a_mema_chunk *__xc;\
   __xp.map_vp = (C).map_vp;\
   __xc = R(struct a_mema_chunk*,__xp.T - 1);\
   a_MEMA_HOPE_INC((C).map_cp);\
   a_MEMA_HOPE_LOWER(__xp.map_u8p[0], &__xp.map_u8p[0]);\
   a_MEMA_HOPE_LOWER(__xp.map_u8p[1], &__xp.map_u8p[1]);\
   a_MEMA_HOPE_LOWER(__xp.map_u8p[2], &__xp.map_u8p[2]);\
   a_MEMA_HOPE_LOWER(__xp.map_u8p[3], &__xp.map_u8p[3]);\
   a_MEMA_HOPE_LOWER(__xp.map_u8p[4], &__xp.map_u8p[4]);\
   a_MEMA_HOPE_LOWER(__xp.map_u8p[5], &__xp.map_u8p[5]);\
   a_MEMA_HOPE_LOWER(__xp.map_u8p[6], &__xp.map_u8p[6]);\
   a_MEMA_HOPE_LOWER(__xp.map_u8p[7], &__xp.map_u8p[7]);\
   a_MEMA_HOPE_INC(__xp.map_u8p) + __xc->mac_size - __xc->mac_user_off;\
   a_MEMA_HOPE_UPPER(__xp.map_u8p[0], &__xp.map_u8p[0]);\
   a_MEMA_HOPE_UPPER(__xp.map_u8p[1], &__xp.map_u8p[1]);\
   a_MEMA_HOPE_UPPER(__xp.map_u8p[2], &__xp.map_u8p[2]);\
   a_MEMA_HOPE_UPPER(__xp.map_u8p[3], &__xp.map_u8p[3]);\
   a_MEMA_HOPE_UPPER(__xp.map_u8p[4], &__xp.map_u8p[4]);\
   a_MEMA_HOPE_UPPER(__xp.map_u8p[5], &__xp.map_u8p[5]);\
   a_MEMA_HOPE_UPPER(__xp.map_u8p[6], &__xp.map_u8p[6]);\
   a_MEMA_HOPE_UPPER(__xp.map_u8p[7], &__xp.map_u8p[7]);\
}while(0)

# define a_MEMA_HOPE_GET_TRACE(T,C,BAD) \
do{\
   a_MEMA_HOPE_INC((C).map_cp);\
   a_MEMA_HOPE_GET(T, C, BAD);\
   a_MEMA_HOPE_INC((C).map_cp);\
}while(0)

# define a_MEMA_HOPE_GET(T,C,BAD) \
do{\
   union a_mema_ptr __xp;\
   struct a_mema_chunk *__xc;\
   u32 __i;\
   u8 __m;\
   __xp.map_vp = (C).map_vp;\
   a_MEMA_HOPE_DEC(__xp.map_cp);\
   (C).map_cp = __xp.map_cp;\
   __xc = R(struct a_mema_chunk*,__xp.T - 1);\
   (BAD) = FAL0;\
   __i = 0;\
   a_MEMA_HOPE_LOWER(__m, &__xp.map_u8p[0]);\
      if(__xp.map_u8p[0] != __m) __i |= 1<<7;\
   a_MEMA_HOPE_LOWER(__m, &__xp.map_u8p[1]);\
      if(__xp.map_u8p[1] != __m) __i |= 1<<6;\
   a_MEMA_HOPE_LOWER(__m, &__xp.map_u8p[2]);\
      if(__xp.map_u8p[2] != __m) __i |= 1<<5;\
   a_MEMA_HOPE_LOWER(__m, &__xp.map_u8p[3]);\
      if(__xp.map_u8p[3] != __m) __i |= 1<<4;\
   a_MEMA_HOPE_LOWER(__m, &__xp.map_u8p[4]);\
      if(__xp.map_u8p[4] != __m) __i |= 1<<3;\
   a_MEMA_HOPE_LOWER(__m, &__xp.map_u8p[5]);\
      if(__xp.map_u8p[5] != __m) __i |= 1<<2;\
   a_MEMA_HOPE_LOWER(__m, &__xp.map_u8p[6]);\
      if(__xp.map_u8p[6] != __m) __i |= 1<<1;\
   a_MEMA_HOPE_LOWER(__m, &__xp.map_u8p[7]);\
      if(__xp.map_u8p[7] != __m) __i |= 1<<0;\
   if(__i != 0){\
      (BAD) = (__i >= (1<<3)) ? TRUM1 : TRU1;\
      a_MEMA_HOPE_INC((C).map_cp);\
      su_log_write(su_LOG_ALERT,\
         "! SU memory: %p: corrupt lower canary: " \
            "0x%02X: %s, line %" PRIu32 "\n",\
         (C).map_cp, __i, su_DBG_LOC_ARGS_FILE, su_DBG_LOC_ARGS_LINE);\
      a_MEMA_HOPE_DEC((C).map_cp);\
   }\
   a_MEMA_HOPE_INC(__xp.map_u8p) + __xc->mac_size - __xc->mac_user_off;\
   __i = 0;\
   a_MEMA_HOPE_UPPER(__m, &__xp.map_u8p[0]);\
      if(__xp.map_u8p[0] != __m) __i |= 1<<0;\
   a_MEMA_HOPE_UPPER(__m, &__xp.map_u8p[1]);\
      if(__xp.map_u8p[1] != __m) __i |= 1<<1;\
   a_MEMA_HOPE_UPPER(__m, &__xp.map_u8p[2]);\
      if(__xp.map_u8p[2] != __m) __i |= 1<<2;\
   a_MEMA_HOPE_UPPER(__m, &__xp.map_u8p[3]);\
      if(__xp.map_u8p[3] != __m) __i |= 1<<3;\
   a_MEMA_HOPE_UPPER(__m, &__xp.map_u8p[4]);\
      if(__xp.map_u8p[4] != __m) __i |= 1<<4;\
   a_MEMA_HOPE_UPPER(__m, &__xp.map_u8p[5]);\
      if(__xp.map_u8p[5] != __m) __i |= 1<<5;\
   a_MEMA_HOPE_UPPER(__m, &__xp.map_u8p[6]);\
      if(__xp.map_u8p[6] != __m) __i |= 1<<6;\
   a_MEMA_HOPE_UPPER(__m, &__xp.map_u8p[7]);\
      if(__xp.map_u8p[7] != __m) __i |= 1<<7;\
   if(__i != 0){\
      (BAD) |= (__i >= (1<<3)) ? TRUM1 : TRU1;\
      a_MEMA_HOPE_INC((C).map_cp);\
      su_log_write(su_LOG_ALERT,\
         "! SU memory: %p: corrupt upper canary: " \
            "0x%02X: %s, line %" PRIu32 "\n",\
         (C).map_cp, __i, su_DBG_LOC_ARGS_FILE, su_DBG_LOC_ARGS_LINE);\
      a_MEMA_HOPE_DEC((C).map_cp);\
   }\
   if(BAD)\
      su_log_write(su_LOG_ALERT,\
         "! SU memory:   ..canary last seen: %s, line %" PRIu32 "\n",\
         __xc->mac_file, __xc->mac_line);\
}while(0)
#endif /* su_MEM_ALLOC_DEBUG */

#ifdef su_MEM_ALLOC_DEBUG
struct a_mema_chunk{
   char const *mac_file;
   u32 mac_line : 29;
   u32 mac_isfree : 1;
   u32 mac_mark : 2;
   u32 mac_user_off;    /* .mac_size-.mac_user_off: user size */
   uz mac_size;
};
# define a_MEMA_MARK_TO_STORE(X) \
   ((S(u32,X) >> su__MEM_ALLOC_MARK_SHIFT) & su__MEM_ALLOC_MARK_MASK)
# define a_MEMA_STORE_TO_MARK(MACP) \
   ((MACP)->mac_mark << su__MEM_ALLOC_MARK_SHIFT)

/* The heap memory mem_free() may become delayed to detect double frees */
struct a_mema_heap_chunk{
   struct a_mema_chunk mahc_super;
   struct a_mema_heap_chunk *mahc_prev;
   struct a_mema_heap_chunk *mahc_next;
};

struct a_mema_stats{
   u64 mas_cnt_all;
   u64 mas_cnt_curr;
   u64 mas_cnt_max;
   u64 mas_mem_all;
   u64 mas_mem_curr;
   u64 mas_mem_max;
};
#endif /* su_MEM_ALLOC_DEBUG */

union a_mema_ptr{
   void *map_vp;
   char *map_cp;
   u8 *map_u8p;
#ifdef su_MEM_ALLOC_DEBUG
   struct a_mema_chunk *map_c;
   struct a_mema_heap_chunk *map_hc;
#endif
};

#ifdef su_MEM_ALLOC_DEBUG
static char const * const a_mema_mark_names[] = {
# ifdef su_USECASE_MX
   "heap", "auto", "auto-huge", "lofi"
# else
   "zero/0", "one/1", "two/2", "three/3"
# endif
};
CTAV(a_MEMA_MARK_TO_STORE(su_MEM_ALLOC_MARK_0) == 0);
CTAV(a_MEMA_MARK_TO_STORE(su_MEM_ALLOC_MARK_1) == 1);
CTAV(a_MEMA_MARK_TO_STORE(su_MEM_ALLOC_MARK_2) == 2);
CTAV(a_MEMA_MARK_TO_STORE(su_MEM_ALLOC_MARK_3) == 3);
#endif

static uz a_mema_conf /*= su_MEM_CONF_NONE*/;
CTAV(su_MEM_CONF_NONE == 0);

#ifdef su_MEM_ALLOC_DEBUG
static struct a_mema_heap_chunk *a_mema_heap_list;
static struct a_mema_heap_chunk *a_mema_free_list;

static struct a_mema_stats a_mema_stats[su__MEM_ALLOC_MARK_MAX + 1];
#endif

#ifdef su_MEM_ALLOC_DEBUG
/* */
static void a_mema_release_free(void);
#endif

#ifdef su_MEM_ALLOC_DEBUG
static void
a_mema_release_free(void){
   uz c, s;
   union a_mema_ptr p;
   NYD2_IN;

   if((p.map_hc = a_mema_free_list) != NIL){
      a_mema_free_list = NIL;
      c = s = 0;

      for(; p.map_hc != NIL;){
         void *vp;

         vp = p.map_hc;
         ++c;
         s += p.map_c->mac_size;
         p.map_hc = p.map_hc->mahc_next;
         free(vp);
      }

      su_log_write(su_LOG_INFO,
         "su_mem_set_conf(LINGER_FREE_RELEASE): freed %" PRIuZ
            " chunks / %" PRIuZ " bytes\n",
         c, s);
   }
   NYD2_OU;
}
#endif /* su_MEM_ALLOC_DEBUG */

#ifdef su_MEM_ALLOC_DEBUG
boole
su__mem_check(su_DBG_LOC_ARGS_DECL_SOLE){
   union a_mema_ptr p, xp;
   boole anybad, isbad;
   NYD2_IN;

   anybad = FAL0;

   for(p.map_hc = a_mema_heap_list; p.map_hc != NIL;
         p.map_hc = p.map_hc->mahc_next){
      xp = p;
      ++xp.map_hc;
      a_MEMA_HOPE_GET_TRACE(map_hc, xp, isbad);
      if(isbad){
         anybad |= isbad;
         su_log_write(su_LOG_ALERT,
            "! SU memory: CANARY ERROR (heap): %p (%" PRIuZ
               " bytes): %s, line %" PRIu32 "\n",
            xp.map_vp, (p.map_c->mac_size - p.map_c->mac_user_off),
            p.map_c->mac_file, p.map_c->mac_line);
      }
   }

   for(p.map_hc = a_mema_free_list; p.map_hc != NIL;
         p.map_hc = p.map_hc->mahc_next){
      xp = p;
      ++xp.map_hc;
      a_MEMA_HOPE_GET_TRACE(map_hc, xp, isbad);
      if(isbad){
         anybad |= isbad;
         su_log_write(su_LOG_ALERT,
            "! SU memory: CANARY ERROR (free list): %p (%" PRIuZ
               " bytes): %s, line %" PRIu32 "\n",
            xp.map_vp, (p.map_c->mac_size - p.map_c->mac_user_off),
            p.map_c->mac_file, p.map_c->mac_line);
      }
   }

   if(anybad)
      su_log_write(((a_mema_conf & su_MEM_CONF_ON_ERROR_EMERG)
            ? su_LOG_EMERG : su_LOG_CRIT),
         "SU memory check: errors encountered");
   NYD2_OU;
   return anybad;
}

boole
su__mem_trace(su_DBG_LOC_ARGS_DECL_SOLE){
   union a_mema_ptr p, xp;
   u32 mark;
   boole anybad, isbad;
   NYD2_IN;

   anybad = FAL0;

   for(mark = su__MEM_ALLOC_MARK_MAX;; --mark){
      struct a_mema_stats const *masp;

      masp = &a_mema_stats[mark];

      su_log_write(su_LOG_INFO,
         "MARK \"%s\" MEMORY:\n"
         "   Count cur/peek/all: %7" PRIu64 "/%7" PRIu64 "/%10" PRIu64 "\n"
         "  Memory cur/peek/all: %7" PRIu64 "/%7" PRIu64 "/%10" PRIu64 "\n\n",
         a_mema_mark_names[mark],
         masp->mas_cnt_curr, masp->mas_cnt_max, masp->mas_cnt_all,
         masp->mas_mem_curr, masp->mas_mem_max, masp->mas_mem_all);

      for(p.map_hc = a_mema_heap_list; p.map_hc != NIL;
            p.map_hc = p.map_hc->mahc_next){
         if(p.map_c->mac_mark != mark)
            continue;
         xp = p;
         ++xp.map_hc;
         a_MEMA_HOPE_GET_TRACE(map_hc, xp, isbad);
         anybad |= isbad;
         su_log_write((isbad ? su_LOG_ALERT : su_LOG_INFO),
            "  %s%p (%" PRIuZ " bytes): %s, line %" PRIu32 "\n",
            (isbad ? "! SU memory: CANARY ERROR: " : ""), xp.map_vp,
            p.map_c->mac_size - p.map_c->mac_user_off,
            p.map_c->mac_file, p.map_c->mac_line);
      }

      if(mark == su_MEM_ALLOC_MARK_0)
         break;
   }

   if(a_mema_free_list != NIL){
      su_log_write(su_LOG_INFO, "Freed memory lingering for release:\n");

      for(p.map_hc = a_mema_free_list; p.map_hc != NIL;
            p.map_hc = p.map_hc->mahc_next){
         xp = p;
         ++xp.map_hc;
         a_MEMA_HOPE_GET_TRACE(map_hc, xp, isbad);
         anybad |= isbad;
         su_log_write((isbad ? su_LOG_ALERT : su_LOG_INFO),
            "  %s%p (%" PRIuZ " bytes): %s, line %" PRIu32 "\n",
            (isbad ? "! SU memory: CANARY ERROR: " : ""), xp.map_vp,
            p.map_c->mac_size - p.map_c->mac_user_off,
            p.map_c->mac_file, p.map_c->mac_line);
      }
   }
   NYD2_OU;
   return anybad;
}
#endif /* su_MEM_ALLOC_DEBUG */

void *
su_mem_allocate(uz size, uz no, u32 maf  su_DBG_LOC_ARGS_DECL){
#ifdef su_MEM_ALLOC_DEBUG
   u32 mark;
   union a_mema_ptr p;
   uz user_sz, user_no;
#endif
   void *rv;
   NYD_IN;

   a_MEMA_DBG( user_sz = size su_COMMA user_no = no; )
   if(UNLIKELY(size == 0))
      size = 1;
   if(UNLIKELY(no == 0))
      no = 1;
   maf &= su__MEM_ALLOC_USER_MASK;

   rv = NIL;

   if(a_MEMA_DBG( UZ_MAX - a_MEMA_HOPE_SIZE_ADD > size && )
         LIKELY(((maf & su_MEM_ALLOC_32BIT_OVERFLOW) ? U32_MAX :
               ((maf & su_MEM_ALLOC_31BIT_OVERFLOW) ? U32_MAX >> 1 : UZ_MAX))
            / no > size + a_MEMA_HOPE_SIZE_ADD)){
      size *= no;
#if !defined su_MEM_ALLOC_DEBUG && !defined su_HAVE_MEM_CANARIES_DISABLE
      if(size < su_MEM_ALLOC_MIN)
         size = su_MEM_ALLOC_MIN;
#endif
#ifdef su_MEM_ALLOC_DEBUG
      size += a_MEMA_HOPE_SIZE_ADD;
      user_sz *= user_no;
#endif

      if(LIKELY((rv = malloc(size)) != NIL)){
         /* XXX Of course this may run on odd ranges, but once upon a time
          * XXX i will port my C++ cache and then we're fine again (it will not
          * XXX even be handled in here) */
         if(maf & su_MEM_ALLOC_CLEAR)
            su_mem_set(rv, 0, size);
#ifdef su_MEM_ALLOC_DEBUG
         else
            su_mem_set(rv, 0xAA, size);
         p.map_vp = rv;

         p.map_hc->mahc_prev = NIL;
         if((p.map_hc->mahc_next = a_mema_heap_list) != NIL)
            a_mema_heap_list->mahc_prev = p.map_hc;
         p.map_c->mac_file = su_DBG_LOC_ARGS_FILE;
         p.map_c->mac_line = su_DBG_LOC_ARGS_LINE;
         p.map_c->mac_isfree = FAL0;
         p.map_c->mac_mark = mark = a_MEMA_MARK_TO_STORE(maf);
         ASSERT(size - user_sz <= S32_MAX);
         p.map_c->mac_user_off = S(u32,size - user_sz);
         p.map_c->mac_size = size;
         a_mema_heap_list = p.map_hc++;

         a_MEMA_HOPE_SET(map_hc, p);
         rv = p.map_vp;

         ++a_mema_stats[mark].mas_cnt_all;
         ++a_mema_stats[mark].mas_cnt_curr;
         a_mema_stats[mark].mas_cnt_max = MAX(
               a_mema_stats[mark].mas_cnt_max,
               a_mema_stats[mark].mas_cnt_curr);
         a_mema_stats[mark].mas_mem_all += user_sz;
         a_mema_stats[mark].mas_mem_curr += user_sz;
         a_mema_stats[mark].mas_mem_max = MAX(
               a_mema_stats[mark].mas_mem_max,
               a_mema_stats[mark].mas_mem_curr);
#endif /* su_MEM_ALLOC_DEBUG */
      }else
         su_state_err(su_STATE_ERR_NOMEM, maf,
            _("SU memory: allocation request"));
   }else
      su_state_err(su_STATE_ERR_OVERFLOW, maf,
         _("SU memory: allocation request"));
   NYD_OU;
   return rv;
}

void *
su_mem_reallocate(void *ovp, uz size, uz no, u32 maf  su_DBG_LOC_ARGS_DECL){
#ifdef su_MEM_ALLOC_DEBUG
   u32 mark;
   union a_mema_ptr p;
   void *origovp;
   uz user_sz, user_no, orig_sz;
#endif
   void *rv;
   NYD_IN;

   a_MEMA_DBG( user_sz = size su_COMMA user_no = no su_COMMA orig_sz = 0; )
   if(UNLIKELY(size == 0))
      size = 1;
   if(UNLIKELY(no == 0))
      no = 1;
   maf &= su__MEM_ALLOC_USER_MASK;

   rv = NIL;

#ifdef su_MEM_ALLOC_DEBUG
   if((p.map_vp = origovp = ovp) != NIL){
      boole isbad;

      ovp = NIL;
      a_MEMA_HOPE_GET(map_hc, p, isbad);
      --p.map_hc;

      if(!p.map_c->mac_isfree)
         orig_sz = p.map_c->mac_size - p.map_c->mac_user_off;
      else if(isbad == TRUM1){
         su_log_write(su_LOG_ALERT,
            "SU memory: reallocation: pointer corrupted!  At %s, line %" PRIu32
               "\n\tLast seen: %s, line %" PRIu32 "\n"
            su_DBG_LOC_ARGS_USE, p.map_c->mac_file, p.map_c->mac_line);
         su_state_err(su_STATE_ERR_NOMEM, maf,
            _("SU memory: reallocation of corrupted pointer"));
         goto su_NYD_OU_LABEL;
      }else{
         su_log_write(su_LOG_ALERT,
            "SU memory: reallocation: pointer freed!  At %s, line %" PRIu32
               "\n\tLast seen: %s, line %" PRIu32 "\n"
            su_DBG_LOC_ARGS_USE, p.map_c->mac_file, p.map_c->mac_line);
         su_state_err(su_STATE_ERR_NOMEM, maf,
            _("SU memory: reallocation of a freed pointer"));
         goto su_NYD_OU_LABEL;
      }
   }
#endif /* su_MEM_ALLOC_DEBUG */

   if(a_MEMA_DBG( UZ_MAX - a_MEMA_HOPE_SIZE_ADD > size && )
         LIKELY(((maf & su_MEM_ALLOC_32BIT_OVERFLOW) ? U32_MAX :
               ((maf & su_MEM_ALLOC_31BIT_OVERFLOW) ? U32_MAX >> 1 : UZ_MAX))
            / no > size + a_MEMA_HOPE_SIZE_ADD)){
      size *= no;
#if !defined su_MEM_ALLOC_DEBUG && !defined su_HAVE_MEM_CANARIES_DISABLE
      if(size < su_MEM_ALLOC_MIN)
         size = su_MEM_ALLOC_MIN;
#endif
      size *= no;
#ifdef su_MEM_ALLOC_DEBUG
      size += a_MEMA_HOPE_SIZE_ADD;
      user_sz *= user_no;
#endif

      if(UNLIKELY((rv = realloc(ovp, size)) == NIL))
         su_state_err(su_STATE_ERR_NOMEM, maf,
            _("SU memory: reallocation request"));
#ifdef su_MEM_ALLOC_DEBUG
      else{
         p.map_vp = rv;

         p.map_hc->mahc_prev = NIL;
         if((p.map_hc->mahc_next = a_mema_heap_list) != NIL)
            a_mema_heap_list->mahc_prev = p.map_hc;
         p.map_c->mac_file = su_DBG_LOC_ARGS_FILE;
         p.map_c->mac_line = su_DBG_LOC_ARGS_LINE;
         p.map_c->mac_isfree = FAL0;
         p.map_c->mac_mark = mark = a_MEMA_MARK_TO_STORE(maf);
         ASSERT(size - user_sz <= S32_MAX);
         p.map_c->mac_user_off = S(u32,size - user_sz);
         p.map_c->mac_size = size;
         size -= p.map_c->mac_user_off; /* Real user size for potential copy */
         a_mema_heap_list = p.map_hc++;

         a_MEMA_HOPE_SET(map_hc, p);
         rv = p.map_vp;

         ++a_mema_stats[mark].mas_cnt_all;
         ++a_mema_stats[mark].mas_cnt_curr;
         a_mema_stats[mark].mas_cnt_max = MAX(
               a_mema_stats[mark].mas_cnt_max,
               a_mema_stats[mark].mas_cnt_curr);
         a_mema_stats[mark].mas_mem_all += user_sz;
         a_mema_stats[mark].mas_mem_curr += user_sz;
         a_mema_stats[mark].mas_mem_max = MAX(
               a_mema_stats[mark].mas_mem_max,
               a_mema_stats[mark].mas_mem_curr);

         if(origovp != NIL){
            su_mem_copy(rv, origovp, MIN(orig_sz, size));
            su_mem_free(origovp su_DBG_LOC_ARGS_USE);
         }
      }
#endif /* su_MEM_ALLOC_DEBUG */
   }else
      su_state_err(su_STATE_ERR_OVERFLOW, maf,
         _("SU memory: reallocation request"));
   NYD_OU;
   return rv;
}

void
su_mem_free(void *ovp  su_DBG_LOC_ARGS_DECL){
   NYD_IN;

   if(LIKELY(ovp != NIL)){
#ifdef su_MEM_ALLOC_DEBUG
      u32 mark;
      uz orig_sz;
      union a_mema_ptr p;
      boole isbad;

      p.map_vp = ovp;
      a_MEMA_HOPE_GET(map_hc, p, isbad);
      --p.map_hc;

      if(isbad == TRUM1){
         su_log_write(su_LOG_ALERT,
            "SU memory: free of corrupted pointer at %s, line %" PRIu32 "\n"
            "\tLast seen: %s, line %" PRIu32 "\n"
            su_DBG_LOC_ARGS_USE, p.map_c->mac_file, p.map_c->mac_line);
         goto su_NYD_OU_LABEL;
      }else if(p.map_c->mac_isfree){
         su_log_write(su_LOG_ALERT,
            "SU memory: double-free avoided at %s, line %" PRIu32 "\n"
            "\tLast seen: %s, line %" PRIu32 "\n"
            su_DBG_LOC_ARGS_USE, p.map_c->mac_file, p.map_c->mac_line);
         goto su_NYD_OU_LABEL;
      }

      orig_sz = p.map_c->mac_size - p.map_c->mac_user_off;
      su_mem_set(ovp, 0xBA, orig_sz);
      ovp = p.map_vp;

      p.map_c->mac_file = su_DBG_LOC_ARGS_FILE;
      p.map_c->mac_line = su_DBG_LOC_ARGS_LINE;
      p.map_c->mac_isfree = TRU1;
      if(p.map_hc == a_mema_heap_list){
         if((a_mema_heap_list = p.map_hc->mahc_next) != NIL)
            a_mema_heap_list->mahc_prev = NIL;
      }else
         p.map_hc->mahc_prev->mahc_next = p.map_hc->mahc_next;
      if(p.map_hc->mahc_next != NIL)
         p.map_hc->mahc_next->mahc_prev = p.map_hc->mahc_prev;

      mark = p.map_c->mac_mark;
      --a_mema_stats[mark].mas_cnt_curr;
      a_mema_stats[mark].mas_mem_curr -= orig_sz;

      if(a_mema_conf & su_MEM_CONF_LINGER_FREE){
         p.map_hc->mahc_next = a_mema_free_list;
         a_mema_free_list = p.map_hc;
      }else
#endif /* su_MEM_ALLOC_DEBUG */
         free(ovp);
   }
#ifdef su_MEM_ALLOC_DEBUG
   else
      su_log_write(su_LOG_DEBUG,
         "SU memory: free(NIL) from %s, line %" PRIu32 "\n"
         su_DBG_LOC_ARGS_USE);
#endif
   NYD_OU;
}

void
su_mem_set_conf(u32 mco, uz val){
   uz rmco;
   NYD_IN;

   rmco = S(uz,mco);
   ASSERT_NYD(rmco <= su__MEM_CONF_MAX);

   if((rmco & su_MEM_CONF_LINGER_FREE_RELEASE) ||
         (!val && (rmco & su_MEM_CONF_LINGER_FREE))){
      rmco &= ~su_MEM_CONF_LINGER_FREE_RELEASE;
#ifdef su_MEM_ALLOC_DEBUG
      su_mem_check();
      a_mema_release_free();
#endif
   }

   /* xxx !MEM_DEBUG does not test whether mem_conf_option is available */
   if(rmco != 0){
      if(val != FAL0)
         a_mema_conf |= rmco;
      else
         a_mema_conf &= ~rmco;
   }
   NYD_OU;
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/su/mem-bag.c000066400000000000000000000570541352610246600157650ustar00rootroot00000000000000/*@ Implementation of mem-bag.h.
 *@ TODO - flux memory: pool specific (like _auto_ and _lofi_), but normal
 *@ TODO   heap beside, which can be free()d in random order etc.
 *@ XXX - With (this) new approach, the memory backing _auto_buf and
 *@ XXX  _lofi_pool is of equal size, and thus a (global?) cache could be added
 *
 * Copyright (c) 2012 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE su_mem_bag
#define su_SOURCE
#define su_SOURCE_MEM_BAG

#include "su/code.h"

#include "su/mem.h"

#include "su/mem-bag.h"
#include "su/code-in.h"

su_EMPTY_FILE()
#ifdef su_HAVE_MEM_BAG

/* Are we a managing hull for the heap cache, for ASAN etc. integration? */
#if defined su_HAVE_DEBUG || defined su_HAVE_MEM_CANARIES_DISABLE
# define a_MEMBAG_HULL 1
#else
# define a_MEMBAG_HULL 0
#endif

/* We presume a buffer is full if less than this remain (in order to avoid
 * searching in buffers which will never be able to serve anything again) */
#define a_MEMBAG_BSZ_GAP 32u

/* .mb_bsz limits (lower must be (much) greater than a_MEMBAG_BSZ_BASE plus
 * a_MEMBAG_BSZ_GAP: CTAsserted) */
#define a_MEMBAG_BSZ_LOWER 1024u
#define a_MEMBAG_BSZ_UPPER (1024u * 1024 * 10)

CTA(a_MEMBAG_BSZ_UPPER <= S32_MAX, "Excesses datatype storage");

/* When allocating a .mb_bsz buffer, add management sizes (back) */
#define a_MEMBAG_BSZ_BASE \
   MAX(Z_ALIGN_SMALL(a_MEMBAG__SZA), Z_ALIGN_SMALL(a_MEMBAG__SZB))

#ifdef su_HAVE_MEM_BAG_AUTO
# define a_MEMBAG__SZA VSTRUCT_SIZEOF(struct su__mem_bag_auto_buf,mbab_buf)
#else
# define a_MEMBAG__SZA sizeof(up)
#endif
#ifdef su_HAVE_MEM_BAG_LOFI
# define a_MEMBAG__SZB VSTRUCT_SIZEOF(struct su__mem_bag_lofi_pool,mblp_buf)
#else
# define a_MEMBAG__SZB sizeof(up)
#endif

/* enum su_mem_bag_alloc_flags == enum su_mem_alloc_flags */
CTAV(S(u32,su_MEM_BAG_ALLOC_NONE) == S(u32,su_MEM_ALLOC_NONE));
CTAV(S(u32,su_MEM_BAG_ALLOC_CLEAR) == S(u32,su_MEM_ALLOC_CLEAR));
CTAV(S(u32,su_MEM_BAG_ALLOC_OVERFLOW_OK) == S(u32,su_MEM_ALLOC_OVERFLOW_OK));
CTAV(S(u32,su_MEM_BAG_ALLOC_NOMEM_OK) == S(u32,su_MEM_ALLOC_NOMEM_OK));
CTAV(S(u32,su_MEM_BAG_ALLOC_MUSTFAIL) == S(u32,su_MEM_ALLOC_MUSTFAIL));

#ifdef su_HAVE_MEM_BAG_AUTO
struct su__mem_bag_auto_buf{
   struct su__mem_bag_auto_buf *mbab_last;
   char *mbab_bot;      /* For _fixate(): keep startup memory lingering */
   char *mbab_relax;    /* !NIL: used by _relax_unroll(), not .mbab_bot */
   char *mbab_caster;   /* Point of casting off memory */
   char mbab_buf[VFIELD_SIZE(0)]; /* MEMBAG_HULL: void*[] */
};

struct su__mem_bag_auto_huge{
   struct su__mem_bag_auto_huge *mbah_last;
   char mbah_buf[VFIELD_SIZE(0)];   /* MEMBAG_HULL: void* to real chunk */
};
#endif /* su_HAVE_MEM_BAG_AUTO */

#ifdef su_HAVE_MEM_BAG_LOFI
struct su__mem_bag_lofi_pool{
   struct su__mem_bag_lofi_pool *mblp_last;
   char *mblp_caster;
   char mblp_buf[VFIELD_SIZE(0)];   /* su__mem_bag_lofi_chunk* */
};

struct su__mem_bag_lofi_chunk{
   /* Bit 0x1 set: .mblc_buf contains indeed a void* to user heap memory.
    * (Always so with MEMBAG_HULL) */
   struct su__mem_bag_lofi_chunk *mblc_last;
   char mblc_buf[VFIELD_SIZE(0)];
};
#endif

CTA(a_MEMBAG_BSZ_BASE + a_MEMBAG_BSZ_GAP < a_MEMBAG_BSZ_LOWER,
   "The chosen buffer size minimum is (much) too small");

/* Free .mb_lofi_top */
#ifdef su_HAVE_MEM_BAG_LOFI
su_SINLINE struct su_mem_bag *a_membag_lofi_free_top(struct su_mem_bag *self);
#endif

/* Free vp, which really is storage for (a) user heap pointer(s) */
#ifdef su_HAVE_MEM_BAG_AUTO
su_SINLINE void a_membag_free_auto_hulls(void *vp, char *maxp);
su_SINLINE void a_membag_free_auto_huge_hull(void *vp);
#endif
#ifdef su_HAVE_MEM_BAG_LOFI
su_SINLINE void a_membag_free_lofi_hulls(void *vp, char *maxp);
#endif

#ifdef su_HAVE_MEM_BAG_LOFI
su_SINLINE struct su_mem_bag *
a_membag_lofi_free_top(struct su_mem_bag *self){
   struct su__mem_bag_lofi_pool *mblpp;
   boole isheap;
   union {struct su__mem_bag_lofi_chunk *mblc; up u; void *v; void **vp;
      struct su__mem_bag_lofi_pool *mblp;} p;
   struct su__mem_bag_lofi_chunk *mblcp;
   NYD2_IN;

   mblcp = self->mb_lofi_top;
   p.mblc = mblcp->mblc_last;
   if((isheap = (p.u & 0x1)))
      p.u ^= 0x1;
   self->mb_lofi_top = p.mblc;

   if(isheap){
      p.v = &mblcp->mblc_buf[0];
      su_FREE(p.vp[0]);
   }else
      ASSERT(!a_MEMBAG_HULL);

   /* We remove free pools but the last (== the first) one */
   mblpp = self->mb_lofi_pool;
   if((mblpp->mblp_caster = R(char*,mblcp)) == mblpp->mblp_buf &&
         (p.mblp = mblpp->mblp_last) != NIL){
      self->mb_lofi_pool = p.mblp;
      su_FREE(mblpp);
   }
   NYD2_OU;
   return self;
}
#endif /* su_HAVE_MEM_BAG_LOFI */

#ifdef su_HAVE_MEM_BAG_AUTO
su_SINLINE void
a_membag_free_auto_hulls(void *vp, char *maxp){
   NYD2_IN;
   UNUSED(vp);
   UNUSED(maxp);

# if a_MEMBAG_HULL
   /* C99 */{
      union {char *c; char **cp;} p;

      for(p.c = S(char*,vp); p.c < maxp; ++p.cp)
         su_FREE(*p.cp);
   }
# endif
   NYD2_OU;
}

su_SINLINE void
a_membag_free_auto_huge_hull(void *vp){
   NYD2_IN;
   UNUSED(vp);

# if a_MEMBAG_HULL
   /* C99 */{
      union {void *v; void **vp;} p;

      p.v = vp;
      su_FREE(p.vp[0]);
   }
# endif
   NYD2_OU;
}
#endif /* su_HAVE_MEM_BAG_AUTO */

#ifdef su_HAVE_MEM_BAG_LOFI
su_SINLINE void
a_membag_free_lofi_hulls(void *vp, char *maxp){
   NYD2_IN;
   UNUSED(vp);
   UNUSED(maxp);

# if a_MEMBAG_HULL
   /* C99 */{
      union {char *c; struct su__mem_bag_lofi_chunk *mblc;} p;

      for(p.c = S(char*,vp); p.c < maxp;
            p.c += VSTRUCT_SIZEOF(struct su__mem_bag_lofi_chunk,mblc_buf) +
                  sizeof p.c){
         union {void *v; void **vp;} p2;

         p2.v = &p.mblc->mblc_buf[0];
         su_FREE(p2.vp[0]);
      }
   }
# endif
   NYD2_OU;
}
#endif /* su_HAVE_MEM_BAG_LOFI */

struct su_mem_bag *
su_mem_bag_create(struct su_mem_bag *self, uz bsz){
   NYD_IN;
   ASSERT(self);

   su_mem_set(self, 0, sizeof *self);

   if(bsz == 0)
      bsz = su_PAGE_SIZE * 2;
   else
      CLIP(bsz, a_MEMBAG_BSZ_LOWER, a_MEMBAG_BSZ_UPPER);
   bsz -= a_MEMBAG_BSZ_BASE;
   self->mb_bsz = S(u32,bsz);
   bsz -= a_MEMBAG_BSZ_GAP;
   self->mb_bsz_wo_gap = S(u32,bsz);
   NYD_OU;
   return self;
}

void
su_mem_bag_gut(struct su_mem_bag *self){
   NYD_IN;
   ASSERT(self);

   DBG( if(self->mb_top != NIL)
      su_log_write(su_LOG_DEBUG, "su_mem_bag_gut(%p): has bag stack!\n",
         self); )

   self = su_mem_bag_reset(self);

   /* _fixate()d auto-reclaimed memory will still linger now */
#ifdef su_HAVE_MEM_BAG_AUTO
   ASSERT_EXEC(self->mb_auto_full == NIL, (void)0);
   ASSERT_EXEC(self->mb_auto_huge == NIL, (void)0);
   /* C99 */{
      struct su__mem_bag_auto_buf *mbabp;

      while((mbabp = self->mb_auto_top) != NIL){
         self->mb_auto_top = mbabp->mbab_last;
         a_membag_free_auto_hulls(mbabp->mbab_buf, mbabp->mbab_caster);
         su_FREE(mbabp);
      }
   }
#endif

   /* We (may) have kept a single LOFI buffer */
#ifdef su_HAVE_MEM_BAG_LOFI
   /* C99 */{
      struct su__mem_bag_lofi_pool *mblpp;

      if((mblpp = self->mb_lofi_pool) != NIL){
         ASSERT_EXEC(mblpp->mblp_last == NIL, (void)0);
         DBG( self->mb_lofi_pool = NIL; )
         a_membag_free_lofi_hulls(mblpp->mblp_buf, mblpp->mblp_caster);
         su_FREE(mblpp);
      }
   }
#endif
   NYD_OU;
}

struct su_mem_bag *
su_mem_bag_fixate(struct su_mem_bag *self){
   struct su_mem_bag *oself;
   NYD2_IN;
   ASSERT(self);

   if((oself = self)->mb_top != NIL)
      self = self->mb_top;

#ifdef su_HAVE_MEM_BAG_AUTO
   /* C99 */{
      struct su__mem_bag_auto_buf *mbabp;

      for(mbabp = self->mb_auto_top; mbabp != NIL; mbabp = mbabp->mbab_last)
         mbabp->mbab_bot = mbabp->mbab_caster;
      for(mbabp = self->mb_auto_full; mbabp != NIL; mbabp = mbabp->mbab_last)
         mbabp->mbab_bot = mbabp->mbab_caster;
   }
#endif

   self = oself;
   NYD2_OU;
   return self;
}

struct su_mem_bag *
su_mem_bag_reset(struct su_mem_bag *self){
   NYD_IN;
   ASSERT(self);

   /* C99 */{
      struct su_mem_bag *mbp;

      while((mbp = self->mb_top) != NIL){
         self->mb_top = mbp->mb_outer;
         su_mem_bag_gut(mbp);
      }
   }

#ifdef su_HAVE_MEM_BAG_AUTO
   /* C99 */{
      struct su__mem_bag_auto_buf **mbabpp, *mbabp;

      /* Forcefully gut() an active relaxation */
      if(self->mb_auto_relax_recur > 0){
         DBG( su_log_write(su_LOG_DEBUG,
            "su_mem_bag_reset(): has relaxation!\n"); )
         self->mb_auto_relax_recur = 1;
         self = su_mem_bag_auto_relax_gut(self);
      }

      /* Simply move buffers away from .mb_auto_full */
      for(mbabpp = &self->mb_auto_full; (mbabp = *mbabpp) != NIL;){
         *mbabpp = mbabp->mbab_last;
         mbabp->mbab_last = self->mb_auto_top;
         self->mb_auto_top = mbabp;
      }

      /* Then give away all buffers which are not _fixate()d */
      mbabp = *(mbabpp = &self->mb_auto_top);
      *mbabpp = NIL;
      while(mbabp != NIL){
         struct su__mem_bag_auto_buf *tmp;

         tmp = mbabp;
         mbabp = mbabp->mbab_last;

         a_membag_free_auto_hulls(tmp->mbab_bot, tmp->mbab_caster);
         if(tmp->mbab_bot != tmp->mbab_buf){
            tmp->mbab_relax = NIL;
            tmp->mbab_caster = tmp->mbab_bot;
            tmp->mbab_last = *mbabpp;
            *mbabpp = tmp;
         }else
            su_FREE(tmp);
      }

      /* Huge allocations simply vanish */
      /* C99 */{
         struct su__mem_bag_auto_huge **mbahpp, *mbahp;

         for(mbahpp = &self->mb_auto_huge; (mbahp = *mbahpp) != NIL;){
            *mbahpp = mbahp->mbah_last;
            a_membag_free_auto_huge_hull(mbahp->mbah_buf);
            su_FREE(mbahp);
         }
      }
   }
#endif /* su_HAVE_MEM_BAG_AUTO */

#ifdef su_HAVE_MEM_BAG_LOFI
   if(self->mb_lofi_top != NIL){
      DBG( su_log_write(su_LOG_DEBUG,
         "su_mem_bag_reset(%p): still has LOFI memory!\n", self); )
      do
         self = a_membag_lofi_free_top(self);
      while(self->mb_lofi_top != NIL);
   }
#endif /* su_HAVE_MEM_BAG_LOFI */

   NYD_OU;
   return self;
}

struct su_mem_bag *
su_mem_bag_push(struct su_mem_bag *self, struct su_mem_bag *that_one){
   NYD_IN;
   ASSERT(self);
   ASSERT_NYD(that_one != NIL);
   ASSERT_NYD(that_one->mb_outer_save == NIL /* max once yet! */);

   that_one->mb_outer_save = that_one->mb_outer;
   that_one->mb_outer = self->mb_top;
   self->mb_top = that_one;
   NYD_OU;
   return self;
}

struct su_mem_bag *
su_mem_bag_pop(struct su_mem_bag *self, struct su_mem_bag *that_one){
   NYD_IN;
   ASSERT(self);
   ASSERT_NYD(that_one != NIL);

   for(;;){
      struct su_mem_bag *mbp;

      mbp = self->mb_top;
      ASSERT_EXEC(mbp != NIL /* False stack pop()ped! */, break);
      self->mb_top = mbp->mb_outer;
      mbp->mb_outer = mbp->mb_outer_save;
      mbp->mb_outer_save = NIL;

      if(mbp == that_one)
         break;
   }
   NYD_OU;
   return self;
}

#ifdef su_HAVE_MEM_BAG_AUTO
struct su_mem_bag *
su_mem_bag_auto_relax_create(struct su_mem_bag *self){
   struct su_mem_bag *oself;
   NYD2_IN;
   ASSERT(self);

   if((oself = self)->mb_top != NIL)
      self = oself->mb_top;

   if(self->mb_auto_relax_recur++ == 0){
      struct su__mem_bag_auto_buf *mbabp;

      for(mbabp = self->mb_auto_top; mbabp != NIL; mbabp = mbabp->mbab_last)
         mbabp->mbab_relax = mbabp->mbab_caster;
      for(mbabp = self->mb_auto_full; mbabp != NIL; mbabp = mbabp->mbab_last)
         mbabp->mbab_relax = mbabp->mbab_caster;
   }

   self = oself;
   NYD2_OU;
   return self;
}

struct su_mem_bag *
su_mem_bag_auto_relax_gut(struct su_mem_bag *self){
   struct su_mem_bag *oself;
   NYD2_IN;
   ASSERT(self);

   if((oself = self)->mb_top != NIL)
      self = oself->mb_top;
   ASSERT_NYD_EXEC(self->mb_auto_relax_recur > 0, self = oself);

   if(--self->mb_auto_relax_recur == 0){
      struct su__mem_bag_auto_buf *mbabp;

      self->mb_auto_relax_recur = 1;
      self = su_mem_bag_auto_relax_unroll(self);
      self->mb_auto_relax_recur = 0;

      for(mbabp = self->mb_auto_top; mbabp != NIL; mbabp = mbabp->mbab_last)
         mbabp->mbab_relax = NIL;
      for(mbabp = self->mb_auto_full; mbabp != NIL; mbabp = mbabp->mbab_last)
         mbabp->mbab_relax = NIL;
   }

   self = oself;
   NYD2_OU;
   return self;
}

struct su_mem_bag *
su_mem_bag_auto_relax_unroll(struct su_mem_bag *self){
   /* The purpose of relaxation is to reset the casters, *not* to give back
    * memory to the system.  Presumably in some kind of iteration, it would be
    * counterproductive to give the system allocator a chance to waste time */
   struct su_mem_bag *oself;
   NYD2_IN;
   ASSERT(self);

   if((oself = self)->mb_top != NIL)
      self = oself->mb_top;
   ASSERT_NYD_EXEC(self->mb_auto_relax_recur > 0, self = oself);

   if(self->mb_auto_relax_recur == 1){
      struct su__mem_bag_auto_buf **pp, *mbabp, *p;

      /* Buffers in the full list may become usable again.. */
      for(pp = &self->mb_auto_full, mbabp = *pp; (p = mbabp) != NIL;){
         mbabp = mbabp->mbab_last;

         /* ..if either they are not covered by relaxation, or if they have
          * been relaxed with "sufficient" buffer size available */
         if(p->mbab_relax == NIL ||
               p->mbab_relax <= &p->mbab_buf[self->mb_bsz_wo_gap]){
            p->mbab_last = self->mb_auto_top;
            self->mb_auto_top = p;
         }else{
            *pp = p;
            pp = &p->mbab_last;
         }
      }
      *pp = NIL;

      for(mbabp = self->mb_auto_top; mbabp != NIL; mbabp = mbabp->mbab_last){
         a_membag_free_auto_hulls(((mbabp->mbab_relax != NIL)
            ? mbabp->mbab_relax : mbabp->mbab_bot), mbabp->mbab_caster);
         mbabp->mbab_caster = (mbabp->mbab_relax != NIL) ? mbabp->mbab_relax
               : mbabp->mbab_bot;
      }
   }

   self = oself;
   NYD2_OU;
   return self;
}

void *
su_mem_bag_auto_allocate(struct su_mem_bag *self, uz size, uz no, u32 mbaf
      su_DBG_LOC_ARGS_DECL){
   void *rv;
   NYD_IN;
   ASSERT(self);

   if(self->mb_top != NIL)
      self = self->mb_top;
   if(UNLIKELY(size == 0))
      size = 1;
   if(UNLIKELY(no == 0))
      no = 1;
   mbaf &= su__MEM_BAG_ALLOC_USER_MASK;

   rv = NIL;

   /* Any attempt to allocate more is prevented (for simplicity, going for
    * UZ_MAX would require more expensive care: for nothing) */
   if(LIKELY(S(uz,S32_MAX) / no > size)){
      uz chunksz;

      size *= no;
      chunksz = a_MEMBAG_HULL ? sizeof(up) : Z_ALIGN_SMALL(size);

      /* Pool allocation possible?  Huge allocations are special */
      if(LIKELY(chunksz <= self->mb_bsz)){
         char *top, *cp;
         struct su__mem_bag_auto_buf **mbabpp, *mbabp;

         /* For all non-full pools.. */
         for(mbabpp = &self->mb_auto_top, mbabp = *mbabpp; mbabp != NIL;
               mbabpp = &mbabp->mbab_last, mbabp = mbabp->mbab_last){
            top = &mbabp->mbab_buf[self->mb_bsz];

            /* Check whether allocation can be placed.. */
            if(top - chunksz >= (cp = mbabp->mbab_caster)){
               rv = cp;
               mbabp->mbab_caster = cp = &cp[chunksz];
               /* ..and move this pool to the full list if the possibility
                * of further allocations is low */
               if(cp > (top -= a_MEMBAG_BSZ_GAP)){
                  *mbabpp = mbabp->mbab_last;
                  mbabp->mbab_last = self->mb_auto_full;
                  self->mb_auto_full = mbabp;
               }
               goto jhave_pool;
            }
         }

         /* Ran out of usable pools.  Allocate one, and make it the serving
          * head (top) if possible */
         mbabp = S(struct su__mem_bag_auto_buf*,su_ALLOCATE_LOC((self->mb_bsz +
                  a_MEMBAG_BSZ_BASE), 1, (mbaf | su_MEM_ALLOC_MARK_AUTO),
               su_DBG_LOC_ARGS_FILE, su_DBG_LOC_ARGS_LINE));
         if(mbabp == NIL)
            goto jleave;
         cp = mbabp->mbab_buf;
         rv = mbabp->mbab_bot = top = mbabp->mbab_buf;
         mbabp->mbab_relax = NIL;
         mbabp->mbab_caster = cp = &top[chunksz];
         if(LIKELY(cp <= (top += self->mb_bsz_wo_gap))){
            mbabp->mbab_last = self->mb_auto_top;
            self->mb_auto_top = mbabp;
         }else{
            mbabp->mbab_last = self->mb_auto_full;
            self->mb_auto_full = mbabp;
         }

         /* Have a pool, chunk in rv (and: cp==.mbab_caster == rv+chunksz) */
jhave_pool:;
#if a_MEMBAG_HULL
         /* C99 */{
            union {void *p; void **pp;} v;

            v.p = rv;
            rv = su_ALLOCATE_LOC(size, 1, (mbaf | su_MEM_ALLOC_MARK_AUTO),
                  su_DBG_LOC_ARGS_FILE, su_DBG_LOC_ARGS_LINE);
            if(rv != NIL)
               *v.pp = rv;
            else{
               mbabp->mbab_caster = v.p;
               goto jleave;
            }
         }
#endif
      }else{
         struct su__mem_bag_auto_huge *mbahp;

         DBG( su_log_write(su_LOG_DEBUG, "su_mem_bag_auto_allocate(): huge: "
            "%" PRIuZ " bytes from %s:%" PRIu32 "!\n",
            size  su_DBG_LOC_ARGS_USE); )

         mbahp = S(struct su__mem_bag_auto_huge*,su_ALLOCATE_LOC(
               VSTRUCT_SIZEOF(struct su__mem_bag_auto_huge,mbah_buf) + chunksz,
               1, (mbaf | su_MEM_ALLOC_MARK_AUTO_HUGE),
               su_DBG_LOC_ARGS_FILE, su_DBG_LOC_ARGS_LINE));
         if(UNLIKELY(mbahp == NIL))
            goto jleave;
#if !a_MEMBAG_HULL
         rv = mbahp->mbah_buf;
#else
         rv = su_ALLOCATE_LOC(size, 1,
               (mbaf | su_MEM_ALLOC_MARK_AUTO_HUGE),
               su_DBG_LOC_ARGS_FILE, su_DBG_LOC_ARGS_LINE);
         if(rv != NIL){
            union {void *p; void **pp;} v;

            v.p = mbahp->mbah_buf;
            *v.pp = rv;
         }else{
            su_FREE(mbahp);
            goto jleave;
         }
#endif
         mbahp->mbah_last = self->mb_auto_huge;
         self->mb_auto_huge = mbahp;
      }

      if(mbaf & su_MEM_BAG_ALLOC_CLEAR)
         su_mem_set(rv, 0, size);
   }else
      su_state_err(su_STATE_ERR_OVERFLOW, mbaf,
         _("SU memory bag: auto allocation request"));
jleave:
   NYD_OU;
   return rv;
}
#endif /* su_HAVE_MEM_BAG_AUTO */

#ifdef su_HAVE_MEM_BAG_LOFI
void *
su_mem_bag_lofi_snap_create(struct su_mem_bag *self){
   void *rv;
   NYD2_IN;
   ASSERT(self);

   if(self->mb_top != NIL)
      self = self->mb_top;

   /* XXX Before SU this allocated one and returned that, now we do
    * XXX have no real debug support no more... */
   rv = self->mb_lofi_top;
   NYD2_OU;
   return rv;
}

struct su_mem_bag *
su_mem_bag_lofi_snap_unroll(struct su_mem_bag *self, void *cookie){
   struct su__mem_bag_lofi_chunk *mblcp;
   struct su_mem_bag *oself;
   NYD2_IN;
   ASSERT(self);

   if((oself = self)->mb_top != NIL)
      self = oself->mb_top;

   while((mblcp = self->mb_lofi_top) != cookie){
#ifdef su_HAVE_DEBUG
      if(mblcp == NIL){
         su_log_write(su_LOG_DEBUG, "su_mem_bag_lofi_snap_unroll(%p): no such "
            "snap exists: non-debug crash!\n", oself);
         break;
      }
#endif
      self = a_membag_lofi_free_top(self);
   }

   self = oself;
   NYD2_OU;
   return self;
}

void *
su_mem_bag_lofi_allocate(struct su_mem_bag *self, uz size, uz no, u32 mbaf
      su_DBG_LOC_ARGS_DECL){
   void *rv;
   NYD_IN;
   ASSERT(self);

   if(self->mb_top != NIL)
      self = self->mb_top;
   if(UNLIKELY(size == 0))
      size = 1;
   if(UNLIKELY(no == 0))
      no = 1;
   mbaf &= su__MEM_BAG_ALLOC_USER_MASK;

   rv = NIL;

   /* Any attempt to allocate more is prevented (for simplicity, going for
    * UZ_MAX would require more expensive care: for nothing) */
   if(LIKELY(S(uz,S32_MAX) / no > size)){
      struct su__mem_bag_lofi_chunk *mblcp;
      char *top, *cp;
      struct su__mem_bag_lofi_pool *mblpp;
      boole isheap;
      uz chunksz;

      size *= no;
      /* C99 */{
         uz realsz;

         realsz = Z_ALIGN_SMALL(size);
         DBG( if(realsz > self->mb_bsz)
            su_log_write(su_LOG_DEBUG, "su_mem_bag_lofi_allocate(): huge: "
                  "%" PRIuZ " bytes from %s:%" PRIu32 "!\n",
               size  su_DBG_LOC_ARGS_USE); )
         isheap = (a_MEMBAG_HULL || realsz > self->mb_bsz);
         chunksz = Z_ALIGN_SMALL(VSTRUCT_SIZEOF(struct su__mem_bag_lofi_chunk,
                  mblc_buf)) + (isheap ? sizeof(up) : realsz);
      }

      if((mblpp = self->mb_lofi_pool) != NIL){
         top = &mblpp->mblp_buf[self->mb_bsz];

         /* Check whether allocation can be placed.. */
         if(LIKELY(top - chunksz >= (cp = mblpp->mblp_caster))){
            rv = cp;
            mblpp->mblp_caster = cp = &cp[chunksz];
            goto jhave_pool;
         }
      }

      /* Need a pool */
      mblpp = S(struct su__mem_bag_lofi_pool*,su_ALLOCATE_LOC((self->mb_bsz +
            a_MEMBAG_BSZ_BASE), 1, (mbaf | su_MEM_ALLOC_MARK_LOFI),
            su_DBG_LOC_ARGS_FILE, su_DBG_LOC_ARGS_LINE));
      if(mblpp == NIL)
         goto jleave;
      rv = cp = mblpp->mblp_buf;
      mblpp->mblp_caster = cp = &cp[chunksz];
      mblpp->mblp_last = self->mb_lofi_pool;
      self->mb_lofi_pool = mblpp;

      /* Have a pool, chunk in rv (and: cp==.mbab_caster == rv+chunksz) */
jhave_pool:
      mblcp = S(struct su__mem_bag_lofi_chunk*,rv);
      mblcp->mblc_last = R(struct su__mem_bag_lofi_chunk*,
            R(up,self->mb_lofi_top) | isheap);
      if(!isheap)
         rv = mblcp->mblc_buf;
      else{
         union {void *p; void **pp;} v;

         v.p = rv;
         rv = su_ALLOCATE_LOC(size, 1, (mbaf | su_MEM_ALLOC_MARK_LOFI),
               su_DBG_LOC_ARGS_FILE, su_DBG_LOC_ARGS_LINE);
         if(rv != NIL){
            v.p = mblcp->mblc_buf;
            *v.pp = S(void*,rv);
         }else{
            mblpp->mblp_caster = v.p;
            goto jleave;
         }
      }
      self->mb_lofi_top = mblcp;

      if(mbaf & su_MEM_BAG_ALLOC_CLEAR)
         su_mem_set(rv, 0, size);
   }else
      su_state_err(su_STATE_ERR_OVERFLOW, mbaf,
         _("SU memory bag: lofi allocation request"));
jleave:
   NYD_OU;
   return rv;
}

struct su_mem_bag *
su_mem_bag_lofi_free(struct su_mem_bag *self, void *ovp  su_DBG_LOC_ARGS_DECL){
   NYD_IN;
   UNUSED(ovp);
   ASSERT(self);

   if(self->mb_top != NIL)
      self = self->mb_top;

#ifdef su_HAVE_DEBUG
   /* C99 */{
      struct su__mem_bag_lofi_chunk *mblcp;

      if(ovp == NIL){
         su_log_write(su_LOG_DEBUG,
            "su_mem_bag_lofi_free(): NIL from %s:%" PRIu32 "\n"
            su_DBG_LOC_ARGS_USE);
         goto NYD_OU_LABEL;
      }

      if(((mblcp = self->mb_lofi_top) == NIL)){
         su_log_write(su_LOG_DEBUG, "su_mem_bag_lofi_free(): "
            "no LOFI memory exists at %s:%" PRIu32 "!\n"  su_DBG_LOC_ARGS_USE);
         goto NYD_OU_LABEL;
      }

      /* From heap? */
      if(R(up,mblcp->mblc_last) & 0x1){
         union {void *p; void **pp;} v;

         v.p = mblcp->mblc_buf;
         if(*v.pp != ovp)
            goto jeinval;
      }else if(ovp != mblcp->mblc_buf){
jeinval:
         su_log_write(su_LOG_DEBUG, "su_mem_bag_lofi_free(): "
            "invalid pointer from %s:%" PRIu32 "!\n"  su_DBG_LOC_ARGS_USE);
         goto NYD_OU_LABEL;
      }
   }
#endif /* su_HAVE_DEBUG */

   self = a_membag_lofi_free_top(self);

   NYD_OU;
   return self;
}
#endif /* su_HAVE_MEM_BAG_LOFI */

#endif /* su_HAVE_MEM_BAG */
#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/su/mem-tools.c000066400000000000000000000051721352610246600163660ustar00rootroot00000000000000/*@ Implementation of mem.h: utility funs.
 *@ The implementations are in ./x-mem-tools.h:
 *@ - a_memt_FUN().
 *@ - Data is asserted, length cannot be 0.
 *
 * Copyright (c) 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE su_mem_tools
#define su_SOURCE
#define su_SOURCE_MEM_TOOLS

#include "su/code.h"

#include "su/mem.h"
#include "su/x-mem-tools.h" /* $(SU_SRCDIR) */
#include "su/code-in.h"

void * (* volatile su_mem_set_volatile)(void*, int, uz) = &su_mem_set;

void *
su_mem_find(void const *vp, s32 what, uz len){
   void *rv;
   NYD_IN;
   ASSERT_NYD_EXEC(len == 0 || vp != NIL, rv = NIL);

   rv = LIKELY(len != 0) ? a_memt_find(vp, what, len) : NIL;
   NYD_OU;
   return rv;
}

void *
su_mem_rfind(void const *vp, s32 what, uz len){
   void *rv;
   NYD_IN;
   ASSERT_NYD_EXEC(len == 0 || vp != NIL, rv = NIL);

   rv = LIKELY(len != 0) ? a_memt_rfind(vp, what, len) : NIL;
   NYD_OU;
   return rv;
}

sz
su_mem_cmp(void const *vpa, void const *vpb, uz len){
   sz rv;
   NYD_IN;
   ASSERT_NYD_EXEC(len == 0 || vpa != NIL, rv = (vpb == NIL) ? 0 : -1);
   ASSERT_NYD_EXEC(len == 0 || vpb != NIL, rv = 1);

   rv = LIKELY(len != 0) ? a_memt_cmp(vpa, vpb, len) : 0;
   NYD_OU;
   return rv;
}

void *
su_mem_copy(void *vp, void const *src, uz len){
   NYD_IN;
   ASSERT_NYD(len == 0 || vp != NIL);
   ASSERT_NYD(len == 0 || src != NIL);

   if(LIKELY(len > 0))
      a_memt_copy(vp, src, len);
   NYD_OU;
   return vp;
}

void *
su_mem_move(void *vp, void const *src, uz len){
   NYD_IN;
   ASSERT_NYD(len == 0 || vp != NIL);
   ASSERT_NYD(len == 0 || src != NIL);

   if(LIKELY(len > 0))
      a_memt_move(vp, src, len);
   NYD_OU;
   return vp;
}

void *
su_mem_set(void *vp, s32 what, uz len){
   NYD_IN;
   ASSERT_NYD(len == 0 || vp != NIL);

   if(LIKELY(len > 0))
      a_memt_set(vp, what, len);
   NYD_OU;
   return vp;
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/su/prime.c000066400000000000000000000104241352610246600155620ustar00rootroot00000000000000/*@ Implementation of prime.h.
 *
 * Copyright (c) 2001 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE su_prime
#define su_SOURCE
#define su_SOURCE_PRIME

#include "su/code.h"

#include "su/prime.h"
#include "su/code-in.h"

/* Collected & merged from 'GLIB 1's 'gprimes.c' and 'GNU STL's 'hashtable'
 * (around Y2K+1 or so) */
static u32 const a_prime_lookup[] = {
   0x00000002, 0x00000005, 0x0000000B,
   0x00000017, 0x0000002F, 0x00000061, 0x0000009D,
   0x0000011B, 0x0000022D, 0x00000337, 0x000004D5, 0x00000741, 0x00000AD9,
   0x00001051, 0x00001867, 0x0000249B, 0x000036E9, 0x00005261, 0x00007B8B,
          0x0000B947,
   0x000115E7, 0x0001A0E1, 0x00027149, 0x0003A9E5, 0x00057EE3, 0x00083E39,
          0x000C5D67,
   0x00128C09, 0x001BD1FF, 0x0029BB13, 0x003E988B, 0x005DE4C1, 0x008CD721,
          0x00D342AB,
   0x01800013, 0x03000005, 0x06000017, 0x0C000013,
   0x18000005
};
#define a_PRIME_FIRST &a_prime_lookup[0]
#define a_PRIME_MIDDLE &a_prime_lookup[NELEM(a_prime_lookup) / 2]
#define a_PRIME_LAST &a_prime_lookup[NELEM(a_prime_lookup) - 1]

static u64 const a_prime_max = su_U64_C(0xFFFFFFFFFFFFFFFD);

/* */
static boole a_prime_is_pseudo(u64 no);
static boole a_prime_is_real(u64 no);

/* */
static u64 a_prime_calc(u64 no, sz add, boole pseudo_ok);

static boole
a_prime_is_pseudo(u64 no){
   boole rv;
   NYD_IN;

   switch(no){
   case 2:
   case 3:
   case 5:
   case 7:
      rv = TRU1; break;
   case 0:
   case 1:
   case 4:
   case 6:
      rv = FAL0; break;
   default:
      rv = ((no & 1) && (no % 3) && (no % 5) && (no % 7));
      break;
   }
   NYD_OU;
   return rv;
}

static boole
a_prime_is_real(u64 no){ /* TODO brute force yet */
   /* no is pseudo! */
   union {uz x; u64 x64; boole rv;} u;
   NYD_IN;

   if(no < 2)
      goto jfal;
   if(no != 2)
      for(u.x64 = 3; u.x64 < no; u.x64 += 2)
         if(no % u.x64 == 0)
            goto jfal;

   u.rv = TRU1;
jleave:
   NYD_OU;
   return u.rv;
jfal:
   u.rv = FAL0;
   goto jleave;
}

static u64
a_prime_calc(u64 no, sz add, boole pseudo_ok){
   NYD_IN;

   /* Primes are all odd (except 2 but that is NEVER evaluated in here) */
   if(!(no & 1)){
      no += add;
      add <<= 1;
      goto jdiver;
   }

   add <<= 1;
   for(;;){
      no += add;
jdiver:
      if(!a_prime_is_pseudo(no))
         continue;
      if(pseudo_ok || a_prime_is_real(no))
         break;
   }
   NYD_OU;
   return no;
}

boole
su_prime_is_prime(u64 no, boole allowpseudo){
   boole rv;
   NYD_IN;

   rv = a_prime_is_pseudo(no);
   if(rv && !allowpseudo)
      rv = a_prime_is_real(no);
   NYD_OU;
   return rv;
}

u64
su_prime_get_former(u64 no, boole allowpseudo){
   NYD_IN;

   if(no <= 2 + 1)
      no = 2;
   else if(no > a_prime_max)
      no = a_prime_max;
   else
      no = a_prime_calc(no, -1, allowpseudo);
   NYD_OU;
   return no;
}

u64
su_prime_get_next(u64 no, boole allowpseudo){
   NYD_IN;

   if(no < 2)
      no = 2;
   else if(no >= a_prime_max - 2)
      no = a_prime_max;
   else
      no = a_prime_calc(no, +1, allowpseudo);
   NYD_OU;
   return no;
}

u32
su_prime_lookup_former(u32 no){
   u32 const *cursor;
   NYD_IN;

   cursor = ((no < *a_PRIME_MIDDLE) ? a_PRIME_MIDDLE - 1 : a_PRIME_LAST);
   while(*cursor >= no && --cursor > a_PRIME_FIRST)
      ;
   NYD_OU;
   return *cursor;
}

u32
su_prime_lookup_next(u32 no){
   u32 const *cursor;
   NYD_IN;

   cursor = ((no > *a_PRIME_MIDDLE) ? a_PRIME_MIDDLE + 1 : a_PRIME_FIRST);
   while(*cursor <= no && ++cursor < a_PRIME_LAST)
      ;
   NYD_OU;
   return *cursor;
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/su/sort.c000066400000000000000000000034331352610246600154370ustar00rootroot00000000000000/*@ Implementation of sort.h.
 *
 * Copyright (c) 2001 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE su_sort
#define su_SOURCE
#define su_SOURCE_SORT

#include "su/code.h"

#include "su/sort.h"
#include "su/code-in.h"

void
su_sort_shell_vpp(void const **arr, uz entries, su_compare_fun cmp_or_nil){
   void const **vpp, *vpa, *vpb;
   sz j, tmp;
   uz gap, i;
   NYD_IN;
   ASSERT_NYD(entries == 0 || arr != NIL);

   for(gap = entries; (gap >>= 1) != 0;){
      for(i = gap; i < entries; ++i){
         for(j = S(sz,i - gap); j >= 0; j -= gap){
            vpp = &arr[j];
            vpb = vpp[0];
            vpa = vpp[gap];
            tmp = S(sz,P2UZ(vpa) - P2UZ(vpb));

            if(tmp == 0)
               break;
            if(cmp_or_nil != NIL && vpa != NIL && vpb != NIL)
               tmp = (*cmp_or_nil)(vpa, vpb);
            if(tmp >= 0)
               break;

            vpp[0] = vpa;
            vpp[gap] = vpb;
         }
      }
   }

   NYD_OU;
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/su/utf.c000066400000000000000000000120611352610246600152430ustar00rootroot00000000000000/*@ Implementation of utf.h.
 *
 * Copyright (c) 2015 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */
#undef su_FILE
#define su_FILE su_utf
#define su_SOURCE
#define su_SOURCE_UTF

#include "su/code.h"

#include "su/utf.h"
#include "su/code-in.h"

char const su_utf8_replacer[sizeof su_UTF8_REPLACER] = su_UTF8_REPLACER;

u32
su_utf8_to_32(char const **bdat, uz *blen){
   u32 c, x, x1;
   char const *cp, *cpx;
   uz l, lx;
   NYD_IN;

   lx = l = *blen - 1;
   x = S(u8,*(cp = *bdat));
   cpx = ++cp;

   if(LIKELY(x <= 0x7Fu))
      c = x;
   /* 0xF8, but Unicode guarantees maximum of 0x10FFFFu -> F4 8F BF BF.
    * Unicode 9.0, 3.9, UTF-8, Table 3-7. Well-Formed UTF-8 Byte Sequences */
   else if(LIKELY(x > 0xC0u && x <= 0xF4u)){
      if(LIKELY(x < 0xE0u)){
         if(UNLIKELY(l < 1))
            goto jenobuf;
         --l;

         c = (x &= 0x1Fu);
      }else if(LIKELY(x < 0xF0u)){
         if(UNLIKELY(l < 2))
            goto jenobuf;
         l -= 2;

         x1 = x;
         c = (x &= 0x0Fu);

         /* Second byte constraints */
         x = S(u8,*cp++);
         switch(x1){
         case 0xE0u:
            if(UNLIKELY(x < 0xA0u || x > 0xBFu))
               goto jerr;
            break;
         case 0xEDu:
            if(UNLIKELY(x < 0x80u || x > 0x9Fu))
               goto jerr;
            break;
         default:
            if(UNLIKELY((x & 0xC0u) != 0x80u))
               goto jerr;
            break;
         }
         c <<= 6;
         c |= (x &= 0x3Fu);
      }else{
         if(UNLIKELY(l < 3))
            goto jenobuf;
         l -= 3;

         x1 = x;
         c = (x &= 0x07u);

         /* Third byte constraints */
         x = S(u8,*cp++);
         switch(x1){
         case 0xF0u:
            if(UNLIKELY(x < 0x90u || x > 0xBFu))
               goto jerr;
            break;
         case 0xF4u:
            if(UNLIKELY((x & 0xF0u) != 0x80u)) /* 80..8F */
               goto jerr;
            break;
         default:
            if(UNLIKELY((x & 0xC0u) != 0x80u))
               goto jerr;
            break;
         }
         c <<= 6;
         c |= (x &= 0x3Fu);

         x = S(u8,*cp++);
         if(UNLIKELY((x & 0xC0u) != 0x80u))
            goto jerr;
         c <<= 6;
         c |= (x &= 0x3Fu);
      }

      x = S(u8,*cp++);
      if(UNLIKELY((x & 0xC0u) != 0x80u))
         goto jerr;
      c <<= 6;
      c |= x & 0x3Fu;
   }else
      goto jerr;

   cpx = cp;
   lx = l;
jleave:
   *bdat = cpx;
   *blen = lx;
   NYD_OU;
   return c;
jenobuf:
jerr:
   c = U32_MAX;
   goto jleave;
}

uz
su_utf32_to_8(u32 c, char *bp){
   struct{
      u32 lower_bound;
      u32 upper_bound;
      u8 enc_leader;
      u8 enc_lval;
      u8 dec_leader_mask;
      u8 dec_leader_val_mask;
      u8 dec_bytes_togo;
      u8 cat_index;
      u8 d__ummy[2];
   } const a_cat[] = {
      {0x00000000, 0x00000000, 0x00, 0, 0x00,      0x00,   0, 0, {0,}},
      {0x00000000, 0x0000007F, 0x00, 1, 0x80,      0x7F, 1-1, 1, {0,}},
      {0x00000080, 0x000007FF, 0xC0, 2, 0xE0, 0xFF-0xE0, 2-1, 2, {0,}},
      /* We assume surrogates are U+D800 - U+DFFF, a_cat index 3 */
      /* xxx _from_utf32() simply assumes magic code points for surrogates!
       * xxx (However, should we ever get yet another surrogate range we
       * xxx need to deal with that all over the place anyway? */
      {0x00000800, 0x0000FFFF, 0xE0, 3, 0xF0, 0xFF-0xF0, 3-1, 3, {0,}},
      {0x00010000, 0x0010FFFF, 0xF0, 4, 0xF8, 0xFF-0xF8, 4-1, 4, {0,}},
   }, *catp;
   NYD_IN;

   catp = &a_cat[0];
   if(LIKELY(c <= a_cat[0].upper_bound)) {catp += 0; goto j0;}
   if(LIKELY(c <= a_cat[1].upper_bound)) {catp += 1; goto j1;}
   if(LIKELY(c <= a_cat[2].upper_bound)) {catp += 2; goto j2;}
   if(LIKELY(c <= a_cat[3].upper_bound)){
      /* Surrogates may not be converted (Compatibility rule C10) */
      if(UNLIKELY(c >= 0xD800u && c <= 0xDFFFu))
         goto jerr;
      catp += 3;
      goto j3;
   }
   if(LIKELY(c <= a_cat[4].upper_bound)) {catp += 4; goto j4;}
jerr:
   c = su_UTF32_REPLACER;
   catp += 3;
   goto j3;
j4:
   bp[3] = S(char,0x80u) | S(char,c & 0x3Fu); c >>= 6;
j3:
   bp[2] = S(char,0x80u) | S(char,c & 0x3Fu); c >>= 6;
j2:
   bp[1] = S(char,0x80u) | S(char,c & 0x3Fu); c >>= 6;
j1:
   bp[0] = S(char,catp->enc_leader) | S(char,c);
j0:
   bp[catp->enc_lval] = '\0';
   NYD_OU;
   return catp->enc_lval;
}

#include "su/code-ou.h"
/* s-it-mode */
s-nail-14.9.15/src/su/x-assoc-map.h000066400000000000000000000561761352610246600166210ustar00rootroot00000000000000/*@ Shared implementation of associative map-alike containers.
 *@ Include once, define a_TYPE correspondingly, include again.
 *@ XXX "Unroll" more.
 *
 * Copyright (c) 2001 - 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */

#ifndef a_ASSOC_MAP_H
# define a_ASSOC_MAP_H

# undef a_TYPE_CSDICT
# undef a_TYPE
# undef a_TYPE_IS_DICT
# undef a_TYPE_NAME
# undef a_T
# undef a_T_F
# undef a_T_PRINAME
# undef a_T_PUBNAME
# undef a_T_PRISYM
# undef a_T_PUBSYM
# undef a_FUN
# undef a_TK
# undef a_N
# undef a_N_F
# undef a_N_ALLOC
# undef a_N_FREE
# undef a_V
# undef a_V_F
# undef a_V_PRINAME
# undef a_V_PUBNAME
# undef a_V_PRISYM
# undef a_V_PUBSYM
# undef a_LA
# undef a_LA_F

# define a_TYPE_CSDICT 1
#else
# undef a_ASSOC_MAP_H

# if a_TYPE == a_TYPE_CSDICT
#  define a_TYPE a_TYPE_CSDICT
#  define a_TYPE_IS_DICT 1
#  define a_TYPE_NAME "su_cs_dict"
#  define a_T su_cs_dict
#  define a_T_F(X) su_CONCAT(csd_, X)
#  define a_T_PRINAME(X) su_CONCAT(su__CS_DICT_, X)
#  define a_T_PUBNAME(X) su_CONCAT(su_CS_DICT_, X)
#  define a_T_PRISYM(X) su_CONCAT(su__cs_dict_, X)
#  define a_T_PUBSYM(X) su_CONCAT(su_cs_dict_, X)
#  define a_FUN(X) su_CONCAT(a_csdict_, X)
#  define a_TK char
#  define a_N su__cs_dict_node
#  define a_N_F(X) su_CONCAT(csdn_, X)
#  define a_N_ALLOC(SELF,KLEN,FLAGS) \
   S(struct a_N*,su_ALLOCATE((VSTRUCT_SIZEOF(struct a_N, a_N_F(key)) +\
            (KLEN) +1), 1, ((FLAGS) & su_STATE_ERR_MASK)))
#  define a_N_FREE(SELF,NP) su_FREE(NP)
#  define a_V su_cs_dict_view
#  define a_V_F(X) su_CONCAT(csdv_, X)
#  define a_V_PRINAME(X) su_CONCAT(su__CS_DICT_VIEW_, X)
#  define a_V_PUBNAME(X) su_CONCAT(su_CS_DICT_VIEW_, X)
#  define a_V_PRISYM(X) su_CONCAT(su__cs_dict_view_, X)
#  define a_V_PUBSYM(X) su_CONCAT(su_cs_dict_view_, X)
#  define a_LA a_csdict_lookarg
#  define a_LA_F(X) su_CONCAT(csdla_, X)
# else /* a_TYPE == a_TYPE_CSDICT */
#  error Unknown a_TYPE
# endif

struct a_LA{
   struct a_N **la_slot;
   struct a_N *la_last; /* Only with AUTO_RESORT */
   u32 la_slotidx;
   u32 la_klen; /* Only if a_TYPE_IS_DICT */
   uz la_khash;
};

/* force: ignore FROZEN state */
SINLINE s32 a_FUN(check_resize)(struct a_T *self, boole force, u32 xcount);
static s32 a_FUN(resize)(struct a_T *self, u32 xcount);

/* */
static s32 a_FUN(assign)(struct a_T *self, struct a_T const *t, boole flags);

/* */
static s32 a_FUN(node_new)(struct a_T *self, struct a_N **res,
      struct a_LA *lap, a_TK const *key, void *value);

/* */
static s32 a_FUN(replace)(struct a_T *self, struct a_N *np, void *value);
static struct a_T *a_FUN(remove)(struct a_T *self, struct a_N *np,
      struct a_LA *lap);

SINLINE s32
a_FUN(check_resize)(struct a_T *self, boole force, u32 xcount){
   /* Simple approach: shift a 64-bit value, do not care for overflow */
   s32 rv;
   u64 s;
   NYD2_IN;

   rv = 0;

   if(force || !(self->a_T_F(flags) & a_T_PUBNAME(FROZEN))){
      s = self->a_T_F(size);

      if(xcount >= (s << self->a_T_F(tshift)) ||
            (xcount < (s >>= 1) &&
             (self->a_T_F(flags) & a_T_PUBNAME(AUTO_SHRINK))))
         rv = a_FUN(resize)(self, xcount);
   }else
      ASSERT(xcount == 0 || self->a_T_F(size) > 0);
   NYD2_OU;
   return rv;
}

static s32
a_FUN(resize)(struct a_T *self, u32 xcount){
   s32 rv;
   struct a_N **narr, **arr;
   boole pow2;
   u32 size, nsize;
   NYD_IN;

   size = self->a_T_F(size);
   pow2 = ((self->a_T_F(flags) & a_T_PUBNAME(POW2_SPACED)) != 0);

   /* Calculate new size */
   /* C99 */{
      u32 onsize;
      boole grow;

      /* >= to catch initial 0 size */
      grow = ((xcount >> self->a_T_F(tshift)) >= size);
      nsize = size;

      for(;;){
         onsize = nsize;

         if(pow2){
            ASSERT(nsize == 0 || nsize == 1 || IS_POW2(nsize));
            if(grow){
               if(nsize == 0)
                  nsize = 1u << 1;
               else if(UCMP(32, nsize, <, S32_MIN))
                  nsize <<= 1;
            }else if(nsize > (1u << 1))
               nsize >>= 1;
         }else
            nsize = grow ? su_prime_lookup_next(nsize)
                  : su_prime_lookup_former(nsize);

         /* Refuse to excess storage bounds, but do not fail for this: simply
          * keep on going and place more nodes in the slots.
          * Because of the numbers we are talking about this is a theoretical
          * issue (at the time of this writing). */
         if(nsize == onsize)
            break;
         if(grow){
            /* (Again, shift 64-bit to avoid overflow) */
            if((S(u64,nsize) << self->a_T_F(tshift)) >= xcount)
               break;
         }else if((nsize >> 1) <= xcount)
            break;
      }

      if(size == nsize)
         goto jleave0;
   }

   /* Try to allocate new array, give up on failure */
   narr = su_TCALLOCF(struct a_N*, nsize,
         (self->a_T_F(flags) & su_STATE_ERR_MASK));
   if(UNLIKELY(narr == NIL)){
      rv = su_err_no();
      goto jleave;
   }

   /* We will succeed: move pointers over */
   self->a_T_F(size) = nsize;

   arr = self->a_T_F(array);
   self->a_T_F(array) = narr;

   if((xcount = self->a_T_F(count)) > 0){
      struct a_N *np, **npos, *xnp;

      do for(np = arr[--size]; np != NIL; --xcount){
         uz i;

         i = np->a_N_F(khash);
         i= pow2 ? i & (nsize - 1) : i % nsize;
         npos = &narr[i];
         xnp = np;
         np = np->a_N_F(next);
         xnp->a_N_F(next) = *npos;
         *npos = xnp;
      }while(xcount > 0 && size > 0);
   }

   if(arr != NIL)
      su_FREE(arr);

jleave0:
   rv = 0;
jleave:
   NYD_OU;
   return rv;
}

static s32
a_FUN(assign)(struct a_T *self, struct a_T const *t, boole flags){
   s32 rv;
   NYD_IN;
   ASSERT(self);

   /* Clear old elements first if there are any, and if necessary */
   rv = 0;
jerr:
   if(self->a_T_F(count) > 0){
      if(self->a_T_F(flags) & a_T_PUBNAME(OWNS))
         self = a_T_PUBSYM(clear_elems)(self);
      self->a_T_F(count) = 0;
   }

   ASSERT_NYD_EXEC(t != NIL, rv = su_ERR_FAULT);

   if(flags){
      self->a_T_F(flags) = t->a_T_F(flags);
      self->a_T_F(tbox) = t->a_T_F(tbox);
   }

   if(rv != 0)
      goto jleave;

   if(t->a_T_F(count) == 0)
      goto jleave;

   /* We actually ignore a resize failure if we do have some backing store to
    * put elements into! */
   rv = a_FUN(check_resize)(self, TRU1, t->a_T_F(count));
   if(rv != 0 && self->a_T_F(array) == NIL)
      goto jleave;

   /* C99 */{
      struct a_LA la;
      boole pow2;
      u32 size, tsize, tcnt;
      struct a_N **arr, **tarr, *np, *tnp;

      arr = self->a_T_F(array);
      tarr = t->a_T_F(array);
      size = self->a_T_F(size);
      tsize = t->a_T_F(size);
      tcnt = t->a_T_F(count);
      pow2 = ((self->a_T_F(flags) & a_T_PUBNAME(POW2_SPACED)) != 0);

      while(tcnt != 0){
         for(tnp = tarr[--tsize]; tnp != NIL; --tcnt, tnp = tnp->a_N_F(next)){
            uz i;

            la.la_khash = i = tnp->a_N_F(khash);
            la.la_slotidx = S(u32,i = pow2 ? i & (size - 1) : i % size);
            la.la_slot = &arr[i];
# if a_TYPE_IS_DICT
            la.la_klen = tnp->a_N_F(klen);
# endif
            rv = a_FUN(node_new)(self, &np, &la, tnp->a_N_F(key),
                  tnp->a_N_F(data));
            if(UNLIKELY(rv != 0))
               goto jerr;
         }
      }
   }

   ASSERT(rv == 0);
jleave:
   NYD_OU;
   return rv;
}

static s32
a_FUN(node_new)(struct a_T *self, struct a_N **res, struct a_LA *lap,
      a_TK const *key, void *value){
   s32 rv;
   u16 flags;
   void *xvalue;
   struct a_N *np;
   NYD_IN;

   np = NIL;
   xvalue = NIL;
   flags = self->a_T_F(flags);

   if(flags & a_T_PUBNAME(OWNS)){
      if(value != NIL){
         xvalue = value = (*self->a_T_F(tbox)->tb_clone)(value,
               (flags & su_STATE_ERR_MASK));
         if(UNLIKELY(xvalue == NIL) && !(flags & a_T_PUBNAME(NILISVALO))){
            rv = su_err_no();
            goto jleave;
         }
      }else
         ASSERT(flags & a_T_PUBNAME(NILISVALO));
   }

   /* ..so create a new node */
   np = a_N_ALLOC(self, lap->la_klen, flags);
   if(UNLIKELY(np == NIL)){
      if(xvalue != NIL)
         (*self->a_T_F(tbox)->tb_delete)(xvalue);
      rv = su_ERR_NOMEM; /* (Cannot be overflow error) */
      goto jleave;
   }
   ++self->a_T_F(count);

   np->a_N_F(next) = *lap->la_slot;
   *lap->la_slot = np;
   np->a_N_F(data) = value;
   np->a_N_F(khash) = lap->la_khash;
   np->a_N_F(klen) = lap->la_klen;

# if a_TYPE == a_TYPE_CSDICT
   su_mem_copy(np->a_N_F(key), key, lap->la_klen +1);
   if(flags & a_T_PUBNAME(CASE)){
      a_TK *nckey;

      for(nckey = np->a_N_F(key); *nckey != '\0'; ++nckey)
         *nckey = su_cs_to_lower(*nckey);
   }
# else
#  error
# endif

   rv = 0;
jleave:
   *res = np;
   NYD_OU;
   return rv;
}

static s32
a_FUN(replace)(struct a_T *self, struct a_N *np, void *value){
   u16 flags;
   s32 rv;
   NYD_IN;

   rv = 0;
   flags = self->a_T_F(flags);

   if(flags & a_T_PUBNAME(OWNS)){
      void *ndat;

      ndat = np->a_N_F(data);

      if(LIKELY(value != NIL)){
         if((flags & a_T_PUBNAME(NILISVALO)) && ndat != NIL){
            value = (*self->a_T_F(tbox)->tb_assign)(ndat,
                  value, (flags & su_STATE_ERR_MASK));
            if(LIKELY(value != NIL))
               ndat = NIL;
            else
               rv = su_err_no();
         }else{
            value = (*self->a_T_F(tbox)->tb_clone)(value,
                  (flags & su_STATE_ERR_MASK));
            if(UNLIKELY(value == NIL)){
               rv = su_err_no();
               ndat = NIL;
            }
         }
      }else
         ASSERT(flags & a_T_PUBNAME(NILISVALO));

      if(ndat != NIL)
         (*self->a_T_F(tbox)->tb_delete)(ndat);
   }

   np->a_N_F(data) = value;
   NYD_OU;
   return rv;
}

static struct a_T *
a_FUN(remove)(struct a_T *self, struct a_N *np, struct a_LA *lap){
   NYD_IN;

   --self->a_T_F(count);

   if(np->a_N_F(data) != NIL && (self->a_T_F(flags) & a_T_PUBNAME(OWNS)))
      (*self->a_T_F(tbox)->tb_delete)(np->a_N_F(data));
   if(lap->la_last != NIL)
      lap->la_last->a_N_F(next) = np->a_N_F(next);
   else
      *lap->la_slot = np->a_N_F(next);
   a_N_FREE(self, np);
   NYD_OU;
   return self;
}

struct a_N *
a_T_PRISYM(lookup)(struct a_T const *self, a_TK const *key,
      void *lookarg_or_nil){
# if !a_TYPE_IS_DICT
   boole const key_is = (key != NIL);
# else
   u32 klen;
# endif
   u32 cnt, slotidx;
   uz khash;
   struct a_N *rv, **arr, *last;
   NYD_IN;
   ASSERT(self);

   rv = NIL;

# if a_TYPE == a_TYPE_CSDICT
   khash = su_cs_len(key);
   klen = S(u32,khash);
   if(khash != klen)
      goto jleave;
   khash = ((self->a_T_F(flags) & a_T_PUBNAME(CASE)) ? su_cs_hash_case_cbuf
         : su_cs_hash_cbuf)(key, klen);
# else
#  error
# endif

   if(LIKELY((slotidx = self->a_T_F(size)) > 0))
      slotidx = (self->a_T_F(flags) & a_T_PUBNAME(POW2_SPACED))
            ? khash & (slotidx - 1) : khash % slotidx;
   arr = self->a_T_F(array) + slotidx;

   if(lookarg_or_nil != NIL){
      struct a_LA *lap;

      lap = S(struct a_LA*,lookarg_or_nil);
      lap->la_slot = arr;
      lap->la_last = NIL;
      lap->la_slotidx = slotidx;
# if a_TYPE_IS_DICT
      lap->la_klen = klen;
# endif
      lap->la_khash = khash;
   }

   if(UNLIKELY((cnt = self->a_T_F(count)) == 0))
      goto jleave;

   for(last = rv, rv = *arr; rv != NIL; last = rv, rv = rv->a_N_F(next)){
      if(khash != rv->a_N_F(khash))
         continue;

# if a_TYPE == a_TYPE_CSDICT
      if(klen != rv->a_N_F(klen))
         continue;
      if(((self->a_T_F(flags) & a_T_PUBNAME(CASE))
            ? R(sz(*)(void const*,void const*,uz),&su_cs_cmp_case_n)
            : &su_mem_cmp)(key, rv->a_N_F(key), klen))
         continue;
# else
#  error
# endif

      /* Match! */
      if(last != NIL){
         if(self->a_T_F(flags) & a_T_PUBNAME(HEAD_RESORT)){
            last->a_N_F(next) = rv->a_N_F(next);
            rv->a_N_F(next) = *arr;
            *arr = rv;
         }else if(lookarg_or_nil != NIL)
            S(struct a_LA*,lookarg_or_nil)->la_last = last;
      }
      break;
   }

jleave:
   NYD_OU;
   return rv;
}

s32
a_T_PRISYM(insrep)(struct a_T *self, a_TK const *key, void *value,
      boole replace){
   struct a_LA la;
   struct a_N *np;
   s32 rv;
   u16 flags;
   NYD_IN;
   ASSERT(self);

   flags = self->a_T_F(flags);

   /* Ensure this basic precondition */
   if(value == NIL &&
         (flags & a_T_PUBNAME(OWNS)) && !(flags & a_T_PUBNAME(NILISVALO))){
      rv = su_ERR_INVAL;
      goto jleave;
   }

   /* But on error we will put new node in case we are empty, so create some
    * array space right away */
   if(UNLIKELY(self->a_T_F(size) == 0) &&
         (rv = a_FUN(resize)(self, 1)) != 0)
      goto jleave;

   /* Try to find a yet existing key */
   np = a_T_PRISYM(lookup)(self, key, &la);

# if a_TYPE == a_TYPE_CSDICT
   /* (Ensure documented maximum key length first) */
   if(UNLIKELY(UCMP(z, la.la_klen, >, S32_MAX))){
      rv = su_state_err(su_STATE_ERR_OVERFLOW, (flags & su_STATE_ERR_MASK),
            _(a_TYPE_NAME ": insertion: key length excess"));
      goto jleave;
   }
# endif

   /* Is it an insertion of something new? */
   if(LIKELY(np == NIL)){
      if(UNLIKELY((rv = a_FUN(node_new)(self, &np, &la, key, value)) != 0))
         goto jleave;
      /* Never grow array storage if no other node is in this slot.
       * And do not fail if a resize fails at this point, it would only be
       * expensive and of no value, especially as it seems the user is
       * ignoring ENOMEM++ */
      if(UNLIKELY(np->a_N_F(next) != NIL))
         /*rv = */a_FUN(check_resize)(self, FAL0, self->a_T_F(count));
   }else{
      if(LIKELY(replace) && ((rv = a_FUN(replace)(self, np, value)) != 0))
         goto jleave;
      rv = -1;
   }

jleave:
   NYD_OU;
   return rv;
}

#if DVLOR(1, 0)
void
a_T_PRISYM(stats)(struct a_T const *self){
   ul size, empties, multies, maxper, i, j;
   struct a_N **arr, *np;
   NYD_IN;
   ASSERT(self);

   su_log_lock();
   su_log_write(su_LOG_INFO,
      "----------\n>>> " a_TYPE_NAME "(%p): statistics\n",
      self);

   arr = self->a_T_F(array);
   size = self->a_T_F(size);
   empties = multies = maxper = 0;

   /* First pass: overall stats */
   for(i = 0; i < size; ++i){
      j = 0;
      for(np = arr[i]; np != NIL; np = np->a_N_F(next))
         ++j;
      if(j == 0)
         ++empties;
      else{
         if(j > 1)
            ++multies;
         maxper = MAX(maxper, j);
      }
   }
   j = (multies != 0) ? multies : size - empties;
   if(j != 0)
      j = self->a_T_F(count) / j;

   su_log_write(su_LOG_INFO,
      "* Overview\n"
      "  - POW2_SPACED=%d "
# if a_TYPE != a_TYPE_CSDICT
         "OWNS_KEYS=%d "
# endif
# if a_TYPE == a_TYPE_CSDICT
         "CASE=%d "
# endif
         "OWNS=%d "
         "\n"
      "  - HEAD_RESORT=%d AUTO_SHRINK=%d FROZEN=%d ERR_PASS=%d NILISVALO=%d\n"
      "  - Array size : %lu\n"
      "  - Elements   : %lu\n"
      "  - Tresh-Shift: %u\n"
      "* Distribution\n"
      "  - Slots: empty: %lu, multiple: %lu, maximum: %lu, avg/multi: ~%lu\n"
      "* Distribution, visual\n"
      ,
      ((self->a_T_F(flags) & a_T_PUBNAME(POW2_SPACED)) != 0),
# if a_TYPE != a_TYPE_CSDICT
         ((self->a_T_F(flags) & a_T_PUBNAME(OWNS_KEYS)) != 0),
# endif
# if a_TYPE == a_TYPE_CSDICT
         ((self->a_T_F(flags) & a_T_PUBNAME(CASE)) != 0),
# endif
         ((self->a_T_F(flags) & a_T_PUBNAME(OWNS)) != 0),
      ((self->a_T_F(flags) & a_T_PUBNAME(HEAD_RESORT)) != 0),
         ((self->a_T_F(flags) & a_T_PUBNAME(AUTO_SHRINK)) != 0),
         ((self->a_T_F(flags) & a_T_PUBNAME(FROZEN)) != 0),
         ((self->a_T_F(flags) & a_T_PUBNAME(ERR_PASS)) != 0),
         ((self->a_T_F(flags) & a_T_PUBNAME(NILISVALO)) != 0),
      (ul)self->a_T_F(size),
      (ul)self->a_T_F(count),
      (ui)self->a_T_F(tshift),
      (ul)empties, (ul)multies, (ul)maxper, (ul)j
   );

   /* Second pass: visual distribution */
   for(i = 0; i < size; ++i){
      char buf[50], *cp;

      cp = buf;
      for(j = 0, np = arr[i]; np != NIL; np = np->a_N_F(next)){
         if(++j >= sizeof(buf) - 1 -1){
            *cp++ = '@';
            break;
         }
         *cp++ = '*';
      }
      *cp = '\0';

      su_log_write(su_LOG_INFO, "%5lu |%s\n", (ul)i, buf);
   }

   su_log_write(su_LOG_INFO,
      "<<<" a_TYPE_NAME "(%p): statistics\n----------\n",
      self);
   su_log_unlock();
   NYD_OU;
}
#endif /* DVLOR(1, 0) */

struct a_T *
a_T_PUBSYM(create)(struct a_T *self, u16 flags,
      struct su_toolbox const *tbox_or_nil){
   NYD_IN;
   ASSERT(self);

   su_mem_set(self, 0, sizeof *self);

   ASSERT_NYD(!(flags & a_T_PUBNAME(OWNS)) ||
      (tbox_or_nil != NIL && tbox_or_nil->tb_clone != NIL &&
       tbox_or_nil->tb_delete != NIL && tbox_or_nil->tb_assign != NIL));

   self->a_T_F(flags) = (flags &= a_T_PRINAME(CREATE_MASK));
   self->a_T_F(tshift) = 2;
   self->a_T_F(tbox) = tbox_or_nil;
   NYD_OU;
   return self;
}

SHADOW struct a_T *
a_T_PUBSYM(create_copy)(struct a_T *self, struct a_T const *t){
   NYD_IN;
   ASSERT(self);

   su_mem_set(self, 0, sizeof *self);

   ASSERT_JUMP(t != NIL, su_NYD_OU_LABEL);
   (void)a_FUN(assign)(self, t, TRU1);
   NYD_OU;
   return self;
}

void
a_T_PUBSYM(gut)(struct a_T *self){ /* XXX inline */
   NYD_IN;
   ASSERT(self);

   if(self->a_T_F(array) != NIL)
      self = a_T_PUBSYM(clear)(self);
   NYD_OU;
}

s32
a_T_PUBSYM(assign)(struct a_T *self, struct a_T const *t){ /* XXX inline */
   s32 rv;
   NYD_IN;
   ASSERT(self);

   rv = a_FUN(assign)(self, t, TRU1);
   NYD_OU;
   return rv;
}

s32
a_T_PUBSYM(assign_elems)(struct a_T *self, struct a_T const *t){ /* XXX inline*/
   s32 rv;
   NYD_IN;
   ASSERT(self);

   rv = a_FUN(assign)(self, t, FAL0);
   NYD_OU;
   return rv;
}

struct a_T *
a_T_PUBSYM(clear)(struct a_T *self){
   NYD_IN;
   ASSERT(self);

   if(self->a_T_F(array) != NIL){
      if(self->a_T_F(count) > 0)
         self = a_T_PUBSYM(clear_elems)(self);

      su_FREE(self->a_T_F(array));
      self->a_T_F(array) = NIL;
      self->a_T_F(size) = 0;
   }
   NYD_OU;
   return self;
}

struct a_T *
a_T_PUBSYM(clear_elems)(struct a_T *self){
   void *vp;
   struct a_N **arr, *np, *tmp;
   su_delete_fun delfun;
   u32 cnt, size;
   NYD_IN;
   ASSERT(self);

   cnt = self->a_T_F(count);
   self->a_T_F(count) = 0;
   size = self->a_T_F(size);
   delfun = (self->a_T_F(flags) & a_T_PUBNAME(OWNS))
         ? self->a_T_F(tbox)->tb_delete : S(su_delete_fun,NIL);
   arr = self->a_T_F(array);

   while(cnt > 0){
      ASSERT(size != 0);
      if((np = arr[--size]) != NIL){
         arr[size] = NIL;

         do{
            tmp = np;
            np = np->a_N_F(next);
            --cnt;

            if(delfun != NIL && (vp = tmp->a_N_F(data)) != NIL)
               (*delfun)(vp);
            a_N_FREE(self, tmp);
         }while(np != NIL);
      }
   }
   NYD_OU;
   return self;
}

struct a_T *
a_T_PUBSYM(swap)(struct a_T *self, struct a_T *t){
   struct a_T tmp;
   NYD_IN;
   ASSERT(self);
   ASSERT(t != NIL);

   tmp = *self;
   *self = *t;
   *t = tmp;
   NYD_OU;
   return self;
}

struct a_T *
a_T_PUBSYM(balance)(struct a_T *self){
   NYD_IN;
   ASSERT(self);

   self->a_T_F(flags) &= ~a_T_PUBNAME(FROZEN);
   (void)a_FUN(check_resize)(self, TRU1, self->a_T_F(count));
   NYD_OU;
   return self;
}

boole
a_T_PUBSYM(remove)(struct a_T *self, a_TK const *key){
   struct a_LA la;
   struct a_N *np;
   NYD_IN;
   ASSERT(self);
   ASSERT_NYD_EXEC(key != NIL, np = NIL);

   np = a_T_PRISYM(lookup)(self, key, &la);
   if(LIKELY(np != NIL))
      self = a_FUN(remove)(self, np, &la);
   NYD_OU;
   return (np != NIL);
}

struct a_V *
a_V_PRISYM(move)(struct a_V *self, u8 type){
   u32 size, idx;
   struct a_N **arr, *np;
   struct a_T *parent;
   NYD_IN;
   ASSERT(self);

   parent = self->a_V_F(parent);
   arr = parent->a_T_F(array);
   size = parent->a_T_F(size);

   if(type == a_V_PRINAME(MOVE_BEGIN)){
      for(np = NIL, idx = 0; idx < size; ++idx)
         if((np = arr[idx]) != NIL)
            break;
      goto jset;
   }else{
      idx = self->a_V_F(index);
      if(UNLIKELY((np = self->a_V_F(node)->a_N_F(next)) == NIL)){
         while(++idx < size)
            if((np = arr[idx]) != NIL)
               break;
      }

      if(LIKELY(type != a_V_PRINAME(MOVE_HAS_NEXT))){
jset:
         if((self->a_V_F(node) = np) != NIL){
            self->a_V_F(index) = idx;
            self->a_V_F(next_node) = NIL;
         }
      }else{
         self->a_V_F(next_index) = idx;
         self->a_V_F(next_node) = np;
      }
   }

   NYD_OU;
   return self;
}

s32
a_V_PUBSYM(set_data)(struct a_V *self, void *value){
   s32 rv;
   struct a_T *parent;
   NYD_IN;
   ASSERT(self);
   ASSERT_NYD_EXEC(a_V_PUBSYM(is_valid)(self), rv = su_ERR_INVAL);

   parent = self->a_V_F(parent);

   if(value == NIL){
      u16 flags;

      flags = parent->a_T_F(flags);

      if((flags & a_T_PUBNAME(OWNS)) && !(flags & a_T_PUBNAME(NILISVALO))){
         rv = su_ERR_INVAL;
         goto jleave;
      }
   }

   rv = a_FUN(replace)(parent, self->a_V_F(node), value);
jleave:
   NYD_OU;
   return rv;
}

boole
a_V_PUBSYM(find)(struct a_V *self, a_TK const *key){
   struct a_LA la;
   struct a_N *np;
   NYD_IN;
   ASSERT(self);

   self->a_V_F(node) =
   np = a_T_PRISYM(lookup)(self->a_V_F(parent), key, &la);
   if(np != NIL){
      self->a_V_F(index) = la.la_slotidx;
      self->a_V_F(next_node) = NIL;
   }
   NYD_OU;
   return (np != NIL);
}

struct a_V *
a_V_PUBSYM(remove)(struct a_V *self){
   struct a_LA la;
   struct a_N *np, *mynp;
   u32 idx;
   struct a_T *parent;
   NYD_IN;
   ASSERT(self);
   ASSERT_NYD(a_V_PUBSYM(is_valid)(self));

   parent = self->a_V_F(parent);

   /* Setup the look arg for our position */
   np = *(la.la_slot = &parent->a_T_F(array)[idx = self->a_V_F(index)]);
   mynp = self->a_V_F(node);
   if(np == mynp)
      la.la_last = NIL;
   else{
      while(np->a_N_F(next) != mynp)
         np = np->a_N_F(next);
      la.la_last = np;
   }

   /* Remove our position */
   np = mynp;
   mynp = mynp->a_N_F(next);
   parent = a_FUN(remove)(parent, np, &la);

   /* Move to the next valid position, if any */
   if(mynp != NIL)
      self->a_V_F(node) = mynp;
   else{
      while(++idx < parent->a_T_F(size))
         if((mynp = parent->a_T_F(array)[idx]) != NIL)
            break;
      self->a_V_F(node) = mynp;
      self->a_V_F(index) = idx;
   }
   self->a_V_F(next_node) = NIL;
   NYD_OU;
   return self;
}

# undef a_TYPE_CSDICT
# undef a_TYPE
# undef a_TYPE_IS_DICT
# undef a_TYPE_NAME
# undef a_T
# undef a_T_F
# undef a_T_PRINAME
# undef a_T_PUBNAME
# undef a_T_PUBSYM
# undef a_T_PRISYM
# undef a_FUN
# undef a_TK
# undef a_N
# undef a_N_F
# undef a_N_ALLOC
# undef a_N_FREE
# undef a_V
# undef a_V_F
# undef a_V_PRINAME
# undef a_V_PUBNAME
# undef a_V_PRISYM
# undef a_V_PUBSYM
# undef a_LA
# undef a_LA_F
#endif /* a_ASSOC_MAP_H */

/* s-it-mode */
s-nail-14.9.15/src/su/x-mem-tools.h000066400000000000000000000043461352610246600166420ustar00rootroot00000000000000/*@ Implementation of mem.h: utility funs: generic implementations.
 *
 * Copyright (c) 2019 Steffen (Daode) Nurpmeso .
 * SPDX-License-Identifier: ISC
 *
 * 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.
 */

#include  /* TODO unroll them in C */

#include "su/code-in.h"

/**/
SINLINE void *a_memt_find(void const *vp, s32 what, uz len);
SINLINE void *a_memt_rfind(void const *vp, s32 what, uz len);
SINLINE sz a_memt_cmp(void const *vpa, void const *vpb, uz len);
SINLINE void *a_memt_copy(void *vp, void const *src, uz len);
SINLINE void *a_memt_move(void *vp, void const *src, uz len);
SINLINE void *a_memt_set(void *vp, s32 what, uz len);

SINLINE void *
a_memt_find(void const *vp, s32 what, uz len){
   /* Need to cast away const for g++ 8.2.0 (OSUKISS Linux) */
   return memchr(C(void*,vp), what, len);
}

SINLINE void *
a_memt_rfind(void const *vp, s32 what, uz len){
   u8 *rv;

   for(rv = &C(u8*,S(u8 const*,vp))[len];;){
      ASSERT(&rv[-1] >= S(u8 const*,vp));
      if(*--rv == S(u8,what))
         break;
      if(UNLIKELY(rv == vp)){
         rv = NIL;
         break;
      }
   }
   return rv;
}

SINLINE sz
a_memt_cmp(void const *vpa, void const *vpb, uz len){
   return memcmp(vpa, vpb, len);
}

SINLINE void *
a_memt_copy(void *vp, void const *src, uz len){
   memcpy(vp, src, len);
   return vp;
}

SINLINE void *
a_memt_move(void *vp, void const *src, uz len){
   memmove(vp, src, len);
   return vp;
}

SINLINE void *
a_memt_set(void *vp, s32 what, uz len){
   memset(vp, what, len);
   return vp;
}

#include "su/code-ou.h"
/* s-it-mode */