Viewing File: /usr/local/cpanel/whostmgr/docroot/cgi/mailflow.cgi

#!/usr/local/cpanel/3rdparty/bin/perl

# cpanel - whostmgr/docroot/cgi/mailflow.cgi       Copyright 2022 cPanel, L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited

#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# 3. Neither the name of the owner nor the names of its contributors may be
# used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

use strict;
use warnings;

# The flowchart creation needs this to avoid overencoding text.
use utf8;

use Graph::Flowchart;
use Graph::Easy;
use Cpanel::Config::LoadCpConf ();
use Cpanel::Form               ();
use Cpanel::Exim::Config::Def  ();
use Cpanel::Binaries           ();
use Whostmgr::ACLS             ();
use Whostmgr::HTMLInterface    ();

print "Content-type: text/html; charset=utf-8\r\n\r\n";

_check_acls();

#print qq{<pre>};

my %FORM = Cpanel::Form::parseform();
my %OPTS;
my $cpconf = Cpanel::Config::LoadCpConf::loadcpconf();

my $clam = 0;
if ( -x Cpanel::Binaries::path("clamd") && -e "/etc/exim.conf.mailman2.exiscan.dist" ) {
    $clam = 1;
}
my @DEFAULT_RBL_ACLS = ( 'spamhaus_rbl', 'spamcop_rbl', 'spamhaus_spamcop_rbl', );
my $globalsa         = ( -e '/etc/global_spamassassin_enable' ? 1 : 0 );
my $senderverify     = 1;
my $callouts         = 1;
my $systemfilter     = '/etc/cpanel_exim_system_filter';
my $setsenderheader  = 0;
my $hasboxtrapper    = $cpconf->{'skipboxtrapper'} ? 0 : 1;
my %ACLBLOCKS;
my %ACLINSERTS;
my %FILTERS;
my %ACLS;

if ( opendir my $acls_opts, '/usr/local/cpanel/etc/exim/acls' ) {
    while ( my $aclsblock = readdir $acls_opts ) {
        next if ( $aclsblock =~ /^\./ );
        if ( opendir my $acls_iopts, '/usr/local/cpanel/etc/exim/acls/' . $aclsblock ) {
            while ( my $acl = readdir $acls_iopts ) {
                next if $acl =~ m/^\./;
                $ACLS{$acl} = 1;
                push @{ $ACLBLOCKS{$aclsblock} }, $acl;
            }
            closedir $acls_iopts;
        }
    }
    closedir $acls_opts;
}

foreach my $acl (@Cpanel::Exim::Config::Def::OFF_DEFAULT_ACLS) {
    $ACLS{$acl} = 0;
}

if ( opendir my $filter_opts, '/usr/local/cpanel/etc/exim/sysfilter/options' ) {
    while ( my $filter = readdir $filter_opts ) {
        next if $filter =~ m/^\./;
        $FILTERS{$filter} = 1;
    }
    closedir $filter_opts;
}

foreach my $filter (@Cpanel::Exim::Config::Def::OFF_DEFAULT_FILTERS) {
    $FILTERS{$filter} = 0;
}

if ( $FORM{'use_form'} ) {
    %OPTS = %FORM;
    delete $OPTS{'use_form'};
}
else {
    open( EXIMCOPTS, '/etc/exim.conf.localopts' );
    while (<EXIMCOPTS>) {
        chomp();
        my ( $opt, $value ) = split( /=/, $_ );
        $OPTS{$opt} = $value;
    }
    close(EXIMCOPTS);
}
my @custom_rbls;
foreach my $opt ( keys %OPTS ) {
    my $value = $OPTS{$opt};
    if ( $opt =~ m/^acl_(\S+)/ ) {
        my $acl = $1;
        if ( $value eq '0' ) { $ACLS{$acl} = 0; }
        if ( $value eq '1' ) { $ACLS{$acl} = 1; }
        if ( $acl =~ m/_rbl$/ && !grep { $acl eq $_ } @DEFAULT_RBL_ACLS ) {
            push @custom_rbls, $acl;
        }
    }
    elsif ( $opt =~ /^filter_(\S+)/ ) {
        if ( $value eq '0' ) { $FILTERS{$1} = 0; }
        if ( $value eq '1' ) { $FILTERS{$1} = 1; }
    }
    elsif ( $opt eq 'callouts' && $value eq '0' )        { $callouts        = 0; }
    elsif ( $opt eq 'senderverify' && $value eq '0' )    { $senderverify    = 0; }
    elsif ( $opt eq 'setsenderheader' && $value eq '1' ) { $setsenderheader = 1; }
    elsif ( $opt eq 'systemfilter' )                     { $systemfilter    = $value; }
}

my $chart  = Graph::Flowchart->new();
my $format = shift || 'as_html_page';
my $current;

sub Graph::Flowchart::add_if_end {
    my ( $self, $if, $end, $where ) = @_;

    $if  = $self->new_block( $if,  Graph::Flowchart::N_IF() )  unless ref $if;
    $end = $self->new_block( $end, Graph::Flowchart::N_END() ) unless ref $end;

    $where = $self->{_cur} unless defined $where;

    $if = $self->insert_block( $if, $where );

    $self->connect( $if, $end, 'true', 'true' );

    my $joint = $self->add_joint;

    $self->connect( $if, $joint, 'false', 'false' );

    $self->{_cur} = $joint;

    return ( $if, $end, $self->{_cur} ) if wantarray;

    $self->{_cur};
}

sub Graph::Flowchart::add_if_not_end {
    my ( $self, $if, $end, $where ) = @_;

    $if  = $self->new_block( $if,  Graph::Flowchart::N_IF() )  unless ref $if;
    $end = $self->new_block( $end, Graph::Flowchart::N_END() ) unless ref $end;

    $where = $self->{_cur} unless defined $where;

    $if = $self->insert_block( $if, $where );

    $self->connect( $if, $end, 'false', 'false' );

    my $joint = $self->add_joint;

    $self->connect( $if, $joint, 'true', 'true' );

    $self->{_cur} = $joint;

    return ( $if, $end, $self->{_cur} ) if wantarray;

    $self->{_cur};
}

sub Graph::Flowchart::add_if_not_then {
    my ( $self, $if, $then, $where ) = @_;

    $if   = $self->new_block( $if,   Graph::Flowchart::N_IF() )   unless ref $if;
    $then = $self->new_block( $then, Graph::Flowchart::N_THEN() ) unless ref $then;

    $where = $self->{_cur} unless defined $where;

    $if = $self->insert_block( $if, $where );

    $self->connect( $if, $then, 'false', 'false' );

    # then --> '*'
    $self->{_cur} = $self->add_joint($then);

    # if -- true --> '*'
    $self->connect( $if, $self->{_cur}, 'true', 'true' );

    return ( $if, $then, $self->{_cur} ) if wantarray;

    $self->{_cur};
}

$chart->first_block("Incoming Email via SMTP");
$current = $chart->add_if_end( 'Mail is a Mailman bounce', 'accept' );
$current = $chart->add_if_not_end( 'Recipient Verification (The destination is a valid account.)', 'reject' );
$current = $chart->add_if_end( 'Sender Host is Authenticated (using SMTP AUTH)',        'accept' );
$current = $chart->add_if_end( 'Sender Host has done POP/IMAP before SMTP or is local', 'accept' );
if ( $ACLS{'spamcop_rbl'} ) {
    $current = $chart->add_if_end( 'Sender Host is in the RBL bl.spamcop.net', 'reject' );
}
if ( $ACLS{'spamhaus_rbl'} ) {
    $current = $chart->add_if_end( 'Sender Host is in the RBL zen.spamhaus.org', 'reject' );
}
if ( $ACLS{'spamhaus_spamcop_rbl'} ) {
    $current = $chart->add_if_end( 'Sender Host is in the RBL bl.spamcop.net or zen.spamhaus.org', 'reject' );
}

# Add custom RBLs
foreach my $rbl (@custom_rbls) {
    if ( $ACLS{$rbl} ) {
        my $rbl_name = $rbl;
        $rbl_name =~ s/_rbl$//;
        $current = $chart->add_if_end( 'Sender Host is in the RBL ' . $rbl_name, 'reject' );
    }
}

if ($senderverify) {
    if ($callouts) {
        $current = $chart->add_if_not_end( 'Sender Address can be verified using callouts', 'reject' );
    }
    else {
        $current = $chart->add_if_not_end( 'Sender Address can be verified', 'reject' );
    }
}
$current = $chart->add_if_end( 'Recipent Domain is local',                                   'accept' );
$current = $chart->add_if_end( 'Server is a backup mail exchanger for the Recipent Domain.', 'accept' );

if ( !$globalsa ) {
    $current = $chart->add_block('Scan mail with Apache SpamAssassin™ if enabled.');
}
else {
    $current = $chart->add_if_then( 'Apache SpamAssassin is enabled for recipient.', 'Scan mail with Apache SpamAssassin.' );
}
if ( $ACLS{'deny_spam_score_over_200'} ) {
    $current = $chart->add_if_end( 'Spam Score > 20.0.', 'reject' );
}
if ( $ACLS{'deny_spam_score_over_175'} ) {
    $current = $chart->add_if_end( 'Spam Score > 17.5.', 'reject' );
}
if ( $ACLS{'deny_spam_score_over_150'} ) {
    $current = $chart->add_if_end( 'Spam Score > 15.0.', 'reject' );
}
if ( $ACLS{'deny_spam_score_over_125'} ) {
    $current = $chart->add_if_end( 'Spam Score > 12.5.', 'reject' );
}
if ( $ACLS{'deny_spam_score_over_100'} ) {
    $current = $chart->add_if_end( 'Spam Score > 10.0.', 'reject' );
}

if ($clam) {
    $current = $chart->add_if_end( 'Message has a virus detected in it or other mailware', 'reject' );
}
my $end = $chart->new_block( 'accept', Graph::Flowchart::N_END() );
$end = $chart->add_block($end);
$chart->{_last} = $end;
my $start = $chart->{_first};

my $gr = $chart->as_graph();

$gr->set_attribute( 'node.if',    'fill',  'white' );
$gr->set_attribute( 'edge.true',  'color', 'green' );
$gr->set_attribute( 'edge.false', 'color', 'red' );
$gr->{att}->{graph}->{title} = 'Exim ACL Flowchart';

print STDERR "Resulting graph has ", scalar $gr->nodes(), " nodes and ", scalar $gr->edges(), " edges:\n\n";

binmode STDOUT, ':encoding(UTF-8)' or die("binmode STDOUT, ':encoding(UTF-8)' failed: $!");
print $gr->$format();

sub _check_acls {
    Whostmgr::ACLS::init_acls();

    if ( !Whostmgr::ACLS::hasroot() ) {
        Whostmgr::HTMLInterface::defheader( '', '', '/cgi/tweakcphulk.cgi' );
        print <<'EOM';

    <br />
    <br />
    <div><h1>Permission denied</h1></div>
    </body>
    </html>
EOM
        exit;
    }
}
Back to Directory File Manager