402 lines
12 KiB
Plaintext
402 lines
12 KiB
Plaintext
|
#
|
||
|
# 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
|
||
|
translated. The current translation routine is C<s/\W/_/g;>, see
|
||
|
L</Hook - Subroutine translations> for more info. If you choose
|
||
|
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.
|
||
|
|
||
|
=head2 Hook - Subroutine translations
|
||
|
|
||
|
As mentioned above, the hook name needs to be translated to a valid perl
|
||
|
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
|
||
|
|
||
|
All valid hooks are defined in F<lib/Qpsmtpd/Plugins.pm>, C<our @hooks>.
|
||
|
|
||
|
=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
|
||
|
|
||
|
=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");
|
||
|
}
|
||
|
|
||
|
sub hook_rcpt {
|
||
|
my ($self, $transaction, $recipient) = @_;
|
||
|
# do something special here...
|
||
|
$self->SUPER::hook_rcpt($transaction, $recipient);
|
||
|
}
|
||
|
|
||
|
See also chapter C<Changing return values> and
|
||
|
F<contrib/vetinari/rcpt_ok_maxrelay> in SVN.
|
||
|
|
||
|
=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->log($loglevel, $logmessage);
|
||
|
|
||
|
The log level is one of (from low to high priority)
|
||
|
|
||
|
=over 4
|
||
|
|
||
|
=item *
|
||
|
|
||
|
LOGDEBUG
|
||
|
|
||
|
=item *
|
||
|
|
||
|
LOGINFO
|
||
|
|
||
|
=item *
|
||
|
|
||
|
LOGNOTICE
|
||
|
|
||
|
=item *
|
||
|
|
||
|
LOGWARN
|
||
|
|
||
|
=item *
|
||
|
|
||
|
LOGERROR
|
||
|
|
||
|
=item *
|
||
|
|
||
|
LOGCRIT
|
||
|
|
||
|
=item *
|
||
|
|
||
|
LOGALERT
|
||
|
|
||
|
=item *
|
||
|
|
||
|
LOGEMERG
|
||
|
|
||
|
=back
|
||
|
|
||
|
While debugging your plugins, set your plugins loglevel to LOGDEBUG. This
|
||
|
will log every logging statement within your plugin.
|
||
|
|
||
|
For more information about logging, see F<docs/logging.pod>.
|
||
|
|
||
|
=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.
|
||
|
|
||
|
=item YIELD
|
||
|
|
||
|
Only used in F<qpsmtpd-async>, see F<plugins/async/*>
|
||
|
|
||
|
=back
|
||
|
|
||
|
=cut
|
||
|
|
||
|
# vim: ts=2 sw=2 expandtab
|