1# -*- indent-tabs-mode: nil; -*- 2# vim:ft=perl:et:sw=4 3# $Id$ 4 5# Sympa - SYsteme de Multi-Postage Automatique 6# 7# Copyright (c) 1997, 1998, 1999 Institut Pasteur & Christophe Wolfhugel 8# Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 9# 2006, 2007, 2008, 2009, 2010, 2011 Comite Reseau des Universites 10# Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017 GIP RENATER 11# 12# This program is free software; you can redistribute it and/or modify 13# it under the terms of the GNU General Public License as published by 14# the Free Software Foundation; either version 2 of the License, or 15# (at your option) any later version. 16# 17# This program is distributed in the hope that it will be useful, 18# but WITHOUT ANY WARRANTY; without even the implied warranty of 19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20# GNU General Public License for more details. 21# 22# You should have received a copy of the GNU General Public License 23# along with this program. If not, see <http://www.gnu.org/licenses/>. 24 25package Sympa::Spindle::DoForward; 26 27use strict; 28use warnings; 29 30use Sympa; 31use Conf; 32use Sympa::Log; 33use Sympa::Mailer; 34 35use base qw(Sympa::Spindle::ProcessIncoming); 36 37my $log = Sympa::Log->instance; 38 39# Old name: DoForward() in sympa_msg.pl. 40sub _twist { 41 my $self = shift; 42 my $message = shift; 43 44 # Fail-safe: Skip messages with unwanted types. 45 return 0 unless $self->_splicing_to($message) eq __PACKAGE__; 46 47 my ($name, $robot); 48 if (ref $message->{context} eq 'Sympa::List') { 49 $name = $message->{context}->{'name'}; 50 $robot = $message->{context}->{'domain'}; 51 } elsif ($message->{context} and $message->{context} ne '*') { 52 $name = 'sympa'; 53 $robot = $message->{context}; 54 } else { 55 $name = 'sympa'; 56 $robot = $Conf::Conf{'domain'}; 57 } 58 my $function = $message->{listtype}; 59 60 my $messageid = $message->{message_id}; 61 my $sender = $message->{sender}; 62 63 if ($message->{'spam_status'} eq 'spam') { 64 $log->syslog( 65 'notice', 66 'Message for %s-%s ignored, because tagued as spam (message ID: %s)', 67 $name, 68 $function, 69 $messageid 70 ); 71 return undef; 72 } 73 74 # Search for the list. 75 my ($list, $recipient, $priority); 76 77 if ($function eq 'listmaster') { 78 $recipient = Sympa::get_address($robot, 'listmaster'); 79 $priority = 0; 80 } else { 81 $list = $message->{context}; 82 unless (ref $list eq 'Sympa::List') { 83 $log->syslog( 84 'notice', 85 'Message for %s function %s ignored, unknown list %s (message ID: %s)', 86 $name, 87 $function, 88 $name, 89 $messageid 90 ); 91 Sympa::send_dsn($message->{context} || '*', $message, {}, 92 '5.1.1'); 93 return undef; 94 } 95 96 $recipient = Sympa::get_address($list, $function); 97 $priority = $list->{'admin'}{'priority'}; 98 } 99 100 my @rcpt; 101 102 $log->syslog('info', 103 'Processing %s; message_id=%s; priority=%s; recipient=%s', 104 $message, $messageid, $priority, $recipient); 105 106 delete $message->{'rcpt'}; 107 delete $message->{'family'}; 108 109 if ($function eq 'listmaster') { 110 @rcpt = Sympa::get_listmasters_email($robot); 111 $log->syslog('notice', 112 'No listmaster defined; incoming message is rejected') 113 unless @rcpt; 114 } elsif ($function eq 'owner') { # -request 115 @rcpt = $list->get_admins_email('receptive_owner'); 116 @rcpt = $list->get_admins_email('owner') unless @rcpt; 117 $log->syslog( 118 'notice', 119 'No owner defined at all in list %s; incoming message is rejected', 120 $name 121 ) unless @rcpt; 122 } elsif ($function eq 'editor') { 123 @rcpt = $list->get_admins_email('receptive_editor'); 124 @rcpt = $list->get_admins_email('actual_editor') unless @rcpt; 125 $log->syslog( 126 'notice', 127 'No owner and editor defined at all in list %s; incoming message is rejected', 128 $name 129 ) unless @rcpt; 130 } 131 132 # Did we find a recipient? 133 # If not, send back DSN to original sender to notify failure. 134 unless (@rcpt) { 135 Sympa::send_notify_to_listmaster( 136 $message->{context} || '*', 137 'mail_intern_error', 138 { error => sprintf( 139 'Impossible to forward a message to %s function %s : undefined in this list', 140 $name, $function 141 ), 142 who => $sender, 143 msg_id => $messageid, 144 entry => 'forward', 145 function => $function, 146 } 147 ); 148 Sympa::send_dsn( 149 $message->{context} || '*', $message, 150 {function => $function}, '5.2.4' 151 ); 152 $log->db_log( 153 'robot' => $robot, 154 'list' => $list->{'name'}, 155 'action' => 'DoForward', 156 'parameters' => "$name,$function", 157 'target_email' => '', 158 'msg_id' => $messageid, 159 'status' => 'error', 160 'error_type' => 'internal', 161 'user_email' => $sender 162 ); 163 return undef; 164 } 165 166 # Add or remove several headers to forward message safely. 167 # - Add X-Loop: field to mitigate mail looping. 168 # - The Sender: field should be added (overwritten) at least for Sender ID 169 # (a.k.a. SPF 2.0) compatibility. Note that Resent-Sender: field will 170 # be removed. 171 # - Apply DMARC protection if needed. 172 #FIXME: Existing DKIM signature depends on these headers will be broken. 173 #FIXME: Currently messages via -request and -editor addresses will be 174 # protected against DMARC if neccessary. The listmaster address 175 # would be protected, too. 176 $message->add_header('X-Loop', $recipient); 177 $message->replace_header('Sender', Sympa::get_address($robot, 'owner')); 178 $message->delete_header('Resent-Sender'); 179 if ($function eq 'owner' or $function eq 'editor') { 180 $message->dmarc_protect if $list; 181 } 182 183 # Overwrite envelope sender. It is REQUIRED for delivery. 184 $message->{envelope_sender} = Sympa::get_address($robot, 'owner'); 185 186 unless (defined Sympa::Mailer->instance->store($message, \@rcpt)) { 187 $log->syslog('err', 'Impossible to forward mail for %s function %s', 188 $name, $function); 189 Sympa::send_notify_to_listmaster( 190 $message->{context} || '*', 191 'mail_intern_error', 192 { error => sprintf( 193 'Impossible to forward a message for %s function %s', 194 $name, $function 195 ), 196 who => $sender, 197 msg_id => $messageid, 198 entry => 'forward', 199 function => $function, 200 } 201 ); 202 Sympa::send_dsn($message->{context} || '*', $message, {}, '5.3.0'); 203 $log->db_log( 204 'robot' => $robot, 205 'list' => $list->{'name'}, 206 'action' => 'DoForward', 207 'parameters' => "$name,$function", 208 'target_email' => '', 209 'msg_id' => $messageid, 210 'status' => 'error', 211 'error_type' => 'internal', 212 'user_email' => $sender 213 ); 214 return undef; 215 } 216 $log->db_log( 217 'robot' => $robot, 218 'list' => $list->{'name'}, 219 'action' => 'DoForward', 220 'parameters' => "$name,$function", 221 'target_email' => '', 222 'msg_id' => $messageid, 223 'status' => 'success', 224 'error_type' => '', 225 'user_email' => $sender 226 ); 227 228 return 1; 229} 230 2311; 232__END__ 233 234=encoding utf-8 235 236=head1 NAME 237 238Sympa::Spindle::DoForward - Workflow to forward messages to administrators 239 240=head1 DESCRIPTION 241 242L<Sympa::Spindle::DoForward> handles a message sent to [list]-editor (the list 243editor), [list]-request (the list owner) or the listmaster. 244 245If a message has one of types above, message will be forwarded to the users 246according to types using mailer directly (See L<Sympa::Mailer>). 247Otherwise messages will be skipped. 248 249=head2 Public methods 250 251See also L<Sympa::Spindle::ProcessIncoming/"Public methods">. 252 253=over 254 255=item new ( key =E<gt> value, ... ) 256 257=item spin ( ) 258 259In most cases, L<Sympa::Spindle::ProcessIncoming> splices messages 260to this class. These methods are not used in ordinal case. 261 262=back 263 264=head1 SEE ALSO 265 266L<Sympa::Mailer>, L<Sympa::Message>, L<Sympa::Spindle::ProcessIncoming>. 267 268=head1 HISTORY 269 270L<Sympa::Spindle::DoForward> appeared on Sympa 6.2.13. 271 272=cut 273