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 )