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