[svn:qpsmtpd] r602 - in branches/0.3x: lib/Qpsmtpd plugins
[email protected] 25 Jan 2006 02:59:31 -0000
| Newsgroups | perl.cvs.qpsmtpd |
|---|---|
| Message-ID | <[email protected]> |
Author: jpeacock Date: Tue Jan 24 18:59:31 2006 New Revision: 602 Added: branches/0.3x/lib/Qpsmtpd/DSN.pm Modified: branches/0.3x/plugins/check_badrcptto branches/0.3x/plugins/check_loop branches/0.3x/plugins/rcpt_ok branches/0.3x/plugins/require_resolvable_fromhost branches/0.3x/plugins/spamassassin Log: Support for RFC 1893 - Enhanced Mail System Status Codes Patch by Hanno Hecker <[email protected]>. Adds the RFC 1893 status codes to the messages which are returned to the sending client. Added: branches/0.3x/lib/Qpsmtpd/DSN.pm ============================================================================== --- (empty file) +++ branches/0.3x/lib/Qpsmtpd/DSN.pm Tue Jan 24 18:59:31 2006 @@ -0,0 +1,621 @@ +# +# Enhanced Mail System Status Codes - RFC 1893 +# +package Qpsmtpd::DSN; +use strict; +use Qpsmtpd::Constants; + +=head1 NAME + +Qpsmtpd::DSN - Enhanced Mail System Status Codes - RFC 1893 + +=head1 DESCRIPTION + +The B<Qpsmtpd::DSN> implements the I<Enhanced Mail System Status Codes> from +RFC 1893. + +=head1 USAGE + +Any B<qpsmtpd> plugin can access these status codes. All sub routines are used +the same way: + use Qpsmtpd::DSN; + ...; + return Qpsmtpd::DSN->relaying_denied(); + +or + + return Qpsmtpd::DSN->relaying_denied("Relaying from $ip denied"); + +or + + return Qpsmtpd::DSN->relaying_denied(DENY,"Relaying from $ip denied"); + +If no status message was given, it will use the predefined one from the +RFC. If the first argument is numeric, it will use this as a return code, +else the default return code is used. See below which default return code +is used in the different functions. + +The first example will return +I<(DENY, "Relaying denied");> +the others +I<(DENY, "Relaying from $ip denied");> +which will be returned to qpsmtpd. + +In those sub routines which don't start with I<addr_, sys_, net_, proto_, +media_, sec_> I've added a default message which describes the status better +than the RFC message. + +=cut + +my @rfc1893 = ( + [ + "Other or Undefined Status", # x.0.x + ], + [ + "Other address status.", # x.1.0 + "Bad destination mailbox address.", # x.1.1 + "Bad destination system address.", # x.1.2 + "Bad destination mailbox address syntax.", # x.1.3 + "Destination mailbox address ambiguous.", # x.1.4 + "Destination address valid.", # x.1.5 + "Destination mailbox has moved, No forwarding address.", # x.1.6 + "Bad sender's mailbox address syntax.", # x.1.7 + "Bad sender's system address.", # x.1.8 + ], + [ + "Other or undefined mailbox status.", # x.2.0 + "Mailbox disabled, not accepting messages.", # x.2.1 + "Mailbox full.", # x.2.2 + "Message length exceeds administrative limit.", # x.2.3 + "Mailing list expansion problem.", # x.2.4 + ], + [ + "Other or undefined mail system status.", # x.3.0 + "Mail system full.", # x.3.1 + "System not accepting network messages.", # x.3.2 + "System not capable of selected features.", # x.3.3 + "Message too big for system.", # x.3.4 + "System incorrectly configured.", # x.3.5 + ], + [ + "Other or undefined network or routing status.", # x.4.0 + "No answer from host.", # x.4.1 + "Bad connection.", # x.4.2 + "Directory server failure.", # x.4.3 + "Unable to route.", # x.4.4 + "Mail system congestion.", # x.4.5 + "Routing loop detected.", # x.4.6 + "Delivery time expired.", # x.4.7 + ], + [ + "Other or undefined protocol status.", # x.5.0 + "Invalid command.", # x.5.1 + "Syntax error.", # x.5.2 + "Too many recipients.", # x.5.3 + "Invalid command arguments.", # x.5.4 + "Wrong protocol version.", # x.5.5 + ], + [ + "Other or undefined media error.", # x.6.0 + "Media not supported.", # x.6.1 + "Conversion required and prohibited.", # x.6.2 + "Conversion required but not supported.", # x.6.3 + "Conversion with loss performed.", # x.6.4 + "Conversion Failed.", # x.6.5 + ], + [ + "Other or undefined security status.", # x.7.0 + "Delivery not authorized, message refused.", # x.7.1 + "Mailing list expansion prohibited.", # x.7.2 + "Security conversion required but not possible.", # x.7.3 + "Security features not supported.", # x.7.4 + "Cryptographic failure.", # x.7.5 + "Cryptographic algorithm not supported.", # x.7.6 + "Message integrity failure.", # x.7.7 + ], +); + +sub _status { + my $return = shift; + my $const = Qpsmtpd::Constants::return_code($return); + if ($const =~ /^DENYSOFT/) { + return 4; + } + elsif ($const =~ /^DENY/) { + return 5; + } + elsif ($const eq 'OK' or $const eq 'DONE') { + return 2; + } + else { # err .... no :) + return 4; # just 2,4,5 are allowed.. temp error by default + } +} + +sub _dsn { + my ($self,$return,$reason,$default,$subject,$detail) = @_; + if (!defined $return) { + $return = $default; + } + elsif ($return !~ /^\d+$/) { + $reason = $return; + $return = $default; + } + my $msg = $rfc1893[$subject][$detail]; + unless (defined $msg) { + $detail = 0; + $msg = $rfc1893[$subject][$detail]; + unless (defined $msg) { + $subject = 0; + $msg = $rfc1893[$subject][$detail]; + } + } + my $class = &_status($return); + if (defined $reason) { + $msg = $reason; + } + return ($return, "$msg (#$class.$subject.$detail)"); +} + +sub unspecified { shift->_dsn(shift,shift,DENYSOFT,0,0); } + +=head1 ADDRESS STATUS + +=over 9 + +=item addr_unspecified + +X.1.0 +default: DENYSOFT + +=cut + +sub addr_unspecified { shift->_dsn(shift,shift,DENYSOFT,1,0); } + +=item no_such_user, addr_bad_dest_mbox + +X.1.1 +default: DENY + +=cut + +sub no_such_user { shift->_dsn(shift,(shift||"No such user"),DENY,1,1); } +sub addr_bad_dest_mbox { shift->_dsn(shift,shift,DENY,1,1); } + +=item addr_bad_dest_system + +X.1.2 +default: DENY + +=cut + +sub addr_bad_dest_system { shift->_dsn(shift,shift,DENY,1,2); } + +=item addr_bad_dest_syntax + +X.1.3 +default: DENY + +=cut + +sub addr_bad_dest_syntax { shift->_dsn(shift,shift,DENY,1,3); } + +=item addr_dest_ambigous + +X.1.4 +default: DENYSOFT + +=cut + +sub addr_dest_ambigous { shift->_dsn(shift,shift,DENYSOFT,1,4); } + +=item addr_rcpt_ok + +X.1.5 +default: OK + +=cut + +# XXX: do we need this? Maybe in all address verifying plugins? +sub addr_rcpt_ok { shift->_dsn(shift,shift,OK,1,5); } + +=item addr_mbox_moved + +X.1.6 +default: DENY + +=cut + +sub addr_mbox_moved { shift->_dsn(shift,shift,DENY,1,6); } + +=item addr_bad_from_syntax + +X.1.7 +default: DENY + +=cut + +sub addr_bad_from_syntax { shift->_dsn(shift,shift,DENY,1,7); } + +=item addr_bad_from_system + +X.1.8 +default: DENY + +=back + +=cut + +sub addr_bad_from_system { shift->_dsn(shift,shift,DENY,1,8); } + +=head1 MAILBOX STATUS + +=over 5 + +=item mbox_unspecified + +X.2.0 +default: DENYSOFT + +=cut + +sub mbox_unspecified { shift->_dsn(shift,shift,DENYSOFT,2,0); } + +=item mbox_disabled + +X.2.1 +default: DENY ...but RFC says: + The mailbox exists, but is not accepting messages. This may + be a permanent error if the mailbox will never be re-enabled + or a transient error if the mailbox is only temporarily + disabled. + +=cut + +sub mbox_disabled { shift->_dsn(shift,shift,DENY,2,1); } + +=item mbox_full + +X.2.2 +default: DENYSOFT + +=cut + +sub mbox_full { shift->_dsn(shift,shift,DENYSOFT,2,2); } + +=item mbox_msg_too_long + +X.2.3 +default: DENY + +=cut + +sub mbox_msg_too_long { shift->_dsn(shift,shift,DENY,2,3); } + +=item mbox_list_expansion_problem + +X.2.4 +default: DENYSOFT + +=back + +=cut + +sub mbox_list_expansion_problem { shift->_dsn(shift,shift,DENYSOFT,2,4); } + +=head1 MAIL SYSTEM STATUS + +=over 4 + +=item sys_unspecified + +X.3.0 +default: DENYSOFT + +=cut + +sub sys_unspecified { shift->_dsn(shift,shift,DENYSOFT,3,0); } + +=item sys_disk_full + +X.3.1 +default: DENYSOFT + +=cut + +sub sys_disk_full { shift->_dsn(shift,shift,DENYSOFT,3,1); } + +=item sys_not_accepting_mail + +X.3.2 +default: DENYSOFT + +=cut + +sub sys_not_accepting_mail { shift->_dsn(shift,shift,DENYSOFT,3,2); } + +=item sys_not_supported + +X.3.3 +default: DENYSOFT + Selected features specified for the message are not + supported by the destination system. This can occur in + gateways when features from one domain cannot be mapped onto + the supported feature in another. + +=cut + +sub sys_not_supported { shift->_dsn(shift,shift,DENYSOFT,3,3); } + +=item sys_msg_too_big + +X.3.4 +default DENY + +=back + +=cut + +sub sys_msg_too_big { shift->_dsn(shift,shift,DENY,3,4); } + +=head1 NETWORK AND ROUTING STATUS + +=cut + +=over 4 + +=item net_unspecified + +X.4.0 +default: DENYSOFT + +=cut + +sub net_unspecified { shift->_dsn(shift,shift,DENYSOFT,4,0); } + +# not useful # sub net_no_answer { shift->_dsn(shift,shift,4,1); } +# not useful # sub net_bad_connection { shift->_dsn(shift,shift,4,2); } + +=item net_directory_server_failed, temp_resolver_failed + +X.4.3 +default: DENYSOFT + +=cut + +sub temp_resolver_failed { + shift->_dsn(shift, + (shift || "Temporary address resolution failure"), + DENYSOFT,4,3); +} +sub net_directory_server_failed { shift->_dsn(shift,shift,DENYSOFT,4,3); } + +# not useful # sub net_unable_to_route { shift->_dsn(shift,shift,4,4); } + +=item net_system_congested + +X.4.5 +default: DENYSOFT + +=cut + +sub net_system_congested { shift->_dsn(shift,shift,DENYSOFT,4,5); } + +=item net_routing_loop, too_many_hops + +X.4.6 +default: DENY, but RFC says: + A routing loop caused the message to be forwarded too many + times, either because of incorrect routing tables or a user + forwarding loop. This is useful only as a persistent + transient error. + +Why do we want to DENYSOFT something like this? + +=back + +=cut + +sub net_routing_loop { shift->_dsn(shift,shift,DENY,4,6); } +sub too_many_hops { shift->_dsn(shift,(shift || "Too many hops"),DENY,4,6,); } +# not useful # sub delivery_time_expired { shift->_dsn(shift,shift,4,7); } + +=head1 MAIL DELIVERY PROTOCOL STATUS + +=over 6 + +=item proto_unspecified + +X.5.0 +default: DENYSOFT + +=cut + +sub proto_unspecified { shift->_dsn(shift,shift,DENYSOFT,5,0); } + +=item proto_invalid_command + +X.5.1 +default: DENY + +=cut + +sub proto_invalid_command { shift->_dsn(shift,shift,DENY,5,1); } + +=item proto_syntax_error + +X.5.2 +default: DENY + +=cut + +sub proto_syntax_error { shift->_dsn(shift,shift,DENY,5,2); } + +=item proto_rcpt_list_too_long, too_many_rcpts + +X.5.3 +default: DENYSOFT + +=cut + +sub proto_rcpt_list_too_long { shift->_dsn(shift,shift,DENYSOFT,5,3); } +sub too_many_rcpts { shift->_dsn(shift,shift,DENYSOFT,5,3); } + +=item proto_invalid_cmd_args + +X.5.4 +default: DENY + +=cut + +sub proto_invalid_cmd_args { shift->_dsn(shift,shift,DENY,5,4); } + +=item proto_wrong_version + +X.5.5 +default: DENYSOFT + +=back + +=cut + +sub proto_wrong_version { shift->_dsn(shift,shift,DENYSOFT,5,5); } + +=head1 MESSAGE CONTENT OR MESSAGE MEDIA STATUS + +=over 5 + +=item media_unspecified + +X.6.0 +default: DENYSOFT + +=cut + +sub media_unspecified { shift->_dsn(shift,shift,DENYSOFT,6,0); } + +=item media_unsupported + +X.6.1 +default: DENY + +=cut + +sub media_unsupported { shift->_dsn(shift,shift,DENY,6,1); } + +=item media_conv_prohibited + +X.6.2 +default: DENY + +=cut + +sub media_conv_prohibited { shift->_dsn(shift,shift,DENY,6,2); } + +=item media_conv_unsupported + +X.6.3 +default: DENYSOFT + +=cut + +sub media_conv_unsupported { shift->_dsn(shift,shift,DENYSOFT,6,3); } + +=item media_conv_lossy + +X.6.4 +default: DENYSOFT + +=back + +=cut + +sub media_conv_lossy { shift->_dsn(shift,shift,DENYSOFT,6,4); } + +=head1 SECURITY OR POLICY STATUS + +=over 8 + +=item sec_unspecified + +X.7.0 +default: DENYSOFT + +=cut + +sub sec_unspecified { shift->_dsn(shift,shift,DENYSOFT,7,0); } + +=item sec_sender_unauthorized, bad_sender_ip, relaying_denied + +X.7.1 +default: DENY + +=cut + +sub sec_sender_unauthorized { shift->_dsn(shift,shift,DENY,7,1); } +sub bad_sender_ip { + shift->_dsn(shift,(shift || "Bad sender's IP"),DENY,7,1,); +} +sub relaying_denied { + shift->_dsn(shift,(shift || "Relaying denied"),DENY,7,1); +} + +=item sec_list_dest_prohibited + +X.7.2 +default: DENY + +=cut + +sub sec_list_dest_prohibited { shift->_dsn(shift,shift,DENY,7,2); } + +=item sec_conv_failed + +X.7.3 +default: DENY + +=cut + +sub sec_conv_failed { shift->_dsn(shift,shift,DENY,7,3); } + +=item sec_feature_unsupported + +X.7.4 +default: DENY + +=cut + +sub sec_feature_unsupported { shift->_dsn(shift,shift,DENY,7,4); } + +=item sec_crypto_failure + +X.7.5 +default: DENY + +=cut + +sub sec_crypto_failure { shift->_dsn(shift,shift,DENY,7,5); } + +=item sec_crypto_algorithm_unsupported + +X.7.6 +default: DENYSOFT + +=cut + +sub sec_crypto_algorithm_unsupported { shift->_dsn(shift,shift,DENYSOFT,7,6); } + +=item sec_msg_integrity_failure + +X.7.7 +default: DENY + +=back + +=cut + +sub sec_msg_integrity_failure { shift->_dsn(shift,shift,DENY,7,7); } + +1; + +# vim: st=4 sw=4 expandtab Modified: branches/0.3x/plugins/check_badrcptto ============================================================================== --- branches/0.3x/plugins/check_badrcptto (original) +++ branches/0.3x/plugins/check_badrcptto Tue Jan 24 18:59:31 2006 @@ -1,4 +1,5 @@ # this plugin checks the badrcptto config (like badmailfrom for rcpt address) +use Qpsmtpd::DSN; sub hook_rcpt { my ($self, $transaction, $recipient) = @_; @@ -9,9 +10,9 @@ sub hook_rcpt { for my $bad (@badrcptto) { $bad = lc $bad; $bad =~ s/^\s*(\S+)/$1/; - return (DENY, "mail to $bad not accepted here") + return Qpsmtpd::DSN->no_such_user("mail to $bad not accepted here") if $bad eq $from; - return (DENY, "mail to $bad not accepted here") + return Qpsmtpd::DSN->no_such_user("mail to $bad not accepted here") if substr($bad,0,1) eq '@' && $bad eq "\@$host"; } return (DECLINED); Modified: branches/0.3x/plugins/check_loop ============================================================================== --- branches/0.3x/plugins/check_loop (original) +++ branches/0.3x/plugins/check_loop Tue Jan 24 18:59:31 2006 @@ -25,8 +25,9 @@ Written by Keith C. Ivey Released to the public domain, 17 June 2005. =cut +use Qpsmtpd::DSN; -sub register { +sub init { my ($self, $qp, @args) = @_; $self->{_max_hops} = $args[0] || 100; @@ -45,7 +46,8 @@ sub hook_data_post { $transaction->header->get('Delivered-To'); if ( $hops >= $self->{_max_hops} ) { - return DENY, "Too many hops. This message is looping."; + # default of too_many_hops is DENY, see comment in POD of Qpsmtpd::DSN + return Qpsmtpd::DSN->too_many_hops(); } return DECLINED; Modified: branches/0.3x/plugins/rcpt_ok ============================================================================== --- branches/0.3x/plugins/rcpt_ok (original) +++ branches/0.3x/plugins/rcpt_ok Tue Jan 24 18:59:31 2006 @@ -2,6 +2,7 @@ # # It should be configured to be run _LAST_! # +use Qpsmtpd::DSN; sub hook_rcpt { my ($self, $transaction, $recipient) = @_; @@ -30,6 +31,8 @@ sub hook_rcpt { return (OK); } else { - return (DENY); + # default of relaying_denied is obviously DENY, + # we use the default "Relaying denied" message... + return Qpsmtpd::DSN->relaying_denied(); } } Modified: branches/0.3x/plugins/require_resolvable_fromhost ============================================================================== --- branches/0.3x/plugins/require_resolvable_fromhost (original) +++ branches/0.3x/plugins/require_resolvable_fromhost Tue Jan 24 18:59:31 2006 @@ -1,3 +1,4 @@ +use Qpsmtpd::DSN; use Net::DNS qw(mx); use Socket; @@ -17,15 +18,21 @@ sub hook_mail { } } - $sender->format ne "<>" - and $self->qp->config("require_resolvable_fromhost") - and !$self->check_dns($sender->host) - and return (DENYSOFT, - ($sender->host - ? "Could not resolve ". $sender->host - : "FQDN required in the envelope sender")); - - return DECLINED; + if ($sender ne "<>" + and $self->qp->config("require_resolvable_fromhost") + and !$self->check_dns($sender->host)) { + if ($sender->host) { + # default of temp_resolver_failed is DENYSOFT + return Qpsmtpd::DSN->temp_resolver_failed("Could not resolve " . $sender->host); + } + else { + # default of addr_bad_from_system is DENY, we use DENYSOFT here to + # get the same behaviour as without Qpsmtpd::DSN... + return Qpsmtpd::DSN->addr_bad_from_system(DENYSOFT, + "FQDN required in the envelope sender"); + } + } + return DECLINED; } Modified: branches/0.3x/plugins/spamassassin ============================================================================== --- branches/0.3x/plugins/spamassassin (original) +++ branches/0.3x/plugins/spamassassin Tue Jan 24 18:59:31 2006 @@ -71,6 +71,7 @@ Make the "subject munge string" configur =cut +use Qpsmtpd::DSN; use Socket qw(:DEFAULT :crlf); use IO::Handle; @@ -225,7 +226,8 @@ sub check_spam_reject { my $score = $self->get_spam_score($transaction) or return DECLINED; $self->log(LOGDEBUG, "check_spam_reject: score=$score"); - return (DENY, "spam score exceeded threshold") + # default of media_unsupported is DENY, so just change the message + return Qpsmtpd::DSN->media_unsupported("spam score exceeded threshold") if $score >= $self->{_args}->{reject_threshold}; $self->log(LOGDEBUG, "check_spam_reject: passed");