Haineko
view release on metacpan or search on metacpan
lib/Haineko/Sendmail.pm view on Meta::CPAN
# - etc/mailertable
# - etc/sendermt
try {
$exceptions = 0;
$mailerconf->{ $e } = Haineko::JSON->loadfile( $serverconf->{'mailer'}->{ $e } );
} catch {
# Failed to load etc/mailertable or etc/sendermt. Maybe the file
# format is wrong or is not JSON or YAML.
$exceptions = 1;
};
# Load ``default:'' section in etc/mailertable
$defaulthub //= $mailerconf->{'rcpt'}->{'default'};
last if $e eq 'rcpt';
# If the value of ``disabled'' is 1, the mailer table based on the
# domain part of the envelope sender address will not be used.
next unless exists $mailerconf->{'mail'}->{ $submission->addresser->host };
next if $mailerconf->{'mail'}->{ $submission->addresser->host }->{'disabled'};
$sendershub = $mailerconf->{'mail'}->{ $submission->addresser->host };
}
# ``default:'' section was not defined in etc/mailertable. Use system
# configuration as a default hub for relaying.
$defaulthub //= Haineko::SMTPD::Relay->defaulthub;
}
my $autheninfo = undef;
AUTHINFO: {
# Load etc/authinfo file. Entries defined in etc/authinfo are used at
# relaying to an external SMTP server or sending message to an email
# clouds.
try {
$exceptions = 0;
$mailerconf->{'auth'} = Haineko::JSON->loadfile( $serverconf->{'mailer'}->{'auth'} );
} catch {
# Failed to load etc/authinfo file.
$exceptions = 1;
};
$autheninfo = $mailerconf->{'auth'} // {};
}
SENDMIAL: {
# ____ _____ _ _ ____ __ __ _ ___ _
# / ___|| ____| \ | | _ \| \/ | / \ |_ _| |
# \___ \| _| | \| | | | | |\/| | / _ \ | || |
# ___) | |___| |\ | |_| | | | |/ ___ \ | || |___
# |____/|_____|_| \_|____/|_| |_/_/ \_\___|_____|
#
require Module::Load;
my $maxworkers = scalar @$recipients;
my $preforkset = undef; # (Parallel::Prefork) object
my $preforkarg = undef; # (Ref->Hash) arguments for Parallel::Prefork
my $preforkipc = []; # (Ref->Array) IO::Pipe objects
my $useprefork = 0; # (Integer) use fork() or not
my $procnumber = 0; # (Integer) Job ID of each child process
my $trappedsig = 0; # (Integer) The number of received USR2 signal
if( $maxworkers > 1 ) {
# Adjust the number of max worker processes.
my $xprocesses = $serverconf->{'max_workers'} // $defaultset->{'smtpd'}->{'max_workers'};
if( Scalar::Util::looks_like_number $xprocesses ) {
# Limit the value of max_workers to the value defined in
# etc/haineko.cf or Haineko::Default.
$maxworkers = $xprocesses if $maxworkers > $xprocesses;
} else {
# The value of max_workers does not look like number, such as
# "max_workers": "neko"
$esresponse = $responsecn->r( 'conf', 'not-looks-like-number' );
$esresponse->mesg( sprintf( "Wrong value of max_workers: '%s'", $xprocesses ) );
$tmpsession->add_response( $esresponse );
$nekosyslog->w( 'err', $esresponse->damn );
return $httpd->res->json( 500, $tmpsession->damn );
}
$useprefork = 1 if $maxworkers > 1;;
}
if( $useprefork ) {
# If the number of recipients or the value of `maxworkers` is greater
# than 1, fork and send emails by each child process.
require IO::Pipe;
require Parallel::Prefork;
$preforkarg = {
'max_workers' => $maxworkers,
'err_respawn_interval' => 2,
'spawn_interval' => 0,
'trap_signals' => { 'HUP' => 'TERM', 'TERM' => 'TERM' },
'before_fork' => sub {
my $k = shift;
$k->{'procnumber'} = $procnumber;
if( $procnumber < $maxworkers ) {
$preforkipc->[ $procnumber ] = IO::Pipe->new;;
$procnumber++;
}
}
};
$preforkset = Parallel::Prefork->new( $preforkarg );
$SIG{'USR2'} = sub {
# Trap signal from each child process
$trappedsig++;
kill( 'TERM', $$ ) if $trappedsig >= $maxworkers;
};
}
my $sendmailto = sub {
# Code reference for sending an email to each recipient which called
# from Parallel::Prefork->start().
my $thisworker = undef;
local $SIG{'TERM'} = 'IGNORE';
if( $useprefork ) {
# fork and send each email
kill( 'USR2', $preforkset->manager_pid );
$thisworker = $preforkset->{'procnumber'};
# The number of worker processes has exceeded the limit
return -1 if $thisworker >= $maxworkers;
} else {
# send each email in order
$thisworker = 0;
}
ONE_TO_ONE: for( my $i = $thisworker; $i < $maxworkers; $i += $maxworkers ) {
# Skip if the recipient address is in @$cannotsend
my $e = $recipients->[ $i ];
next if grep { $e eq $_ } @$cannotsend;
# Create email address objects from each envelope recipient address
my $r = Haineko::SMTPD::Address->new( 'address' => $e );
my $relayclass = q(); # (String) Class name of $smtpmailer
my $smtpmailer = undef; # (Haineko::SMTPD::Relay::*) Mailer object
my $relayingto = undef; # (Ref->Hash) Mailertable
my $credential = undef; # (Ref->Hash) Username and password for SMTP-AUTH or API
$relayingto = $mailerconf->{'rcpt'}->{ $r->host } // $sendershub;
$relayingto = $sendershub if $relayingto->{'disabled'};
$relayingto = $defaulthub unless keys %$relayingto;
$relayingto = $defaulthub if $relayingto->{'disabled'};
$relayingto->{'port'} //= 25;
$relayingto->{'host'} //= '127.0.0.1';
$relayingto->{'mailer'} //= 'ESMTP';
if( $relayingto->{'auth'} ) {
$credential = $autheninfo->{ $relayingto->{'auth'} } // {};
}
$relayingto->{'auth'} = q() unless keys %$credential;
if( $relayingto->{'mailer'} =~ m/\A(?:ESMTP|Haineko|MX)\z/ ) {
# Use Haineko::SMTPD::Relay::ESMTP or Haineko::SMTPD::Relay::Haineko
# ::MX = Directly connect to the host listed in MX Resource record
# ::ESMTP = Generic SMTP connection to an external server
# ::Haineko = Relay an message to Haineko running on other host
my $methodargv = {
'ehlo' => $serverconf->{'hostname'},
'mail' => $submission->addresser->address,
'rcpt' => $r->address,
( run in 1.858 second using v1.01-cache-2.11-cpan-71847e10f99 )