Ok, here is it. I tried to make a very complete description here; hope you like it.
-- Robert Millan
diff -Nur qpsmtpd-0.32.old/config.sample/plugins qpsmtpd-0.32/config.sample/plugins --- qpsmtpd-0.32.old/config.sample/plugins 2006-02-26 13:22:16.000000000 +0100 +++ qpsmtpd-0.32/config.sample/plugins 2006-06-19 19:41:30.737265480 +0200 @@ -26,6 +26,8 @@ check_badrcptto check_spamhelo +# redirect_local + # sender_permitted_from # this plugin needs to run after all other "rcpt" plugins diff -Nur qpsmtpd-0.32.old/lib/Qpsmtpd/Constants.pm qpsmtpd-0.32/lib/Qpsmtpd/Constants.pm --- qpsmtpd-0.32.old/lib/Qpsmtpd/Constants.pm 2006-02-26 13:22:16.000000000 +0100 +++ qpsmtpd-0.32/lib/Qpsmtpd/Constants.pm 2006-06-19 19:24:13.873892672 +0200 @@ -23,6 +23,7 @@ DENYHARD => 903, # 550 + disconnect (deprecated in 0.29) DENY_DISCONNECT => 903, # 550 + disconnect DENYSOFT_DISCONNECT => 904, # 450 + disconnect + REDIRECT => 905, # 551 DECLINED => 909, DONE => 910, ); diff -Nur qpsmtpd-0.32.old/lib/Qpsmtpd/DSN.pm qpsmtpd-0.32/lib/Qpsmtpd/DSN.pm --- qpsmtpd-0.32.old/lib/Qpsmtpd/DSN.pm 2006-02-26 13:22:16.000000000 +0100 +++ qpsmtpd-0.32/lib/Qpsmtpd/DSN.pm 2006-06-19 19:24:13.873892672 +0200 @@ -172,7 +172,7 @@ sub addr_unspecified { shift->_dsn(shift,shift,DENYSOFT,1,0); } -=item no_such_user, addr_bad_dest_mbox +=item no_such_user, addr_bad_dest_mbox, user_not_local X.1.1 default: DENY @@ -181,6 +181,7 @@ 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); } +sub user_not_local { shift->_dsn(shift,shift,REDIRECT,1,1); } =item addr_bad_dest_system diff -Nur qpsmtpd-0.32.old/lib/Qpsmtpd/SMTP.pm qpsmtpd-0.32/lib/Qpsmtpd/SMTP.pm --- qpsmtpd-0.32.old/lib/Qpsmtpd/SMTP.pm 2006-02-26 13:22:16.000000000 +0100 +++ qpsmtpd-0.32/lib/Qpsmtpd/SMTP.pm 2006-06-19 19:24:13.874892520 +0200 @@ -355,6 +355,9 @@ $self->respond(421, $msg); $self->disconnect; } + elsif ($rc == REDIRECT) { + $self->respond(551, $msg); + } elsif ($rc == OK) { $self->respond(250, $rcpt->format . ", recipient ok"); return $self->transaction->add_recipient($rcpt); diff -Nur qpsmtpd-0.32.old/plugins/redirect_local qpsmtpd-0.32/plugins/redirect_local --- qpsmtpd-0.32.old/plugins/redirect_local 1970-01-01 01:00:00.000000000 +0100 +++ qpsmtpd-0.32/plugins/redirect_local 2006-06-19 19:40:02.984605912 +0200 @@ -0,0 +1,41 @@ +# This plugin checks for ~/.redirect. If it exists, it uses the recipient +# address contained there to inform the sender that this user is actualy +# somewhere else. This is specialy useful as an alternative for traditional +# forwarding because: +# +# - It doesn't break when the sender has published SPF records. +# - It gives the final recipient's MTA a chance to perform its own delivery-time +# anti-spam checks (greylisting, spf, etc), in addition to whatever checks we +# have (or have not) performed on our side. +# +# Note that ATTOW most senders will not automaticaly redirect their mail to the +# final recipient. In that case, a sane MTA would generate an MDN, informing +# the user of the new recipient address. RFC 2821 says that you must not assume +# this will actualy happen, though, so you do it on your own responsability. +# +# From RFC 2821, section 3.4: +# +# * Servers MAY reject or bounce messages when they are not +# deliverable when addressed. When they do so, they MAY either +# provide address-updating information with a 551 code, or may +# reject the message as undeliverable with a 550 code and no +# address-specific information. But, if a 551 code is used, they +# MUST NOT assume that the client will actually update address +# information or even return that information to the user. + +use Qpsmtpd::DSN; +use File::HomeDir; + +sub hook_rcpt { + my ($self, $transaction, $recipient) = @_; + + # Check for ~/.redirect + if (open (REDIR, home(lc($recipient->user)) . "/.redirect")) { + my ($redir) = <REDIR>; + close(REDIR); + chomp ($redir); + return Qpsmtpd::DSN->user_not_local("User not local; please try <$redir>"); + } + + return (OK); +}