#!/usr/bin/perl

=head1 NAME

pfredirect - IMAP and POP3 redirectors

=head1 SYNOPSIS

pfredirect [options]

 Options:
   -d      Daemonize       
   -h      Help
   -v      Verbose

=cut

use warnings;
use strict;
use English qw( ‐no_match_vars ) ;  # Avoids regex performance penalty
use File::Basename qw(basename);
use Getopt::Std;
use IO::Socket::INET;
use Log::Log4perl;
use Pod::Usage;
use POSIX();
use threads;
use threads::shared;

use constant INSTALL_DIR => '/usr/local/pf';

use lib INSTALL_DIR . "/lib";
use pf::config;
use pf::util;

Log::Log4perl->init_and_watch( INSTALL_DIR . "/conf/log.conf", $LOG4PERL_RELOAD_TIMER );
my $logger = Log::Log4perl->get_logger( basename($0) );
Log::Log4perl::MDC->put( 'proc', basename($0) );
Log::Log4perl::MDC->put( 'tid',  $PID );

POSIX::sigaction(
    &POSIX::SIGHUP,
    POSIX::SigAction->new(
        'normal_sighandler', POSIX::SigSet->new(), &POSIX::SA_NODEFER
    )
) or $logger->logdie("pfredirect: could not set SIGHUP handler: $!");

POSIX::sigaction(
    &POSIX::SIGTERM,
    POSIX::SigAction->new(
        'normal_sighandler', POSIX::SigSet->new(), &POSIX::SA_NODEFER
    )
) or $logger->logdie("pfredirect: could not set SIGTERM handler: $!");

POSIX::sigaction(
    &POSIX::SIGINT,
    POSIX::SigAction->new(
        'normal_sighandler', POSIX::SigSet->new(), &POSIX::SA_NODEFER
    )
) or $logger->logdie("pfredirect: could not set SIGINT handler: $!");


my %args;
getopts( 'dhv', \%args );

pod2usage( -verbose => 1 ) if ( $args{h} );
my $daemonize = $args{d};
my $verbose   = $args{v};

daemonize() if ($daemonize);
$logger->info("initialized");

my $pop3_port = 110;
my $imap_port = 143;
my $hostname
    = $Config{'general'}{'hostname'} . "." . $Config{'general'}{'domain'};
my $msg = "$conf_dir/listener.msg";
my $date = POSIX::strftime( "%a, %d %b %Y %T %z", localtime );

# Build message
my $msg_fh;
open( $msg_fh, '<', $msg ) || $logger->logdie("Unable to open message: $!");
my $message = join( '', <$msg_fh> );
close($msg_fh);

my $dateline = "Date: $date\n";
my $from     = "From: PacketFence <do not reply to this email!>\n";
my $subject  = "Subject: Network Access Blocked\n";
my $email    = $dateline . $from . $subject . "\n" . $message . ".\n";
my $length   = length($email);

my $kid1;
my @listeners = split( /\s*,\s*/, $Config{'ports'}{'listeners'} );
if (scalar(@listeners) == 1) {
    if ($listeners[0] eq 'pop3') {
        $logger->info("starting pop3 redirector");
        pop3_director();
    } else {
        $logger->info("starting imap redirector");
        imap_director();
    }
} else {
    $logger->info("starting pop3 and imap redirector");
    $kid1 = threads->create( \&pop3_director );
    imap_director();
}

sub imap_director {
    my $sock = new IO::Socket::INET(
        LocalPort => $imap_port,
        Proto     => 'tcp',
        Listen    => 1,
        Reuse     => 1,
    );
    if ( !$sock ) {
        $logger->error("can't create IMAP socket: $!");
        $logger->logdie("Can't create IMAP socket: $!");
    }

    $sock->autoflush(1);
    while ( my $sock_handle = $sock->accept() ) {
        my $client_ip = $sock_handle->peerhost();
        print "Accepting connection from $client_ip\n" if ($verbose);
        $logger->info("IMAP connection from $client_ip");
        print $sock_handle
            "* OK [CAPABILITY IMAP4REV1 AUTH=LOGIN] $hostname IMAP4rev1 at $date\n";
        while ( my $request = <$sock_handle> ) {
            chomp $request;
            print "\tClient request: $request\n" if ($verbose);
            if ( $request =~ /logout/i ) {
                print $sock_handle
                    "* BYE $hostname IMAP4rev1 server terminating connection\n";
                close $sock_handle;
            } elsif ( $request =~ /capability/i ) {
                print $sock_handle "* CAPABILITY IMAP4REV1 AUTH=LOGIN\n";
            } else {
                print $sock_handle "* OK [ALERT] $message";
                print $sock_handle
                    "* BYE $hostname IMAP4rev1 server terminating connection\n";
                close $sock_handle;
            }
        }
    }
    close($sock);
}

sub pop3_director {
    my $sock = new IO::Socket::INET(
        LocalPort => $pop3_port,
        Proto     => 'tcp',
        Listen    => 1,
        Reuse     => 1,
    );
    $logger->logdie("Can't create socket: $!") unless $sock;

    $sock->autoflush(1);
    while ( my $sock_handle = $sock->accept() ) {
        my $client_ip = $sock_handle->peerhost();

        print "Accepting connection from $client_ip\n" if ($verbose);
        $logger->info("POP3 connection from $client_ip");

        print $sock_handle "+OK POP3 $hostname server ready\n";

        while ( my $request = <$sock_handle> ) {
            chomp $request;
            print "\tClient request: $request\n" if ($verbose);
            if ( $request =~ /^QUIT/i ) {
                print $sock_handle "+OK I'm out!\n";
                close $sock_handle;
            } elsif ( $request =~ /^STAT/i ) {
                print $sock_handle "+OK 1 $length\n";
            } elsif ( $request =~ /^CAPA/i ) {
                print $sock_handle "+OK Capability list follows:\nUSER\n.\n";
            } elsif ( $request =~ /^LIST/i ) {
                print $sock_handle
                    "+OK Mailbox scan listing follows\n1 $length\n.\n";
            } elsif ( $request =~ /^(RETR|TOP)/i ) {
                print $sock_handle "+OK $length octets\n";
                print $sock_handle $email;
            } elsif ( $request =~ /^USER/i ) {
                print $sock_handle
                    "+OK User name accepted, password please\n";
            } elsif ( $request =~ /^PASS/i ) {
                print $sock_handle "+OK Mailbox open, 1 message\n";
            } elsif ( $request =~ /^DELE/i ) {
                print $sock_handle "+OK Message deleted\n";
            } elsif ( $request =~ /^AUTH LOGIN/i ) {
                my $tmp;
                print $sock_handle "+ XXXXXXXXXXXXXXXX\n";
                $tmp = <$sock_handle>;
                print $sock_handle "+ XXXXXXXXXXXXXXXX\n";
                $tmp = <$sock_handle>;
                print $sock_handle "+OK Mailbox open, 1 message\n";
            } else {
                print $sock_handle "-ERR\n";
            }
        }
    }
    close($sock);
}

sub normal_handler {
    deletepid();
    $logger->info( "caught SIG" . $_[0] . " - terminating" );
}

sub daemonize {
    chdir '/' or $logger->logdie("Can't chdir to /: $!");
    open STDIN, '<', '/dev/null'
        or $logger->logdie("Can't read /dev/null: $!");
    my $log_file = "$install_dir/logs/pfredirect";
    open STDOUT, '>>', $log_file
        or $logger->logdie("Can't write to $log_file: $!");

    defined( my $pid = fork )
        or $logger->logdie("pfredirect: could not fork: $!");
    POSIX::_exit(0) if ($pid);
    if ( !POSIX::setsid() ) {
        $logger->warn("could not start a new session: $!");
    }
    open STDERR, '>&STDOUT' or $logger->logdie("Can't dup stdout: $!");
    my $daemon_pid = createpid();

    # updating Log4perl's pid info
    Log::Log4perl::MDC->put( 'tid',  $daemon_pid );
}

END {
    if ( !$args{h} ) {
        deletepid();
        $logger->info("stopping pfredirect");
        if ( defined($kid1) ) {
            $kid1->detach;

            # kill kids...
            kill 6, -$$;
        }
    }
}

exit(0);

=head1 AUTHOR

Dave Laporte <dave@laportestyle.org>

Kevin Amorin <kev@amorin.org>

Dominik Gehl <dgehl@inverse.ca>

Olivier Bilodeau <obilodeau@inverse.ca>

=head1 COPYRIGHT

Copyright (C) 2005 Dave Laporte

Copyright (C) 2005 Kevin Amorin

Copyright (C) 2009,2011 Inverse inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
USA.

=cut

