2007-08-08 19:25:54 +02:00
|
|
|
#
|
|
|
|
# This file is best read with ``perldoc plugins.pod''
|
|
|
|
#
|
|
|
|
|
|
|
|
###
|
|
|
|
# Conventions:
|
|
|
|
# plugin names: F<myplugin>, F<qpsmtpd-async>
|
|
|
|
# constants: I<LOGDEBUG>
|
|
|
|
# smtp commands, answers: B<HELO>, B<250 Queued!>
|
|
|
|
#
|
|
|
|
# Notes:
|
|
|
|
# * due to restrictions of some POD parsers, no C<<$object->method()>>
|
|
|
|
# are allowed, use C<$object-E<gt>method()>
|
|
|
|
#
|
|
|
|
|
|
|
|
=head1 Introduction
|
|
|
|
|
|
|
|
Plugins are the heart of qpsmtpd. The core implements only basic SMTP protocol
|
|
|
|
functionality. 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 I<hooks> provided by the qpsmtpd core engine.
|
|
|
|
|
|
|
|
At least one plugin B<must> allow or deny the B<RCPT> command to enable
|
|
|
|
receiving mail. The F<check_relay> plugin is the standard plugin for this.
|
|
|
|
Other plugins provide extra functionality related to this; for example the
|
|
|
|
F<require_resolvable_fromhost> plugin.
|
|
|
|
|
|
|
|
=head2 Loading Plugins
|
|
|
|
|
|
|
|
The list of plugins to load are configured in the I<config/plugins>
|
|
|
|
configuration file. One plugin per line, empty lines and lines starting
|
|
|
|
with I<#> are ignored. The order they are loaded is the same as given
|
|
|
|
in this config file. This is also the order the registered I<hooks>
|
|
|
|
are run. The plugins are loaded from the F<plugins/> directory or
|
|
|
|
from a subdirectory of it. If a plugin should be loaded from such a
|
|
|
|
subdirectory, the directory must also be given, like the
|
|
|
|
F<virus/clamdscan> in the example below. Alternate plugin directories
|
|
|
|
may be given in the F<config/plugin_dirs> config file, one directory
|
|
|
|
per line, these will be searched first before using the builtin fallback
|
|
|
|
of F<plugins/> relative to the qpsmtpd root directory. It may be
|
|
|
|
necessary, that the F<config/plugin_dirs> must be used (if you're using
|
|
|
|
F<Apache::Qpsmtpd>, for example).
|
|
|
|
|
|
|
|
Some plugins may be configured by passing arguments in the F<plugins>
|
|
|
|
config file.
|
|
|
|
|
|
|
|
A plugin can be loaded two or more times with different arguments by adding
|
|
|
|
I<:N> to the plugin filename, with I<N> being a number, usually starting at
|
|
|
|
I<0>.
|
|
|
|
|
|
|
|
Another method to load a plugin is to create a valid perl module, drop this
|
|
|
|
module in perl's C<@INC> path and give the name of this module as
|
|
|
|
plugin name. The only restriction to this is, that the module name B<must>
|
|
|
|
contain I<::>, e.g. C<My::Plugin> would be ok, C<MyPlugin> not. Appending of
|
|
|
|
I<:0>, I<:1>, ... does not work with module plugins.
|
|
|
|
|
|
|
|
check_relay
|
|
|
|
virus/clamdscan
|
|
|
|
spamassassin reject_threshold 7
|
|
|
|
my_rcpt_check example.com
|
|
|
|
my_rcpt_check:0 example.org
|
|
|
|
My::Plugin
|
|
|
|
|
|
|
|
=head1 Anatomy of a plugin
|
|
|
|
|
|
|
|
A plugin has at least one method, which inherits from the
|
|
|
|
C<Qpsmtpd::Plugin> object. The first argument for this method is always the
|
|
|
|
plugin object itself (and usually called C<$self>). The most simple plugin
|
|
|
|
has one method with a predefined name which just returns one constant.
|
|
|
|
|
|
|
|
# plugin temp_disable_connection
|
|
|
|
sub hook_connect {
|
|
|
|
return(DENYSOFT, "Sorry, server is temporarily unavailable.");
|
|
|
|
}
|
|
|
|
|
|
|
|
While this is a valid plugin, it is not very useful except for rare
|
|
|
|
circumstances. So let us see what happens when a plugin is loaded.
|
|
|
|
|
|
|
|
=head2 Initialisation
|
|
|
|
|
|
|
|
After the plugin is loaded the C<init()> method of the plugin is called,
|
|
|
|
if present. The arguments passed to C<init()> are
|
|
|
|
|
|
|
|
=over 4
|
|
|
|
|
|
|
|
=item $self
|
|
|
|
|
|
|
|
the current plugin object, usually called C<$self>
|
|
|
|
|
|
|
|
=item $qp
|
|
|
|
|
|
|
|
the Qpsmtpd object, usually called C<$qp>.
|
|
|
|
|
|
|
|
=item @args
|
|
|
|
|
|
|
|
the values following the plugin name in the F<plugins> config, split by
|
|
|
|
white space. These arguments can be used to configure the plugin with
|
|
|
|
default and/or static config settings, like database paths,
|
|
|
|
timeouts, ...
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
This is mainly used for inheriting from other plugins, but may be used to do
|
|
|
|
the same as in C<register()>.
|
|
|
|
|
|
|
|
The next step is to register the hooks the plugin provides. Any method which
|
|
|
|
is named C<hook_$hookname> is automagically added.
|
|
|
|
|
|
|
|
Plugins should be written using standard named hook subroutines. This
|
|
|
|
allows them to be overloaded and extended easily. Because some of the
|
|
|
|
callback names have characters invalid in subroutine names , they must be
|
2009-02-10 21:27:10 +01:00
|
|
|
translated. The current translation routine is C<s/\W/_/g;>, see
|
|
|
|
L</Hook - Subroutine translations> for more info. If you choose
|
2007-08-08 19:25:54 +02:00
|
|
|
not to use the default naming convention, you need to register the hooks in
|
|
|
|
your plugin in the C<register()> method (see below) with the
|
|
|
|
C<register_hook()> call on the plugin object.
|
|
|
|
|
|
|
|
sub register {
|
|
|
|
my ($self, $qp, @args) = @_;
|
|
|
|
$self->register_hook("mail", "mail_handler");
|
|
|
|
$self->register_hook("rcpt", "rcpt_handler");
|
|
|
|
}
|
|
|
|
sub mail_handler { ... }
|
|
|
|
sub rcpt_handler { ... }
|
|
|
|
|
|
|
|
The C<register()> method is called last. It receives the same arguments as
|
|
|
|
C<init()>. There is no restriction, what you can do in C<register()>, but
|
|
|
|
creating database connections and reuse them later in the process may not be
|
|
|
|
a good idea. This initialisation happens before any C<fork()> is done.
|
|
|
|
Therefore the file handle will be shared by all qpsmtpd processes and the
|
|
|
|
database will probably be confused if several different queries arrive on
|
|
|
|
the same file handle at the same time (and you may get the wrong answer, if
|
|
|
|
any). This is also true for F<qpsmtpd-async> and the pperl flavours, but
|
|
|
|
not for F<qpsmtpd> started by (x)inetd or tcpserver.
|
|
|
|
|
|
|
|
In short: don't do it if you want to write portable plugins.
|
|
|
|
|
2009-02-10 21:27:10 +01:00
|
|
|
=head2 Hook - Subroutine translations
|
|
|
|
|
2009-02-11 07:18:09 +01:00
|
|
|
As mentioned above, the hook name needs to be translated to a valid perl
|
2009-02-10 21:27:10 +01:00
|
|
|
C<sub> name. This is done like
|
|
|
|
|
|
|
|
($sub = $hook) =~ s/\W/_/g;
|
|
|
|
$sub = "hook_$sub";
|
|
|
|
|
|
|
|
Some examples follow, for a complete list of available (documented ;-))
|
|
|
|
hooks (method names), use something like
|
|
|
|
|
|
|
|
$ perl -lne 'print if s/^=head2\s+(hook_\S+)/$1/' docs/plugins.pod
|
|
|
|
|
2009-02-11 07:18:09 +01:00
|
|
|
All valid hooks are defined in F<lib/Qpsmtpd/Plugins.pm>, C<our @hooks>.
|
|
|
|
|
2009-02-10 21:27:10 +01:00
|
|
|
=head3 Translation table
|
|
|
|
|
|
|
|
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
|
|
|
|
|
2007-08-08 19:25:54 +02:00
|
|
|
=head2 Inheritance
|
|
|
|
|
|
|
|
Inheriting methods from other plugins is an advanced topic. You can alter
|
|
|
|
arguments for the underlying plugin, prepare something for the I<real>
|
|
|
|
plugin or skip a hook with this. Instead of modifying C<@ISA>
|
|
|
|
directly in your plugin, use the C<isa_plugin()> method from the
|
|
|
|
C<init()> subroutine.
|
|
|
|
|
|
|
|
# rcpt_ok_child
|
|
|
|
sub init {
|
|
|
|
my ($self, $qp, @args) = @_;
|
|
|
|
$self->isa_plugin("rcpt_ok");
|
|
|
|
}
|
2009-06-03 01:30:20 +02:00
|
|
|
|
2007-08-08 19:25:54 +02:00
|
|
|
sub hook_rcpt {
|
|
|
|
my ($self, $transaction, $recipient) = @_;
|
|
|
|
# do something special here...
|
|
|
|
$self->SUPER::hook_rcpt($transaction, $recipient);
|
|
|
|
}
|
|
|
|
|
2007-12-10 09:49:08 +01:00
|
|
|
See also chapter C<Changing return values> and
|
|
|
|
F<contrib/vetinari/rcpt_ok_maxrelay> in SVN.
|
|
|
|
|
2007-08-08 19:25:54 +02:00
|
|
|
=head2 Config files
|
|
|
|
|
|
|
|
Most of the existing plugins fetch their configuration data from files in the
|
|
|
|
F<config/> sub directory. This data is read at runtime and may be changed
|
|
|
|
without restarting qpsmtpd.
|
|
|
|
B<(FIXME: caching?!)>
|
|
|
|
The contents of the files can be fetched via
|
|
|
|
|
|
|
|
@lines = $self->qp->config("my_config");
|
|
|
|
|
|
|
|
All empty lines and lines starting with C<#> are ignored.
|
|
|
|
|
|
|
|
If you don't want to read your data from files, but from a database you can
|
|
|
|
still use this syntax and write another plugin hooking the C<config>
|
|
|
|
hook.
|
|
|
|
|
|
|
|
=head2 Logging
|
|
|
|
|
|
|
|
Log messages can be written to the log file (or STDERR if you use the
|
|
|
|
F<logging/warn> plugin) with
|
|
|
|
|
|
|
|
$self->qp->log($loglevel, $logmessage);
|
|
|
|
|
|
|
|
The log level is one of (from low to high priority)
|
|
|
|
|
|
|
|
=over 4
|
|
|
|
|
2007-12-15 21:11:49 +01:00
|
|
|
=item *
|
2007-08-08 19:25:54 +02:00
|
|
|
|
2007-12-15 21:11:49 +01:00
|
|
|
LOGDEBUG
|
2007-08-08 19:25:54 +02:00
|
|
|
|
2007-12-15 21:11:49 +01:00
|
|
|
=item *
|
2007-08-08 19:25:54 +02:00
|
|
|
|
2007-12-15 21:11:49 +01:00
|
|
|
LOGINFO
|
2007-08-08 19:25:54 +02:00
|
|
|
|
2007-12-15 21:11:49 +01:00
|
|
|
=item *
|
2007-08-08 19:25:54 +02:00
|
|
|
|
2007-12-15 21:11:49 +01:00
|
|
|
LOGNOTICE
|
2007-08-08 19:25:54 +02:00
|
|
|
|
2007-12-15 21:11:49 +01:00
|
|
|
=item *
|
2007-08-08 19:25:54 +02:00
|
|
|
|
2007-12-15 21:11:49 +01:00
|
|
|
LOGWARN
|
|
|
|
|
|
|
|
=item *
|
|
|
|
|
|
|
|
LOGERROR
|
|
|
|
|
|
|
|
=item *
|
|
|
|
|
|
|
|
LOGCRIT
|
|
|
|
|
|
|
|
=item *
|
|
|
|
|
|
|
|
LOGALERT
|
|
|
|
|
|
|
|
=item *
|
|
|
|
|
|
|
|
LOGEMERG
|
2007-08-08 19:25:54 +02:00
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
While debugging your plugins, you want to set the log level in the F<logging>
|
|
|
|
config file to I<LOGDEBUG>. This will log very much data. To restrict this
|
|
|
|
output just to the plugin you are debugging, you can use the following plugin:
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
FIXME: Test if this really works as inteded ;-)
|
|
|
|
|
|
|
|
=pod
|
|
|
|
|
|
|
|
# logging/debug_plugin - just show LOGDEBUG messages of one plugin
|
|
|
|
# Usage:
|
|
|
|
# logging/debug_plugin my_plugin LOGLEVEL
|
|
|
|
#
|
|
|
|
# LOGLEVEL is the log level for all other log messages
|
|
|
|
use Qpsmtpd::Constants;
|
2009-06-03 01:30:20 +02:00
|
|
|
|
2007-08-08 19:25:54 +02:00
|
|
|
sub register {
|
|
|
|
my ($self, $qp, $plugin, $loglevel) = @_;
|
|
|
|
die "no plugin name given"
|
|
|
|
unless $plugin;
|
|
|
|
$loglevel = "LOGWARN"
|
|
|
|
unless defined $loglevel;
|
|
|
|
$self->{_plugin} = $plugin;
|
|
|
|
$self->{_level} = Qpsmtpd::Constants::log_level($loglevel);
|
|
|
|
$self->{_level} = LOGWARN
|
|
|
|
unless defined $self->{_level};
|
|
|
|
}
|
2009-06-03 01:30:20 +02:00
|
|
|
|
2007-08-08 19:25:54 +02:00
|
|
|
sub hook_logging {
|
|
|
|
my ($self, $transaction, $trace, $hook, $plugin, @log) = @_;
|
|
|
|
return(OK) # drop these lines
|
|
|
|
if $plugin ne $self->{_plugin} and $trace > $self->{_level};
|
|
|
|
return(DECLINED);
|
|
|
|
}
|
|
|
|
|
|
|
|
The above plugin should be loaded before the default logging plugin, which
|
|
|
|
logs with I<LOGDEBUG>. The plugin name must be the one returned by the
|
|
|
|
C<plugin_name()> method of the debugged plugin. This is probably not
|
|
|
|
the same as the name of the plugin (i.e. not the same you write in the
|
|
|
|
F<plugins> config file). In doubt: take a look in the log file for lines
|
|
|
|
like C<queue::qmail_2dqueue hooking queue> (here: F<queue/qmail-queue>
|
|
|
|
=E<gt> F<queue::qmail_2dqueue>).
|
|
|
|
|
2009-02-11 07:18:09 +01:00
|
|
|
For more information about logging, see F<docs/logging.pod>.
|
|
|
|
|
2007-08-08 19:25:54 +02:00
|
|
|
=head2 Information about the current plugin
|
|
|
|
|
|
|
|
Each plugin inherits the public methods from C<Qpsmtpd::Plugin>.
|
|
|
|
|
|
|
|
=over 4
|
|
|
|
|
|
|
|
=item plugin_name()
|
|
|
|
|
|
|
|
Returns the name of the currently running plugin
|
|
|
|
|
|
|
|
=item hook_name()
|
|
|
|
|
|
|
|
Returns the name of the running hook
|
|
|
|
|
|
|
|
=item auth_user()
|
|
|
|
|
|
|
|
Returns the name of the user the client is authed as (if authentication is
|
|
|
|
used, of course)
|
|
|
|
|
|
|
|
=item auth_mechanism()
|
|
|
|
|
|
|
|
Returns the auth mechanism if authentication is used
|
|
|
|
|
|
|
|
=item connection()
|
|
|
|
|
|
|
|
Returns the C<Qpsmtpd::Connection> object associated with the current
|
|
|
|
connection
|
|
|
|
|
|
|
|
=item transaction()
|
|
|
|
|
|
|
|
Returns the C<Qpsmtpd::Transaction> object associated with the current
|
|
|
|
transaction
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
=head2 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 C<$self-E<gt>qp-E<gt>temp_dir()>, which takes a
|
|
|
|
single parameter for the permissions requested (see L<mkdir> for details).
|
|
|
|
A directory created like this will not be deleted when the transaction
|
|
|
|
is ended.
|
|
|
|
|
|
|
|
=item spool_dir()
|
|
|
|
|
|
|
|
Returns the configured system-wide spool directory.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
|
|
|
|
=head2 Connection and Transaction Notes
|
|
|
|
|
|
|
|
Both may be used to share notes across plugins and/or hooks. The only real
|
|
|
|
difference is their life time. The connection notes start when a new
|
|
|
|
connection is made and end, when the connection ends. This can, for example,
|
|
|
|
be used to count the number of none SMTP commands. The plugin which uses
|
|
|
|
this is the F<count_unrecognized_commands> plugin from the qpsmtpd core
|
|
|
|
distribution.
|
|
|
|
|
|
|
|
The transaction note starts after the B<MAIL FROM: > command and are just
|
|
|
|
valid for the current transaction, see below in the I<reset_transaction>
|
|
|
|
hook when the transaction ends.
|
|
|
|
|
|
|
|
|
|
|
|
=head1 Return codes
|
|
|
|
|
|
|
|
Each plugin must return an allowed constant for the hook and (usually)
|
|
|
|
optionally a ``message'' for the client.
|
|
|
|
Generally all plugins for a hook are processed until one returns
|
|
|
|
something other than I<DECLINED>.
|
|
|
|
|
|
|
|
Plugins are run in the order they are listed in the F<plugins>
|
|
|
|
configuration file.
|
|
|
|
|
|
|
|
The return constants are defined in C<Qpsmtpd::Constants> and have
|
|
|
|
the following meanings:
|
|
|
|
|
|
|
|
=over 4
|
|
|
|
|
|
|
|
=item DECLINED
|
|
|
|
|
|
|
|
Plugin declined work; proceed as usual. This return code is I<always allowed>
|
|
|
|
unless noted otherwise.
|
|
|
|
|
|
|
|
=item OK
|
|
|
|
|
|
|
|
Action allowed.
|
|
|
|
|
|
|
|
=item DENY
|
|
|
|
|
|
|
|
Action denied.
|
|
|
|
|
|
|
|
=item DENYSOFT
|
|
|
|
|
|
|
|
Action denied; return a temporary rejection code (say B<450> instead
|
|
|
|
of B<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.
|
|
|
|
See note above about SMTP specs.
|
|
|
|
|
|
|
|
=item DONE
|
|
|
|
|
|
|
|
Finishing processing of the request. Usually used when the plugin sent the
|
|
|
|
response to the client.
|
|
|
|
|
2009-02-11 07:18:09 +01:00
|
|
|
=item YIELD
|
2007-08-08 19:25:54 +02:00
|
|
|
|
2009-02-11 07:18:09 +01:00
|
|
|
Only used in F<qpsmtpd-async>, see F<plugins/async/*>
|
2007-08-08 19:25:54 +02:00
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
# vim: ts=2 sw=2 expandtab
|