Skip to content
Browse files

r6446@embla: ask | 2005-11-18 01:30:05 -0800

 rename 0.31 branch to 0.3x


git-svn-id: http://svn.perl.org/qpsmtpd/branches/0.3x@573 958fd67b-6ff1-0310-b445-bb7760255be9
  • Loading branch information...
0 parents commit 78110b5ef9ad2fa64a2301152006f6d38144b039 ask committed Nov 18, 2005
Showing with 9,891 additions and 0 deletions.
  1. +16 −0 .perltidyrc
  2. +36 −0 CREDITS
  3. +582 −0 Changes
  4. +19 −0 LICENSE
  5. +100 −0 MANIFEST
  6. +27 −0 MANIFEST.SKIP
  7. +27 −0 Makefile.PL
  8. +210 −0 README
  9. +74 −0 README.logging
  10. +351 −0 README.plugins
  11. +110 −0 STATUS
  12. +4 −0 config.sample/IP
  13. +4 −0 config.sample/badhelo
  14. +5 −0 config.sample/badrcptto_patterns
  15. +4 −0 config.sample/dnsbl_zones
  16. +6 −0 config.sample/invalid_resolvable_fromhost
  17. +1 −0 config.sample/logging
  18. +10 −0 config.sample/loglevel
  19. +54 −0 config.sample/plugins
  20. +4 −0 config.sample/relayclients
  21. +3 −0 config.sample/require_resolvable_fromhost
  22. +5 −0 config.sample/rhsbl_zones
  23. +3 −0 config.sample/size_threshold
  24. +220 −0 lib/Apache/Qpsmtpd.pm
  25. +456 −0 lib/Qpsmtpd.pm
  26. +328 −0 lib/Qpsmtpd/Address.pm
  27. +333 −0 lib/Qpsmtpd/Auth.pm
  28. +149 −0 lib/Qpsmtpd/Connection.pm
  29. +108 −0 lib/Qpsmtpd/Constants.pm
  30. +176 −0 lib/Qpsmtpd/Plugin.pm
  31. +202 −0 lib/Qpsmtpd/Postfix.pm
  32. +602 −0 lib/Qpsmtpd/SMTP.pm
  33. +320 −0 lib/Qpsmtpd/SelectServer.pm
  34. +90 −0 lib/Qpsmtpd/TcpServer.pm
  35. +323 −0 lib/Qpsmtpd/Transaction.pm
  36. +15 −0 lib/Qpsmtpd/Utils.pm
  37. +2 −0 log/.cvsignore
  38. +5 −0 log/run
  39. +109 −0 plugins/auth/auth_cvm_unix_local
  40. +75 −0 plugins/auth/auth_flat_file
  41. +192 −0 plugins/auth/auth_ldap_bind
  42. +132 −0 plugins/auth/auth_vpopmail_sql
  43. +18 −0 plugins/auth/authdeny
  44. +17 −0 plugins/auth/authnull
  45. +54 −0 plugins/check_badmailfrom
  46. +58 −0 plugins/check_badmailfromto
  47. +18 −0 plugins/check_badrcptto
  48. +47 −0 plugins/check_badrcptto_patterns
  49. +70 −0 plugins/check_basicheaders
  50. +145 −0 plugins/check_earlytalker
  51. +52 −0 plugins/check_loop
  52. +57 −0 plugins/check_norelay
  53. +26 −0 plugins/check_relay
  54. +33 −0 plugins/check_spamhelo
  55. +26 −0 plugins/content_log
  56. +49 −0 plugins/count_unrecognized_commands
  57. +151 −0 plugins/dns_whitelist_soft
  58. +307 −0 plugins/dnsbl
  59. +271 −0 plugins/greylisting
  60. +49 −0 plugins/http_config
  61. +30 −0 plugins/ident/geoip
  62. +99 −0 plugins/ident/p0f
  63. +187 −0 plugins/logging/adaptive
  64. +7 −0 plugins/logging/devnull
  65. +78 −0 plugins/logging/warn
  66. +235 −0 plugins/milter
  67. +138 −0 plugins/queue/exim-bsmtp
  68. +82 −0 plugins/queue/maildir
  69. +44 −0 plugins/queue/postfix-queue
  70. +114 −0 plugins/queue/qmail-queue
  71. +69 −0 plugins/queue/smtp-forward
  72. +18 −0 plugins/quit_fortune
  73. +35 −0 plugins/rcpt_ok
  74. +98 −0 plugins/require_resolvable_fromhost
  75. +112 −0 plugins/rhsbl
  76. +123 −0 plugins/sender_permitted_from
  77. +253 −0 plugins/spamassassin
  78. +152 −0 plugins/tls
  79. +180 −0 plugins/virus/aveclient
  80. +133 −0 plugins/virus/bitdefender
  81. +39 −0 plugins/virus/check_for_hi_virus
  82. +210 −0 plugins/virus/clamav
  83. +179 −0 plugins/virus/clamdscan
  84. +159 −0 plugins/virus/hbedv
  85. +177 −0 plugins/virus/kavscanner
Sorry, we could not display the entire diff because it was too big.
16 .perltidyrc
@@ -0,0 +1,16 @@
+
+-i=4 # 4 space indentation (we used to use 2; in the future we'll use 4)
+-ci=2 # continuation indention
+
+-pt=2 # tight parens
+-sbt=2 # tight square parens
+-bt=2 # tight curly braces
+-bbt=0 # open code block curly braces
+
+-lp # line up with parentheses
+-cti=1 # align closing parens with opening parens ("closing token placement")
+
+# -nolq # don't outdent long quotes (not sure if we should enable this)
+
+
+
36 CREDITS
@@ -0,0 +1,36 @@
+Jim Winstead <jimw@trainedmonkey.com>: the core "command dispatch"
+system in qpsmtpd is taken from his colobus nntp server. The
+check_badmailfrom and check_mailrcptto plugins.
+
+John Peacock <jpeacock@cpan.org>: More changes, fixes and vast
+improvements for me to ever catch up on here.
+
+Matt Sergeant <matt@sergeant.org>: Clamav plugin. Patch for the dnsbl
+plugin to give us all the dns results. Resident SpamAssassin guru.
+PPerl. smtp-forward plugin. Documentation (yay!). Lots of fixes and
+tweaks. Apache module. Event based high performance experiment.
+
+Devin Carraway <qpsmtpd@devin.com>: Patch to not accept half mails if
+the connection gets dropped at the wrong moment. Support and enable
+taint checking. MAIL FROM host dns check configurable. HELO hook.
+initial earlytalker plugin.
+
+Andrew Pam <xanni@glasswings.com.au>: fixing the maximum message size
+(databytes) stuff.
+
+Marius Kjeldahl <marius@kjeldahl.net>, Zukka Zitting
+<jukka.zitting@iki.fi>: Patches for supporting $ENV{RELAYCLIENT}
+
+Robert Spier <robert@perl.org>: Klez filter.
+
+Rasjid Wilcox <rasjidw@openminddev.net>: Lots of patches as per the
+Changes file.
+
+Kee Hinckley <nazgul@somewhere.com>: Sent me the correct strftime
+format for the dates in the "Received" headers.
+
+Gergely Risko <risko@risko.hu>: Fixed timeout bug when the client sent
+DATA and then stopped before sending the next line.
+
+... and many many others per the Changes file and subversion logs and
+ mailing list archives. Thanks everyone!
582 Changes
@@ -0,0 +1,582 @@
+0.31 - 2005/11/16
+
+ STARTTLS support (see plugins/tls)
+
+ Added queue/exim-bsmtp plugin to spool accepted mail into an Exim
+ backend via BSMTP. (Devin Carraway)
+
+ New plugin inheritance system, see the bottom of README.plugins for
+ more information
+
+ qpsmtpd-forkserver: --listen-address may now be given more than once, to
+ request listening on multiple local addresses (Devin Carraway)
+ (also: no more signal problems making qpsmtpd-forkserver crash/loop
+ when forking).
+
+ qpsmtpd-forkserver: add an option for writing a PID file (pjh)
+
+ qpsmtpd-forkserver: set auxiliary groups (this is needed for the
+ postfix backend, which expects to have write permission to a fifo
+ which usually belongs to group postdrop). (pjh)
+
+ qpsmtpd-forkserver: if -d or --detach is given on the commandline,
+ forkserver will detach from the controlling terminal and daemonize
+ itself (Devin Carraway)
+
+ replace some fun smtp comments with boring ones.
+
+ example patterns for badrcptto plugin - Gordon Rowell
+
+ Extend require_resolvable_fromhost to include a configurable list of
+ "impossible" addresses to combat spammer forging. (Hanno Hecker)
+
+ Use qmail/control/smtpdgreeting if it exists, otherwise
+ show the original qpsmtpd greeting (with version information).
+
+ Apply slight variation on patch from Peter Holzer to allow specification of
+ an explicit $QPSMTPD_CONFIG variable to specify where the config lives,
+ overriding $QMAIL/control and /var/qmail/control if set. The usual
+ "last location with the file wins" rule still applies.
+
+ Refactor Qpsmtpd::Address
+
+ when disconncting with a temporary failure, return 421 rather than
+ 450 or 451. (Peter J. Holzer)
+
+ The unrecognized_command hook now uses DENY_DISCONNECT return
+ for disconnecting the user.
+
+ If the environment variable $QPSMTPD_CONFIG is set, qpsmtpd will look
+ for its config files in the directory given therein, in addition to (and
+ in preference to) other locations. (Peter J. Holzer)
+
+ Updated documentation
+
+ Various minor cleanups
+
+
+0.30 - 2005/07/05
+
+ Add plugable logging support include sample plugin which replicates
+ the existing core code. Add OK hook.
+
+ Add new logging plugin, logging/adaptive, which logs at different
+ levels depending on whether the message was accepted/rejected.
+
+ (See README.logging for information about the new logging system by
+ John Peacock)
+
+ plugins/auth/auth_ldap_bind - New plugin to authenticate against an
+ LDAP database. Thanks to Elliot Foster <elliotf@gratuitous.net>
+
+ new plugin: plugins/auth/auth_flat_file - flat file auth plugin
+
+ new plugin: plugins/auth/auth_cvm_unix_local - Only DENY if the
+ credentials were accepted but incorrect (bad password?). Interfaces
+ with Bruce Guenther's Credential Validation Module (CVM)
+
+ Revamp Qpsmtpd::Constants so it is possible to retrieve the text
+ representation from the numeric (for logging purposes).
+
+ new plugin: plugins/check_badrcptto_patterns - Match bad RCPTO
+ address with regex (Gordon Rowell)
+
+ new plugin: plugins/check_norelay - Carve out holes from larger
+ relay blocks (Also Gordon Rowell)
+
+ new plugin: plugins/virus/sophie - Uses SOPHOS Antivirus via Sophie
+ resident daemon.
+
+ Store mail in memory up to a certain threshold (default 10k).
+
+ Remove needless restriction on temp_file() to allow the spool
+ directory path to include dots (as in ../)
+
+ Fix off-by-one line numbers in warnings from plugins (thanks to
+ Brian Grossman).
+
+ Don't check the HELO host for rfc-ignorant compliance
+
+ body_write patches from Brian Grossman
+
+ Fix for corruption problem under Apache
+
+ Update Apache::Qpsmtpd to work with the latest Apache/mod_perl 2.0
+ API. Fix various bucket issues.
+
+ Replace $ENV{RELAYCLIENT} with $connection->relay_client in last plugin.
+
+ Fix typo in qpsmtpd-forkserver commandline help
+
+0.29 - 2005/03/03
+
+ Store entire incoming message in spool file (so that scanners can read
+ the complete message) and ignore old headers before adding lines and
+ queuing for delivery.
+
+ New anti-virus scanners: hbedv (Hanno Hecker), bitdefender, and clamdscan
+ (John Peacock). Update clamav plugin to directly scan the spool file.
+
+ New temp_file() and temp_dir() methods; when used by plugins, they create
+ a filename or directory which will last only as long as the current
+ transaction. Also created a spool_dir() method which checks/creates the
+ spool_dir when the application starts up. All three methods are also
+ available in the base class where the temp_* objects are not automatically
+ limited to the transaction's lifetime. (John Peacock)
+
+ Added Gavin Carr's greylisting plugin
+
+ Renamed config/ to config.sample/
+
+ Qpsmtpd::Auth - document $mechanism option, improve fallback to generic
+ hooks, document that auth-login works now, stash auth user and method for
+ later use by Qpsmtpd::SMTP to generate authentication header.
+ (Michael Toren)
+
+ Qpsmtpd::SMTP - "MAIL FROM: <#@[]>" now works like qmail (null sender),
+ add LOGIN to default auth mechanisms, display auth user and method in
+ Received: line instead of X-Qpsmtpd-Auth header.
+ (Michael Toren)
+
+ check_badmailfromto - NEW PLUGIN - like check_badmailfrom except matches
+ both FROM: and TO:, and effectively makes it seem like the recipient
+ no longer exists for that sender (great for harassment cases).
+ (John Peacock)
+
+ check_earlytalker and require_resolvable_fromhost - short circuit test if
+ whitelistclient is set. (Michael Toren)
+
+ check_badmailfrom - Do not say why a given message is denied.
+ (Michael Toren)
+
+ dns_whitelist_soft - NEW PLUGIN - dns-based whitelist override for
+ other qpsmtpd plugins. Add suuport for whitelisthost to dnsbl.
+ (John Peacock)
+
+ auth/auth_vpopmail_sql - Support CRAM-MD5 (requires clear_passwd)
+ (John Peacock)
+
+ plugins/queue/qmail-queue - Added a timestamp and the qmail-queue qp
+ identifier to the "Queued!" message, for compatibility with qmail-smtpd
+ (Michael Toren)
+
+ Support qmail-smtpd's timeoutsmtpd config file
+
+ Many improvements to the forking server (qpsmtpd-forkserver)
+
+ Plugin testing framework (Matt)
+
+ Added Apache::Qpsmtpd (Apache/mod_perl 2.0 connection handler)
+
+ Allow for multiple instances of a single plugin by using plugin:0
+ notation (Robert)
+
+ Fix CDB support so the server can work without it
+
+ VRFY plugin support (Robert Spier)
+
+ Added Makefile.PL etc to make it easier to build a package (Matt).
+
+ Added Apache::Qpsmtpd to the distro.
+
+ Make the distro follow the CPAN module style (Makefile.PL, MANIFEST, etc)
+
+ Make the rhsbl plugin do DNS lookups in the background. (Mark Powell)
+
+ Fix warning in count_unrecognized_commands plugin (thanks to spaze
+ and Roger Walker)
+
+ Improve error messages from the Postfix module (Erik I. Bolsø,
+ <knan at mo.himolde.no>)
+
+ make the maildir plugin record who the message was to (with a bit of improvements
+ this could make a decent local delivery plugin)
+
+ Pass extra "stuff" to HELO/EHLO callbacks (to make it easier to
+ support SMTP extensions)
+
+ Renamed the *HARD return codes to DENY_DISCONNECT and
+ DENYSOFT_DISCONNECT (DENYSOFT_DISCONNECT is new)
+
+ Mail::Address does RFC822 addresses, we need SMTP addresses.
+ Replace Mail::Address with Peter J. Holzer's Qpsmtpd::Address module.
+
+ Don't keep adding ip addresses to the process status line ($0) when
+ running under PPerl.
+
+ Include the date and time the session started in the process status line.
+
+ Add "plugin/virus/uvscan" - McAfee commandline virus scanner
+
+ Inbound connections logged as soon as the remote host address is known
+ when running under tcpserver.
+
+ Add Qpsmtpd::Auth (authentication handlers! See plugins/auth/) (John Peacock)
+
+ Add a plugin hook for the DATA command
+
+ check_earlytalker -
+ + optionally react to an earlytalker by denying all MAIL-FROM commands
+ rather than issuing a 4xx/5xx greeting and disconnecting. (Mark
+ Powell)
+ + initial "awkward silence" period now configurable (Mark Powell)
+ + DENY/DENYSOFT now configurable
+
+ Move relay flag to connection object (John Peacock):
+ + add relay_client() method to Connection.pm
+ + Remove $transaction->relaying() completely (due to popular demand)
+
+ Split check_relay plugin into two plugins (John Peacock):
+ + check_relay now fires on connect and sets relay_client() flag
+ + rcpt_ok runs last of rcpt plugins and performs final OK/DENY
+ + change default config/plugins to reflect new order
+
+0.28 - 2004/06/05
+
+ Don't keep adding ip addresses to the process status line ($0) when running under PPerl.
+
+ Include the date and time the session started in the process status line.
+
+ Added a "queue/maildir" plugin for writing incoming mails to a maildir.
+
+ Create temp files with permissions 0600 (thanks to Robert James Kaes again)
+
+ Fix warning in check_badrcptto plugin (Thanks to Robert James Kaes)
+
+ Proper "Log levels" with a configuration option
+
+ $Include feature in config/plugins
+
+
+0.27.1 - 2004/03/11
+
+ SpamAssassin plugin Outlook compatibility fix (Thanks to Gergely Risko)
+
+
+0.27 - 2004/03/10
+
+ Support for unix sockets in the spamassassin plugin (requires SA
+ 2.60 or higher). Thanks to John Peacock!
+
+ Modified the dnsbl plugin to better support both A and TXT records and
+ support all of the RBLSMTPD functionality. (Thanks to Mark Powell)
+
+ reject bare carriage-returns in addition to the bare line-feeds
+ (based on a patch from Robert James Kaes, thanks!)
+
+ Bugfix to the count_unrecognized_commands plugin so it works
+ under PPerl (it wasn't resetting the count properly).
+
+ reset_transaction is called after disconnect plugins are called so
+ the Transaction objects DESTROY method is called. (Thanks to Robert
+ James Kaes <rjkaes@flarenet.com>)
+
+ Made the SpamAssassin plugin work with SA 2.6+ (thanks to numerous
+ contributors, thanks everyone!). Note that for now it's not
+ including the Spam: headers with the score explained. For that use
+ the spamassassin_spamc plugin from http://projects.bluefeet.net/
+ (for now).
+
+ Added Postfix queue plugin thanks to Peter J Holzer!
+
+ Took out the last "exit" call from the SMTP object; the "transport"
+ module ("TcpServer", "SelectServer") needs to do the right thing in
+ it's disconnect method.
+
+ Update the SPF plugin (Philip Gladstone, philip@gladstonefamily.net):
+ * Integrated with Mail::SPF::Query 1.991
+ * Don't do SPF processing when you are acting as a relay system
+ * Remove the MX changes as they are now inside Mail::SPF::Query
+
+ Take out Data::Dumper to save a few bytes of memory
+
+ Say Received: ... via ESMTP instead of via SMTP when the client
+ speaks ESMTP. (Hoping this can be a useful SpamAssassin rule).
+
+ Take out the X-SMTPD header.
+
+ Add pod documentation and sanity checking of the config to
+ check_badmailfrom
+
+ Use $ENV{QMAIL} to override /var/qmail for where to find the
+ control/ directory.
+
+ Enable "check_earlytalker" in the default plugins config
+
+ Added a milter plugin to allow use of sendmail milters
+
+ Don't store the Qpsmtpd object in the Plugin object any more (this
+ caused a circular reference)
+
+ Added a new qpsmtpd-server - a select() based server for qpsmtpd
+
+ Allow a config/relayclients and config/morerelayclients files to
+ define who can relay (useful with the select() server)
+
+ Fixed qpsmtpd unfolding all header lines
+
+ Speed up persistent qpsmtpd's by checking for plugin functions after
+ munging the name (the main breakage was with queue/qmail-queue)
+
+ Use dup2() instead of perl open("<&") style. POSIX seems to work better.
+
+ Added SPF, sender permitted from, plugin
+
+ More minor changes and probably a few big ones that we missed adding here :-)
+
+
+0.26 - 2003/06/11
+
+ Add queue/smtp-forward plugin (Matt Sergeant)
+
+ Add documentation to Qpsmtpd::Transaction (Matt Sergeant)
+
+ Fix bug in dnsbl that made it sometimes ignore "hits" (thanks to
+ James H. Thompson <jht@lava.net>)
+
+ Fix bug hiding the error message when an existing configuration file
+ isn't readable.
+
+ If a plugin running the ehlo hook add something to the ARRAY
+ reference $self->transaction->notes('capabilities') then it will be
+ added to the EHLO response.
+
+ Add command_counter method to the SMTP object. Plugins can use this
+ to catch (or not) consecutive commands. In particular useful with
+ the unrecognized_command hook.
+
+ Filter out all uncommon characters from the remote_host
+ setting. (thanks to Frank Denis / Jedi/Sector One for the hint).
+
+ Added a check for the spool_dir having mode 0700.
+
+ Don't break under taint mode on OpenBSD. (thanks to Frank Denis /
+ Jedi/Sector One)
+
+ Have the qmail-queue plugin add the message-id to the "Queued!"
+ message we send back to the client (to help those odd sendmail using
+ people debug their logs)
+
+ Set the process name to "qpsmtpd [1.2.3.4 : host.name.tld]"
+
+ Fixed timeout bug when the client sent DATA and then stopped before
+ sending the next line. (Gergely Risko <risko@risko.hu>)
+
+ unrecognized_command hook and a count_unrecognized_commands
+ plugin. (Rasjid Wilcox)
+
+ check_earlytalker plugin. Deny the connection if the client talks
+ before we show our SMTP banner. (From Devin Carraway)
+
+ Patch Qpsmtpd::SMTP to allow connect plugins to give DENY and
+ DENYSOFT return codes. Based on patch from Devin Carraway.
+
+ Support morercpthosts.cdb
+
+ config now takes an extra "type" parameter. If it's "map" then a
+ reference to a tied hash will be returned.
+
+
+0.25 - 2003/03/18
+
+ Use the proper RFC2822 date format in the Received headers. (Somehow
+ I had convinced myself that ISO8601 dates were okay). Thanks to
+ Kee Hinckley <nazgul@somewhere.com>.
+
+ Print the date in the local timezone instead of in -0000. (Not
+ entirely convinced this is a good idea)
+
+ Lots of changes from Rasjid Wilcox <rasjidw@openminddev.net>:
+
+ Fix error handling in queue/qmail-queue. (Rasjid)
+
+ Add option to queue/qmail-queue to specify an alternate qmail-queue
+ location. (Rasjid)
+
+ Add support for the QMAILQUEUE environment variable. (Rasjid)
+
+ PPerl compatibility (yay!) (Rasjid)
+
+ Allow mail to <abuse> and <postmaster> to go through. (Rasjid)
+
+ Add "deny" hook that gets called when another hook returns DENY or
+ DENYSOFT. (Rasjid)
+
+ Add list of required modules to the README. Thanks to Skaag Argonius
+ <skaag@skaag.net>.
+
+ Fix dnsbl plugin to give us all the results. (Patch from Matt
+ Sergeant <matt@sergeant.org>)
+
+ Disable identd lookups by passing -R to tcpserver. (Thanks to Matt)
+
+ add plugin hooks for HELO and EHLO (Devin Carraway
+ <qpsmtpd-list@devin.com>)
+
+ check_spamhelo plugin to deny mail from claimed senders from the
+ list specified in F<badhelo>. (For example aol.com or yahoo.com)
+ (Devin Carraway)
+
+
+0.20 - 2002/12/09
+
+ Fix the "too many dots in the beginning of the line" bug.
+
+ Add munge_subject_threshold and reject_threshold options to the
+ spamassassin plugin. Add documentation to the spamassassin plugin.
+
+ Add -p to mkdir in log/run (Rasjid Wilcox <rasjidw@openminddev.net>)
+
+ clamav plugin, thanks to Matt Sergeant, matt@sergeant.org.
+ Enabling this might require you to increase your "softlimit" in
+ the run file. http://www.clamav.org/
+
+ Make the spamassassin plugin not stop the next content plugins from
+ running.
+
+ Store hooks runtime config globally so they will work within the
+ transaction objects too.
+
+ content_log plugin - log the content of all mails for
+ debugging. Robert Spier <robert@perl.org>.
+
+ http_config plugin - get configuration via http
+
+ plugins can take arguments via their line in the "plugins" file
+
+ make the quit_fortune plugin check that the fortune program exists
+
+
+0.12 - 2002/10/17
+
+ Better error messages when a plugin fails
+
+ Remove some debug messages in the log
+
+ Fix NOOP command with perl 5.6.
+
+ Better installation instructions and error message when no plugin
+ allowed or denied relaying (thanks to Lars Rander
+ <lrNOSPAM@rander.dk>).
+
+ Use /usr/bin/perl instead of the non-standard /home/perl/bin/perl
+
+
+0.11 - 2002/10/09
+
+ Make a "queue" plugin hook and move the qmail-queue functionality
+ to plugins/queue/qmail-queue. This allows you to make qpsmtpd
+ delivery mail via smtp or lmtp or into a database or whatever you want.
+
+ Reorganize most of Qpsmtpd.pm into Qpsmtpd/SMTP.pm.
+
+ Add spool_dir option (thanks to Ross Mueller <ross@visual.com>)
+
+ Add plugin name to the "hooks" data structure, so we can log plugin
+ module had an error when we run a hook.
+
+ Make klez filter run for mails bigger than 220KB; they are sometimes
+ bigger than that.
+
+ Avoid "use of uninitialized variable" warning when the "MAIL" or the
+ "RCPT" command is executed without a parameter.
+
+ Compatibility with perl 5.5.3.
+
+ Fix "Could not print" error message in the TcpServer object. (Thanks
+ to Ross Mueller <ross@visual.com>)
+
+ dnsbl plugin queues lookups in the background upon connect but
+ doesn't block for the results until they are needed, greatly
+ speeding up connection times. Also fix a typo in the dnsbl plugin
+ so it'll actually work(!).
+
+ check_badmailfrom and check_badrcptto plugins (Jim Winstead
+ <jimw@trainedmonkey.com>)
+
+ Better RFC conformance. (Reset transactions after the DATA command and
+ when the MAIL command is being done)
+
+
+0.10 - 2002/09/08
+
+ New object oriented internals
+
+ Very flexible plugin
+
+ All functionality not core to SMTP moved to plugins
+
+ Can accept mails as large as your file system will allow (instead of
+ up to as much memory you would allow qpsmtpd to eat).
+
+2002/09/08
+ Add klez_filter plugin
+
+ Support more return codes for data_post
+
+ Document data_post
+
+ Add plugin name to the log entries when plugins use log()
+
+ Add plugin_name method to the default plugin object.
+
+ Improve error handling in the spamassassin plugin
+
+
+2002/08/06
+ Spool message bodies to a tmp file so we can support HUGE messages
+
+ API to read the message body (undocumented, subject to change)
+
+ data_post hook (undocumented)
+
+ SpamAssassin plugin (connects to spamd on localhost), see
+ plugins/spamassassin
+
+
+2002/07/15
+ DNS RBL and RHSBL support via plugins.
+
+ More hooks.
+
+2002/07/03
+ First (non functional) version of the new object oriented mail engine (0.10).
+
+
+Changes on the old v0.0x branch:
+
+2002/05/09
+ Klez filter (thanks to Robert Spier)
+
+2002/04/20
+ Bumped version number to 0.07
+
+ Support comments in configuration files (prefix the line with #)
+
+ Support RELAYCLIENT like qmail-smtpd (thanks to Marius Kjeldahl
+ <marius@kjeldahl.net> and Zukka Zitting <jukka.zitting@iki.fi>)
+
+ If the connection fails while in DATA we would just accept the
+ message. Ouch! Thanks to Devin Carraway <qpsmtpd@devin.com> for the
+ patch.
+
+
+2002/01/26
+ Allow [1.2.3.4] for the hostname when checking if the dns resolves
+
+
+2002/01/21
+ assorted fixes; getting dnsbl's to actually work
+
+ fixing the maximum message size (databytes) stuff (thanks for the
+ spot to Andrew Pam <xanni@glasswings.com.au>)
+
+ support and enable taint checking (thanks to Devin Carraway
+ <qpsmtpd@devin.com>)
+
+ Make the MAIL FROM host dns check configurable. (thanks to Devin
+ Carraway).
+
+ Add more documentation to the README file.
+
+
19 LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2001-2005 Ask Bjoern Hansen, Develooper LLC
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
100 MANIFEST
@@ -0,0 +1,100 @@
+Changes
+config.sample/badhelo
+config.sample/badrcptto_patterns
+config.sample/dnsbl_zones
+config.sample/invalid_resolvable_fromhost
+config.sample/IP
+config.sample/logging
+config.sample/loglevel
+config.sample/plugins
+config.sample/relayclients
+config.sample/require_resolvable_fromhost
+config.sample/rhsbl_zones
+config.sample/size_threshold
+CREDITS
+lib/Apache/Qpsmtpd.pm
+lib/Qpsmtpd.pm
+lib/Qpsmtpd/Address.pm
+lib/Qpsmtpd/Auth.pm
+lib/Qpsmtpd/Connection.pm
+lib/Qpsmtpd/Constants.pm
+lib/Qpsmtpd/Plugin.pm
+lib/Qpsmtpd/Postfix.pm
+lib/Qpsmtpd/SelectServer.pm
+lib/Qpsmtpd/SMTP.pm
+lib/Qpsmtpd/TcpServer.pm
+lib/Qpsmtpd/Transaction.pm
+lib/Qpsmtpd/Utils.pm
+LICENSE
+log/run
+Makefile.PL
+MANIFEST This list of files
+MANIFEST.SKIP
+META.yml Module meta-data (added by MakeMaker)
+plugins/auth/auth_cvm_unix_local
+plugins/auth/auth_flat_file
+plugins/auth/auth_ldap_bind
+plugins/auth/auth_vpopmail_sql
+plugins/auth/authdeny
+plugins/auth/authnull
+plugins/check_badmailfrom
+plugins/check_badmailfromto
+plugins/check_badrcptto
+plugins/check_badrcptto_patterns
+plugins/check_basicheaders
+plugins/check_earlytalker
+plugins/check_loop
+plugins/check_norelay
+plugins/check_relay
+plugins/check_spamhelo
+plugins/content_log
+plugins/count_unrecognized_commands
+plugins/dns_whitelist_soft
+plugins/dnsbl
+plugins/greylisting
+plugins/http_config
+plugins/ident/geoip
+plugins/ident/p0f
+plugins/logging/adaptive
+plugins/logging/devnull
+plugins/logging/warn
+plugins/milter
+plugins/queue/exim-bsmtp
+plugins/queue/maildir
+plugins/queue/postfix-queue
+plugins/queue/qmail-queue
+plugins/queue/smtp-forward
+plugins/quit_fortune
+plugins/rcpt_ok
+plugins/require_resolvable_fromhost
+plugins/rhsbl
+plugins/sender_permitted_from
+plugins/spamassassin
+plugins/tls
+plugins/virus/aveclient
+plugins/virus/bitdefender
+plugins/virus/check_for_hi_virus
+plugins/virus/clamav
+plugins/virus/clamdscan
+plugins/virus/hbedv
+plugins/virus/kavscanner
+plugins/virus/klez_filter
+plugins/virus/sophie
+plugins/virus/uvscan
+qpsmtpd
+qpsmtpd-forkserver
+qpsmtpd-server
+README
+README.logging
+README.plugins
+run
+STATUS
+t/addresses.t
+t/helo.t
+t/plugin_tests.t
+t/plugin_tests/check_badrcptto
+t/plugin_tests/dnsbl
+t/qpsmtpd-address.t
+t/tempstuff.t
+t/Test/Qpsmtpd.pm
+t/Test/Qpsmtpd/Plugin.pm
27 MANIFEST.SKIP
@@ -0,0 +1,27 @@
+CVS/.*
+\.cvsignore$
+\.bak$
+\.sw[a-z]$
+\.tar$
+\.tgz$
+\.tar\.gz$
+\.o$
+\.xsi$
+\.bs$
+output/.*
+\.#
+^mess/
+^sqlite/
+^output/
+^tmp/
+^blib/
+^blibdirs$
+^Makefile$
+^Makefile\.[a-z]+$
+^pm_to_blib$
+~$
+^MANIFEST\.bak
+^tv\.log$
+^MakeMaker-\d
+\#$
+\B\.svn\b
27 Makefile.PL
@@ -0,0 +1,27 @@
+#!/usr/bin/perl -w
+
+use strict;
+use ExtUtils::MakeMaker;
+
+WriteMakefile(
+ NAME => 'qpsmtpd',
+ VERSION_FROM => 'lib/Qpsmtpd.pm',
+ PREREQ_PM => {
+ 'Mail::Header' => 0,
+ 'MIME::Base64' => 0,
+ 'Net::DNS' => 0.39,
+ 'Data::Dumper' => 0,
+ 'File::Temp' => 0,
+ 'Time::HiRes' => 0,
+ },
+ ABSTRACT_FROM => 'README',
+ AUTHOR => 'Ask Bjorn Hansen <ask@develooper.com>',
+ EXE_FILES => [qw(qpsmtpd qpsmtpd-forkserver)],
+);
+
+sub MY::libscan {
+ my $path = $_[1];
+ return '' if $path =~ /\B\.svn\b/;
+ return $path;
+}
+
210 README
@@ -0,0 +1,210 @@
+#
+# this file is best read with `perldoc README`
+#
+
+=head1 NAME
+
+Qpsmtpd - qmail perl simple mail transfer protocol daemon
+
+web:
+ http://smtpd.develooper.com/
+
+mailinglist:
+ qpsmtpd-subscribe@perl.org
+
+
+=head1 DESCRIPTION
+
+What is Qpsmtpd?
+
+Qpsmtpd is an extensible smtp engine written in Perl. No, make that
+easily extensible! See plugins/quit_fortune for a very useful, er,
+cute example.
+
+
+=head2 What's new in this release?
+
+See the Changes file! :-)
+
+
+=head1 Installation
+
+=head2 Required Perl Modules
+
+The following Perl modules are required:
+ Net::DNS
+ MIME::Base64
+ Mail::Header (part of the MailTools distribution)
+
+If you use a version of Perl older than 5.8.0 you will also need
+ Data::Dumper
+ File::Temp
+ Time::HiRes
+
+The easiest way to install modules from CPAN is with the CPAN shell.
+Run it with
+
+ perl -MCPAN -e shell
+
+=head2 qpsmtpd installation
+
+Make a new user and a directory where you'll install qpsmtpd. I
+usually use "smtpd" for the user and /home/smtpd/qpsmtpd/ for the
+directory.
+
+Put the files there. If you install from Subversion you can just do
+run the following command in the /home/smtpd/ directory.
+
+ svn co http://svn.perl.org/qpsmtpd/trunk .
+
+Or if you want a specific release, use for example
+
+ svn co http://svn.perl.org/qpsmtpd/tags/0.30 .
+
+In the branch L<http://svn.perl.org/qpsmtpd/branches/high_perf/> we
+have an experimental event based version of qpsmtpd that can handle
+thousands of simultaneous connections with very little overhead.
+
+chmod o+t ~smtpd/qpsmtpd/ (or whatever directory you installed qpsmtpd
+in) to make supervise start the log process.
+
+Edit the file config/IP and put the ip address you want to use for
+qpsmtpd on the first line (or use 0 to bind to all interfaces).
+
+If you use the supervise tools, then you are practically done now!
+Just symlink /home/smtpd/qpsmtpd into your /services (or /var/services
+or /var/svscan or whatever) directory. Remember to shutdown
+qmail-smtpd if you are replacing it with qpsmtpd.
+
+If you don't use supervise, then you need to run the ./run script in
+some other way.
+
+The smtpd user needs write access to ~smtpd/qpsmtpd/tmp/ but should
+not need to write anywhere else. This directory can be configured
+with the "spool_dir" configuration.
+
+As per version 0.25 the distributed ./run script runs tcpserver with
+the -R flag to disable identd lookups. Remove the -R flag if that's
+not what you want.
+
+
+=head2 Configuration
+
+Configuration files can go into either /var/qmail/control or into the
+config subdirectory of the qpsmtpd installation. Configuration should
+be compatible with qmail-smtpd making qpsmtpd a drop-in replacement.
+
+If qmail is installed in a nonstandard location you should set the
+$QMAIL environment variable to that location in your "./run" file.
+
+If there is anything missing, then please send a patch (or just
+information about what's missing) to the mailinglist or to
+ask@develooper.com.
+
+
+=head1 Better Performance
+
+As of version 0.21 qpsmtpd supports "PPerl"
+http://search.cpan.org/search?dist=PPerl
+
+"PPerl turns ordinary perl scripts into long running daemons, making
+subsequent executions extremely fast. It forks several processes for
+each script, allowing many processes to call the script at once."
+
+Running under PPerl is easy - just change your "run" file to contain
+the following command:
+
+ pperl -Tw -- --prefork=$MAXCLIENTS --maxclients=$MAXCLIENTS \
+ --no-cleanup ./qpsmtpd 2>&1
+
+As an alternative to PPerl (some users find PPerl unstable) we recommend using
+the forkserver. This forks for every connection, but pre-loads all the plugins
+to reduce the overhead.
+
+=head1 Plugins
+
+The qpsmtpd core only implements the SMTP protocol. No useful
+function can be done by qpsmtpd without loading plugins.
+
+Plugins are loaded on startup where each of them register their
+interest in various "hooks" provided by the qpsmtpd core engine.
+
+At least one plugin MUST allow or deny the RCPT command to enable
+receiving mail. The "check_relay" plugin is the standard plugin for
+this. Other plugins provides extra functionality related to this; for
+example the require_resolvable_fromhost plugin described above.
+
+
+=head1 Configuration files
+
+All the files used by qmail-smtpd should be supported; so see the man
+page for qmail-smtpd. Extra files used by qpsmtpd includes:
+
+=over 4
+
+=item plugins
+
+List of plugins, one per line, to be loaded in the order they
+appear in the file. Plugins are in the plugins directory (or in
+a subdirectory of there).
+
+
+=item rhsbl_zones
+
+Right hand side blocking lists, one per line. For example:
+
+ dsn.rfc-ignorant.org does not accept bounces - http://www.rfc-ignorant.org/
+
+See http://www.rfc-ignorant.org/ for more examples.
+
+
+=item dnsbl_zones
+
+Normal ip based dns blocking lists ("RBLs"). For example:
+
+ relays.ordb.org
+ spamsources.fabel.dk
+
+
+=item require_resolvable_fromhost
+
+If this file contains anything but a 0 on the first line, envelope
+senders will be checked against DNS. If an A or a MX record can't be
+found the mail command will return a soft rejection (450).
+
+
+=item spool_dir
+
+If this file contains a directory, it will be the spool directory
+smtpd uses during the data transactions. If this file doesnt exist, it
+will default to use $ENV{HOME}/tmp/. This directory should be set with
+a mode of 700 and owned by the smtpd user.
+
+
+=item everything (?) that qmail-smtpd supports.
+
+In my test qpsmtpd installation I have a "config/me" file containing
+the hostname I use for testing qpsmtpd (so it doesn't introduce itself
+with the normal name of the server).
+
+=back
+
+
+
+=head1 Problems
+
+In case of problems always first check the logfile.
+
+As default it goes into log/main/current. Qpsmtpd can log a lot of
+debug information. You can get more or less by adjusting $TRACE_LEVEL
+in lib/Qpsmtpd.pm (sorry, no easy switch for that yet). Something
+between 1 and 3 should give you just a little bit. If you set it to
+10 or higher you will get lots of information in the logs.
+
+If the logfile doesn't give away the problem, then post to the
+mailinglist (subscription instructions above). If possibly then put
+the logfile on a webserver and include a reference to it in the mail.
+
+
+
+
74 README.logging
@@ -0,0 +1,74 @@
+#
+# read this with 'perldoc README.logging' ...
+#
+
+=head1 qpsmtpd logging system; developer documentation
+
+Qpsmtpd now (as of 0.30-dev) supports a plugable logging architecture, so
+that different logging plugins can be supported. See the example logging
+plugins in plugins/logging, specifically the L<plugins/logging/warn> and
+L<plugins/logging/adaptive> files for examples of how to write your own
+logging plugins.
+
+=head1 Internal support for pluggable logging
+
+Any code in the core can call C<$self->log()> and those log lines will be
+dispatched to each of the registered logging plugins. When C<log()> is
+called from a plugin, the plugin and hook names are automatically included
+in the parameters passed the logging hooks. All plugins which register for
+the logging hook should expect the following parameters to be passed:
+
+ $self, $transaction, $trace, $hook, $plugin, @log
+
+where those terms are:
+
+=over 4
+
+=item C<$self>
+
+The object which was used to call the log() method; this can be any object
+within the system, since the core code will automatically load logging
+plugins on behalf of any object.
+
+=item C<$transaction>
+
+This is the current SMTP transaction (defined as everything that happens
+between HELO/EHLO and QUIT/RSET). If you want to defer outputting certain
+log lines, you can store them in the transaction object, but you will need
+to bind the C<reset_transaction> hook in order to retrieve that information
+before it is discarded when the transaction is closed (see the
+L<logging/adaptive> plugin for an example of doing this).
+
+=item C<$trace>
+
+This is the log level (as shown in config.sample/loglevel) that the caller
+asserted when calling log(). If you want to output the textural
+representation (e.g. C<LOGERROR>) of this in your log messages, you can use
+the log_level() function exported by Qpsmtpd::Constants (which is
+automatically available to all plugins).
+
+=item C<$hook>
+
+This is the hook that is currently being executed. If log() is called by
+any core code (i.e. not as part of a hook), this term will be C<undef>.
+
+=item C<$plugin>
+
+This is the plugin name that executed the log(). Like C<$hook>, if part of
+the core code calls log(), this wil be C<undef>. See L<logging/warn> for a
+way to prevent logging your own plugin's log entries from within that
+plugin (the system will not infinitely recurse in any case).
+
+=item C<@log>
+
+The remaining arguments are as passed by the caller, which may be a single
+term or may be a list of values. It is usually sufficient to call
+C<join(" ",@log)> to deal with these terms, but it is possible that some
+plugin might pass additional arguments with signficance.
+
+=back
+
+Note: if you register a handler for certain hooks, e.g. C<deny>, there may
+be additional terms passed between C<$self> and C<$transaction>. See
+L<logging/adaptive> for and example.
+
351 README.plugins
@@ -0,0 +1,351 @@
+#
+# read this with 'perldoc README.plugins' ...
+#
+
+=head1 qpsmtpd plugin system; developer documentation
+
+See the examples in plugins/ and ask questions on the qpsmtpd
+mailinglist; subscribe by sending mail to qpsmtpd-subscribe@perl.org.
+
+=head1 General return codes
+
+Each plugin must return an allowed constant for the hook and (usually)
+optionally a "message".
+
+Generally all plugins for a hook are processed until one returns
+something other than "DECLINED".
+
+Plugins are run in the order they are listed in the "plugins"
+configuration.
+
+=over 4
+
+=item OK
+
+Action allowed
+
+=item DENY
+
+Action denied
+
+=item DENYSOFT
+
+Action denied; return a temporary rejection code (say 450 instead of 550).
+
+=item DENY_DISCONNECT
+
+Action denied; return a permanent rejection code and disconnect the client.
+Use this for "rude" clients. Note that you're not supposed to do this
+according to the SMTP specs, but bad clients don't listen sometimes.
+
+=item DENYSOFT_DISCONNECT
+
+Action denied; return a temporary rejection code and disconnect the client.
+
+=item DECLINED
+
+Plugin declined work; proceed as usual. This return code is _always_
+_allowed_ unless noted otherwise.
+
+=item DONE
+
+Finishing processing of the request. Usually used when the plugin
+sent the response to the client.
+
+=back
+
+See more detailed description for each hook below.
+
+=head1 Hooks
+
+=head2 pre-connection
+
+Called by a controlling process (e.g. forkserver or Apache::Qpsmtpd) after
+accepting the remote server, but before beginning a new instance. Useful for
+load-management and rereading large config files at some frequency less than
+once per session. The hook doesn't have a predefined additional input value,
+but one can be passed as a hash of name/value pairs.
+
+=head2 post-connection
+
+Like pre-connection only it can be called after an instance has been
+completely finished (e.g. after the child process has ended in forkserver).
+The hook doesn't have a predefined additional input value, but one can be
+passed as a hash of name/value pairs.
+
+
+=head2 connect
+
+Allowed return codes:
+
+ OK - Stop processing plugins, give the default response
+ DECLINED - Process the next plugin
+ DONE - Stop processing plugins and don't give the default response
+ DENY - Return hard failure code and disconnect
+ DENYSOFT - Return soft failure code and disconnect
+
+Note: DENY_DISCONNECT and DENYSOFT_DISCONNECT are not supported here due to
+them having no meaning beyond what DENY and DENYSOFT already do.
+
+
+=head2 helo
+
+Called on "helo" from the client.
+
+ DENY - Return a 550 code
+ DENYSOFT - Return a 450 code
+ DENY_DISCONNECT & DENYSOFT_DISCONNECT - as above but with disconnect
+ DONE - Qpsmtpd won't do anything; the plugin sent the message
+ DECLINED - Qpsmtpd will send the standard HELO message
+
+
+=head2 ehlo
+
+Called on "ehlo" from the client.
+
+ DENY - Return a 550 code
+ DENYSOFT - Return a 450 code
+ DENY_DISCONNECT & DENYSOFT_DISCONNECT - as above but with disconnect
+ DONE - Qpsmtpd won't do anything; the plugin sent the message
+ DECLINED - Qpsmtpd will send the standard HELO message
+
+
+=head2 mail
+
+Called right after the envelope sender address is passed. The plugin
+gets passed a Mail::Address object. Default is to allow the
+recipient.
+
+Allowed return codes
+
+ OK - sender allowed
+ DENY - Return a hard failure code
+ DENYSOFT - Return a soft failure code
+ DENY_DISCONNECT & DENYSOFT_DISCONNECT - as above but with disconnect
+ DONE - skip further processing
+
+
+=head2 rcpt
+
+Hook for the "rcpt" command. Defaults to deny the mail with a soft
+error code.
+
+Allowed return codes
+
+ OK - recipient allowed
+ DENY - Return a hard failure code
+ DENYSOFT - Return a soft failure code
+ DENY_DISCONNECT & DENYSOFT_DISCONNECT - as above but with disconnect
+ DONE - skip further processing
+
+
+=head2 data
+
+Hook for the "data" command. Defaults to '354, "go ahead"'.
+
+ DENY - Return a hard failure code
+ DENYSOFT - Return a soft failure code
+ DENY_DISCONNECT & DENYSOFT_DISCONNECT - as above but with disconnect
+ DONE - Plugin took care of receiving data and calling the queue (not
+ recommended)
+
+
+=head2 data_post
+
+Hook after receiving all data; just before the message is queued.
+
+ DENY - Return a hard failure code
+ DENYSOFT - Return a soft failure code
+ DENY_DISCONNECT & DENYSOFT_DISCONNECT - as above but with disconnect
+ DONE - skip further processing (message will not be queued)
+
+All other codes and the message will be queued normally
+
+
+=head2 queue
+
+Called on completion of the DATA command, after the data_post hook.
+
+ DONE - skip further processing (plugin gave response code)
+ OK - Return success message
+ DENY - Return hard failure code
+ DENYSOFT - Return soft failure code
+
+Any other code will return a soft failure code.
+
+
+=head2 quit
+
+Called on the "quit" command.
+
+Allowed return codes:
+
+ DONE
+
+Works like the "connect" hook.
+
+
+=head2 unrecognized_command
+
+Called when we get a command that isn't recognized.
+
+ DENY_DISCONNECT - Return 521 and disconnect the client
+ DENY - Return 500
+ DONE - Qpsmtpd won't do anything; the plugin responded
+ Anything else - Return '500 Unrecognized command'
+
+=head2 disconnect
+
+Called just before we shutdown a connection.
+
+The return code is ignored. If a plugin returns anything but DECLINED
+the following plugins will not be run (like with all other hooks).
+
+=head2 deny
+
+Called when another hook returns DENY or DENYSOFT. First parameter is
+the previous hook return code; the second parameter the message the
+hook returned.
+
+Returning DONE or OK will stop the next deny hook from being run.
+DECLINED will make qpsmtpd run the remaining configured deny hooks.
+
+=head2 vrfy
+
+Hook for the "VRFY" command. Defaults to returning a message telling
+the user to just try sending the message.
+
+Allowed return codes:
+
+ OK - Recipient Exists
+ DENY - Return a hard failure code
+ DONE - Return nothing and move on
+ Anything Else - Return a 252
+
+=head1 Return Values and Notes
+
+Insert stuff here about how:
+
+ - if we're in a transaction, the results of a callback are stored
+in
+ $self->transaction->notes( $code->{name})->{"hook_$hook"}->{return}
+
+ - if we're in a connection, store things in the connection notes instead.
+
+
+=head1 Include Files
+
+(put more about how the $Include stuff works here)
+
+With the $Include stuff you order using the filename of the plugin.d
+file. So if you have a plugin called xyz but want it to come early on,
+you call it's config file 00_xyz, but that file still refers to the
+plugin called xyz.
+
+=head1 Temporary Files
+
+The temporary file and directory functions can be used for plugin specific
+workfiles and will automatically be deleted at the end of the current
+transaction.
+
+=over 4
+
+=item temp_file()
+
+Returns a unique name of a file located in the default spool directory, but
+does not open that file (i.e. it is the name not a file handle).
+
+=item temp_dir()
+
+Returns the name of a unique directory located in the default spool
+directory, after creating the directory with 0700 rights. If you need a
+directory with different rights (say for an antivirus daemon), you will
+need to use the base function $self->qp->temp_dir() which takes a single
+parameter for the permissions requested (see L<mkdir> for details). A
+directory created like this will B<not> be deleted when the transaction is
+ended.
+
+=item spool_dir()
+
+Returns the configured system-wide spool directory.
+
+=back
+
+=head1 Naming Conventions
+
+Plugins should be written using standard named hook subroutines. This
+allows them to be overloaded and extended easily.
+
+Because some of our callback names have characters invalid in
+subroutine names, they must be translated. The current translation
+routine is: C< s/\W/_/g; >
+
+=head2 Naming Map
+
+ hook method
+ ---------- ------------
+ config hook_config
+ queue hook_queue
+ data hook_data
+ data_post hook_data_post
+ quit hook_quit
+ rcpt hook_rcpt
+ mail hook_mail
+ ehlo hook_ehlo
+ helo hook_helo
+ auth hook_auth
+ auth-plain hook_auth_plain
+ auth-login hook_auth_login
+ auth-cram-md5 hook_auth_cram_md5
+ connect hook_connect
+ reset_transaction hook_reset_transaction
+ unrecognized_command hook_unrecognized_command
+
+=head1 Register
+
+If you choose not to use the default naming convention, you need to
+register the hooks in your plugin. You do this with the C< register >
+method call on the plugin object.
+
+ sub register {
+ my ($self, $qp) = @_;
+
+ $self->register_hook('mail', 'mail_handler');
+ $self->register_hook('rcpt', 'rcpt_handler');
+ $self->register_hook('disconnect', 'disconnect_handler');
+ }
+
+ sub mail_handler { ... }
+ sub rcpt_handler { ... }
+ sub disconnect_handler { ... }
+
+A single plugin can register as many hooks as it wants, and can
+register a hook multiple times.
+
+The C< register > method is also often used for initialization and
+reading configuration.
+
+=head1 Init
+
+The 'init' method is the first method called after a plugin is
+loaded. It's mostly for inheritance, below.
+
+=head1 Inheritance
+
+Instead of modifying @ISA directly in your plugin, use the
+C< isa_plugin > method from the init subroutine.
+
+ # rcpt_ok_child
+ sub init {
+ my ($self, $qp) = @_;
+ $self->isa_plugin('rcpt_ok');
+ }
+
+ sub hook_rcpt {
+ my ($self, $transaction, $recipient) = @_;
+ # do something special here...
+ $self->SUPER::hook_rcpt( $transaction, $recipient );
+ }
+
+
+
110 STATUS
@@ -0,0 +1,110 @@
+
+New Name Suggestions
+====================
+ignite
+flare(mta)
+quench
+pez (or pezmail)
+
+
+Near term roadmap
+=================
+
+0.32:
+ - Bugfixes
+ - add module requirements to the META.yml file
+
+0.40:
+ - Add user configuration plugin
+ - Add plugin API for checking if a local email address is valid
+ - use keyword "ESMTPA" in Received header in case of authentication to comply with RFC 3848.
+
+
+0.50:
+ Include the popular check_delivery[1] functionality via the 0.30 API
+ [1] until then get it from
+ http://www.openminddev.net/files/qpsmtpd/plugins/check_delivery/
+
+ Add API to reject individual recipients after the RCPT has been
+ accepted and generate individual bounce messages.
+
+0.51: bugfixes
+
+0.60: merge with the highperf branch?
+
+1.0bN: bugfixes (repeat until we run out of bugs to fix)
+1.0.0: it just might happen!
+1.1.0: new development
+
+
+Issues
+======
+
+Before next release
+-------------------
+
+update clamav plugin config to support the latest version properly
+
+
+Some day...
+-----------
+
+Understand "extension parameters" to the MAIL FROM and RCPT TO
+parameters (and make the plugin hooks able to get at them).
+
+plugins/queue/qmail-queue is still calling exit inappropriately
+(should call disconnect or some such)
+
+add whitelist support to the dnsbl plugin (and maybe to the rhsbl
+plugin too). Preferably both supporting DNS based whitelists and
+filebased (CDB) ones.
+
+
+plugin support;
+
+ allow plugins to return multiple response lines (does it have to
+ join them to one for SMTP?)
+
+ support plugins for the rest of the commands.
+
+ specify a priority in register_hook. ("LAST", "FIRST", "MIDDLE", or
+ maybe a number)
+
+ plugin access to the data line by line during the DATA phase
+ (instead of just after)
+
+ if qmail-queue can't be loaded we still return 250 ?!
+
+Make a system for configuring the plugins per user/domain/...
+
+ support databytes per user / domain
+
+plugin to reject mails from <> if it has multiple recipients.
+
+localiphost - support foo@[a.b.c.d] addresses
+
+support smtpgreeting (?)
+
+
+
+TRACE in Constants.pm is not actually being used. Should it be?
+
+Move dispatch() etc from SMTP.pm to Qpsmtpd.pm to allow other similar
+protocols to use the qpsmtpd framework.
+
+
+
+Future Ideas
+============
+
+Methods to create a bounce message easily; partly so we can accept a
+mail for one user but bounce it right away for another RCPT'er.
+
+The data_post hook should be able to put in the notes what addresses
+should go through, bounce and get rejected respectively, and qpsmtpd
+should just do the right thing. See also
+http://nntp.perl.org/group/perl.qpsmtpd/170
+
+David Carraway has some thoughts for "user filters"
+http://nntp.perl.org/group/perl.qpsmtpd/2
+
4 config.sample/IP
@@ -0,0 +1,4 @@
+0
+# the first line of this file is being used as the IP
+# address tcpserver will bind to. Use 0 to bind to all
+# interfaces.
4 config.sample/badhelo
@@ -0,0 +1,4 @@
+# these domains never uses their domain when greeting us, so reject transactions
+aol.com
+yahoo.com
+
5 config.sample/badrcptto_patterns
@@ -0,0 +1,5 @@
+# Format is pattern\s+Response
+# Don't forget to anchor the pattern if required
+! Sorry, bang paths not accepted here
+@.*@ Sorry, multiple at signs not accepted here
+% Sorry, percent hack not accepted here
4 config.sample/dnsbl_zones
@@ -0,0 +1,4 @@
+rbl.mail-abuse.org
+spamsources.fabel.dk
+relays.ordb.org
+sbl.spamhaus.org
6 config.sample/invalid_resolvable_fromhost
@@ -0,0 +1,6 @@
+# include full network block including mask
+127.0.0.0/8
+0.0.0.0/8
+224.0.0.0/4
+169.254.0.0/16
+10.0.0.0/8
1 config.sample/logging
@@ -0,0 +1 @@
+logging/warn 9
10 config.sample/loglevel
@@ -0,0 +1,10 @@
+# Log levels
+# LOGDEBUG = 7
+# LOGINFO = 6
+# LOGNOTICE = 5
+# LOGWARN = 4
+# LOGERROR = 3
+# LOGCRIT = 2
+# LOGALERT = 1
+# LOGEMERG = 0
+4
54 config.sample/plugins
@@ -0,0 +1,54 @@
+#
+# Example configuration file for plugins
+#
+
+# enable this to get configuration via http; see perldoc
+# plugins/http_config for details.
+# http_config http://localhost/~smtpd/config/ http://www.example.com/smtp.pl?config=
+
+quit_fortune
+
+check_earlytalker
+count_unrecognized_commands 4
+check_relay
+
+require_resolvable_fromhost
+
+rhsbl
+dnsbl
+check_badmailfrom
+check_badrcptto
+check_spamhelo
+
+# sender_permitted_from
+
+# this plugin needs to run after all other "rcpt" plugins
+rcpt_ok
+
+# content filters
+virus/klez_filter
+
+
+# You can run the spamassassin plugin with options. See perldoc
+# plugins/spamassassin for details.
+#
+spamassassin
+
+# rejects mails with a SA score higher than 20 and munges the subject
+# of the score is higher than 10.
+#
+# spamassassin reject_threshold 20 munge_subject_threshold 10
+
+
+# run the clamav virus checking plugin
+# virus/clamav
+
+# queue the mail with qmail-queue
+queue/qmail-queue
+
+
+# If you need to run the same plugin multiple times, you can do
+# something like the following
+# check_relay
+# check_relay:0 somearg
+# check_relay:1 someotherarg
4 config.sample/relayclients
@@ -0,0 +1,4 @@
+# Format is IP, or IP part with trailing dot
+# e.g. "127.0.0.1", or "192.168."
+127.0.0.1
+192.168.
3 config.sample/require_resolvable_fromhost
@@ -0,0 +1,3 @@
+1
+
+# use 0 to disable; anything else to enable.
5 config.sample/rhsbl_zones
@@ -0,0 +1,5 @@
+dsn.rfc-ignorant.org does not accept bounces. This violates RFC 821/2505/2821 http://www.rfc-ignorant.org/
+
+
+
+
3 config.sample/size_threshold
@@ -0,0 +1,3 @@
+# Messages below the size below will be stored in memory and not spooled.
+# Without this file, the default is 0 bytes, i.e. all messages will be spooled.
+10000
220 lib/Apache/Qpsmtpd.pm
@@ -0,0 +1,220 @@
+# $Id$
+
+package Apache::Qpsmtpd;
+
+use 5.006001;
+use strict;
+use warnings FATAL => 'all';
+
+use Apache2::ServerUtil ();
+use Apache2::Connection ();
+use Apache2::Const -compile => qw(OK MODE_GETLINE);
+use APR::Const -compile => qw(SO_NONBLOCK EOF SUCCESS);
+use APR::Error ();
+use APR::Brigade ();
+use APR::Bucket ();
+use APR::Socket ();
+use Apache2::Filter ();
+use ModPerl::Util ();
+
+our $VERSION = '0.02';
+
+sub handler {
+ my Apache2::Connection $c = shift;
+ $c->client_socket->opt_set(APR::Const::SO_NONBLOCK => 0);
+
+ my $qpsmtpd = Qpsmtpd::Apache->new();
+ $qpsmtpd->start_connection(
+ ip => $c->remote_ip,
+ host => $c->remote_host,
+ info => undef,
+ dir => $c->base_server->dir_config('QpsmtpdDir'),
+ conn => $c,
+ );
+
+ $qpsmtpd->run($c);
+
+ return Apache2::Const::OK;
+}
+
+package Qpsmtpd::Apache;
+
+use Qpsmtpd::Constants;
+use base qw(Qpsmtpd::SMTP);
+
+sub start_connection {
+ my $self = shift;
+ my %opts = @_;
+
+ $self->{qpdir} = $opts{dir};
+ $self->{conn} = $opts{conn};
+ $self->{conn}->client_socket->timeout_set($self->config('timeout') * 1_000_000);
+ $self->{bb_in} = APR::Brigade->new($self->{conn}->pool, $self->{conn}->bucket_alloc);
+ $self->{bb_out} = APR::Brigade->new($self->{conn}->pool, $self->{conn}->bucket_alloc);
+
+ my $remote_host = $opts{host} || ( $opts{ip} ? "[$opts{ip}]" : "[noip!]");
+ my $remote_info = $opts{info} ? "$opts{info}\@$remote_host" : $remote_host;
+ my $remote_ip = $opts{ip};
+
+ $self->log(LOGNOTICE, "Connection from $remote_info [$remote_ip]");
+
+ $self->SUPER::connection->start(
+ remote_info => $remote_info,
+ remote_ip => $remote_ip,
+ remote_host => $remote_host,
+ @_);
+}
+
+sub config {
+ my $self = shift;
+ my ($param, $type) = @_;
+ if (!$type) {
+ my $opt = $self->{conn}->base_server->dir_config("qpsmtpd.$param");
+ return $opt if defined($opt);
+ }
+ return $self->SUPER::config(@_);
+}
+
+sub run {
+ my $self = shift;
+
+ # should be somewhere in Qpsmtpd.pm and not here...
+ $self->load_plugins;
+
+ my $rc = $self->start_conversation;
+ return if $rc != DONE;
+
+ # this should really be the loop and read_input should just
+ # get one line; I think
+ $self->read_input();
+}
+
+sub config_dir {
+ my ($self, $config) = @_;
+ -e "$_/$config" and return $_
+ for "$self->{qpdir}/config";
+ return "/var/qmail/control";
+}
+
+
+sub plugin_dir {
+ my $self = shift;
+ return "$self->{qpdir}/plugins";
+}
+
+sub getline {
+ my $self = shift;
+ my $c = $self->{conn} || die "Cannot getline without a conn";
+
+ return if $c->aborted;
+
+ my $bb = $self->{bb_in};
+
+ while (1) {
+ my $rc = $c->input_filters->get_brigade($bb, Apache2::Const::MODE_GETLINE);
+ return if $rc == APR::Const::EOF;
+ die APR::Error::strerror($rc) unless $rc == APR::Const::SUCCESS;
+
+ next unless $bb->flatten(my $data);
+
+ $bb->cleanup;
+ return $data;
+ }
+
+ return '';
+}
+
+sub read_input {
+ my $self = shift;
+ my $c = $self->{conn};
+
+ while (defined(my $data = $self->getline)) {
+ $data =~ s/\r?\n$//s; # advanced chomp
+ $self->log(LOGDEBUG, "dispatching $data");
+ defined $self->dispatch(split / +/, $data)
+ or $self->respond(502, "command unrecognized: '$data'");
+ last if $self->{_quitting};
+ }
+}
+
+sub respond {
+ my ($self, $code, @messages) = @_;
+ my $c = $self->{conn};
+ while (my $msg = shift @messages) {
+ my $bb = $self->{bb_out};
+ my $line = $code . (@messages?"-":" ").$msg;
+ $self->log(LOGDEBUG, $line);
+ my $bucket = APR::Bucket->new(($c->bucket_alloc), "$line\r\n");
+ $bb->insert_tail($bucket);
+ $c->output_filters->fflush($bb);
+ # $bucket->remove;
+ $bb->cleanup;
+ }
+ return 1;
+}
+
+sub disconnect {
+ my $self = shift;
+ $self->SUPER::disconnect(@_);
+ $self->{_quitting} = 1;
+ $self->{conn}->client_socket->close();
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Apache::Qpsmtpd - a mod_perl-2 connection handler for qpsmtpd
+
+=head1 SYNOPSIS
+
+ Listen 0.0.0.0:25
+
+ LoadModule perl_module modules/mod_perl.so
+
+ <Perl>
+ use lib qw( /path/to/qpsmtpd/lib );
+ use Apache::Qpsmtpd;
+ </Perl>
+
+ <VirtualHost _default_:25>
+ PerlSetVar QpsmtpdDir /path/to/qpsmtpd
+ PerlModule Apache::Qpsmtpd
+ PerlProcessConnectionHandler Apache::Qpsmtpd
+ PerlSetVar qpsmtpd.loglevel 4
+ </VirtualHost>
+
+=head1 DESCRIPTION
+
+This module implements a mod_perl/apache 2.0 connection handler
+that turns Apache into an SMTP server using Qpsmtpd.
+
+It also allows you to set single-valued config options (such
+as I<loglevel>, as seen above) using C<PerlSetVar> in F<httpd.conf>.
+
+This module should be considered beta software as it is not yet
+widely tested. However it is currently the fastest way to run
+Qpsmtpd, so if performance is important to you then consider this
+module.
+
+=head1 BUGS
+
+Currently the F<check_early_talker> plugin will not work because it
+relies on being able to do C<select()> on F<STDIN> which does not
+work here. It should be possible with the next release of mod_perl
+to do a C<poll()> on the socket though, so we can hopefully get
+that working in the future.
+
+Other operations that perform directly on the STDIN/STDOUT filehandles
+will not work.
+
+=head1 AUTHOR
+
+Matt Sergeant, <matt@sergeant.org>
+
+Some credit goes to <mock@obscurity.org> for Apache::SMTP which gave
+me the inspiration to do this.
+
+=cut
456 lib/Qpsmtpd.pm
@@ -0,0 +1,456 @@
+package Qpsmtpd;
+use strict;
+use vars qw($VERSION $Logger $TraceLevel $Spool_dir $Size_threshold);
+
+use Sys::Hostname;
+use Qpsmtpd::Constants;
+
+$VERSION = "0.31";
+
+sub version { $VERSION };
+
+sub TRACE_LEVEL { $TraceLevel }; # leave for plugin compatibility
+
+sub load_logging {
+ # need to do this differently that other plugins so as to
+ # not trigger logging activity
+ my $self = shift;
+ return if $self->{hooks}->{"logging"};
+ my $configdir = $self->config_dir("logging");
+ my $configfile = "$configdir/logging";
+ my @loggers = $self->_config_from_file($configfile,'logging');
+ my $dir = $self->plugin_dir;
+
+ $self->_load_plugins($dir, @loggers);
+
+ foreach my $logger (@loggers) {
+ $self->log(LOGINFO, "Loaded $logger");
+ }
+
+ return @loggers;
+}
+
+sub trace_level {
+ my $self = shift;
+ return $TraceLevel if $TraceLevel;
+
+ my $configdir = $self->config_dir("loglevel");
+ my $configfile = "$configdir/loglevel";
+ $TraceLevel = $self->_config_from_file($configfile,'loglevel');
+
+ unless (defined($TraceLevel) and $TraceLevel =~ /^\d+$/) {
+ $TraceLevel = LOGWARN; # Default if no loglevel file found.
+ }
+
+ return $TraceLevel;
+}
+
+sub init_logger { # needed for compatibility purposes
+ shift->trace_level();
+}
+
+sub log {
+ my ($self, $trace, @log) = @_;
+ $self->varlog($trace,join(" ",@log));
+}
+
+sub varlog {
+ my ($self, $trace) = (shift,shift);
+ my ($hook, $plugin, @log);
+ if ( $#_ == 0 ) { # log itself
+ (@log) = @_;
+ }
+ elsif ( $#_ == 1 ) { # plus the hook
+ ($hook, @log) = @_;
+ }
+ else { # called from plugin
+ ($hook, $plugin, @log) = @_;
+ }
+
+ $self->load_logging; # in case we already don't have this loaded yet
+
+ my ($rc) = $self->run_hooks("logging", $trace, $hook, $plugin, @log);
+
+ unless ( $rc and $rc == DECLINED or $rc == OK ) {
+ # no logging plugins registered so fall back to STDERR
+ warn join(" ", $$ .
+ (defined $plugin ? " $plugin plugin:" :
+ defined $hook ? " running plugin ($hook):" : ""),
+ @log), "\n"
+ if $trace <= $self->trace_level();
+ }
+}
+
+#
+# method to get the configuration. It just calls get_qmail_config by
+# default, but it could be overwritten to look configuration up in a
+# database or whatever.
+#
+sub config {
+ my ($self, $c, $type) = @_;
+
+ #warn "SELF->config($c) ", ref $self;
+
+ my %defaults = (
+ me => hostname,
+ timeout => 1200,
+ );
+
+ my ($rc, @config) = $self->run_hooks("config", $c);
+ @config = () unless $rc == OK;
+
+ if (wantarray) {
+ @config = $self->get_qmail_config($c, $type) unless @config;
+ @config = $defaults{$c} if (!@config and $defaults{$c});
+ return @config;
+ }
+ else {
+ return ($config[0] || $self->get_qmail_config($c, $type) || $defaults{$c});
+ }
+}
+
+sub config_dir {
+ my ($self, $config) = @_;
+ my $configdir = ($ENV{QMAIL} || '/var/qmail') . '/control';
+ my ($name) = ($0 =~ m!(.*?)/([^/]+)$!);
+ $configdir = "$name/config" if (-e "$name/config/$config");
+ if (exists $ENV{QPSMTPD_CONFIG}) {
+ $ENV{QPSMTPD_CONFIG} =~ /^(.*)$/; # detaint
+ $configdir = $1 if -e "$1/$config";
+ }
+ return $configdir;
+}
+
+sub plugin_dir {
+ my ($name) = ($0 =~ m!(.*?)/([^/]+)$!);
+ my $dir = "$name/plugins";
+}
+
+sub get_qmail_config {
+ my ($self, $config, $type) = @_;
+ $self->log(LOGDEBUG, "trying to get config for $config");
+ if ($self->{_config_cache}->{$config}) {
+ return wantarray ? @{$self->{_config_cache}->{$config}} : $self->{_config_cache}->{$config}->[0];
+ }
+ my $configdir = $self->config_dir($config);
+
+ my $configfile = "$configdir/$config";
+
+ if ($type and $type eq "map") {
+ return +{} unless -e $configfile . ".cdb";
+ eval { require CDB_File };
+
+ if ($@) {
+ $self->log(LOGERROR, "No CDB Support! Did NOT read $configfile.cdb, could not load CDB_File module: $@");
+ return +{};
+ }
+
+ my %h;
+ unless (tie(%h, 'CDB_File', "$configfile.cdb")) {
+ $self->log(LOGERROR, "tie of $configfile.cdb failed: $!");
+ return +{};
+ }
+ #warn Data::Dumper->Dump([\%h], [qw(h)]);
+ # should we cache this?
+ return \%h;
+ }
+
+ return $self->_config_from_file($configfile, $config);
+}
+
+sub _config_from_file {
+ my ($self, $configfile, $config, $visited) = @_;
+ return unless -e $configfile;
+
+ $visited ||= [];
+ push @{$visited}, $configfile;
+
+ open CF, "<$configfile" or warn "$$ could not open configfile $configfile: $!" and return;
+ my @config = <CF>;
+ chomp @config;
+ @config = grep { length($_) and $_ !~ m/^\s*#/ and $_ =~ m/\S/} @config;
+ close CF;
+
+ my $pos = 0;
+ while ($pos < @config) {
+ # recursively pursue an $include reference, if found. An inclusion which
+ # begins with a leading slash is interpreted as a path to a file and will
+ # supercede the usual config path resolution. Otherwise, the normal
+ # config_dir() lookup is employed (the location in which the inclusion
+ # appeared receives no special precedence; possibly it should, but it'd
+ # be complicated beyond justifiability for so simple a config system.
+ if ($config[$pos] =~ /^\s*\$include\s+(\S+)\s*$/) {
+ my ($includedir, $inclusion) = ('', $1);
+
+ splice @config, $pos, 1; # remove the $include line
+ if ($inclusion !~ /^\//) {
+ $includedir = $self->config_dir($inclusion);
+ $inclusion = "$includedir/$inclusion";
+ }
+
+ if (grep($_ eq $inclusion, @{$visited})) {
+ $self->log(LOGERROR, "Circular \$include reference in config $config:");
+ $self->log(LOGERROR, "From $visited->[0]:");
+ $self->log(LOGERROR, " includes $_")
+ for (@{$visited}[1..$#{$visited}], $inclusion);
+ return wantarray ? () : undef;
+ }
+ push @{$visited}, $inclusion;
+
+ for my $inc ($self->expand_inclusion_($inclusion, $configfile)) {
+ my @insertion = $self->_config_from_file($inc, $config, $visited);
+ splice @config, $pos, 0, @insertion; # insert the inclusion
+ $pos += @insertion;
+ }
+ } else {
+ $pos++;
+ }
+ }
+
+ $self->{_config_cache}->{$config} = \@config;
+
+ return wantarray ? @config : $config[0];
+}
+
+sub expand_inclusion_ {
+ my $self = shift;
+ my $inclusion = shift;
+ my $context = shift;
+ my @includes;
+
+ if (-d $inclusion) {
+ $self->log(LOGDEBUG, "inclusion of directory $inclusion from $context");
+
+ if (opendir(INCD, $inclusion)) {
+ @includes = map { "$inclusion/$_" }
+ (grep { -f "$inclusion/$_" and !/^\./ } readdir INCD);
+ closedir INCD;
+ } else {
+ $self->log(LOGERROR, "Couldn't open directory $inclusion,".
+ " referenced from $context ($!)");
+ }
+ } else {
+ $self->log(LOGDEBUG, "inclusion of file $inclusion from $context");
+ @includes = ( $inclusion );
+ }
+ return @includes;
+}
+
+
+sub load_plugins {
+ my $self = shift;
+
+ $self->log(LOGWARN, "Plugins already loaded") if $self->{hooks};
+ $self->{hooks} = {};
+
+ my @plugins = $self->config('plugins');
+
+ my $dir = $self->plugin_dir;
+ $self->log(LOGNOTICE, "loading plugins from $dir");
+
+ @plugins = $self->_load_plugins($dir, @plugins);
+
+ return @plugins;
+}
+
+sub _load_plugins {
+ my $self = shift;
+ my ($dir, @plugins) = @_;
+
+ my @ret;
+ for my $plugin_line (@plugins) {
+ my ($plugin, @args) = split ' ', $plugin_line;
+
+ my $plugin_name = $plugin;
+ $plugin =~ s/:\d+$//; # after this point, only used for filename
+
+ # Escape everything into valid perl identifiers
+ $plugin_name =~ s/([^A-Za-z0-9_\/])/sprintf("_%2x",unpack("C",$1))/eg;
+
+ # second pass cares for slashes and words starting with a digit
+ $plugin_name =~ s{
+ (/+) # directory
+ (\d?) # package's first character
+ }[
+ "::" . (length $2 ? sprintf("_%2x",unpack("C",$2)) : "")
+ ]egx;
+
+ my $package = "Qpsmtpd::Plugin::$plugin_name";
+
+ # don't reload plugins if they are already loaded
+ unless ( defined &{"${package}::plugin_name"} ) {
+ Qpsmtpd::Plugin->compile($plugin_name,
+ $package, "$dir/$plugin", $self->{_test_mode});
+ $self->log(LOGDEBUG, "Loading $plugin_line")
+ unless $plugin_line =~ /logging/;
+ }
+
+ my $plug = $package->new();
+ push @ret, $plug;
+ $plug->_register($self, @args);
+
+ }
+
+ return @ret;
+}
+
+sub transaction {
+ return {}; # base class implements empty transaction
+}
+
+sub run_hooks {
+ my ($self, $hook) = (shift, shift);
+ my $hooks = $self->{hooks};
+ if ($hooks->{$hook}) {
+ my @r;
+ for my $code (@{$hooks->{$hook}}) {
+ if ( $hook eq 'logging' ) { # without calling $self->log()
+ eval { (@r) = $code->{code}->($self, $self->transaction, @_); };
+ $@ and warn("FATAL LOGGING PLUGIN ERROR: ", $@) and next;
+ }
+ else {
+ $self->varlog(LOGINFO, $hook, $code->{name});
+ eval { (@r) = $code->{code}->($self, $self->transaction, @_); };
+ $@ and $self->log(LOGCRIT, "FATAL PLUGIN ERROR: ", $@) and next;
+
+ !defined $r[0]
+ and $self->log(LOGERROR, "plugin ".$code->{name}
+ ." running the $hook hook returned undef!")
+ and next;
+
+ if ($self->transaction) {
+ my $tnotes = $self->transaction->notes( $code->{name} );
+ $tnotes->{"hook_$hook"}->{'return'} = $r[0]
+ if (!defined $tnotes || ref $tnotes eq "HASH");
+ } else {
+ my $cnotes = $self->connection->notes( $code->{name} );
+ $cnotes->{"hook_$hook"}->{'return'} = $r[0]
+ if (!defined $cnotes || ref $cnotes eq "HASH");
+ }
+
+ # should we have a hook for "OK" too?
+ if ($r[0] == DENY or $r[0] == DENYSOFT or
+ $r[0] == DENY_DISCONNECT or $r[0] == DENYSOFT_DISCONNECT)
+ {
+ $r[1] = "" if not defined $r[1];
+ $self->log(LOGDEBUG, "Plugin ".$code->{name}.
+ ", hook $hook returned ".return_code($r[0]).", $r[1]");
+ $self->run_hooks("deny", $code->{name}, $r[0], $r[1]) unless ($hook eq "deny");
+ } else {
+ $r[1] = "" if not defined $r[1];
+ $self->log(LOGDEBUG, "Plugin ".$code->{name}.
+ ", hook $hook returned ".return_code($r[0]).", $r[1]");
+ $self->run_hooks("ok", $code->{name}, $r[0], $r[1]) unless ($hook eq "ok");
+ }
+
+ }
+
+ last unless $r[0] == DECLINED;
+ }
+ $r[0] = DECLINED if not defined $r[0];
+ return @r;
+ }
+ return (0, '');
+}
+
+sub _register_hook {
+ my $self = shift;
+ my ($hook, $code, $unshift) = @_;
+
+ my $hooks = $self->{hooks};
+ if ($unshift) {
+ unshift @{$hooks->{$hook}}, $code;
+ }
+ else {
+ push @{$hooks->{$hook}}, $code;
+ }
+}
+
+sub spool_dir {
+ my $self = shift;
+
+ unless ( $Spool_dir ) { # first time through
+ $self->log(LOGINFO, "Initializing spool_dir");
+ $Spool_dir = $self->config('spool_dir')
+ || Qpsmtpd::Utils::tildeexp('~/tmp/');
+
+ $Spool_dir .= "/" unless ($Spool_dir =~ m!/$!);
+
+ $Spool_dir =~ /^(.+)$/ or die "spool_dir not configured properly";
+ $Spool_dir = $1; # cleanse the taint
+
+ # Make sure the spool dir has appropriate rights
+ if (-e $Spool_dir) {
+ my $mode = (stat($Spool_dir))[2];
+ $self->log(LOGWARN,
+ "Permissions on spool_dir $Spool_dir are not 0700")
+ if $mode & 07077;
+ }
+
+ # And finally, create it if it doesn't already exist
+ -d $Spool_dir or mkdir($Spool_dir, 0700)
+ or die "Could not create spool_dir $Spool_dir: $!";
+ }
+
+ return $Spool_dir;
+}
+
+# For unique filenames. We write to a local tmp dir so we don't need
+# to make them unpredictable.
+my $transaction_counter = 0;
+
+sub temp_file {
+ my $self = shift;
+ my $filename = $self->spool_dir()
+ . join(":", time, $$, $transaction_counter++);
+ return $filename;
+}
+
+sub temp_dir {
+ my $self = shift;
+ my $mask = shift || 0700;
+ my $dirname = $self->temp_file();
+ -d $dirname or mkdir($dirname, $mask)
+ or die "Could not create temporary directory $dirname: $!";
+ return $dirname;
+}
+
+sub size_threshold {
+ my $self = shift;
+ unless ( defined $Size_threshold ) {
+ $Size_threshold = $self->config('size_threshold') || 0;
+ $self->log(LOGNOTICE, "size_threshold set to $Size_threshold");
+ }
+ return $Size_threshold;
+}
+
+sub auth_user {
+ my $self = shift;
+ return (defined $self->{_auth_user} ? $self->{_auth_user} : "" );
+}
+
+sub auth_mechanism {
+ my $self = shift;
+ return (defined $self->{_auth_mechanism} ? $self->{_auth_mechanism} : "" );
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Qpsmtpd
+
+=head1 DESCRIPTION
+
+This is the base class for the qpsmtpd mail server. See
+L<http://smtpd.develooper.com/> and the I<README> file for more information.
+
+=head1 COPYRIGHT
+
+Copyright 2001-2005 Ask Bjoern Hansen, Develooper LLC. See the
+LICENSE file for more information.
+
+
+
328 lib/Qpsmtpd/Address.pm
@@ -0,0 +1,328 @@
+#!/usr/bin/perl -w
+package Qpsmtpd::Address;
+use strict;
+
+=head1 NAME
+
+Qpsmtpd::Address - Lightweight E-Mail address objects
+
+=head1 DESCRIPTION
+
+Based originally on cut and paste from Mail::Address and including
+every jot and tittle from RFC-2821/2822 on what is a legal e-mail
+address for use during the SMTP transaction.
+
+=head1 USAGE
+
+ my $rcpt = Qpsmtpd::Address->new('<email.address@example.com>');
+
+The objects created can be used as is, since they automatically
+stringify to a standard form, and they have an overloaded comparison
+for easy testing of values.
+
+=head1 METHODS
+
+=cut
+
+use overload (
+ '""' => \&format,
+ 'cmp' => \&_addr_cmp,
+);
+
+=head2 new()
+
+Can be called two ways:
+
+=over 4
+
+=item * Qpsmtpd::Address->new('<full_address@example.com>')
+
+The normal mode of operation is to pass the entire contents of the
+RCPT TO: command from the SMTP transaction. The value will be fully
+parsed via the L<canonify> method, using the full RFC 2821 rules.
+
+=item * Qpsmtpd::Address->new("user", "host")
+
+If the caller has already split the address from the domain/host,
+this mode will not L<canonify> the input values. This is not
+recommended in cases of user-generated input for that reason. This
+can be used to generate Qpsmtpd::Address objects for accounts like
+"<postmaster>" or indeed for the bounce address "<>".
+
+=back
+
+The resulting objects can be stored in arrays or used in plugins to