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# Copyright 2018, 2019 The Sympa Community. See the AUTHORS.md file at 12# the top-level directory of this distribution and at 13# <https://github.com/sympa-community/sympa.git>. 14# 15# This program is free software; you can redistribute it and/or modify 16# it under the terms of the GNU General Public License as published by 17# the Free Software Foundation; either version 2 of the License, or 18# (at your option) any later version. 19# 20# This program is distributed in the hope that it will be useful, 21# but WITHOUT ANY WARRANTY; without even the implied warranty of 22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23# GNU General Public License for more details. 24# 25# You should have received a copy of the GNU General Public License 26# along with this program. If not, see <http://www.gnu.org/licenses/>. 27 28package Sympa::Spindle::AuthorizeMessage; 29 30use strict; 31use warnings; 32 33use Sympa; 34use Sympa::List; 35use Sympa::Log; 36use Sympa::Scenario; 37use Sympa::Spool::Topic; 38use Sympa::Tools::Data; 39 40use base qw(Sympa::Spindle); 41 42my $log = Sympa::Log->instance; 43 44# Old name: (part of) DoMessage() in sympa_msg.pl. 45sub _twist { 46 my $self = shift; 47 my $message = shift; 48 49 my $list = $message->{context}; 50 my $messageid = $message->{message_id}; 51 52 # Now check if the sender is an authorized address. 53 my $sender = $self->{confirmed_by} || $message->{sender}; 54 55 my $context = { 56 'sender' => $sender, 57 'message' => $message 58 }; 59 60 # List msg topic. 61 if (not $self->{confirmed_by} # Not in ProcessHeld spindle. 62 and $list->is_there_msg_topic 63 ) { 64 my $topic; 65 if ($topic = Sympa::Spool::Topic->load($message)) { 66 # Is message already tagged? 67 ; 68 } elsif ($topic = 69 Sympa::Spool::Topic->load($message, in_reply_to => 1)) { 70 # Is message in-reply-to already tagged? 71 $topic = Sympa::Spool::Topic->new( 72 topic => $topic->{topic}, 73 method => 'auto' 74 ); 75 $topic->store($message); 76 } elsif (my $topic_list = $message->compute_topic) { 77 # Not already tagged. 78 $topic = Sympa::Spool::Topic->new( 79 topic => $topic_list, 80 method => 'auto' 81 ); 82 $topic->store($message); 83 } 84 85 if ($topic) { 86 $context->{'topic'} = $context->{'topic_' . $topic->{method}} = 87 $topic->{topic}; 88 } 89 $context->{'topic_needed'} = 90 (!$context->{'topic'} && $list->is_msg_topic_tagging_required); 91 } 92 93 # Call scenario: auth_method MD5 do not have any sense in "send" 94 # scenario because auth is performed by distribute or reject command. 95 96 my $action; 97 my $result; 98 99 # The order of the following 3 lines is important! SMIME > DKIM > SMTP. 100 my $auth_method = 101 $message->{'smime_signed'} ? 'smime' 102 : $message->{'md5_check'} ? 'md5' 103 : $message->{'dkim_pass'} ? 'dkim' 104 : 'smtp'; 105 106 $result = 107 Sympa::Scenario->new($list, 'send')->authz($auth_method, $context); 108 $action = $result->{'action'} if (ref($result) eq 'HASH'); 109 110 unless (defined $action) { 111 $log->syslog( 112 'err', 113 'Message %s ignored because unable to evaluate scenario "send" for list %s', 114 $message, 115 $list 116 ); 117 Sympa::send_notify_to_listmaster( 118 $list, 119 'mail_intern_error', 120 { error => 121 'Message ignored because scenario "send" cannot be evaluated', 122 who => $sender, 123 msg_id => $messageid, 124 } 125 ); 126 Sympa::send_dsn($list, $message, {}, '5.3.0'); 127 $log->db_log( 128 'robot' => $list->{'domain'}, 129 'list' => $list->{'name'}, 130 'action' => 'DoMessage', 131 'parameters' => $message->get_id, 132 'target_email' => '', 133 'msg_id' => $messageid, 134 'status' => 'error', 135 'error_type' => 'internal', 136 'user_email' => $sender 137 ); 138 return undef; 139 } 140 141 # Message topic context. 142 if ($action =~ /^do_it\b/ and $context->{'topic_needed'}) { 143 if ($list->{'admin'}{'msg_topic_tagging'} eq 'required_sender') { 144 $action = 'request_auth'; 145 } elsif ( 146 $list->{'admin'}{'msg_topic_tagging'} eq 'required_moderator') { 147 $action = 'editorkey'; 148 } 149 } 150 151 # Check TT2 syntax for personalization feature. 152 if ($action !~ /\Areject\b/ 153 and not $self->{confirmed_by} # Not in ProcessHeld spindle. 154 and $message->{shelved}{merge} 155 and $message->{shelved}{merge} ne 'footer' # 'all' or '1'(<=6.2.58) 156 and not _test_personalize($message, $list) 157 ) { 158 $log->syslog('err', 159 'Failed to personalize. Message %s for list %s was rejected', 160 $message, $list); 161 Sympa::send_dsn($list, $message, {}, '5.6.5'); 162 return undef; 163 } 164 165 if ($action =~ /^do_it\b/) { 166 $self->{quiet} ||= ($action =~ /,\s*quiet\b/); # Overwrite. 167 168 unless ($self->{confirmed_by}) { # Not in ProcessHeld spindle. 169 $message->{shelved}{dkim_sign} = 1 170 if Sympa::Tools::Data::is_in_array( 171 $list->{'admin'}{'dkim_signature_apply_on'}, 'any') 172 or ( 173 Sympa::Tools::Data::is_in_array( 174 $list->{'admin'}{'dkim_signature_apply_on'}, 175 'smime_authenticated_messages') 176 and $message->{'smime_signed'} 177 ) 178 or ( 179 Sympa::Tools::Data::is_in_array( 180 $list->{'admin'}{'dkim_signature_apply_on'}, 181 'dkim_authenticated_messages') 182 and $message->{'dkim_pass'} 183 ); 184 } else { 185 $message->add_header('X-Validation-by', $self->{confirmed_by}); 186 187 $message->{shelved}{dkim_sign} = 1 188 if Sympa::Tools::Data::is_in_array( 189 $list->{'admin'}{'dkim_signature_apply_on'}, 'any') 190 or Sympa::Tools::Data::is_in_array( 191 $list->{'admin'}{'dkim_signature_apply_on'}, 192 'md5_authenticated_messages'); 193 } 194 195 # Keep track of known message IDs...if any. 196 $self->{_msgid}{$list->get_id}{$messageid} = time 197 unless $self->{confirmed_by}; 198 199 return ['Sympa::Spindle::DistributeMessage']; 200 } elsif ( 201 not $self->{confirmed_by} # Not in ProcessHeld spindle. 202 and $action =~ /^request_auth\b/ 203 ) { 204 return ['Sympa::Spindle::ToHeld']; 205 } elsif ($action =~ /^editorkey\b/) { 206 $self->{quiet} ||= ($action =~ /,\s*quiet\b/); # Overwrite 207 208 return ['Sympa::Spindle::ToModeration']; 209 } elsif ($action =~ /^editor\b/) { 210 $self->{quiet} ||= ($action =~ /,\s*quiet\b/); # Overwrite 211 212 return ['Sympa::Spindle::ToEditor']; 213 } elsif ($action =~ /^reject\b/) { 214 my $quiet = $self->{quiet} || ($action =~ /,\s*quiet\b/); 215 216 $log->syslog( 217 'notice', 218 'Message %s for %s from %s rejected(%s) because sender not allowed', 219 $message, 220 $list, 221 $sender, 222 $result->{'tt2'} 223 ); 224 225 # Do not report to the sender if the message was tagged as a spam. 226 unless ($quiet or $message->{'spam_status'} eq 'spam') { 227 if (defined $result->{'tt2'}) { 228 unless ( 229 Sympa::send_file( 230 $list, $result->{'tt2'}, 231 $sender, {auto_submitted => 'auto-replied'} 232 ) 233 ) { 234 $log->syslog('notice', 235 'Unable to send template "%s" to %s', 236 $result->{'tt2'}, $sender); 237 Sympa::send_dsn($list, $message, 238 {reason => $result->{'reason'}}, '5.7.1'); 239 } 240 } else { 241 Sympa::send_dsn($list, $message, 242 {reason => $result->{'reason'}}, '5.7.1'); 243 } 244 } 245 $log->db_log( 246 'robot' => $list->{'domain'}, 247 'list' => $list->{'name'}, 248 'action' => 'DoMessage', 249 'parameters' => $message->get_id, 250 'target_email' => '', 251 'msg_id' => $messageid, 252 'status' => 'error', 253 'error_type' => 'rejected_authorization', 254 'user_email' => $sender 255 ); 256 return undef; 257 } else { 258 $log->syslog('err', 259 'Unknown action %s returned by the scenario "send"', $action); 260 Sympa::send_notify_to_listmaster( 261 $list, 262 'mail_intern_error', 263 { error => 'Unknown action returned by the scenario "send"', 264 who => $sender, 265 msg_id => $messageid, 266 } 267 ); 268 Sympa::send_dsn($list, $message, {}, '5.3.0'); 269 $log->db_log( 270 'robot' => $list->{'domain'}, 271 'list' => $list->{'name'}, 272 'action' => 'DoMessage', 273 'parameters' => $message->get_id, 274 'target_email' => '', 275 'msg_id' => $messageid, 276 'status' => 'error', 277 'error_type' => 'internal', 278 'user_email' => $sender 279 ); 280 return undef; 281 } 282} 283 284# Private subroutine. 285 286# Tests if personalization can be performed successfully over all subscribers 287# of list. 288# Returns: 1 if succeed, or undef. 289# Old name: Sympa::Message::test_personalize(). 290sub _test_personalize { 291 my $message = shift; 292 my $list = shift; 293 294 # Get available recipients to test. 295 my $available_recipients = $list->get_recipients_per_mode($message) || {}; 296 # Always test all available reception modes using sender. 297 foreach my $mode ('mail', 298 grep { $_ and $_ ne 'nomail' and $_ ne 'not_me' } 299 @{$list->{'admin'}{'available_user_options'}->{'reception'} || []}) { 300 push @{$available_recipients->{$mode}{'verp'}}, $message->{'sender'}; 301 } 302 303 foreach my $mode (sort keys %$available_recipients) { 304 my $new_message = $message->dup; 305 $new_message->prepare_message_according_to_mode($mode, $list); 306 307 foreach my $rcpt ( 308 @{$available_recipients->{$mode}{'verp'} || []}, 309 @{$available_recipients->{$mode}{'noverp'} || []} 310 ) { 311 unless ($new_message->personalize($list, $rcpt)) { 312 return undef; 313 } 314 } 315 } 316 return 1; 317} 318 3191; 320__END__ 321 322=encoding utf-8 323 324=head1 NAME 325 326Sympa::Spindle::AuthorizeMessage - 327Workflow to authorize messages bound for lists 328 329=head1 DESCRIPTION 330 331L<Sympa::Spindle::AuthorizeMessage> authorizes messages and stores them 332into confirmation spool, moderation spool or the lists. 333 334=over 335 336=item * 337 338Messages fetched from incoming (C<msg>) spool or held (C<auth>) spool may be 339passed to this class (messages fetched from moderation spool won't be passed 340to this class). 341 342=item * 343 344Then this class checks the message with C<send> scenario. 345 346=item * 347 348According to the results of scenario processing, each message is passed 349to any of classes for succeeding processing: 350L<Sympa::Spindle::DistributeMessage> for C<do_it> (except if tagging topics 351is required or when personalization failed); 352L<Sympa::Spindle::ToHeld> for C<request_auth> (except if personalization 353failed); 354L<Sympa::Spindle::ToModeration> for C<editorkey> (except if personalization 355failed); 356L<Sympa::Spindle::ToEditor> for C<editor>; 357otherwise reject it. 358 359=back 360 361If the message was confirmed, i.e. it has been fetched from held spool and 362at last decided to be distributed, C<X-Validation-By> header field is added. 363If the message at last will be distributed, C<{shelved}> attribute (see 364L<Sympa::Message>) is added as necessity. 365 366=head2 Public methods 367 368See also L<Sympa::Spindle/"Public methods">. 369 370=over 371 372=item new ( key =E<gt> value, ... ) 373 374In most cases, L<Sympa::Spindle::DoMessage> 375splices messages to this class. This method is not used in ordinal case. 376 377=item spin ( ) 378 379Not implemented. 380 381=back 382 383=head1 SEE ALSO 384 385L<Sympa::Internals::Workflow>. 386 387L<Sympa::Message>, L<Sympa::Scenario>, L<Sympa::Spindle::DistributeMessage>, 388L<Sympa::Spindle::DoMessage>, L<Sympa::Spindle::ProcessHeld>, 389L<Sympa::Spindle::ToEditor>, L<Sympa::Spindle::ToHeld>, 390L<Sympa::Spindle::ToModeration>, 391L<Sympa::Spool::Topic>. 392 393=head1 HISTORY 394 395L<Sympa::Spindle::AuthorizeMessage> appeared on Sympa 6.2.13. 396 397=cut 398