#!/usr/bin/perl
#
#
# * v2.2.4 -  saves only seen mails if -B Fabian_Schmidt).
#
#

use strict;
use Getopt::Std;
use IO::Socket;
use IO::Socket::SSL;
use Mail::IMAPClient;
use File::Temp qw/tempfile/;
use Carp;
use Date::Parse;
use DBI;

my $VERSION = '2.4.0';

my %opts;
getopts('DScdfhlsvVF:H:i:o:u:p:P:B:',\%opts);

version_exit($VERSION) if $opts{V};
help_exit() if $opts{h};
help_exit() if not ($opts{u} and $opts{p});

my $DEBUG       = $opts{D};
my $subfolders  = $opts{S};                     # only subfolders
my $count       = $opts{c};
my $delete      = $opts{d};                     # delete mail on server
my $user        = $opts{u};
my $with_folders = $opts{f};
my $folder      = $opts{F};
my $ssl         = $opts{s};
my $pass        = $opts{p};
my $inbox       = $opts{i}  || '/srv/benno/inbox'; # load mails to inbox directory 
my $host        = $opts{H}  || 'localhost';
my $verbose     = $opts{v};
my $port_arg    = $opts{P};
my $version     = $opts{V};
my $sqllite  	= $opts{B};
my $sqllite  	= $opts{B};
my $real_to     = $opts{r};
my $older_ts    = $opts{o};


my $dbh;
my $sth;

$with_folders = 1 if $subfolders;

my $db = 1 if $sqllite;

if($db){
   #### CONNECT TO SQLLITE DB AND CREATE TABLE IF NOT EXIST
   my $driver   = "SQLite"; 
   my $database = $sqllite."/benno_imap.db";
   my $dsn = "DBI:$driver:dbname=$database";
   my $userid = "";
   my $password = "";

   $dbh = DBI->connect($dsn, $userid, $password, { RaiseError => 1 }) 
                      or die $DBI::errstr;
   $dbh->do("CREATE TABLE IF NOT EXISTS mails (id INTEGER PRIMARY KEY, msg_id VARCHAR(500), msg_date VARCHAR(20))") or die $DBI::errstr;

}



# port_arg with predence
my $port = $port_arg ne ''  ? $port_arg
         : $ssl             ? 993
         :                    143
         ;

my $socket;
if ($ssl) {
    $socket = IO::Socket::SSL->new(
        Proto           => 'tcp',
        PeerAddr        => $host,
        PeerPort        => $port,
        SSL_verify_mode => 0x00,
    ) or die "Cannot connect to $host with SSL";
}
else {
    $socket = IO::Socket::INET->new(
        Proto    => 'tcp',
        PeerAddr => $host,
        PeerPort => $port,
    );
}

if ($verbose || $DEBUG) {
    print 'Connect to '.$socket->peerhost.':'.$socket->peerport."\n";
}

my $imap = Mail::IMAPClient->new(
    User        => $user,
    Password    => $pass,
    Socket      => $socket,
) or die "Cannot connect to $host as $user: $@";;
$imap->Uid(1);
$imap->Debug($DEBUG) if $DEBUG;

$imap->login();

## check if login successfully
my $pos=index($@,"failed");
if($pos != -1){
  die "Could not login: $@\n";
}

my $sepChar = $imap->separator();

if ($imap->Authenticated()) {
    print "Login for user $user successful.\n\n" if $verbose;
}

$imap->Peek(1);     # don't set \Seen flag

my ($p_pfx,$p_sep);
if ($folder) {
    # fetch personal namespace
    my $prefix= $imap->namespace;
    # $prefix = [
    #             [   
    #               [$user_pfx,$user_sep],
    #               ... 
    #             ],  # or undef
    #             [   
    #               [ $shared_pfx,$shared_sep],
    #               ... 
    #             ],  # or undef
    #             [   
    #               [ $public_pfx, $public_sep],
    #               ...
    #             ], # or undef
    #           ]
    $p_pfx = $prefix->[0]->[0]->[0];
    $p_sep = $prefix->[0]->[0]->[1];
    if ($p_pfx) {
        $folder = $p_pfx.$p_sep.$folder;
    }
}
else {
    $folder = 'INBOX';
}

if (!$subfolders) {
    $imap->select($folder);
    if ($verbose) {
        print "Fetch $folder";
        print ': ',$imap->message_count,' messages(seen/unseen)' if $count;
        print "\n";
    }
    save_mails($imap,$inbox,$delete,$real_to,$older_ts) if $inbox;
    $imap->close();
}

if ($with_folders) {
    print "* Subfolders:\n" if $verbose;
    my @folders = $imap->folders() or die "Cannot list folders: $@\n";
    foreach my $subfolder (@folders) {
        $subfolder =~ s/\s+$//;
        if ($subfolder =~ /^INBOX$/) {
            next;
        }
        $imap->select($subfolder);
        #$subfolder =~ s/^INBOX$sepChar?(.+)$/$1/;
        if ($verbose) {
            print "  $subfolder";
            print ': ',$imap->message_count,' messages(seen/unseen)' if $count;
            print "\n";
        }
        save_mails($imap,$inbox,$delete,$real_to,$older_ts) if $inbox;
        $imap->close();
    }
}


my @raw_out = $imap->list() or die "Could not list: $@\n";
$imap->logout() or die "Could not logout: $@\n";

### SUBS ###
#

### save_mails
sub save_mails
{
    my ($imap,$inbox,$delete,$real_to,$older_ts) = @_;

    my $sync = 0;
    my $notsync = 0;

    my @msglist;
    if ($older_ts) {
        my $Rfc3501_date = $imap->Rfc3501_date($older_ts);
        @msglist = $imap->before($Rfc3501_date);
    }
    else {
        @msglist = $imap->messages();
    }
    foreach my $msg_id (@msglist) {
		##### count Mail
		##### IF DB is activ check if mail saved before
		if($db){
			my $messageId = $imap->get_header( $msg_id, "Message-Id" );
			my $messageDate = str2time(substr($imap->get_header( $msg_id, "Date" ),5));
			print $messageId." | ".$messageDate."\n" if ($DEBUG);
        	
			$sth = $dbh->prepare(
        		"SELECT id, msg_id, msg_date FROM mails WHERE msg_id = ? and msg_date = ?")
        		or die "prepare statement failed: $dbh->errstr()";
			$sth->execute($messageId,$messageDate) or die "execution failed: $dbh->errstr()";
			my @row = $sth->fetchrow_array();
        	my $row = scalar @row;
			print $row . " rows found.\n" if ($DEBUG);

			if( $row eq 0){
				###mail is not there -> save
				$sth = $dbh->prepare(
                        	'INSERT INTO mails (msg_id,msg_date) VALUES (?,?)')
                        	or die "prepare statement failed: $dbh->errstr()";
                $sth->execute($messageId,$messageDate) or die "execution failed: $dbh->errstr()";
				$sync++;
				print "save\n" if ($DEBUG);
			}else{
				###mail is there, skip 
				$notsync++;
				print "not save\n" if ($DEBUG);
				next;
			}
		}
	
		my ($fh,$tmpfile) = tempfile($msg_id.'_XXXXXXXXXXXX',
                                     DIR => $inbox,
                                     SUFFIX => '');

        if (defined $fh) {
            print $fh "X-REAL-RCPTTO: $real_to\r\n" if $real_to;
            $imap->message_to_file($fh,$msg_id);
            if (! $fh->close) {
                croak "Cannot write tempfile $tmpfile: $!\n";
            }
            my $emlfile = $tmpfile.'.eml';
            link $tmpfile, $emlfile;
            unlink $tmpfile;
            $imap->delete_message($msg_id) if $delete;
        }
    }
    if($db){
	$sth->finish;
    }
    print ":: Summery ::\nSync    = $sync\nNotsync = $notsync\n\n" if ($count && $db);
}

### help_exit
sub help_exit
{
    print "Aufruf: benno-imap [-D] -u <user> -p <pass> [-H <host>] [-P <port>] [-s] [-v]\n";
    print "        [-c] [-F <folder>] [-f][-S] [-i <inbox_dir>] [-d]\n";
    print "        [-r <address>] [-o <epochtime> ] [-v] [-V]\n";
    print "\n";
    print "  -u <username>  imap username\n";
    print "  -p <password>  imap password\n";
    print "  -H <host>      imap host (default localhost)\n";
    print "  -P <port>      imap port (default 143)\n";
    print "  -F <folder>    select folder <folder> after login (default INBOX)\n";
    print "  -i <inbox_dir> save mails in <inbox_dir> (/srv/benno/inbox)\n";
    print "  -B <DB Path>   work with SQLLITE DB to sync only new mails\n";
    print "  -f             with subfolders\n";
    print "  -S             only subfolders (implies -f)\n";
    print "  -s             imaps (default port 993)\n";
    print "  -c             count messages in folder\n";
    print "  -d             delete mails on server after stored in inbox\n";
    print "  -o <epochtime> import mails older epoch timestamp\n";
    print "  -r <address>   add address as X-REAL-RCPTTO header\n";
    print "  -v             verbose\n";
    print "  -V             print version and exit\n";

    exit 1;
}

sub version_exit
{
    my $version = shift;
    print "$0 $version\n";
    exit 0;
}

