Consolidate plugin documentation in docs/plugins.pod
* Include missing stuff from README.plugins into docs/plugins.pod * clear README.plugins to redirect to docs/plugins.pod
This commit is contained in:
parent
059771d31d
commit
8bce5f0278
381
README.plugins
381
README.plugins
@ -4,385 +4,10 @@
|
|||||||
|
|
||||||
=head1 qpsmtpd plugin system; developer documentation
|
=head1 qpsmtpd plugin system; developer documentation
|
||||||
|
|
||||||
|
Plugin documentation is now in F<docs/plugins.pod>.
|
||||||
|
|
||||||
See the examples in plugins/ and ask questions on the qpsmtpd
|
See the examples in plugins/ and ask questions on the qpsmtpd
|
||||||
mailinglist; subscribe by sending mail to qpsmtpd-subscribe@perl.org.
|
mailinglist; subscribe by sending mail to qpsmtpd-subscribe@perl.org.
|
||||||
|
|
||||||
=head1 General return codes
|
=cut
|
||||||
|
|
||||||
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_headers_end
|
|
||||||
|
|
||||||
Hook fires after all header lines of the message data has been received.
|
|
||||||
Defaults to doing nothing, just continue processing. At this step,
|
|
||||||
the sender is not waiting for a reply, but we can try and prevent him from
|
|
||||||
sending the entire message by disconnecting immediately. (Although it is
|
|
||||||
likely the packets are already in flight due to buffering and pipelining).
|
|
||||||
|
|
||||||
BE CAREFUL! If you drop the connection legal MTAs will retry again and again,
|
|
||||||
spammers will probably not. This is not RFC compliant and can lead to
|
|
||||||
an unpredictable mess. Use with caution.
|
|
||||||
|
|
||||||
Allowed return codes:
|
|
||||||
|
|
||||||
DENY_DISCONNECT - Return '554 Message denied' and disconnect
|
|
||||||
DENYSOFT_DISCONNECT - Return '421 Message denied temporarily' and disconnect
|
|
||||||
DECLINED - Do nothing
|
|
||||||
|
|
||||||
=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.
|
|
||||||
|
|
||||||
=head2 received_line
|
|
||||||
|
|
||||||
If you wish to provide your own Received header line, do it here.
|
|
||||||
|
|
||||||
The hook is passed the following extra parameters (beyond $self and $transaction):
|
|
||||||
|
|
||||||
- $smtp - the SMTP type used (e.g. "SMTP" or "ESMTP").
|
|
||||||
- $auth - the Auth header additionals.
|
|
||||||
- $sslinfo - information about SSL for the header.
|
|
||||||
|
|
||||||
You're free to use or discard any of the above.
|
|
||||||
|
|
||||||
Allowed return codes:
|
|
||||||
|
|
||||||
OK, $string - use this string for the Received header.
|
|
||||||
Anything Else - use the standard Received header.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
=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_headers_end hook_data_headers_end
|
|
||||||
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 );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -111,7 +111,8 @@ is named C<hook_$hookname> is automagically added.
|
|||||||
Plugins should be written using standard named hook subroutines. This
|
Plugins should be written using standard named hook subroutines. This
|
||||||
allows them to be overloaded and extended easily. Because some of the
|
allows them to be overloaded and extended easily. Because some of the
|
||||||
callback names have characters invalid in subroutine names , they must be
|
callback names have characters invalid in subroutine names , they must be
|
||||||
translated. The current translation routine is C<s/\W/_/g;>. If you choose
|
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
|
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
|
your plugin in the C<register()> method (see below) with the
|
||||||
C<register_hook()> call on the plugin object.
|
C<register_hook()> call on the plugin object.
|
||||||
@ -136,6 +137,40 @@ not for F<qpsmtpd> started by (x)inetd or tcpserver.
|
|||||||
|
|
||||||
In short: don't do it if you want to write portable plugins.
|
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
|
||||||
|
|
||||||
|
=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
|
=head2 Inheritance
|
||||||
|
|
||||||
Inheriting methods from other plugins is an advanced topic. You can alter
|
Inheriting methods from other plugins is an advanced topic. You can alter
|
||||||
@ -740,6 +775,45 @@ Arguments are
|
|||||||
# $auth - the Auth header additionals.
|
# $auth - the Auth header additionals.
|
||||||
# $sslinfo - information about SSL for the header.
|
# $sslinfo - information about SSL for the header.
|
||||||
|
|
||||||
|
=head2 data_headers_end
|
||||||
|
|
||||||
|
This hook fires after all header lines of the message data has been received.
|
||||||
|
Defaults to doing nothing, just continue processing. At this step,
|
||||||
|
the sender is not waiting for a reply, but we can try and prevent him from
|
||||||
|
sending the entire message by disconnecting immediately. (Although it is
|
||||||
|
likely the packets are already in flight due to buffering and pipelining).
|
||||||
|
|
||||||
|
B<NOTE:> BE CAREFUL! If you drop the connection legal MTAs will retry again
|
||||||
|
and again, spammers will probably not. This is not RFC compliant and can lead
|
||||||
|
to an unpredictable mess. Use with caution.
|
||||||
|
|
||||||
|
Why this head may be useful for you, see
|
||||||
|
L<http://www.nntp.perl.org/group/perl.qpsmtpd/2009/02/msg8502.html>, ff.
|
||||||
|
|
||||||
|
Allowed return codes:
|
||||||
|
|
||||||
|
=over 4
|
||||||
|
|
||||||
|
=item DENY_DISCONNECT
|
||||||
|
|
||||||
|
Return B<554 Message denied> and disconnect
|
||||||
|
|
||||||
|
=item DENYSOFT_DISCONNECT
|
||||||
|
|
||||||
|
Return B<421 Message denied temporarily> and disconnect
|
||||||
|
|
||||||
|
=item DECLINED
|
||||||
|
|
||||||
|
Do nothing
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
my ($self, $transaction) = @_;
|
||||||
|
|
||||||
|
B<FIXME:> check arguments
|
||||||
|
|
||||||
=head2 hook_data_post
|
=head2 hook_data_post
|
||||||
|
|
||||||
The C<data_post> hook is called after the client sent the final C<.\r\n>
|
The C<data_post> hook is called after the client sent the final C<.\r\n>
|
||||||
@ -1570,7 +1644,16 @@ should be configured to run I<last>, like B<rcpt_ok>.
|
|||||||
"Too many relaying attempts");
|
"Too many relaying attempts");
|
||||||
}
|
}
|
||||||
|
|
||||||
=head2 TBC... :-)
|
=head2 Results of other hooks
|
||||||
|
|
||||||
|
B<NOTE:> just copied from README.plugins
|
||||||
|
|
||||||
|
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.
|
||||||
|
B<FIXME>: does the above (regarding connection notes) work?
|
||||||
|
|
||||||
=cut
|
=cut
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user