=head1 NAME bcc =head1 DESCRIPTION Forwards a copy of all mail (or just incoming or outgoing mail) to the given email address (useful for archiving etc.). bcc addresses may be passed as parameters to bcc, or may be defined in a 'bcc' config file. bcc also checks config files 'bcc_ignore_mailfrom' and 'bcc_ignore_rcptto' for exceptions - these contain lists of email addresses bcc should ignore as senders or recipients. They also support bare username and '@domain' entries as wildcards. bcc will use per_recipient configs if passed a true 'per_recipient' argument, but it cannot handle multiple different configs for different recipients (so use denysoft_multi or similar to prevent those). bcc should generally be called before the 'check_relay' plugin, since incoming processing uses a RCPT hook. =head1 CONFIG The following parameters can be passed to bcc, or set via a 'bcc' config file. =over 4 =item mode [ bcc | cc | off ] bcc mode. Defaults to 'bcc' if any of the all/incoming/outgoing parameters are set, and 'off' otherwise. 'cc' mode is identical to 'bcc' mode, except that an 'X-Copied-To' header is added to the mail headers for each all/incoming/outgoing recipient. =item all Send a copy of all email to the given address. =item incoming Send a copy of all incoming email to the given address. Mail is considered incoming if it is going to a recipient with a domain in 'rcpthosts'. =item outgoing Send a copy of all outgoing email to the given address. Mail is considered outgoing if it is sent from a relay client. =back The following parameter can be passed to bcc (but not set via a config file): =over 4 =item per_recipient 1 Allow per-recipient configs to be used (using the per_user_config plugin). =back =head1 BUGS Note that the definitions of incoming and outgoing mail mean that relayed mail to a local domain is considered both incoming and outgoing, and will be copied twice. This is a feature, not a bug. :-) =head1 AUTHOR Written by Gavin Carr . =cut use Mail::Address; my $VERSION = 0.05; sub register { my ($self, $qp, %arg) = @_; $self->{_bcc_mode} = $arg{mode}; $self->{_bcc_all} = $arg{all}; $self->{_bcc_incoming} = $arg{incoming}; $self->{_bcc_outgoing} = $arg{outgoing}; $self->{_bcc_per_recipient} = $arg{per_recipient}; if ($self->{_bcc_per_recipient}) { $self->register_hook("rcpt", "check_sender"); $self->register_hook("rcpt", "check_rcpt"); $self->register_hook("data_post", "bcc"); } else { $self->load_bcc_config; unless ($self->{_bcc_mode} eq 'off') { $self->register_hook("mail", "check_sender"); $self->register_hook("rcpt", "check_rcpt"); $self->register_hook("data_post", "bcc"); } } } sub load_bcc_config { my ($self, $rcpt) = @_; my $arg = $rcpt ? { user => $rcpt } : {}; for ($self->qp->config("bcc", $arg)) { my ($key, $value) = split /\s+/, $_, 2; next unless $key =~ m/(mode|all|incoming|outgoing)/; $self->{"_bcc_$1"} = $value; } # Default bcc mode $self->{_bcc_mode} ||= $self->{_bcc_all} || $self->{_bcc_incoming} || $self->{_bcc_outgoing} ? 'bcc' : 'off'; } sub check_sender { my ($self, $transaction, $sender) = @_; my $rcpt; if ($self->{_bcc_per_recipient}) { $rcpt = $sender; $sender = $transaction->sender; } my $ignore = 0; my $config_arg = $rcpt ? { user => $rcpt } : {}; for ($self->qp->config("bcc_ignore_mailfrom", $config_arg)) { $_ = lc; $ignore = 1,last if $_ eq lc $sender->address; $ignore = 1,last if substr($_,0,1) eq '@' && substr($_,1) eq lc $sender->host; $ignore = 1,last if index($_,'@') < 0 && $_ eq lc $sender->user; } $transaction->notes("bcc_ignore",1) if $ignore; return (DECLINED); } sub check_rcpt { my ($self, $transaction, $rcpt) = @_; # Only copy once for multiple recipients return (DECLINED) if $transaction->notes("bcc_ignore"); return (DECLINED) if $transaction->notes('bcc_incoming'); my $ignore = 0; my $config_arg = $rcpt ? { rcpt => $rcpt } : {}; for ($self->qp->config("bcc_ignore_rcptto", $config_arg)) { $_ = lc; $ignore = 1,last if $_ eq lc $rcpt->address; $ignore = 1,last if substr($_,0,1) eq '@' && substr($_,1) eq lc $rcpt->host; $ignore = 1,last if index($_,'@') < 0 && $_ eq lc $rcpt->user; } if ($ignore) { my $ignore_rcpt_cnt = $transaction->notes("bcc_ignore_rcpt"); $transaction->notes("bcc_ignore_rcpt",$ignore_rcpt_cnt+1); return (DECLINED); } # Load per-recipient config, if per_recipient if ($self->{_bcc_per_recipient}) { $self->load_bcc_config($rcpt); return (DECLINED) if $self->{_bcc_mode} eq 'off'; } # For 'incoming', at least one recipient must be on a rcpthosts domain return (DECLINED) unless $self->{_bcc_incoming}; my $host = lc $rcpt->host; my @rcpthosts = ($self->qp->config("me"),$self->qp->config("rcpthosts")); for my $allowed (@rcpthosts) { ($allowed) =~ s/^\s*(\S+)/\L$1\E/; if ($host eq $allowed || (substr($allowed,0,1) eq '.' && $host =~ m/\Q$allowed\E$/)) { $transaction->notes('bcc_incoming',1); last; } } return (DECLINED); } # Actual copying is deferred to data_post so as not to mess up the rcpt list sub bcc { my ($self, $transaction) = @_; return (DECLINED) if $self->{_bcc_mode} eq 'off'; return (DECLINED) if $transaction->notes("bcc_ignore"); return (DECLINED) unless ($transaction->notes("bcc_ignore_rcpt") || 0) < $transaction->recipients; if ($self->{_bcc_all}) { my $rcpt = (Mail::Address->parse($self->{_bcc_all}))[0]; $transaction->add_recipient($rcpt); $transaction->header->add('X-Copied-To', $self->{_bcc_all}) if $self->{_bcc_mode} eq 'cc'; $self->log(3,"message copied to " . $self->{_bcc_all}); } if ($self->{_bcc_outgoing} && $self->qp->connection->relay_client) { my $rcpt = (Mail::Address->parse($self->{_bcc_outgoing}))[0]; $transaction->add_recipient($rcpt); $transaction->header->add('X-Copied-To', $self->{_bcc_outgoing}) if $self->{_bcc_mode} eq 'cc'; $self->log(3,"outgoing message copied to " . $self->{_bcc_outgoing}); } if ($transaction->notes('bcc_incoming')) { my $rcpt = (Mail::Address->parse($self->{_bcc_incoming}))[0]; $transaction->add_recipient($rcpt); $transaction->header->add('X-Copied-To', $self->{_bcc_incoming}) if $self->{_bcc_mode} eq 'cc'; $self->log(3,"incoming message copied to " . $self->{_bcc_incoming}); } return (DECLINED); } # arch-tag: a1f2db91-5652-4040-9a4e-1747b7fbe4d8