#!/usr/bin/perl

use Socket;
use Curses;

# detach
chdir("/");
if($pid = fork()) {
  # parent
  exit;
}

$SIG{'TERM'} = sub { endwin; exit; };
$SIG{'INT'} = sub { endwin; exit; };
$SIG{'ALRM'} = sub { $sleep = 80; }; # breaks out of the sleep and continues

$hdrCol = 1;
$errCol = 2;
$chrCol = 3;
$dtCol = 4;
$frmToCol = 5;
$subjCol = 6;
$lineCol = 7;

$tty = $ARGV[0];
if(! $tty) { print "Must supply a device to display on.\n"; exit; }

$r = open(STDIN, "<$tty");
if($r) { $r = open(STDOUT, ">$tty"); }
if(! $r) { print "Couldn't open the specified device.\n"; exit; }

# initialise curses, hide cursor, set up my colour scheme
$fullWin = initscr;
start_color;
curs_set(0); # invisible

init_pair($hdrCol, COLOR_WHITE, COLOR_BLUE);    # top-line header
init_pair($errCol, COLOR_RED, COLOR_BLACK);     # errors
init_pair($chrCol, COLOR_YELLOW, COLOR_BLACK);  # id character
init_pair($dtCol, COLOR_CYAN, COLOR_BLACK);     # date/time
init_pair($frmToCol, COLOR_WHITE, COLOR_BLACK); # from/to addresses
init_pair($subjCol, COLOR_GREEN, COLOR_BLACK);  # subject
init_pair($lineCol, COLOR_BLACK, COLOR_WHITE);  # progress line

# draw header
attron($fullWin, COLOR_PAIR($hdrCol));
addstr(0, 0, "@ Date        To         From                  Subject                          ");
refresh;

$win = newwin(0, 0, 1, 0);
scrollok($win, 1);

$currLine = 0;

for(;;) {
  # fetch mail from these accounts
  # id char, server, local hostname, username, password, forced to-addr
  &getMail("A", "myISP-A.com", "myMachine.myISP-A.com", "myMachine", "myPass", "");
  &getMail("B", "myISP-B.com", "myLocalMachine", "ispBuser", "myPass2", "root");

  attron($win, COLOR_PAIR($lineCol));

  for($sleep = 0; $sleep < 80; $sleep++) {
    sleep(1.5*60); # 1.5 minutes

    if($sleep < 79) { # don't do the last one.  it'll scroll
      addstr($win, $currLine, $sleep, " ");
      refresh($win);
    }
  }

  # clear the line off
  attron($win, A_REVERSE); # save me defining a whole new colour
  addstr($win, $currLine, 0, " " x 79);
  attroff($win, A_REVERSE);
  refresh($win);
}

exit;

# display error (and (maybe) close connection)
sub rptError {
  ($msg, $ch, $close) = @_;

  $msg = "$ch " . &getDate . "$msg";

  attron($win, A_BOLD);
  attron($win, COLOR_PAIR($errCol));
  addstr($win, $currLine, 0, $msg . " " x (80 - length($msg))); # force scroll
  attroff($win, A_BOLD);

  refresh($win);

  $currLine++;
  if($currLine == 24) { $currLine = 23; }

  if($close) {
    print SERV "QUIT\n";
    $ok = <SERV>;
    close(SERV);
  }
}

sub getMail {
  ($char, $server, $local, $user, $pass, $forceTo) = @_;

  # connect to server
  $port = getservbyname('pop3','tcp') || 110;
  $saddr = inet_aton($server) || (rptError("Couldn't resolve server: $!", $char), return);
  $servdat = sockaddr_in($port, $saddr);
  $proto = getprotobyname('tcp') || 6;

  socket(SERV,PF_INET,SOCK_STREAM,$proto) || (rptError("socket: $!", $char), return);
  connect(SERV,$servdat) || (rptError("connect: $!\n", $char), return);

  select((select(SERV),$| = 1)[0]);

  # ignore banner
  chop($ok = <SERV>);
  if($ok !~ /^\+OK/) { (rptError("Server connect error: $ok", $char, 1), return); }

  # username
  print SERV "USER $user\n";
  chop($ok = <SERV>);
  if($ok !~ /^\+OK/) { (rptError("Couldn't login: USER failed ($ok)", $char, 1), return); }

  # password
  print SERV "PASS $pass\n";
  chop($ok = <SERV>);
  if($ok !~ /^\+OK/) { (rptError("Couldn't login: PASS failed ($ok)", $char, 1), return); }

  # get message count
  print SERV "STAT\n";
  chop($ok = <SERV>);
  if($ok !~ /^\+OK/) { rptError("STAT failed ($ok)", $char, 1); }
  ($msgs) = ($ok =~ /^\+OK (\d+)/);

  if($msgs > 0) {
    $fileNum = 1; while(-e "/tmp/pulled$fileNum") { $fileNum++; }

    for($i = 1; $i <= $msgs; $i++) {
      $shown = 0; # shown header info yet?

      $subj = 0;
      $to = 0;   # ie, unset
      $from = 0;

      attron($win, COLOR_PAIR($chrCol));
      addstr($win, $currLine, 0, "$char ");
      refresh($win);

      # open file to write mail to
      open(FILE,">/tmp/pulled$fileNum");

      # fetch mail
      print SERV "RETR $i\n";
      chop($ok = <SERV>);
      if($ok !~ /^\+OK/) { rptError("RETR failed ($ok)", $char, 1); }

      while(<SERV>)
      {
        # done if . on a blank line
        last if /^\.\r?$/;

        # blank line and not shown headers => end of headers
        if(/^\r?$/ && !($shown))
        {
           &showinfo;
           $shown = 1;
        }

        # To address - the first is an ftechism
        if(/^X-Frontier-To:/i || /^Apparently-To:/i || /^To:/i)
        {
           if(!($to)) { &getto($_); }
        }

        # From address
        if(/^From:/i && (!($from))) { &getfrom($_); }

        # Subject - skip leading spaces
        if(/^Subject:/i && (!($subj))) { ($subj) = /^Subject:\s+(.*)$/;
                                         $subj =~ tr/\r\n//d; }

        # sendmail'd handle this, if I was using sendmail
        if(/^From /) { s/^F/>F/; }

        print FILE ;
      }
      close(FILE);

      if($forceTo ne "") {
        $to = $forceTo;
      } else { # default to postmaster
        ($to eq "(nobody)") && ($to = "postmaster");
        $to =~ tr/A-Z/a-z/;
      }

      # received date
      chop($date = `date`);
      # ignore timezone
      $date =~ s/ GMT//;
      $date =~ s/ BST//;

      # write From and Received lines
      open(MBOX,">>/var/spool/mail/$to");
      print MBOX "From $realfrom $date\n";
      print MBOX "Received: from $server by $local via pop-puller for $to\@$local; $date\n";
      close(MBOX);

      # then copy fetched mail into mailbox
      system("cat /tmp/pulled$fileNum >> /var/spool/mail/$to");

      print SERV "DELE $i\n";
      chop($ok = <SERV>);
      if($ok !~ /^\+OK/) { rptError("Couldn't delete mail: $ok", $char, 1); }
    }
    $fileNum++; # and on to the next message
  }

  # all done
  print SERV "QUIT\n";
  chop($ok = <SERV>);
  if($ok !~ /^\+OK/) { rptError("Quit failed: $ok", $char, 1); }
  close(SERV) || rptError("Close failed: $!", $char, 1);
}

sub showinfo
{
  # set defaults, truncate to max length
  if(!($to)) { $to = "(nobody)"; }
  $to = substr($to,0,10); # 10 = postmaster.  usual is max 8.

  if(!($from)) { $from = "(nobody)"; $realfrom = "(nobody)"; }
  $from = substr($from,0,21);
 
  if(!($subj)) { $subj = "(none)"; }
  $subj = substr($subj,0,33);

  attron($win, COLOR_PAIR($dtCol));
  addstr($win, $currLine, 2, &getDate);

  attron($win, COLOR_PAIR($frmToCol));
  addstr($win, $currLine, 14, "$to ");
  addstr($win, $currLine, 25, "$from ");

  attron($win, COLOR_PAIR($subjCol));
  addstr($win, $currLine, 47, $subj . " " x (33 - length($subj))); # to force
                                                                   #  scrolling
  refresh($win);

  $currLine++;
  if($currLine == 24) { $currLine = 23; }
}

sub getto
{
   ($text) = @_;

   ($to,$mach) = ($text =~ /^.*:\s+(.+)@(.*)$/);
   $to =~ s/^\./-/;
   $to =~ s#/#\.#g;
}

sub getfrom
{
   $_ = $_[0];
  
   if(/^From:? .+\s+<.+>/)
   {
      ($from,$realfrom) = /^From:\s+(.+)\s+<(.+)>/;
      if($from =~ /^".+"$/) { ($from) = ($from =~ /^"(.+)"$/); }
   }
   elsif(/^From:?\s+.+\s+\(.+\)/)
   {
      ($realfrom,$from) = /^From:?\s+(.+)\s+\((.+)\)/;
   }
   else
   {
      ($from) = /^From:?\s+(\S+)/;
      $realfrom = $from;
   }
}

sub getDate {
  ($sec,$min,$hour,$mday,$mon,@stuff) = localtime(time);
  $mon++; # annoying 0-11-ness
  return sprintf("%02d/%02d %02d:%02d ", $mday, $mon, $hour, $min);
}
