059771d31d
Hook after receiving all headers lines. Defaults to nothing, just continue processing. At this step, sender does not wait for a reply, but we can stop him from sending remaining data by disconnecting. (Cleaned up by Robert for english and coding style.) Signed-off-by: Ask Bjørn Hansen <ask@develooper.com> Signed-off-by: Robert Spier <robert@perl.org>
389 lines
11 KiB
Plaintext
389 lines
11 KiB
Plaintext
#
|
|
# 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_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 );
|
|
}
|
|
|
|
|
|
|