--- /dev/null
+v1.4 December XX, 2012
+* New config file locating method. Attempts to detect Optware.
+* Fix: Bug introduced in 1.3.1 caused HUP not to use command-line vars when
+ reloading config variables.
+* Re-worked command-line/config parser in anticipation of 'bare options'.
+* New command-line option '--config' to specify the config file.
+* Finally added section on configuring Postfix to use POP-Before-SMTP-Auth.
+* Logging to syslog now optional.
+* Config parser now parses entire config file instead of terminating at the
+ first error. This allows you to see all config errors at once.
+* HUP now parses config and only reloads if there are no errors.
+* Includes a manpage
+
+v1.3.1 September 21, 2012
+* Internal code changes. Switched to single-quoting.
+* Fixed some config variable bugs that probably made 3.0 unusable for some.
+
+v1.3 September 21, 2012
+* tailfile class and file (tailfile.phpc) have been eliminated, making
+ pop-before-smtp-auth a self-contained script with fewer total lines.
+* Generates a message to syslog if the monitored log file disappears
+ for more than a configurable amount of seconds.
+* Various internal changes that didn't change functionality available,
+ but cleaned up the code a bit.
+
+v1.2 September 15, 2012
+* Use #!/usr/bin/env php for better portability
+* Use pcntl-signal-dispatch instead of ticks.
+* Now requires PHP 5.3 as a result.
+* New configuration setting 'logignores' (default TRUE) determines whether
+ messages about mail collectors get logged.
+* Fixed some error messages that weren't being sent to syslog.
+* Daemonization config setting (default TRUE) determines whether or not
+ pop-before-smtp-auth daemonizes.
+* The regex to match log lines can now be changed by setting the
+ 'regex', 'regexstamp', 'regexuser', and 'regexip" configuration settings.
+
+v1.1 January 22, 2012
+* Fixed up init scripts a bit
+* Changed to ignore Gmail mail collectors
+* Log failure if tailfile class file can't be found
+* Makes own PID file,
+* Daemonized
+* De-PHPified the config file.
+
+v1.0 January 4, 2012
+Initial Version
--- /dev/null
+ 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.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) 19yy <name of author>
+
+ 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) 19yy 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.
+
+ <signature of Ty Coon>, 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.
--- /dev/null
+See README for all instructions available at this time.
--- /dev/null
+POP-Before-SMTP-Auth v.1.3.2 October 27, 2012
+(c) 2012 Ron Guerin <ron@vnetworx.net>
+Licensed under GPL2 or later.
+Requires PHP-CLI 5.3 or greater with pcntl, PCRE and POSIX.
+
+This code is unsupported, though you can try mailing the list and if I can,
+and I have time, I may try to help. You have to join the list to post to it.
+
+Mailing List: http://lists.gothamcode.com/listinfo/gothamcode
+
+This script needs to be run as root to read log files and run postmap
+
+Installing
+----------
+1. Copy the script pop-before-smtp-auth to /usr/local/sbin
+2. Copy the manpage pop-before-smtp-auth.8.gz to /usr/local/man/man8
+3. Create the config file /etc/pop-before-smtp-auth.conf
+ Put your settings in this file rather than altering the script.
+
+Configuring Postfix
+-------------------
+Postfix's main.cf needs to be modified to include:
+ check_client_access hash:${posthashfile}
+
+where you'd substitute something like /var/lib/pop-before-smtp-auth/hosts
+for ${posthashfile}. This needs to go under smtp_recipient_restrictions,
+perhaps immediately follwing permit_sasl_authenticated .
+
+
+Settings
+--------
+Do not edit the script. Create a /etc/pop-before-smtp-auth.conf file
+to change settings, or set them via the command-line.
+
+Setting Default
+------------ -------------------------------------------------------------
+config = ./${scriptname}.conf, /etc/${scriptname}.conf,
+ or /etc/local/${scriptname}.conf (in that order)
+ If Optware is detected, these will be prefixed by /opt and
+ checked first. (see Config Search below)
+pidpath = /var/run (where to put the pidfile)
+hostname = (default: output of hostname --short)
+maillog = /var/log/mail.log (log file to monitor)
+authperiod = 30 (minutes to allow an IP address to relay)
+checkdelay = 5 (seconds, length of time between checking for changes)
+popserver = dovecot
+popservice = pop3-login
+postmap = postmap
+postinstance = /etc/postfix (the instance of Postfix to work with)
+posthashfile = /var/lib/${scriptname}/hosts
+debug = FALSE
+daemonize = TRUE (run as a daemon)
+logignores = TRUE (log ignored POP mail collectors)
+regex = (.*) $HOSTNAME $POPSERVER: $POPSERVICE: Login: user=<(.*)>, method=.*, rip=(.*), lip
+regexstamp = 1
+regexuser = 2
+regexip = 3
+silentmax = 30 (seconds to wait for monitored file exist before warning)
+stderr = /dev/null (where to point stderr)
+
+${scriptname} = whatever the name of the script is. Usually=pop-before-smtp-auth
+
+Command-line Options
+--------------------
+All configuration settings are available as command-line options. Settings
+made via the command-line override config file settings.
+
+For example, to use a config file with a non-standard name in a non-standard
+location, use the option --config=/path/to/configfile.conf
+
+To override a config file setting with the default setting, set the option
+on the command-line without a value. For example, if in your config file
+you have set: authperiod=60
+
+You can override it with its default like this:
+ --authperiod=
+To override it with a specific value:
+ --authperiod=45
+
+Options can be set to TRUE by either of the following:
+ --option=TRUE
+ --option
+The form --option is an abbreviation for --option=TRUE
+
+Config Search
+-------------
+The config file is searched for in the following order:
+1. If a config file is specified on the command-line, use that,
+ or terminate because it doesn't exist or it can't be read.
+2. ./${scriptname}.conf
+3. /opt/usr/local/etc/${scriptname}.conf
+4. /opt/etc/${scriptname}.conf
+5. /usr/local/etc/${scriptname}.conf
+6. /etc/${scriptname}.conf
+
+Changing the log-line matching regular expression
+-------------------------------------------------
+You should be able to substitute any PCRE regex for your own, by defining
+the configuration setting "regex". The following substitutions will be
+made at runtime:
+
+ * $HOSTNAME
+ * $POPSERVER
+ * $POPSERVICE
+
+These substitutions are made based on their corresponding configuation
+variable values. (ie: $HOSTNAME = the hostname config value)
+
+The values 'regexstamp', 'regexuser', and 'regexip' indicate which sub-matches
+in 'regex' contain the timestamp, user, and remote IP address, respectively.
+These three values are required to be able to use a custom regex in 'regex'.
+
+Unless you change it, the following default regex will be used:
+$HOSTNAME $POPSERVER: $POPSERVICE: Login: user=<(.*)>, method=.*, rip=(.*), lip
+
+With the default regex, the regexstamp is 1, regexuser is 2,
+and regexip is 3.
+
+Specifying regex changes on the command line is possible as with any other
+configuration setting, but you may find it difficult to properly escape
+everything.
+
+
+Init Scripts
+------------
+ Debian/Ubuntu/et al:
+ --------------------
+ Copy: pop-before-smtp-auth.init-debian to /etc/init.d/pop-before-smtp-auth
+ Run: update-rc.d pop-before-smtp-auth start 20 2 3 4 5 . stop 20 0 1 6 .
+
+ RedHat/Fedora/CentOS/et al:
+ ---------------------------
+ Copy: pop-before-smtp-auth.init-fedora to /etc/init.d/pop-before-smtp-auth
+ Run: ????
+
+The RedHat/Fedora init script is untested, and I don't know how to properly
+install it on a contemporary RedHat system.
+
+Good luck, Mr. Phelps.
--- /dev/null
+If upgrading from a version prior to 1.3, the configuration
+setting 'postinstance' has changed.
+
+From version 1.0 through 1.2, this setting assumed the Postfix
+directory was in /etc so that for /etc/postfix it was:
+
+ postinstance = postfix
+
+From version 1.3 on, this configuration setting requires the
+entire path to the Postfix instance:
+
+ postinstance = /etc/postfix
+
--- /dev/null
+git://git.gothamcode.com/pop-before-smtp-auth (git/read only)
+http://git.gothamcode.com/pop-before-smtp-auth (http/read only)
+ssh://git@git.gothamcode.com/pop-before-smtp-auth (ssh-public key/read-write)
--- /dev/null
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+
+[gitweb]
+ owner = Ron Guerin <ron@vnetworx.net>
+ description = POP-Before-SMTP-Auth, POP before SMTP daemon for *nix systems :: http://gothamcode.com/pop-before-smtp-auth
+ category = System daemons
+
+[remote "origin"]
+ url = ssh://git@git.gothamcode.com/pop-before-smtp-auth.git
+ fetch = +refs/heads/*:refs/remotes/origin/*
--- /dev/null
+#!/usr/bin/env php
+<?php
+/*
+ * POPBeforeSMTPAuth
+ *
+ * Implements POP-before-SMTP authorization for Dovecot POP3 with Postfix.
+ *
+ * Version 1.3.2, October 27, 2012
+ * Copyright (c) 2012, Ron Guerin <ron@vnetworx.net>
+ *
+ * This script implements POP-before-SMTP authorization for Dovecot POP3
+ * with Postfix by reading the syslog file.
+ *
+ * Requires: PHP_PCRE, PHP_pcntl, POSIX
+ *
+ * Do not edit this script! Edit /etc/${scriptname}.conf to change settings.
+ * where ${scriptname} is the name of this file. Changes made to the script
+ * will get overwritten on upgrades.
+ *
+ * POP-Before-SMTP-Auth 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.
+ *
+ * POP-Before-SMTP-Auth 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.
+ *
+ * If you are not able to view the file COPYING, please write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * to get a copy of the GNU General Public License or to report a
+ * possible license violation.
+ *
+ * @package POPBeforeSMTPAuth
+ * @author Ron Guerin <ron@vnetworx.net>
+ * @license http://www.fsf.org/licenses/gpl.html GNU Public License
+ * @copyright Copyright © 2012 Ron Guerin
+ * @filesourc
+ * @link http://gothamcode.com/pop-before-smtp-auth POPBeforeSMTPAuth
+ * @version 1.3.2
+ *
+ */
+
+# Configuration variables:
+# pidpath, hostname, maillog, authperiod, checkdelay, logignores,
+# popserver, popservice, postmap, postinstance, posthashfile, debug,
+# daemonize, regex, regexstamp, regexuser, regexip, silentmax
+#
+# To change, edit /etc/${scriptname}.conf:
+# instance = postfix-instance
+# maillog = /var/log/mymaillog
+
+define('VERSION', '1.3.2');
+
+// No, PHP Maintainers, it *is* safe to rely on the system timezone setting.
+@date_default_timezone_set(@date_default_timezone_get());
+
+// Settings
+$confvars = array('debug' => NULL, 'myname' => NULL, 'args' => array(),
+ 'myconfigname' => NULL, 'mydirs' => NULL, 'config' => NULL,
+ 'error' => NULL, 'syslog' => TRUE,
+ 'pidpath' => NULL, 'stdin' => NULL, 'stdout' => NULL, 'stderr' => NULL,
+ 'hostname' => NULL, 'maillog' => NULL, 'authperiod' => NULL,
+ 'checkdelay' => NULL, 'popserver' => NULL, 'popservice' => NULL,
+ 'postmap' => NULL, 'postinstance' => NULL, 'posthashfile' => NULL,
+ 'logignores' => NULL, 'daemonize' => NULL, 'regex' => NULL,
+ 'regexstamp' => NULL, 'regexuser' => NULL, 'regexip' => NULL,
+ 'silentmax' => NULL);
+$confvars['myname'] = preg_replace(chr(7).'.php$'.chr(7), '', basename($argv[0])); // Get my name, minus any .php extension
+
+$init = init_settings($confvars, $argv); // Get settings from defaults, config file, and commandline
+
+if ($confvars['syslog']) openlog($confvars['myname'], LOG_PID, LOG_MAIL); // Open syslog
+// Enable garbage collection (this means PHP 5.3+ now)
+gc_enable(); // Collect garbage every 60 cycles below
+
+$msg = 'Using '.$confvars['config'].' and watching log for '.$confvars['postinstance'];
+if ($confvars['syslog']) syslog(LOG_INFO, $msg);
+if ($init === FALSE) {
+ if ($confvars['error'][0] == CANT_OPEN_CONF) {
+ $retcode = CANT_OPEN_CONF;
+ log_msg($confvars, 'Error: '.((array_key_exists('myconfigname', $confvars) && $confvars['myconfigname'])
+ ? $confvars['myconfigname'] : $confvars['myname']).'.conf not found. Can\'t continue. Terminating', TRUE);
+ }
+ else {
+ foreach ($confvars['error'] as $msg) log_msg($confvars, $msg, TRUE);
+ $retcode = BAD_CONFIG;
+ }
+ if ($confvars['syslog']) closelog();
+ die((int) $retcode);
+}
+
+// Daemonize, handle TERM and HUP signals
+if ($confvars['daemonize']) $lock = daemonize($confvars['myname'], $confvars['syslog'], $confvars['pidpath'], $confvars['stdin'], $confvars['stdout'], $confvars['stderr']);
+pcntl_signal(SIGTERM, 'sig_handler');
+pcntl_signal(SIGHUP, 'sig_handler');
+
+// Now get about the point of all this...
+
+$iptable=array();
+$update = FALSE;
+
+$msg = 'POP-Before-SMTP-Auth Version '.VERSION.' starting.';
+if ($confvars['debug']) $msg .= ' postmap: '.$confvars['postmap'].' '.$confvars['posthashfile'].' mem:'.memory_get_usage().' max:'.memory_get_peak_usage();
+if ($confvars['syslog']) syslog(LOG_MAIL|LOG_INFO, $msg);
+
+init_posthashfile($confvars['postmap'], $confvars['postinstance'], $confvars['posthashfile']);
+
+$cycle = 0; $lastsize = 0; $waiting = 0;
+
+while (TRUE) {
+ pcntl_signal_dispatch();
+ if (! file_exists($confvars['maillog'])) {
+ $waiting = $waiting + $confvars['checkdelay'];
+ if ($waiting > $confvars['silentmax']) {
+ $msg = 'WARNING: Operation suspended. '.$confvars['maillog'].' hasn\'t existed for at least '.$confvars['silentmax'].' seconds.';
+ if ($confvars['syslog']) syslog(LOG_MAIL|LOG_INFO, $msg);
+ }
+ sleep($confvars['checkdelay']); // wait for it
+ continue;
+ }
+ if ($waiting > $confvars['silentmax']) {
+ $msg = 'NOTICE: '.$confvars['maillog'].' finally exists. Resuming normal operation.';
+ if ($confvars['syslog']) syslog(LOG_MAIL|LOG_INFO, $msg);
+ }
+ $waiting = 0;
+
+ $data = '';
+ clearstatcache();
+ $cursize = filesize($confvars['maillog']);
+ if ($cursize != $lastsize) {
+ $fd = fopen($confvars['maillog'], 'r');
+ // Better, I think, to seek forward than back, for accuracy sake
+ //fseek($fd, ($lastsize - $cursize) - 1, SEEK_END);
+ fseek($fd, $lastsize, SEEK_SET);
+ while((! feof($fd)) && ($lastsize > 0)) $data .= fgets($fd, 1024);
+ fclose($fd);
+ $lastsize = $cursize;
+ }
+
+ // Look for all matches in this chunk of data
+ if (preg_match_all(chr(7).$confvars['regex'].chr(7), $data, $matches) != FALSE) {
+ // loop through matches
+ foreach ($matches[$confvars['regexip']] as $key => $ip) {
+ // Ignore POP3 mail pickups from Gmail, these don't need relay access
+ $dnsname = dnsname($ip);
+ if (! preg_match('/mail-.*\.google\.com/', $dnsname)) {
+ if (! isset($iptable[$ip])) {
+ $update = TRUE;
+ $msg = 'Relaying for: ['.$ip.'] '.$dnsname;
+ //echo date('M j h:i:s ').'Relaying for: ['.$ip.'] '.$dnsname.' on '.date('r', strtotime($matches[$confvars['regexstamp']][$key]))."\n";
+ if ($confvars['debug']) $msg .= ' mem:'.memory_get_usage().' max:'.memory_get_peak_usage();
+ if ($confvars['syslog']) syslog(LOG_MAIL|LOG_INFO, $msg);
+ }
+ $iptable[$ip]=strtotime($matches[1][$key]) + $confvars['authperiod']; // last auth + authperiod = time auth expires
+ }
+ else {
+ if ($confvars['logignores']) {
+ $msg = 'Ignoring mail collector: '.$dnsname;
+ if ($confvars['debug']) $msg .= ' mem:'.memory_get_usage().' max:'.memory_get_peak_usage();
+ if ($confvars['syslog']) syslog(LOG_MAIL|LOG_INFO, $msg);
+ }
+ }
+ }
+ }
+
+ // If something expired, set update flag
+ foreach ($iptable as $ip => $authtime) {
+ if ($authtime < time()) {
+ $msg = 'Ending relay for: ['.$ip.']';
+ if ($confvars['debug']) $msg .= ' mem:'.memory_get_usage().' max:'.memory_get_peak_usage();
+ if ($confvars['syslog']) syslog(LOG_MAIL|LOG_INFO, $msg);
+ //echo date('M j h:i:s ').'Ending relay for: '.$ip.' on '.date('r', $authtime)."\n";
+ unset($iptable[$ip]);
+ $update = TRUE;
+ }
+ }
+
+ if ($update) {
+ if (! $handle = fopen($confvars['posthashfile'].'.new', 'w')) {
+ $msg = 'Cannot open file: '.$confvars['posthashfile'].'.new';
+ if ($confvars['syslog']) {
+ syslog(LOG_MAIL|LOG_INFO, $msg);
+ closelog();
+ }
+ die(date('M j h:i:s ').$msg."\n");
+ }
+
+ foreach ($iptable as $ip => $authtime) {
+ if (fwrite($handle, $ip." OK\n") === FALSE) {
+ $msg = 'Cannot write to file: '.$confvars['posthashfile'].'.new';
+ if ($confvars['syslog']) {
+ syslog(LOG_MAIL|LOG_INFO, $msg);
+ closelog();
+ }
+ die(date('M j h:i:s ').$msg."\n");
+ }
+ }
+ fclose($handle);
+ postmap($confvars['postmap'], $confvars['postinstance'], $confvars['posthashfile']);
+ $update = FALSE;
+ }
+
+ l
+
+ if (++$cycle == 60) {
+ $cycle = 0;
+ gc_collect_cycles();
+ }
+
+ sleep ($confvars['checkdelay']);
+}
+// We can never get here because the while loop never ends
+
+/* *********************************************************************** */
+
+function sig_handler($signo){
+ global $confvars, $argv;
+
+ switch ($signo) {
+ case SIGTERM:
+ // handle shutdown tasks
+ $msg = 'Clearing IPs and shutting down.';
+ if ($confvars['debug']) $msg .= ' mem:'.memory_get_usage().' max:'.memory_get_peak_usage();
+ if ($confvars['syslog']) syslog(LOG_MAIL|LOG_INFO, $msg);
+ init_posthashfile($confvars['postmap'], $confvars['postinstance'], $confvars['posthashfile']);
+ if ($confvars['syslog']) closelog();
+ exit;
+ break;
+ case SIGHUP:
+ // handle reload tasks
+ $msg = 'Reloading configuration settings from config file and command-line.';
+ if ($confvars['debug']) $msg .= ' mem:'.memory_get_usage().' max:'.memory_get_peak_usage();
+ if ($confvars['syslog']) syslog(LOG_MAIL|LOG_INFO, $msg);
+ $tempvars = $confvars;
+ $init = init_settings($tempvars, $argv);
+ $msg = 'Reloading from '.$tempvars['config'].'.';
+ if ($confvars['syslog']) syslog(LOG_INFO, $msg);
+ if ($init === FALSE) {
+ $msg = 'Errors reloading from '.$tempvars['config'];
+ if ($confvars['syslog']) syslog(LOG_MAIL|LOG_INFO, $msg);
+ fwrite($confvars['stderr'], date('M j h:i:s ').$msg."\n");
+ foreach ($tempvars['error'] as $msg) {
+ if ($confvars['syslog']) syslog(LOG_MAIL|LOG_INFO, $msg);
+ fwrite($confvars['stderr'], date('M j h:i:s ').$msg."\n");
+ #echo date('M j h:i:s ').$msg."\n";
+ }
+ }
+ else $confvars = $tempvars;
+ break;
+ }
+}
+
+function init_posthashfile($postmap, $postinstance, $posthashfile) {
+ if (file_exists($posthashfile.'.new')) {
+ if (! unlink($posthashfile.'.new')) if ($confvars['syslog']) syslog(LOG_MAIL|LOG_INFO, 'ERROR: Problem unlinking '.$posthashfile.'.new');
+ }
+ if (! touch($posthashfile.'.new')) if ($confvars['syslog']) syslog(LOG_MAIL|LOG_INFO, 'ERROR: Problem touching new '.$posthashfile);
+ postmap($postmap, $postinstance, $posthashfile);
+}
+
+function postmap($postmap, $postinstance, $posthashfile) {
+ if (file_exists($posthashfile.'.new')) {
+ exec($postmap.' -c '.$postinstance.' '.$posthashfile.'.new', $out);
+ if ($out[0]) if ($confvars['syslog']) syslog(LOG_MAIL|LOG_INFO, 'ERROR: Problem ('.explode($out).') running postmap on '.$posthashfile.'.new for '.$postinstance);
+ if (! rename($posthashfile.'.new', $posthashfile)) if ($confvars['syslog']) syslog(LOG_MAIL|LOG_INFO, 'ERROR: Problem renaming '.$posthashfile.'.new');
+ if (! rename($posthashfile.'.new.db', $posthashfile.'.db')) if ($confvars['syslog']) syslog(LOG_MAIL|LOG_INFO, 'ERROR: Problem renaming '.$posthashfile.'.new.db');
+ }
+}
+
+function dnsname($ip){
+ $ptr= implode('.',array_reverse(explode('.',$ip))).'.in-addr.arpa';
+ $host = dns_get_record($ptr,DNS_PTR);
+ if ($host == null) return FALSE;
+ else return $host[0]['target'];
+}
+
+function init_settings(&$confvars, $argv) {
+ // This function is generic and passes data via $confvars
+ $confvars['error'] = NULL; // We turn this into an array to pass data back
+ $cmdline = TRUE;
+
+ // Parse out any command-line args first, skipping argv[0]
+ $cmdlinevars = array();
+ $cmdlineargs = array();
+ $optsdone = FALSE;
+ $skip = TRUE;
+ foreach ($argv as $argnum => $arg) {
+ if ($skip) $skip = FALSE;
+ else {
+ if ($arg == '--') $optsdone = TRUE;
+ if ((substr($arg, 0, 2) == '--') && (! $optsdone)) {
+ $sep = strpos($arg,'=');
+ if ($sep !== FALSE) { // This is set to a value
+ $var = trim(substr($arg, 2, $sep - 2));
+ $val = trim(substr($arg, $sep + 1));
+ }
+ else { // This is an implicit setting of TRUE
+ $var = trim(substr($arg, 2));
+ $val = TRUE;
+ }
+
+ if (array_key_exists($var, $confvars)) {
+ if ($val == '') $val = NULL;
+ if (strtoupper($val) == 'NULL') $val = NULL;
+ if (strtoupper($val) == 'TRUE') $val = TRUE;
+ if (strtoupper($val) == 'FALSE') $val = FALSE;
+ if (is_array($confvars[$var])) {
+ $cmdlinevars["$var"][] = $val;
+ }
+ else {
+ $cmdlinevars["$var"] = $val;
+ }
+ }
+ else {
+ $confvars['error'][] = "Unknown command-line option --$var";
+ $cmdline = FALSE;
+ }
+ }
+ elseif ((substr($arg, 0, 1) == '-') && (strlen($arg) == 2) && (! $optsdone)) {
+ // Try to parse single letter, single dash options
+ if (in_array(substr($arg, 1), $confvars)) { // Does not accept value
+ $cmdlinevars["$var"] = TRUE;
+ }
+ elseif (array_key_exists(substr($arg, 1).':', $confvars)) { // Required value
+ if (($argnum + 1 >= $argc) || (substr($argv[$argnum + 1], 0, 1) == '-')) {
+ $confvars['error'][] = "Required value for $arg not given.";
+ $cmdline = FALSE;
+ }
+ else {
+ $var = $confvars[substr($arg, 1).':'];
+ $val = trim($argv[$argnum + 1]);
+ $skip = TRUE;
+ $cmdlinevars["$var"] = $val;
+ }
+ }
+ elseif (array_key_exists(substr($arg, 1).'::', $confvars)) { // Optional value
+ if (($argnum + 1 < $argc) && (substr($argv[$argnum + 1], 0, 1) != '-')) {
+ $var = $confvars[substr($arg, 1).'::'];
+ $val = trim($argv[$argnum + 1]);
+ $skip = TRUE;
+ $cmdlinevars["$var"] = $val;
+ }
+ else {
+ $var = $confvars[substr($arg, 1).'::'];
+ $cmdlinevars["$var"] = TRUE;
+ }
+ }
+ else {
+ $confvars['error'][] = "Unknown command-line option -$var";
+ $cmdline = FALSE;
+ }
+ }
+ else { // Else it's a command argument
+ $cmdlineargs[]=$arg;
+ }
+ }
+ }
+ $confvars['arguments'] = $cmdlineargs;
+
+ // Set configuration settings with settings from config file.
+ // Get config file name from command line or look for it in the file system.
+ if (array_key_exists('config', $cmdlinevars)) {
+ if (file_exists($cmdlinevars('config'))) $config = $cmdlinevars('config');
+ else $config = FALSE;
+ }
+ else {
+ $config = config_location($confvars); // Returns config file pathname or FALSE
+ }
+ if ($config) {
+ $confvars['config'] = $config;
+ $lines = file($config);
+ foreach ($lines as $key => $line) {
+ $line=trim($line);
+ // Ignore blank lines and lines beginning with #, //, or ;
+ if (($line != '') && (substr($line, 0, 1) != '#') && (substr($line, 0, 2) != '//') && (substr($line, 0, 1) != ';')) {
+ $sep = strpos($line, '=');
+ if ($sep === FALSE) { // This is an implicit setting of TRUE
+ $var = $line;
+ $val = TRUE;
+ }
+ else { // This is set to a value
+ $var = trim(substr($line, 0, $sep));
+ $val = trim(substr($line, $sep + 1));
+ }
+ if (array_key_exists($var, $confvars)) {
+ if ($val == '') $val = NULL;
+ if (strtoupper($val) == 'NULL') $val = NULL;
+ if (strtoupper($val) == 'TRUE') $val = TRUE;
+ if (strtoupper($val) == 'FALSE') $val = FALSE;
+
+ if (is_array($confvars[$var])) {
+ $confvars["$var"][] = $val;
+ }
+ else {
+ $confvars["$var"] = $val;
+ }
+ }
+ else {
+ $confvars['error'][] = "Unknown configuration file setting: $var";
+ $config = FALSE;
+ }
+ }
+ }
+ }
+ elseif ($confvars['mustconf']) { // If must have config file, return FALSE now
+ $confvars['error'] = array(CANT_OPEN_CONF);
+ return FALSE;
+ }
+
+ // Override all previous settings via any command-line args
+ $confvars = array_merge($confvars, $cmdlinevars);
+
+ // Certain things *have* to be set for this to work. If they're not set by now,
+ // attempt to set them to reasonable defaults. Return config file pathname
+ // or FALSE if anything goes wrong here or in set_defaults.
+ $defaults = init_settings_defaults($confvars);
+ // Resolve variables, ie: variable=$someothervariable
+ do {
+ $unresolved = FALSE;
+ foreach ($confvars as $key => $value) {
+ if ((! is_array($value)) && (! is_resource($value))) {
+ if (substr($value, 0, 1) == '$') {
+ $var = substr($value, 1);
+ if (array_key_exists($var, $confvars)) {
+ $confvars[$key] = $confvars[$var];
+ if (substr($confvars[$var], 0, 1) == '$') $unresolved = TRUE;
+ }
+ }
+ }
+ }
+ } while ($unresolved);
+ return (($defaults && $config && $cmdline) ? TRUE : FALSE);
+}
+
+function init_settings_defaults(&$confvars) {
+ // Application-specific code, returns FALSE on error and message in $confvars['error']
+ $return = TRUE;
+ if ((($confvars['debug'] == NULL) || (trim($confvars['debug']) == '')) && ($confvars['debug'] !== TRUE)) $confvars['debug'] = FALSE; // Don't print debug messages
+ if (($confvars['myconfigname'] == NULL) || (trim($confvars['myconfigname']) == '')) $confvars['myconfigname'] = FALSE; // Use default config file name
+ if (is_resource(STDIN)){ // STDIN will not be a resource if we've already closed it
+ // These can't be changed after daemonization. Restart to change standard file handles.
+ $confvars['stdin'] = '/dev/null'; // Don't allow stdin to be pointed away from /dev/null
+ if (($confvars['stdout'] == NULL) || (trim($confvars['stdout']) == '')) $confvars['stdout'] = '/dev/null';
+ if (($confvars['stderr'] == NULL) || (trim($confvars['stderr']) == '')) $confvars['stderr'] = '/dev/null';
+ }
+ if ((($confvars['syslog'] == NULL) || (trim($confvars['syslog']) == '')) && ($confvars['syslog'] !== FALSE)) $confvars['syslog'] = TRUE; // Log to syslog
+ if (($confvars['pidpath'] == NULL) || (trim($confvars['pidpath']) == '')) $confvars['pidpath'] = '/var/run';
+ if (($confvars['hostname'] == NULL) || (trim($confvars['hostname']) == '')) $confvars['hostname'] = substr(php_uname('n'), 0, strpos(php_uname('n'), '.'));
+ if ((($confvars['daemonize'] == NULL) || (trim($confvars['daemonize']) == '')) && ($confvars['daemonize'] !== FALSE)) $confvars['daemonize'] = TRUE; // Daemonize when run
+ if (($confvars['popserver'] == NULL) || (trim($confvars['popserver']) == '')) $confvars['popserver'] = 'dovecot';
+ if (($confvars['popservice'] == NULL) || (trim($confvars['popservice']) == '')) $confvars['popservice'] = 'pop3-login';
+ if (($confvars['postinstance'] == NULL) || (trim($confvars['postinstance']) == '')) $confvars['postinstance'] = '/etc/postfix';
+ if (($confvars['postmap'] == NULL) || (trim($confvars['postmap']) == '')) $confvars['postmap'] = '/usr/sbin/postmap';
+ if (($confvars['maillog'] == NULL) || (trim($confvars['maillog']) == '')) $confvars['maillog'] = '/var/log/mail.log';
+ if (($confvars['posthashfile'] == NULL) || (trim($confvars['posthashfile']) == '')) $confvars['posthashfile'] = '/var/lib/pop-before-smtp-auth/hosts';
+ if (($confvars['authperiod'] == NULL) || (! is_numeric($confvars['authperiod']))) $confvars['authperiod'] = 1800; // 1800 = 30 minutes
+ if (($confvars['checkdelay'] == NULL) || (! is_numeric($confvars['checkdelay']))) $confvars['checkdelay'] = 5; // 5 seconds
+ if ((($confvars['logignores'] == NULL) || (trim($confvars['logignores']) == '')) && ($confvars['logignores'] !== FALSE)) $confvars['logignores'] = TRUE; // Log ignored mail collectors
+ if (($confvars['regex'] == NULL) || (trim($confvars['regex']) == '')) {
+ // Default regex
+ //Jan 3 16:08:23 hostname dovecot: pop3-login: Login: user=<user@example.com>, method=PLAIN, rip=192.168.1.201, lip=4.2.2.2, mpid=18943
+ $confvars['regex'] = '(.*) '.$confvars['hostname'].' '.$confvars['popserver'].': '.$confvars['popservice'].': Login: user=<(.*)>, method=.*, rip=(.*), lip';
+ $confvars['regexstamp'] = 1; $confvars['regexuser'] = 2; $confvars['regexip'] = 3;
+ }
+ else {
+ // User-provided regex
+ if ((! isset($confvars['regexstamp'])) || (trim($confvars['regexstamp']) == '') ||
+ (! isset($confvars['regexuser'])) || (trim($confvars['regexuser']) == '') ||
+ (! isset($confvars['regexip'])) || (trim($confvars['regexip']) == '')) {
+ $confvars['error'][] = 'Custom regex specified, but required \'regexstamp\', or \'regexuser\', or \'regexip\' was not specified.';
+ $return = FALSE;
+ }
+ // \\\$ = $ needs to be escaped with \, and so does the escape or PCRE won't see it
+ $confvars['regex'] = preg_replace('/\\\$HOSTNAME/', $confvars['hostname'], $confvars['regex']);
+ $confvars['regex'] = preg_replace('/\\\$POPSERVER/', $confvars['popserver'], $confvars['regex']);
+ $confvars['regex'] = preg_replace('/\\\$POPSERVICE/', $confvars['popservice'], $confvars['regex']);
+ }
+ if (($confvars['silentmax'] == NULL) || (! is_numeric($confvars['silentmax']))) $confvars['silentmax'] = 30; // 30 seconds
+ return $return;
+}
+
+function daemonize($myname, $syslog, $pidpath, &$stdin='/dev/null', &$stdout='/dev/null', &$stderr='/dev/null') {
+ // Returns file descriptors in place of file paths for $stdin, $stdout, $stderr
+
+ // Fork, become session leader, fork again, and close open handles so there's no zombie.
+ // Open lock file
+ $lock = fopen($pidpath.'/'.$myname.'.pid', 'c+');
+ if (! flock($lock, LOCK_EX | LOCK_NB)) {
+ $msg = $myname.' already running.';
+ if ($syslog) {
+ syslog(LOG_MAIL|LOG_INFO, $msg);
+ closelog();
+ }
+ die(date('M j h:i:s ').$msg."\n");
+ }
+
+ // Fork. If we get a PID, exit. If we get 0, we're the child, continue
+ if (pcntl_fork()) exit();
+
+ // Dissociate from controlling terminal, become session leader
+ if (posix_setsid() === -1) {
+ $msg = $myname.' could not setsid.';
+ if ($syslog) {
+ syslog(LOG_MAIL|LOG_INFO, $msg);
+ closelog();
+ }
+ die(date('M j h:i:s ').$msg."\n");
+ }
+ usleep(100000); // sleep 1/10th of a second
+
+ // Fork again as session leader to be free of other processes. If pcntl_fork
+ // returns 0 we're the child, else we're the parent getting the PID of the child
+ $childpid = pcntl_fork();
+ if($childpid) {
+ // If we get here, we're the parent, write the child's PID to pidfile.
+ $msg = $myname.' daemonizing.';
+ if ($syslog) {
+ syslog(LOG_MAIL|LOG_INFO, $msg);
+ closelog();
+ }
+ echo date('M j h:i:s ').$msg."\n";
+ fseek($lock, 0);
+ ftruncate($lock, 0);
+ fwrite($lock, $childpid);
+ fflush($lock);
+ exit();
+ }
+ else {
+ // If we get here, we're the child, finally independent. Grab lockfile.
+ usleep(100000); // sleep 1/10th of a second
+ flock($lock, LOCK_EX | LOCK_NB);
+ }
+
+ // As we are a daemon, close standard file descriptors.
+ fclose(STDIN); fclose(STDOUT); fclose(STDERR);
+
+ // http://andytson.com/blog/2010/05/daemonising-a-php-cli-script-on-a-posix-system/
+ // When a standard file descriptor is closed, it can be replaced.
+ // Create new standard file descriptors in case anything tries to use them.
+ // Variable names are not important, but do not re-order the fopens
+ if (($stdout = '/dev/null') && ($stderr = '/dev/null')) {
+ $stderr = 'php://stdout'; // hack to duplicate fd1 to fd2
+ }
+ $stdin = fopen('/dev/null', 'r'); // set fd0
+ $stdout = fopen($stdout, 'w'); // set fd1
+ $stderr = fopen($stderr, 'w'); // set fd2 or set fd2 to fd1
+
+ // Ignore some signals we don't care about
+ pcntl_signal(SIGCHLD, SIG_IGN);
+ pcntl_signal(SIGTSTP, SIG_IGN);
+ pcntl_signal(SIGTTOU, SIG_IGN);
+ pcntl_signal(SIGTTIN, SIG_IGN);
+
+ return $lock;
+}
+
+function config_location($confvars) {
+ // Find config file, and return it. Looks first in current directory,
+ // then iterates through array of standard directories. For each
+ // standard directory, it first looks in the standard directory, then in
+ // whatever subdirectories are specified in array $confvars['mydirs'].
+ // Tries to detect Optware, searches /opt first when detected.
+
+ if ($confvars['myconfigname']) $myname = $confvars['myconfigname']; else $myname = $confvars['myname'];
+ $mydirs = $confvars['mydirs'];
+ $conf = './'.$myname.'.conf';
+
+ if (file_exists($conf)) return $conf;
+ $prefixes = file_exists('/opt/bin/ipkg-opt') ? array('/opt', '') : array(''); # This is a crude test for Optware
+ if ((! is_array($mydirs)) && (($mydirs == NULL) || (trim($mydirs) == ''))) $mydirs = array();
+ $confdirs = array_merge((array)'', $mydirs); // '' == current directory
+ $stddirs = array('/usr/local/etc', '/etc');
+
+ foreach ($prefixes as $prefix) {
+ foreach ($stddirs as $base) {
+ foreach ($confdirs as $dir) {
+ if ($dir != '') $dir = $dir.'/';
+ $conf = $prefix.$base.'/'.$dir.$myname.'.conf';
+ if (file_exists($conf)) return $conf;
+ }
+ }
+ }
+ return FALSE;
+}
+
+function log_msgs($vars, $msgs, $stderr=FALSE) {
+ foreach ($msgs as $msg) log_msg($vars, $msg, $stderr);
+}
+
+function log_msg($vars, $msg, $stderr=FALSE) {
+ if ($vars['debug']) $msg .= ' mem: '.memory_get_usage().' max: '.memory_get_peak_usage();
+ if ($vars['syslog']) syslog($vars['sl_prio'], $msg);
+ if (array_key_exists('daemonize', $vars) && (! $vars['daemonize']))echo date('M j h:i:s ').$msg."\n";
+ elseif ($stderr === TRUE) {
+ fwrite($vars['stderr'], date('M j h:i:s ').$msg."\n");
+ }
+}
+?>
--- /dev/null
+.\" Manpage for pop-before-smtp-auth.
+.\" Contact ron@vnetworx.net with corrections and improvements.
+.TH POP-BEFORE-SMTP-AUTH 8 "2012-11-30" "1.3.2" "pop-before-smtp-auth man page"
+.SH NAME
+pop-before-smtp-auth \- POP Before SMTP Authorization Daemon for Postfix and Dovecot
+.SH SYNOPSIS
+pop-before-smtp-auth [--debug=TRUE|FALSE] [--config=/path/to/config] [--syslog=TRUE|FALSE] [--pidpath=/path/to/pidfile]
+[--hostname=hostname] [--maillog=/path/to/mail.log] [--authperiod=seconds] [--checkdelay=seconds] [--popserver=popserver]
+[--popservice=popservice] [--postmap=/path/to/postmap] [--postinstance=/path/to/postfix_config_dir]
+[--posthashfile=/path/to/pop_users_hashfile] [--logignores=TRUE|FALSE] [--daemonize=TRUE|FALSE] [--regex='regex']
+[--regexstamp=''] [--regexuser=''] [--regexip=''] [--silentmax=seconds]
+.SH DESCRIPTION
+pop-before-smtp-auth (PBSA) is a POP before SMTP Authorization Daemon for use with Postfix and Dovecot.
+PBSA monitors your mail server log file looking for successful POP3 logins via Dovecot, and authorizes
+the corresponding IP addresses to use Postfix as a SMTP relay for a limited time.
+.SH OPTIONS
+pop-before-smtp-auth has many options which control its behavior. These may be set by default, through
+a configuration file, or via the command-line. Options may be specified in the configuration file like this:
+ option
+ option=value
+ option='string value'
+
+Options may be specified on the command-line as follows:
+ --option
+ --option=value
+ --option='string value'
+
+Options:
+--debug
+.RE
+Include debugging information in messages.
+--config
+.RE
+Path to configuration file (can only be set on command-line).
+
+An option not set to anything is the same as setting it to TRUE. For example on the command-line,
+.B --option
+is the same as
+.B --option=TRUE
+, while in the configuration file,
+.B option
+is the same as
+.B option=TRUE
+
+.SH FILES
+/etc/pop-before-smtp-auth.conf
+.SH NOTES
+pop-before-smtp-auth (PBSA) recognizes the signals HUP and TERM. Send TERM to get PBSA to shut down,
+send HUP to get PBSA to reload its configuration settings.
+.SH AUTHOR
+Ron Guerin <ron@vnetworx.net>
--- /dev/null
+<?php
+# Values here override defaults
+# Lines that start with "#" are comments
+
+# $myname = "localhost";
+# $myserver = "dovecot";
+# $myservice = "pop3-login";
+# $instance = "/etc/postfix";
+# $maillog = "/var/log/mail.log";
+# $hashfile = "/var/lib/pop-before-smtp-auth/hosts";
+# $authperiod = 1800; // 1800 = 30 minutes
+# $check_delay = 5; // 5 seconds
+?>
--- /dev/null
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides: pop-before-smtp-auth
+# Required-Start: $syslog
+# Required-Stop: $syslog
+# Should-Start: $local_fs
+# Should-Stop: $local_fs
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+### END INIT INFO
+
+NAME=pop-before-smtp-auth
+DESC=pop-before-smtp-auth
+PIDFILE=/var/run/$NAME.pid
+DIR=/usr/local/sbin
+
+case "$1" in
+ start)
+ echo -n "Starting $DESC: "
+ start-stop-daemon -S -p $PIDFILE --exec $DIR/$NAME
+ ;;
+ restart)
+ $0 stop
+ $0 start
+ ;;
+ reload)
+ PID=`pidof php $DIR/$NAME`
+ if [ -f $PIDFILE ]; then
+ PIDFILEPID=`cat $PIDFILE`
+ if [ "X$PIDFILEPID" = "X$PID" ]; then
+ echo "Reloading $NAME."
+ kill -HUP $PID
+ exit 0
+ fi
+ fi
+ echo "$NAME not running."
+ ;;
+ stop)
+ echo -n "Stopping $DESC: "
+ start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE
+ echo "$NAME."
+ rm -f $PIDFILE
+ ;;
+ *)
+ N=/etc/init.d/$NAME
+ echo "Usage: $N {start|stop|reload}" >&2
+ ;;
+esac
+
+exit 0
+
--- /dev/null
+#!/bin/sh
+#
+# pop-before-smtp-auth SMTP Auth for POP3
+#
+# chkconfig: 2345 20 80
+# description: Provides SMTP Auth for POP3 clients
+
+### BEGIN INIT INFO
+# Provides:
+# Required-Start:
+# Required-Stop:
+# Should-Start:
+# Should-Stop:
+# Default-Start:
+# Default-Stop:
+# Short-Description:
+# Description:
+### END INIT INFO
+
+# Source function library.
+. /etc/rc.d/init.d/functions
+
+exec="/usr/local/sbin/pop-before-smtp-auth"
+prog="pop-before-smtp-auth"
+config="/etc/pop-before-smtp-auth.conf"
+pid=`pidof php $exec`
+
+[ -e /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog
+
+lockfile=/var/lock/subsys/$prog
+
+start() {
+ [ -x $exec ] || exit 5
+ [ -f $config ] || exit 6
+ echo -n $"Starting $prog: "
+ daemon $exec
+ retval=$?
+ echo
+ [ $retval -eq 0 ] && touch $lockfile
+ return $retval
+}
+
+stop() {
+ echo -n $"Stopping $prog: "
+ killproc $prog
+ retval=$?
+ echo
+ [ $retval -eq 0 ] && rm -f $lockfile
+ return $retval
+}
+
+restart() {
+ stop
+ start
+}
+
+reload() {
+ if [ "X$pid" != "X" ]; then
+ term -HUP $pid
+ fi
+}
+
+force_reload() {
+ restart
+}
+
+rh_status() {
+ # run checks to determine if the service is running or use generic status
+ status $prog
+}
+
+rh_status_q() {
+ rh_status >/dev/null 2>&1
+}
+
+
+case "$1" in
+ start)
+ rh_status_q && exit 0
+ $1
+ ;;
+ stop)
+ rh_status_q || exit 0
+ $1
+ ;;
+ restart)
+ $1
+ ;;
+ reload)
+ rh_status_q || exit 7
+ $1
+ ;;
+ force-reload)
+ force_reload
+ ;;
+ status)
+ rh_status
+ ;;
+ condrestart|try-restart)
+ rh_status_q || exit 0
+ restart
+ ;;
+ *)
+ echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}"
+ exit 2
+esac
+exit $?
--- /dev/null
+pop-before-smtp-auth
\ No newline at end of file