Server IP : 85.214.239.14 / Your IP : 3.149.239.70 Web Server : Apache/2.4.62 (Debian) System : Linux h2886529.stratoserver.net 4.9.0 #1 SMP Tue Jan 9 19:45:01 MSK 2024 x86_64 User : www-data ( 33) PHP Version : 7.4.18 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare, MySQL : OFF | cURL : OFF | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : OFF Directory : /usr/share/perl5/Amavis/Out/ |
Upload File : |
# SPDX-License-Identifier: GPL-2.0-or-later package Amavis::Out::Pipe; use strict; use re 'taint'; use warnings; use warnings FATAL => qw(utf8 void); no warnings 'uninitialized'; # use warnings 'extra'; no warnings 'experimental::re_strict'; use re 'strict'; BEGIN { require Exporter; use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION); $VERSION = '2.412'; @ISA = qw(Exporter); @EXPORT = qw(&mail_via_pipe); } use Errno qw(ENOENT EACCES ESRCH); use POSIX qw(WIFEXITED WIFSIGNALED WIFSTOPPED WEXITSTATUS WTERMSIG WSTOPSIG); use Amavis::Conf qw(:platform c cr ca); use Amavis::Out::EditHeader; use Amavis::ProcControl qw(exit_status_str proc_status_ok kill_proc run_command_consumer); use Amavis::rfc2821_2822_Tools; use Amavis::Timing qw(section_time); use Amavis::Util qw(untaint min max minmax ll do_log snmp_count collect_equal_delivery_recips); # Send mail using external mail submission program 'sendmail' or its lookalike # (also available with Postfix and Exim) - used for forwarding original mail # or sending notifications or quarantining. May throw exception (die) on # temporary failure (4xx) or other problem. # sub mail_via_pipe(@) { my($msginfo, $initial_submission, $dsn_per_recip_capable, $filter) = @_; my(@snmp_vars) = !$initial_submission ? ('', 'Relay', 'ProtoPipe', 'ProtoPipeRelay') : ('', 'Submit', 'ProtoPipe', 'ProtoPipeSubmit', 'Submit'.$initial_submission); snmp_count('OutMsgs'.$_) for @snmp_vars; my $id = $msginfo->parent_mail_id; $id = $msginfo->mail_id . (defined $id ? "($id)" : ""); my $logmsg = sprintf("%s %s via PIPE from %s", $id, ($initial_submission ? 'SEND' : 'FWD'), $msginfo->sender_smtp); my($per_recip_data_ref, $proto_sockname) = collect_equal_delivery_recips($msginfo, $filter, qr/^pipe:/i); if (!$per_recip_data_ref || !@$per_recip_data_ref) { do_log(5, "%s, nothing to do", $logmsg); return 1; } $proto_sockname = $proto_sockname->[0] if ref $proto_sockname; ll(1) && do_log(1, "delivering to %s, %s -> %s", $proto_sockname, $logmsg, join(',', qquote_rfc2821_local( map($_->recip_final_addr, @$per_recip_data_ref)) )); # just use the first one, ignoring failover alternatives local($1); $proto_sockname =~ /^pipe:(.*)\z/si or die "Bad fwd method syntax: ".$proto_sockname; my $pipe_args = $1; $pipe_args =~ s/^flags=\S*\s*//i; # flags are currently ignored, q implied $pipe_args =~ s/^argv=//i; my(@pipe_args) = split(' ',$pipe_args); my(@command) = shift(@pipe_args); my $dsn_capable = c('propagate_dsn_if_possible'); # assume, unless disabled $dsn_capable = 0 if $command[0] !~ /sendmail/; # a hack, don't use -N or -V if ($dsn_capable) { # DSN is supported since Postfix 2.3 # notify options are per-recipient, yet a command option -N applies to all my $common_list; my $not_all_the_same = 0; for my $r (@{$msginfo->per_recip_data}) { my $dsn_notify = $r->dsn_notify; my $d; if ($msginfo->sender eq '') { $d = 'NEVER'; } elsif (!$dsn_notify) { $d = 'DELAY,FAILURE'; # sorted } else { $d = uc(join(',', sort @$dsn_notify)); # normalize order } if (!defined($common_list)) { $common_list = $d } elsif ($d ne $common_list) { $not_all_the_same = 1 } } if ($common_list=~/\bSUCCESS\b/ && c('terminate_dsn_on_notify_success')) { # strip out option SUCCESS, we want to handle it locally my(@dsn_notify) = grep($_ ne 'SUCCESS', split(/,/,$common_list)); @dsn_notify = ('NEVER') if !@dsn_notify; $common_list = join(',',@dsn_notify); do_log(3,"stripped out SUCCESS, result: NOTIFY=%s", $common_list); } if ($not_all_the_same || $msginfo->sender eq '') {} # leave at default elsif ($common_list eq 'DELAY,FAILURE') {} # leave at default else { unshift(@pipe_args, '-N', $common_list) } unshift(@pipe_args, '-V', $msginfo->dsn_envid) if defined $msginfo->dsn_envid; # but there is no mechanism to specify ORCPT or RET } for (@pipe_args) { # The sendmail command line expects addresses quoted as per RFC 822. # "funny user"@some.domain # For compatibility with Sendmail, the Postfix sendmail command line also # accepts address formats that are legal in RFC 822 mail header section: # Funny Dude <"funny user"@some.domain> # Although addresses passed as args to sendmail submission program # should not be <...> bracketed, for some reason original sendmail # issues a warning on null reverse-path, but gladly accepts <>. # As this is not strictly wrong, we comply to make it happy. # NOTE: the -fsender is not allowed, -f and sender must be separate args! my $null_ret_path = '<>'; # some sendmail lookalikes want '<>', others '' # Courier sendmail accepts '' but not '<>' for null reverse path if (/^\$\{sender\}\z/i) { push(@command, $msginfo->sender eq '' ? $null_ret_path : do { my $s = $msginfo->sender_smtp; $s =~ s/^<//; $s =~ s/>\z//; untaint($s) }); } elsif (/^\$\{recipient\}\z/i) { push(@command, map { $_ eq '' ? $null_ret_path : untaint(quote_rfc2821_local($_)) } map($_->recip_final_addr, @$per_recip_data_ref)); } else { push(@command, $_); } } ll(5) && do_log(5, "mail_via_pipe running command: %s", join(' ',@command)); local $SIG{CHLD} = 'DEFAULT'; local $SIG{PIPE} = 'IGNORE'; # don't signal on a write to a widowed pipe my($proc_fh,$pid) = run_command_consumer(undef,undef,@command); my $hdr_edits = $msginfo->header_edits; $hdr_edits = Amavis::Out::EditHeader->new if !$hdr_edits; my($received_cnt,$file_position) = $hdr_edits->write_header($msginfo,$proc_fh,!$initial_submission); my $msg = $msginfo->mail_text; my $msg_str_ref = $msginfo->mail_text_str; # have an in-memory copy? $msg = $msg_str_ref if ref $msg_str_ref; if ($received_cnt > 100) { # loop detection required by RFC 5321 section 6.3 # deal with it later, for now just skip the body } elsif (!defined $msg) { # empty mail } elsif (ref $msg eq 'SCALAR') { # do it in chunks, saves memory, cache friendly while ($file_position < length($$msg)) { $proc_fh->print(substr($$msg,$file_position,16384)) or die "writing mail text to a pipe failed: $!"; $file_position += 16384; # may overshoot, no problem } } elsif ($msg->isa('MIME::Entity')) { $msg->print_body($proc_fh); } else { my($nbytes,$buff); while (($nbytes = $msg->read($buff,32768)) > 0) { $proc_fh->print($buff) or die "writing mail text to a pipe failed: $!"; } defined $nbytes or die "Error reading: $!"; } $proc_fh->flush or die "Can't flush pipe to a mail submission program: $!"; my $smtp_response; if ($received_cnt > 100) { # loop detection required by RFC 5321 section 6.3 do_log(-2, "Too many hops: %d 'Received:' header fields", $received_cnt); kill_proc($pid,$command[0],10,$proc_fh,'too many hops') if defined $pid; $proc_fh->close; undef $proc_fh; undef $pid; # and ignore status $smtp_response = "554 5.4.6 Reject: " . "Too many hops: $received_cnt 'Received:' header fields"; } else { my $err = 0; $proc_fh->close or $err=$!; my $child_stat = defined $pid && waitpid($pid,0) > 0 ? $? : undef; undef $proc_fh; undef $pid; # sendmail program (Postfix variant) can return the following exit codes: # EX_OK(0), EX_DATAERR, EX_SOFTWARE, EX_TEMPFAIL, EX_NOUSER, EX_UNAVAILABLE if (proc_status_ok($child_stat,$err, EX_OK)) { $smtp_response = "250 2.6.0 Ok"; # submitted to MTA snmp_count('OutMsgsDelivers'); my $size = $msginfo->msg_size; snmp_count( ['OutMsgsSize'.$_, $size, 'C64'] ) for @snmp_vars; } elsif (proc_status_ok($child_stat,$err, EX_TEMPFAIL)) { $smtp_response = "450 4.5.0 Temporary failure submitting message"; snmp_count('OutMsgsAttemptFails'); } elsif (proc_status_ok($child_stat,$err, EX_NOUSER)) { $smtp_response = "554 5.1.1 Recipient unknown"; snmp_count('OutMsgsRejects'); } elsif (proc_status_ok($child_stat,$err, EX_UNAVAILABLE)) { $smtp_response = "554 5.5.0 Mail submission service unavailable"; snmp_count('OutMsgsRejects'); } else { $smtp_response = "451 4.5.0 Failed to submit a message: ". exit_status_str($child_stat,$err); snmp_count('OutMsgsAttemptFails'); } ll(3) && do_log(3,"mail_via_pipe %s, %s, %s", $command[0], exit_status_str($child_stat,$err), $smtp_response); } $smtp_response .= ", id=" . $msginfo->log_id; for my $r (@$per_recip_data_ref) { next if $r->recip_done; $r->recip_smtp_response($smtp_response); $r->recip_done(2); $r->recip_mbxname($r->recip_final_addr) if $smtp_response =~ /^2/; } $msginfo->dsn_passed_on($dsn_capable && $smtp_response=~/^2/ && !c('terminate_dsn_on_notify_success') ? 1 : 0); section_time('fwd-pipe'); 1; } 1;