#!/usr/bin/perl
#
# spamgourmet is provided under the Artistic License of the Open Source Initiative
# A copy of the license should be provided with this distribution

use strict;
use lib "/path/to/modules";
use Mail::Spamgourmet::Config;


# these dependencies are listed here for reference -- they're loaded later on an as-needed basis:
#use DBD::mysql;
#use Digest::MD5 qw(md5_hex);
#use Mail::Spamgourmet::Util;
#use Mail::Spamgourmet::MessageIdChecker;
####################### path to config file #######################

my $configfile = "/path/to/spamgourmet.config";

##################### try not to edit anything below ##############

use vars qw {$config $util $mailer $checker};
$config = Mail::Spamgourmet::Config->new(configfile=>$configfile,mode=>0);

$config->{'debugfilename'} = '/path/to/outbound.log';

#my $extradebug = 1;
my $extradebug = 0;
$config->debug('outbound started') if $extradebug;

# variables to be used by the main routine (don't change these)
my (
 $delimiters,$headers,$inHeaders,$line,$msg,$subjecttext,%headerValues,%headerTokens, 
 $from,$replyto,$to,$for,$fromdomain,$todomain,$forname,$allRecipients,
 %deliveryAddresses,$subfrom,$subreplyto,$fromname,$replytoname,$trusted,
 $disposable,$disposableID,$PublicHash,$PrivateHash,
 $mprefix,$mword,$musername,$mcount,$mexpiretime,
 $db,$sql,$st,%attr,$now,$nowcount,@Watchwords,
 $Prefix,$EmailID,$InitialCount,$Count,$UserID,$ExpireTime,
 $RealEmail,$SpamEmail,$Sender,$Features,$updatelog,$XHeader, $Hidden,
 $connected, $connectTries, $interval,$DefaultNumber,
 $RecThrottleTime, $RecThrottleCount, 
 $SendThrottleTime, $SendThrottleCount
 );


use constant EX_TEMPFAIL    => 75; # temp failure; user is invited to retry

# snarf the message line by line
# get 'From' data (first occurrence)
# get 'for' data (first occurence)
# get 'To' data (first occurence)
# get 'Reply-To' (first occurence)
# if message is to $otherdomainemail
#  then it was forwarded from another domain,
#  so look at 'Delivered-To' instead
$inHeaders = 1;  # flag that will be set to 0 after headers have been parsed
my $currHeader;

$headerTokens{'from'} = 'From';
$headerTokens{'to'} = 'To';
$headerTokens{'cc'} = 'CC';
$headerTokens{'replyto'} = 'Reply-To';
$headerTokens{'messageid'} = 'Message-ID';

#if ($ENV{'RECIPIENT'}) { # Exim
#  $headerValues{'to'} = $ENV{'RECIPIENT'};
#  $headerValues{'from'} = $ENV{'SENDER'};
#  $headerValues{'replyto'} = $ENV{'REPLYTO'};
#  $inHeaders = 0; # got relevant info from environment - yay!
#}
if ($ENV{'RECIPIENT'}) { # Exim
  $headerValues{'to'} = $ENV{'RECIPIENT'};
  $headerValues{'namedTo'} = $ENV{'TO'};
  $headerValues{'from'} = $ENV{'FROM'};
  $headerValues{'sender'} = $ENV{'SENDER'};
  $headerValues{'replyto'} = $ENV{'REPLYTO'};
  $inHeaders = 0; # got relevant info from environment - yay!
}

while (defined($line = <STDIN>)) {
  $msg .= $line;  # accumulate message line by line into $msg variable
  if ($XHeader) {
    if ($line =~ /X-Spamgourmet\: (.*)$/) {
      $XHeader = $1;
    }
  }
}
if ($extradebug) {
  $config->debug('--------------start original message----------------------');
  $config->debug($msg);
  $config->debug('--------------end original message----------------------');
}

$now = time();

use DBD::mysql;
use Digest::MD5 qw(md5_hex);
use Mail::Spamgourmet::Util;
use Mail::Spamgourmet::MessageIdChecker;

$util = Mail::Spamgourmet::Util->new(config=>$config);
$mailer = $config->getMailer();


#$config->debug($msg);

## clean up the from, to, and for chunks, hopefully leaving just the addresses
($from, $fromname) = $util->getAddressAndDisplay($headerValues{'from'}, 1);
($replyto, $replytoname) = $util->getAddressAndDisplay($headerValues{'replyto'}, 1);
    
$allRecipients = $for . ',' . $headerValues{'to'} . ',' . $headerValues{'cc'};

$config->debug("allrecips is $allRecipients") if $extradebug;

## get the domain from the from address
$from =~ /\@(.*)/;
$fromdomain = $1;

#check for fake admin messages
if ($config->isLocalDomain($fromdomain)) {
  if ($from =~ /^info@/i
   || $from =~ /^abuse@/i
   || $from =~ /^postmaster@/i
   || $from =~ /^privacy@/i
   || $from =~ /^admin@/i
   || $from =~ /^administrator@/i
   || $from =~ /^root@/i
   || $from =~ /^register@/i
   || $from =~ /^support@/i
   || $from =~ /^webmaster@/i
   ) {
    # insert validation code here:
    if ($fromname !~ /josh/) {
      $config->debug("attempt at forged admin message from $from (fromname: $fromname");
      #$config->debug($msg);
      $config->die() if $config;
      exit;
    }
  }
}

#my $pcpu = `ps h -p $$ -o pcpu`;
#chomp ($pcpu);
#if ($pcpu > 15) {
#  $config->debug("heavy cpu ($pcpu) -> enabling extradebug");
#  $extradebug = 1;
#}


#$config->debug($msg) if $extradebug;
#if (keys %deliveryAddresses > 1) {
#  $config->debug("handling multiple delivery addresses - turning on extradebug");
#  $extradebug = 1;
#}

my @to = split(',', $allRecipients);
foreach $to (@to) {
  ($to, undef) = $util->getAddressAndDisplay($to,0);
  $deliveryAddresses{lc($to)} = 1;
}



foreach $for (keys %deliveryAddresses) {
  $config->debug("handling $for") if $extradebug;

  # initialize the vars:
  ($UserID,$RealEmail,$SpamEmail,$Prefix,$Features,
   $EmailID,$InitialCount,$Count,$Sender,$PrivateHash,$Hidden,
   $ExpireTime,$trusted,@Watchwords) =
   (0,'','','',0,0,0,0,0,0,0,'','',0,'',0,());

  ($mprefix,$mword,$mcount,$musername,$mexpiretime) = ('','','','','');

  ## check to see if this is *from* a user first (the *new* *new* way):
#  if ($for =~ /^(.+)\.(.+)\.(\w+)\.(.+)\@/) {
  if ($for =~ /^([^\.]+)\.([^\.]+)\.(\w+)\.(.+)\@(.*)/) {

    $mword = $1;
    $musername = $2;
    my $checkTo = $3;
    $subfrom = $4;
    $todomain = $5; # this should be the outbound system domain

    $sql = "SELECT UserID, Features, RealEmail, SendThrottleTime, SendThrottleCount FROM Users where UserName = ?";
    $st = $config->db->prepare($sql);
    $st->execute($musername);
    $st->bind_columns(\%attr,\$UserID,\$Features,\$RealEmail, \$SendThrottleTime,\$SendThrottleCount);
    $st->fetch();

    if (!$util->hasFeature('ACCOUNTDISABLED', $Features)) {

      $sql = "SELECT PrivateHash, Address FROM Emails WHERE UserID = ? AND Word = ?";
      $st = $config->db->prepare($sql);
      $st->execute($UserID, $mword);
      $st->bind_columns(\%attr,\$PrivateHash,\$disposable);
      $st->fetch();

      my $checkFrom = $util->getShortHash($subfrom,$PrivateHash);

      my $checkFrom1 = substr(md5_hex("$PrivateHash$subfrom"),22,32);
      if ($disposable && ($checkTo eq $checkFrom || $checkTo eq $checkFrom1) ) {
        if ($SendThrottleCount < $config->getMaxSendCount()
         || $SendThrottleTime < $now - $config->getSendThrottleInterval()) {

          $subfrom =~ s/\#/\@/;
          $msg =~ s/\Q$for\E/$subfrom/gmi; #try to get the whole redir address - probably won't work

          $msg =~ s/\w+\.\w+\.$checkTo\.\w+\#//gmi; # focus on hash 
          $msg =~ s/\w+\.\w+\@$todomain/$subfrom/gmi; # and try to swap system domain for recip

          $msg =~ s/\Q$from\E/$disposable/gmi;
          $msg =~ s/\Q$RealEmail\E/$disposable/gmi;
          $msg =~ s/(^To\: ).*$/$1 $subfrom/mi;
          $msg =~ s/(Subject: .*)\s\(.*\n/$1\n/mgi;
          if ($subfrom && $config->hasLocalDomain($subfrom)) {
            # this message is coming right back to us, so we need to clear the id
            #$checker->clearMessageId($headerValues{'messageid'});
          }
          my ($senderdisplay,$replytodisplay) = ('','');

          if ($util->hasFeature('PRESERVEREDIRECTDISPLAYNAME', $Features)) {
#$config->debug("getting display for $headerValues{'from'}");
             ($from, $fromname) = $util->getAddressAndDisplay($headerValues{'from'}, 0); # don't combine with real address!
             ($replyto, $replytoname) = $util->getAddressAndDisplay($headerValues{'replyto'}, 0);
#$config->debug("from: $from / fromname: $fromname");
             ($senderdisplay,$replytodisplay) = ($fromname,$replytoname);
          }
#else {$config->debug("no display");}
#$config->debug("about to call sendMail - senderdisplay is $senderdisplay");
          &sendMail($config,$XHeader,$subfrom,\$msg,'',$disposable,$disposable,$senderdisplay,$replytodisplay);

#          &sendMail($config,$XHeader,$subfrom,\$msg,'',$disposable, $disposable, $fromdisplay);

          if ($SendThrottleTime < $now - $config->getSendThrottleInterval()) {
            $SendThrottleTime = $now;
            $SendThrottleCount = 1;
          } else {
            $SendThrottleCount ++;
          }
          $sql = "UPDATE Users SET SendThrottleTime = ?, SendThrottleCount = ? WHERE UserID = ?";
          $st = $config->db->prepare($sql);
          $st->execute($SendThrottleTime, $SendThrottleCount, $UserID);
        } else {
          $config->debug("send throttle is in effect: stt: $SendThrottleTime, stc: $SendThrottleCount, userID: $UserID");
          $sql = "UPDATE Users SET Features = (Features * 13) WHERE UserID = ?";
          $st = $config->db->prepare($sql);
          $st->execute($UserID);
          $config->debug("disabled user $musername ($UserID) due to SendThrottle");
        }
      } else {
        $config->debug("skipping an attempted send: disposable: $disposable, checkTo: $checkTo, checkFrom: $checkFrom, checkFrom1: $checkFrom1, userID: $musername($UserID)");
      }
    } else {
      $config->debug("skipping an attempted send for disabled user: $musername ($UserID)");
    }
    next; # move on to the next recipient 
  }

}
if ($st) {
  $st->finish();  # flush the statement handler
}
$config->debug("outbound exiting") if $extradebug;
$config->die();

exit; # adios


sub sendMail {
  my $config = shift;
  my $XHeader = shift;
  my $rcpt = shift;
  my $msgref = shift;
  my $subjecttext = shift;
  my $sender = shift;
  my $replyto = shift;
  my $senderdisplay = shift;
  my $replytodisplay = shift;

  return if !$rcpt;
  $config->debug("sending...") if $extradebug;
  $config->debug("in sendMail - sender: $sender / senderdisplay: $senderdisplay") if $extradebug;

#  if ((!$useXHeader || $useXHeader==2 && $subjecttext =~ /message/) && $subjecttext) {
#    my $check = $$msgref =~ s/(^Subject\:.*$)/$1$subjecttext/mi;
#    if (!$check) {
#      $$msgref =~ s/(Date: )/Subject: $subjecttext\n$1/;
#    }
#  } else {
#    $$msgref =~ s/\n\n/\nX-Spamgourmet: $subjecttext\n\n/;
#  }
  if ($senderdisplay) {
    $senderdisplay = "\"$senderdisplay\" <$sender>";
  } else {
    $senderdisplay = $sender;
  }
#$config->debug("in sendMail - senderdisplay: $senderdisplay");
  if ($sender) {
#    $$msgref =~ s/(^Return-Path\: ).*$/$1 $sender/mi;
#    $$msgref =~ s/(^From\: ).*$/$1 $senderdisplay/mi;
#    $$msgref =~ s/(^Sender\: ).*$/$1 $sender/mi;
#    $$msgref =~ s/(^X-Sender\:).*$/$1 $sender/mi;
#    $$msgref =~ s/(^X-Sent-From\:).*$/$1 $sender/mi;
#    $$msgref =~ s/(^Disposition-Notification-To:).*$/$1 $sender/mi;
    $$msgref =~ s/(^Return-Path\: ).*$(?:\n .*$)*/$1 $sender/mi;
    $$msgref =~ s/(^From\: ).*$(?:\n .*$)*/$1 $senderdisplay/mi;
    $$msgref =~ s/(^Sender\: ).*$(?:\n .*$)*/$1 $sender/mi;
    $$msgref =~ s/(^X-Sender\:).*$(?:\n .*$)*/$1 $sender/mi;
    $$msgref =~ s/(^X-Sent-From\:).*$(?:\n .*$)*/$1 $sender/mi;
    $$msgref =~ s/(^Disposition-Notification-To:).*$(?:\n .*$)*/$1 $sender/mi;
  }
  if ($replyto) {
    if ($replytodisplay) {
      $replytodisplay = "$replytodisplay <$replyto>";
    } else {
      $replytodisplay = $replyto;
    }
#    $$msgref =~ s/(^Reply-To\: ).*$/$1 $replyto/mi;
    $$msgref =~ s/(^Reply-To\: ).*$(?:\n .*$)*/$1 $replyto/mi;
  }
  $sender =~ s/.*\<//;
  $sender =~ s/\>.*//;

  $config->debug($$msgref) if $extradebug;
  $config->getMailer()->sendMail($msgref,$rcpt,$sender);
  $$msgref =~ s/$XHeader// if $XHeader;
  $$msgref =~ s/$subjecttext//mi if $subjecttext; #now get rid of the subject text for the next iteration
}

