# # 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 for details). A directory created like this will B 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 C< 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 ); }