Server IP : 85.214.239.14 / Your IP : 3.137.219.221 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 : /proc/2/task/2/root/usr/share/perl5/Amavis/Out/SMTP/ |
Upload File : |
# SPDX-License-Identifier: GPL-2.0-or-later package Amavis::Out::SMTP::Session; # provides a mechanism for SMTP session caching 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_OK = qw(&rundown_stale_sessions); } use subs @EXPORT_OK; use vars qw(%sessions_cache); use Time::HiRes qw(time); use Amavis::Conf qw(:platform c cr ca $smtp_connection_cache_enable %smtp_tls_client_options); use Amavis::Util qw(min max minmax ll do_log snmp_count idn_to_ascii); sub new { my($class, $socket_specs, $deadline, $wildcard_implied_host, $wildcard_implied_port) = @_; my $self; my $cache_key; my $found_cached = 0; for my $proto_sockname (ref $socket_specs ? @$socket_specs : $socket_specs) { $cache_key = $proto_sockname; local($1,$2,$3,$4); if ($proto_sockname =~ # deal with dynamic destinations (wildcards) /^([a-z][a-z0-9.+-]*) : (?: \[ ([^\]]*) \] | ([^:]*) ) : ([^:]*)/xsi) { my $peeraddress = defined $2 ? $2 : $3; my $peerport = $4; $peeraddress = $wildcard_implied_host if $peeraddress eq '*'; $peerport = $wildcard_implied_port if $peerport eq '*'; $cache_key = sprintf("%s:[%s]:%s", $1, $peeraddress, $peerport); } if (exists $sessions_cache{$cache_key}) { $found_cached = 1; last } } if ($found_cached) { $self = $sessions_cache{$cache_key}; $self->{deadline} = $deadline; do_log(3, "smtp session reuse (%s), %d transactions so far", $cache_key, $self->{transaction_count}); } else { do_log(3, "smtp session: setting up a new session"); $cache_key = undef; $self = bless { socket_specs => $socket_specs, socketname => undef, protocol => undef, smtp_handle => undef, deadline => $deadline, timeout => undef, in_xactn => 0, transaction_count => 0, state => 'down', established_at_time => undef, wildcard_implied_host => $wildcard_implied_host, wildcard_implied_port => $wildcard_implied_port, }, $class; } $self->establish_or_refresh; if (!defined $cache_key) { # newly established session $cache_key = sprintf("%s:%s", $self->protocol, $self->socketname); $sessions_cache{$cache_key} = $self; } $self; } sub smtp_handle { @_<2 ? $_[0]->{handle} : ($_[0]->{handle} = $_[1]) } sub socketname { @_<2 ? shift->{socketname} : ($_[0]->{socketname} = $_[1]) } sub protocol { @_<2 ? shift->{protocol} : ($_[0]->{protocol} = $_[1]) } sub session_state { @_<2 ? shift->{state} : ($_[0]->{state} = $_[1]) } sub in_smtp_transaction { @_<2 ? shift->{in_xactn} : ($_[0]->{in_xactn} = $_[1]) } sub established_at_time { @_<2 ? shift->{established_at_time} : ($_[0]->{established_at_time}=$_[1])} sub transaction_begins { my $self = $_[0]; !$self->in_smtp_transaction or die "smtp session: transaction_begins, but already active"; $self->in_smtp_transaction(1); } sub transaction_begins_unconfirmed { my $self = $_[0]; snmp_count('OutConnTransact'); $self->{transaction_count}++; !$self->in_smtp_transaction or die "smtp session: transaction_begins_unconfirmed, but already active"; $self->in_smtp_transaction(undef); } sub transaction_ends { my $self = $_[0]; $self->in_smtp_transaction(0); } sub transaction_ends_unconfirmed { my $self = $_[0]; # if already 0 then keep it, otherwise undefine $self->in_smtp_transaction(undef) if $self->in_smtp_transaction; } sub timeout { my $self = shift; if (@_) { my $timeout = shift; $self->{timeout} = $timeout; $self->{handle}->timeout($timeout) if defined $self->{handle}; # do_log(5, "smtp session, timeout set to %s", $timeout); } $self->{timeout}; } sub supports { my($self,$keyword) = @_; $self->{handle} ? $self->{handle}->supports($keyword) : undef; } sub smtp_response { my $self = $_[0]; $self->{handle} ? $self->{handle}->smtp_response : undef; } sub quit { my $self = $_[0]; my $smtp_handle = $self->smtp_handle; if (defined $smtp_handle) { $self->session_state('quitsent'); snmp_count('OutConnQuit'); $smtp_handle->quit; #flush! QUIT } } sub close { my($self,$keep_connected) = @_; my $msg; my $smtp_handle = $self->smtp_handle; if (defined($smtp_handle) && $smtp_handle->eof) { $msg = 'already disconnected'; $keep_connected = 0; } else { $msg = $keep_connected ? 'keeping connection' : 'disconnecting'; } do_log(3, "Amavis::Out::SMTP::Session close, %s", $msg); if (!$keep_connected) { if (defined $smtp_handle) { $smtp_handle->close or do_log(1, "Error closing Amavis::Out::SMTP::Protocol obj"); $self->in_smtp_transaction(0); $self->established_at_time(undef); $self->smtp_handle(undef); $self->session_state('down'); } if (defined $self->socketname) { my $cache_key = sprintf("%s:%s", $self->protocol, $self->socketname); delete $sessions_cache{$cache_key} if exists $sessions_cache{$cache_key}; } } 1; } sub rundown_stale_sessions($) { my $close_all = $_[0]; my $num_sessions_closed = 0; for my $cache_key (keys %sessions_cache) { my $smtp_session = $sessions_cache{$cache_key}; my $smtp_handle = $smtp_session->smtp_handle; my $established_at_time = $smtp_session->established_at_time; my $last_event_time; $last_event_time = $smtp_handle->last_io_event_timestamp if $smtp_handle; my $now = Time::HiRes::time; if ($close_all || !$smtp_connection_cache_enable || !defined($last_event_time) || $now - $last_event_time >= 30 || !defined($established_at_time) || $now - $established_at_time >= 60) { ll(3) && do_log(3,"smtp session rundown%s%s%s, %s, state %s", $close_all ? ' all sessions' : $smtp_connection_cache_enable ? ' stale sessions' : ', cache off', !defined($last_event_time) ? '' : sprintf(", idle %.1f s", $now - $last_event_time), !defined($established_at_time) ? '' : sprintf(", since %.1f s ago", $now - $established_at_time), $cache_key, $smtp_session->session_state); if ($smtp_session->session_state ne 'down' && $smtp_session->session_state ne 'quitsent' && (!defined($last_event_time) || $now - $last_event_time <= 55)) { do_log(3,"smtp session rundown, sending QUIT"); eval { $smtp_session->quit } or 1; #flush! QUIT (ignoring failures) } if ($smtp_session->session_state eq 'quitsent') { # collect response $smtp_session->timeout(5); my $smtp_resp = eval { $smtp_session->smtp_response }; if (!defined $smtp_resp) { do_log(3,"No SMTP resp. to QUIT"); } elsif ($smtp_resp eq '') { do_log(3,"Empty SMTP resp. to QUIT"); } elsif ($smtp_resp !~ /^2/) { do_log(3,"Negative SMTP resp. to QUIT: %s", $smtp_resp); } else { # success, $smtp_resp =~ /^2/ do_log(3,"smtp resp to QUIT: %s", $smtp_resp); } } if ($smtp_session->session_state ne 'down') { do_log(3,"smtp session rundown, closing session %s", $cache_key); $smtp_session->close(0) or do_log(-2, "Error closing smtp session %s", $cache_key); $num_sessions_closed++; } } } $num_sessions_closed; } sub establish_or_refresh { my $self = $_[0]; # Timeout should be more than MTA normally takes to check DNS and RBL, # which may take a minute or more in case of unreachable DNS server. # Specifying shorter timeout will cause alarm to terminate the wait # for SMTP status line prematurely, resulting in status code 000. # RFC 5321 (ex RFC 2821) section 4.5.3.2 requires timeout to be # at least 5 minutes my $smtp_connect_timeout = 35; # seconds my $smtp_helo_timeout = 300; my $smtp_starttls_timeout = 300; my $smtp_handle = $self->smtp_handle; my $smtp_resp; my $last_event_time; $last_event_time = $smtp_handle->last_io_event_timestamp if $smtp_handle; my $now = Time::HiRes::time; do_log(5,"establish_or_refresh, state: %s", $self->session_state); die "panic, still in SMTP transaction" if $self->in_smtp_transaction; if (defined($smtp_handle) && $self->session_state ne 'down' && $self->session_state ne 'quitsent') { # if session has been idling for some time, check with a low-cost NOOP # whether the session is still alive - reconnecting at this time is cheap; # note that NOOP is non-pipelinable, MTA must respond immediately if (defined($last_event_time) && $now - $last_event_time <= 18) { snmp_count('OutConnReuseRecent'); do_log(3,"smtp session most likely still valid (short idle %.1f s)", $now - $last_event_time); } else { # Postfix default smtpd idle timeout is 60 s eval { $self->timeout(15); $smtp_handle->noop; #flush! $smtp_resp = $self->smtp_response; # fetch response to NOOP do_log(3,"smtp resp to NOOP (idle %.1f s): %s", $now - $last_event_time, $smtp_resp); 1; } or do { my $eval_stat = $@ ne '' ? $@ : "errno=$!"; chomp $eval_stat; do_log(3,"smtp NOOP failed (idle %.1f s): %s", $now - $last_event_time, $eval_stat); $smtp_resp = ''; }; if ($smtp_resp =~ /^2/) { snmp_count('OutConnReuseRefreshed'); } else { snmp_count('OutConnReuseFail'); $self->close(0) or do_log(-1, "Error closing smtp session"); } } } if ($self->session_state eq 'down' || $self->session_state eq 'quitsent') { if (defined $smtp_handle) { $smtp_handle->close or do_log(-2, "Error closing Amavis::Out::SMTP::Protocol obj"); undef $smtp_handle; } my $localaddr = c('local_client_bind_address'); # IP assigned to socket snmp_count('OutConnNew'); require Amavis::Out::SMTP::Protocol; $smtp_handle = Amavis::Out::SMTP::Protocol->new( $self->{socket_specs}, LocalAddr => $localaddr, Timeout => 35, WildcardImpliedHost => $self->{wildcard_implied_host}, WildcardImpliedPort => $self->{wildcard_implied_port}); $self->smtp_handle($smtp_handle); defined $smtp_handle # don't change die text, it is referred to elsewhere or die sprintf("Can't connect to %s", !ref $self->{socket_specs} ? $self->{socket_specs} : join(", ",@$self->{socket_specs}) ); $self->socketname($smtp_handle->socketname); $self->protocol($smtp_handle->protocol); $self->session_state('connected'); $self->established_at_time(time); $self->timeout($smtp_connect_timeout); $smtp_resp = $self->smtp_response; # fetch greeting if (!defined $smtp_resp || $smtp_resp eq '') { die sprintf("%s greeting, dt: %.3f s\n", !defined $smtp_resp ? 'No' : 'Empty', time - $smtp_handle->last_io_event_tx_timestamp); } elsif ($smtp_resp !~ /^2/) { die "Negative greeting: $smtp_resp\n"; } else { # success, $smtp_resp =~ /^2/ do_log(3,"smtp greeting: %s, dt: %.1f ms", $smtp_resp, 1000*(time-$smtp_handle->last_io_event_tx_timestamp)); } } if ($self->session_state eq 'connected') { my $lmtp = lc($self->protocol) eq 'lmtp' ? 1 : 0; # RFC 2033 my $deadline = $self->{deadline}; my $tls_security_level = c('tls_security_level_out'); $tls_security_level = 0 if !defined($tls_security_level) || lc($tls_security_level) eq 'none'; my $myheloname = c('localhost_name'); # host name used in EHLO/HELO/LHLO $myheloname = 'localhost' if $myheloname eq ''; $myheloname = idn_to_ascii($myheloname); for (1..2) { # send EHLO/LHLO/HELO $self->timeout(max(60,min($smtp_helo_timeout, $deadline - time))); if ($lmtp) { $smtp_handle->lhlo($myheloname) } #flush! else { $smtp_handle->ehlo($myheloname) } #flush! $smtp_resp = $self->smtp_response; # fetch response to EHLO/LHLO if (!defined $smtp_resp || $smtp_resp eq '') { die sprintf("%s response to %s, dt: %.3f s\n", !defined $smtp_resp ? 'No' : 'Empty', $lmtp ? 'LHLO' : 'EHLO', time - $smtp_handle->last_io_event_tx_timestamp); } elsif ($smtp_resp =~ /^2/) { # success do_log(3,"smtp resp to %s: %s", $lmtp?'LHLO':'EHLO', $smtp_resp); } elsif ($lmtp) { # failure, no fallback possible die "Negative SMTP resp. to LHLO: $smtp_resp\n"; } else { # failure, SMTP fallback to HELO do_log(3,"Negative SMTP resp. to EHLO, will try HELO: %s", $smtp_resp); $smtp_handle->helo($myheloname); #flush! $smtp_resp = $self->smtp_response; # fetch response to HELO if (!defined $smtp_resp || $smtp_resp eq '') { die sprintf("%s response to HELO, dt: %.3f s\n", !defined $smtp_resp ? 'No' : 'Empty', time - $smtp_handle->last_io_event_tx_timestamp); } elsif ($smtp_resp !~ /^2/) { die "Negative response to HELO: $smtp_resp\n"; } else { # success, $smtp_resp =~ /^2/ do_log(3,"smtp resp to HELO: %s", $smtp_resp); } } $self->session_state('ehlo'); $smtp_handle->ehlo_response_parse($smtp_resp); my $tls_capable = defined $self->supports('STARTTLS'); # RFC 3207 ll(5) && do_log(5, "tls active=%d, capable=%s, sec_level=%s", $smtp_handle->ssl_active, $tls_capable, $tls_security_level); if ($smtp_handle->ssl_active) { last; # done } elsif (!$tls_capable && $tls_security_level && lc($tls_security_level) ne 'may') { die "MTA does not offer STARTTLS, ". "but TLS is required: \"$tls_security_level\""; } elsif (!$tls_capable || !$tls_security_level) { last; # not offered and not mandated } else { $self->timeout(max(60,min($smtp_starttls_timeout, $deadline - time))); $smtp_handle->command('STARTTLS'); #flush! $smtp_resp = $self->smtp_response; # fetch response to STARTTLS $smtp_resp = '' if !defined $smtp_resp; do_log(3,"smtp resp to STARTTLS: %s", $smtp_resp); if ($smtp_resp !~ /^2/) { (!$tls_security_level || lc($tls_security_level) eq 'may') or die "Negative SMTP resp. to STARTTLS: $smtp_resp\n"; } else { $smtp_handle->ssl_upgrade(%smtp_tls_client_options) or die "Error upgrading socket to SSL"; $self->session_state('connected'); } } } } $self; } 1;