pax_global_header00006660000000000000000000000064133602043320014506gustar00rootroot0000000000000052 comment=7c581ab6cb767f3969c855a2ed9e37fc5e82b903 nullmailer-2.2/000077500000000000000000000000001336020433200135155ustar00rootroot00000000000000nullmailer-2.2/.gitignore000066400000000000000000000007561336020433200155150ustar00rootroot00000000000000*.[ao] *~ .deps ANNOUNCEMENT ChangeLog Makefile Makefile.in aclocal.m4 autom4te.cache compile config.h config.h.in config.log config.status configure depcomp install-sh lib/defines.cc missing mkinstalldirs protocols/qmqp protocols/smtp src/mailq src/nullmailer-dsn src/nullmailer-inject src/nullmailer-queue src/nullmailer-send src/nullmailer-smtpd src/sendmail stamp-h.in stamp-h1 test/address-test test/argparse-test test/clitest0 test/clitest1 test/functions test/log test/runtests test-tmp nullmailer-2.2/AUTHORS000066400000000000000000000000451336020433200145640ustar00rootroot00000000000000Bruce Guenter nullmailer-2.2/BUGS000066400000000000000000000002301336020433200141730ustar00rootroot00000000000000If you find anything in these programs that you would consider a bug, please report it immediately to: Bruce Guenter Thank you! nullmailer-2.2/COPYING000066400000000000000000000431101336020433200145470ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. nullmailer-2.2/ChangeLog.old000066400000000000000000000422321336020433200160470ustar00rootroot000000000000002001-02-01 Bruce Guenter * src/send.cc (send_all): If sending the file fails, skip to the next file instead of aborting sending completely. 2000-12-29 Bruce Guenter * Released version 1.00RC5 2000-10-05 Bruce Guenter * lib/hostname.cc: Fixed the prototype for getdomainname. 2000-10-02 Bruce Guenter * protocols/smtp.cc (docmd): Return proper error codes for permanent and temporary SMTP failures. 2000-08-28 Bruce Guenter * Released version 1.00RC4 * lib/address.cc (RULE(domain)): Modified to handle (and strip) a trailing period in the domain name. 2000-08-15 Bruce Guenter * lib/hostname.cc (getnames): Added an extern declaration of getdomainname for systems that don't declare it. 2000-08-10 Bruce Guenter * Released version 1.00RC3 * src/inject.cc (read_header): Fixed header parsing logic. 2000-08-07 Bruce Guenter * src/inject.cc: Treat the first non-header line as the first line of the body. * src/sendmail.cc: Ignore the -L option (set the identifier for syslog messages). 2000-07-12 Bruce Guenter * src/send.cc: Added code to handle buggy named pipes that require both a reader and a writer to be opened simultaneously. * lib/tcpconnect.cc (tcpconnect): AF_INET should have been PF_INET in call to socket. Also, the sin_family needed to be set to AF_INET. 2000-07-03 Bruce Guenter * lib/hostname.cc (getnames): Fixed typo that caused systems without "domainname" in struct utsname to fail to compile. 2000-06-28 Bruce Guenter * Released version 1.00RC2 * lib/hostname.cc (getnames): Changed the logic here to append the domain name to the node name to produce the fully qualified host name if the node name does not already contain the domain name. * protocols/smtp.cc: Changed all the calls to smtp::docmd to use the start of the range instead of the expected response. This may have been causing some SMTP sessions to fail. 2000-06-19 Bruce Guenter * lib/tcpconnect.cc (tcpconnect): Removed an extraneous const from the call to connect. 2000-06-15 Bruce Guenter * Released version 1.00RC1 * Fixed up copyright notices in all the sources. * lib/makefield.cc (make_date): Totally reworked the logic here if tm_gmtoff is detected to fix timezone and DST detection bugs. 2000-01-11 Bruce Guenter * Released version 0.40 * lib/makefield.cc: Fixed up to autodetect (with the help of configure) the tm_isdst and tm_gmtoff flags in struct tm. 2000-01-11 Bruce Guenter * lib/tcpconnect.cc: Reordered the include files here to avoid problems with FreeBSD headers. 2000-01-10 Bruce Guenter * lib/address.cc: Renamed isspecial to issymbol to avoid problems on FreeBSD caused by an existing "isspecial" macro. * src/sendmail.cc (cli_main): Fixed typo that caused parseargs to not be called. 1999-10-22 Bruce Guenter * protocols/protocol.cc (cli_main): Use the already opened FD to load the message from instead of opening it. * src/send.cc (send_one): Open the named file for writing before attempting to send message, and pass it to exec_protocol. (exec_protocol): Dup the open message to FD 0 before executing program. 1999-10-21 Bruce Guenter * Released version 0.39 * lib/list.h: Added a const_list_iterator, as well as a copy constructor for list. 1999-10-20 Bruce Guenter * src/inject.cc (parse_line): Added test for non-RFC header lines (ie lines starting with anything except a sequence of non-whitespace characters follows by a colon). (read_header): Test if the first header line is a continuation line and fail if so. * src/send.cc (load_remotes): Fixed parsing of "remotes" control file to allow any whitespace between, before, and after elements, as well as to handle lines starting with a '#' as comments. 1999-10-04 Bruce Guenter * Released version 0.38 * src/send.cc (load_config): Don't fail reading the configuration if pausetime can't be read. 1999-09-23 Bruce Guenter * Released version 0.37 * lib/address.cc (RULE(route_spec)): Made the "phrase" optional to allow addresses like "" (note, no comment) to be parsed. 1999-09-16 Bruce Guenter * src/send.cc (send_all): Load the config each time the queue is run. (do_select): Removed the logic here to handle reloading the config on HUP, since it's reloaded all the time. (load_files): Removed the 'stat' on the queue directory as an indicator of if it needs to be rescanned, and made the contents reloaded always. (do_select): Force a reload of the queue if the select times out. 1999-09-11 Bruce Guenter * Released version 0.36 * src/sendmail.cc: Rewrote to use the CLI library. * src/inject.cc: Rewrote to use the CLI library. * protocols/protocol.cc: Rewrote to use the CLI library. 1999-09-02 Bruce Guenter * Released version 0.35 1999-08-26 Bruce Guenter * src/send.cc (remote): Parse options from the string. (load_remotes): Handle options after the protocol name. (exec_protocol): Build an argument list including options. * protocols/protocol.cc (parse_args): Added support for a port number. * protocols/qmqp.cc: Adapted to use generic interface. * protocols/smtp.cc: Adapted to use generic interface. * protocols/protocol.cc (main): Started writing a generic protocol library, which will open the message file and determine a port number. * src/send.cc (load_files): Stat the queue before opening it to see if the contents have changed. 1999-07-22 Bruce Guenter * lib/hostname.cc (getnames): Switched the implementation of domainname and hostname to use uname(2). This fixes a problem observed with empty domain names. 1999-07-16 Bruce Guenter * lib/address.cc: Rewrote this parsing framework to do a top-level tokenization before parsing, and then parse based on those tokens. 1999-07-15 Bruce Guenter * Imported latest mystring and fdbuf libraries, and adapted the rest of the code to call the new routines. 1999-03-30 Bruce Guenter * Integrated new modularized mystring library from vmailmgr. 1999-03-27 Bruce Guenter * Released version 0.33 * src/queue.cc (copyenv): Only do the envelope remapping on recipient addresses. * lib/listtest.cc: Wrote test code for the list class (not compiled, but included in the distribution). * lib/list.h (remove): Fixed (hopefully) the code to advance the prev/curr pointers after removing a node. * src/send.cc (catchsender): Removed an extraneous return line. * doc/nullmailer-queue.8: Updated to describe the admin address facility. * src/queue.cc (main): Load in the "admin" address and localhost to determine how to remap addresses. If the hostname is localhost, remap this address to the admin address. 1999-03-04 Bruce Guenter * Released version 0.32 * lib/address.cc (unquote): Wrote this routine to remove quoting and escaped characters from a string. (quote): Wrote this routine to add necessary quoting and escaping to addresses. (RULE(quoted_string)): Unquote strings before returning them. Modified many other routines to handle unquoting. 1999-02-25 Bruce Guenter * Released version 0.31 * Makefile.am (install-root): Needed to make nullmailer-queue setuid nullmail here. * HOWTO: Added a quick guide to getting nullmailer running. 1999-02-23 Bruce Guenter * Released version 0.30 * src/inject.cc (struct header_field): Do not empty the recipient list when a resent field is seen if header recipients are being ignored. * doc/nullmailer-inject.1: Adjusted the section on setting the host name to account for the below change. * src/inject.cc (setup_from): Set the host name to defaulthost instead of hostname(), and canonicalize host and shost before using them. * lib/fdbuf_copy.cc (fdbuf_copy): Added an initial EOF test and moved the loop's EOF test to the end of the loop. 1999-02-22 Bruce Guenter * src/inject.cc (send_body): Removed the test to see if fin is at EOF before calling fdbuf_copy. * lib/fdbuf_copy.cc (fdbuf_copy): Revised loop termination condition from in.last_count() == 0 (which could happen at EOF) to !in.eof() (since EOF is not an error condition). * Released version 0.29 * src/inject.cc: Fixed a number of offsets in the header_has_* and header_field_* declarations. * doc/nullmailer-inject.1: Modified the documentation to describe the "-n" (do not queue) and "-v" (show envelope) flags. * src/inject.cc: Added handling for flags to print out the message and optionally the envelope instead of queueing the message. * src/sendmail.cc (parseargs): Added a whole long list of sendmail flags to ignore. * lib/address.cc (RULE(route)): fixed missing pointer advance 1999-02-21 Bruce Guenter * test/address-test.cc: Moved this testing code out of the main address.cc source code module. 1999-02-20 Bruce Guenter * lib/address.cc (RULE(address)): Reversed order of group and mailbox. (RULE(phrase)): Fixed the extra trailing space added by this rule along with cleaning up how this rule works. (RULE(mailboxes)): Added this rule, a parallel of addresses, for use in group. (RULE(group)): Fixed several bugs in this rule. 1999-02-19 Bruce Guenter * lib/address.cc (RULE(quoted_string)): Fixed inclusion of quotes in quoted addresses. (TEST_ADDRESS): Added in a self-test framework (RULE(comment)): fixed missing comment carry-through (RULE(group)): fixed missing comment insertion (RULE(addresses)): fixed missing comment insertion (RULE(local_part)): forgot to advance pointer if a comment was matched. (RULE(domain)): forgot to advance pointer after SKIPDELIM (RULE(addr_spec)): forgot to advance pointer after SKIPDELIM 1999-02-18 Bruce Guenter * Released version 0.28 * src/sendmail.cc (main): Pass "-e" to inject (use either) instead of "-a" (use args only) if sendmail was not passed a "-t", otherwise pass "-b" (use both) instead of "-h" (use header only). * spec: Fixed the useradd part of this script to add the "nullmail" user instead of "$1". * init: Reworked large parts of this script to make it work on my systems. * src/send.cc (read_trigger): Modified behavior of this routine to read a chunk of data from the trigger before closing it. This prevents the trigger from being "pulled" after it's been reopened. * src/sendmail.cc: Prefixed all error messages with "sendmail:". * src/inject.cc (setup_from): Added code to handle the "c" flag -- use "address (comment)" style in the generated from line instead of "comment
". (exec_queue): Fixed error message to print out the actual value of SBIN_DIR, plus prefixing the message with "nullmailer-inject". * Released version 0.27 * src/inject.cc (main): Whoops -- write should have been writeln. * protocols/qmqp.cc: Completed (with minimal testing) the QMQP module. * lib/fdbuf_copy.cc (fdbuf_copy): Added a "noflush" option to make calling flush on the output fdbuf optional. * lib/makefield.cc (make_date): Fixed bug: neglected to NUL-terminate the timezone string. * lib/canonicalize.cc (canonicalize): Moved this function out of address.cc * lib/errcodes.cc (errorstr): Added some new error codes * lib/fdibuf.cc (getchar): Wrote this routine to read in a single character. (getnetstring): Wrote this routine to read in a netstring * lib/netstring.cc (strnl2net): Wrote this routine that converts a string plus a newline character into a netstring. This avoids an extra memory allocation and copy to do the concatenation before making the netstring. * protocols/qmqp.cc: Started a QMQP protocol module. * lib/netstring.cc (str2net): Wrote this routine to convert strings into netstrings (for details, see ftp://koobera.math.uic.edu/www/proto/netstrings.txt). * protocols/smtp.cc (main): Moved the message file open above the connection to avoid making a connection if the file is unreadable. * lib/fdibuf.cc (rewind): Wrote this routine to rewind an open fdibuf. * src/inject.cc (make_recipient_list): Completed code to handle the "t" flag. * doc/nullmailer-inject.1: Added section describing the parsing of the NULLMAILER_FLAGS environment variable. * src/inject.cc (struct header_field): Added code to handle fields that are ignored. (fix_header): Added preliminary code to add in a missing to field. (parse_flags): Wrote this routine to handle the NULLMAILER_FLAGS environment variable. (parse_args): Added a call to parse_flags. 1999-02-17 Bruce Guenter * Released version 0.26 * Moved address.cc, address.h, makefield.cc into lib. 1999-02-16 Bruce Guenter * src/queue.cc (deliver): Fixed unique filename generator to call itoa only once per constructor, and to avoid calling time() and getpid() more than necessary. * src/send.cc (exec_protocol): Stripped out the error messages printed in this routine, since they go nowhere anyways. (send_one): Added tracing information about program execution. * src/makefield.cc (make_messageid): Can't have two calls to itoa as arguments to the same function call (mystring constructor in this case) because itoa uses a static buffer. * src/inject.cc (exec_queue): Execute nullmailer-queue from SBIN_DIR. * lib/defines.h: Added a definition for SBIN_DIR. 1999-02-15 Bruce Guenter * Released version 0.25 * doc/nullmailer-send.8: Updated the documentation to reflect the new parsing code for "remotes". * src/makefield.cc (make_messageid): Changed the format of the message-id string to make it easier to generate and more unique. (make_date): Fixed timezone number generator code. * lib/itoa.cc (itoa): Fixed bug in padding code that caused the string to be padded with unending NUL bytes instead of zeros. * lib/config_readlist.cc (config_readlist): Modified to strip all the lines that are read in, to ignore lines that start with a pound ('#') or are empty, and to return false if no lines were read. * lib/config_read.cc (config_read): Modified to strip the resulting string. * lib/mystring.cc: Revised lstrip, rstrip, and strip to return *this if no modifications to the string were necessary. * src/send.cc (load_config): Rewrote the logic of this routine to always load all the config files, and moved the loading of the remotes into a separate section. (load_remotes): Added support for loading "hostname protocol" pairs from the remotes file. * protocols/smtp.cc (main): Filename is argv[2], not argv[1]. * src/address.cc (RULE(local_part)): Fixed another pointer increment bug. * src/inject.cc (main): Moved check for no recipients into here. 1999-02-14 Bruce Guenter * lib/hostname.cc: Fixed a problem that could cause nullmailer-inject (and possibly others) to core dump on exit. * src/inject.cc (send_body): Fixed bug that caused transmission of an empty body (legal) to nullmailer-queue to fail. (read_header): Added a check to ensure that at least one recipient was given. 1999-02-13 Bruce Guenter * src/send.cc: Rewrote the portion of this code that executes the protocol program to not do a connect, and to expect a numerical error code from the exit status of the program. Also implemented a configurable pause between queue runs. * protocols/smtp.cc: Rewrote this module extensively. It now expects the hostname as the first command-line parameter and the filename as the second, and returns a single error code (no text error messages and no pipes). 1999-02-12 Bruce Guenter * src/address.cc (RULE(domain)): Fixed pointer increment bug in this rule. * doc/nullmailer-inject.1: Updated to reflect status of configuration variable reading. * src/makefield.cc (make_messageid): Use idhost instead of hostname(). * src/inject.cc (read_config): Read the defaultdomain, defaulthost, and idhost config files. * src/address.cc (canonicalize): Use the defaulthost and defaultdomain strings, declared in src/inject.cc, instead of hostname() and domainname(). * src/inject.cc: Added code to parse the command line options as documented in the man page. 1999-02-11 Bruce Guenter * Released version 0.22 * src/sendmail.cc: Completed the basics of this program. * doc/nullmailer-inject.1: Filled in the DESCRIPTION a little bit. nullmailer-2.2/HOWTO000066400000000000000000000042571336020433200143500ustar00rootroot00000000000000Configuration of this program in the typical case is quite simple. 1. Compile and install the program. If you are using the RPM, skip this step. See the INSTALL file for more details. Note that this package is designed to execute as a non-privileged user named "nullmail". When you do the final install, add the "nullmail" user (typically with either a non-privileged group or a new "nullmail" group) and run "make install-root" to set up the files with the proper permissions. 2. Set up your hostname in SYSCONFDIR[1]/nullmailer/me. Usually this will be as simple as typing: /bin/hostname >SYSCONFDIR/nullmailer/me You may also wish to set up the defaultdomain and defaulthost configuration files. See the man page for nullmailer-inject for more details. 3. Into the file SYSCONFDIR/nullmailer/remotes, put one line for each of your relay hosts (the computer or computers to which you will send all your mail): HOSTNAME PROTOCOL You will have specified the SYSCONFDIR during configuration of the install step. On most systems, it will be /usr/local/etc. The RPMs use /etc. HOSTNAME is the fully-qualified host name of the relay host, or its numerical IP address. PROTOCOL will typically be "smtp" (without the quotes), but QMQP[2] is also supported with the "qmqp" module. 4. Start nullmailer-send. If you are using the RPM, ignore the rest of the instructions and type: svc-start nullmailer Otherwise, you will need to set up the appropriate scripts to run nullmailer-send in the background, with its output going to a logging program. The following is an appropriate run script for use with daemontools[3]: #!/bin/sh exec setuidgid nullmail nullmailer-send 2>&1 5. You're done! The included sendmail emulator front-end should allow most (if not all) sendmail-compatible programs to run without any changes. See the man page for nullmailer-inject for details on outgoing email message header formatting options. Footnotes: 1. SYSCONFDIR is the system configuration directory, as configured during compilation. It defaults to /usr/local/etc 2. QMQP is a high-speed mail queueing protocol, used with qmail. See http://cr.yp.to/proto/qmqp.html 3. See http://cr.yp.to/daemontools.html. nullmailer-2.2/INSTALL000066400000000000000000000011131336020433200145420ustar00rootroot00000000000000To build and install nullmailer, run: sh autogen.sh ./configure make sudo make install install-root (Running autogen.sh is optional for release packages) To enable SSL/TLS support, add "--enable-tls" to the configure command line above. The default installation expects that a user and group "nullmail" have already been set up. The install steps will create all appropriate configuration and queue directories, and change their ownership as needed. You may set the installation directory and other parameters by options to ./configure. To see them, run: ./configure --help nullmailer-2.2/Makefile.am000066400000000000000000000021051336020433200155470ustar00rootroot00000000000000localstatedir = @localstatedir@/spool/nullmailer sysconfdir = @sysconfdir@/nullmailer EXTRA_DIST = BUGS HOWTO INSTALL TODO spec \ scripts/nullmailer.run scripts/nullmailer-log.run \ scripts/nullmailer.service SUBDIRS = doc lib protocols src test install-data-local: @$(NORMAL_INSTALL) $(mkinstalldirs) $(DESTDIR)$(localstatedir)/failed chmod 700 $(DESTDIR)$(localstatedir)/failed $(mkinstalldirs) $(DESTDIR)$(localstatedir)/queue chmod 700 $(DESTDIR)$(localstatedir)/queue $(mkinstalldirs) $(DESTDIR)$(localstatedir)/tmp chmod 700 $(DESTDIR)$(localstatedir)/tmp $(mkinstalldirs) $(DESTDIR)$(sysconfdir) $(RM) -f $(DESTDIR)$(localstatedir)/trigger mkfifo $(DESTDIR)$(localstatedir)/trigger chmod 600 $(DESTDIR)$(localstatedir)/trigger install-root: chown nullmail $(DESTDIR)$(localstatedir)/* chown nullmail $(DESTDIR)$(sbindir)/nullmailer-queue chmod u+s $(DESTDIR)$(sbindir)/nullmailer-queue chown nullmail $(DESTDIR)$(bindir)/mailq chmod u+s $(DESTDIR)$(bindir)/mailq dist-hook: sed -e s/@VERSION\@/@VERSION@/ \ <$(srcdir)/spec >$(distdir)/nullmailer-@VERSION@.spec nullmailer-2.2/NEWS000066400000000000000000000424751336020433200142300ustar00rootroot00000000000000This file lists all the major user-visible changes to nullmailer. ------------------------------------------------------------------------------- Changes in version 2.2 - nullmailer-send no longer generates bounces for rejected bounces. Thanks Fejes József - Fixed compile error in sendmail on GCC older than 4.9. - Fixed treating authentication failure as message rejection. Thanks Fejes József - nullmailer-inject now sets the full name of the sender to the user name as a fallback. This helps distinguish system sent messages when the MTA rewrites the address (as does GMail, for example). - Fixed compatibility issue with gnutls 3.6 (and possibly others). ------------------------------------------------------------------------------- Changes in version 2.1 - Added support for TLS anonymous authentication. Thanks Uffe Jakobsen. - Fixed sendmail wrapper handling of empty sender on command line. Thanks Sebastian Wiedenroth. - Fixed handling of quoted strings in the "remotes" file. Thanks Mihai Moldovan. - Fixed nullmailer-inject handling of leading "From " lines. - Some build fixes. - Fixed bogus temporary gethostbyname error message when the protocol source address was incorrect. - Fixed potential race condition in tests. Thanks Felix Lechner. - Fixed handling of time values on 32-bit big-endian systems. Thanks Felix Lechner. Development of this version has been sponsored by FutureQuest, Inc. ossi@FutureQuest.net http://www.FutureQuest.net/ ------------------------------------------------------------------------------- Changes in version 2.0 - Added support to nullmailer-send to move permanently failing messages out of the queue, and to generate bounce messages. - Added support for IPv6. - Added program to generate bounce/delay messages. - Added an "allmailfrom" control file to nullmailer-queue, causing all messages to share a hard-coded envelope sender. - Added logging the message sender/recipient in nullmailer-send. - Improved handling of system errors when reading config files. - Secured handling of password options for protocol modules. - Support standard shell quoting for options in the "remotes" file. - Added protocol option to set a separate TLS client private key file. - Added protocol option to bind the source address on connections. - Fixed nullmailer-inject to report errors to stderr. - Fixed gnutls cast to pointer from integer of different size warning. - Fixed nullmailer-inject and -queue to handle the null (empty) sender address. Needed for RFC 3798 (Message Disposition Notification). - Moved spool directory to /var/spool/nullmailer like other MTAs. Development of this version has been sponsored by FutureQuest, Inc. ossi@FutureQuest.net http://www.FutureQuest.net/ ------------------------------------------------------------------------------- Changes in version 1.13 - Fixed growing recipient list when using sendmail -bs to send multiple messages. Development of this version has been sponsored by FutureQuest, Inc. ossi@FutureQuest.net http://www.FutureQuest.net/ ------------------------------------------------------------------------------- Changes in version 1.12 - Added a quadratic backoff for resend times. - Added support for multiple admin addresses in nullmailer-queue. - SMTP sender now sends a QUIT on success too. - Fixed sendmail -bs mode to properly strip leading periods. - Fixed duplicated final header line when message has no body. Development of this version has been sponsored by FutureQuest, Inc. ossi@FutureQuest.net http://www.FutureQuest.net/ ------------------------------------------------------------------------------- Changes in version 1.11 - Simplify (and expand) handling of ignored sendmail options. - Fixed command-line interpretation of short options with values. - Added support for SSL/TLS client certificates. Development of this version has been sponsored by FutureQuest, Inc. ossi@FutureQuest.net http://www.FutureQuest.net/ ------------------------------------------------------------------------------- Changes in version 1.10 - Added support for SMTPS (SMTP over SSL/TLS) and STARTTLS. - Added support for auto-detection of login method in SMTP protocol. - Added support for -bp (mailq) and -bs (SMTP) mode in the sendmail wrapper. Development of this version has been sponsored by FutureQuest, Inc. ossi@FutureQuest.net http://www.FutureQuest.net/ ------------------------------------------------------------------------------- Changes in version 1.06 - Made nullmailer-queue sync and not just flush queued files to protect against crashes during sensitive operations. - Fixed handling of whitespace in quoted strings in headers. - Fixed handling of timeouts when sending. Development of this version has been sponsored by FutureQuest, Inc. ossi@FutureQuest.net http://www.FutureQuest.net/ ------------------------------------------------------------------------------- Changes in version 1.05 - Fixed compilation of src/selfpipe.cc on Solaris and other OSes. - Fixed handling of TM_HAS_ISDST on Solaris. - Fixed a few address parsing glitches. Development of this version has been sponsored by FutureQuest, Inc. ossi@FutureQuest.net http://www.FutureQuest.net/ ------------------------------------------------------------------------------- Changes in version 1.04 - Fixed rewriting mail to @localhost if defaultdomain is set by not appending .defaultdomain to *@localhost. - Added support for reporting the sender and recipients in mailq. - Added a send timeout in nullmailer-send, to kill sending messages that stall. - Added support in nullmailer-inject for $NULLMAILER_QUEUE to name the program which is used to queue messages. Development of this version has been sponsored by FutureQuest, Inc. ossi@FutureQuest.net http://www.FutureQuest.net/ ------------------------------------------------------------------------------- Changes in version 1.03 - Fixed the SMTP protocol module to send QUIT on protocol failures. - Added support for the SMTP AUTH LOGIN method. Thanks to Sean MacLennan for the suggestion. - Fixed the SMTP protocol module to send EHLO instead of HELO when doing authentication. Thanks to Norbert Tretkowski for pointing this out. - Fixed compile breakage on Solaris due to use of setenv. - Fixed netstring length bug in QMQP sending module. - If the sender name cannot be determined from the environment variables, try to pull it from /etc/passwd before using "unknown". Thanks Roderick Schertler. Development of this version has been sponsored by FutureQuest, Inc. ossi@FutureQuest.net http://www.FutureQuest.net/ ------------------------------------------------------------------------------- Changes in version 1.02 - Fixed bug in SMTP AUTH that would cause SMTP sending to crash with a segfault when it was used. Development of this version has been sponsored by FutureQuest, Inc. ossi@FutureQuest.net http://www.FutureQuest.net/ ------------------------------------------------------------------------------- Changes in version 1.01 - Added support for SMTP AUTH PLAIN. Thanks to Ace Jones for the initial implementation. - Fixed compile error in lib/list.h Development of this version has been sponsored by FutureQuest, Inc. ossi@FutureQuest.net http://www.FutureQuest.net/ ------------------------------------------------------------------------------- Changes in version 1.00 - Fixed problem with multiple arguments to protocol modules. Thanks Sascha Silbe. - Fixed extraneous spaces in SMTP sender. - Made permissions on queued files more strict. - Handle the "-bs" flag in the sendmail wrapper by dieing when we see it. - Fixed the address parser handling domain names with trailing periods. - Fixed bug in header parsing that would cause the last header line to be repeated if there was no body. - Fixed bug in handling headers containing CR+LF line endings. Development of this version has been sponsored by FutureQuest, Inc. ossi@FutureQuest.net http://www.FutureQuest.net/ ------------------------------------------------------------------------------- Changes in version 1.00RC7 - Fixed typo in smtp protocol module that caused HELO to be sent without a hostname. - Added a one-shot mode to nullmailer-send, triggered by setting the pausetime to zero. Development of this version has been sponsored by FutureQuest, Inc. ossi@FutureQuest.net http://www.FutureQuest.net/ ------------------------------------------------------------------------------- Changes in version 1.00RC6 - Protocol modules now report all failure and success messages. - Removed the whole gethostname/getdomainname mess and replaced it with code to read the hostname from the "me" control file, and the default domain name from the "defaultdomain" control file. - Fixed a bug that would cause lines starting with a period to have that period stripped when it was sent via SMTP. - Added some missing includes to fix compilation failures in various sources. - Fixed a bug in the setenv function in sendmail.cc. NOTE! Make sure to set up the new "me" control file before using this version. The RPM install does this automatically. Development of this version has been sponsored by FutureQuest, Inc. ossi@FutureQuest.net http://www.FutureQuest.net/ ------------------------------------------------------------------------------- Changes in version 1.00RC5 - Fixed getdomainname prototype in lib/hostname.cc. - Return proper error codes for permanent and temporary SMTP failures. - Adapted to use supervise-scripts 3. ------------------------------------------------------------------------------- Changes in version 1.00RC4 - Fixed the reversed logic in the named pipe bug check. - Updated the HOWTO notes. - Fixed a compile problem on Solaris (and possibly other systems). - Fixed address parsing to strip a trailing period in the domain name. ------------------------------------------------------------------------------- Changes in version 1.00RC3 - Treat the first non-header line in a message as the first line of the body, even if it isn't preceded by a blank line. - Fixed another host name bug causing compilation failures on systems without "domainname" in struct utsname. - Fixed some typos that would prevent proper TCP connections on many systems. - Handle systems that require both a reader and a writer on a named pipe to do a proper select. - Added a man page for the sendmail emulator, and an overall man page. - Fixed a bug in the fdbuf library that was causing data loss or damage. ------------------------------------------------------------------------------- Changes in version 1.00RC2 - Fixed a bug in the way host names are determined. This should fix problems with nullmailer-queue reporting an invalid envelope. - Fixed a portability bug in the mergelib.sh script. - Fixed a potential bug in the SMTP protocol response handling. ------------------------------------------------------------------------------- Changes in version 1.00RC1 - Imported latest fdbuf, mystring, and cli libraries. - Fixed a bug in the timezone handling on systems with tm_gmtoff. ------------------------------------------------------------------------------- Changes in version 0.40 - Fixed some compile problems to allow nullmailer to compile on FreeBSD. - Fixed a bug in the sendmail emulator to allow it to set the sender name and address from the command line properly. - Modified the CLI library to behave more like the standard getopt library. - Made a change to protocol between nullmailer-send and protocol modules. nullmailer-send now opens up the message files and passes them to the protocol module as FD 0 instead of passing a filename. - Added some notes to the nullmailer-send manual page explaining how messages are delivered. ------------------------------------------------------------------------------- Changes in version 0.39 - Fixed problems in nullmailer-send in parsing the "remotes" file. It previously did not handle comment lines or tabs or multiple white space. - Made nullmailer-inject stricter about header lines to ensure that they comply with RFC822. - Several minor updates to the libraries to bring them up to date. ------------------------------------------------------------------------------- Changes in version 0.38 - Fixed bug in nullmailer-send that caused it to refuse to send mail when the optional "pausetime" configuration file did not exist. ------------------------------------------------------------------------------- Changes in version 0.37 - Fixed bug in address parser that caused strings like "" (without a leading comment) to fail. - Make nullmailer-send rescan the mail queue each time it wakes up, rather than only if the timestamp changes to avoids race conditions. - Make nullmailer-send reload its config files ("remotes" and "pausetime" each time it scans the queue). - Fixed top-level install-root target to run chmod/chown on the right path to nullmailer-queue. - Added a configure test for libinet, libsocket, and libxnet libraries for systems that have their networking code separate from the main C library (such as Solaris). ------------------------------------------------------------------------------- Changes in version 0.36 - Imported generic CLI library, to avoid the use of getopt on systems that don't have one. ------------------------------------------------------------------------------- Changes in version 0.35 - Protocols now take a "-p #" to specify the port number to connect to. - This option can be specified in the "remotes" file immediately after the protocol name. - nullmailer-send will now only rescan the mail queue if its timestamp has changed since the last scan. - Fixed an observed problem with empty domain names by using uname(2) instead of domainname and hostname. - The header address parsing code is rewritten to do a lexical tokenization before parsing. - Imported new fdbuf and mystring libraries - Updated init scripts and RPM spec to work with new daemontools 0.61 and supervise-scripts packages. ------------------------------------------------------------------------------- Changes in version 0.33 - fixed a bug in the "list" template class that caused nullmailer-send to only send out one message before sleeping - added a trivial address remapping facility to nullmailer-queue to allow local addresses to be redirected to a remote administrator address. ------------------------------------------------------------------------------- Changes in version 0.32 - the address parser now deals properly with quoted addresses ------------------------------------------------------------------------------- Changes in version 0.31 - added a HOWTO document - "make install-root" will now properly make nullmailer-queue setuid nullmail ------------------------------------------------------------------------------- Changes in version 0.30 - fixed bug in the I/O library that caused nullmailer-inject to return an error on messages with a single blank line following the header, even though the message was successfully sent to nullmailer-queue - in nullmailer-inject, the hostname of the sender is set from the defaulthost config file instead of hostname() (note that default is set from hostname() if the file does not exist) - fixed a bug in nullmailer-inject that would cause it to incorrectly clear its recipient list when using command-line recipients with a "resent" message ------------------------------------------------------------------------------- Changes in version 0.29 - included the testing framework (mostly empty) into the package - fixed some bugs in nullmailer-inject caused by incorrect offsets into the array of header fields - many bug fixes to the address parsing framework - nullmailer-inject now has an option to only print out the message instead of passing it on to nullmailer-queue - many sendmail flags are now ignored instead of causing errors in the sendmail front end to nullmailer-inject ------------------------------------------------------------------------------- Changes in version 0.28 - fixed the bugs in the spec and init script - nullmailer-inject now handles the "c" flag like qmail-inject - changed some error messages - the sendmail front end should have its header vs command-line recipients logic fixed now ------------------------------------------------------------------------------- Changes in version 0.27 - added a QMQP protocol module - fixed a missing NUL-termination when creating the Date header line - nullmailer-inject now parses the NULLMAILER_FLAGS -- see the man page ------------------------------------------------------------------------------- Changes in version 0.26 - nullmailer-queue and nullmailer-send now go into sbin instead of bin - bugfixes to the unique number generation routines - moved some files around internally ------------------------------------------------------------------------------- Changes in version 0.25 - nullmailer-send now reads a protocol name along with the remote host - nullmailer-inject now handles its command-line options properly, as well as reading and using defaulthost, defaultdomain, and idhost - fixed several bugs in the address parsing and date-generation code - revised the interface between nullmailer-send and the protocol modules to simplify the interface and nullmailer-send ------------------------------------------------------------------------------- Changes in version 0.22 - added simple sendmail front end ------------------------------------------------------------------------------- nullmailer-2.2/README000066400000000000000000000015531336020433200144010ustar00rootroot00000000000000nullmailer Simple relay-only mail transport agent Bruce Guenter Version 2.2 2018-10-12 This is nullmailer, a sendmail/qmail/etc replacement MTA for hosts which relay to a fixed set of smart relays. It is designed to be simple to configure, secure, and easily extendable. A mailing list has been set up to discuss this package. To subscribe, send an email to: nullmailer-subscribe@lists.untroubled.org A mailing list archive is available at: http://lists.untroubled.org/?list=nullmailer Development versions of nullmailer are available at GitHub: https://github.com/bruceg/nullmailer This package is Copyright (C) 2018 Bruce Guenter, and may be copied according to the GNU GENERAL PUBLIC LICENSE (GPL) Version 2 or a later version. A copy of this license is included with this package. This package comes with no warranty of any kind. nullmailer-2.2/TODO000066400000000000000000000016631336020433200142130ustar00rootroot00000000000000- Convert all proto modules to use timedout reads. - Add patterns to the remotes file, to allow messages to be delivered to different remotes based on the sender or recipient addresses - Remove "adminaddr" facility from -queue, and add a more general destination address rewriting facility to -inject: - Read a list containing "PATTERN:ADDRESS" lines. - PATTERN can be a literal "user@FQDN" or just "user", in which case it must be matched exactly (before qualification). - PATTERN can be "@FQDN" which matches any user. - Generate DDNs for messages older than a configurable time. - For version 3: three-state queueing? - Queue message partially (tmp -> holding) - Send to smarthost immediately - Remove from holding if sending succeeds - Complete queueing (holding -> queue) if sending is deferred - Return with an error if sending fails (permanently) - Move all from holding -> queue on startup of nullmailer-send nullmailer-2.2/acinclude.m4000066400000000000000000000070621336020433200157130ustar00rootroot00000000000000dnl TRY_CXX_FLAG(FLAG,[ACTION-IF-FOUND[,ACTION-IF-NOT-FOUND]]) AC_DEFUN([TRY_CXX_FLAG], [echo >conftest.cc if ${CXX-g++} ${CXXFLAGS} -c [$1] conftest.cc >/dev/null 2>&1; then ifelse([$2], , :, [rm -f conftest* $2]) else ifelse([$3], , :, [rm -f conftest* $3]) fi rm -f conftest*]) AC_DEFUN([CXX_NO_RTTI], [AC_CACHE_CHECK(whether ${CXX-g++} accepts -fno-rtti, local_cv_flag_NO_RTTI, TRY_CXX_FLAG(-fno-rtti, local_cv_flag_NO_RTTI=yes, local_cv_flag_NO_RTTI=no)) test "$local_cv_flag_NO_RTTI" = yes && CXXFLAGS="$CXXFLAGS -fno-rtti" ]) AC_DEFUN([CXX_NO_EXCEPTIONS], [AC_CACHE_CHECK(whether ${CXX-g++} accepts -fno-exceptions, local_cv_flag_NO_EXCEPTIONS, TRY_CXX_FLAG(-fno-exceptions, local_cv_flag_NO_EXCEPTIONS=yes, local_cv_flag_NO_EXCEPTIONS=no)) test "$local_cv_flag_NO_EXCEPTIONS" = yes && CXXFLAGS="$CXXFLAGS -fno-exceptions" ]) dnl TRY_STRUCT_TM_MEMBER(MEMBER,FLAGNAME) AC_DEFUN([TRY_STRUCT_TM_MEMBER], [ AC_CACHE_CHECK(whether struct tm contains [$1], [$2], AC_COMPILE_IFELSE([AC_LANG_SOURCE([ #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif int main() { struct tm* foo; foo->[$1]; } ])], [$2]=yes, [$2]=no)) ]) AC_DEFUN([TEST_STRUCT_TM],[ TRY_STRUCT_TM_MEMBER(tm_isdst, local_cv_flag_TM_HAS_ISDST) TRY_STRUCT_TM_MEMBER(__tm_isdst, local_cv_flag_TM_HAS___ISDST) if test "$local_cv_flag_TM_HAS_ISDST" = yes then AC_DEFINE(TM_HAS_ISDST,tm_isdst,[struct tm has tm_isdst member]) elif test "$local_cv_flag_TM_HAS___ISDST" = yes then AC_DEFINE(TM_HAS_ISDST,__tm_isdst,[struct tm has tm_isdst member]) fi TRY_STRUCT_TM_MEMBER(tm_gmtoff, local_cv_flag_TM_HAS_GMTOFF) TRY_STRUCT_TM_MEMBER(__tm_gmtoff, local_cv_flag_TM_HAS___GMTOFF) if test "$local_cv_flag_TM_HAS_GMTOFF" = yes then AC_DEFINE(TM_HAS_GMTOFF,tm_gmtoff,[struct tm has tm_gmtoff member]) elif test "$local_cv_flag_TM_HAS___GMTOFF" = yes then AC_DEFINE(TM_HAS_GMTOFF,__tm_gmtoff,[struct tm has tm_gmtoff member]) fi ]) dnl TRY_STRUCT_UTSNAME_MEMBER(MEMBER,FLAGNAME) AC_DEFUN([TRY_STRUCT_UTSNAME_MEMBER], [ AC_CACHE_CHECK(whether struct utsname contains [$1], [$2], AC_COMPILE_IFELSE([AC_LANG_SOURCE([ #include int main() { struct utsname* foo; foo->[$1]; } ])], [$2]=yes, [$2]=no)) ]) AC_DEFUN([TEST_STRUCT_UTSNAME],[ TRY_STRUCT_UTSNAME_MEMBER(domainname, local_cv_flag_UTSNAME_HAS_DOMAINNAME) TRY_STRUCT_UTSNAME_MEMBER(__domainname, local_cv_flag_UTSNAME_HAS___DOMAINNAME) if test "$local_cv_flag_UTSNAME_HAS_DOMAINNAME" = yes then AC_DEFINE(UTSNAME_HAS_DOMAINNAME,domainname,[struct utsname has domainname member]) elif test "$local_cv_flag_UTSNAME_HAS___DOMAINNAME" = yes then AC_DEFINE(UTSNAME_HAS_DOMAINNAME,__domainname,[struct utsname has domainname member]) fi ]) AC_DEFUN([CHECK_NAMED_PIPE_BUG], [ AC_CACHE_CHECK(whether named pipes are buggy, local_cv_flag_NAMEDPIPEBUG, cat >conftest.c < #include #include #include int main(int argc, char** argv) { struct timeval tv; fd_set rfds; int fd = open(*(argv+1), O_RDONLY | O_NONBLOCK); FD_ZERO(&rfds); FD_SET(fd,&rfds); tv.tv_sec = tv.tv_usec = 0; return (select(fd+1, &rfds, 0, 0,&tv) > 0) ? 0 : 1; } EOF if ! ${CC} ${CFLAGS} conftest.c -o conftest 2>/dev/null then echo Compile failed exit 1 fi mkfifo conftest.pipe if ./conftest conftest.pipe then AC_DEFINE(NAMEDPIPEBUG, 1, [Named pipes have read/write bug]) local_cv_flag_NAMEDPIPEBUG=yes else local_cv_flag_NAMEDPIPEBUG=no fi rm -f conftest*) ]) nullmailer-2.2/autogen.sh000066400000000000000000000003401336020433200155100ustar00rootroot00000000000000#!/bin/sh aclocal -I . autoheader # automake needs a file named "ChangeLog" if ! test -f "ChangeLog"; then echo "INFO: creating phony ChangeLog for automake to succeed"; touch "ChangeLog"; fi automake --add-missing autoconf nullmailer-2.2/configure.ac000066400000000000000000000045251336020433200160110ustar00rootroot00000000000000AC_INIT([nullmailer], [2.2]) AC_LANG([C++]) AC_CONFIG_SRCDIR([lib/defines.h]) AM_INIT_AUTOMAKE AM_CONFIG_HEADER(config.h) AC_PROG_MAKE_SET dnl Checks for programs. AC_PROG_CC AC_PROG_CXX CXX_NO_RTTI CXX_NO_EXCEPTIONS AC_SYS_LARGEFILE CFLAGS="$CFLAGS -W -Wall" CXXFLAGS="$CXXFLAGS -W -Wall" AC_PROG_INSTALL AC_PROG_RANLIB AC_PATH_PROG(STRIP, strip) AC_PATH_PROG(RM, rm) AC_PATH_PROG(MKDIR, mkdir) dnl Checks for libraries. AC_CHECK_LIB(xnet, socket) AC_CHECK_LIB(inet, socket) AC_CHECK_LIB(socket, socket) dnl Checks for header files. AC_HEADER_DIRENT AC_HEADER_STDC AC_HEADER_SYS_WAIT AC_HEADER_TIME dnl AC_CHECK_HEADERS(fcntl.h shadow.h crypt.h) AC_CHECK_HEADERS(sys/time.h unistd.h) dnl Checks for typedefs, structures, and compiler characteristics. dnl AC_TYPE_UID_T dnl AC_TYPE_SIGNAL dnl AC_C_INLINE dnl AC_TYPE_PID_T AC_TYPE_SIZE_T TEST_STRUCT_TM TEST_STRUCT_UTSNAME CHECK_NAMED_PIPE_BUG dnl Checks for library functions. dnl AC_CHECK_FUNCS(gettimeofday mkdir putenv rmdir socket) AC_CHECK_FUNCS(setenv srandom) AC_MSG_CHECKING(for getaddrinfo) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #include #include #include #include ]], [[ getaddrinfo(NULL, NULL, NULL, NULL) ]])], [has_getaddrinfo=yes], [has_getaddrinfo=no]) if test "$has_getaddrinfo" = yes; then AC_MSG_RESULT(yes) AC_DEFINE(HAVE_GETADDRINFO,,[getaddrinfo code enabled]) else AC_MSG_RESULT(no) AC_MSG_RESULT(disabled: getaddrinfo missing) fi AC_SUBST(HAVE_GETADDRINFO) AC_DEFINE(BUFSIZE, 4096, [Generic buffer size]) AM_CONDITIONAL(FDBUF_NO_MYSTRING, false) AC_ARG_ENABLE(tls, [ --enable-tls Enable SSL/TLS support], [case "${enableval}" in yes) tls=true; AC_DEFINE(HAVE_TLS, 1, [Build in support for TLS]) ;; no) tls=false ;; *) AC_MSG_ERROR(bad value ${enableval} for --enable-tls) ;; esac],[tls=false]) AM_CONDITIONAL(TLS, $tls) if $tls; then AC_CHECK_LIB(gnutls, gnutls_certificate_set_verify_function, [AC_DEFINE(HAVE_GNUTLS_SET_VERIFY_FUNCTION, 1, [libgnutls has gnutls_certificate_set_verify_function])]) AC_CHECK_LIB(gnutls, gnutls_priority_set_direct, [AC_DEFINE(HAVE_GNUTLS_PRIORITY_SET_DIRECT, 1, [libgnutls has gnutls_priority_set_direct])]) fi AC_CONFIG_FILES([Makefile doc/Makefile lib/Makefile lib/cli++/Makefile lib/fdbuf/Makefile lib/mystring/Makefile protocols/Makefile src/Makefile test/Makefile]) AC_OUTPUT nullmailer-2.2/doc/000077500000000000000000000000001336020433200142625ustar00rootroot00000000000000nullmailer-2.2/doc/DIAGRAM000066400000000000000000000012361336020433200152530ustar00rootroot00000000000000 sendmail --------------- mailq / \ / \ / \ nullmailer-inject nullmailer-smtpd \ / \ / \ / nullmailer-queue | | | QUEUE/tmp -> QUEUE/queue -> QUEUE/failed | | | nullmailer-send ---- nullmailer-dsn | | | BIN/PROTOCOL nullmailer-2.2/doc/Makefile.am000066400000000000000000000003021336020433200163110ustar00rootroot00000000000000# info_TEXINFOS = nullmailer.texi man_MANS = \ nullmailer-dsn.1 \ nullmailer-inject.1 \ sendmail.1 \ nullmailer.7 \ nullmailer-queue.8 \ nullmailer-send.8 EXTRA_DIST = DIAGRAM $(man_MANS) nullmailer-2.2/doc/mailq.1000066400000000000000000000006221336020433200154470ustar00rootroot00000000000000.TH mailq 1 .SH NAME mailq \- List the contents of the .B nullmailer queue. .SH SYNOPSIS .B mailq .SH DESCRIPTION This program loads the contents of the .B nullmailer queue and presents a short summary of its contents. .SH OPTIONS None. .SH RETURN VALUE Exits 0 if it was successful, otherwise it prints a diagnostic message to standard error and exits 1. .SH SEE ALSO nullmailer-queue(8), sendmail(1) nullmailer-2.2/doc/nullmailer-dsn.1000066400000000000000000000066141336020433200173010ustar00rootroot00000000000000.TH nullmailer-dsn 1 .SH NAME nullmailer-dsn \- Reformat a queued message into a delivery status notification (DSN) .SH SYNOPSIS .B nullmailer-dsn [ .B options ] .I status-code < .I message .SH DESCRIPTION This program reads a nullmailer queue message from standard input, transforms it into a delivery status notification, and writes the result to standard output. The output is formatted to be fed into .BR nullmailer-queue . .P All times in the options are expressed as seconds since midnight GMT January 1, 1970 (the UNIX epoch). .SH OPTIONS .TP .BI \-\-diagnostic\-code= STRING Sets the actual diagnostic code issued by the mail transport. Must be in the form of "\fItype\fR; \fItext\fR", where .I type would typically be the protocol name, such as .BR SMTP . This adds a .B Diagnostic-Code header for each recipient. .TP .BI \-\-envelope\-id= STRING Original envelope ID. Setting this adds a .B Original-Envelope-Id header to the delivery status section. .TP .BI \-\-last\-attempt= TIME Sets the time of the last attempted delivery. Defaults to the access time of the input message. This sets the date in the .B Last-Attempt-Date header for each recipient. .TP .BI \-\-max\-lines= COUNT Sets the maximum number of lines of the original message to copy into the generated message. A value of zero copies only the original header. Negative values copy the whole message. Defaults to .I bouncelines below, or zero if that is empty. .TP .BI \-\-orig\-timestamp= TIME Sets the time of the original message. Defaults to the change time of the input message. This sets the date in the .B Arrival-Date header in the delivery status section. .TP .BI \-\-remote= STRING Sets the name of the remote MTA server. This adds a .B Remote-MTA header for each recipient. .TP .BI \-\-retry\-until= TIME Sets the time of the (future) final delivery attempt. No default. This adds a .B Will-Retry-Until header for each recipient. .SH RETURN VALUE Exits 0 if it was successful, otherwise it prints a diagnostic message to standard error and exits 1. .SH CONTROL FILES .TP .B adminaddr If .B doublebounceto is empty, double bounces are delivered to this address instead. If no address is configured, double bounces are not generated. .TP .B bouncelines Sets the maximum number of lines of the original message to copy. May be overridden by .B \-\-max\-lines as above. .TP .B bounceto The address to which all bounces should be sent. If it is empty, the original sender address is used. .TP .B defaultdomain The content of this file is appended to any host name that does not contain a period (except .BR localhost ), including .I defaulthost and .IR idhost . Defaults to the value of the .B me control file, if it exists, otherwise the literal name .BR defaultdomain . .TP .B defaulthost The content of this file is appended to any address that is missing a host name. Defaults to the value of the .I me control file, if it exists, otherwise the literal name .BR defaulthost . .TP .B doublebounceto If the original sender was empty (the original message was a delivery status or disposition notification), the double bounce is sent to the address in this file. .TP .B idhost The content of this file is used when building the message-id string for the message. Defaults to the canonicalized value of .IR defaulthost . .TP .B me The fully-qualified host name of the computer running nullmailer. Defaults to the literal name .BR me . .SH SEE ALSO nullmailer-queue(8), nullmailer-send(8) nullmailer-2.2/doc/nullmailer-inject.1000066400000000000000000000137721336020433200177740ustar00rootroot00000000000000.TH nullmailer-inject 1 .SH NAME nullmailer-inject \- Reformat and inject a message into the queue. .SH SYNOPSIS .B nullmailer-inject [\-a] [\-b] [\-e] [\-f sender] [\-h] .I [recipient [recipient ...]] .SH DESCRIPTION This program reads a email message from standard input, reformats its header to comply with RFC822, and sends the resulting message to the queue. .SS HEADER FIELDS The following lines are parsed for recipient addresses: .IR To , .IR Cc , .IR Bcc , .IR Apparently-To , .IR Resent-To , .IR Resent-Cc , and .IR Resent-Bcc . The following sender address lines are parsed and rewritten: .IR Sender , .IR From , .IR Reply-To , .IR Return-Path , .IR Return-Receipt-To , .IR Errors-To , .IR Resent-Sender , .IR Resent-From , and .IR Resent-Reply-To . If the .I Return-Path header field is present and contains a single address, its contents will be used to set the envelope sender address. If the message contains any of the following fields, it is treated as a resent message: .IR Resent-Sender , .IR Resent-From , .IR Resent-Reply-To , .IR Resent-To , .IR Resent-Cc , .IR Resent-Bcc , .IR Resent-Date , .IR Resent-Message-ID . If the message is resent, only the recipient fields prefixed with .I Resent- are examined for addresses. Any occurrences of .IR Bcc , .IR Resent-Bcc , .IR Return-Path , or .I Content-Length are discarded after they are parsed (if necessary). If the header lacks a .I Message-Id field, a unique string is generated and added to the message. If the header lacks a .I Date field, the current local date and time in RFC822 format is appended to the message. If the message has no .I To or .I Cc fields, the following line is appended to the message: .EX Cc: recipient list not shown: ; .EE .SS ADDRESS LISTS Address lists are expected to follow the syntax set out in RFC822. The following is a simplified explanation of the syntax. An address list is list of addresses separated by commas. An individual address may have one of the following three forms: .IR user@fqdn , .IR comment , or .IR phrase: address-list; . Any of the first two forms may be used within the address list of the third form. Any word containing special characters must be placed in double quotes and the special characters must be preceded with a backslash. Comments may be placed between addresses in parenthesis. All comments are ignored. Addresses lists are reformatted as they are parsed for ease of later re-parsing when the message reaches the destination(s). If an address is missing a fqdn, .B nullmailer-inject adds one. .SH OPTIONS .TP .I \-a Use only the command line arguments as recipient addresses. Ignore the header recipient lines. .TP .I \-b Use both the command line arguments and data from the message header as recipient addresses. .TP .I \-e Use either the command line arguments (if there are any) or data from the message header (if there are no arguments) as the recipient addresses. .TP .I \-f sender Set the envelope sender address to .IR sender . .TP .I \-h Use only data from the message header as the recipient addresses. .TP .I \-n Do not queue the message, but print the reformatted contents to standard output. .TP .I \-v Print out the envelope (sender and recipient addresses) preceding the message when printing the message to standard output. .SH RETURN VALUE Exits 0 if it was successful, otherwise it prints a diagnostic message to standard error and exits 1. .SH ENVIRONMENT The environment variable .BR NULLMAILER_FLAGS is parsed and the behavior of .B nullmailer-inject is modified if any of the following letters are present: .TP .B c Use "address (comment)" style in the generated .B From field instead of the default "comment
" style. .TP .B f Ignore and remove any .B From header lines and always insert a generated one. .TP .B i Ignore and remove any .B Message-Id header lines. .TP .B s Ignore and remove any .B Return-Path header lines. .TP .B t Insert a .BR To line containing a list of the recipients if the header does not contain either a .B To or a .B Cc field. If the message is determined to be a resent message (see above), a .B Resent-To field is added if the header does not contain either a .BR Resent-To or a .BR Resent-Cc field. .PP The user name is set by .BR NULLMAILER_USER , .BR MAILUSER , .BR USER , or .BR LOGNAME , whichever comes first. If none of the above are set the name is taken from the password file, or set to .I unknown if that fails. The host name is set by the canonicalized value of .BR NULLMAILER_HOST , .BR MAILHOST , or .BR HOSTNAME , whichever comes first, or the .I defaulthost config file if none of the above are set (see below). The full name of the user is set by .BR NULLMAILER_NAME , .BR MAILNAME , .BR NAME , or the user name above, whichever comes first. The user and host name of the envelope sender default to the user and host name set above, but may be overridden by .BR NULLMAILER_SUSER and .BR NULLMAILER_SHOST . If .BR NULLMAILER_QUEUE is set, the program named is used in place of .B nullmailer-queue to queue the formatted message. .SH CONTROL FILES When reading the following files, a single line is read and stripped of all leading and trailing whitespace characters. .TP .B defaultdomain The content of this file is appended to any host name that does not contain a period (except .BR localhost ), including .I defaulthost and .IR idhost . Defaults to the value of the .B me control file, if it exists, otherwise the literal name .BR defaultdomain . .TP .B defaulthost The content of this file is appended to any address that is missing a host name. Defaults to the value of the .I me control file, if it exists, otherwise the literal name .BR defaulthost . .TP .B idhost The content of this file is used when building the message-id string for the message. Defaults to the canonicalized value of .IR defaulthost . .TP .B me The fully-qualified host name of the computer running nullmailer. Defaults to the literal name .BR me . .SH SEE ALSO nullmailer-queue(8) .SH NOTES This document glosses over very many details of how address parsing and rewriting actually works (among other things). nullmailer-2.2/doc/nullmailer-queue.8000066400000000000000000000035131336020433200176430ustar00rootroot00000000000000.TH nullmailer-queue 8 .SH NAME nullmailer-queue \- insert mail messages into the queue .SH SYNOPSIS .B nullmailer-queue .SH DESCRIPTION This program reads a formatted mail message from standard input and safely injects it into the outgoing mail queue. .PP The data sent into standard input is expected to have the following format: one line containing the envelope sender, one or more lines containing the recipients, a single blank line, and then the contents of the message exactly as it is to be transmitted to the destination. All lines are terminated with a single line-feed character. All addresses must contain a fully-qualified domain name. .PP .SH RETURN VALUE Exits 0 if it successfully queues the message. If it failed to queue the message, it exits 1 and prints an error message to stdandard output. .SH CONTROL FILES .TP .B adminaddr If this file is not empty, all recipients to users at either "localhost" (the literal string) or the canonical host name (from the .I me control file) are remapped to this address. This is provided to allow local daemons to be able to send email to "somebody@localhost" and have it go somewhere sensible instead of being bounced by your relay host. To send to multiple addresses, put them all on one line separated by a comma. .TP .B allmailfrom If this file is not empty, its contents will override the envelope sender on all messages. .SH OTHER FILES .TP .B /var/spool/nullmailer/queue The directory into which the completed messages are moved. .TP .B /var/spool/nullmailer/tmp The directory in which messages are formed temporarily. .TP .B /var/spool/nullmailer/trigger A pipe used to trigger .BR nullmailer-send to immediately start sending the message from the queue. .SH SEE ALSO nullmailer-inject(1), nullmailer-send(8) .SH LIMITATIONS This program should enforce system-wide configurable message length limits. nullmailer-2.2/doc/nullmailer-send.8000066400000000000000000000143651336020433200174570ustar00rootroot00000000000000.TH nullmailer-send 8 .SH NAME nullmailer-send \- Send queued messages .SH SYNOPSIS .B nullmailer-send .SH DESCRIPTION This program is responsible for coordinating the transmission of messages that have been queued by .BR nullmailer-queue . It uses a variety of protocol modules to deliver the messages from the queue to remote "smart" servers. .P When the program starts, the queue is scanned to build a list of messages to send. The queue is rescanned when either the trigger is pulled, or after .B pausetime seconds have elapsed after the last failed delivery. When there are no messages in the queue, nullmailer does no rescanning until the trigger is pulled. Pulling the trigger consists of opening up the trigger named pipe and writing a single byte to it, which causes this program to be awakened (if it's not already processing the queue). This procedure is done by .B nullmailer-queue to ensure that messages are delivered immediately. You can start delivery by hand from the command line like this: .EX echo 1 > trigger .EE .P Delivery of messages consists of reading the list of remote servers and then trying to deliver the messages to these servers as follows. For each remote in the list, the named protocol handler is executed once for each message remaining in the queue. If the protocol handler succeeds, the message is removed from the queue and processing continues with the next message. If the protocol handler reports a permanent failure or the message has been in the queue longer than .IR queuelifetime , the message is moved into the .B failed queue and a bounce message is generated with .BR nullmailer-dsn . If any messages remain in the queue, processing of the remaining messages continues with the next remote. When all the remotes have been tried, .B nullmailer-send sleeps for a number of seconds specified by .B pausetime before retrying sending the contents of the queue. .SH CONTROL FILES All the control files are reread each time the queue is run. .TP .B helohost Sets the environment variable .B $HELOHOST which is used by the SMTP protocol module to set the parameter given to the .I HELO command. Defaults to the value of the .B me configuration file. .TP .B maxpause The maximum time to pause between successive queue runs, in seconds. Defaults to 24 hours .RB ( 86400 ). .TP .B pausetime The minimum time to pause between successive queue runs when there are messages in the queue, in seconds. Defaults to 1 minute .RB ( 60 ). Each time this timeout is reached, the timeout is doubled to a maximum of .BR maxpause . After new messages are injected, the timeout is reset. If this is set to .BR 0 , nullmailer-send will exit immediately after going through the queue once (one-shot mode). .TP .B queuelifetime The maximum time a message is allowed to live in the queue before being considered permanently failed, in seconds. Defaults to 7 days .RB ( 604800 ). .TP .B remotes This file contains a list of remote servers to which to send each message. Each line of this file contains a remote host name or address followed by an optional protocol string, separated by white space. The protocol name defaults to .BR smtp , and may be followed by additional options for that module. See the "PROTOCOL OPTIONS" section for a list of the available options. The options may optionally be prefixed by .I -- but this is not required. The line is parsed according to standard shell quoting rules. For example, to connect to port 2525 on your SMTP smart host, which also requires SMTP authentication, and initiate TLS with STARTTLS, use: .EX smarthost.dom smtp port=2525 starttls user=user pass='my pass phrase' .EE Blank lines and lines starting with a pound (\fI#\fR) are ignored. .TP .B sendtimeout The time to wait for a remote module listed above to complete sending a message before killing it and trying again, in seconds. Defaults to 1 hour .RB ( 3600 ). If this is set to .BR 0 , .B nullmailer-send will wait forever for messages to complete sending. .SH "PROTOCOL OPTIONS" .TP .B port=\fIPORT Set an alternate port number to connect to on the remote host. For example, SMTP may use .B port=587 for the alternate SMTP "submission" port. .TP .B user=\fIUSERNAME Set the SMTP authentication user name. .TP .B pass=\fIPASSWORD Set the SMTP authentication password. .TP .BI source= HOSTNAME Set the source address for connections to the remote host. .TP .B auth-login Force SMTP "AUTH LOGIN" mode instead of auto-detecting. .TP .B tls Connect using TLS. This will automatically switch the default port to .BR 465 . .TP .B ssl Alias for the .B tls option for compatibility. .TP .B starttls Use the .B STARTTLS command to initiate a TLS session. .TP .B x509certfile=\fIFILENAME Set the filename for a TLS client certificate to send to the server. .TP .B x509keyfile=\fIFILENAME Set the filename for the private key for a TLS client certificate. Defaults to the same file name as .BR x509certfile . .TP .B x509cafile=\fIFILENAME Set the TLS certificate authority trust filename. Defaults to .BR /etc/ssl/certs/ca-certificates.crt . .TP .B x509crlfile=\fIFILENAME Set the TLS certificate revocation list filename. .TP .B x509fmtder Specify that TLS X.509 files above are in DER format instead of PEM. .TP .B insecure Don't abort a TLS connection if the server certificate fails validation. Use this only if you know the server uses an invalid certificate. .TP .B tls-anon-auth Use TLS anonymous authentication - replacing certificate authentication. This means no external certificates or passwords are needed to set up the connection. With this option your connection is vulnerable to man-in-the-middle (active or redirection) attacks. However, the data are integrity protected and encrypted from passive eavesdroppers. This option must be used with the insecure option - to acknowledge that you know what you are doing. .SH FILES .TP .B /var/spool/nullmailer/failed The failed message queue. .TP .B /var/spool/nullmailer/queue The outgoing message queue. .TP .B /var/spool/nullmailer/trigger A trigger file to cause immediate delivery. .TP .B /usr/local/etc/nullmailer The configuration directory. .TP .B /usr/local/libexec/nullmailer The protocol program directory. .SH SEE ALSO nullmailer-dsn(1), nullmailer-inject(1), nullmailer-queue(8), mailq(1) http://www.postfix.org/TLS_README.html on how to setup a certificate-less Postfix SMTP server nullmailer-2.2/doc/nullmailer-smtpd.1000066400000000000000000000013421336020433200176350ustar00rootroot00000000000000.TH nullmailer-smtpd 1 .SH NAME nullmailer-smtpd \- Simple SMTP agent for emulating sendmail -bs mode. .SH SYNOPSIS .B nullmailer-smtpd .SH DESCRIPTION This program emulates a simple SMTP interface that queues messages directly to .BR nullmailer-queue . It is invoked by the .BR sendmail wrapper when the .B \-bs option is used. .SH OPTIONS None. .SH RETURN VALUE Exits 0 if it was successful, otherwise it prints a diagnostic message to standard error and exits 1. .SH WARNINGS This is not a general purpose SMTP server. In particular, it cannot handle authentication, and it only does minimal validation of email addresses. Do not use this as a SMTP server on untrusted network interfaces. .SH SEE ALSO sendmail(1), nullmailer-queue(8) nullmailer-2.2/doc/nullmailer.7000066400000000000000000000021151336020433200165150ustar00rootroot00000000000000.TH nullmailer 7 .SH NAME nullmailer \- Overview of nullmailer documentation .SH INTRODUCTION .B nullmailer is a simple and secure relay-only mail transport agent. .P Documentation on how messages are reformatted and injected into the queue can be found in .BR nullmailer-inject (1). Documentation on how messages are actually inserted into the queue can be found in .BR nullmailer-queue (8). The process of sending queued messages is described in .BR nullmailer-send (8). .P The following table lists all the control files used by .BR nullmailer . .P .RS .nf .ta 5c control file used by .I adminaddr \fBnullmailer-dsn\fR, \fBnullmailer-queue .I allmailfrom \fBnullmailer-queue .I defaultdomain \fBnullmailer-dsn\fR, \fBnullmailer-inject .I defaulthost \fBnullmailer-dsn\fR, \fBnullmailer-inject .I doublebounceto \fBnullmailer-dsn .I helohost \fBnullmailer-send .I idhost \fBnullmailer-dsn\fR, \fBnullmailer-inject .I maxpause \fBnullmailer-send .I me \fBnullmailer-dsn\fR, \fBnullmailer-inject .I pausetime \fBnullmailer-send .I remotes \fBnullmailer-send .I sendtimeout \fBnullmailer-send .fi .RE nullmailer-2.2/doc/sendmail.1000066400000000000000000000027011336020433200161400ustar00rootroot00000000000000.TH sendmail 1 .SH NAME sendmail \- sendmail emulator interface for nullmailer .SH SYNOPSIS .B sendmail [ .B flags ] [ .I recipients ] < .I message .SH DESCRIPTION This program is a front end program for .B nullmailer-inject and .BR nullmailer-smtpd . It is used by programs that expect a .I sendmail interface for sending email. After parsing the command-line arguments, this program executes either .B nullmailer-inject or .B nullmailer-smtpd depending on the presence of the .I \-bs option on the command-line. See the documentation for .B nullmailer-inject for details on how messages are reformatted and queued. .SH OPTIONS .TP .B \-B TYPE .TP .B \-C FILE .TP .B \-d DEBUG .TP .B \-h COUNT .TP .B \-i .TP .B \-L TAG .TP .B \-N DSN .TP .B \-n .TP .B \-O OPTION .TP .B \-o OPTION .TP .B \-p PROTOCOL .TP .B \-q TIME .TP .B \-R RETURN .TP .B \-U .TP .B \-V ENVID .TP .B \-v .TP .B \-X LOGFILE Ignored for compatibility .TP .B \-bm Read mail from standard input (default). .TP .B \-bp List information about mail queue. This executes .BR mailq . .TP .B \-bs Use the SMTP protocol on standard input and standard output. This executes .BR nullmailer-smtpd . .TP .B \-F ADDRESS Sets the full name of the sender. .TP .B \-f ADDRESS Sets the envelope sender address. .TP .B \-r ADDRESS An alternate and obsolete form of the \-f flag. .TP .B \-t Read message for recipients and ignore the command-line arguments. .SH SEE ALSO mailq(1), nullmailer-inject(1), nullmailer-smtpd(1) nullmailer-2.2/lib/000077500000000000000000000000001336020433200142635ustar00rootroot00000000000000nullmailer-2.2/lib/Makefile.am000066400000000000000000000022551336020433200163230ustar00rootroot00000000000000SUBDIRS = cli++ fdbuf mystring noinst_LIBRARIES = libmisc.a libnullmailer.a noinst_HEADERS = list.h EXTRA_DIST = make_defines.sh listtest.cc mergelib.sh CLEANFILES = defines.cc libmisc_a_SOURCES = \ ac/dirent.h ac/time.h ac/wait.h \ address.h address.cc \ argparse.h argparse.cc \ autoclose.h \ base64.h base64.cc \ canonicalize.h canonicalize.cc \ configio.h config_path.cc \ config_read.cc config_readlist.cc config_readint.cc config_syserr.cc \ connect.h tcpconnect.cc \ defines.h \ errcodes.h errcodes.cc \ hostname.h hostname.cc \ itoa.h itoa.cc \ makefield.cc makefield.h \ netstring.h netstring.cc \ forkexec.cc forkexec.h \ selfpipe.cc selfpipe.h \ setenv.cc setenv.h nodist_libmisc_a_SOURCES = defines.cc libnullmailer_a_SOURCES = libnullmailer.a: mergelib.sh libmisc.a fdbuf/libfdbuf.a \ mystring/libmystring.a Makefile $(RM) -f libnullmailer.a sh $(srcdir)/mergelib.sh libnullmailer.a \ libmisc.a \ fdbuf/libfdbuf.a \ mystring/libmystring.a defines.cc: Makefile make_defines.sh @echo Creating defines.cc @sh $(srcdir)/make_defines.sh \ @localstatedir@/spool/nullmailer \ @sysconfdir@/nullmailer \ @libexecdir@/nullmailer \ @bindir@ \ @sbindir@ nullmailer-2.2/lib/ac/000077500000000000000000000000001336020433200146465ustar00rootroot00000000000000nullmailer-2.2/lib/ac/dirent.h000066400000000000000000000005231336020433200163040ustar00rootroot00000000000000#include #if HAVE_DIRENT_H # include # define NAMLEN(dirent) strlen((dirent)->d_name) #else # define dirent direct # define NAMLEN(dirent) (dirent)->d_namlen # if HAVE_SYS_NDIR_H # include # endif # if HAVE_SYS_DIR_H # include # endif # if HAVE_NDIR_H # include # endif #endif nullmailer-2.2/lib/ac/time.h000066400000000000000000000002361336020433200157560ustar00rootroot00000000000000#if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif nullmailer-2.2/lib/ac/wait.h000066400000000000000000000003571336020433200157700ustar00rootroot00000000000000#include #if HAVE_SYS_WAIT_H # include #endif #ifndef WEXITSTATUS # define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) #endif #ifndef WIFEXITED # define WIFEXITED(stat_val) (((stat_val) & 255) == 0) #endif nullmailer-2.2/lib/address-old.cc000066400000000000000000000306401336020433200167760ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include "config.h" #include #include "canonicalize.h" #include "mystring.h" struct result { const char* next; mystring str; mystring comment; mystring addr; result(const result&); result(const char* = 0); result(const char*, const mystring&, const mystring&, const mystring&); bool operator!() const { return !next; } operator bool() const { return next; } }; result::result(const char* n) : next(n) { } result::result(const char* n, const mystring& s, const mystring& c, const mystring& l) : next(n), str(s), comment(c), addr(l) { } result::result(const result& r) : next(r.next), str(r.str), comment(r.comment), addr(r.addr) { } #ifndef TRACE #define ENTER(R) #define FAIL(MSG) return result() #define RETURNR(R) return R #define RETURN(N,S,C,L) return result(N,S,C,L); #else #include "fdbuf.h" static const char indentstr[] = " "; static const char* indent = indentstr + sizeof indentstr - 1; #define ENTER(R) do{ fout.write(indent--); fout.write(__FUNCTION__); fout.write(": "); fout.write(ptr); fout.write(": "); fout.writeln(R); }while(0) #define FAIL(MSG) do{ fout.write(++indent); fout.write(__FUNCTION__); fout.write(": failed: "); fout.writeln(MSG); return result(); }while(0) #define RETURNR(R) do{ fout.write(++indent); fout.write(__FUNCTION__); fout.write(": succeeded str="); fout.write(R.str); fout.write(" comment="); fout.writeln(R.comment); return (R); }while(0) #define RETURN(N,S,C,L) do{ result r(N,S,C,L); RETURNR(r); }while(0) #endif #define RULE(X) static result match_##X(const char* ptr) #define SKIPSPACE do{ while(*ptr && isspace(*ptr)) ++ptr; }while(0) #define SKIPDELIM(N) do{ ptr = skipdelim(ptr, N); }while(0) #define MATCHCHAR(X) do{ if(*ptr != X) FAIL("*ptr is not " #X); else ++ptr; }while(0) #define OR_RULE(ALT1,ALT2) { result r=match_##ALT1(ptr); if(r) RETURNR(r); }{ result r=match_##ALT2(ptr); if(r) RETURNR(r); } FAIL("did not match " #ALT1 " OR " #ALT2); #define COLON ':' #define SEMICOLON ';' #define LABRACKET '<' #define RABRACKET '>' #define AT '@' #define ESCAPE '\\' #define PERIOD '.' #define LSQBRACKET '[' #define RSQBRACKET ']' #define QUOTE '"' #define CR '\n' #define LPAREN '(' #define RPAREN ')' #define COMMA ',' static bool isspecial(char c) { switch(c) { case LPAREN: case RPAREN: case LABRACKET: case RABRACKET: case LSQBRACKET: case RSQBRACKET: case AT: case COMMA: case SEMICOLON: case COLON: case ESCAPE: case QUOTE: case PERIOD: return true; default: return false; } } static bool isctl(char c) { return (c >= 0 && c <= 31) || (c == 127); } static bool isqtext(char c) { return c && c != QUOTE && c != ESCAPE && c != CR; } static bool isdtext(char c) { return c && c != LSQBRACKET && c != RSQBRACKET && c != ESCAPE && c != CR; } // quoted-pair = ESCAPE CHAR static bool isqpair(const char* ptr) { return *ptr && *ptr == ESCAPE && *(ptr+1); } static bool isctext(char c) { return c && c != LPAREN && c != RPAREN && c != ESCAPE && c != CR; } static bool isrtext(char c) { return c && c != LABRACKET && c != RABRACKET && c != ESCAPE && c != CR; } static mystring quote(const mystring& in) { unsigned length = in.length(); // The result will never be more than double the length of the input plus 2 char out[length*2 + 2 + 1]; char* ptrout = out; const char* ptrin = in.c_str(); bool quoted = false; for(; length; ++ptrin, ++ptrout, --length) { if(isspecial(*ptrin)) { *ptrout++ = ESCAPE; quoted = true; } *ptrout = *ptrin; } *ptrout = 0; if(quoted) return mystringjoin("\"") + out + "\""; else return in; } static mystring unquote(const mystring& in) { unsigned length = in.length(); // The result will never be more than the length of the input char out[length+1]; bool modified = false; const char* ptrin = in.c_str(); char* ptrout = out; if(in[0] == QUOTE && in[length-1] == QUOTE) { length -= 2; ptrin++; modified = true; } for(; length; ++ptrin, ++ptrout, --length) { if(isqpair(ptrin)) { ++ptrin; --length; modified = true; } *ptrout = *ptrin; } *ptrout = 0; if(modified) return out; else return in; } RULE(comment) { ENTER("LPAREN *(ctext / quoted-pair / comment) RPAREN"); SKIPSPACE; const char* start = ptr; MATCHCHAR(LPAREN); for(;;) { if(isctext(*ptr)) ++ptr; else if(isqpair(ptr)) ptr += 2; else { result r = match_comment(ptr); if(r) ptr = r.next; else break; } } MATCHCHAR(RPAREN); char tmp[ptr-start+2]; tmp[0] = ' '; memcpy(tmp+1, start, ptr-start); tmp[ptr-start+1] = 0; RETURN(ptr, tmp, "", ""); } static const char* skipdelim(const char* ptr, mystring& comment) { for(;;) { while(*ptr && isspace(*ptr)) ++ptr; if(*ptr != LPAREN) break; result r = match_comment(ptr); if(r) { ptr = r.next; comment += r.str; } else break; } return ptr; } RULE(atom) { ENTER("1*"); mystring comment; SKIPDELIM(comment); const char* start = ptr; while(*ptr && !isspace(*ptr) && !isspecial(*ptr) && !isctl(*ptr)) ++ptr; if(start == ptr) FAIL("no CHARs matched"); mystring atom(start, ptr-start); RETURN(ptr, atom, comment, atom); } RULE(quoted_string) { ENTER("QUOTE *(qtext/quoted-pair) QUOTE"); mystring comment; SKIPDELIM(comment); const char* start = ptr; MATCHCHAR(QUOTE); for(; *ptr; ++ptr) { if(isqtext(*ptr)) continue; else if(isqpair(ptr)) ++ptr; else break; } MATCHCHAR(QUOTE); mystring text(start, ptr-start); mystring addr(unquote(text)); text = quote(addr); RETURN(ptr, text, comment, addr); } RULE(domain_literal) { ENTER("LSQBRACKET *(dtext/quoted-pair) RSQBRACKET"); SKIPSPACE; MATCHCHAR(LSQBRACKET); SKIPSPACE; const char* start = ptr; mystring str; for(; *ptr; ++ptr) { if(isdtext(*ptr)) continue; else if(isqpair(ptr)) ++ptr; else break; } SKIPSPACE; MATCHCHAR(RSQBRACKET); mystring text(start, ptr-start); RETURN(ptr, text, "", text); } RULE(sub_domain) { // NOTE: domain-ref = atom ENTER("domain-ref / domain-literal"); OR_RULE(atom, domain_literal); } RULE(domain) { ENTER("sub-domain *(PERIOD sub-domain)"); result r = match_sub_domain(ptr); if(!r) FAIL("did not match sub-domain"); mystring comment; for(;;) { ptr = r.next; SKIPDELIM(comment); r.next = ptr; if(*ptr++ != PERIOD) break; result r1 = match_sub_domain(ptr); if(!r1) break; r.next = r1.next; r.str += "."; r.str += r1.str; comment += r1.comment; r.addr += "."; r.addr += r1.addr; } r.comment += comment; RETURNR(r); } RULE(route) { ENTER("1#(AT domain) COLON"); unsigned count=0; mystring str; mystring comment; for(;;) { if(*ptr != AT) break; ++ptr; result r = match_domain(ptr); if(!r) FAIL("did not match domain"); str += "@"; str += r.str; comment += r.comment; ++count; ptr = r.next; } if(count == 0) FAIL("matched no domains"); SKIPDELIM(comment); MATCHCHAR(COLON); RETURN(ptr, str, comment, ""); } RULE(word) { ENTER("atom / quoted-string"); OR_RULE(atom, quoted_string); } RULE(local_part) { ENTER("word *(PERIOD word)"); result r = match_word(ptr); if(!r) FAIL("did not match word"); for(;;) { ptr = r.next; SKIPDELIM(r.comment); r.next = ptr; if(*ptr++ != PERIOD) break; result r1 = match_word(ptr); if(!r1) break; r.next = r1.next; r.str += "."; r.str += r1.str; r.comment += r1.comment; r.addr += "."; r.addr += r1.addr; } RETURNR(r); } RULE(addr_spec) { ENTER("local-part *( AT domain )"); result r = match_local_part(ptr); if(!r) FAIL("did not match local-part"); mystring domain; for(;;) { ptr = r.next; SKIPDELIM(r.comment); r.next = ptr; if(*ptr++ != AT) break; result r2 = match_domain(ptr); if(!r2) break; if(!!domain) { r.str += "@"; r.str += domain; r.addr += "@"; r.addr += domain; } domain = r2.addr; r.comment += r2.comment; r.next = r2.next; } canonicalize(domain); RETURN(r.next, r.str + "@" + domain, r.comment, r.addr + "@" + domain + "\n"); } RULE(route_addr) { ENTER("LABRACKET [route] addr-spec RABRACKET"); mystring comment; SKIPDELIM(comment); MATCHCHAR(LABRACKET); result r1 = match_route(ptr); if(r1) ptr = r1.next; comment += r1.comment; result r2 = match_addr_spec(ptr); if(!r2) FAIL("did not match addr-spec"); ptr = r2.next; comment += r2.comment; SKIPDELIM(comment); MATCHCHAR(RABRACKET); RETURN(ptr, "<" + r2.str + ">" + comment, "", r2.addr); } RULE(phrase) { ENTER("word *word"); result r1 = match_word(ptr); if(!r1) FAIL("did not match word"); for(;;) { result r2 = match_word(r1.next); if(!r2) break; r1.str += " "; r1.str += r2.str; r1.comment += r2.comment; r1.next = r2.next; } RETURNR(r1); } RULE(route_phrase) { ENTER("*(rtext/quoted-pair)"); SKIPSPACE; const char* start = ptr; for(;; ++ptr) { if(isrtext(*ptr)) continue; else if(isqpair(ptr)) ++ptr; else break; } RETURN(ptr, mystring(start, ptr-start), "", ""); } RULE(route_spec) { ENTER("route-phrase route-addr"); result r1 = match_route_phrase(ptr); if(!r1) FAIL("did not match route-phrase"); result r2 = match_route_addr(r1.next); if(!r2) FAIL("did not match route-addr"); r2.str = r1.str + r1.comment + " " + r2.str + r2.comment; RETURNR(r2); } RULE(mailbox) { ENTER("route-spec / addr-spec"); OR_RULE(route_spec, addr_spec); } RULE(mailboxes) { ENTER("mailbox *(*(COMMA) mailbox)"); result r1 = match_mailbox(ptr); if(!r1) FAIL("did not match mailbox"); r1.str += r1.comment; r1.comment = ""; for(;;) { ptr = r1.next; for(;;) { SKIPDELIM(r1.str); if(*ptr == COMMA) ++ptr; else break; } if(!*ptr) break; result r2 = match_mailbox(ptr); if(!r2) break; r1.next = r2.next; r1.str = r1.str + ", " + r2.str + r2.comment; r1.addr += r2.addr; } SKIPDELIM(r1.str); r1.next = ptr; RETURNR(r1); } RULE(group) { ENTER("phrase COLON [#mailboxes] SEMICOLON"); result r1 = match_phrase(ptr); if(!r1) FAIL("did not match phrase"); ptr = r1.next; SKIPSPACE; MATCHCHAR(COLON); result r2 = match_mailboxes(ptr); if(r2) ptr = r2.next; mystring comment; SKIPDELIM(comment); MATCHCHAR(SEMICOLON); RETURN(ptr, r1.str + ": " + r2.str + r2.comment + comment + ";", "", r2.addr); } RULE(address) { ENTER("group / mailbox"); OR_RULE(group, mailbox); } RULE(part) { ENTER("address / comment"); OR_RULE(address, comment); } RULE(addresses) { ENTER("part *(*(COMMA) part) EOF"); result r1 = match_part(ptr); if(!r1) FAIL("did not match part"); r1.str += r1.comment; r1.comment = ""; for(;;) { ptr = r1.next; for(;;) { SKIPDELIM(r1.str); if(*ptr == COMMA) ++ptr; else break; } if(!*ptr) break; result r2 = match_part(ptr); if(!r2) break; r1.next = r2.next; r1.str = r1.str + ", " + r2.str + r2.comment; r1.addr += r2.addr; } SKIPDELIM(r1.str); if(*ptr) FAIL("Rule ended before EOF"); RETURNR(r1); } bool parse_addresses(mystring& line, mystring& list) { result r = match_addresses(line.c_str()); if(r) { line = r.str; list = r.addr; return true; } else return false; } nullmailer-2.2/lib/address.cc000066400000000000000000000353551336020433200162320ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include "config.h" #include #include "canonicalize.h" #include "mystring/mystring.h" #include "list.h" #define LSQBRACKET '[' #define RSQBRACKET ']' #define QUOTE '"' #define CR '\n' #define LPAREN '(' #define RPAREN ')' enum node_type { EMPTY = 0, // Full tokens, with string content: ATOM = 'A', QUOTED_STRING = 'Q', DOMAIN_LITERAL = 'D', COMMENT = 'C', // Special characters, no content: LABRACKET = '<', RABRACKET = '>', AT = '@', COMMA = ',', SEMICOLON = ';', COLON = ':', ESCAPE = '\\', PERIOD = '.', // End of tokens EOT = '$', }; struct token { const node_type type; const bool has_ws; const mystring str; token(node_type); token(node_type, bool, mystring); }; token::token(node_type t) : type(t), has_ws(false) { } token::token(node_type t, bool w, mystring s) : type(t), has_ws(w), str(s) { } struct anode : public token { anode* next; anode(node_type, const char*, const char*, const char*); anode(node_type, bool, mystring); }; anode::anode(node_type t, const char* wstart, const char* start, const char* end) : token(t, start > wstart, mystring(start, end-start)), next(0) { } anode::anode(node_type t, bool w, mystring s) : token(t, w, s), next(0) { } struct result { anode* next; bool good; mystring str; mystring comment; mystring addr; result(); result(const result&); result(anode*); result(anode*, const mystring&, const mystring&, const mystring&); bool operator!() const { return !good; } operator bool() const { return good; } }; result::result() : next(0), good(0) { } result::result(anode* n) : next(n), good(1) { } result::result(anode* n, const mystring& s, const mystring& c, const mystring& l) : next(n), good(1), str(s), comment(c), addr(l) { } result::result(const result& r) : next(r.next), good(r.good), str(r.str), comment(r.comment), addr(r.addr) { } #ifndef TRACE #define ENTER(R) #define FAIL(MSG) return result() #define RETURNR(R) return R #define RETURN(N,S,C,L) return result(N,S,C,L) #else #include "fdbuf/fdbuf.h" static const char indentstr[] = " "; static const char* indent = indentstr + sizeof indentstr - 1; #define ENTER(R) do{ fout << indent-- << __FUNCTION__ << ": \"" << node->str << "\": " << R << endl; }while(0) #define FAIL(MSG) do{ fout << ++indent << __FUNCTION__ << ": failed: " << MSG << endl; return result(); }while(0) #define RETURNR(R) do{ fout << ++indent << __FUNCTION__ << ": succeeded str=" << R.str << " comment=" << R.comment << " addr=" << R.addr << endl; return (R); }while(0) #define RETURN(N,S,C,L) do{ result _result(N,S,C,L); RETURNR(_result); }while(0) #endif #define RULE(X) static result match_##X(anode* node) #define MATCHTOKEN(X) do{ if(node->type != X) FAIL("node is not type " #X); else node = node->next; }while(0) #define MATCHRULE(V,R) result V = match_##R(node); if(!V) FAIL("did not match " #R); #define OR_RULE(ALT1,ALT2) { result r=match_##ALT1(node); if(r) RETURNR(r); }{ result r=match_##ALT2(node); if(r) RETURNR(r); } FAIL("did not match " #ALT1 " OR " #ALT2); static bool issymbol(char c) { switch(c) { case LPAREN: case RPAREN: case LABRACKET: case RABRACKET: case LSQBRACKET: case RSQBRACKET: case AT: case COMMA: case SEMICOLON: case COLON: case ESCAPE: case QUOTE: case PERIOD: return true; default: return false; } } static bool isctl(char c) { return (c >= 0 && c <= 31) || (c == 127); } static bool isqtext(char c) { return c && c != QUOTE && c != ESCAPE; } static bool isdtext(char c) { return c && c != LSQBRACKET && c != RSQBRACKET && c != ESCAPE && c != CR; } // quoted-pair = ESCAPE CHAR static bool isqpair(const char* ptr) { return *ptr && *ptr == ESCAPE && *(ptr+1); } static bool isatom(char ch) { return !(isspace(ch) || issymbol(ch) || isctl(ch)); } static anode* tokenize_atom(const char* wstart, const char* &ptr) { if(!isatom(*ptr)) return 0; const char* start = ptr; do { ++ptr; } while(isatom(*ptr)); return new anode(ATOM, wstart, start, ptr); } static anode* tokenize_comment(const char* wstart, const char* &ptr) { if(*ptr != LPAREN) return 0; unsigned count = 0; const char* start = ptr; char ch = *ptr; while(ch) { if(isqpair(ptr)) ++ptr; else if(ch == LPAREN) ++count; else if(ch == RPAREN) { --count; if(!count) return new anode(COMMENT, wstart, start, ++ptr); } else if(ch == CR) return 0; // ERROR ++ptr; ch = *ptr; } return 0; // ERROR } static anode* tokenize_domain_literal(const char* wstart, const char* &ptr) { if(*ptr != LSQBRACKET) return 0; const char* start = ptr; ++ptr; while(isspace(*ptr)) ++ptr; for(; *ptr; ++ptr) { if(isdtext(*ptr)) continue; else if(isqpair(ptr)) ++ptr; else break; } while(isspace(*ptr)) ++ptr; if(*ptr != RSQBRACKET) return 0; // ERROR return new anode(DOMAIN_LITERAL, wstart, start, ptr); } static anode* tokenize_quoted_string(const char* wstart, const char* &ptr) { if(*ptr != QUOTE) return 0; const char* start = ptr; for(++ptr; *ptr; ++ptr) { if(isqtext(*ptr)) continue; else if(isqpair(ptr)) ++ptr; else break; } if(*ptr != QUOTE) return 0; ++ptr; return new anode(QUOTED_STRING, wstart, start, ptr); } static anode* tokenize(const char* &ptr) { const char* wstart = ptr; while(isspace(*ptr)) ++ptr; char ch = *ptr; switch(ch) { case 0: return new anode(EOT, wstart, ptr, ptr); case LABRACKET: case RABRACKET: case AT: case COMMA: case SEMICOLON: case COLON: case ESCAPE: case PERIOD: ++ptr; return new anode((node_type)ch, wstart, ptr-1, ptr); case LPAREN: return tokenize_comment(wstart, ptr); case LSQBRACKET: return tokenize_domain_literal(wstart, ptr); case QUOTE: return tokenize_quoted_string(wstart, ptr); default: return tokenize_atom(wstart, ptr); } } anode* tokenize(const mystring str) { const char* ptr = str.c_str(); anode* head = new anode(EMPTY, ptr, ptr, ptr); anode* tail = head; anode* tmp; while((tmp = tokenize(ptr)) != 0) { tail = tail->next = tmp; if(tmp->type == EOT) { tail = head->next; delete head; return tail; } } return 0; } static mystring quote(const mystring& in) { unsigned length = in.length(); // The result will never be more than double the length of the input plus 2 char out[length*2 + 2 + 1]; char* ptrout = out; const char* ptrin = in.c_str(); bool quoted = false; for(; length; ++ptrin, ++ptrout, --length) { if(*ptrin == QUOTE || *ptrin == ESCAPE) *ptrout++ = ESCAPE; if(issymbol(*ptrin)) quoted = true; *ptrout = *ptrin; } *ptrout = 0; if(quoted) return mystringjoin("\"") + out + "\""; else return in; } static mystring unquote(const mystring& in) { unsigned length = in.length(); // The result will never be more than the length of the input char out[length+1]; bool modified = false; const char* ptrin = in.c_str(); char* ptrout = out; if(in[0] == QUOTE && in[length-1] == QUOTE) { length -= 2; ptrin++; modified = true; } // Skip leading whitespace before copying to out for(; length > 0 && isspace(*ptrin); ++ptrin, --length, modified = true) ; for(; length; ++ptrin, ++ptrout, --length) { if(isqpair(ptrin)) { ++ptrin; --length; modified = true; } *ptrout = *ptrin; } // Skip trailing whitespace copied into out for(; ptrout > out && isspace(ptrout[-1]); --ptrout, modified = true) ; *ptrout = 0; if(modified) return out; else return in; } anode* skipcomment(anode* node, mystring& comment) { while(node->type == COMMENT) { comment = comment + " " + node->str; node = node->next; } return node; } RULE(sub_domain) { // Note atom <= domain-ref ENTER("atom / domain-literal"); mystring comment; node = skipcomment(node, comment); if(node->type == ATOM || node->type == DOMAIN_LITERAL) RETURN(node->next, node->str, comment, node->str); FAIL("did not match ATOM or DOMAIN-LITERAL"); } RULE(domain) { ENTER("sub-domain *(PERIOD sub-domain) [PERIOD]"); MATCHRULE(r, sub_domain); if(!r) FAIL("did not match sub-domain"); mystring comment; for(;;) { node = r.next = skipcomment(r.next, comment); if(node->type != PERIOD) break; r.str += PERIOD; r.addr += PERIOD; node = node->next; result r1 = match_sub_domain(node); if(r1) { r.next = r1.next; r.str += r1.str; comment += r1.comment; r.addr += r1.addr; } else { r.next = node; node = r.next = skipcomment(r.next, comment); } } r.comment += comment; RETURNR(r); } RULE(route) { ENTER("1#(AT domain) COLON"); unsigned count=0; mystring str; mystring comment; for(;;) { if(node->type != AT) break; node = node->next; MATCHRULE(r, domain); str += AT; str += r.str; comment += r.comment; ++count; node = r.next; } if(count == 0) FAIL("matched no domains"); node = skipcomment(node, comment); MATCHTOKEN(COLON); RETURN(node, str, comment, ""); } RULE(word) { ENTER("atom / quoted-string"); mystring comment; node = skipcomment(node, comment); if(node->type == ATOM) RETURN(node->next, node->str, comment, node->str); else if(node->type == QUOTED_STRING) { mystring addr = unquote(node->str); RETURN(node->next, quote(addr), comment, addr); } FAIL("did not match ATOM or QUOTED-STRING"); } RULE(local_part) { ENTER("word *(PERIOD word)"); MATCHRULE(r, word); for(;;) { node = r.next = skipcomment(r.next, r.comment); if(node->type != PERIOD) break; node = node->next; result r1 = match_word(node); if(!r1) break; r.next = r1.next; r.str += PERIOD; r.str += r1.str; r.comment += r1.comment; r.addr += PERIOD; r.addr += r1.addr; } RETURNR(r); } RULE(addr_spec) { ENTER("local-part *( AT domain )"); MATCHRULE(r, local_part); mystring domain; for(;;) { node = r.next = skipcomment(r.next, r.comment); if(node->type != AT) break; node = node->next; result r2 = match_domain(node); if(!r2) break; if(!!domain) { r.str += AT; r.str += domain; r.addr += AT; r.addr += domain; } domain = r2.addr; r.comment += r2.comment; r.next = r2.next; } canonicalize(domain); RETURN(r.next, r.str + "@" + domain, r.comment, r.addr + "@" + domain + "\n"); } RULE(route_addr) { ENTER("LABRACKET [route] addr-spec RABRACKET"); mystring comment; node = skipcomment(node, comment); MATCHTOKEN(LABRACKET); result r1 = match_route(node); if(r1) node = r1.next; comment += r1.comment; MATCHRULE(r2, addr_spec); node = r2.next; comment += r2.comment; node = skipcomment(node, comment); MATCHTOKEN(RABRACKET); RETURN(node, "<" + r2.str + ">" + comment, "", r2.addr); } RULE(phrase) { ENTER("word *(word / PERIOD / CFWS)"); MATCHRULE(r1, word); for(;;) { if(r1.next->type == PERIOD) { if (r1.next->has_ws) r1.str += ' '; r1.str += PERIOD; r1.next = r1.next->next; } else { result r2 = match_word(r1.next); if(!r2) break; if (r1.next->has_ws) r1.str += ' '; r1.str += r2.str; r1.comment += r2.comment; r1.next = r2.next; } } RETURNR(r1); } RULE(route_spec) { ENTER("[phrase] route-addr"); result r1 = match_phrase(node); if(r1) node = r1.next; MATCHRULE(r2, route_addr); if(!r1) RETURNR(r2); r2.str = r1.str + r1.comment + " " + r2.str + r2.comment; RETURNR(r2); } RULE(mailbox) { ENTER("route-spec / addr-spec"); OR_RULE(route_spec, addr_spec); } RULE(mailboxes) { ENTER("mailbox *(*(COMMA) mailbox)"); MATCHRULE(r1, mailbox); r1.str += r1.comment; r1.comment = ""; for(;;) { node = r1.next; for(;;) { node = skipcomment(node, r1.str); if(node->type == COMMA) node = node->next; else break; } if(node->type == EOT) break; result r2 = match_mailbox(node); if(!r2) break; r1.next = r2.next; r1.str = r1.str + ", " + r2.str + r2.comment; r1.addr += r2.addr; } node = skipcomment(node, r1.str); r1.next = node; RETURNR(r1); } RULE(group) { ENTER("phrase COLON [#mailboxes] SEMICOLON"); MATCHRULE(r1, phrase); node = r1.next; MATCHTOKEN(COLON); result r2 = match_mailboxes(node); if(r2) node = r2.next; mystring comment; node = skipcomment(node, comment); MATCHTOKEN(SEMICOLON); RETURN(node, r1.str + ": " + r2.str + r2.comment + comment + ";", "", r2.addr); } RULE(address) { ENTER("group / mailbox"); OR_RULE(group, mailbox); } RULE(addresses) { ENTER("[address *(*(COMMA) address)] EOF"); // Special-case handling for empty address lists if(node->type == EOT) RETURN(0, "", "", ""); if(node->type == COMMENT && node->next->type == EOT) RETURN(0, node->str, "", ""); MATCHRULE(r1, address); r1.str += r1.comment; r1.comment = ""; for(;;) { node = r1.next; for(;;) { node = skipcomment(node, r1.str); if(node->type == COMMA) node = node->next; else break; } if(node->type == EOT) break; result r2 = match_address(node); if(!r2) break; r1.next = r2.next; r1.str = r1.str + ", " + r2.str + r2.comment; r1.addr += r2.addr; } node = skipcomment(node, r1.str); if(node->next) FAIL("Rule ended before EOF"); RETURNR(r1); } static void del_tokens(anode* node) { while(node) { anode* tmp = node->next; delete node; node = tmp; } } bool parse_addresses(mystring& line, mystring& list) { anode* tokenlist = tokenize(line.c_str()); if(!tokenlist) return false; result r = match_addresses(tokenlist); del_tokens(tokenlist); if(r) { line = r.str; list = r.addr; return true; } else return false; } nullmailer-2.2/lib/address.h000066400000000000000000000002751336020433200160650ustar00rootroot00000000000000#ifndef NULLMAILER__ADDRESS__H__ #define NULLMAILER__ADDRESS__H__ #include "mystring/mystring.h" bool parse_addresses(mystring& line, mystring& list); #endif // NULLMAILER__ADDRESS__H__ nullmailer-2.2/lib/argparse.cc000066400000000000000000000025321336020433200164000ustar00rootroot00000000000000#include #include "argparse.h" static const char* parse_arg(mystring& arg, const char* start, const char* end) { const char* ptr; for (ptr = start; ptr < end && ! isspace(*ptr); ++ptr) { switch (*ptr) { case '\'': arg.append(start, ptr - start); for (start = ++ptr; ptr < end && *ptr != '\''; ++ptr) ; arg.append(start, ptr - start); start = ptr + 1; continue; case '"': arg.append(start, ptr - start); for (start = ++ptr; ptr < end && *ptr != '\"'; ptr++) { if (*ptr == '\\') { arg.append(start, ptr - start); if (++ptr < end) arg.append(ptr, 1); start = ++ptr; } } arg.append(start, ptr - start); start = ptr + 1; continue; case '\\': arg.append(start, ptr - start); if (++ptr < end) arg.append(ptr, 1); start = ++ptr; continue; } } if ((ptr - start) > 0) arg.append(start, ptr - start); return ptr; } unsigned parse_args(arglist& lst, const mystring& str) { lst.empty(); const char* ptr = str.c_str(); const char* end = ptr + str.length(); unsigned count = 0; while (ptr < end) { // Skip any leading spaces if (isspace(*ptr)) ++ptr; else { mystring s; ptr = parse_arg(s, ptr, end); if (ptr == 0) break; lst.append(s); ++count; } } return count; } nullmailer-2.2/lib/argparse.h000066400000000000000000000003171336020433200162410ustar00rootroot00000000000000#ifndef NULLMAILER__ARGPARSE__H #define NULLMAILER__ARGPARSE__H #include "mystring/mystring.h" #include "list.h" typedef list arglist; unsigned parse_args(arglist&, const mystring& str); #endif nullmailer-2.2/lib/autoclose.h000066400000000000000000000023401336020433200164310ustar00rootroot00000000000000#ifndef NULLMAILER_AUTOCLOSE__H__ #define NULLMAILER_AUTOCLOSE__H__ #include // Simple inline wrapper to automatically close an open file descriptor class autoclose { private: int fd; public: inline autoclose(int f = -1) : fd(f) { } inline ~autoclose() { close(); } inline operator int() const { return fd; } inline int operator =(int f) { close(); return fd = f; } inline void close() { if (fd >= 0) { ::close(fd); fd = -1; } } }; // Simple inline wrapper to handle opening and closing a pipe pair class autoclose_pipe { private: int fds[2]; public: inline autoclose_pipe() { fds[0] = fds[1] = -1; } inline ~autoclose_pipe() { close(); } inline int operator[](int i) const { return fds[i]; } inline bool open() { return pipe(fds) == 0; } inline void close() { if (fds[0] >= 0) { ::close(fds[0]); ::close(fds[1]); fds[0] = fds[1] = -1; } } // Close one half of the pair, return the other, and mark both as if they were closed. inline int extract(int which) { int result = fds[which]; ::close(fds[1-which]); fds[0] = fds[1] = -1; return result; } }; #endif // NULLMAILER_AUTOCLOSE__H__ nullmailer-2.2/lib/base64.cc000066400000000000000000000040611336020433200156570ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include "base64.h" static char basis[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; void base64_encode(const mystring& in, mystring& out) { size_t length; const unsigned char* ptr; char buf[4]; for (length = in.length(), ptr = (const unsigned char*)in.c_str(); length >= 3; length -= 3, ptr += 3) { base64_encode_chunk(ptr, 3, buf); out.append(buf, 4); } if (length > 0) { base64_encode_chunk(ptr, length, buf); out.append(buf, 4); } } void base64_encode_chunk(const unsigned char bin[3], unsigned len, char encoded[4]) { encoded[0] = basis[bin[0] >> 2]; switch(len) { case 1: encoded[1] = basis[(bin[0] << 4) & 0x3f]; encoded[2] = encoded[3] = '='; break; case 2: encoded[1] = basis[(bin[0] << 4 | bin[1] >> 4) & 0x3f]; encoded[2] = basis[(bin[1] << 2) & 0x3f]; encoded[3] = '='; break; case 3: encoded[1] = basis[(bin[0] << 4 | bin[1] >> 4) & 0x3f]; encoded[2] = basis[(bin[1] << 2 | bin[2] >> 6) & 0x3f]; encoded[3] = basis[bin[2] & 0x3f]; } } nullmailer-2.2/lib/base64.h000066400000000000000000000004371336020433200155240ustar00rootroot00000000000000#ifndef NULLMAILER_BASE64__H__ #define NULLMAILER_BASE64__H__ #include "mystring/mystring.h" extern void base64_encode(const mystring& in, mystring& out); extern void base64_encode_chunk(const unsigned char bin[3], unsigned len, char encoded[4]); #endif // NULLMAILER_BASE64__H__ nullmailer-2.2/lib/canonicalize.cc000066400000000000000000000025211336020433200172310ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include "config.h" #include "mystring/mystring.h" #include "canonicalize.h" #include "hostname.h" void canonicalize(mystring& domain) { if(!domain) domain = defaulthost; if(domain != "localhost" && domain.find_first('.') < 0) { if(!!defaultdomain) { if (!!domain) domain += "."; domain += defaultdomain; } } } nullmailer-2.2/lib/canonicalize.h000066400000000000000000000002721336020433200170740ustar00rootroot00000000000000#ifndef NULLMAILER__CANONICALIZE__H__ #define NULLMAILER__CANONICALIZE__H__ #include "mystring/mystring.h" void canonicalize(mystring& domain); #endif // NULLMAILER__CANONICALIZE__H__ nullmailer-2.2/lib/cli++/000077500000000000000000000000001336020433200151605ustar00rootroot00000000000000nullmailer-2.2/lib/cli++/Makefile.am000066400000000000000000000003641336020433200172170ustar00rootroot00000000000000noinst_LIBRARIES = libcli++.a EXTRA_DIST = clitest.cc cli++topod.pl AM_CPPFLAGS = -I$(top_srcdir)/lib #LIBS = @LIBS@ -L. -lcli++ -L../lib -lvmailmgr libcli___a_SOURCES = cli++.h main.cc messages.cc only_long.cc #clitest_SOURCES = clitest.cc nullmailer-2.2/lib/cli++/cli++.h000066400000000000000000000031651336020433200162330ustar00rootroot00000000000000#ifndef VMAILMGR__CLIPP__CLIPP__H__ #define VMAILMGR__CLIPP__CLIPP__H__ typedef bool (*cli_funcptr)(void*); struct cli_stringlist { const char* string; cli_stringlist* next; cli_stringlist(const char* s) : string(s), next(0) { } }; struct cli_option { char ch; const char* name; enum { flag, counter, integer, string, stringlist, ulong } type; int flag_value; void* dataptr; const char* helpstr; const char* defaultstr; int set(const char* arg); int parse_long_eq(const char* arg, int as_short); int parse_long_noeq(const char* arg, int as_short); }; #define CLI_OPTION_END {0, 0, cli_option::flag, 0, 0, 0, 0} /* The following are required from the CLI program */ extern const char* cli_program; extern const char* cli_help_prefix; extern const char* cli_help_suffix; extern const char* cli_args_usage; extern const int cli_args_min; extern const int cli_args_max; extern cli_option cli_options[]; extern const bool cli_only_long; extern int cli_main(int argc, char* argv[]); /* The following are provided to the CLI program */ extern const char* argv0; extern const char* argv0base; extern const char* argv0dir; extern void usage(int exit_value, const char* errorstr = 0); extern int cli_parse_args(int argc, char* argv[]); extern void cli_error(int exit_value, const char*, const char* = 0, const char* = 0, const char* = 0); extern void cli_syserror(int exit_value, const char*, const char* = 0, const char* = 0, const char* = 0); extern void cli_warning(const char*, const char* = 0, const char* = 0, const char* = 0); #endif nullmailer-2.2/lib/cli++/cli++topod.pl000066400000000000000000000102411336020433200174560ustar00rootroot00000000000000#!/usr/bin/perl sub cstr2pod { local($_) = shift; s/\\"/"/go; s/"([^\"]*)"/"C<$1>"/go; $_; } $section = 1; @section_order = ( 'NAME', 'SYNOPSIS', 'DESCRIPTION', 'OPTIONS', 'RETURN VALUE', 'ERRORS', 'EXAMPLES', 'ENVIRONMENT', 'FILES', 'SEE ALSO', 'NOTES', 'CAVEATS', 'WARNINGS', 'DIAGNOSTICS', 'BUGS', 'RESTRICTIONS', 'AUTHOR', 'AUTHORS', 'HISTORY' ); sub type2word { my($type) = shift; return 'INT' if $type eq 'integer'; return 'UINT' if $type eq 'ulong'; return 'STR' if $type eq 'string' || $type eq 'stringlist'; return '' if $type eq 'flag' || $type eq 'counter'; die "Invalid cli option type '$type'"; } sub add_option { my($short, $long, $type, $desc) = @_; my $s = '[B<'; my $o = '=item B<'; if($short) { $s .= "-$short"; $o .= "-$short"; if($type) { $s .= " $type"; $o .= " $type"; } } if($short && $long) { $s .= ">]\n[B<"; $o .= ">, B<"; } if($long) { $s .= "--$long"; $o .= "--$long"; if($type) { $s .= "=$type"; $o .= "=$type"; } } $s .= ">]\n"; $o .= ">\n\n$desc\n\n"; $synopsis .= $s; $options = "=over 8\n\n" unless $options; $options .= $o; } sub parse_option { local($_) = shift; s/^\s*\{\s*//o; s/\s*\},?\s*/ /o; my $short = $1 if s/^'([^\'])',\s*//o; die "Invalid cli option" unless $short || s/^0,\s*//o; my $long = $1 if s/^"([^\"]+)",\s*//o; die "Invalid cli_option" unless $long || s/^0,\s*//o; my $type = $1 if s/^cli_option::(\S+),\s*//o; die "Invalid cli_option" unless $type; $type = &type2word($type); my $val = $1 if s/^([^,]+),\s*//o; my $var = $1 if s/^&([^,]+),\s*//o; my $desc = cstr2pod($1) if s/^"([^,]+)",\s*//o; die "Invalid cli_option" unless $desc; $desc =~ s/\.?$/./o if $desc; my $default = $1 if s/^"([^\"]+)"\s+//o; die "Invalid cli_option" unless $default || s/^0\s+//o; $desc .= " Defaults to $default." if $default; s/\s*\/\/\s*/ /go; s/^\s*//o; add_option($short, $long, $type, $_ || $desc); } sub parse_options { $synopsis = "B<$program>\n"; my $line; while(<>) { s/^\s+//o; s/\s+$//o; if($line && /^\{/o) { &parse_option($line); $line = ""; } next if /^\{\s*0\s*\},?/o; next if /^\{\s*0\s*,\s*\},?/o; last if /^\s*\};/o; $line =~ s/$/ $_/; } &parse_option($line) if $line; $synopsis .= "I<$usage>" if $usage; $options .= "=back" if $options; $sections{'SYNOPSIS'} = $synopsis; $sections{'OPTIONS'} = $options; } sub parse_notes { my $section; my $title; while(<>) { chomp; last unless /^$/o || s/^\/\/\s*//o; if(/^[\sA-Z]+$/o) { $sections{$title} = $section if $title && $section; undef $section; $title = $_; } else { $section .= "$_\n"; } } $sections{$title} = $section if $title && $section; } sub parse_header_line { local($_, $comment) = @_; if(s/^\s*const\s+char\s*\*\s*cli_(\S+)\s*=\s*//o) { my $name = $1; s/;\s*$//o; s/^\"//; s/\"$//o; s/\\n$//o; s/\\n""/\n/go; $program = $_ if $name eq 'program'; $prefix = $_ if $name eq 'help_prefix'; $usage = $_ if $name eq 'args_usage'; $suffix = $_ if $name eq 'help_suffix'; } } sub parse_header { my $comment = ''; my $line = ''; while(<>) { s/^\s+//o; s/\s+$//o; if(s/^.*Copyright\s*\(C\)\s*[\d,]+\s*//o) { $author = $_; } else { last if ($program && $prefix && /^$/o); next if /^#/o; $comment .= "$1\n" if s|\s*//\s*(.*)$||o; $line =~ s/$/\n$_/; if(/;$/o) { &parse_header_line($line, $comment); undef $line; undef $comment; } } } } sub parse_description { while(<>) { s/^\s+//o; s/\s+$//o; last if / cli_options\[\]\s*=\s*\{/o; next unless s/^\/\/\s*//o; $description .= "$_\n"; } } &parse_header; &parse_description; &parse_options; &parse_notes; $description .= "\n\n$suffix\n" if $suffix; $sections{'NAME'} = "$program - $prefix"; $sections{'DESCRIPTION'} = $description; $sections{'AUTHORS'} = $author if $author; foreach $section (@section_order) { print "=head1 $section\n\n$sections{$section}\n\n" if $sections{$section}; } 1; nullmailer-2.2/lib/cli++/clitest.cc000066400000000000000000000034371336020433200171450ustar00rootroot00000000000000// Copyright (C) 2018 Bruce Guenter // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include #include "cli++.h" #include "fdbuf/fdbuf.h" const char* cli_program = "clitest"; const char* cli_help_prefix = "Does nothing but set flags\n"; const char* cli_help_suffix = ""; const char* cli_args_usage = ""; const int cli_args_min = 0; const int cli_args_max = -1; int o_flag = 0; int o_int = 0; char* o_string = "nostring"; cli_option cli_options[] = { { 'f', "flag", cli_option::flag, 1, &o_flag, "Sets a flag", 0 }, { 'i', "int", cli_option::integer, 0, &o_int, "Sets an integer", 0 }, { 's', "str", cli_option::string, 0, &o_string, "Sets a string", 0}, {0} }; int cli_main(int argc, char* argv[]) { fout << "argv0=" << argv0 << endl << " argv0dir=" << argv0dir << endl << " argv0base=" << argv0base << endl; fout << "The flag is set to " << o_flag << endl; fout << "The integer is set to " << o_int << endl; fout << "The string is set to " << o_string << endl; for(int i = 0; i < argc; i++) fout << "argv[" << i << "] = '" << argv[i] << "'\n"; return 0; } nullmailer-2.2/lib/cli++/main.cc000066400000000000000000000216271336020433200164230ustar00rootroot00000000000000// Copyright (C) 2018 Bruce Guenter // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include #include "ac/time.h" #include "fdbuf/fdbuf.h" #include #include #include #include "cli++.h" #ifndef HAVE_SRANDOM void srandom(unsigned int seed); #endif static bool do_show_usage = false; const char* argv0; const char* argv0base; const char* argv0dir; static cli_option help_option = { 'h', "help", cli_option::flag, true, &do_show_usage, "Display this help and exit", 0 }; static cli_option** options; static unsigned optionc; static const char* short_options; static void build_options() { for(optionc = 0; cli_options[optionc].ch || cli_options[optionc].name; optionc++) ; optionc++; options = new cli_option*[optionc]; for(unsigned i = 0; i < optionc-1; i++) options[i] = &cli_options[i]; options[optionc-1] = &help_option; char* so; short_options = so = new char[optionc+1]; for (unsigned i = 0; i < optionc; i++) if (options[i]->ch != 0) *so++ = options[i]->ch; *so = 0; } static inline int max(int a, int b) { return (a>b) ? a : b; } static const char* fill(int i) { static int lastlen = 0; static char* buf = 0; if(i > lastlen) { delete[] buf; buf = new char[i+1]; lastlen = i; } memset(buf, ' ', i); buf[i] = 0; return buf; } static void show_usage() { fout << "usage: " << cli_program << " [flags] " << cli_args_usage << endl; } static int calc_width(const cli_option* o) { int width = (o->ch || !cli_only_long) ? 4 : 2; if (o->name) { width += strlen(o->name) + 2 + !cli_only_long; switch (o->type) { case cli_option::string: width += 6; break; case cli_option::integer: width += 4; break; case cli_option::ulong: width += 4; break; case cli_option::stringlist: width += 5; break; case cli_option::flag: break; case cli_option::counter: break; } } return width; } static int calc_max_width() { // maxwidth is the maximum width of the option text prefix int maxwidth = 0; for(unsigned i = 0; i < optionc; i++) maxwidth = max(maxwidth, calc_width(options[i])); return maxwidth; } static void show_option(const cli_option* o, int maxwidth) { if(o == &help_option) fout << '\n'; fout << " "; if(o->ch) fout << '-' << o->ch; else if (!cli_only_long) fout << " "; if (o->ch || !cli_only_long) fout << (o->ch && o->name ? ", " : " "); if(o->name) { const char* extra = ""; switch(o->type) { case cli_option::string: extra = "=VALUE"; break; case cli_option::integer: extra = "=INT"; break; case cli_option::ulong: extra = "=UNS"; break; case cli_option::stringlist: extra = "=ITEM"; break; case cli_option::flag: break; case cli_option::counter: break; } fout << (cli_only_long ? "-" : "--") << o->name << extra << fill(maxwidth - strlen(o->name) - strlen(extra) - !cli_only_long - (o->ch || !cli_only_long ? 4 : 0)); } else fout << fill(maxwidth-3); fout << o->helpstr << '\n'; if(o->defaultstr) fout << fill(maxwidth+3) << "(Defaults to " << o->defaultstr << ")\n"; } static void show_help() { if(cli_help_prefix) fout << cli_help_prefix; int maxwidth = calc_max_width(); for(unsigned i = 0; i < optionc; i++) show_option(options[i], maxwidth); if(cli_help_suffix) fout << cli_help_suffix; } void usage(int exit_value, const char* errorstr) { if(errorstr) ferr << cli_program << ": " << errorstr << endl; show_usage(); show_help(); exit(exit_value); } cli_stringlist* stringlist_append(cli_stringlist* node, const char* newstr) { cli_stringlist* newnode = new cli_stringlist(newstr); if(node) { cli_stringlist* head = node; while(node->next) node = node->next; node->next = newnode; return head; } else return newnode; } int cli_option::set(const char* arg) { char* endptr; switch(type) { case flag: *(int*)dataptr = flag_value; return 0; case counter: *(int*)dataptr += flag_value; return 0; case integer: { long longresult = strtol(arg, &endptr, 10); if(*endptr || longresult < INT_MIN || longresult > INT_MAX) { ferr << argv0 << ": invalid integer: " << arg << endl; return -1; } *(int*)dataptr = (int) longresult; return 1; } case ulong: *(unsigned long*)dataptr = strtoul(arg, &endptr, 10); if(*endptr) { ferr << argv0 << ": invalid unsigned integer: " << arg << endl; return -1; } return 1; case stringlist: *(cli_stringlist**)dataptr = stringlist_append(*(cli_stringlist**)dataptr, arg); return 1; default: // string *(const char**)dataptr = arg; return 1; } } static int parse_short(int argc, char* argv[]) { int end = strlen(argv[0]) - 1; for(int i = 1; i <= end; i++) { int ch = argv[0][i]; unsigned j; for(j = 0; j < optionc; j++) { cli_option* o = options[j]; if(o->ch == ch) { if(o->type != cli_option::flag && o->type != cli_option::counter) { if(i < end) { if(o->set(argv[0]+i+1) != -1) return 0; } else if(argc <= 1) { ferr << argv0 << ": option -" << o->ch << " requires a value." << endl; } else if(o->set(argv[1]) != -1) return 1; } else if(o->set(0) != -1) break; return -1; } } if(j >= optionc) { ferr << argv0 << ": unknown option letter -" << argv[0][i] << endl; return -1; } } return 0; } static void option_error(const cli_option* o, int as_short, const char* text) { ferr << argv0 << ": option "; if (as_short) ferr << '-' << o->ch; else ferr << (cli_only_long ? "-" : "--") << o->name; ferr << text << endl; } int cli_option::parse_long_eq(const char* arg, int as_short) { if(type == flag || type == counter) { option_error(this, as_short, " does not take a value."); return -1; } else return set(arg)-1; } int cli_option::parse_long_noeq(const char* arg, int as_short) { if(type == flag || type == counter) return set(0); else if(arg) return set(arg); else { option_error(this, as_short, " requires a value."); return -1; } } static int parse_long(int, char* argv[]) { const char* arg = argv[0]+1; // Handle both short and long args if (arg[0] == '-') ++arg; for(unsigned j = 0; j < optionc; j++) { cli_option* o = options[j]; if(o->name) { size_t len = strlen(o->name); if(!memcmp(arg, o->name, len)) { if(arg[len] == '\0') return o->parse_long_noeq(argv[1], false); else if(arg[len] == '=') return o->parse_long_eq(arg+len+1, false); } } } ferr << argv0 << ": unknown option string: '" << argv[0] << "'" << endl; return -1; } static int parse_either(int argc, char* argv[]) { return (strchr(short_options, argv[0][1]) != 0) ? parse_short(argc, argv) : parse_long(argc, argv); } int cli_parse_args(int argc, char* argv[]) { int i; for(i = 1; i < argc; i++) { const char* arg = argv[i]; // Stop at the first non-option argument if(arg[0] != '-') break; // Stop after the first "-" or "--" if(arg[1] == '\0' || (arg[1] == '-' && arg[2] == '\0')) { i++; break; } int j = (arg[1] == '-') ? parse_long(argc-i, argv+i) : cli_only_long ? parse_either(argc-i, argv+i) : parse_short(argc-i, argv+i); if(j < 0) usage(1); else i += j; } return i; } static void set_argv0(const char* p) { argv0 = p; static const char* empty = ""; const char* s = strrchr(p, '/'); if(s) { ++s; argv0base = s; size_t length = s-p; char* tmp = new char[length+1]; memcpy(tmp, p, length); tmp[length] = 0; argv0dir = tmp; } else { argv0base = p; argv0dir = empty; } } int main(int argc, char* argv[]) { struct timeval tv; gettimeofday(&tv, 0); srandom(tv.tv_usec ^ tv.tv_sec); set_argv0(argv[0]); build_options(); int lastarg = cli_parse_args(argc, argv); if(do_show_usage) usage(0); argc -= lastarg; argv += lastarg; if(argc < cli_args_min) usage(1, "Too few command-line arguments"); if(cli_args_max >= cli_args_min && argc > cli_args_max) usage(1, "Too many command-line arguments"); return cli_main(argc, argv); } nullmailer-2.2/lib/cli++/messages.cc000066400000000000000000000034021336020433200172750ustar00rootroot00000000000000// Copyright (C) 2018 Bruce Guenter // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include #include #include "fdbuf/fdbuf.h" #include #include #include "cli++.h" extern const char* argv0; static void cli_msg(const char* prefix, const char* a, const char* b, const char* c, const char* d, bool add_error = false) { ferr << cli_program << ": " << prefix << a; if(b) ferr << b; if(c) ferr << c; if(d) ferr << d; if (add_error) ferr << ": " << strerror(errno); ferr << endl; } void cli_error(int exit_value, const char* a, const char* b, const char* c, const char* d) { cli_msg("Error: ", a, b, c, d, false); exit(exit_value); } void cli_syserror(int exit_value, const char* a, const char* b, const char* c, const char* d) { cli_msg("Error: ", a, b, c, d, true); exit(exit_value); } void cli_warning(const char* a, const char* b, const char* c, const char* d) { cli_msg("Warning: ", a, b, c, d, false); } nullmailer-2.2/lib/cli++/only_long.cc000066400000000000000000000000661336020433200174710ustar00rootroot00000000000000#include "cli++.h" const bool cli_only_long = false; nullmailer-2.2/lib/config_path.cc000066400000000000000000000035051336020433200170560ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include "config.h" #include "defines.h" #include #include "configio.h" #include "fdbuf/fdbuf.h" mystring config_BIN_DIR; mystring config_CONFIG_DIR; mystring config_HOME_DIR; mystring config_PROTOCOLS_DIR; mystring config_SBIN_DIR; static mystring test_prefix; static bool initialized = false; mystring config_path(const char* dflt, const char* testdir, const char* subdir, const char* filename) { if (!initialized) { // Check if the program is running setuid, to avoid privilege escalation. if (getuid() == geteuid()) test_prefix = getenv("NULLMAILER_TEST_PREFIX"); } mystring result = test_prefix; if (!result) result = dflt; else { result += '/'; result += testdir; } result += '/'; if (subdir) { result += subdir; result += '/'; } result += filename; return result; } nullmailer-2.2/lib/config_read.cc000066400000000000000000000026141336020433200170350ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include "config.h" #include "defines.h" #include "configio.h" #include "fdbuf/fdbuf.h" bool config_read(const char* filename, mystring& result) { const mystring fullname = CONFIG_PATH(CONFIG, NULL, filename); fdibuf in(fullname.c_str()); if (!in) return config_syserr(fullname.c_str()); if(!in.getline(result)) return false; result = result.strip(); return result.length() > 0; } nullmailer-2.2/lib/config_readint.cc000066400000000000000000000026561336020433200175560ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include "config.h" #include #include #include "defines.h" #include "configio.h" #include "fdbuf/fdbuf.h" bool config_readint(const char* filename, int& result) { mystring tmp; if(!config_read(filename, tmp)) return false; char* endptr; long longresult = strtol(tmp.c_str(), &endptr, 10); result = (int) longresult; return endptr > tmp.c_str() && longresult >= INT_MIN && longresult <= INT_MAX; } nullmailer-2.2/lib/config_readlist.cc000066400000000000000000000030051336020433200177240ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include "config.h" #include "defines.h" #include "configio.h" #include "fdbuf/fdbuf.h" bool config_readlist(const char* filename, list& result) { const mystring fullname = CONFIG_PATH(CONFIG, NULL, filename); fdibuf in(fullname.c_str()); if(!in) return config_syserr(fullname.c_str()); mystring tmp; bool nonempty = false; while(in.getline(tmp)) { tmp = tmp.strip(); if(tmp[0] != '#' && tmp.length() > 0) { result.append(tmp); nonempty = true; } } return nonempty; } nullmailer-2.2/lib/config_syserr.cc000066400000000000000000000024121336020433200174450ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include #include "config.h" #include "errcodes.h" #include "configio.h" #include "cli++/cli++.h" bool config_syserr(const char* filename) { if (errno != ENOENT) cli_syserror(ERR_CONFIG, "Could not read config file \"", filename, "\""); return false; } nullmailer-2.2/lib/configio.h000066400000000000000000000011131336020433200162250ustar00rootroot00000000000000#ifndef NULLMAILER__CONFIGIO__H__ #define NULLMAILER__CONFIGIO__H__ #include "mystring/mystring.h" #include "list.h" mystring config_path(const char* dflt, const char* testdir, const char* subdir, const char* filename); #define CONFIG_PATH(NAME, SUBDIR, FILENAME) config_path(NAME##_DIR, NAME##_TEST_DIR, SUBDIR, FILENAME) bool config_read(const char* filename, mystring& result); bool config_readlist(const char* filename, list& result); bool config_readint(const char* filename, int& result); bool config_syserr(const char* filename); #endif // NULLMAILER__CONFIGIO__H__ nullmailer-2.2/lib/connect.h000066400000000000000000000002571336020433200160710ustar00rootroot00000000000000#ifndef NULLMAILER_CONNECT__H__ #define NULLMAILER_CONNECT__H__ extern int tcpconnect(const char* hostname, int port, const char* source); #endif // NULLMAILER_CONNECT__H__ nullmailer-2.2/lib/defines.h000066400000000000000000000006451336020433200160560ustar00rootroot00000000000000#ifndef NULLMAILER__DEFINES__H__ #define NULLMAILER__DEFINES__H__ extern const char QUEUE_DIR[]; extern const char CONFIG_DIR[]; extern const char PROTOCOLS_DIR[]; extern const char BIN_DIR[]; extern const char SBIN_DIR[]; #define QUEUE_TEST_DIR "queue" #define CONFIG_TEST_DIR "conf" #define PROTOCOLS_TEST_DIR "protocols" #define BIN_TEST_DIR "bin" #define SBIN_TEST_DIR "sbin" #endif /* NULLMAILER__DEFINES__H__ */ nullmailer-2.2/lib/errcodes.cc000066400000000000000000000045431336020433200164060ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include "errcodes.h" const char* errorstr(int code) { switch (code) { case 0: return "No error"; case ERR_HOST_NOT_FOUND: return "Host not found"; case ERR_NO_ADDRESS: return "Host has no address"; case ERR_GHBN_FATAL: return "Fatal error in gethostbyname"; case ERR_GHBN_TEMP: return "Temporary error in gethostbyname"; case ERR_SOCKET: return "Socket failed"; case ERR_CONN_REFUSED: return "Connection refused"; case ERR_CONN_TIMEDOUT: return "Connection timed out"; case ERR_CONN_UNREACHABLE: return "Host or network unreachable"; case ERR_CONN_FAILED: return "Connection failed"; case ERR_PROTO: return "Protocol error"; case ERR_MSG_OPEN: return "Could not open message"; case ERR_MSG_READ: return "Could not read message"; case ERR_MSG_WRITE: return "Could not write message"; case ERR_EXEC_FAILED: return "Could not exec program"; case ERR_MSG_TEMPFAIL: return "Temporary error in sending the message"; case ERR_CONFIG: return "Could not read config files"; case ERR_MSG_REFUSED: return "Server refused the message"; case ERR_MSG_PERMFAIL: return "Permanent error in sending the message"; case ERR_BIND_FAILED: return "Failed to bind source address"; case ERR_AUTH_FAILED: return "Failed to authenticate to server"; } return (code & ERR_PERMANENT_FLAG) ? "Unspecified permanent error" : "Unspecified temporary error"; } nullmailer-2.2/lib/errcodes.h000066400000000000000000000027541336020433200162520ustar00rootroot00000000000000#ifndef NULLMAILER__ERRCODES__H__ #define NULLMAILER__ERRCODES__H__ // Temporary errors #define ERR_USAGE 1 // Invalid command-line arguments #define ERR_HOST_NOT_FOUND 2 // gethostbyname failed with HOST_NOT_FOUND #define ERR_NO_ADDRESS 3 // gethostbyname failed with NO_ADDRESS #define ERR_GHBN_TEMP 5 // gethostbyname failed with TRY_AGAIN #define ERR_SOCKET 6 // socket failed #define ERR_CONN_REFUSED 7 // connect failed with ECONNREFUSED #define ERR_CONN_TIMEDOUT 8 // connect failed with ETIMEDOUT #define ERR_CONN_UNREACHABLE 9 // connect failed with ENETUNREACH #define ERR_CONN_FAILED 10 // connect failed #define ERR_PROTO 11 // unexpected result from server #define ERR_MSG_OPEN 12 // could not open the message #define ERR_MSG_READ 13 // reading the message failed #define ERR_MSG_WRITE 14 // writing the message failed #define ERR_EXEC_FAILED 15 // executing a program failed #define ERR_MSG_TEMPFAIL 16 // server temporarily failed to receive #define ERR_UNKNOWN 17 // Arbitrary error code #define ERR_CONFIG 18 // Error reading a config file #define ERR_BIND_FAILED 19 // Failed to bind source address #define ERR_AUTH_FAILED 20 // Failed to authenticate to server // Permanent errors #define ERR_GHBN_FATAL 33 // gethostbyname failed with NO_RECOVERY #define ERR_MSG_REFUSED 34 // server refused the message #define ERR_MSG_PERMFAIL 35 // server permanently failed to receive #define ERR_PERMANENT_FLAG 32 extern const char* errorstr(int); #endif // NULLMAILER__ERRCODES__H__ nullmailer-2.2/lib/fdbuf/000077500000000000000000000000001336020433200153515ustar00rootroot00000000000000nullmailer-2.2/lib/fdbuf/Makefile.am000066400000000000000000000007621336020433200174120ustar00rootroot00000000000000noinst_LIBRARIES = libfdbuf.a AM_CPPFLAGS = -I$(top_srcdir)/lib if FDBUF_NO_MYSTRING mystring_sources = else mystring_sources = fdibuf_mystring.cc fdibuf_netstring.cc endif if TLS tls_sources = tlsibuf.h tlsibuf.cc tlsobuf.h tlsobuf.cc else tls_sources = endif libfdbuf_a_SOURCES = \ fdbuf.h \ fdbuf.cc \ fdbuf_copy.cc \ fdibuf.h \ fdibuf.cc \ fdobuf.h \ fdobuf.cc \ fdobuf_chownmod.cc \ fdobuf_seek.cc \ fdobuf_signed.cc \ fdobuf_unsigned.cc \ $(tls_sources) \ $(mystring_sources) nullmailer-2.2/lib/fdbuf/fdbuf.cc000066400000000000000000000043231336020433200167500ustar00rootroot00000000000000// Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "fdbuf.h" #include #include #include #include /////////////////////////////////////////////////////////////////////////////// // Class fdbuf /////////////////////////////////////////////////////////////////////////////// fdbuf::fdbuf(int fdesc, bool dc, unsigned bufsz) : buf(new char[bufsz]), buflength(0), bufstart(0), offset(0), errnum(0), flags(0), bufsize(bufsz), fd(fdesc), do_close(dc) { if(!buf) { flags = flag_error; errnum = errno; } if(fdesc < 0) flags |= flag_closed; #ifdef _REENTRANT pthread_mutex_t tmp = PTHREAD_MUTEX_INITIALIZER; mutex = tmp; pthread_mutex_init(&mutex, 0); #else #ifdef FDBUF_MUTEX_DEBUG mutex_count = 0; #endif #endif } fdbuf::~fdbuf() { close(); #ifdef _REENTRANT pthread_mutex_destroy(&mutex); #endif delete buf; } bool fdbuf::error() const { return flags & flag_error; } bool fdbuf::closed() const { return flags & flag_closed; } bool fdbuf::close() { if(do_close && fd >= 0 && !(flags & flag_closed)) { if(::close(fd) == -1) { errnum = errno; flags |= flag_error; return false; } flags |= flag_closed; } return true; } #if defined(FDBUF_MUTEX_DEBUG) && !defined(_REENTRANT) { int* null = 0; (*null)++; kill(getpid(), 9); } // Debugging code void fdbuf::lock() { if(mutex) abort(); ++mutex; } void fdbuf::unlock() { if(mutex != 1) abort(); --mutex; } #endif nullmailer-2.2/lib/fdbuf/fdbuf.h000066400000000000000000000040721336020433200166130ustar00rootroot00000000000000// Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #ifndef FDBUF__H__ #define FDBUF__H__ #include "config.h" #include #include #include #ifdef _REENTRANT #include #endif #ifndef FDBUF_SIZE #define FDBUF_SIZE 4096 #endif class mystring; class fdbuf { public: enum flagbits { flag_eof=1, flag_error=2, flag_closed=4 }; fdbuf(int fdesc, bool dc, unsigned bufsz = FDBUF_SIZE); ~fdbuf(); bool error() const; bool closed() const; bool close(); #ifdef _REENTRANT void lock() { pthread_mutex_lock(&mutex); } void unlock() { pthread_mutex_unlock(&mutex); } #else #ifdef FDBUF_MUTEX_DEBUG void lock(); void unlock(); #else void lock() { } void unlock() { } #endif #endif protected: char* const buf; unsigned buflength; // Length of the data in the buffer unsigned bufstart; // Start of the data in the buffer unsigned offset; // Current file read/write offset int errnum; // Saved error flag unsigned flags; // Status flags const unsigned bufsize; // Total buffer size const int fd; const bool do_close; // True to close on destructor #ifdef _REENTRANT pthread_mutex_t mutex; #else #ifdef FDBUF_MUTEX_DEBUG unsigned mutex; #endif #endif }; bool fdbuf_copy(class fdibuf&, class fdobuf&, bool noflush = false); #include "fdbuf/fdibuf.h" #include "fdbuf/fdobuf.h" #endif // FDBUF__H__ nullmailer-2.2/lib/fdbuf/fdbuf_copy.cc000066400000000000000000000025621336020433200200050ustar00rootroot00000000000000// Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "fdbuf.h" /////////////////////////////////////////////////////////////////////////////// // Other routines /////////////////////////////////////////////////////////////////////////////// bool fdbuf_copy(fdibuf& in, fdobuf& out, bool noflush) { if(in.eof()) return true; if(!in || !out) return false; do { char buf[FDBUF_SIZE]; if(!in.read(buf, FDBUF_SIZE) && in.last_count() == 0) break; if(!out.write(buf, in.last_count()) && out.last_count() < in.last_count()) return false; } while(!in.eof()); if(!noflush && !out.flush()) return false; return in.eof(); } nullmailer-2.2/lib/fdbuf/fdbuf_test.cc000066400000000000000000000004541336020433200200100ustar00rootroot00000000000000#include "fdbuf.h" int main() { fdibuf in("testfile"); char buf[17]; buf[16] = 0; in.read(buf, 16); fout.write(buf); fout.flush(); in.seek(1024); in.read(buf, 16); fout.write(buf); fout.flush(); in.seek(8192+16); in.read(buf, 16); fout.write(buf); fout.flush(); return 0; } nullmailer-2.2/lib/fdbuf/fdibuf.cc000066400000000000000000000103741336020433200171240ustar00rootroot00000000000000// Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "fdbuf.h" #include #include #include #include /////////////////////////////////////////////////////////////////////////////// // Class fdibuf /////////////////////////////////////////////////////////////////////////////// fdibuf::fdibuf(int fdesc, bool dc, unsigned bufsz) : fdbuf(fdesc, dc, bufsz) { } fdibuf::fdibuf(const char* filename, unsigned bufsz) : fdbuf(open(filename, O_RDONLY), true, bufsz) { if(fd == -1) { flags = flag_error; errnum = errno; } } fdibuf::~fdibuf() { } bool fdibuf::eof() const { return (flags & flag_eof) && (bufstart >= buflength); } bool fdibuf::operator!() const { return eof() || error() || closed(); } // refill is protected -- no locking bool fdibuf::refill() { if(flags) return false; if(bufstart != 0) { if(bufstart < buflength) { buflength -= bufstart; memcpy(buf, buf+bufstart, buflength); } else buflength = 0; bufstart = 0; } unsigned oldbuflength = buflength; if(buflength < bufsize) { ssize_t red = _read(buf+buflength, bufsize-buflength); if(red < 0) { errnum = errno; flags |= flag_error; } else if(red == 0) flags |= flag_eof; else { buflength += red; offset += red; } } return buflength > oldbuflength; } bool fdibuf::get(char& ch) { lock(); count = 0; if(bufstart >= buflength) refill(); bool r = true; if(eof() || error()) r = false; else { ch = buf[bufstart++]; count = 1; } unlock(); return r; } bool fdibuf::read_large(char* data, unsigned datalen) { lock(); count = 0; // If there's any content in the buffer, memcpy it out first. unsigned len = buflength - bufstart; if(len > datalen) len = datalen; memcpy(data, buf+bufstart, len); data += len; datalen -= len; bufstart += len; count += len; // After the buffer is empty and there's still data to read, // read it straight from the fd instead of copying it through the buffer. while(datalen > 0) { ssize_t red = _read(data, datalen); if(red < 0) { errnum = errno; flags |= flag_error; break; } else if(red == 0) { flags |= flag_eof; break; } data += red; datalen -= red; offset += red; count += red; } unlock(); return datalen == 0; } bool fdibuf::read(char* data, unsigned datalen) { if(datalen >= bufsize) return read_large(data, datalen); lock(); count = 0; char* ptr = data; while(datalen && !eof()) { if(bufstart >= buflength) refill(); unsigned len = buflength-bufstart; if(len > datalen) len = datalen; memcpy(ptr, buf+bufstart, len); bufstart += len; datalen -= len; ptr += len; count += len; } unlock(); return !datalen; } bool fdibuf::seek(unsigned o) { lock(); unsigned buf_start = offset - buflength; if(o >= buf_start && o < offset) { bufstart = o - buf_start; } else { if(lseek(fd, o, SEEK_SET) != (off_t)o) { errnum = errno; flags |= flag_error; unlock(); return false; } offset = o; buflength = bufstart = 0; } count = 0; flags &= ~flag_eof; unlock(); return true; } bool fdibuf::seekfwd(unsigned o) { return seek(tell() + o); } ssize_t fdibuf::_read(char* buf, ssize_t len) { return ::read(fd, buf, len); } /////////////////////////////////////////////////////////////////////////////// // Globals /////////////////////////////////////////////////////////////////////////////// fdibuf fin(0); nullmailer-2.2/lib/fdbuf/fdibuf.h000066400000000000000000000037101336020433200167620ustar00rootroot00000000000000// Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #ifndef FDBUF__FDIBUF__H__ #define FDBUF__FDIBUF__H__ #include "fdbuf.h" class fdibuf : protected fdbuf { public: fdibuf(const char* filename, unsigned bufsz = FDBUF_SIZE); fdibuf(int fdesc, bool dc = false, unsigned bufsz = FDBUF_SIZE); virtual ~fdibuf(); bool close() { lock(); bool r = fdbuf::close(); unlock(); return r; } bool eof() const; bool operator!() const ; operator bool() const { return !operator!(); } virtual bool get(char& ch); virtual bool getline(mystring& out, char terminator = '\n'); virtual bool getnetstring(mystring& out); virtual bool read(char*, unsigned); virtual bool read_large(char*, unsigned); bool read(unsigned char* b, unsigned l) { return read((char*)b, l); } bool read(signed char* b, unsigned l) { return read((char*)b, l); } unsigned last_count() { return count; } bool seek(unsigned o); bool seekfwd(unsigned o); bool rewind() { return seek(0); } unsigned tell() const { return offset-buflength+bufstart; } int error_number() const { return errnum; } protected: unsigned count; // Number of bytes read by last operation bool refill(); virtual ssize_t _read(char*, ssize_t); }; extern fdibuf fin; #endif // FDBUF__FDIBUF__H__ nullmailer-2.2/lib/fdbuf/fdibuf_mystring.cc000066400000000000000000000027731336020433200210640ustar00rootroot00000000000000// Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include "fdbuf.h" #include "mystring/mystring.h" bool fdibuf::getline(mystring& out, char terminator) { lock(); count = 0; if(bufstart >= buflength) refill(); if(eof() || error()) { unlock(); return false; } out = ""; while(!eof() && !error()) { char* ptr = buf+bufstart; unsigned bufleft = buflength - bufstart; const char* end = (const char*)memchr(ptr, terminator, bufleft); if(!end) { out += mystring(ptr, bufleft); bufstart = buflength; count += bufleft; refill(); } else { unsigned copylen = end - ptr; out += mystring(ptr, copylen); bufstart += copylen+1; count += copylen+1; break; } } unlock(); return true; } nullmailer-2.2/lib/fdbuf/fdibuf_netstring.cc000066400000000000000000000023551336020433200212210ustar00rootroot00000000000000// Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include "fdbuf.h" #include "mystring/mystring.h" bool fdibuf::getnetstring(mystring& out) { // Read in the size char ch; unsigned long size = 0; for(;;) { if(!get(ch)) return false; if(ch == ':') break; else if(ch >= '0' && ch <= '9') size = size*10 + (ch-'0'); else return false; } char tmp[size]; if(!read(tmp, size) || !get(ch) || ch != ',') return false; out = mystring(tmp, size); return true; } nullmailer-2.2/lib/fdbuf/fdobuf.cc000066400000000000000000000106751336020433200171360ustar00rootroot00000000000000// Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "fdbuf.h" #include #include #include #include /////////////////////////////////////////////////////////////////////////////// // Globals /////////////////////////////////////////////////////////////////////////////// fdobuf fout(1); fdobuf ferr(2); /////////////////////////////////////////////////////////////////////////////// // Class fdobuf /////////////////////////////////////////////////////////////////////////////// fdobuf::fdobuf(int fdesc, bool dc, unsigned bufsz) : fdbuf(fdesc, dc, bufsz), bufpos(0) { } fdobuf::fdobuf(const char* filename, int f, int mode, unsigned bufsz) : fdbuf(open(filename, O_WRONLY | f, mode), true, bufsz), bufpos(0) { if(fd == -1) { flags = flag_error; errnum = errno; } } fdobuf::~fdobuf() { flush(); } bool fdobuf::close() { if(!flush()) return false; lock(); bool r = fdbuf::close(); unlock(); return r; } bool fdobuf::operator!() const { return error() || closed(); } bool fdobuf::nflush(bool withsync) { if(flags) return false; while(bufstart < buflength) { ssize_t written = _write(buf+bufstart, buflength-bufstart); if(written < 0) { flags |= flag_error; errnum = errno; return false; } else { bufstart += written; offset += written; } } buflength = 0; bufstart = 0; bufpos = 0; if(withsync && (fsync(fd) == -1)) { flags |= flag_error; errnum = errno; return false; } return true; } bool fdobuf::flush() { lock(); bool r = nflush(false); unlock(); return r; } bool fdobuf::sync() { lock(); bool r = nflush(true); unlock(); return r; } bool fdobuf::write(char ch) { if(flags) return false; lock(); count = 0; buf[bufpos++] = ch; //if(buflength >= bufsize && !nflush(false)) { // unlock(); // return false; //} if(bufpos >= buflength) buflength = bufpos; if(buflength >= bufsize && !nflush(false)) { unlock(); return false; } count = 1; unlock(); return true; } bool fdobuf::write_large(const char* data, unsigned datalen) { if(flags) return false; lock(); count = 0; if(!nflush(false)) { unlock(); return false; } while(datalen > 0) { ssize_t written = _write(data, datalen); if(written < 0) { flags |= flag_error; errnum = errno; unlock(); return false; } datalen -= written; data += written; offset += written; count += written; } unlock(); return true; } bool fdobuf::write(const char* data, unsigned datalen) { if(datalen >= bufsize) return write_large(data, datalen); if(flags) return false; lock(); const char* ptr = data; count = 0; // Amount is the number of bytes available in the buffer unsigned amount = bufsize-bufpos; while(datalen >= amount) { // If we get here, this copy will completely fill the buffer, // requiring a flush memcpy(buf+bufpos, ptr, amount); bufpos = bufsize; buflength = bufsize; datalen -= amount; ptr += amount; if(!nflush(false)) { unlock(); return false; } count += amount; amount = bufsize-bufpos; } // At this point, the remaining data will fit into the buffer memcpy(buf+bufpos, ptr, datalen); count += datalen; bufpos += datalen; if(bufpos > buflength) buflength = bufpos; unlock(); return true; } ssize_t fdobuf::_write(const char* buf, ssize_t len) { return ::write(fd, buf, len); } /////////////////////////////////////////////////////////////////////////////// // Manipulators /////////////////////////////////////////////////////////////////////////////// fdobuf& endl(fdobuf& fd) { fd.write("\n", 1); fd.flush(); return fd; } nullmailer-2.2/lib/fdbuf/fdobuf.h000066400000000000000000000055621336020433200167770ustar00rootroot00000000000000// Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #ifndef FDBUF__FDOBUF__H__ #define FDBUF__FDOBUF__H__ #include "fdbuf.h" class fdobuf : protected fdbuf { public: enum openflags { create=O_CREAT, excl=O_EXCL, trunc=O_TRUNC, append=O_APPEND }; fdobuf(const char* filename, int, int mode = 0666, unsigned bufsz = FDBUF_SIZE); fdobuf(int fdesc, bool dc=false, unsigned bufsz = FDBUF_SIZE); virtual ~fdobuf(); bool close(); bool operator!() const; operator bool() const { return !operator!(); } bool flush(); bool sync(); virtual bool write(char); bool write(unsigned char c) { return write((char)c); } bool write(signed char c) { return write((char)c); } virtual bool write(const char*, unsigned); bool write(const unsigned char* b, unsigned l) { return write((char*)b, l); } bool write(const signed char* b, unsigned l) { return write((char*)b, l); } virtual bool write_large(const char*, unsigned); unsigned last_count() { return count; } bool seek(unsigned o); bool rewind() { return seek(0); } unsigned tell() const { return offset + bufpos; } bool chown(uid_t, gid_t) const; bool chmod(mode_t) const; fdobuf& operator<<(const char* str) { write(str, strlen(str)); return *this; } fdobuf& operator<<(char ch) { write(ch); return *this; } fdobuf& operator<<(fdobuf& (*manip)(fdobuf&)) { return manip(*this); } fdobuf& operator<<(unsigned long); fdobuf& operator<<(signed long); fdobuf& operator<<(unsigned i) { return operator<<((unsigned long)i); } fdobuf& operator<<(signed i) { return operator<<((signed long)i); } fdobuf& operator<<(unsigned short i) { return operator<<((unsigned long)i); } fdobuf& operator<<(signed short i) { return operator<<((signed long)i); } int error_number() const { return errnum; } protected: virtual bool nflush(bool withsync); virtual ssize_t _write(const char* buf, ssize_t len); unsigned bufpos; // Current write position in the buffer unsigned count; // Number of bytes written by last operation }; fdobuf& endl(fdobuf& fd); extern fdobuf fout; extern fdobuf ferr; #endif // FDBUF__FDOBUF__H__ nullmailer-2.2/lib/fdbuf/fdobuf_chownmod.cc000066400000000000000000000020761336020433200210300ustar00rootroot00000000000000// Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include "fdbuf.h" #include #include bool fdobuf::chown(uid_t uid, gid_t gid) const { if(error()) return false; return fchown(fd, uid, gid) != -1; } bool fdobuf::chmod(mode_t mode) const { if(error()) return false; return fchmod(fd, mode) != -1; } nullmailer-2.2/lib/fdbuf/fdobuf_seek.cc000066400000000000000000000024541336020433200201410ustar00rootroot00000000000000// Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "fdbuf.h" #include #include bool fdobuf::seek(unsigned o) { if(flags) return false; lock(); unsigned buf_start = offset; unsigned buf_end = offset + buflength; if(o >= buf_start && o < buf_end) { bufpos = o - buf_start; } else { if(!nflush(false)) { unlock(); return false; } if(lseek(fd, o, SEEK_SET) != (off_t)o) { errnum = errno; flags |= flag_error; unlock(); return false; } offset = o; } count = 0; unlock(); return true; } nullmailer-2.2/lib/fdbuf/fdobuf_signed.cc000066400000000000000000000022111336020433200204520ustar00rootroot00000000000000// Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "fdbuf.h" #include #define MAXSTRLEN ((sizeof(signed long)*CHAR_BIT)/3) fdobuf& fdobuf::operator<<(signed long i) { if(i == 0) return operator<<('0'); if(i < 0) { operator<<('-'); i = -i; } char buf[MAXSTRLEN+1]; char* ptr = buf+MAXSTRLEN; *ptr-- = 0; while(i) { *ptr-- = i % 10 + '0'; i /= 10; } return operator<<(ptr+1); } nullmailer-2.2/lib/fdbuf/fdobuf_unsigned.cc000066400000000000000000000021301336020433200210150ustar00rootroot00000000000000// Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "fdbuf.h" #include #define MAXSTRLEN ((sizeof(signed long)*CHAR_BIT)/3) fdobuf& fdobuf::operator<<(unsigned long i) { if(i == 0) return operator<<('0'); char buf[MAXSTRLEN+1]; char* ptr = buf+MAXSTRLEN; *ptr-- = 0; while(i) { *ptr-- = i % 10 + '0'; i /= 10; } return operator<<(ptr+1); } nullmailer-2.2/lib/fdbuf/tlsibuf.cc000066400000000000000000000023601336020433200173310ustar00rootroot00000000000000// Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "tlsibuf.h" /////////////////////////////////////////////////////////////////////////////// // Class tlsibuf /////////////////////////////////////////////////////////////////////////////// tlsibuf::tlsibuf(gnutls_session_t s, unsigned bufsz) : fdibuf(-1, false, bufsz), session(s) { flags &= ~flag_closed; } ssize_t tlsibuf::_read(char* buf, ssize_t len) { int rc; do { rc = gnutls_record_recv(session, buf, len); } while (rc == GNUTLS_E_AGAIN); return rc; } nullmailer-2.2/lib/fdbuf/tlsibuf.h000066400000000000000000000021151336020433200171710ustar00rootroot00000000000000// Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #ifndef FDBUF__TLSIBUF__H__ #define FDBUF__TLSIBUF__H__ #include "fdibuf.h" #include class tlsibuf : public fdibuf { public: tlsibuf(gnutls_session_t, unsigned bufsz = FDBUF_SIZE); protected: gnutls_session_t session; virtual ssize_t _read(char* buf, ssize_t len); }; #endif // FDBUF__TLSIBUF__H__ nullmailer-2.2/lib/fdbuf/tlsobuf.cc000066400000000000000000000022651336020433200173430ustar00rootroot00000000000000// Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "tlsobuf.h" /////////////////////////////////////////////////////////////////////////////// // Class tlsobuf /////////////////////////////////////////////////////////////////////////////// tlsobuf::tlsobuf(gnutls_session_t s, unsigned bufsz) : fdobuf(-1, false, bufsz), session(s) { flags &= ~flag_closed; } ssize_t tlsobuf::_write(const char* buf, ssize_t len) { return gnutls_record_send(session, buf, len); } nullmailer-2.2/lib/fdbuf/tlsobuf.h000066400000000000000000000021241336020433200171770ustar00rootroot00000000000000// Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #ifndef FDBUF__TLSOBUF__H__ #define FDBUF__TLSOBUF__H__ #include "fdobuf.h" #include class tlsobuf : public fdobuf { public: tlsobuf(gnutls_session_t, unsigned bufsz = FDBUF_SIZE); protected: gnutls_session_t session; virtual ssize_t _write(const char* buf, ssize_t len); }; #endif // FDBUF__TLSOBUF__H__ nullmailer-2.2/lib/forkexec.cc000066400000000000000000000103051336020433200163770ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include "config.h" #include #include #include #include #include #include "cli++/cli++.h" #include "fdbuf/fdbuf.h" #include "mystring/mystring.h" #include "defines.h" #include "errcodes.h" #include "forkexec.h" #define ERR(MSG) do{ ferr << cli_program << ": " << MSG << ": " << strerror(errno) << endl; } while(0) #define FAIL(MSG) do{ ERR(MSG); return false; }while(0) static int fdnull = -1; fork_exec::fork_exec(const char* p) : pid(-1), name(p) { } fork_exec::~fork_exec() { wait(); } bool fork_exec::operator!() const { return pid < 0; } bool fork_exec::start(const char* args[], int redirn, int redirs[]) { autoclose_pipe pipes[redirn]; for (int i = 0; i < redirn; i++) { if (redirs[i] == REDIRECT_PIPE_TO || redirs[i] == REDIRECT_PIPE_FROM) if (!pipes[i].open()) FAIL("Could not create pipe"); if (redirs[i] == REDIRECT_NULL) if (fdnull < 0) if ((fdnull = open("/dev/null", O_RDWR)) < 0) FAIL("Could not open \"/dev/null\""); } if ((pid = fork()) < 0) FAIL("Could not fork"); if (pid == 0) { // Child process, exec program for (int i = 0; i < redirn; i++) { int r = redirs[i]; if (r == REDIRECT_NULL) dup2(fdnull, i); else if (r == REDIRECT_PIPE_FROM) { dup2(pipes[i][1], i); pipes[i].close(); } else if (r == REDIRECT_PIPE_TO) { dup2(pipes[i][0], i); pipes[i].close(); } else if (r > 0) { dup2(r, i); if (r >= redirn) close(r); } } execv(args[0], (char**)args); ERR("Could not exec " << name); _exit(ERR_EXEC_FAILED); } for (int i = 0; i < redirn; i++) { if (redirs[i] == REDIRECT_PIPE_TO) redirs[i] = pipes[i].extract(1); else if (redirs[i] == REDIRECT_PIPE_FROM) redirs[i] = pipes[i].extract(0); } return true; } bool fork_exec::start(const char* program, int redirn, int redirs[]) { const char* args[2] = { program, NULL }; return start(args, redirn, redirs); } int fork_exec::wait_status() { if (pid > 0) { int status; if (waitpid(pid, &status, 0) == pid) { pid = -1; return status; } } return -1; } bool fork_exec::wait() { if (pid > 0) { int status = wait_status(); if (status < 0) FAIL("Error catching the return value from " << name); if (WIFEXITED(status)) { status = WEXITSTATUS(status); if (status) { ferr << cli_program << ": " << name << " failed: " << status << endl; return false; } } else FAIL(name << " crashed or was killed"); } return true; } mystring program_path(const char* program) { return CONFIG_PATH(BIN, NULL, program); } static const char* nqpath() { static mystring cache; if (!cache) { const char* env; if ((env = getenv("NULLMAILER_QUEUE")) != NULL) cache = env; else cache = CONFIG_PATH(SBIN, NULL, "nullmailer-queue"); } return cache.c_str(); } queue_pipe::queue_pipe() : fork_exec("nullmailer-queue") { } int queue_pipe::start() { int redirs[] = { REDIRECT_PIPE_TO, REDIRECT_NULL, REDIRECT_NULL }; if (!fork_exec::start(nqpath(), 3, redirs)) return -1; return redirs[0]; } nullmailer-2.2/lib/forkexec.h000066400000000000000000000014441336020433200162450ustar00rootroot00000000000000#ifndef NULLMAILER__FORK_EXEC__H #define NULLMAILER__FORK_EXEC__H #include #include #include #include "mystring/mystring.h" #include "autoclose.h" #include "configio.h" mystring program_path(const char* program); #define REDIRECT_NONE -1 #define REDIRECT_NULL -2 #define REDIRECT_PIPE_FROM -3 #define REDIRECT_PIPE_TO -4 class fork_exec { private: pid_t pid; const char* name; public: fork_exec(const char*); ~fork_exec(); bool operator!() const; bool start(const char* args[], int redirn, int redirs[]); bool start(const char* program, int redirn, int redirs[]); bool wait(); int wait_status(); inline void kill(int sig) { ::kill(pid, sig); } }; class queue_pipe : public fork_exec { public: queue_pipe(); int start(); }; #endif nullmailer-2.2/lib/hostname.cc000066400000000000000000000030221336020433200164050ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include "config.h" #include "mystring/mystring.h" #include "configio.h" #include "hostname.h" #include "canonicalize.h" mystring me; mystring defaulthost; mystring defaultdomain; void read_hostnames() { int nome; nome = 0; if (!config_read("me", me)) { nome = 1; me = "me"; } if (!config_read("defaultdomain", defaultdomain)) defaultdomain = nome ? "defaultdomain" : me.c_str(); if (!config_read("defaulthost", defaulthost)) defaulthost = nome ? "defaulthost" : me.c_str(); canonicalize(defaulthost); } nullmailer-2.2/lib/hostname.h000066400000000000000000000002651336020433200162550ustar00rootroot00000000000000#ifndef NULLMAILER__HOSTNAME__H__ #define NULLMAILER__HOSTNAME__H__ extern mystring me; extern mystring defaulthost; extern mystring defaultdomain; void read_hostnames(); #endif nullmailer-2.2/lib/itoa.cc000066400000000000000000000006071336020433200155310ustar00rootroot00000000000000#include "itoa.h" const char *itoa(long v, int digits) { static char buf[INTLENGTH]; bool neg = false; if(v < 0) { v = -v; neg = true; } char* ptr = buf + INTLENGTH; *--ptr = '\0'; do { *--ptr = (v % 10) + '0'; v /= 10; --digits; } while(v != 0); while(digits > 0 && ptr > buf-1) *--ptr = '0', --digits; if(neg) *--ptr = '-'; return ptr; } nullmailer-2.2/lib/itoa.h000066400000000000000000000002771336020433200153760ustar00rootroot00000000000000#ifndef ITOA__H__ #define ITOA__H__ #ifndef INTLENGTH #define INTLENGTH 64 /* 40 digits is long enough to handle unsigned 128-bit numbers */ #endif const char *itoa(long, int = 0); #endif nullmailer-2.2/lib/list.h000066400000000000000000000061151336020433200154120ustar00rootroot00000000000000#ifndef LIST__H__ #define LIST__H__ template struct list_node { list_node* next; T data; list_node(T d, list_node* n = 0) : next(n), data(d) { } ~list_node() { } }; template class list_iterator; template class const_list_iterator; template class list { public: typedef list_node node; typedef list_iterator iter; typedef const_list_iterator const_iter; friend class list_iterator; friend class const_list_iterator; list() : head(0), tail(0), cnt(0) { } list(const list&); ~list() { empty(); } unsigned count() const { return cnt; } void empty() { while(head) { node* next = head->next; delete head; head = next; } tail = 0; cnt = 0; } bool append(T data) { node* n = new node(data); if(tail) tail->next = n; else head = n; tail = n; ++cnt; return true; } bool prepend(T data) { head = new node(data, head); if(!tail) tail = head; ++cnt; return true; } bool remove(iter&); private: node* head; node* tail; unsigned cnt; }; template class const_list_iterator { friend class list; private: inline void go_next() { prev = curr; if(curr) curr = curr->next; } public: const_list_iterator(const list& l) : lst(l), prev(0), curr(l.head) { } void operator++() { go_next(); } void operator++(int) { go_next(); } T operator*() const { return curr->data; } bool operator!() const { return curr == 0; } operator bool() const { return !operator!(); } private: const list& lst; // g++ 3.2 insists const typename list::node* prev; const typename list::node* curr; }; template list::list(const list& that) : head(0), tail(0), cnt(0) { for(const_iter i = that; i; ++i) append(*i); } template class list_iterator { friend class list; private: inline void go_next() { prev = curr; if(curr) curr = curr->next; } public: list_iterator(list& l) : lst(l), prev(0), curr(l.head) { } void operator++() { go_next(); } void operator++(int) { go_next(); } T operator*() const { return curr->data; } T& operator*() { return curr->data; } bool operator!() const { return curr == 0; } operator bool() const { return !operator!(); } private: list& lst; typename list::node* prev; typename list::node* curr; }; template bool list::remove(list::iter& iter) { if(this != &iter.lst) return false; if(!iter.curr) return false; if(iter.prev) { iter.prev->next = iter.curr->next; if(iter.curr == tail) tail = iter.prev; delete iter.curr; iter.curr = iter.prev->next; } else { head = iter.curr->next; if(!head) tail = 0; delete iter.curr; iter.curr = head; } --cnt; return true; } #endif // LIST__H__ nullmailer-2.2/lib/listtest.cc000066400000000000000000000020401336020433200164410ustar00rootroot00000000000000#include #include "list.h" typedef list ilist; typedef ilist::iter iiter; void test_remove_first() { ilist l; l.append(1); l.append(2); iiter i(l); l.remove(i); if(!i) cout << "After removing first, iter no longer valid\n"; else if(*i != 2) cout << "After removing first, iter is wrong\n"; if(l.count() != 1) cout << "After removing first, count is wrong\n"; } void test_remove_mid() { ilist l; l.append(1); l.append(2); l.append(3); iiter i(l); i++; l.remove(i); if(!i) cout << "After removing middle, iter no longer valid\n"; else if(*i != 3) cout << "After removing middle, iter is wrong\n"; if(l.count() != 2) cout << "After removing middle, count is wrong\n"; } void test_remove_last() { ilist l; l.append(1); l.append(2); iiter i(l); i++; l.remove(i); if(i) cout << "After removing last, iter is still valid\n"; if(l.count() != 1) cout << "After removing last, count is wrong\n"; } int main() { test_remove_first(); test_remove_mid(); test_remove_last(); return 0; } nullmailer-2.2/lib/make_defines.sh000066400000000000000000000004031336020433200172260ustar00rootroot00000000000000#!/bin/sh ( echo "extern const char QUEUE_DIR[]=\"$1\";" echo "extern const char CONFIG_DIR[]=\"$2\";" echo "extern const char PROTOCOLS_DIR[]=\"$3\";" echo "extern const char BIN_DIR[]=\"$4\";" echo "extern const char SBIN_DIR[]=\"$5\";" ) > defines.cc nullmailer-2.2/lib/makefield.cc000066400000000000000000000045701336020433200165210ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include "config.h" #include "defines.h" #include #include "ac/time.h" #include "itoa.h" #include "mystring/mystring.h" mystring make_date(time_t t) { char buf[256]; if (t == 0) t = time(0); struct tm* l = localtime(&t); strftime(buf, 256, "%a, %d %b %Y %H:%M:%S ", l); #ifdef TM_HAS_GMTOFF long tznum = l->TM_HAS_GMTOFF/60; #else long tznum = -timezone/60; #ifdef TM_HAS_ISDST int daylight = l->TM_HAS_ISDST; #endif // TM_HAS_ISDST if(daylight) tznum += 60; #endif // TM_HAS_GMTOFF char tz[6]; tz[0] = '+'; if(tznum < 0) { tznum = -tznum; tz[0] = '-'; } long tzhours = tznum / 60; tz[1] = (tzhours/10)%10 + '0'; tz[2] = tzhours%10 + '0'; long tzmins = tznum % 60; tz[3] = (tzmins/10)%10 + '0'; tz[4] = tzmins%10 + '0'; tz[5] = 0; return mystringjoin(buf) + tz; } // Message ID strings have the form SECONDS.USEC.PID.nullmailer@HOST mystring make_messageid(const mystring& idhost) { struct timeval tv; gettimeofday(&tv, 0); mystring tmp = "<"; tmp += itoa(tv.tv_sec); tmp += '.'; tmp += itoa(tv.tv_usec, 6); tmp += '.'; tmp += itoa(getpid()); tmp += ".nullmailer@"; tmp += idhost; tmp += '>'; return tmp; } mystring make_boundary() { struct timeval tv; gettimeofday(&tv, 0); mystring tmp = itoa(tv.tv_sec); tmp += '.'; tmp += itoa(tv.tv_usec, 6); tmp += '.'; tmp += itoa(getpid()); return tmp; } nullmailer-2.2/lib/makefield.h000066400000000000000000000003571336020433200163620ustar00rootroot00000000000000#ifndef NULLMAILER__MAKEFIELD__H__ #define NULLMAILER__MAKEFIELD__H__ extern mystring make_date(time_t t = 0); extern mystring make_messageid(const mystring& idhost); extern mystring make_boundary(); #endif // NULLMAILER__MAKEFIELD__H__ nullmailer-2.2/lib/mergelib.sh000066400000000000000000000004771336020433200164150ustar00rootroot00000000000000set -e archive="$1" shift tmpdir=".libmerge.$archive.$$.$RANDOM.$USER" mkdir "$tmpdir" cd "$tmpdir" trap 'cd ..; rm -rf "$tmpdir"' EXIT for input in "$@"; do dir="`basename "$input"`" mkdir "$dir" cd "$dir" "${AR:-ar}" x ../../"$input" cd .. done "${AR:-ar}" rc ../"$archive" */* "${RANLIB:-ranlib}" ../"$archive" nullmailer-2.2/lib/mystring/000077500000000000000000000000001336020433200161375ustar00rootroot00000000000000nullmailer-2.2/lib/mystring/Makefile.am000066400000000000000000000006611336020433200201760ustar00rootroot00000000000000noinst_LIBRARIES = libmystring.a EXTRA_DIST = ChangeLog iter.h join.h mystring.h rep.h trace.h AM_CPPFLAGS = -I$(top_srcdir)/lib libmystring_a_SOURCES = \ append.cc \ assign.cc \ count.cc \ fdobuf.cc \ find_first_ch.cc \ find_first_of.cc \ find_last_ch.cc \ find_last_of.cc \ iter.cc \ join.cc \ lower.cc \ lstrip.cc \ mystring.cc \ rep.cc \ starts_with.cc \ rstrip.cc \ sub.cc \ subst.cc \ strip.cc \ upper.cc nullmailer-2.2/lib/mystring/append.cc000066400000000000000000000004571336020433200177230ustar00rootroot00000000000000#include #include "mystring.h" #include "trace.h" void mystring::append(const char* str, size_t len) { if(!str || !len) return; if(!*this) assign(str, len); else rep = rep->append(str, len); } void mystring::append(const char* in) { if(in) append(in, strlen(in)); } nullmailer-2.2/lib/mystring/assign.cc000066400000000000000000000015221336020433200177320ustar00rootroot00000000000000#include "mystring.h" #include "trace.h" #include #include void mystring::dupnil() { trace(""); rep = &nil; rep->attach(); } void mystring::assign(const char* in) { if(in) assign(in, strlen(in)); else { mystringrep* tmp = rep; dupnil(); tmp->detach(); } } void mystring::assign(const char* in, size_t len) { trace("in='" << in << "'"); if(in != rep->buf) { mystringrep* tmp = rep; dup(in, len); tmp->detach(); } } void mystring::dup(const char* in, size_t len) { trace("in='" << in << "'"); rep = mystringrep::dup(in, len); rep->attach(); } void mystring::dup(const char* in) { if(in) dup(in, strlen(in)); else dupnil(); } void mystring::operator=(const mystringjoin& in) { mystringrep* tmp = rep; rep = in.traverse(); rep->attach(); tmp->detach(); } nullmailer-2.2/lib/mystring/count.cc000066400000000000000000000016701336020433200176020ustar00rootroot00000000000000// Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "mystring.h" unsigned mystring::count(char ch) const { unsigned c = 0; for(int pos = find_first(ch); pos > 0; pos = find_first(ch, pos+1)) ++c; return c; } nullmailer-2.2/lib/mystring/fdobuf.cc000066400000000000000000000002401336020433200177070ustar00rootroot00000000000000#include "mystring.h" #include "fdbuf/fdbuf.h" fdobuf& operator<<(fdobuf& out, const mystring& str) { out.write(str.c_str(), str.length()); return out; } nullmailer-2.2/lib/mystring/find_first_ch.cc000066400000000000000000000003371336020433200212520ustar00rootroot00000000000000#include "mystring.h" #include int mystring::find_first(char ch, size_t offset) const { if(offset >= rep->length) return -1; char* ptr = strchr(rep->buf+offset, ch); return ptr ? ptr-rep->buf : -1; } nullmailer-2.2/lib/mystring/find_first_of.cc000066400000000000000000000010401336020433200212540ustar00rootroot00000000000000#include "mystring.h" #include int mystring::find_first_of(const char* setstr, size_t setlen, size_t offset) const { for(; offset < rep->length; offset++) { if(memchr(setstr, rep->buf[offset], setlen)) return offset; } return -1; } int mystring::find_first_of(const char* setstr, size_t offset) const { return find_first_of(setstr, strlen(setstr), offset); } int mystring::find_first_of(const mystring& setstr, size_t offset) const { return find_first_of(setstr.rep->buf, setstr.rep->length, offset); } nullmailer-2.2/lib/mystring/find_last_ch.cc000066400000000000000000000004261336020433200210650ustar00rootroot00000000000000#include "mystring.h" int mystring::find_last(char ch, size_t offset) const { if(offset == (size_t)-1) offset = rep->length-1; const char* ptr = rep->buf + offset; while(ptr >= rep->buf) { if(*ptr == ch) return ptr - rep->buf; --ptr; } return -1; } nullmailer-2.2/lib/mystring/find_last_of.cc000066400000000000000000000011021336020433200210670ustar00rootroot00000000000000#include "mystring.h" #include int mystring::find_last_of(const char* setstr, size_t setlen, size_t offset) const { if(offset == (size_t)-1) offset = rep->length-1; for(int i = offset; i >= 0; --i) { if(memchr(setstr, rep->buf[i], setlen)) return i; } return -1; } int mystring::find_last_of(const char* setstr, size_t offset) const { return find_last_of(setstr, strlen(setstr), offset); } int mystring::find_last_of(const mystring& setstr, size_t offset) const { return find_last_of(setstr.rep->buf, setstr.rep->length, offset); } nullmailer-2.2/lib/mystring/iter.cc000066400000000000000000000023721336020433200174150ustar00rootroot00000000000000// Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "mystring.h" mystring_iter::mystring_iter(const mystring& s, char e) : str(s), sep(e), pos(0) { advance(); } mystring_iter::~mystring_iter() { } void mystring_iter::advance() { if(pos == -1) return; int i = str.find_first(sep, pos); if(i == -1) { if(pos >= 0 && pos < (int)str.length()) { part = str.right(pos); pos = str.length(); } else { part = ""; pos = -1; } } else { part = str.sub(pos, i-pos); pos = i + 1; } } nullmailer-2.2/lib/mystring/iter.h000066400000000000000000000023451336020433200172570ustar00rootroot00000000000000/* $Id: iter.h 616 2005-08-19 20:11:01Z bruce $ */ // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #ifndef MYSTRING__ITER__H__ #define MYSTRING__ITER__H__ class mystring_iter { const mystring str; const char sep; int pos; mystring part; void advance(); public: mystring_iter(const mystring&, char = '\0'); ~mystring_iter(); operator bool() const { return pos >= 0; } bool operator!() const { return pos < 0; } mystring operator*() const { return part; } void operator++() { advance(); } }; #endif nullmailer-2.2/lib/mystring/join.cc000066400000000000000000000035001336020433200174030ustar00rootroot00000000000000#include "mystring.h" #include // This "join" class relies on one fairly obscure detail in the C++ // standard: temporaries are destructed only after the entire // "full-expression" has completed. That is, if the sequence: // f(f(f(x))) creates three temporary objects, the inner objects are // destroyed only after the execution has completed. This allows us // to build a complete linked-list on the stack. Tricky, but efficient! struct tmpitem { const char* str; unsigned len; }; mystringrep* mystringjoin::traverse() const { // At first glance, a recursive implementation would be the most logical // way of doing this, but it turned out to be a significant loss. This // method traverses the pointer chain to first determine the length, and // then to do the actual copying. // Note the use of do/while loops to avoid a test at the head of the loop // which will always succeed (there is always at least one node or item). unsigned count = 0; const mystringjoin* node = this; do { ++count; } while((node = node->prev) != 0); // The use of a temporary array avoids re-traversing the pointer // chain, which is a slight performance win. tmpitem items[count]; unsigned length = 0; node = this; tmpitem* item = items; do { unsigned l = node->rep ? node->rep->length : strlen(node->str); length += l; item->str = node->str; item->len = l; ++item; } while((node = node->prev) != 0); // Since the chain is constructed such that the last item is the // first node, the string gets constructed in reverse order. mystringrep* rep = mystringrep::alloc(length); char* buf = rep->buf + length; item = items; do { unsigned l = item->len; buf -= l; memcpy(buf, item->str, l); ++item; } while(--count != 0); rep->buf[length] = 0; return rep; } nullmailer-2.2/lib/mystring/join.h000066400000000000000000000036221336020433200172520ustar00rootroot00000000000000/* $Id: join.h 616 2005-08-19 20:11:01Z bruce $ */ // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #ifndef MYSTRING__JOIN__H__ #define MYSTRING__JOIN__H__ class mystringjoin { private: const mystringjoin* prev; mystringrep* rep; const char* str; mystringjoin(); public: mystringjoin(const mystringjoin& j) : prev(j.prev), rep(j.rep), str(j.str) { rep->attach(); } mystringjoin(const mystring& s) : prev(0), rep(s.rep), str(s.rep->buf) { rep->attach(); } mystringjoin(const char* s) : prev(0), rep(0), str(s) { } mystringjoin(const mystringjoin& p, const mystring& s) : prev(&p), rep(s.rep), str(s.rep->buf) { rep->attach(); } mystringjoin(const mystringjoin& p, const char* s) : prev(&p), rep(0), str(s) { } ~mystringjoin() { if(rep) rep->detach(); } mystringrep* traverse() const; }; inline mystring::mystring(const mystringjoin& j) : rep(j.traverse()) { rep->attach(); } inline mystringjoin operator+(const mystringjoin& a, const mystring& b) { return mystringjoin(a, b); } inline mystringjoin operator+(const mystringjoin& a, const char* b) { return mystringjoin(a, b); } #endif nullmailer-2.2/lib/mystring/lower.cc000066400000000000000000000006461336020433200176040ustar00rootroot00000000000000#include "mystring.h" #include mystring mystring::lower() const { const unsigned length = rep->length; char buf[length+1]; const char* in = rep->buf + length; bool changed = false; for(char* out = buf+length; out >= buf; in--, out--) if(isupper(*in)) *out = tolower(*in), changed = true; else *out = *in; if(!changed) return *this; else return mystring(buf, length); } nullmailer-2.2/lib/mystring/lstrip.cc000066400000000000000000000002461336020433200177650ustar00rootroot00000000000000#include "mystring.h" #include mystring mystring::lstrip() const { const char* ptr = rep->buf; while(*ptr && isspace(*ptr)) ++ptr; return ptr; } nullmailer-2.2/lib/mystring/mystring.cc000066400000000000000000000007401336020433200203230ustar00rootroot00000000000000#include "mystring.h" #include "trace.h" #include #include #ifdef MYSTRING_TRACE mystring::~mystring() { trace("rep=" << (void*)rep); rep->detach(); } #endif int mystring::operator!=(const char* in) const { if(rep->buf == in) return 0; return strcmp(rep->buf, in); } int mystring::operator!=(const mystring& in) const { if(rep->buf == in.rep->buf) return 0; return strcmp(rep->buf, in.rep->buf); } const mystring mystring::NUL("", 1); nullmailer-2.2/lib/mystring/mystring.h000066400000000000000000000070471336020433200201740ustar00rootroot00000000000000/* $Id: mystring.h 635 2005-11-02 17:37:50Z bruce $ */ // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #ifndef MYSTRING__H__ #define MYSTRING__H__ #include #include "mystring/rep.h" class mystringjoin; class mystring { friend class mystringtmp; friend class mystringjoin; private: mystringrep* rep; protected: void dupnil(); void dup(const char*, size_t); void dup(const char*); void assign(const char*); void assign(const char*, size_t); public: static const mystring NUL; mystring() { dupnil(); } mystring(const char* s) { dup(s); } mystring(const mystring& s) { dup(s.rep->buf, s.rep->length); } mystring(const char* str, size_t len) { dup(str, len); } mystring(const mystringjoin&); ~mystring(); const char* c_str() const { return rep->buf; } bool operator!() const { return empty(); } char operator[](size_t i) const { return rep->buf[i]; } size_t length() const { return rep->length; } bool empty() const { return rep->length == 0; } int operator!=(const char* in) const; int operator!=(const mystring& in) const; bool operator==(const char* in) const { return !operator!=(in); } bool operator==(const mystring& in) const { return !operator!=(in); } void operator=(const char* in) { assign(in); } void operator=(const mystring& in) { assign(in.rep->buf, in.rep->length); } void operator=(const mystringjoin& in); mystring subst(char from, char to) const; mystring lower() const; mystring upper() const; bool starts_with(const mystring&) const; bool starts_with(const char*) const; bool starts_with(const char*, size_t) const; int find_first(char, size_t = 0) const; int find_first_of(const mystring&, size_t = 0) const; int find_first_of(const char*, size_t = 0) const; int find_first_of(const char*, size_t, size_t) const; int find_last(char, size_t = (size_t)-1) const; int find_last_of(const mystring&, size_t = (size_t)-1) const; int find_last_of(const char*, size_t = 0) const; int find_last_of(const char*, size_t, size_t) const; mystring left(size_t) const; mystring right(size_t) const; mystring sub(size_t, size_t) const; mystring lstrip() const; mystring rstrip() const; mystring strip() const; unsigned count(char ch) const; void append(const char*); void append(const char*, size_t); void operator+=(const mystring& str) {append(str.rep->buf, str.rep->length);} void operator+=(const char* str) { append(str); } void operator+=(char ch) { char str[2] = { ch, 0 }; append(str, 1); } }; #ifndef MYSTRING_TRACE inline mystring::~mystring() { rep->detach(); } #endif #include "mystring/iter.h" #include "mystring/join.h" class fdobuf; fdobuf& operator<<(fdobuf& out, const mystring& str); //istream& operator>>(istream& in, mystring& str); typedef mystring string; #endif nullmailer-2.2/lib/mystring/operator_in.cc000066400000000000000000000014171336020433200207720ustar00rootroot00000000000000#include "mystring.h" #include istream& operator>>(istream& in, mystring& str) { str = ""; char buf[256]; // buffer this many characters at a time unsigned i = 0; in >> ws; // skip leading whitespace char ch; while((ch = in.get()) != EOF) { if(isspace(ch)) { // end the input on whitespace buf[i] = 0; str += buf; i = 0; break; } else { buf[i++] = ch; if(i == sizeof(buf)-1) { // append the filled buffer and empty it buf[i] = 0; str += buf; i = 0; } } } if(i > 0) { // If EOF was reached before whitespace, buf[i] = 0; // append the buffer to the string. str += buf; } else if(str.length() == 0) // Mark failure if no string was read. in.set(ios::failbit); return in; } nullmailer-2.2/lib/mystring/rep.cc000066400000000000000000000071701336020433200172410ustar00rootroot00000000000000#include "mystring.h" #include "trace.h" #include #include mystringrep nil = { 0, 1, 1, "" }; static const unsigned replength = sizeof(unsigned)*3; static const unsigned sizestep = sizeof(unsigned); static const unsigned slackdiv = 4; static const unsigned slackmax = 16; #ifdef MYSTRINGREP_STATS #include "fdbuf.h" struct _rep_stats { unsigned allocs; unsigned alloc_size; unsigned alloc_len; unsigned appends; unsigned appends_dup; _rep_stats() : allocs(0) { } void stat(const char* name, unsigned value) { ferr << "mystringrep: " << name << ": " << value << '\n'; } void pcnt(const char* name, unsigned denom, unsigned divis) { ferr << "mystringrep: " << name << ": " << denom << '/' << divis << '='; if(divis) ferr << denom * 100 / divis << '%'; else ferr << "N/A"; ferr << '\n'; } ~_rep_stats() { stat(" size step", sizestep); stat(" slack divisor", slackdiv); stat(" slack maximum", slackmax); stat(" allocs", allocs); stat(" alloc length", alloc_len); stat(" alloc size", alloc_size); pcnt(" alloc slack", alloc_size-alloc_len, alloc_len); stat("alloc overhead", allocs*replength); pcnt(" appends->dup", appends_dup, appends); } }; static _rep_stats stats; #define ACCOUNT(NAME,VALUE) stats. NAME += VALUE #else // MYSTRINGREP_STATS #define ACCOUNT(NAME,VALUE) #endif // MYSTRINGREP_STATS /////////////////////////////////////////////////////////////////////////////// // class mystringrep /////////////////////////////////////////////////////////////////////////////// mystringrep* mystringrep::alloc(unsigned length) { ACCOUNT(allocs, 1); trace_static("length=" << length); if(length == 0) return &nil; ACCOUNT(alloc_len, length); unsigned slack = length / slackdiv; if(slack > slackmax) slack = slackmax; unsigned size = length+1 + sizestep-1 + slack; size = size - size % sizestep; ACCOUNT(alloc_size, size); mystringrep* ptr = (mystringrep*)new char[size+replength]; ptr->length = length; ptr->references = 0; ptr->size = size; return ptr; } mystringrep* mystringrep::dup(const char* str, unsigned length) { trace_static("str=" << (void*)str << " length=" << length); if(length == 0) return &nil; mystringrep* ptr = alloc(length); memcpy(ptr->buf, str, length); ptr->buf[length] = 0; return ptr; } mystringrep* mystringrep::dup(const char* str1, unsigned length1, const char* str2, unsigned length2) { trace_static(""); if(length1+length2 == 0) return &nil; mystringrep* ptr = alloc(length1+length2); memcpy(ptr->buf, str1, length1); memcpy(ptr->buf+length1, str2, length2); ptr->buf[length1+length2] = 0; return ptr; } mystringrep* mystringrep::append(const char* str, unsigned len) { ACCOUNT(appends, 1); unsigned newlen = length + len; // If there are more than one references, always make a duplicate // Also, if this does not have enough space to add the new string, dup it if(references > 1 || newlen >= size) { ACCOUNT(appends_dup, 1); mystringrep* tmp = dup(buf, length, str, len); tmp->attach(); detach(); return tmp; } // Otherwise, just add the new string to the end of this else { memcpy(buf+length, str, len); buf[newlen] = 0; length = newlen; return this; } } #ifdef MYSTRING_TRACE void mystringrep::attach() { trace("references=" << references); ++references; } #endif void mystringrep::detach() { trace("references=" << references); --references; if(!references) { trace("deleting this"); delete this; } } nullmailer-2.2/lib/mystring/rep.h000066400000000000000000000025041336020433200170770ustar00rootroot00000000000000/* $Id: rep.h 616 2005-08-19 20:11:01Z bruce $ */ // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #ifndef MYSTRING__REP__H__ #define MYSTRING__REP__H__ struct mystringrep { unsigned length; unsigned references; unsigned size; char buf[1]; void attach(); void detach(); mystringrep* append(const char*, unsigned); static mystringrep* alloc(unsigned); static mystringrep* dup(const char*, unsigned); static mystringrep* dup(const char*, unsigned, const char*, unsigned); }; #ifndef MYSTRING_TRACE inline void mystringrep::attach() { references++; } #endif extern mystringrep nil; #endif nullmailer-2.2/lib/mystring/rstrip.cc000066400000000000000000000003421336020433200177700ustar00rootroot00000000000000#include "mystring.h" #include mystring mystring::rstrip() const { const char* ptr = rep->buf + rep->length - 1; while(ptr >= rep->buf && isspace(*ptr)) --ptr; return mystring(rep->buf, ptr-rep->buf+1); } nullmailer-2.2/lib/mystring/starts_with.cc000066400000000000000000000006041336020433200210210ustar00rootroot00000000000000#include "mystring.h" #include bool mystring::starts_with(const char* that, size_t len) const { return len <= rep->length && memcmp(that, rep->buf, len) == 0; } bool mystring::starts_with(const char* that) const { return starts_with(that, strlen(that)); } bool mystring::starts_with(const mystring& that) const { return starts_with(that.rep->buf, that.rep->length); } nullmailer-2.2/lib/mystring/strip.cc000066400000000000000000000004501336020433200176060ustar00rootroot00000000000000#include "mystring.h" #include mystring mystring::strip() const { const char* start = rep->buf; while(*start && isspace(*start)) ++start; const char* end = rep->buf + rep->length - 1; while(end >= start && isspace(*end)) --end; return mystring(start, end-start+1); } nullmailer-2.2/lib/mystring/sub.cc000066400000000000000000000015231336020433200172400ustar00rootroot00000000000000#include "mystring.h" // return the sub-string ending at 'offset' mystring mystring::left(size_t offset) const { if(offset > rep->length) return *this; else return mystring(rep->buf, offset); } // return the sub-string starting at 'offset' mystring mystring::right(size_t offset) const { if(offset >= rep->length) return mystring(); else if(offset == 0) return *this; else return mystring(rep->buf+offset, rep->length-offset); } // return the 'len' characters of the string starting at 'offset' mystring mystring::sub(size_t offset, size_t len) const { // return right(offset).left(len); if(len == 0) return mystring(); else if(offset == 0 && len >= rep->length) return *this; else { if(len+offset >= rep->length) len = rep->length - offset; return mystring(rep->buf+offset, len); } } nullmailer-2.2/lib/mystring/subst.cc000066400000000000000000000006311336020433200176060ustar00rootroot00000000000000#include "mystring.h" mystring mystring::subst(char from, char to) const { const unsigned length = rep->length; char buf[length+1]; const char* in = rep->buf + length; bool changed = true; for(char* out = buf+length; out >= buf; in--, out--) if(*in == from) *out = to, changed = true; else *out = *in; if(!changed) return *this; else return mystring(buf, length); } nullmailer-2.2/lib/mystring/trace.h000066400000000000000000000006021336020433200174040ustar00rootroot00000000000000/* $Id: trace.h 616 2005-08-19 20:11:01Z bruce $ */ #include "mystring.h" #ifdef MYSTRING_TRACE ostream& operator<<(ostream& out, const mystringtmp& s); #define trace(X) cerr << (void*)this << "->" << __PRETTY_FUNCTION__ << X << endl #define trace_static(X) cerr << __PRETTY_FUNCTION__ << X << endl #else #define trace(X) do { } while(0) #define trace_static(X) do { } while(0) #endif nullmailer-2.2/lib/mystring/upper.cc000066400000000000000000000006671336020433200176120ustar00rootroot00000000000000#include "mystring.h" #include mystring mystring::upper() const { const unsigned length = rep->length; char buf[length+1]; const char* in = rep->buf + length; bool changed = false; for(char* out = buf+length; out >= buf; in--, out--) if(islower(*in)) { *out = toupper(*in); changed = true; } else *out = *in; if(!changed) return *this; else return mystring(buf, length); } nullmailer-2.2/lib/netstring.cc000066400000000000000000000004111336020433200166030ustar00rootroot00000000000000#include "config.h" #include "netstring.h" #include "itoa.h" mystring str2net(const mystring& s) { return mystringjoin(itoa(s.length())) + ":" + s + ","; } mystring strnl2net(const mystring& s) { return mystringjoin(itoa(s.length()+1)) + ":" + s + "\012,"; } nullmailer-2.2/lib/netstring.h000066400000000000000000000003241336020433200164500ustar00rootroot00000000000000#ifndef NULLMAILER__NETSTRING__H__ #define NULLMAILER__NETSTRING__H__ #include "mystring/mystring.h" mystring str2net(const mystring&); mystring strnl2net(const mystring&); #endif // NULLMAILER__NETSTRING__H__ nullmailer-2.2/lib/selfpipe.cc000066400000000000000000000046741336020433200164140ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include #include #include #include #include #include #include "selfpipe.h" static int fds[2] = { -1, -1 }; static int fcntl_fl_on(int fd, int flag) { int flags; int newflags; if ((flags = fcntl(fd, F_GETFL, 0)) == -1) return 0; if ((newflags = flags | flag) != flags) if (fcntl(fd, F_SETFL, newflags)) return 0; return 1; } selfpipe::selfpipe() { if (fds[0] < 0) { if (pipe(fds) != -1) { if (fcntl_fl_on(fds[0], O_NONBLOCK) && fcntl_fl_on(fds[1], O_NONBLOCK) && fcntl_fl_on(fds[1], FD_CLOEXEC) && fcntl_fl_on(fds[1], FD_CLOEXEC)) return; close(fds[0]); close(fds[1]); } fds[0] = fds[1] = -1; } } selfpipe::operator bool() const { return fds[0] >= 0; } static void catcher(int sig) { signal(sig, catcher); write(fds[1], &sig, sizeof sig); } void selfpipe::catchsig(int sig) { signal(sig, catcher); } int selfpipe::caught() { int buf; if (read(fds[0], &buf, sizeof buf) == sizeof buf) return buf; return 0; } int selfpipe::waitsig(int timeout) { if (timeout > 0) { fd_set fdset; FD_ZERO(&fdset); FD_SET(fds[0], &fdset); struct timeval tv; tv.tv_sec = timeout; tv.tv_usec = 0; int s; while ((s = select(fds[0] + 1, &fdset, 0, 0, (timeout <= 0) ? 0 : &tv)) == -1) { if (errno != EINTR) return -1; } if (s != 1) return 0; } return caught(); } nullmailer-2.2/lib/selfpipe.h000066400000000000000000000003671336020433200162510ustar00rootroot00000000000000#ifndef NULLMAILER_SELFPIPE__H__ #define NULLMAILER_SELFPIPE__H__ class selfpipe { public: selfpipe(); operator bool() const; void catchsig(int sig); int caught(); int waitsig(int timeout = 0); }; #endif // NULLMAILER_SELFPIPE__H__ nullmailer-2.2/lib/setenv.cc000066400000000000000000000027701336020433200161040ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include #include #include #include "setenv.h" #ifndef HAVE_SETENV // This is not really a full emulation of setenv, but close enough int setenv(const char* var, const char* val, int overwrite) { size_t varlen = strlen(var); size_t vallen = strlen(val); char* str = (char*)malloc(varlen+vallen+2); if (str == 0) return -1; memcpy(str, var, varlen); str[varlen] = '='; memcpy(str+varlen+1, val, vallen); str[varlen+vallen+1] = 0; return putenv(str); } #endif nullmailer-2.2/lib/setenv.h000066400000000000000000000001771336020433200157450ustar00rootroot00000000000000#ifndef NULLMAILER__SETENV__H__ #define NULLMAILER__SETENV__H__ extern "C" int setenv(const char*, const char*, int); #endif nullmailer-2.2/lib/tcpconnect.cc000066400000000000000000000113101336020433200167260ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include "config.h" #include #include #include #include #include #include #include #include "errcodes.h" #include "itoa.h" #include "connect.h" static int err_return(int errn, int dflt) { if (errn == HOST_NOT_FOUND) return -ERR_HOST_NOT_FOUND; if (errn == NO_ADDRESS) return -ERR_NO_ADDRESS; if (errn == NO_RECOVERY || errn == EAI_FAIL) return -ERR_GHBN_FATAL; if (errn == TRY_AGAIN || errn == EAI_AGAIN) return -ERR_GHBN_TEMP; if (errn == EAI_NONAME) return -ERR_HOST_NOT_FOUND; if (errn == ECONNREFUSED) return -ERR_CONN_REFUSED; if (errn == ETIMEDOUT) return -ERR_CONN_TIMEDOUT; if (errn == ENETUNREACH) return -ERR_CONN_UNREACHABLE; return -dflt; } #ifdef HAVE_GETADDRINFO static int getaddr(const char* hostname, int port, struct addrinfo** result) { const char *service = itoa(port, 6); struct addrinfo req; memset(&req, 0, sizeof(req)); req.ai_flags = AI_NUMERICSERV; req.ai_socktype = SOCK_STREAM; int e = getaddrinfo(hostname, service, &req, result); return e ? err_return(e, ERR_GHBN_TEMP) : 0; } static bool canbind(int family, const struct addrinfo* ai) { for (; ai; ai = ai->ai_next) if (ai->ai_family == family) return true; return false; } static bool bindit(int fd, int family, const struct addrinfo* ai) { for (; ai; ai = ai->ai_next) if (ai->ai_family == family) if (bind(fd, ai->ai_addr, ai->ai_addrlen) == 0) return true; return false; } int tcpconnect(const char* hostname, int port, const char* source) { struct addrinfo* res; int err = getaddr(hostname, port, &res); if (err) return err; struct addrinfo* source_addr = NULL; if (source) { err = getaddr(source, 0, &source_addr); if (err) return err; } int s = -1; err = ERR_CONN_FAILED; struct addrinfo* orig_res = res; if (source_addr) // Check if some address is the same family as the source for (; res != NULL; res = res->ai_next) if (canbind(res->ai_family, source_addr)) break; if (res == NULL) return -ERR_BIND_FAILED; for (; res != NULL; res = res->ai_next) { if (!source_addr || canbind(res->ai_family, source_addr)) { s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if(s > 0) { if(source_addr && !bindit(s, res->ai_family, source_addr)) { close(s); err = ERR_BIND_FAILED; s = -1; break; } if(connect(s, res->ai_addr, res->ai_addrlen) == 0) break; close(s); s = -1; } } } freeaddrinfo(orig_res); if (source_addr) freeaddrinfo(source_addr); if(s < 0) return err_return(errno, err); return s; } #else static int sethostbyname(const char* hostname, struct sockaddr_in& sa) { struct hostent *he = gethostbyname(hostname); if(!he) return err_return(h_errno, ERR_GHBN_TEMP); memcpy(&sa.sin_addr, he->h_addr, he->h_length); return 0; } int tcpconnect(const char* hostname, int port, const char* source) { struct sockaddr_in sa; memset(&sa, 0, sizeof(sa)); int e = sethostbyname(hostname, sa); if(e) return e; struct sockaddr_in source_sa; memset(&source_sa, 0, sizeof source_sa); if(source) { e = sethostbyname(source, source_sa); if(e) return e; } sa.sin_family = AF_INET; sa.sin_port = htons(port); int s = socket(PF_INET, SOCK_STREAM, 0); if(s == -1) return -ERR_SOCKET; if(source && bind(s, (sockaddr*)&source_sa, sizeof source_sa) != 0) { close(s); return err_return(errno, ERR_BIND_FAILED); } if(connect(s, (sockaddr*)&sa, sizeof(sa)) != 0) { close(s); return err_return(errno, ERR_CONN_FAILED); } return s; } #endif nullmailer-2.2/makedist.in000066400000000000000000000024041336020433200156460ustar00rootroot00000000000000# Template makedist.in file # Set PKGURL to the full base URL for the package web site. PKGURL=http://untroubled.org/nullmailer/ LISTURL='http://lists.untroubled.org/?list=nullmailer' # If LISTSUB is set, makedist will add a note regarding mailing list # subscription. LISTSUB=nullmailer-subscribe@lists.untroubled.org # Set MAKERPM to true if the tarball is to be built into a RPM. MAKERPM=false NOARCH=false # Set DOCS to the list of files that should go into the "docs" directory # in the destination site. DOCS= # Set EXTRAS to a list of any other extra files that should go into the # base directory in the destination site. EXTRAS="ChangeLog ChangeLog.old HOWTO NEWS README TODO" # Set EXTRAS_VER to a list of any other extra files that should go into # the version-numbered directory in the destination site. EXTRAS_VER="ANNOUNCEMENT" EXTRAS_DIST= VERSION_BASE= WEBSITE=untroubled.org WEBDIR=www/nullmailer # Set RPMUPLOAD to a list of additional "hostname/path" destinations to # which to upload the source and binary RPMs. #RPMUPLOAD="incoming.redhat.com/libc6" # Set LIST to the mailing list(s) to send the announcement to LIST=nullmailer@lists.untroubled.org # Run any extra commands to prepare the source tree (such as making # documentation) here. make -C doc nullmailer-2.2/protocols/000077500000000000000000000000001336020433200155415ustar00rootroot00000000000000nullmailer-2.2/protocols/Makefile.am000066400000000000000000000007351336020433200176020ustar00rootroot00000000000000libexecdir = @libexecdir@/nullmailer libexec_PROGRAMS = smtp qmqp AM_CPPFLAGS = -I$(top_srcdir)/lib if TLS TLS_SOURCES = tls_gnutls.cc TLS_LDADD = -lgnutls else TLS_SOURCES = tls_none.cc TLS_LDADD = endif smtp_SOURCES = smtp.cc protocol.cc $(TLS_SOURCES) protocol.h smtp_LDADD = ../lib/cli++/libcli++.a ../lib/libnullmailer.a $(TLS_LDADD) qmqp_SOURCES = qmqp.cc protocol.cc $(TLS_SOURCES) protocol.h qmqp_LDADD = ../lib/cli++/libcli++.a ../lib/libnullmailer.a $(TLS_LDADD) nullmailer-2.2/protocols/protocol.cc000066400000000000000000000115441336020433200177160ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include #include #include #include "connect.h" #include "errcodes.h" #include "list.h" #include "mystring/mystring.h" #include "protocol.h" #include "cli++/cli++.h" const char* user = 0; const char* pass = 0; int port = 0; int auth_method = AUTH_DETECT; int use_tls = 0; int use_starttls = 0; const char* remote = 0; const char* source = 0; const char* cli_help_suffix = ""; const char* cli_args_usage = "< options 3< mail-file"; const int cli_args_min = 0; const int cli_args_max = 0; cli_option cli_options[] = { { 0, "host", cli_option::string, 0, &remote, "Set the hostname for the remote", 0 }, { 'p', "port", cli_option::integer, 0, &port, "Set the port number on the remote host to connect to", 0 }, { 0, "user", cli_option::string, 0, &user, "Set the user name for authentication", 0 }, { 0, "pass", cli_option::string, 0, &pass, "Set the password for authentication", 0 }, { 0, "auth-login", cli_option::flag, AUTH_LOGIN, &auth_method, "Use AUTH LOGIN instead of auto-detecting in SMTP", 0 }, { 0, "source", cli_option::string, 0, &source, "Source address for connections", 0 }, #ifdef HAVE_TLS { 0, "tls", cli_option::flag, 1, &use_tls, "Connect using TLS (on an alternate port by default)", 0 }, { 0, "ssl", cli_option::flag, 1, &use_tls, "Alias for --tls", 0 }, { 0, "starttls", cli_option::flag, 1, &use_starttls, "Use STARTTLS command", 0 }, { 0, "x509certfile", cli_option::string, 0, &tls_x509certfile, "Client certificate file", 0 }, { 0, "x509keyfile", cli_option::string, 0, &tls_x509keyfile, "Client certificate private key file", "the same file as --x509certfile" }, { 0, "x509cafile", cli_option::string, 0, &tls_x509cafile, "Certificate authority trust file", DEFAULT_CA_FILE }, { 0, "x509crlfile", cli_option::string, 0, &tls_x509crlfile, "Certificate revocation list file", 0 }, { 0, "x509fmtder", cli_option::flag, true, &tls_x509derfmt, "X.509 files are in DER format", "PEM format" }, { 0, "insecure", cli_option::flag, true, &tls_insecure, "Don't abort if server certificate fails validation", 0 }, { 0, "tls-anon-auth", cli_option::flag, true, &tls_anon_auth, "Use TLS anonymous authentication - needs --insecure option", 0 }, #endif {0, 0, cli_option::flag, 0, 0, 0, 0} }; void protocol_fail(int e, const char* msg) { fout << msg << endl; ferr << cli_program << ": Failed: " << msg << endl; exit(e); } void protocol_succ(const char* msg) { fout << msg << endl; ferr << cli_program << ": Succeeded: " << msg << endl; exit(0); } static void plain_send(fdibuf& in, int fd) { fdibuf netin(fd); fdobuf netout(fd); if (!netin || !netout) protocol_fail(ERR_MSG_TEMPFAIL, "Error allocating I/O buffers"); if (use_starttls) { protocol_starttls(netin, netout); tls_send(in, fd); } else protocol_send(in, netin, netout); } static void parse_option(mystring line) { if (line[0] != '-') line = "--" + line; const char* arg = strdup(line.c_str()); if (arg == NULL) exit(ERR_CONFIG); const char* args[3] = { argv0, arg, NULL }; if (cli_parse_args(2, (char**)args) != 2) exit(ERR_CONFIG); } static void parse_options(void) { mystring line; while (fin.getline(line, '\n')) { if (line.length() == 0) return; parse_option(line); } if (!fin.eof()) exit(ERR_CONFIG); } int cli_main(int, char*[]) { parse_options(); if (remote == 0) protocol_fail(ERR_USAGE, "Remote host not set"); if (port == 0) port = use_tls ? default_tls_port : default_port; if (port < 0) protocol_fail(ERR_USAGE, "Invalid value for port"); if (use_tls || use_starttls) tls_init(remote); fdibuf in(3, true); protocol_prep(in); int fd = tcpconnect(remote, port, source); if(fd < 0) protocol_fail(-fd, "Connect failed"); if (use_tls) tls_send(in, fd); else plain_send(in, fd); return 0; } nullmailer-2.2/protocols/protocol.h000066400000000000000000000017561336020433200175640ustar00rootroot00000000000000#ifndef NULLMAILER__PROTOCOL__H__ #define NULLMAILER__PROTOCOL__H__ #include "fdbuf/fdbuf.h" #define DEFAULT_CA_FILE "/etc/ssl/certs/ca-certificates.crt" extern const int default_port; extern const int default_tls_port; extern void protocol_fail(int e, const char* msg); extern void protocol_succ(const char* msg); #define AUTH_DETECT 0 #define AUTH_LOGIN 1 #define AUTH_PLAIN 2 extern const char* user; extern const char* pass; extern int auth_method; extern int port; extern int use_tls; extern int use_starttls; extern void protocol_prep(fdibuf& in); extern void protocol_send(fdibuf& in, fdibuf& netin, fdobuf& netout); extern void protocol_starttls(fdibuf& netin, fdobuf& netout); extern int tls_insecure; extern int tls_anon_auth; extern const char* tls_x509certfile; extern const char* tls_x509keyfile; extern const char* tls_x509cafile; extern const char* tls_x509crlfile; extern int tls_x509derfmt; extern void tls_init(const char* remote); extern void tls_send(fdibuf& in, int fd); #endif nullmailer-2.2/protocols/qmqp.cc000066400000000000000000000076531336020433200170410ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include "config.h" #include #include #include "errcodes.h" #include "fdbuf/fdbuf.h" #include "hostname.h" #include "itoa.h" #include "mystring/mystring.h" #include "netstring.h" #include "protocol.h" const int default_port = 628; const int default_tls_port = -1; // No standard for QMQP over SSL exists const char* cli_program = "qmqp"; const char* cli_help_prefix = "Send an email message via QMQP\n"; class qmqp { fdibuf& in; fdobuf& out; public: qmqp(fdibuf& netin, fdobuf& netout); ~qmqp(); void send(fdibuf& msg, unsigned long size, const mystring& env); }; qmqp::qmqp(fdibuf& netin, fdobuf& netout) : in(netin), out(netout) { } qmqp::~qmqp() { } bool skip_envelope(fdibuf& msg) { if(!msg.rewind()) return false; mystring tmp; while(msg.getline(tmp)) if(!tmp) break; return msg; } void qmqp::send(fdibuf& msg, unsigned long size, const mystring& env) { if(!skip_envelope(msg)) protocol_fail(ERR_MSG_READ, "Error re-reading message"); unsigned long fullsize = strlen(itoa(size)) + 1 + size + 1 + env.length(); out << itoa(fullsize) << ":"; // Start the "outer" netstring out << itoa(size) << ":"; // Start the message netstring fdbuf_copy(msg, out, true); // Send out the message out << "," // End the message netstring << env // The envelope is already encoded << ","; // End the "outer" netstring if(!out.flush()) protocol_fail(ERR_MSG_WRITE, "Error sending message to remote"); mystring response; if(!in.getnetstring(response)) protocol_fail(ERR_PROTO, "Response from remote was not a netstring"); switch(response[0]) { case 'K': protocol_succ(response.c_str()+1); break; case 'Z': protocol_fail(ERR_MSG_TEMPFAIL, response.c_str()+1); break; case 'D': protocol_fail(ERR_MSG_PERMFAIL, response.c_str()+1); break; default: protocol_fail(ERR_PROTO, "Invalid status byte in response"); } } bool compute_size(fdibuf& msg, unsigned long& size) { char buf[4096]; size = 0; while(msg.read(buf, 4096)) size += msg.last_count(); if(msg.eof()) size += msg.last_count(); return size > 0; } bool make_envelope(fdibuf& msg, mystring& env) { mystring tmp; while(msg.getline(tmp)) { if(!tmp) return true; env += str2net(tmp); } return false; } bool preload_data(fdibuf& msg, unsigned long& size, mystring& env) { return make_envelope(msg, env) && compute_size(msg, size); } static unsigned long msg_size; static mystring msg_envelope; void protocol_prep(fdibuf& in) { if(!preload_data(in, msg_size, msg_envelope)) protocol_fail(ERR_MSG_READ, "Error reading message"); } void protocol_starttls(fdibuf& netin, fdobuf& netout) { protocol_fail(ERR_USAGE, "QMQP does not support STARTTLS"); (void)netin; (void)netout; } void protocol_send(fdibuf& in, fdibuf& netin, fdobuf& netout) { alarm(60*60); // Connection must close after an hour qmqp conn(netin, netout); conn.send(in, msg_size, msg_envelope); } nullmailer-2.2/protocols/smtp.cc000066400000000000000000000134031336020433200170340ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include "config.h" #include #include #include "base64.h" #include "connect.h" #include "errcodes.h" #include "fdbuf/fdbuf.h" #include "itoa.h" #include "mystring/mystring.h" #include "protocol.h" const int default_port = 25; const int default_tls_port = 465; const char* cli_program = "smtp"; const char* cli_help_prefix = "Send an email message via SMTP\n"; class smtp { fdibuf& in; fdobuf& out; mystring caps; public: smtp(fdibuf& netin, fdobuf& netout); ~smtp(); int get(mystring& str); int put(mystring cmd, mystring& result); void docmd(mystring cmd, int range, mystring& result, int permfail=ERR_MSG_PERMFAIL); void docmd(mystring cmd, int range, int permfail=ERR_MSG_PERMFAIL); void dohelo(bool ehlo); bool hascap(const char* name, const char* word = NULL); void auth_login(void); void auth_plain(void); void send_data(fdibuf& msg); void send_envelope(fdibuf& msg); void send(fdibuf& msg); }; smtp::smtp(fdibuf& netin, fdobuf& netout) : in(netin), out(netout) { } smtp::~smtp() { } int smtp::get(mystring& str) { mystring tmp; str = ""; int code = -1; while(in.getline(tmp)) { if(tmp[tmp.length()-1] == '\r') tmp = tmp.left(tmp.length()-1); code = atoi(tmp.c_str()); if(!!str) str += "\n"; str += tmp; if(tmp[3] != '-') break; } return code; } int smtp::put(mystring cmd, mystring& result) { out << cmd << "\r\n"; if(!out.flush()) return -1; return get(result); } void smtp::docmd(mystring cmd, int range, mystring& result, int permfail) { int code; if(!cmd) code = get(result); else code = put(cmd, result); if(code < range || code >= (range+100)) { int e; if(code >= 500) e = permfail; else if(code >= 400) e = ERR_MSG_TEMPFAIL; else e = ERR_PROTO; out << "QUIT\r\n"; out.flush(); protocol_fail(e, result.c_str()); } } void smtp::docmd(mystring cmd, int range, int permfail) { mystring msg; docmd(cmd, range, msg, permfail); } void smtp::dohelo(bool ehlo) { mystring hh = getenv("HELOHOST"); if (!hh) protocol_fail(1, "$HELOHOST is not set"); docmd((ehlo ? "EHLO " : "HELO ") + hh, 200, caps); } static int issep(char ch) { return ch == ' ' || ch == '\n' || ch == '\0'; } bool smtp::hascap(const char* name, const char* word) { const size_t namelen = strlen(name); int i = -1; do { const char* s = caps.c_str() + i + 5; if (strncasecmp(s, name, namelen) == 0) { if (s[namelen] == '\n') return word == NULL; else if (s[namelen] == ' ') { if (word == NULL) return true; s += namelen + 1; const size_t wordlen = strlen(word); do { if (strncasecmp(s, word, wordlen) == 0 && issep(s[wordlen])) return true; while (!issep(*s)) ++s; } while (*s++ == ' '); return false; } } i = caps.find_first('\n', i+1); } while (i > 0); return false; } void smtp::auth_login(void) { mystring encoded; base64_encode(user, encoded); docmd("AUTH LOGIN " + encoded, 300, ERR_AUTH_FAILED); encoded = ""; base64_encode(pass, encoded); docmd(encoded, 200); } void smtp::auth_plain(void) { mystring plain(user); plain += '\0'; plain += user; plain += '\0'; plain += pass; mystring encoded = "AUTH PLAIN "; base64_encode(plain, encoded); docmd(encoded, 200, ERR_AUTH_FAILED); } void smtp::send_envelope(fdibuf& msg) { mystring tmp; msg.getline(tmp); docmd("MAIL FROM:<" + tmp + ">", 200); while(msg.getline(tmp) && !!tmp) docmd("RCPT TO:<" + tmp + ">", 200); } void smtp::send_data(fdibuf& msg) { docmd("DATA", 300); mystring tmp; while(msg.getline(tmp)) { if((tmp[0] == '.' && !(out << ".")) || !(out << tmp << "\r\n")) protocol_fail(ERR_MSG_WRITE, "Error sending message to remote"); } docmd(".", 200, tmp); out << "QUIT\r\n"; out.flush(); protocol_succ(tmp.c_str()); } void smtp::send(fdibuf& msg) { send_envelope(msg); send_data(msg); } void protocol_prep(fdibuf&) { } static int did_starttls = 0; void protocol_starttls(fdibuf& netin, fdobuf& netout) { smtp conn(netin, netout); conn.docmd("", 200); conn.dohelo(true); conn.docmd("STARTTLS", 200); did_starttls = 1; } void protocol_send(fdibuf& in, fdibuf& netin, fdobuf& netout) { smtp conn(netin, netout); if (!did_starttls) conn.docmd("", 200); if (user != 0 && pass != 0) { conn.dohelo(true); if (auth_method == AUTH_LOGIN) conn.auth_login(); else if (auth_method == AUTH_PLAIN) conn.auth_plain(); else { // Detect method if (conn.hascap("AUTH", "PLAIN")) conn.auth_plain(); else if (conn.hascap("AUTH", "LOGIN")) conn.auth_login(); else protocol_fail(ERR_MSG_TEMPFAIL, "Server does not advertise any supported authentication methods"); } } else conn.dohelo(false); conn.send(in); } nullmailer-2.2/protocols/tls_gnutls.cc000066400000000000000000000146651336020433200202620ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include #include #include "errcodes.h" #include "mystring/mystring.h" #include "protocol.h" #include #include #include "fdbuf/tlsibuf.h" #include "fdbuf/tlsobuf.h" int tls_insecure = false; int tls_anon_auth = false; const char* tls_x509certfile = NULL; const char* tls_x509keyfile = NULL; const char* tls_x509cafile = NULL; const char* tls_x509crlfile = NULL; int tls_x509derfmt = false; static int gnutls_wrap(int ret, const char* msg) { if (ret < 0) { mystring m = msg; m += ": "; m += gnutls_strerror(ret); protocol_fail(ERR_MSG_TEMPFAIL, m.c_str()); } return ret; } static int cert_verify(gnutls_session_t session) { if (tls_x509cafile != NULL && !tls_insecure) { // Verify the certificate unsigned int status = 0; gnutls_wrap(gnutls_certificate_verify_peers2(session, &status), "Could not verify SSL/TLS certificate"); if (status != 0) protocol_fail(ERR_MSG_TEMPFAIL, "Server SSL/TLS certificate is untrusted"); // Verify the hostname unsigned int cert_list_size = 0; const gnutls_datum_t* cert_list = gnutls_certificate_get_peers(session, &cert_list_size); const char* hostname = (const char*)gnutls_session_get_ptr(session); gnutls_x509_crt_t crt; gnutls_wrap(gnutls_x509_crt_init(&crt), "Error allocating memory"); gnutls_wrap(gnutls_x509_crt_import(crt, &cert_list[0], GNUTLS_X509_FMT_DER), "Error decoding SSL/TLS certificate"); if (gnutls_x509_crt_check_hostname(crt, hostname) == 0) protocol_fail(ERR_MSG_TEMPFAIL, "Server SSL/TLS certificate does not match hostname"); gnutls_x509_crt_deinit(crt); } // All verification errors cause protocol to exit return 0; } static gnutls_session_t tls_session; void tls_cert_auth_init(const char* remote) { gnutls_certificate_credentials_t creds; gnutls_wrap(gnutls_global_init(), "Error initializing TLS library"); gnutls_wrap(gnutls_certificate_allocate_credentials(&creds), "Error allocating TLS certificate"); gnutls_wrap(gnutls_init(&tls_session, GNUTLS_CLIENT), "Error creating TLS session"); #ifdef HAVE_GNUTLS_PRIORITY_SET_DIRECT gnutls_wrap(gnutls_priority_set_direct(tls_session, "NORMAL", NULL), "Error setting TLS options"); #else gnutls_wrap(gnutls_set_default_priority(tls_session), "Error setting TLS options"); #endif gnutls_wrap(gnutls_credentials_set(tls_session, GNUTLS_CRD_CERTIFICATE, creds), "Error setting TLS credentials"); gnutls_wrap(gnutls_server_name_set(tls_session, GNUTLS_NAME_DNS, remote, strlen(remote)), "Error setting TLS server name"); gnutls_session_set_ptr(tls_session, (void*)remote); #ifdef HAVE_GNUTLS_SET_VERIFY_FUNCTION gnutls_certificate_set_verify_function(creds, cert_verify); #endif gnutls_certificate_set_verify_flags(creds, 0); gnutls_x509_crt_fmt_t x509fmt = tls_x509derfmt ? GNUTLS_X509_FMT_DER : GNUTLS_X509_FMT_PEM; if (tls_x509keyfile == NULL) tls_x509keyfile = tls_x509certfile; if (tls_x509certfile != NULL) gnutls_wrap(gnutls_certificate_set_x509_key_file(creds, tls_x509certfile, tls_x509keyfile, x509fmt), "Error setting SSL/TLS X.509 client certificate"); if (tls_x509cafile == NULL && access(DEFAULT_CA_FILE, R_OK) == 0) tls_x509cafile = DEFAULT_CA_FILE; if (tls_x509cafile != NULL) gnutls_wrap(gnutls_certificate_set_x509_trust_file(creds, tls_x509cafile, x509fmt), "Error loading SSL/TLS X.509 trust file"); if (tls_x509crlfile != NULL) gnutls_wrap(gnutls_certificate_set_x509_crl_file(creds, tls_x509crlfile, x509fmt), "Error loading SSL/TLS X.509 CRL file"); } void tls_anon_auth_init(const char* remote) { gnutls_anon_client_credentials_t anon_creds; gnutls_wrap(gnutls_global_init(), "Error initializing TLS library"); gnutls_wrap(gnutls_anon_allocate_client_credentials(&anon_creds), "Error allocating TLS anonymous client credentials"); gnutls_wrap(gnutls_init(&tls_session, GNUTLS_CLIENT), "Error creating TLS session"); #ifdef HAVE_GNUTLS_PRIORITY_SET_DIRECT gnutls_wrap(gnutls_priority_set_direct(tls_session, "NORMAL:+ANON-ECDH:+ANON-DH", NULL), "Error setting TLS options"); #else gnutls_wrap(gnutls_set_default_priority(tls_session), "Error setting TLS options"); #endif gnutls_wrap(gnutls_credentials_set(tls_session, GNUTLS_CRD_ANON, anon_creds), "Error setting TLS credentials"); gnutls_wrap(gnutls_server_name_set(tls_session, GNUTLS_NAME_DNS, remote, strlen(remote)), "Error setting TLS server name"); gnutls_session_set_ptr(tls_session, (void*)remote); } void tls_init(const char* remote) { if ((tls_x509certfile || tls_x509keyfile || tls_x509cafile || tls_x509crlfile) && (tls_anon_auth)) { protocol_fail(ERR_MSG_TEMPFAIL, "Error: TLS certificate and TLS anonymous auth options cannot both be specified"); } if (tls_anon_auth && tls_insecure) { tls_anon_auth_init(remote); } else { tls_cert_auth_init(remote); } } void tls_send(fdibuf& in, int fd) { int r; gnutls_transport_set_ptr(tls_session, (gnutls_transport_ptr_t)(long)fd); do { r = gnutls_handshake(tls_session); if (gnutls_error_is_fatal(r)) gnutls_wrap(r, "Error completing TLS handshake"); } while (r < 0); #ifndef HAVE_GNUTLS_SET_VERIFY_FUNCTION cert_verify(tls_session); #endif tlsibuf tlsin(tls_session); tlsobuf tlsout(tls_session); if (!tlsin || !tlsout) protocol_fail(ERR_MSG_TEMPFAIL, "Error allocating I/O buffers"); protocol_send(in, tlsin, tlsout); } nullmailer-2.2/protocols/tls_none.cc000066400000000000000000000024421336020433200176730ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include "errcodes.h" #include "protocol.h" void tls_init(const char* remote) { protocol_fail(ERR_USAGE, "SSL/TLS not supported in this build"); (void)remote; } void tls_send(fdibuf& in, int fd) { protocol_fail(ERR_USAGE, "SSL/TLS not supported in this build"); (void)in; (void)fd; } nullmailer-2.2/scripts/000077500000000000000000000000001336020433200152045ustar00rootroot00000000000000nullmailer-2.2/scripts/nullmailer-log.run000066400000000000000000000001021336020433200206460ustar00rootroot00000000000000#! /bin/sh exec setuidgid nullmail multilog t /var/log/nullmailer nullmailer-2.2/scripts/nullmailer.run000066400000000000000000000001071336020433200200740ustar00rootroot00000000000000#! /bin/sh exec 2>&1 exec setuidgid nullmail /usr/sbin/nullmailer-send nullmailer-2.2/scripts/nullmailer.service000066400000000000000000000006141336020433200207330ustar00rootroot00000000000000[Unit] Description=Nullmailer relay-only MTA Requires=network.target After=local-fs.target ConditionPathExists=/var/spool/nullmailer/queue ConditionPathExists=/etc/nullmailer/defaultdomain ConditionPathExists=/etc/nullmailer/me [Service] WorkingDirectory=/var/spool/nullmailer ExecStart=/usr/sbin/nullmailer-send User=nullmail Group=nullmail Restart=always [Install] WantedBy=multi-user.target nullmailer-2.2/spec000066400000000000000000000047461336020433200144050ustar00rootroot00000000000000Name: nullmailer Summary: Simple relay-only mail transport agent Version: @VERSION@ Release: 1 License: GPL Group: Networking/Daemons Source: http://untroubled.org/nullmailer/archive/%{version}/nullmailer-%{version}.tar.gz BuildRoot: /tmp/nullmailer-root URL: http://untroubled.org/nullmailer/ Packager: Bruce Guenter Provides: smtpdaemon Conflicts: sendmail Conflicts: qmail Requires: supervise-scripts >= 3.2 Requires: gnutls BuildRequires: gnutls-devel Requires(pre,preun): shadow-utils %description Nullmailer is a mail transport agent designed to only relay all its messages through a fixed set of "upstream" hosts. It is also designed to be secure. %prep %setup %build CFLAGS="$RPM_OPT_FLAGS" CXXFLAGS="$RPM_OPT_FLAGS" \ ./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --enable-tls make %install rm -fr $RPM_BUILD_ROOT mkdir -p $RPM_BUILD_ROOT/etc mkdir -p $RPM_BUILD_ROOT/usr/lib mkdir -p $RPM_BUILD_ROOT/var/service/nullmailer/log mkdir -p $RPM_BUILD_ROOT/var/log/nullmailer make DESTDIR=$RPM_BUILD_ROOT install-strip ln -s ../sbin/sendmail $RPM_BUILD_ROOT/usr/lib/sendmail install scripts/nullmailer.run $RPM_BUILD_ROOT/var/service/nullmailer/run install scripts/nullmailer-log.run $RPM_BUILD_ROOT/var/service/nullmailer/log/run %clean rm -rf $RPM_BUILD_ROOT %pre PATH="/sbin:/usr/sbin:$PATH" export PATH if [ "$1" = 1 ]; then # pre-install instructions grep ^nullmail: /etc/group >/dev/null || groupadd -r nullmail grep ^nullmail: /etc/passwd >/dev/null || useradd -d /var/lock/svc/nullmailer -g nullmail -M -r -s /bin/true nullmail fi %post if ! [ -L /service/nullmailer ]; then svc-add /var/service/nullmailer fi if ! [ -s /etc/nullmailer/me ]; then /bin/hostname --fqdn >/etc/nullmailer/me fi if ! [ -s /etc/nullmailer/defaultdomain ]; then /bin/hostname --domain >/etc/nullmailer/defaultdomain fi %preun if [ "$1" = 0 ]; then svc-remove nullmailer fi %postun if [ "$1" = 0 ]; then # post-erase instructions /usr/sbin/userdel nullmail /usr/sbin/groupdel nullmail fi %files %defattr(-,nullmail,nullmail) %doc AUTHORS BUGS ChangeLog COPYING INSTALL NEWS README TODO doc/DIAGRAM %dir /etc/nullmailer %attr(04711,nullmail,nullmail) /usr/bin/mailq /usr/bin/nullmailer-inject /usr/bin/nullmailer-smtpd /usr/lib/sendmail %dir /usr/libexec/nullmailer /usr/libexec/nullmailer/* %{_mandir}/*/* %attr(04711,nullmail,nullmail) /usr/sbin/nullmailer-queue /usr/sbin/nullmailer-send /usr/sbin/sendmail %dir /var/log/nullmailer /var/service/nullmailer /var/spool/nullmailer nullmailer-2.2/src/000077500000000000000000000000001336020433200143045ustar00rootroot00000000000000nullmailer-2.2/src/Makefile.am000066400000000000000000000015701336020433200163430ustar00rootroot00000000000000bin_PROGRAMS = \ mailq \ nullmailer-dsn \ nullmailer-inject \ nullmailer-smtpd sbin_PROGRAMS = \ nullmailer-queue \ nullmailer-send \ sendmail #noinst_PROGRAMS = address AM_CPPFLAGS = -I$(top_srcdir)/lib mailq_SOURCES = mailq.cc mailq_LDADD = ../lib/libnullmailer.a nullmailer_dsn_SOURCES = dsn.cc nullmailer_dsn_LDADD = ../lib/libnullmailer.a ../lib/cli++/libcli++.a nullmailer_inject_SOURCES = inject.cc nullmailer_inject_LDADD = ../lib/libnullmailer.a ../lib/cli++/libcli++.a nullmailer_queue_SOURCES = queue.cc nullmailer_queue_LDADD = ../lib/libnullmailer.a ../lib/cli++/libcli++.a nullmailer_send_SOURCES = send.cc nullmailer_send_LDADD = ../lib/libnullmailer.a ../lib/cli++/libcli++.a nullmailer_smtpd_SOURCES = smtpd.cc nullmailer_smtpd_LDADD = ../lib/libnullmailer.a sendmail_SOURCES = sendmail.cc sendmail_LDADD = ../lib/cli++/libcli++.a ../lib/libnullmailer.a nullmailer-2.2/src/address-main.cc000066400000000000000000000005701336020433200171640ustar00rootroot00000000000000#include "config.h" #include "mystring.h" #include "fdbuf.h" #include "address.h" int main(int argc, char* argv[]) { for(int i = 1; i < argc; i++) { mystring s(argv[i]); mystring l; if(!parse_addresses(s, l)) { fout.writeln("Parsing failed."); } else { fout.write(l); fout.write("To: "); fout.writeln(s); } } return 0; } nullmailer-2.2/src/dsn.cc000066400000000000000000000167231336020433200154100ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include "config.h" #include #include #include #include #include #include #include #include #include #include "cli++/cli++.h" #include "itoa.h" #include "defines.h" #include "list.h" #include "mystring/mystring.h" #include "fdbuf/fdbuf.h" #include "canonicalize.h" #include "configio.h" #include "hostname.h" #include "makefield.h" typedef list slist; static time_t opt_timestamp = 0; static time_t opt_last_attempt = 0; static time_t opt_retry_until = 0; static const char* opt_envelope_id = 0; static const char* opt_status = 0; static const char* opt_remote = 0; static const char* opt_diagnostic_code = 0; static int opt_lines = -1; static bool opt_ddn = false; const char* cli_program = "nullmailer-dsn"; const char* cli_help_prefix = "Reformat a queued message into a delivery status notification (DSN)\n"; const char* cli_help_suffix = "\n" "The status code must be in the form 4.#.# or 5.#.#. If the status\n" "code starts with 4, a delivery delay notification is generated.\n"; const char* cli_args_usage = "status-code < message"; const int cli_args_min = 1; const int cli_args_max = 1; cli_option cli_options[] = { { 0, "diagnostic-code", cli_option::string, 0, &opt_diagnostic_code, "Diagnostic code message", 0 }, { 0, "envelope-id", cli_option::string, 0, &opt_envelope_id, "Original envelope ID", 0 }, { 0, "last-attempt", cli_option::ulong, 0, &opt_last_attempt, "UNIX timestamp of the last attempt", "access time on the input message" }, { 0, "orig-timestamp", cli_option::ulong, 0, &opt_timestamp, "UNIX timestamp on the original message", "ctime on the input message" }, { 0, "remote", cli_option::string, 0, &opt_remote, "Name of remote server", 0 }, { 0, "retry-until", cli_option::ulong, 0, &opt_retry_until, "UNIX timestamp of the (future) final attempt", 0 }, { 0, "max-lines", cli_option::integer, 0, &opt_lines, "Maximum number of lines of the original message to copy", "none" }, {0, 0, cli_option::flag, 0, 0, 0, 0} }; #define die1sys(MSG) do{ fout << "nullmailer-dsn: " << MSG << strerror(errno) << endl; exit(111); }while(0) #define die1(MSG) do{ fout << "nullmailer-dsn: " << MSG << endl; exit(111); }while(0) static mystring sender; static mystring bounceto; static mystring doublebounceto; static mystring line; static slist recipients; static mystring idhost; static const mystring boundary = make_boundary(); int cli_main(int, char* argv[]) { struct stat msgstat; if (fstat(0, &msgstat) < 0) die1sys("Could not stat the source message"); if (opt_timestamp == 0) opt_timestamp = msgstat.st_ctime; if (opt_last_attempt == 0) opt_last_attempt = msgstat.st_atime; opt_status = argv[0]; if ((opt_status[0] != '4' && opt_status[0] != '5') || opt_status[1] != '.' || !isdigit(opt_status[2]) || opt_status[3] != '.' || !isdigit(opt_status[4]) || opt_status[5] != '\0') die1("Status must be in the format 4.#.# or 5.#.#"); opt_ddn = opt_status[0] == '4'; if (opt_lines < 0) { config_readint("bouncelines", opt_lines); if (opt_lines < 0) opt_lines = 0; } if (!config_read("doublebounceto", doublebounceto) || !doublebounceto) config_read("adminaddr", doublebounceto); read_hostnames(); if (!config_read("idhost", idhost)) idhost = me; else canonicalize(idhost); config_read("bounceto", bounceto); if (!fin.getline(sender)) die1sys("Could not read sender address from message: "); if (!sender && !doublebounceto) die1("Nowhere to send double bounce"); while (fin.getline(line)) { if (!line) break; recipients.append(line); } if (recipients.count() == 0) die1("No recipients were read from message"); if (!!sender) // Bounces either go to the sender or bounceto, if configured fout << '\n' << (!!bounceto ? bounceto : sender); else fout << "#@[]\n" << doublebounceto; fout << "\n" "\n" "From: Message Delivery Subsystem \n" "To: <" << sender << ">\n" "Subject: Returned mail: Could not send message\n" "Date: " << make_date() << "\n" "Message-Id: " << make_messageid(idhost) << "\n" "MIME-Version: 1.0\n" "Content-Type: multipart/report; report-type=delivery-status;\n" "\tboundary=\"" << boundary << "\"\n"; /* Human readable text portion */ fout << "\n" "--" << boundary << "\n" "Content-Type: text/plain; charset=us-ascii\n" "\n" "This is the nullmailer delivery system. The message attached below\n" << (opt_ddn ? "has not been" : "could not be") << " delivered to one or more of the intended recipients:\n" "\n"; for (slist::const_iter recipient(recipients); recipient; recipient++) fout << "\t<" << (*recipient) << ">\n"; if (opt_ddn) { if (opt_retry_until > 0) fout << "\nDelivery will continue to be attempted until " << make_date(opt_retry_until) << '\n'; fout << "\n" "A final delivery status notification will be generated if delivery\n" "proves to be impossible within the configured time limit.\n"; } /* delivery-status portion */ fout << "\n" "--" << boundary << "\n" "Content-Type: message/delivery-status\n" "\n" "Reporting-MTA: x-local-hostname; " << me << "\n" "Arrival-Date: " << make_date(opt_timestamp) << "\n"; if (opt_envelope_id != 0) fout << "Original-Envelope-Id: " << opt_envelope_id << '\n'; for (slist::const_iter recipient(recipients); recipient; recipient++) { fout << "\n" "Final-Recipient: rfc822; " << (*recipient) << "\n" "Action: " << (opt_ddn ? "delayed": "failed") << "\n" "Status: " << opt_status << "\n" "Last-Attempt-Date: " << make_date(opt_last_attempt) << '\n'; if (opt_remote != 0) fout << "Remote-MTA: dns; " << opt_remote << '\n'; if (opt_diagnostic_code != 0) fout << "Diagnostic-Code: " << opt_diagnostic_code << '\n'; if (opt_ddn and opt_retry_until > 0) fout << "Will-Retry-Until: " << make_date(opt_retry_until) << '\n'; } // Copy the message fout << "\n" "--" << boundary << "\n" "Content-Type: message/rfc822\n" "\n"; // Copy the header while (fin.getline(line) && !!line) fout << line << '\n'; // Optionally copy the body if (opt_lines) { fout << '\n'; for (int i = 0; (opt_lines < 0 || i < opt_lines) && fin.getline(line); i++) fout << line << '\n'; } fout << "\n" "--" << boundary << "--\n"; return 0; } nullmailer-2.2/src/inject.cc000066400000000000000000000360611336020433200160750ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include "config.h" #include "defines.h" #include #include #include #include #include #include #include #include "mystring/mystring.h" #include "list.h" #include "hostname.h" #include "fdbuf/fdbuf.h" #include "itoa.h" #include "address.h" #include "canonicalize.h" #include "configio.h" #include "cli++/cli++.h" #include "makefield.h" #include "forkexec.h" enum { use_args, use_both, use_either, use_header }; static int use_recips = use_either; static int show_message = false; static int show_envelope = false; static const char* o_from = 0; const char* cli_program = "nullmailer-inject"; const char* cli_help_prefix = "Reformat and inject a message into the nullmailer queue\n"; const char* cli_help_suffix = ""; const char* cli_args_usage = "[recipients] slist; // static bool do_debug = false; static mystring cur_line; /////////////////////////////////////////////////////////////////////////////// // Configuration /////////////////////////////////////////////////////////////////////////////// static mystring idhost; extern void canonicalize(mystring& domain); void read_config() { mystring tmp; read_hostnames(); if(!config_read("idhost", idhost)) idhost = me; else canonicalize(idhost); } /////////////////////////////////////////////////////////////////////////////// // Envelope processing /////////////////////////////////////////////////////////////////////////////// static slist recipients; static mystring sender; static bool use_header_recips = true; static bool use_header_sender = true; void parse_recips(const mystring& list) { if(!!list) { int start = 0; int end; while((end = list.find_first('\n', start)) >= 0) { recipients.append(list.sub(start, end-start)); start = end+1; } } } bool parse_recip_arg(mystring str) { mystring list; if(!parse_addresses(str, list)) return false; parse_recips(list); return true; } bool parse_sender(const mystring& list) { int end = list.find_first('\n'); if(end > 0 && list.find_first('\n', end+1) < 0) { sender = list.sub(0, end); return true; } return false; } /////////////////////////////////////////////////////////////////////////////// // Header processing /////////////////////////////////////////////////////////////////////////////// static slist headers; static bool header_is_resent = false; static bool header_has_errors = false; static bool header_add_to = false; struct header_field { // member information const char* name; unsigned length; bool is_address; bool is_recipient; bool is_sender; bool is_resent; bool remove; // remove means strip after parsing bool ignore; // ignore means do not parse bool present; bool parse(mystring& line, bool& rm) { if(strncasecmp(line.c_str(), name, length)) return false; rm = remove; if(ignore) return true; if(is_resent) { if(!header_is_resent) { if(use_header_sender) sender = ""; if(use_header_recips) recipients.empty(); } header_is_resent = true; } if(is_address) { mystring tmp = line.right(length); mystring list; if(!parse_addresses(tmp, list)) bad_hdr(line, "Unable to parse the addresses."); else if(!tmp) { rm = true; return true; } else { line = mystringjoin(name) + " " + tmp; if(is_recipient) { if(is_resent == header_is_resent && use_header_recips) parse_recips(list); } else if(is_sender) { if(is_resent == header_is_resent && use_header_sender) parse_sender(list); } } } present = true; return true; } }; #define F false #define T true #define X(N,IA,IR,IS,IRS,R) { #N ":",strlen(#N ":"),\ IA,IR,IS,IRS,R,false, false } static header_field header_fields[] = { // Sender address fields, in order of priority X(Sender, T,F,F,F,F), // 0 X(From, T,F,F,F,F), // 1 X(Reply-To, T,F,F,F,F), // 2 X(Return-Path, T,F,T,F,T), // 3 X(Return-Receipt-To, T,F,F,F,F), // 4 X(Errors-To, T,F,F,F,F), // 5 X(Resent-Sender, T,F,F,T,F), // 6 X(Resent-From, T,F,F,T,F), // 7 X(Resent-Reply-To, T,F,F,T,F), // 8 // Destination address fields X(To, T,T,F,F,F), // 9 X(Cc, T,T,F,F,F), // 10 X(Bcc, T,T,F,F,T), // 11 X(Apparently-To, T,T,F,F,F), // 12 X(Resent-To, T,T,F,T,F), // 13 X(Resent-Cc, T,T,F,T,F), // 14 X(Resent-Bcc, T,T,F,T,T), // 15 // Other fields of interest X(Date, F,F,F,F,F), // 16 X(Message-Id, F,F,F,F,F), // 17 X(Resent-Date, F,F,F,T,F), // 18 X(Resent-Message-Id, F,F,F,T,F), // 19 X(Content-Length, F,F,F,F,T), // 20 }; #undef X #undef F #undef T #define header_field_count (sizeof header_fields/sizeof(header_field)) static bool& header_has_from = header_fields[1].present; static bool& header_has_rfrom = header_fields[7].present; static bool& header_has_to = header_fields[9].present; static bool& header_has_cc = header_fields[10].present; static bool& header_has_rto = header_fields[13].present; static bool& header_has_rcc = header_fields[14].present; static bool& header_has_date = header_fields[16].present; static bool& header_has_mid = header_fields[17].present; static bool& header_has_rdate = header_fields[18].present; static bool& header_has_rmid = header_fields[19].present; static header_field& header_field_from = header_fields[1]; static header_field& header_field_mid = header_fields[17]; static header_field& header_field_rpath = header_fields[3]; static bool use_name_address_style = true; static mystring from; void setup_from() { mystring user = getenv("NULLMAILER_USER"); if(!user) user = getenv("MAILUSER"); if(!user) user = getenv("USER"); if(!user) user = getenv("LOGNAME"); if(!user) { struct passwd *pw = getpwuid(getuid()); if (pw) user = pw->pw_name; } if(!user) user = "unknown"; mystring host = getenv("NULLMAILER_HOST"); if(!host) host = getenv("MAILHOST"); if(!host) host = getenv("HOSTNAME"); if(!host) host = defaulthost; canonicalize(host); mystring name = getenv("NULLMAILER_NAME"); if(!name) name = getenv("MAILNAME"); if(!name) name = getenv("NAME"); if(!name) name = user; if(use_name_address_style) { if(!name) from = "<" + user + "@" + host + ">"; else from = name + " <" + user + "@" + host + ">"; } else { if(!name) from = user + "@" + host; else from = user + "@" + host + " (" + name + ")"; } mystring suser = getenv("NULLMAILER_SUSER"); if(!suser) suser = user; mystring shost = getenv("NULLMAILER_SHOST"); if(!shost) shost = host; canonicalize(shost); if(use_header_sender && !sender) sender = suser + "@" + shost; } bool parse_line(mystring& line) { bool valid = false; for(unsigned i = 0; i < line.length(); i++) { char ch = line[i]; if(isspace(ch)) break; if(ch == ':') { valid = (i > 0); break; } } if(!valid) //bad_hdr(line, "Missing field name."); return false; bool remove = false; for(unsigned i = 0; i < header_field_count; i++) { header_field& h = header_fields[i]; if(h.parse(line, remove)) break; } if(!remove) headers.append(line); return true; } bool is_header(const mystring& line) { for(unsigned i = 0; i < line.length(); i++) { char ch = line[i]; if(isspace(ch)) return false; if(ch == ':') return true; } return false; } bool is_continuation(const mystring& line) { return isspace(line[0]); } bool read_header() { mystring cur_line; mystring whole; bool first = true; for (;;) { if (!fin.getline(cur_line)) cur_line = ""; if(!cur_line || cur_line == "\r") break; // Ignore a leading RFC 976 "From " line mistakenly inserted by some programs. if(first && (cur_line.starts_with("From ") || cur_line.starts_with(">From "))) continue; first = false; if(!!whole && is_continuation(cur_line)) { //if(!whole) //bad_hdr(cur_line, "First line cannot be a continuation line."); //else whole += "\n" + cur_line; } else if(!is_header(cur_line)) { cur_line += '\n'; break; } else { if(!!whole) parse_line(whole); whole = cur_line; cur_line = ""; } } if(!!whole) parse_line(whole); return !header_has_errors; } mystring make_recipient_list() { mystring result; bool first = true; for(slist::iter iter(recipients); iter; iter++) { if(!first) result = result + ", " + *iter; else result = *iter; first = false; } return result; } bool fix_header() { setup_from(); if(!header_is_resent) { if(!header_has_date) headers.append("Date: " + make_date()); if(!header_has_mid) headers.append("Message-Id: " + make_messageid(idhost)); if(!header_has_from) headers.append("From: " + from); if(!header_has_to && !header_has_cc && header_add_to && recipients.count() > 0) { header_has_to = true; headers.append("To: " + make_recipient_list()); } } else { if(!header_has_rdate) headers.append("Resent-Date: " + make_date()); if(!header_has_rmid) headers.append("Resent-Message-Id: " + make_messageid(idhost)); if(!header_has_rfrom) headers.append("Resent-From: " + from); if(!header_has_rto && !header_has_rcc && header_add_to && recipients.count() > 0) { header_has_rto = true; headers.append("Resent-To: " + make_recipient_list()); } } if(!header_has_to && !header_has_cc) headers.append("Cc: recipient list not shown: ;"); return true; } /////////////////////////////////////////////////////////////////////////////// // Message sending /////////////////////////////////////////////////////////////////////////////// bool send_env(fdobuf& out) { if(!(out << sender << "\n")) fail("Error sending sender to nullmailer-queue."); for(slist::iter iter(recipients); iter; iter++) if(!(out << *iter << "\n")) fail("Error sending recipients to nullmailer-queue."); if(!(out << endl)) fail("Error sending recipients to nullmailer-queue."); return true; } bool send_header(fdobuf& out) { for(slist::iter iter(headers); iter; iter++) if(!(out << *iter << "\n")) fail("Error sending header to nullmailer-queue."); if(!(out << endl)) fail("Error sending header to nullmailer-queue."); return true; } bool send_body(fdobuf& out) { if(!(out << cur_line) || !fdbuf_copy(fin, out)) fail("Error sending message body to nullmailer-queue."); return true; } bool send_message_stdout() { if(show_envelope) send_env(fout); send_header(fout); send_body(fout); return true; } bool send_message_nqueue() { queue_pipe nq; autoclose wfd = nq.start(); if (wfd < 0) return false; fdobuf nqout(wfd); if (!send_env(nqout) || !send_header(nqout) || !send_body(nqout)) return false; nqout.flush(); wfd.close(); return nq.wait(); } bool send_message() { return show_message ? send_message_stdout() : send_message_nqueue(); } /////////////////////////////////////////////////////////////////////////////// // Main /////////////////////////////////////////////////////////////////////////////// bool parse_flags() { for(const char* flagstr = getenv("NULLMAILER_FLAGS"); flagstr && *flagstr; flagstr++) { switch(*flagstr) { case 'c': use_name_address_style=false; break; case 'f': header_field_from.ignore=header_field_from.remove=true; break; case 'i': header_field_mid.ignore=header_field_mid.remove=true; break; case 's': header_field_rpath.ignore=header_field_rpath.remove=true; break; case 't': header_add_to = true; break; default: // Just ignore any flags we can't handle break; } } return true; } bool parse_args(int argc, char* argv[]) { if(!parse_flags()) return false; if(o_from) { mystring list; mystring tmp(o_from); if(tmp == "" || tmp == "<>") sender = ""; else if(!parse_addresses(tmp, list) || !parse_sender(list)) { ferr << "nullmailer-inject: Invalid sender address: " << o_from << endl; return false; } use_header_sender = false; } use_header_recips = (use_recips != use_args); if(use_recips == use_header) return true; if(use_recips == use_either && argc > 0) use_header_recips = false; bool result = true; for(int i = 0; i < argc; i++) { if(!parse_recip_arg(argv[i])) { ferr << "Invalid recipient: " << argv[i] << endl; result = false; } } return result; } int cli_main(int argc, char* argv[]) { read_config(); if(!parse_args(argc, argv) || !read_header() || !fix_header()) return 1; if(recipients.count() == 0) { ferr << "No recipients were listed." << endl; return 1; } if(!send_message()) return 1; return 0; } nullmailer-2.2/src/mailq.cc000066400000000000000000000042661336020433200157260ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include "config.h" #include #include #include #include #include #include "configio.h" #include "defines.h" #include "fdbuf/fdbuf.h" #include "itoa.h" #include "mystring/mystring.h" #define fail(X) do{ fout << X << endl; return 1; }while(0) int main(int, char*[]) { mystring line; mystring msg_dir = CONFIG_PATH(QUEUE, NULL, "queue"); if(chdir(msg_dir.c_str())) fail("Cannot change directory to queue."); DIR* dir = opendir("."); if(!dir) fail("Cannot open queue directory."); struct dirent* entry; while((entry = readdir(dir)) != 0) { const char* name = entry->d_name; if(name[0] == '.') continue; time_t time = atoi(name); char timebuf[100]; strftime(timebuf, 100, "%Y-%m-%d %H:%M:%S ", localtime(&time)); fout << timebuf; struct stat statbuf; if(stat(name, &statbuf) == -1) fout << "?????"; else fout << itoa(statbuf.st_size); fout << " bytes"; fdibuf in(name); if (in.getline(line)) { fout << " from <" << line << '>'; while (in.getline(line) && !!line) fout << "\n to <" << line << '>'; } fout << endl; } closedir(dir); return 0; } nullmailer-2.2/src/queue.cc000066400000000000000000000126751336020433200157520ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include "config.h" #include #include #include #include #include #include #include "autoclose.h" #include "configio.h" #include "itoa.h" #include "defines.h" #include "mystring/mystring.h" #include "fdbuf/fdbuf.h" #include "configio.h" #include "hostname.h" const char* cli_program = "nullmailer-queue"; #define fail(MSG) do{ fout << "nullmailer-queue: " << MSG << endl; return false; }while(0) pid_t pid = getpid(); uid_t uid = getuid(); time_t timesecs = time(0); mystring adminaddr; mystring allmailfrom; static mystring trigger_path; static mystring msg_dir; static mystring tmp_dir; bool is_dir(const char* path) { struct stat buf; return !stat(path, &buf) && S_ISDIR(buf.st_mode); } bool is_exist(const char* path) { struct stat buf; return !stat(path, &buf); } int fsyncdir(const char* path) { autoclose fd = open(path, O_RDONLY); if(fd == -1) return 0; int result = fsync(fd); if(result == -1 && errno != EIO) result = 0; return result; } void trigger() { autoclose fd = open(trigger_path.c_str(), O_WRONLY|O_NONBLOCK, 0666); if(fd == -1) return; char x = 0; write(fd, &x, 1); } bool validate_addr(mystring& addr, bool recipient) { int i = addr.find_last('@'); if(i <= 0) return false; mystring hostname = addr.right(i+1); if (recipient && !!adminaddr && (hostname == me || hostname == "localhost")) addr = adminaddr; else if (!recipient && !!allmailfrom) addr = allmailfrom; else if(hostname.find_first('.') < 0) return false; return true; } bool copyenv(fdobuf& out) { mystring str; if(!fin.getline(str)) fail("Could not read envelope sender."); if(!!str && !validate_addr(str, false)) fail("Envelope sender address is invalid."); if(!(out << str << endl)) fail("Could not write envelope sender."); unsigned count=0; while(fin.getline(str) && !!str) { if(!validate_addr(str, true)) fail("Envelope recipient address is invalid."); if(!(out << str << endl)) fail("Could not write envelope recipient."); ++count; } if(count == 0) fail("No envelope recipients read."); if(!(out << "\n")) fail("Could not write extra blank line to destination."); return true; } bool makereceived(fdobuf& out) { mystring line("Received: (nullmailer pid "); line += itoa(pid); line += " invoked by uid "; line += itoa(uid); line += ");\n\t"; char buf[100]; if(!strftime(buf, 100, "%a, %d %b %Y %H:%M:%S -0000\n", gmtime(×ecs))) fail("Error generating a date string."); line += buf; if(!(out << line)) fail("Could not write received line to message."); return true; } bool dump(int fd) { fdobuf out(fd); if(!copyenv(out)) return false; if(!makereceived(out)) return false; if(!fdbuf_copy(fin, out)) fail("Error copying the message to the queue file."); if(!out.sync()) fail("Error flushing the output file."); if(!out.close()) fail("Error closing the output file."); return true; } bool deliver() { if(!is_dir(msg_dir.c_str()) || !is_dir(tmp_dir.c_str())) fail("Installation error: queue directory is invalid."); // Notes: // - temporary file name is unique to the currently running // nullmailer-queue program // - destination file name is unique to the system // - if the temporary file previously existed, it did so because // the previous nullmailer-queue process crashed, and it can be // safely overwritten const mystring pidstr = itoa(pid); const mystring tmpfile = tmp_dir + pidstr; const mystring newfile = msg_dir + itoa(timesecs) + "." + pidstr; int out = open(tmpfile.c_str(), O_CREAT|O_WRONLY|O_TRUNC, 0600); if(out < 0) fail("Could not open temporary file for writing"); if(!dump(out)) { unlink(tmpfile.c_str()); return false; } if(link(tmpfile.c_str(), newfile.c_str())) fail("Error linking the temp file to the new file."); if(fsyncdir(msg_dir.c_str())) fail("Error syncing the new directory."); if(unlink(tmpfile.c_str())) fail("Error unlinking the temp file."); return true; } int main(int, char*[]) { trigger_path = CONFIG_PATH(QUEUE, NULL, "trigger"); msg_dir = CONFIG_PATH(QUEUE, "queue", ""); tmp_dir = CONFIG_PATH(QUEUE, "tmp", ""); umask(077); if(config_read("adminaddr", adminaddr) && !!adminaddr) { adminaddr = adminaddr.subst(',', '\n'); read_hostnames(); } config_read("allmailfrom", allmailfrom); if(!deliver()) return 1; trigger(); return 0; } nullmailer-2.2/src/send.cc000066400000000000000000000307771336020433200155620ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "ac/time.h" #include "argparse.h" #include "autoclose.h" #include "configio.h" #include "defines.h" #include "errcodes.h" #include "fdbuf/fdbuf.h" #include "forkexec.h" #include "hostname.h" #include "itoa.h" #include "list.h" #include "selfpipe.h" #include "setenv.h" const char* cli_program = "nullmailer-send"; selfpipe selfpipe; typedef enum { tempfail=-1, permfail=0, success=1 } tristate; struct message { time_t timestamp; mystring filename; }; typedef list slist; typedef list msglist; #define msg1(MSG) do{ fout << MSG << endl; }while(0) #define msg2(MSG1,MSG2) do{ fout << MSG1 << MSG2 << endl; }while(0) #define msg1sys(MSG) do{ fout << MSG << strerror(errno) << endl; }while(0) #define fail(MSG) do { msg1(MSG); return false; } while(0) #define fail2(MSG1,MSG2) do{ msg2(MSG1,MSG2); return false; }while(0) #define fail1sys(MSG) do{ msg1sys(MSG); return false; }while(0) #define tempfail1sys(MSG) do{ msg1sys(MSG); return tempfail; }while(0) static mystring trigger_path; static mystring msg_dir; struct remote { static const mystring default_proto; mystring host; mystring proto; mystring program; mystring options; remote(const slist& list); ~remote(); }; const mystring remote::default_proto = "smtp"; remote::remote(const slist& lst) { slist::const_iter iter = lst; host = *iter; options = "host=" + host + "\n"; ++iter; if(!iter) proto = default_proto; else { proto = *iter; for(++iter; iter; ++iter) { mystring option = *iter; // Strip prefix "--" if (option[0] == '-' && option[1] == '-') option = option.right(2); options += option; options += '\n'; } } options += '\n'; program = CONFIG_PATH(PROTOCOLS, NULL, proto.c_str()); } remote::~remote() { } typedef list rlist; static rlist remotes; static int minpause = 60; static int pausetime = minpause; static int maxpause = 24*60*60; static int sendtimeout = 60*60; static int queuelifetime = 7*24*60*60; bool load_remotes() { slist rtmp; config_readlist("remotes", rtmp); remotes.empty(); for(slist::const_iter r(rtmp); r; r++) { if((*r)[0] == '#') continue; arglist parts; if (!parse_args(parts, *r)) continue; remotes.append(remote(parts)); } if (remotes.count() == 0) fail("No remote hosts listed for delivery"); return true; } bool load_config() { mystring hh; if (!config_read("helohost", hh)) hh = me; setenv("HELOHOST", hh.c_str(), 1); int oldminpause = minpause; if(!config_readint("pausetime", minpause)) minpause = 60; if(!config_readint("maxpause", maxpause)) maxpause = 24*60*60; if(!config_readint("sendtimeout", sendtimeout)) sendtimeout = 60*60; if(!config_readint("queuelifetime", queuelifetime)) queuelifetime = 7*24*60*60; if (minpause != oldminpause) pausetime = minpause; return load_remotes(); } static msglist messages; static bool reload_messages = false; void catch_alrm(int) { signal(SIGALRM, catch_alrm); reload_messages = true; } bool load_messages() { reload_messages = false; fout << "Rescanning queue." << endl; DIR* dir = opendir("."); if(!dir) fail1sys("Cannot open queue directory: "); messages.empty(); struct dirent* entry; while((entry = readdir(dir)) != 0) { const char* name = entry->d_name; if (name[0] == '.') continue; struct stat st; if (stat(name, &st) < 0) { fout << "Could not stat " << name << ", skipping." << endl; continue; } struct message m = { st.st_mtime, name }; messages.append(m); } closedir(dir); return true; } tristate catchsender(fork_exec& fp) { for (;;) { switch (selfpipe.waitsig(sendtimeout)) { case 0: // timeout fout << "Sending timed out, killing protocol" << endl; fp.kill(SIGTERM); selfpipe.waitsig(); // catch the signal from killing the child return tempfail; case -1: msg1sys("Error waiting for the child signal: "); return tempfail; case SIGCHLD: break; default: continue; } break; } int status = fp.wait_status(); if(status < 0) { fout << "Error catching the child process return value: " << strerror(errno) << endl; return tempfail; } else { if(WIFEXITED(status)) { status = WEXITSTATUS(status); if(status) { fout << "Sending failed: " << errorstr(status) << endl; return (status & ERR_PERMANENT_FLAG) ? permfail : tempfail; } else { fout << "Sent file." << endl; return success; } } else { fout << "Sending process crashed or was killed." << endl; return tempfail; } } } bool log_msg(mystring& filename, remote& remote, int fd) { fout << "Starting delivery:" << " host: " << remote.host << " protocol: " << remote.proto << " file: " << filename << endl; fdibuf in(fd); mystring line; mystring msg; if (in.getline(line, '\n')) { msg = "From: <"; msg += line; msg += '>'; bool has_to = false; while (in.getline(line, '\n')) { if (!line) break; msg += has_to ? ", " : " to: "; has_to = true; msg += '<'; msg += line; msg += '>'; } fout << msg << endl; while (in.getline(line, '\n')) { if (!line) break; if (line.left(11).lower() == "message-id:") fout << line << endl; } lseek(fd, 0, SEEK_SET); return true; } fout << endl << "Can't read message" << endl; return false; } static bool copy_output(int fd, mystring& output) { output = ""; char buf[256]; ssize_t rd; while ((rd = read(fd, buf, sizeof buf)) > 0) output += mystring(buf, rd); return rd == 0; } tristate send_one(mystring filename, remote& remote, mystring& output) { autoclose fd = open(filename.c_str(), O_RDONLY); if(fd < 0) { fout << "Can't open file '" << filename << "'" << endl; return tempfail; } log_msg(filename, remote, fd); fork_exec fp(remote.proto.c_str()); int redirs[] = { REDIRECT_PIPE_TO, REDIRECT_PIPE_FROM, REDIRECT_NONE, fd }; if (!fp.start(remote.program.c_str(), 4, redirs)) return tempfail; if (write(redirs[0], remote.options.c_str(), remote.options.length()) != (ssize_t)remote.options.length()) fout << "Warning: Writing options to protocol failed" << endl; close(redirs[0]); tristate result = catchsender(fp); if (!copy_output(redirs[1], output)) fout << "Warning: Could not read output from protocol" << endl; close(redirs[1]); return result; } static void parse_output(const mystring& output, const remote& remote, mystring& status, mystring& diag) { diag = remote.proto.upper(); diag += "; "; diag += output.strip(); diag.subst('\n', '/'); status = "5.0.0"; for (unsigned i = 0; i < output.length()-5; i++) if (isdigit(output[i]) && output[i+1] == '.' && isdigit(output[i+2]) && output[i+3] == '.' && isdigit(output[i+4])) { status = output.sub(i, 5); break; } } static bool is_bounce(int fd) { fdibuf in(fd); mystring sender; bool result = in.getline(sender) && sender.length() == 0; lseek(fd, 0, SEEK_SET); return result; } bool bounce_msg(const message& msg, const remote& remote, const mystring& output) { mystring failed = "../failed/"; failed += msg.filename; fout << "Moving message " << msg.filename << " into failed" << endl; if (rename(msg.filename.c_str(), failed.c_str()) == -1) { fout << "Can't rename file: " << strerror(errno) << endl; return false; } autoclose fd = open(failed.c_str(), O_RDONLY); if (fd < 0) fout << "Can't open file " << failed << " to create bounce message" << endl; else if (is_bounce(fd)) fout << "Not generating double bounce for " << msg.filename << "" << endl; else { fout << "Generating bounce for " << msg.filename << endl; queue_pipe qp; autoclose pfd = qp.start(); if (pfd > 0) { mystring program = program_path("nullmailer-dsn"); fork_exec dsn("nullmailer-dsn"); int redirs[] = { fd, pfd }; mystring status_code, diag_code; parse_output(output, remote, status_code, diag_code); const char* args[] = { program.c_str(), "--last-attempt", itoa(time(NULL)), "--remote", remote.host.c_str(), "--diagnostic-code", diag_code.c_str(), status_code.c_str(), NULL }; dsn.start(args, 2, redirs); // Everything else cleans up itself } } return true; } void send_all() { if(!load_config()) { fout << "Could not load the config" << endl; return; } if(remotes.count() <= 0) { fout << "No remote hosts listed for delivery"; return; } if(messages.count() == 0) return; fout << "Starting delivery, " << itoa(messages.count()) << " message(s) in queue." << endl; mystring output; for(rlist::iter remote(remotes); remote; remote++) { msglist::iter msg(messages); while(msg) { switch (send_one((*msg).filename, *remote, output)) { case tempfail: if (time(0) - (*msg).timestamp > queuelifetime) { if (bounce_msg(*msg, *remote, output)) { messages.remove(msg); continue; } } msg++; break; case permfail: if (bounce_msg(*msg, *remote, output)) messages.remove(msg); else msg++; break; default: if(unlink((*msg).filename.c_str()) == -1) { fout << "Can't unlink file: " << strerror(errno) << endl; msg++; } else messages.remove(msg); } } } fout << "Delivery complete, " << itoa(messages.count()) << " message(s) remain." << endl; } static int trigger; #ifdef NAMEDPIPEBUG static int trigger2; #endif bool open_trigger() { trigger = open(trigger_path.c_str(), O_RDONLY|O_NONBLOCK); #ifdef NAMEDPIPEBUG trigger2 = open(trigger_path.c_str(), O_WRONLY|O_NONBLOCK); #endif if(trigger == -1) fail1sys("Could not open trigger file: "); return true; } bool read_trigger() { if(trigger != -1) { char buf[1024]; read(trigger, buf, sizeof buf); #ifdef NAMEDPIPEBUG close(trigger2); #endif close(trigger); } return open_trigger(); } bool do_select() { fd_set readfds; FD_ZERO(&readfds); FD_SET(trigger, &readfds); struct timeval timeout; if (messages.count() == 0) pausetime = maxpause; timeout.tv_sec = pausetime; timeout.tv_usec = 0; pausetime *= 2; if (pausetime > maxpause) pausetime = maxpause; int s = select(trigger+1, &readfds, 0, 0, &timeout); if(s == 1) { fout << "Trigger pulled." << endl; read_trigger(); reload_messages = true; pausetime = minpause; } else if(s == -1 && errno != EINTR) fail1sys("Internal error in select: "); else if(s == 0) reload_messages = true; if(reload_messages) load_messages(); return true; } int main(int, char*[]) { trigger_path = CONFIG_PATH(QUEUE, NULL, "trigger"); msg_dir = CONFIG_PATH(QUEUE, NULL, "queue"); read_hostnames(); if(!selfpipe) { fout << "Could not set up self-pipe." << endl; return 1; } selfpipe.catchsig(SIGCHLD); if(!open_trigger()) return 1; if(chdir(msg_dir.c_str()) == -1) { fout << "Could not chdir to queue message directory." << endl; return 1; } signal(SIGALRM, catch_alrm); signal(SIGHUP, SIG_IGN); load_config(); load_messages(); for(;;) { send_all(); if (minpause == 0) break; do_select(); } return 0; } nullmailer-2.2/src/sendmail.cc000066400000000000000000000117451336020433200164170ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include "config.h" #include #include #include #include "configio.h" #include "fdbuf/fdbuf.h" #include "defines.h" #include "forkexec.h" #include "setenv.h" #include "cli++/cli++.h" const char* cli_program = "sendmail"; const char* cli_help_prefix = "Nullmailer sendmail emulator\n"; const char* cli_help_suffix = 0; const char* cli_args_usage = "[recipients] ") == 0)) { extra_args[extra_argc++] = "-f"; extra_args[extra_argc++] = o_from; } } for(int i = 0; i < argc; i++) extra_args[extra_argc++] = argv[i]; return do_exec("nullmailer-inject", extra_args); } } nullmailer-2.2/src/smtpd.cc000066400000000000000000000151651336020433200157520ustar00rootroot00000000000000// nullmailer -- a simple relay-only MTA // Copyright (C) 2018 Bruce Guenter // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // You can contact me at . There is also a mailing list // available to discuss this package. To subscribe, send an email to // . #include #include #include #include #include "autoclose.h" #include "defines.h" #include "fdbuf/fdbuf.h" #include "mystring/mystring.h" #include "forkexec.h" static const char resp_data_ok[] = "354 End your message with a period on a line by itself"; static const char resp_goodbye[] = "221 2.0.0 Good bye"; static const char resp_help[] = "214 2.0.0 Help not available"; static const char resp_mail_bad[] = "554 5.1.2 Sender invalid"; static const char resp_mail_ok[] = "250 2.1.0 Sender accepted"; static const char resp_need_param[] = "501 5.5.2 Syntax error, command requires a parameter"; static const char resp_no_mail[] = "503 5.5.1 You must send a valid sender first"; static const char resp_no_param[] = "501 5.5.2 Syntax error, no parameters allowed"; static const char resp_no_queue[] = "451 4.3.0 Starting nullmailer-queue failed"; static const char resp_no_rcpt[] = "503 5.5.1 You must send a valid recipient first"; static const char resp_ok[] = "250 2.3.0 OK"; static const char resp_queue_exiterr[] = "451 4.3.0 Error returned from nullmailer-queue"; static const char resp_queue_ok[] = "250 2.6.0 Accepted message"; static const char resp_queue_waiterr[] = "451 4.3.0 Error checking return status from nullmailer-queue"; static const char resp_qwrite_err[] = "451 4.3.0 Write to nullmailer-queue failed"; static const char resp_rcpt_bad[] = "554 5.1.2 Recipient invalid"; static const char resp_rcpt_ok[] = "250 2.1.5 Recipient accepted"; static const char resp_unimp[] = "500 5.5.1 Not implemented"; static mystring line; static mystring sender; static mystring recipients; extern const char cli_program[] = "nullmailer-smtpd"; static int readline() { if (!fin.getline(line)) return 0; if (line.length() > 0 && line[line.length()-1] == '\r') line = line.left(line.length()-1); return 1; } static mystring parse_addr_arg(mystring& param) { mystring addr; unsigned i; char term; if ((i = param.find_first('<') + 1) > 0) term = '>'; else { term = ' '; if ((i = param.find_first(':') + 1) <= 0) if ((i = param.find_first(' ') + 1) <= 0) return 0; while (i < param.length() && param[i] == ' ') ++i; } for (bool quoted = false; i < param.length() && (quoted || param[i] != term); ++i) { switch (param[i]) { case '"': quoted = !quoted; break; case '\\': ++i; // fall through default: addr += param[i]; } } // strip source routing if (addr[0] == '@' && (i = addr.find_first(':')) > 0) addr = addr.right(i+1); return addr + "\n"; } static void do_reset(void) { sender = ""; recipients = ""; } static bool respond(const char* msg) { fout << msg << '\r' << endl; return fout; } static bool qwrite(int qfd, const char* data, size_t len) { ssize_t wr; while (len > 0) { wr = write(qfd, data, len); if (wr <= 0) return false; len -= wr; data += wr; } return true; } static bool DATA(mystring& param) { if (!!param) return respond(resp_no_param); if (!sender) return respond(resp_no_mail); if (!recipients) return respond(resp_no_rcpt); queue_pipe nq; autoclose wfd = nq.start(); if (wfd < 0) return respond(resp_no_queue); if (!qwrite(wfd, sender.c_str(), sender.length()) || !qwrite(wfd, recipients.c_str(), recipients.length()) || !qwrite(wfd, "\n", 1)) return respond(resp_qwrite_err); if (!respond(resp_data_ok)) return false; while (readline()) { if (line.length() == 1 && line[0] == '.') break; if (line.length() > 1 && line[0] == '.') line = line.sub(1, line.length() - 1); line += '\n'; if (!qwrite(wfd, line.c_str(), line.length())) return respond(resp_qwrite_err); } wfd.close(); return respond(nq.wait() ? resp_queue_ok : resp_queue_exiterr); } static bool HELO(mystring& param) { if (!param) return respond(resp_need_param); return respond(resp_ok); } static bool HELP(mystring&) { return respond(resp_help); } static bool MAIL(mystring& param) { if (!param) return respond(resp_need_param); do_reset(); sender = parse_addr_arg(param); return respond(!sender ? resp_mail_bad : resp_mail_ok); } static bool NOOP(mystring&) { return respond(resp_ok); } static bool QUIT(mystring&) { respond(resp_goodbye); return false; } static bool RCPT(mystring& param) { if (!param) return respond(resp_need_param); if (!sender) return respond(resp_no_mail); mystring tmp = parse_addr_arg(param); recipients += tmp; return respond(!tmp ? resp_rcpt_bad : resp_rcpt_ok); } static bool RSET(mystring&) { do_reset(); return respond(resp_ok); } static bool VRFY(mystring&) { return respond(resp_unimp); } typedef bool (*dispatch_fn)(mystring& param); struct dispatch { const char* cmd; dispatch_fn fn; }; static struct dispatch dispatch_table[] = { { "DATA", DATA }, { "EHLO", HELO }, { "HELO", HELO }, { "HELP", HELP }, { "MAIL", MAIL }, { "NOOP", NOOP }, { "QUIT", QUIT }, { "RCPT", RCPT }, { "RSET", RSET }, { "VRFY", VRFY }, { 0, 0 } }; static bool dispatch() { mystring cmd; mystring param; cmd = line; for (size_t i = 0; i < line.length(); i++) { if (line[i] == ' ') { cmd = line.left(i); param = line.right(i+1).lstrip(); break; } } cmd = cmd.upper(); struct dispatch* d; for (d = dispatch_table; d->cmd != 0; d++) { if (cmd == d->cmd) return d->fn(param); } return respond("500 5.5.1 Not implemented"); } int main(void) { mystring line; if (!respond("220 nullmailer-smtpd ready")) return 0; while (readline()) { if (!dispatch()) return 0; } return 0; } nullmailer-2.2/test/000077500000000000000000000000001336020433200144745ustar00rootroot00000000000000nullmailer-2.2/test/Makefile.am000066400000000000000000000022341336020433200165310ustar00rootroot00000000000000noinst_PROGRAMS = address-test argparse-test clitest0 clitest1 EXTRA_DIST = address-trace.cc clitest.cc clitest.sh functions.in runtests \ accept-qmqp.sh accept-smtp.sh authtest-smtp.sh noinst_SCRIPTS = functions CLEANFILES = functions AM_CPPFLAGS = -I$(top_srcdir)/lib address_test_SOURCES = address-test.cc # address-trace.cc address_test_LDADD = ../lib/libnullmailer.a argparse_test_SOURCES = argparse-test.cc argparse_test_LDADD = ../lib/libnullmailer.a clitest0_CPPFLAGS = $(AM_CPPFLAGS) -DCLI_ONLY_LONG=false clitest0_SOURCES = clitest.cc clitest0_LDADD = ../lib/libnullmailer.a ../lib/cli++/libcli++.a clitest1_CPPFLAGS = $(AM_CPPFLAGS) -DCLI_ONLY_LONG=true clitest1_SOURCES = clitest.cc clitest1_LDADD = ../lib/libnullmailer.a ../lib/cli++/libcli++.a functions: functions.in Makefile sed -e 's,[@]SRCDIR[@],$(abs_top_srcdir),g; s,[@]BUILDDIR[@],$(abs_top_builddir),g;' < $< > $@ # The following makes sure that we can't produce a package without the # tests executing properly dist-hook: cp -r $(srcdir)/tests $(distdir) check: all ./address-test ./argparse-test sh $(srcdir)/clitest.sh $(srcdir)/runtests `find $(abs_srcdir)/tests -type f -not -name '.*'` nullmailer-2.2/test/accept-qmqp.sh000066400000000000000000000000161336020433200172400ustar00rootroot00000000000000echo '3:KOK,' nullmailer-2.2/test/accept-smtp.sh000066400000000000000000000001201336020433200172410ustar00rootroot00000000000000echo 250 OK echo 250 OK echo 250 OK echo 250 OK echo 351 OK echo 220 OK sleep 1 nullmailer-2.2/test/address-test.cc000066400000000000000000000114411336020433200174060ustar00rootroot00000000000000#include "config.h" #include #include "canonicalize.h" #include "mystring/mystring.h" #include "address.h" #include "fdbuf/fdbuf.h" #include "itoa.h" static bool test(const mystring& in, const mystring& out, const mystring& list) { mystring line = in; mystring tmplist; if(!parse_addresses(line, tmplist)) { fout << "Parsing of '" << in << "' failed." << endl; return false; } bool status = true; if(!!list && tmplist != list) { fout << "Parsing of '" << in << "' failed: bad result list, was:\n" << tmplist << "should be:\n" << list; status = false; } if(!!out && line != out) { fout << "Parsing of '" << in << "' failed: bad result string, was:\n" << line << "\nshould be:\n" << out << "\n"; status = false; } return status; } #define TEST(X,Y,Z) do{ ++count; if(!test(X,Y,Z)) ++failed; }while(0) mystring defaulthost = "a"; mystring defaultdomain = "b.c"; int main() { int count = 0; int failed = 0; // empty list TEST("", "", ""); // empty list with comment TEST("(no addresses)", "(no addresses)", ""); // periods in local TEST("a.b@c.d", "a.b@c.d", "a.b@c.d\n"); // quoted local TEST("\"e\"@c.d", "e@c.d", "e@c.d\n"); // missing host and domain TEST("e", "e@a.b.c", "e@a.b.c\n"); // missing domain TEST("e@x", "e@x.b.c", "e@x.b.c\n"); // trailing period TEST("e@c.d.", "e@c.d.", "e@c.d.\n"); // trailing period, single domain TEST("e@c.", "e@c.", "e@c.\n"); // comment
style TEST("x", "x ", "y@a.b\n"); TEST("", "", "y@a.b\n"); // address (comment) style TEST("y@a.b(x)", "y@a.b (x)", "y@a.b\n"); // internal comments before local TEST("(j)y@a.b", "y@a.b (j)", "y@a.b\n"); // internal comments after local TEST("y(j)@a.b", "y@a.b (j)", "y@a.b\n"); // internal comments before domain TEST("y@(j)a.b", "y@a.b (j)", "y@a.b\n"); // internal comments before period TEST("y@a(j).b", "y@a.b (j)", "y@a.b\n"); // internal comments after period TEST("y@a.(j)b", "y@a.b (j)", "y@a.b\n"); // normal list TEST("a@b.c,d@e.f", "a@b.c, d@e.f", "a@b.c\nd@e.f\n"); // list with comments TEST("a@b.c(j),d@e.f(k)", "a@b.c (j), d@e.f (k)", "a@b.c\nd@e.f\n"); // list without commas TEST("a@b.c d@e.f", "a@b.c, d@e.f", "a@b.c\nd@e.f\n"); // list without commas with comments TEST("a@b.c(j) d@e.f(k)", "a@b.c (j), d@e.f (k)", "a@b.c\nd@e.f\n"); // simple group TEST("g: a@b.c, d@e.f;", "g: a@b.c, d@e.f;", "a@b.c\nd@e.f\n"); // group with spaces in name TEST("g h: a@b.c, d@e.f;", "g h: a@b.c, d@e.f;", "a@b.c\nd@e.f\n"); // empty group TEST("g: ;", "g: ;", ""); // group with a comment TEST("g: a@b.c(j);", "g: a@b.c (j);", "a@b.c\n"); // group with comments TEST("g:a@b.c(j),d@e.f(k);", "g: a@b.c (j), d@e.f (k);", "a@b.c\nd@e.f\n"); // group with no commas TEST("g:a@b.c d@e.f;", "g: a@b.c, d@e.f;", "a@b.c\nd@e.f\n"); // group with route addresses TEST("g:foo;", "g: foo ;", "a@b.c\n"); // route-path syntax (stripped) TEST("f<@g.h:a@b.c>", "f ", "a@b.c\n"); // multiple route-path syntax TEST("f<@g.h@i.j:a@b.c>", "f ", "a@b.c\n"); // comments with quoted brackets TEST("(f\\)\\()a@b.c", "a@b.c (f\\)\\()", "a@b.c\n"); // nested comments TEST("(f(g)h)a@b.c", "a@b.c (f(g)h)", "a@b.c\n"); // simple quoted addresses TEST("\"a\"@b.c", "a@b.c", "a@b.c\n"); // quoted parts of address TEST("a.\"b\".c@d.e", "a.b.c@d.e", "a.b.c@d.e\n"); // escaped characters within quotes TEST("\"s\\'b\"@d.e", "s'b@d.e", "s'b@d.e\n"); // escaped specials TEST("\"s\\\"a\\\"b\"@d.e", "\"s\\\"a\\\"b\"@d.e", "s\"a\"b@d.e\n"); // twisted syntax //TEST("\"\\\"d\\\" <\"<@_._:e@f.g>", // "who knows", // "e@f.g\n"); TEST("c@d.e (a.b)", "c@d.e (a.b)", "c@d.e\n"); TEST("\"a.b\" ", "\"a.b\" ", "c@d.e\n"); TEST("Mr. T ", "Mr. T ", "c@d.e\n"); TEST("a.b ", "a.b ", "c@d.e\n"); TEST("Mr . and T ", "Mr . and T ", "c@d.e\n"); // Whitespace in the quoted string TEST("\"\n f\n \" ", "f ", "a@b.c\n"); TEST("\" a.b\" ", "\"a.b\" ", "c@d.e\n"); fout << itoa(count) << " tests run, "; fout << itoa(failed) << " failed." << endl; return failed; } nullmailer-2.2/test/address-trace.cc000066400000000000000000000000441336020433200175220ustar00rootroot00000000000000#define TRACE #include "address.cc" nullmailer-2.2/test/argparse-test.cc000066400000000000000000000035121336020433200175650ustar00rootroot00000000000000#include "argparse.h" #include "fdbuf/fdbuf.h" #include "itoa.h" static bool doit(const char* teststr, unsigned count, const char** result) { arglist args; unsigned c = parse_args(args, teststr); if (c != count) { fout << "Parsing of \"" << teststr << "\" failed, wrong count, was: " << c << " should be: " << count << endl; return false; } arglist::const_iter iter(args); for (unsigned i = 0; i < c; i++, iter++) { if (*iter != result[i]) { fout << "Parsing of \"" << teststr << "\" failed, wrong string, was:\n" << *iter << " should be:\n" << result[i] << endl; return false; } } return true; } #define TEST(X,Y,...) do{ const char* result[] = {__VA_ARGS__}; ++count; if (!doit(X, Y, result)) ++failed; } while(0) int main() { int count = 0; int failed = 0; TEST("", 0); TEST("one", 1, "one"); // Simple case TEST(" two", 1, "two"); // Leading space TEST("three ", 1, "three"); // Trailing space TEST("one\\ two", 1, "one two"); // Escaped internal space TEST(" one two three ", 3, "one","two","three"); // Spaces between args TEST("'one'", 1, "one"); // Simple single quoted TEST("'one two'", 1, "one two"); // Single quoted with space TEST("'one\\'two", 1, "one\\two"); // Single quoted with backslash TEST("\"one two\"", 1, "one two"); // Double quoted TEST(" one \"two\"three four", 3, "one", "twothree", "four"); // Mixed quoted and unquoted TEST("\"one\\\" two\"", 1, "one\" two"); // Double quoted with escaped quote TEST("one='two three' four", 2, "one=two three", "four"); // Single quotes within an arg with multiple args TEST("one=\"two three\" four", 2, "one=two three", "four"); // Double quotes within an arg with multiple args fout << itoa(count) << " tests run, "; fout << itoa(failed) << " failed." << endl; return failed; } nullmailer-2.2/test/authtest-smtp.sh000066400000000000000000000002641336020433200176540ustar00rootroot00000000000000echo 250 ME read line echo 250-ME echo 250-AUTH PLAIN echo 250 OK read line cat $1 rm -f $1 read line echo 250 OK read line echo 250 OK read line echo 351 OK read line echo 220 OK nullmailer-2.2/test/clitest.cc000066400000000000000000000021611336020433200164520ustar00rootroot00000000000000#include "cli++/cli++.h" #include "fdbuf/fdbuf.h" const char* cli_program = "clitest"; const char* cli_help_prefix = "Nullmailer CLI test harness\n"; const char* cli_help_suffix = 0; const char* cli_args_usage = "[args]"; const int cli_args_min = 0; const int cli_args_max = -1; const bool cli_only_long = CLI_ONLY_LONG; static int a = 0; static int b = 0; static const char* c = 0; static const char* d = 0; cli_option cli_options[] = { { 'a', 0, cli_option::flag, 1, &a, "Test flag", 0 }, { 0, "bb", cli_option::flag, 1, &b, "Test flag", 0 }, { 'c', 0, cli_option::string, 1, &c, "Test string", 0 }, { 0, "dd", cli_option::string, 1, &d, "Test string", 0 }, CLI_OPTION_END }; static void showcstr(const char* name, const char* value) { fout << ' ' << name; if (value == NULL) fout << "=NULL"; else fout << "=\"" << value << "\""; } int cli_main(int argc, char* argv[]) { fout << "argc=" << argc << " a=" << a << " b=" << b; showcstr("c", c); showcstr("d", d); fout << endl; for (int i = 0; i < argc; i++) fout << "argv[" << i << "]=\"" << argv[i] << "\"" << endl; return 0; } nullmailer-2.2/test/clitest.sh000066400000000000000000000027451336020433200165070ustar00rootroot00000000000000tmp=$PWD/clitest.tmp trap 'rm -f ${tmp}*' EXIT testit() { test "x$1" = 'x!' && isfailed='-eq 0' || isfailed='-ne 0' "$@" >$tmp.out 2>/dev/null if [ $? $isfailed ] then echo "Test \"$*\" failed!" exit 1 fi if [ "x$1" != 'x!' ] then cat >$tmp.exp if ! diff -u $tmp.exp $tmp.out then echo "Test \"$*\" failed!" exit 1 fi fi } testit ./clitest0 < /dev/null 2>&1 || : # Remove all temporary files on success if [ $exit -eq 0 ] then wait rm -rf $tmpdir fi } trap exit_cleanup EXIT fail() { echo "$*" exit 1 } start() { local name=$1 shift mkdir -p $tmpdir/service/$name { echo '#!/bin/sh' echo exec "$@" } >$tmpdir/service/$name/run chmod +x $tmpdir/service/$name/run supervise $tmpdir/service/$name $tmpdir/service/${name}-log 2>&1 & sleep 1 } stop() { for name in $*; do svc -dx $tmpdir/service/$name >/dev/null 2>&1 wait done } catch-port() { local name=$1 port=$( head -n 1 $tmpdir/service/${name}-log ) } #not() { if "$@"; then return 1; else return 0; fi } not() { ! safe "$@"; } safe() { set +e; "$@"; result=$?; set -e; return $result; } error() { local code=$1 shift if "$@"; then echo "Result was 0, should be $code." return 1 else result=$? if test $result -eq $code; then return 0 else echo "Result was $result, should be $code." return 1 fi fi } inject() { ../src/nullmailer-inject "$@"; } mailq() { ../src/mailq "$@"; } smtpd() { ../src/nullmailer-smtpd "$@"; } queue() { ( echo "$1" shift while [ x"$1" != x ] do echo "$1" shift done echo echo "Subject: test" ) | ../src/nullmailer-queue > $tmpdir/queue-out 2> $tmpdir/queue-err } injectlines() { for line in "$@"; do echo "$line" done | inject -n return $? } injectfield() { local field=$1 shift injectlines "$@" | grep -i "^$field:" | cut -d: -f2- } protocol() { local p=$1 local opts="" shift while [ $# -gt 0 -a x"$1" != x-- ] do opts="$opts $1" shift done shift || : for line in "$@" do echo "$line" done > $tmpdir/protocol-in ../protocols/$p $opts < $tmpdir/protocol-in > $tmpdir/protocol-log 2>&1 } # Split an input on blank lines splitblank() { local fn=$1 local n=1 while read line do if [ x"$line" = x ] then n=$(( $n + 1 )) else echo "$line" >> ${fn}.$n fi done } make-testmail() { testmail=$tmpdir/testmail rm -f $testmail cat >$testmail < To: Subject: Nullmailer automated test message Just testing, please ignore EOF } export PATH=/bin:/usr/bin:/usr/local/bin rm -f $SYSCONFDIR/* echo f.q.d.n >$SYSCONFDIR/me echo q.d.n >$SYSCONFDIR/defaultdomain set -e nullmailer-2.2/test/runtests000077500000000000000000000004411336020433200163100ustar00rootroot00000000000000#!/bin/sh set -e failed=0 for test in $* do echo Running test $test... if env - bash $test then echo 'Done.' else echo '******************************Failed!******************************' failed=$(( $failed + 1 )) fi done 2>&1 echo "$# tests run, $failed failed" exit $failed nullmailer-2.2/test/tests/000077500000000000000000000000001336020433200156365ustar00rootroot00000000000000nullmailer-2.2/test/tests/dsn000066400000000000000000000054061336020433200163520ustar00rootroot00000000000000. functions msgid=message.id.$$.$RANDOM@f.q.d.n dsn() { $builddir/src/nullmailer-dsn "$@" << EOF sender@example.com recip1@example.net recip2@example.org Subject: test message Message-Id: <$msgid> From: To: Cc: This is a test. line 2 line 3 line 4 EOF } fn=$tmpdir/dsn-tmp dsn 5.2.9 | splitblank $fn echo -n Testing envelope: sender not test -x $fn.1 echo -n , recipient grep -qx sender@example.com $fn.2 echo -n , end test $( wc -l < $fn.2 ) = 1 echo date='..., [ 0-9][0-9] ... [0-9]{4} [0-9][0-9]:[0-9][0-9]:[0-9][0-9] [+-][0-9]{4}' echo -n Testing header: from grep -qx 'From: Message Delivery Subsystem ' $fn.3 echo -n , to grep -qx 'To: ' $fn.3 echo -n , subject grep -qx 'Subject: Returned mail: Could not send message' $fn.3 echo -n , date egrep -q "^Date: $date$" $fn.3 echo -n , message-id grep -q '^Message-Id: <.*.nullmailer@f.q.d.n>$' $fn.3 echo -n , mime grep -qx 'MIME-Version: 1.0' $fn.3 echo -n , content-type grep -q '^Content-Type: multipart/report; report-type=delivery-status;' $fn.3 echo echo -n Testing report header: reporting-mta grep -qx "Reporting-MTA: x-local-hostname; f.q.d.n" $fn.8 echo -n , date egrep -q "^Arrival-Date: $date$" $fn.8 echo echo -n Testing recipient report 1: final-recipient grep -qx 'Final-Recipient: rfc822; recip1@example.net' $fn.9 echo -n , action grep -qx 'Action: failed' $fn.9 echo -n , status grep -qx 'Status: 5.2.9' $fn.9 echo -n , date egrep -q "^Last-Attempt-Date: $date$" $fn.9 echo echo -n Testing recipient report 2: final-recipient grep -qx 'Final-Recipient: rfc822; recip2@example.org' $fn.10 echo -n , action grep -qx 'Action: failed' $fn.10 echo -n , status grep -qx 'Status: 5.2.9' $fn.10 echo -n , date egrep -q "^Last-Attempt-Date: $date$" $fn.10 echo echo Testing quoted message pre-header grep -qx 'Content-Type: message/rfc822' $fn.11 echo Testing quoted message header grep -qx "Message-Id: <$msgid>" $fn.12 echo Testing quoted message body not grep -qx "This is a test." $fn.13 echo Testing fixed bounce address echo admin@example.com > $SYSCONFDIR/bounceto dsn 5.2.9 | splitblank $fn grep -qx admin@example.com $fn.2 echo -n Testing max-lines: 0 dsn --max-lines=0 5.2.9 | not grep -qx "This is a test." echo -n , 1 dsn --max-lines=1 5.2.9 | grep -qx "This is a test." dsn --max-lines=1 5.2.9 | not grep -qx "line 2" echo -n , 2 dsn --max-lines=2 5.2.9 | grep -qx "line 2" dsn --max-lines=2 5.2.9 | not grep -qx "line 3" echo -n , bouncelines=1 echo 1 > $SYSCONFDIR/bouncelines dsn 5.2.9 | grep -qx "This is a test." dsn 5.2.9 | not grep -qx "line 2" echo -n , override dsn --max-lines=2 5.2.9 | grep -qx "line 2" dsn --max-lines=2 5.2.9 | not grep -qx "line 3" echo rm -f $fn.* $SYSCONFDIR/bounceto $SYSCONFDIR/bouncelines nullmailer-2.2/test/tests/inject/000077500000000000000000000000001336020433200171125ustar00rootroot00000000000000nullmailer-2.2/test/tests/inject/bad-headers000066400000000000000000000005171336020433200211770ustar00rootroot00000000000000. functions injtest() { echo "$@" | not inject >/dev/null; } echo "Checking that inject rejects leading continuation lines." injtest " foo..." echo "Checking that inject rejects malformed RFC headers." injtest "foo : bar" injtest ":foo bar" injtest "foo" echo "Checking that inject rejects bad addresses." injtest "from: foo/dev/null echo "Checking that inject does not add more date lines." test 1 -eq `inj "date: foo" | wc -l` nullmailer-2.2/test/tests/inject/from000066400000000000000000000032171336020433200200030ustar00rootroot00000000000000. functions inj() { injectfield from 'to: nobody' "$@"; } testvar() { set -e echo "Checking that inject obeys $1." export $1="$2" inj | grep -q "$3" } echo "Checking that inject inserts a from line." test -n "`inj`" echo "Checking that inject preserves an existing from line." inj "from: a@b.c" | grep -q '^ a@b\.c$' echo "Checking that inject does not add more from lines." test 1 -eq `inj "from: a@b.c" | wc -l` echo "Checking that inject will strip from lines." export NULLMAILER_FLAGS=f inj "from: a@b.c" | not grep -q '^ a@b\.c$' unset NULLMAILER_FLAGS echo "Checking that inject obeys me" rm -f $SYSCONFDIR/default* inj | grep -q "@f.q.d.n>$" echo "Checking that inject obeys config/defaulthost" echo test.org >$SYSCONFDIR/defaulthost inj | grep -q '@test.org>$' echo "Checking that inject obeys config/defaultdomain" echo test >$SYSCONFDIR/defaulthost echo domain.org >$SYSCONFDIR/defaultdomain inj | grep -q '@test.domain.org>$' echo "Checking that inject ignores config/defaultdomain for localhost" echo localhost >$SYSCONFDIR/defaulthost inj | grep -q '@localhost>$' testvar HOSTNAME test1.org '@test1.org>$' testvar MAILHOST test2.org '@test2.org>$' testvar NULLMAILER_HOST test3.org '@test3.org>$' echo "Checking that inject uses getpwnam" inj | grep -q " <`id -un`@" testvar LOGNAME name1 ' ' do echo "Checking that inject ignores a leading \"${lead}From \" line" injectfrom "$lead" | egrep -qv '^>?From ' injectfrom "$lead" | head -n 1 | grep -q '^Subject:' done nullmailer-2.2/test/tests/inject/message-id000066400000000000000000000012461336020433200210560ustar00rootroot00000000000000. functions inj() { injectfield message-id 'to: n' "$@"; } echo "Checking that inject inserts a message-id." test -n "`inj`" echo "Checking that inject preserves an existing message-id." inj "Message-Id: " | grep -q '^ $' echo "Checking that inject does not add more message-ids." test 1 -eq `inj "Message-Id: " | wc -l` echo "Checking that inject will ignore an existing message-id." export NULLMAILER_FLAGS=i inj "Message-Id: " | not grep -q '^ $' echo "Checking that inject obeys me" inj | grep -q "@f.q.d.n>$" echo "Checking that inject obeys config/idhost" echo test1.org >$SYSCONFDIR/idhost inj | grep -q '@test1.org>$' nullmailer-2.2/test/tests/inject/queue000066400000000000000000000006221336020433200201610ustar00rootroot00000000000000. functions echo 'Testing that inject queues messages properly.' echo 'To: nobody' | inject test $( ls $QUEUEDIR/queue | wc -l ) = 1 egrep -i '^to: *nobody' $QUEUEDIR/queue/* >/dev/null echo 'Testing that inject honors $NULLMAILER_QUEUE.' rm -f $QUEUEDIR/queue/* export NULLMAILER_QUEUE=/bin/cat echo 'To: nobody' | inject >/dev/null test $( ls $QUEUEDIR/queue | wc -l ) = 0 unset NULLMAILER_QUEUE nullmailer-2.2/test/tests/inject/recips000066400000000000000000000030031336020433200203160ustar00rootroot00000000000000. functions inj() { inject -n -v "$@" | tail -n +2 | sed '/^$/,$d'; } inj-find() { echo to: a@b.c | inj "$1" d@e.f | grep -q "$2"; } inj-notfind() { echo to: a@b.c | inj "$1" d@e.f | not grep -q "$2"; } hdrline='^a@b.c$' cmdline='^d@e.f$' echo "Checking that inject uses command line with -a." inj-find -a $cmdline echo "Checking that inject ignores header lines with -a." inj-notfind -a $hdrline echo "Checking that inject uses command line with -b." inj-find -b $cmdline echo "Checking that inject uses header lines with -b." inj-find -b $hdrline echo "Checking that inject ignores command line with -h." inj-notfind -h $cmdline echo "Checking that inject uses header lines with -h." inj-find -h $hdrline echo "Checking that inject uses command line with -e and no header." echo | inj -e d@e.f | grep -q $cmdline echo "Checking that inject uses command line with -e and header." inj-find -e $cmdline echo "Checking that inject uses header with -e and no command line." echo to: a@b.c | inj -e | grep -q $hdrline echo "Checking that inject ignores header with -e and command line." inj-notfind -e $hdrline echo "Checking that inject uses command line with no header by default." echo | inj -e d@e.f | grep -q $cmdline echo "Checking that inject uses command line with header by default." inj-find -e $cmdline echo "Checking that inject uses header with no command line by default." echo to: a@b.c | inj -e | grep -q $hdrline echo "Checking that inject ignores header with command line by default." inj-notfind -e $hdrline nullmailer-2.2/test/tests/inject/return-path000066400000000000000000000003361336020433200213100ustar00rootroot00000000000000. functions inj() { injectfield return-path 'to: n' "$@"; } echo "Checking that inject does not inserts a return-path." test -z "`inj`" echo "Checking that inject strips return-paths." test -z "`inj return-path: blah`" nullmailer-2.2/test/tests/inject/sender000066400000000000000000000041661336020433200203240ustar00rootroot00000000000000. functions inj() { inject -n -v "$@" a /dev/null | head -n 1; } testvar() { set -e echo "Checking that inject obeys $1." export $1="$2" inj | grep -q "$3" } testcanon() { set -e echo "Checking that inject canonicalizes $1." export $1="$2" inj | grep -q "$3" } testhdr() { set -e echo "Checking that inject $1 $2:" echo $2: $3 | inject -n -v a 2>/dev/null | head -n 1 | grep -q "$4" } testign() { testhdr ignores "$@"; } testset() { testhdr uses "$@"; } testfrom() { echo "Checking that inject honors -f '$1'" inj -f "$1" | grep -qx "$2" } testfrom2() { testfrom "$1" "$2" testfrom "<$1>" "$2" } echo "Checking that inject obeys me" rm -f $SYSCONFDIR/default* inj | grep -q "@f.q.d.n$" echo "Checking that inject obeys config/defaulthost" echo test.org >$SYSCONFDIR/defaulthost inj | grep -q '@test.org$' echo "Checking that inject obeys config/defaultdomain" echo test >$SYSCONFDIR/defaulthost echo domain.org >$SYSCONFDIR/defaultdomain inj | grep -q '@test.domain.org$' echo "Checking that inject ignores config/defaultdomain for localhost" echo localhost >$SYSCONFDIR/defaulthost inj | grep -q '@localhost$' testvar HOSTNAME test1.org '@test1.org$' testcanon HOSTNAME test1 '@test1.domain.org$' testvar MAILHOST test2.org '@test2.org$' testcanon MAILHOST test2 '@test2.domain.org$' testvar NULLMAILER_HOST test3.org '@test3.org$' testcanon NULLMAILER_HOST test3 '@test3.domain.org$' echo "Checking that inject uses getpwnam" inj | grep -q "^`id -un`@" testvar LOGNAME name1 '^name1@' testvar USER name2 '^name2@' testvar MAILUSER name3 '^name3@' testvar NULLMAILER_USER name4 '^name4@' testign Errors-To a@b.c '^name4@test3' testign From a@b.c '^name4@test3' testign Reply-To a@b.c '^name4@test3' testign Resent-From a@b.c '^name4@test3' testign Resent-Reply-To a@b.c '^name4@test3' testign Resent-Sender a@b.c '^name4@test3' testign Return-Receipt-To a@b.c '^name4@test3' testign Sender a@b.c '^name4@test3' testset Return-Path name0@host0.org '^name0@host0.org$' export NULLMAILER_FLAGS=s testign Return-Path name0@host0.org '^name4@test3' testfrom2 '' '' testfrom2 'a@b.c' 'a@b.c' testfrom2 'a@b' 'a@b.domain.org' nullmailer-2.2/test/tests/mailq000066400000000000000000000006131336020433200166640ustar00rootroot00000000000000. functions echo "Testing the output of the mailq command" echo To: nobody@nowhere | inject $builddir/src/mailq | head -n 1 | egrep -q '^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2} [0-9]+ bytes from <.*>$' \ || { echo "Header is misformatted."; exit 1; } $builddir/src/mailq | tail -n 1 | egrep -q '^ to $' \ || { echo "Recipient is misformatted."; exit 1; } nullmailer-2.2/test/tests/protocols000066400000000000000000000034621336020433200176120ustar00rootroot00000000000000. functions export HELOHOST=f.q.d.n make-testmail for p in smtp qmqp do start server "tcpserver -1 ::0 0 sh $srcdir/test/accept-$p.sh" catch-port server echo "Testing protocol success with $p (command-line)" protocol $p --host=localhost --port=$port 3<$testmail echo "Testing protocol success with $p (stdin)" protocol $p -- host=localhost port=$port 3<$testmail echo "Testing protocol success with $p (stdin, IPv6)" protocol $p -- host=::1 port=$port 3<$testmail echo "Testing protocol success with $p (source addr)" protocol $p -- host=::1 port=$port source=::1 3<$testmail echo "Testing protocol failure with $p (bad source addr 1)" error 19 protocol $p -- host=::1 port=$port source=127.0.0.1 3<$testmail echo "Testing protocol failure with $p (bad source addr 2)" error 19 protocol $p -- host=127.0.0.1 port=$port source=::1 3<$testmail stop server echo "Testing host not found error with $p." error 2 protocol $p --host=this.host.can.not.exist 3<$testmail echo "Testing connection refused error with $p." error 7 protocol $p -p $port --host=localhost 3<$testmail echo "Testing connection refused error with $p (IPv6)." error 7 protocol $p -p $port --host=::1 3<$testmail echo "Testing usage error with $p (zero args)." error 1 protocol $p 3<$testmail echo "Testing usage error with $p (two args)." error 1 protocol $p --host=localhost foobar 3<$testmail echo "Testing usage error with $p (unknown option)." error 1 protocol $p -x --host=localhost 3<$testmail echo "Testing usage error with $p (invalid integer)." error 1 protocol $p -p foo --host=localhost 3<$testmail start server tcpserver -1 0 0 date port=$( head -n 1 $tmpdir/service/server-log ) echo "Testing protocol failure with $p." error 11 protocol $p -p $port --host=localhost 3<$testmail stop server done stop server nullmailer-2.2/test/tests/queue/000077500000000000000000000000001336020433200167625ustar00rootroot00000000000000nullmailer-2.2/test/tests/queue/rewrite000066400000000000000000000024321336020433200203670ustar00rootroot00000000000000. functions que() { set -e ../src/nullmailer-queue && \ cat $QUEUEDIR/queue/* && \ rm -f $QUEUEDIR/queue/* } que-recip() { set -e que | sed -e '2,/^$/!d' | grep -q "$@" } que-sender() { set -e que | head -n 1 | grep -q "$@" } echo admin@remote >$SYSCONFDIR/adminaddr echo "Checking that queue rewrites user@localhost to adminaddr." que-recip '^admin@remote$' <$SYSCONFDIR/adminaddr echo "Checking that queue rewrites to multiple adminaddr." que-recip '^admin2@remote2$' <$SYSCONFDIR/allmailfrom que-sender '^sender@remote3$' < to <$2>" queue "$@" } queue-good() { if ! queue-test "$@" then fail "nullmailer-queue failed to accept good envelope" fi qc=$( find $queuedir -type f | wc -l ) if [ $qc -eq 0 ] then fail "nullmailer-queue did not queue a message despite succeeding" fi if [ $qc -gt 1 ] then fail "nullmailer-queue queued multiple messages ?!?" fi qm=$queuedir/* sed -e '1,/^$/d' $qm | grep -q '^Subject:' || fail "queued message did not contain body" } check-sender() { qm=$queuedir/* head -n 1 $qm | grep -q "^$1$" || fail "queued message contained wrong sender" } check-recipient() { qm=$queuedir/* sed -e '1d;q' $qm | grep -q "^$1$" || fail "queued message contained wrong sender" } queue-bad() { if queue-test "$@" then fail "nullmailer-queue failed to reject bad envelope" fi qc=$( find $queuedir -type f | wc -l ) if [ $qc -ne 0 ] then fail "nullmailer-queue queued a message after rejecting it" fi } queue-good "a@b.c" "d@e.f" queue-bad "@b.c" "d@e.f" queue-bad "a@b.c" "@e.f" queue-bad "a@b" "d@e.f" queue-bad "a@b.c" "d@e" queue-good "" "d@e.f" nullmailer-2.2/test/tests/send000066400000000000000000000060761336020433200165230ustar00rootroot00000000000000. functions cat <$tmpdir/protocols/dummy #!/bin/sh set -e read opts read code echo "\$opts" | grep -q '^host=' echo "code=\$code (#5.2.1) \$opts" exit \$code EOF chmod +x $tmpdir/protocols/dummy echo 127.0.0.1 smtp >$SYSCONFDIR/remotes # Start up the servers start send $builddir/src/nullmailer-send make_message() { local sender=$1 local recip=$2 msgid=$(date +%s).$$.me cat <$QUEUEDIR/tmp/$msgid $sender $recip Subject: test Message-Id: <$msgid> This is just a test. EOF mv -f $QUEUEDIR/tmp/$msgid $QUEUEDIR/queue/$msgid } send_message() { local sender=$1 local recip=$2 shift 2 echo 127.0.0.1 dummy $@ >$SYSCONFDIR/remotes make_message "$sender" "$recip" svc -a $tmpdir/service/send sleep 2 not test -e $QUEUEDIR/queue/$msgid } echo 'Testing sending with a succeeding protocol' send_message me@example.com me@example.net 0 2.0.0 echo 'Testing sending with a failing protocol' not send_message me@example.com me@example.net 1 5.2.2 rm -f $QUEUEDIR/queue/$msgid echo 'Testing handling of a permanent failure' send_message me@example.com me@example.net 33 5.2.2 echo 'Checking failed message was moved out of the queue' test -e $QUEUEDIR/failed/$msgid rm -f $QUEUEDIR/failed/$msgid echo 'Checking for a generated bounce message' # It will also bounce, so look at failed messages msgid2=$( ls $QUEUEDIR/failed ) test $( wc -w <<< $msgid2 ) = 1 fn=$QUEUEDIR/failed/$msgid2 echo 'Checking bounce sender' head -n 1 $fn | grep -qx '' echo 'Checking bounce recipient' sed -e '1d;q' $fn | grep -qx 'me@example.com' sed -e '1,2d;q' $fn | grep -qx '' echo 'Checking bounce contents' sed -e '1,3d; /^$/q' $fn | grep -qx 'To: ' sed -e '1,3d; 1,/^$/d' $fn | grep -qx 'Reporting-MTA: x-local-hostname; f.q.d.n' sed -e '1,3d; 1,/^$/d' $fn | grep -qx 'Final-Recipient: rfc822; me@example.net' sed -e '1,3d; 1,/^$/d' $fn | grep -qx 'Action: failed' sed -e '1,3d; 1,/^$/d' $fn | grep -qx 'Status: 5.2.1' sed -e '1,3d; 1,/^$/d' $fn | grep -qx 'Diagnostic-Code: DUMMY; code=33 (#5.2.1) host=127.0.0.1' sed -e '1,3d; 1,/^$/d' $fn | grep -qx 'Subject: test' sed -e '1,3d; 1,/^$/d' $fn | not grep -qx 'This is just a test.' rm -f $fn echo 'Checking log outputs' log=$tmpdir/service/send-log grep -qx "^Starting delivery: host: 127.0.0.1 protocol: dummy file: $msgid" $log grep -qx 'From: to: ' $log grep -qx 'Sending failed: Unspecified temporary error' $log grep -qx "Message-Id: <$msgid>" $log echo 'Testing handling of rejected bounces' send_message '' me@example.com 33 5.2.2 echo 'Checking failed message was moved out of the queue' test -e $QUEUEDIR/failed/$msgid rm -f $QUEUEDIR/failed/$msgid echo 'Checking for no generated bounce message' msgid2=$( ls $QUEUEDIR/failed ) test $( wc -w <<< $msgid2 ) = 0 echo 'Checking log outputs' log=$tmpdir/service/send-log grep -qx "^Starting delivery: host: 127.0.0.1 protocol: dummy file: $msgid" $log grep -qx 'From: <> to: ' $log grep -qx 'Sending failed: Unspecified temporary error' $log grep -qx "Message-Id: <$msgid>" $log grep -qx "Not generating double bounce for $msgid" $log nullmailer-2.2/test/tests/smtp-auth000066400000000000000000000015701336020433200175060ustar00rootroot00000000000000. functions export HELOHOST=f.q.d.n make-testmail start server "tcpserver -1 ::0 0 sh $srcdir/test/authtest-smtp.sh $tmpdir/smtp-result" catch-port server echo 'Testing auth success with smtp' echo '250 OK' > $tmpdir/smtp-result protocol smtp --host=localhost --port=$port --user=example --pass=example 3<$testmail echo 'Testing auth login success with smtp' echo $'350 Go ahead\n250 AUTH' > $tmpdir/smtp-result protocol smtp --host=localhost --port=$port --user=example --pass=example --auth-login 3<$testmail echo 'Testing auth temporary failure with smtp' echo '450 No' > $tmpdir/smtp-result error 16 protocol smtp --host=localhost --port $port --user=example --pass=example 3<$testmail echo 'Testing auth permanent failure with smtp' echo '550 No' > $tmpdir/smtp-result error 20 protocol smtp --host=localhost --port $port --user=example --pass=example 3<$testmail stop server nullmailer-2.2/test/tests/smtpd000066400000000000000000000035561336020433200167210ustar00rootroot00000000000000. functions echo "Testing the nullmailer-smtpd program" out=$tmpdir/smtpd.out.$$ smtpd <&1 | cat -v >$out HELO HELO somebody EHLO EHLO somebody HELP HELP something DATA RCPT RCPT TO: MAIL MAIL FROM: RCPT RCPT TO: RSET RCPT TO: MAIL FROM:<> RSET MAIL FROM: RCPT TO: mail from f@example.com rcpt to r@example.com rcpt <@example.org:r2@example.com> DATA Subject: test testing SMTP .line 2 ..line 3 . QUIT QUIT EOF diff -u - $out <