#!/usr/bin/env perl
#
# parp -- Perl Anti-spam Replacement for Procmail
#
# Copyright (c) 1999--2001 Adam Spiers <adam@spiers.net>. All rights
# reserved. This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#
# $Id: parp,v 1.126 2002/01/02 00:17:38 localadams Exp $
#

use strict;
use warnings;

require 5.6.0;
our $VERSION = '0.58';

use Fcntl qw(:flock);
use Mail::Internet;
# use MIME::Tools;
# MIME::Tools->debugging(1);
use Mail::Box::Manager;
use Proc::Daemon;

use Mail::Filterable;
use Parp::Filter  qw(filter);
use Parp::IdCache;
use Parp::Friends;
use Parp::Options qw(opt opts usage);
use Parp::Utils   qw(init_log close_log vprint
                     log_to_file log_rule log_mode 
                     error fatal);

$SIG{TERM} = $SIG{INT} = $SIG{QUIT} = \&quit_handler;
$SIG{__DIE__} = \&die_handler;

# Process options
Parp::Options::process();

# Prepare for output
$| = 1;
init_log() unless opt('test_run');

Parp::Friends::init();
Parp::IdCache::init();

if (opt('filter_files')) { 
  filter_argv();
}
elsif (opt('daemon')) {
  do_daemon_mode();
}
else {
 do_filter_mode();
}

exit 0;

sub do_daemon_mode {
  usage() if @ARGV;

  Proc::Daemon::Init();

  init_log(); # Proc::Daemon closes all open handles
  my $spool_dir = opt('daemon');
  opendir(SPOOL, $spool_dir)
    or fatal(7, "Couldn't open $spool_dir: $!");
  
  my $now = localtime();
  log_mode "pid $$ started in daemon mode at ", scalar(localtime());

  while (1) {
    my @mails = grep /^\d{9,}\.\d{1,}/, readdir(SPOOL);
    filter_incoming(@mails);
    rewinddir(SPOOL);
    sleep(1);
  }
}

sub filter_incoming {
  foreach my $file (@_) {
    my $full = opt('daemon') . "/$file";
    open(MAIL, $full) or error("Couldn't open $full: $!");
    flock(MAIL, LOCK_EX | LOCK_NB) or next;
    my $mail = new Mail::Internet( [<MAIL>] );
    filter->process_mail($mail);
    close(MAIL);
    unlink $full;
  }
}

sub do_filter_mode {
  # Behave like a filter; take one e-mail from STDIN

  usage() if -t;
  usage() if @ARGV;

  my $mail = new Mail::Internet( [<STDIN>] );
  filter->process_mail($mail);
}

sub filter_argv {
  # Filter all folders given in @ARGV

  usage() unless @ARGV;

  log_mode "pid $$ started run on folders in ARGV at ", scalar(localtime());

  my %counts = (
    parsed  => 0,
    total   => 0,
    main    => 0,
    aux     => 0,
    spam    => 0,
    dups    => 0,
    special => 0,
  );
  my %inodes_seen = ();

  foreach my $file (@ARGV) {
    unless (-f $file) {
      # TODO: allow symlinks
      vprint "Skipping non-file $file.\n";
      next;
    }

    my ($inode, $size) = (stat $file)[1, 7];

    if ($size == 0) {
      vprint "Skipping empty file $file.\n";
      next;
    }

    if ($inodes_seen{$inode}) {
      vprint "Skipping $file\n" .
             "  (already seen file $inodes_seen{$inode} with inode $inode\n";
      next;
    }
    $inodes_seen{$inode} = $file;

    filter_folder($file, \%counts);
  }

  # FIXME
#   log_to_file <<EOF;
# Parsed $counts{parsed} of $counts{total} messages:
#   delivered $counts{main} to $CONFIG{main_folder}
#   delivered $counts{aux} to auxiliary folders
#   tagged $counts{spam} as spam
#   discarded $counts{dups} as duplicate
#   tagged $counts{special} as special
# EOF
  log_to_file "pid $$ ended run at ", scalar(localtime()), "\n";
  log_rule('=');
}

sub filter_folder {
  my ($file, $counts) = @_;

  vprint "Reading $file ... ";
  my $mgr = Mail::Box::Manager->new();
  my $folder = $mgr->open(folder => $file,
                          lazy_extract => 'NEVER'
                         );
  vprint "done.\n";

  my ($file_total, $friends) = (0, 0);

  my ($sample, $extract_friends) = opts(qw/sample extracted_friend/);

  foreach my $mail ($folder->allMessages()) {
    $counts->{total}++;

    $mail->{parp_foldername} = $file;
    if (! $mail) {
      error(ref($folder) . '::allMessages() failed',
            "\$mail:\n", Dumper $mail);
      next;
    }

    my $rv = filter->process_mail($mail);
    $counts->{parsed}++  if $rv;
    $counts->{dups}++    if $rv =~ /IS_DUPLICATE/;
    $counts->{spam}++    if $rv =~ /IS_SPAM/;
    $counts->{main}++    if $rv =~ /TO_MAIN/;
    $counts->{aux}++     if $rv =~ /TO_AUX/;
    $counts->{special}++ if $rv =~ /IS_SPECIAL/;
    $counts->{friends}++ if $rv eq 'EXTRACTED_FRIEND';
    $counts->{file_total}++;

    last if $sample && $counts->{total} >= $sample;
  }

  $mgr->close($folder);
}

sub quit_handler {
  my ($sig) = @_;
  log_mode "pid $$ caught a SIG$sig -- shutting down at ", scalar(localtime());
  exit 0;
}

sub die_handler {
  my ($error) = @_;
  error($error, "Called via DIE handler\n");
  exit 255;
}
