#!perl -w

=head1 SYNOPSIS

deny connections to recipients in the I<badrcptto> file

like badmailfrom, but for recipient address rather than sender

=head1 CONFIG

Recipients are matched against entries in I<config/badrcptto>. Entries can be
a complete email address, a host entry that starts with an @ symbol, or a
regular expression. For regexp pattern matches, see PATTERNS.

=head1 PATTERNS

This allows special patterns to be denied (e.g. percent hack, bangs,
double ats).

Patterns are stored in the format pattern\sresponse, where pattern
is a Perl pattern expression. Don't forget to anchor the pattern if
you want to restrict it from matching anywhere in the string.

qpsmtpd already ensures that the address contains an @, with something
to the left and right of the @.

=head1 AUTHOR

2002 - original badrcptto plugin - apparently Jim Winstead
       https://github.com/smtpd/qpsmtpd/commits/master/plugins/check_badrcptto

2005 - pattern feature, (c) Gordon Rowell <gordonr@gormand.com.au>

2012 - merged the two, refactored, added tests - Matt Simerson

=head1 LICENSE

This software is free software and may be distributed under the same
terms as qpsmtpd itself.

=cut

use strict;
use warnings;

use Qpsmtpd::Constants;
use Qpsmtpd::DSN;

sub hook_rcpt {
    my ($self, $transaction, $recipient, %param) = @_;

    return (DECLINED) if $self->is_immune();

    my ($host, $to) = $self->get_host_and_to($recipient)
      or return (DECLINED);

    my @badrcptto = $self->qp->config("badrcptto") or do {
        $self->log(LOGINFO, "skip, empty config");
        return (DECLINED);
    };

    for my $line (@badrcptto) {
        $line =~ s/^\s+//g;    # trim leading whitespace
        my ($bad, $reason) = split /\s+/, $line, 2;
        next if !$bad;
        if ($self->is_match($to, lc($bad), $host)) {
            $self->adjust_karma(-2);
            if ($reason) {
                return (DENY, "mail to $bad not accepted here");
            }
            else {
                return Qpsmtpd::DSN->no_such_user(
                                              "mail to $bad not accepted here");
            }
        }
    }
    $self->log(LOGINFO, 'pass');
    return (DECLINED);
}

sub is_match {
    my ($self, $to, $bad, $host) = @_;

    if ($bad =~ /[\/\^\$\*\+\!\%\?\\]/) {    # it's a regexp
        $self->log(LOGDEBUG, "badmailfrom pattern ($bad) match for $to");
        if ($to =~ /$bad/i) {
            $self->log(LOGINFO, 'fail: pattern match');
            return 1;
        }
        return;
    }

    if ($bad !~ m/\@/) {
        $self->log(LOGERROR, "badrcptto: bad config: no \@ sign in $bad");
        return;
    }

    $bad = lc $bad;
    $to  = lc $to;

    if (substr($bad, 0, 1) eq '@') {
        if ($bad eq "\@$host") {
            $self->log(LOGINFO, 'fail: host match');
            return 1;
        }
        return;
    }

    if ($bad eq $to) {
        $self->log(LOGINFO, 'fail: rcpt match');
        return 1;
    }
    return;
}

sub get_host_and_to {
    my ($self, $recipient) = @_;

    if (!$recipient) {
        $self->log(LOGERROR, 'skip: no recipient!');
        return;
    }

    if (!$recipient->host || !$recipient->user) {
        $self->log(LOGINFO, 'skip: missing host or user');
        return;
    }

    my $host = lc $recipient->host;
    return ($host, lc($recipient->user) . '@' . $host);
}