#!/usr/bin/perl -w # ================================================================== # Gossamer Mail - enhanced email management system # # Website : http://gossamer-threads.com/ # Support : http://gossamer-threads.com/scripts/support/ # Revision : $Id: incoming.pl,v 1.61 2002/06/11 00:08:59 brewt Exp $ # # Copyright (c) 2001 Gossamer Threads Inc. All Rights Reserved. # Redistribution in part or in whole strictly prohibited. Please # see LICENSE file for full details. # ================================================================== # Pragmas use strict; use lib '../admin'; use vars qw/$CLEANUP $VALID_CHARACTERS $JUNK_USER $POP $MBOX @INDEX $ENV_TO/; # Internal Modules use GMail qw/:objects :folders ADMIN/; use GMail::Admin; use GMail::Messages; use GMail::Options; use GMail::Options::Filters; use GMail::Options::Spam; use GMail::Folders; use GMail::Auth; use GMail::User; # GT modules use GT::Mail; use GT::Mail::POP3; use GT::Mail::Parse; # System modules use Fcntl qw/:DEFAULT :flock/; use Getopt::Long; # Error handling local $SIG{__DIE__} = \&term_fatal; # Globals $VALID_CHARACTERS = '[\d\w\-.]+'; # Constants sub SEEK_SET () { 0 } # Start $PLG->dispatch("incoming::main", \&main); sub main { # ---------------------------------------------------------------------------- # # Give our temp module a place to put tmp files $ENV{GT_TMPDIR} = "$CFG->{location}->{path}->{data}/tmp"; $ENV_TO = ''; if ($ENV{REQUEST_METHOD}) { $| = 1; print $IN->header(), "
";
        $CFG->{debug} = 1;
        open STDERR, ">&STDOUT" or die "Couldn't redirect STDERR to STDOUT!";
        select((select(STDERR), $|=1)[0]);
    }    

# Declare for getopts
    my ($mbox, $pipe, $email, $pop, $verbose, $help);
    
# Set defaults
    if ($CFG->{email}->{pop} eq 'shared_pipe') {
        $pipe = 1;
    }
    elsif ($CFG->{email}->{pop} eq 'mbox') {
        $mbox = 1;
    }
    else {
        $pop = 1;
    }
    $verbose = 0;
    GetOptions(
        'mbox'              => \$mbox,
        'pipe'              => \$pipe,
        'email-address=s'   => \$email,
        'catch-all'         => \$pop,
        'verbose'           => \$verbose,
        'help'              => \$help
    ) or exit;
    $MBOX = $mbox;

    lock_me() unless $MBOX;

# Are we running in debug mode
    if ($verbose or $ENV{REQUEST_METHOD}) {
        $CFG->{debug} = 1;
    }
    $GMail::DEBUG = $CFG->{debug};

# If help is requested print usage and exit
    $help and return usage();

    ## Error checking ##

# No domains
    GMail->domain_list() or return usage("No domains have been set up. Please configure incoming email before running incoming.pl.");
    
# Can't specify multiple ways to get input for parsing
    if (
        ($mbox and $pipe) or
        ($mbox and $pop) or
        ($pipe and $pop)
    )
    {
        usage("Only one of --mbox, --catch-all, or --pipe may be specified.");
    }

# Mbox mode
    elsif ($mbox) {
        @{$CFG->{email}->{mbox}} or return usage("No accounts have been set up for mbox mode.");
        for my $account (@{$CFG->{email}->{mbox}}) {
            $PLG->dispatch('incoming::get_mbox', \&get_mbox, $account);
        }
    }

# pipe mode
    elsif ($pipe) {
        if (!$email) {
            $email = $ARGV[0];
        }
# qmail sets a RECIPIENT environment variable, check if we have one.
        if (!$email) {
            $email = $ENV{RECIPIENT};
            $email =~ m/\@(.+)/;
            my $f_dom = $1;
            $email =~ s/^\Q$f_dom\E-//i;
            $ENV_TO = $email;
        }
        $email or return usage("The account you want the mail to be inserted into must be specified with --email-address when in pipe mode.");
        if (!$CFG->{email}->{shared_pipe} or !keys %{$CFG->{email}->{shared_pipe}}) {
            return usage("No accounts have been set up to run in shared pipe mode.");
        }
        exit($PLG->dispatch('incoming::get_input', \&get_input, $email));
    }

# POP3 mode
    else {
        @{$CFG->{email}->{shared_pop}} or return usage("No accounts have been set up from shared pop mode.");
        for my $account (@{$CFG->{email}->{shared_pop}}) {
            $PLG->dispatch('incoming::get_pop', \&get_pop, $account);
        }
    }
}

sub usage {
    my $msg = shift;
    $msg ||= '';
    $msg &&= "\n$msg\n";
    print <{email}->{shared_pipe}->{exit_failure};
    my $success = $CFG->{email}->{shared_pipe}->{exit_success};

# Makes sure we have a properly formated username
    my $in = shift;
    my $user;

    for (@{$CFG->{email}->{shared_pipe}->{domains}}) {
        if ($in =~ /($VALID_CHARACTERS\@\Q$_\E)/i) {
            $user = lc $1;
            last;
        }
    }
    $user or die "The email address ($in) does not have a domain we accept.\n";

# Get our mail object and and parse the email
    binmode STDIN;
    my $mail = new GT::Mail::Parse(
        debug     => $CFG->{debug},
        in_handle => \*STDIN
    );
    my $head = $mail->parse();

# Get the raw header
    my $raw_head = $head->header_as_string();
    my $size     = $mail->size();

# Checksum the message for caching and identifying
    my $checksum = GMail::Messages->get_message_checksum($head, $raw_head, $size);

# Make sure the user is in our system
    unless (my $pass = GMail::Auth->get_pass($user)) {
        GMail->debug("User '$user' is not in the authentication system. Skipping.") if $CFG->{debug};
        if ($CFG->{email}->{junk}->{auth}) {
            $PLG->dispatch('incoming::admin_insert', \&admin_insert, undef, $mail, $checksum);
        }
        if ($CFG->{email}->{bounce}->{auth}) {
            GMail->debug("Bouncing message, user '$user' is not in auth system.") if $CFG->{debug};
            $PLG->dispatch('incoming::bounce', \&bounce, undef, $mail, 'User not in authentication system');
            return $failure;
        }
        return 0;
    }

# Load the user into memory
    $USER = GMail::User->new();
    $USER->load_user($user);

# The user is in the auth system, if this fails there is a real problem
    $USER->check_create();

    if ($USER->{users_status} eq 'Not Validated') {
        if ($CFG->{email}->{junk}->{not_validated}) {
            $PLG->dispatch('incoming::admin_insert', \&admin_insert, undef, $mail, $checksum);
        }
        if ($CFG->{email}->{bounce}->{not_validated}) {
            GMail->debug("Bouncing message, user '$user' is not validated.") if $CFG->{debug};
            $PLG->dispatch('incoming::bounce', \&bounce, undef, $mail, "The account hasn't been enabled yet");
        }
        GMail->debug("Not inserting for '$user'. User is not validated.") if $CFG->{debug};
        return $failure;
    }

# Make sure the user can have this message
    my $ret = $PLG->dispatch('GMail::Messages::validate', sub { GMail::Messages->validate(@_) }, $size);
    
    if ($ret <= 0) {
        $GMail::Messages::error ||= 'UNKNOWN error from GMail::Messages';
        GMail->debug($GMail::Messages::error) if $CFG->{debug};

        if ($CFG->{email}->{junk}->{too_many}) {
            $PLG->dispatch('incoming::admin_insert', \&admin_insert, undef, $mail, $checksum);
        }

# User has too many messages
        if ($CFG->{email}->{bounce}->{too_many}) {
            my %error_msgs = (
                0  => "The user has exceeded the maximum number of allowed messages",
                -1 => "The user has exceeded the space quota",
                -2 => "The email has exceeded the maximum size of $CFG->{limits}->{msgs_size} bytes"
            ); 
            $PLG->dispatch('incoming::bounce', \&bounce, undef, $mail, $error_msgs{$ret});
        }
        return $failure;
    }

# If the user has this account set up to forward do that instead of inserting
    if ($USER->{opts}->{mailbox}->{forward_email} and $USER->{opts}->{mailbox}->{forward_email_to} and $CFG->{limits}->{forwards}) {
        $PLG->dispatch('incoming::forward', \&forward, undef, $mail);
        return $success;
    }

# If there is an autoreply for this user write it to the reply
# directory so outgoing.pl can send it.
    if ($USER->{opts}->{autoreply_ok} and $CFG->{limits}->{autoreplies}) {
        $PLG->dispatch('incoming::autoreply', \&autoreply, undef, $mail) or return $failure;
    }

# Check for filters
    my $spam = $PLG->dispatch('GMail::Options::Spam::process', sub { GMail::Options::Spam->process(@_) }, $mail->top_part() );
    my $fid  = $PLG->dispatch('GMail::Options::Filters::process', sub { GMail::Options::Filters->process(@_) }, $mail->top_part() );
    defined($fid) or $fid = INBOX;
    if ($fid == 0 or $spam == 1) {
        GMail->debug("Filter for ($user) matched, not inserting.") if $CFG->{debug};
        return $success;
    }

# Insert the email
    my ($msg_size, $mid) = $PLG->dispatch('GMail::Messages::insert_data', sub { GMail::Messages->insert_data(@_) }, $mail, $checksum);
    GMail->debug("Inserting message for user ($USER->{email}).") if $CFG->{debug};
    $PLG->dispatch('GMail::Messages::insert_user', sub { GMail::Messages->insert_user(@_) }, {
        msgtrack_fid     => $fid,
        msgtrack_userid  => $USER->{userid},
        msgtrack_status  => 'New',
        msgtrack_mid     => $mid,
        msgtrack_account => 0,
        msgtrack_look    => ($USER->{opts}->{display}->{default_look} || 'clear'),
    }, $msg_size);
    $USER->{opts}->{update_folders} = 1;
    GMail::Options->save();

    return $success;
}

sub get_pop {
# ----------------------------------------------------------------------------
# Get email from POP box and parse user from email.
#
    my $account = shift;

# Initilize the pop box.
# Connect to the POP server
    GMail->debug("Connecting to POP server $account->{account}.") if $CFG->{debug};
    $POP = GT::Mail::POP3->new(
        host      => $account->{account},
        port      => $account->{port} || 110,
        user      => $account->{login},
        pass      => $account->{password},
        auth_mode => $account->{apop} ? 'AUTH' : 'PASS',
        debug     => $CFG->{debug}
    );
    my $count = $POP->connect();
    if (!$count) {
        GMail->debug ("Unable to connect to POP: $GT::Mail::POP3::error");
        return;
    }
    GMail->debug(($count == 0 ? 0 : $count) . " message(s) on server.") if $CFG->{debug};

    if ($count) {
        my $cnt = process($account);
        if ($CFG->{debug}) {
            GMail->debug("$account->{account}: $cnt->{junk_count} message(s) inserted into admin account.");
            GMail->debug("$account->{account}: $cnt->{bounce_count} message(s) were bounced.");
            GMail->debug("$account->{account}: $cnt->{forward_count} message(s) were inserted for outgoing.pl to forward.");
            GMail->debug("$account->{account}: $cnt->{reply_count} message(s) were inserted for outgoing.pl to reply.");
            GMail->debug("$account->{account}: $cnt->{spam_count} message(s) were not inserted because they matched spam filters.");
            GMail->debug("$account->{account}: $cnt->{filter_count} message(s) were not inserted because they matched a delete filter.");
            GMail->debug("$account->{account}: $cnt->{insert_count} message(s) inserted.");
        }
    }
    else {
        GMail->debug("$account->{account}: No messages");
    }
}

sub get_mbox {
    my $account = shift;
    my $file    = $account->{path};
    $DB->table('msgs')->connect() or die $GT::SQL::error;
    -e $file or return;
    open MBOX, $file or die "Could not open $file; Reason: $!";
    flock(MBOX, LOCK_EX);
    my $file2 = "$file.$$.bak";
    rename $file, $file2 or die "Unable to rename $file to $file2; Reason: $!";
    if (-s MBOX > 0) {
        GMail->debug("Processing file $account->{path}; Size: " . -s _);
        my $cnt = process($account);
        if ($CFG->{debug}) {
            GMail->debug("$account->{path}: $cnt->{junk_count} message(s) inserted into admin account.");
            GMail->debug("$account->{path}: $cnt->{bounce_count} message(s) were bounced.");
            GMail->debug("$account->{path}: $cnt->{forward_count} message(s) were inserted for outgoing.pl to forward.");
            GMail->debug("$account->{path}: $cnt->{reply_count} message(s) were inserted for outgoing.pl to reply.");
            GMail->debug("$account->{path}: $cnt->{spam_count} message(s) were not inserted because they matched spam filters.");
            GMail->debug("$account->{path}: $cnt->{filter_count} message(s) were not inserted because they matched a delete filter.");
            GMail->debug("$account->{path}: $cnt->{insert_count} message(s) inserted.");
        }
    }
    else {
        GMail->debug("$account->{path}: No messages");
    }
    close MBOX;
    unlink $file2 or die "Could not unlink $file2; Reason: $!";
}

sub dele {
    my $num = shift;
    return 1 if $MBOX;
    return $POP->dele($num);
}

sub top {
    my $num = shift;
    my $msg;
    if ($MBOX) {
        my $ndx = $num - 1;
        seek(MBOX, $INDEX[$ndx]->{start}, SEEK_SET) or die "Couldn't seek to $INDEX[$ndx]->{start}: " . SEEK_SET . "; Reason: $!";
        my $bytes = $INDEX[$ndx]->{end_header} - $INDEX[$ndx]->{start};
        read(MBOX, $msg, $bytes) == $bytes or die "Could not read $bytes bytes; File size is: " . (-s MBOX) . " bytes; Tried to read to position: " . $INDEX[$ndx]->{end_header} . "\n";
    }
    else {
        $msg = $POP->top($num);
    }
    return $msg;
}

sub list {
    if ($MBOX) {
        my (@ret, $size);
        my $i       = 0;
        @INDEX      = ();
        while() {
            if (index($_, 'From ') == 0) {
                if ($i > 0) {
                    $INDEX[$i - 1]->{stop} = tell(MBOX) - length();
                    GMail->debug("$i) start => $INDEX[$i - 1]->{start}; stop => $INDEX[$i - 1]->{stop}; header => $INDEX[$i - 1]->{end_header}\n") if ($CFG->{debug});
                }
                $i++;
                $INDEX[$i - 1]->{start} = tell MBOX;
                push @ret, $i - 1 . " $size" if $size;
                $size  = 0;
                while () {
                    if (index($_, '>From ') == 0) {
                        next;
                    }
                    last if $_ eq "\n";
                    $size += length;
                }
                $INDEX[$i - 1]->{end_header} = tell MBOX;
            }
            $size += length;
        }
        push @ret, "$i $size";
        seek(MBOX, 0, SEEK_SET);
        $INDEX[$#INDEX]->{stop} = -s MBOX;
        return @ret;
    }
    else {
        return $POP->list();
    }
}

sub parse_message {
    my $num = shift;
    if ($MBOX) {
        seek(MBOX, $INDEX[$num - 1]->{start}, SEEK_SET);
        my $bytes = $INDEX[$num - 1]->{stop} - $INDEX[$num - 1]->{start};
        read(MBOX, my $msg, $bytes);
        $msg =~ s/^>From /From /mg;
        my $parser = GT::Mail::Parse->new(debug => $CFG->{debug}, in_string => $msg, crlf => "\n");
        $parser->parse or return GT::Mail::POP3->error("PARSE", "WARN", $GT::Mail::Parse::error);
        return $parser;

    }
    else {
        my $m = $POP->parse_message($num);
        return $m;
    }
}

sub process {
    my $account = shift;
    my (%skip, %seen);
    my $user_tb  = $DB->table('users');
    my $msgtrack = $DB->table('msgtrack');
    my $ret = {
        junk_count    => 0,
        bounce_count  => 0,
        forward_count => 0,
        reply_count   => 0,
        spam_count    => 0,
        filter_count  => 0,
        insert_count  => 0,
    };

# Load the admin user
    $JUNK_USER = GMail::User->new();
    $JUNK_USER->load_user(ADMIN);
    $JUNK_USER->check_create();

# Go through all emails on the server
    MESSAGE: foreach (list()) {

# Keep track of bounced and junk
        my $can_bounce = 1;
        my $junked     = 0;
        my $bounced    = 0;
        my ($mid, $mail);

# Get the size and message number from the list
        my ($num, $size) = split;

# Quit if we have no messages.
        last if (!$num);

# Get the head part parsed
        GMail->debug("Parsing the header of message number $num.") if $CFG->{debug};
        my $raw_head = top($num);

# Checksum the message for caching
        my $head     = GT::Mail::Parse->parse_head(\$raw_head) or die $GT::Mail::Parse::error;
        my $checksum = GMail::Messages->get_message_checksum($head, $raw_head, $size);

# Find out if we can bounce this message
        {
            my $from = $head->get('reply-to') || $head->get('from') || '';
            my $to   = $head->get('to') || '';
            if (!$from) {
                $can_bounce = 0;
            }
            if ($to eq $from) {
                $can_bounce = 0;
            }
        }

# The message is too big too accept
        if ($size > $CFG->{limits}->{msgs_size}) {
            GMail->debug("The message size is over the message size limit ($size > $CFG->{limits}->{msgs_size}). Skipping.") if ($CFG->{debug});
            if ($CFG->{email}->{bounce}->{too_many} and !$bounced and $can_bounce) {
# We don't want to bounce the large email with it attached, so we'll temporarily disable it
                my $attach = $CFG->{email}->{bounce}->{no_attach};
                $CFG->{email}->{bounce}->{no_attach} = 1;
                $PLG->dispatch('incoming::bounce', \&bounce, $num, $mail, "The email has exceeded the maximum size of $CFG->{limits}->{msgs_size} bytes") or next MESSAGE;
                $CFG->{email}->{bounce}->{no_attach} = $attach;
                $ret->{bounce_count}++;
            }
            dele($num) or die $GT::Mail::POP3::error;
            next MESSAGE;
        }

# For info on how to get sendmail to add the X-Envelope header:
# http://www.sendmail.org/faq/section3.html#3.29
        my @tos;
        for ($head->get('X-GMail-To'), $head->get('X-Envelope-To'), $head->get('Delivered-To')) {
            next unless $_;
            next if not m/[^<>"\s]+@[\w.-]+/;

# Check that the To isn't the pop address
            next if m/\Q$account->{login}\E\@\Q$account->{account}\E/i;

            for my $domain (@{$account->{domains}}) {
                if (m/$VALID_CHARACTERS\@\Q$domain\E/i) {
                    push @tos, $_;
                }
            }
        }

# If we have @tos, then we know the email has been specifically delivered to
# a user, so we don't need to look at the other headers.  We don't have one,
# so we have to grab it from the other fields.
        unless (@tos) {
            @tos = (
                $head->split_field('to'),
                $head->split_field('cc'),
                $head->split_field('bcc'),
                $head->split_field('Received')
            );
        }
        @tos = grep defined, @tos;

# No tos in all this, very unlikely
        my (%users, @insert, $inserted);
        unless (@tos) {
            GMail->debug("No 'To' in message!") if ($CFG->{debug});
            if ($CFG->{admin_user}->{use} and $CFG->{email}->{junk}->{no_to} and !$junked) {
                $mid = $PLG->dispatch('incoming::admin_insert', \&admin_insert, $num, $mail, $checksum) or next MESSAGE;
                $junked = 1;
                $ret->{junk_count}++;
            }
            if ($CFG->{email}->{bounce}->{no_to} and !$bounced and $can_bounce) {
                $bounced = 1;
                GMail->debug("Bouncing message, No 'To' line found in message.") if $CFG->{debug};
                $PLG->dispatch('incoming::bounce', \&bounce, $num, $mail, "Could not find who the message was addressed to") or next MESSAGE;
                $ret->{bounce_count}++;
            }
            dele($num) or die $GT::Mail::POP3::error;
            next MESSAGE;
        }

# Check for valid users in this message
        for (@tos) {
            next unless $_;
            for my $domain (@{$account->{domains}}) {
                my $pop_user = (/($VALID_CHARACTERS\@\Q$domain\E)/i) ? lc $1 : next;
# qmail setup with vpopmail may put domain.com-user@domain.com into the Delivered-To field; strip it out.
# Depending on how you setup qmail, you might want to change this.
                $pop_user =~ s/^\Q$domain\E-//i;
                $users{$pop_user} = 1;
            }
        }
        
# For some reason, doing a for (keys %users) was causing problems on some systems.  This fixes it.
        my @user_list = keys %users;

# Weed out the users who aren't in the authentication system.
        for (@user_list) {
            unless ($PLG->dispatch('GMail::Auth::get_pass', sub { GMail::Auth->get_pass(@_) }, $_)) {
                delete $users{$_};
            }
        }

# No users in this message for these domains
        @user_list = keys %users;
        unless (@user_list) {
            GMail->debug("No users for this site in this email, bouncing.") if ($CFG->{debug});
            if ($CFG->{admin_user}->{use} and $CFG->{email}->{junk}->{auth} and !$junked) {
                $mid = $PLG->dispatch('incoming::admin_insert', \&admin_insert, $num, $mail, $checksum) or next MESSAGE;
                $junked = 1;
                $ret->{junk_count}++;
            }
            if ($CFG->{email}->{bounce}->{auth} and !$bounced and $can_bounce) {
                $bounced = 1;
                GMail->debug("Bouncing message, no user found in message.") if $CFG->{debug};
                $PLG->dispatch('incoming::bounce', \&bounce, $num, $mail, "Could not find a user to deliver the message to") or next MESSAGE;
                $ret->{bounce_count}++;
            }
            dele($num) or die $GT::Mail::POP3::error;
            next MESSAGE;
        }
            
# Go through each possible user and insert the message for real users
        USER: foreach my $user (@user_list) {
            GMail->debug("Looking at ($user) from the 'To' line") if $CFG->{debug};

# If the user has alread gotten this message skip it.
            $seen{$checksum . $user}++ and next USER;

# Next if the user has reached max number of messages
            next USER if exists $skip{$user};

            $inserted = 1;

# Load the user into memory
            $PLG->dispatch('GMail::User::load_user', sub { $USER->load_user(@_) }, $user);

# The user is in the auth system, if this fails there is a real problem
            $USER->check_create();

            if ($USER->{users_status} eq 'Not Validated') {
                $skip{$user} = 1;
                GMail->debug("Skipping '$user'. User is not validated.") if $CFG->{debug};
                if ($CFG->{admin_user}->{use} and $CFG->{email}->{junk}->{not_validated} and !$junked) {
                    $mid = $PLG->dispatch('incoming::admin_insert', \&admin_insert, $num, $mail, $checksum) or next MESSAGE;
                    $junked = 1;
                    $ret->{junk_count}++;
                }
                if ($CFG->{email}->{bounce}->{not_validated} and !$bounced and $can_bounce) {
                    $bounced = 1;
                    GMail->debug("Bouncing message, user $user is not validated.") if $CFG->{debug};
                    $PLG->dispatch('incoming::bounce', \&bounce, $num, $mail, "The account hasn't been enabled yet") or next MESSAGE;
                    $ret->{bounce_count}++;
                }
                next USER;
            }

# Make sure the user can have this message
            my $rep = $PLG->dispatch('GMail::Messages::validate', sub { GMail::Messages->validate(@_) }, $size);
            if ($rep <= 0) {
# User has too many messages
                $rep == 0 and $skip{$user} = 1;
                GMail->debug("Validation: $GMail::Messages::error") if $CFG->{debug};
                if ($CFG->{admin_user}->{use} and $CFG->{email}->{junk}->{too_many} and !$junked) {
                    $mid = $PLG->dispatch('incoming::admin_insert', \&admin_insert, $num, $mail, $checksum) or next MESSAGE;
                    $junked = 1;
                    $ret->{junk_count}++;
                }
                if ($CFG->{email}->{bounce}->{too_many} and !$bounced and $can_bounce) {
                    $bounced = 1;
                    my %error_msgs = (
                        0  => "The user has exceeded the maximum number of allowed messages",
                        -1 => "The user has exceeded the space quota"
                    );
                    GMail->debug("Bouncing message, $error_msgs{$rep}") if $CFG->{debug};
                    $PLG->dispatch('incoming::bounce', \&bounce, $num, $mail, $error_msgs{$rep}) or next MESSAGE;
                    $ret->{bounce_count}++;
                }
                next USER;
            }

# If the user has this account set up to forward do that instead of inserting
            if ($USER->{opts}->{mailbox}->{forward_email} and $USER->{opts}->{mailbox}->{forward_email_to} and $CFG->{limits}->{forwards}) {
                $PLG->dispatch('incoming::forward', \&forward, $num, $mail) or next MESSAGE;
                $ret->{forward_count}++;
                next USER;
            }

# Check for filters
            my $spam = $PLG->dispatch('GMail::Options::Spam::process', sub { GMail::Options::Spam->process(@_) }, $head );
            my $fid  = $PLG->dispatch('GMail::Options::Filters::process', sub { GMail::Options::Filters->process(@_) }, $head );
            defined($fid) or $fid = INBOX;
            if ($fid == 0 or $spam == 1) {
                GMail->debug("Filter for ($user) matched, not inserting") if $CFG->{debug};
                $ret->{spam_count}++ if $spam == 1;
                $ret->{filter_count}++ if $fid == 0;
                next USER;
            }

# Parse the message if it is not already parsed
            unless ($mail) {
                $mail = parse_message($num);
                if (!$mail) {
                    GMail->debug("Unable to parse message; Reason: ($GT::Mail::POP3::error); Deleting.") if $CFG->{debug};
                    dele($num) or die $GT::Mail::POP3::error;
                    next MESSAGE;
                }
            }

# If there is an autoreply for this user write it to the reply
# directory so outgoing.pl can send it.
            if ($USER->{opts}->{autoreply_ok} and $CFG->{limits}->{autoreplies}) {
                $PLG->dispatch('incoming::autoreply', \&autoreply, $num, $mail) or next MESSAGE;
                $ret->{reply_count}++;
            }
                
# Insert the email
            my $size = 0;
   
            ($size, $mid) = $PLG->dispatch('GMail::Messages::insert_data', sub { GMail::Messages->insert_data(@_) }, $mail, $checksum);
            GMail->debug("Inserting message for userid ($USER->{email}).") if $CFG->{debug};
            $PLG->dispatch('GMail::Messages::insert_user', sub { GMail::Messages->insert_user(@_) }, {
                msgtrack_fid     => $fid,
                msgtrack_userid  => $USER->{userid},
                msgtrack_status  => 'New',
                msgtrack_mid     => $mid,
                msgtrack_account => 0,
                msgtrack_look    => ($USER->{opts}->{display}->{default_look} || 'clear'),
            }, $size);
            $ret->{insert_count}++;
            $inserted = 1;
            $USER->{opts}->{update_folders} = 1;
            GMail::Options->save();
        }

        dele($num) or die $GT::Mail::POP3::error;
    }
    return $ret;
}

sub admin_insert {
# ----------------------------------------------------------------------------
# Inserts email for the admin user. Used for undeliverables
#
    my ($num, $mail, $checksum) = @_;

# Parse the message if it is not already parsed
    unless ($mail) {
        $mail = parse_message($num);
        if (!$mail) {
            GMail->debug("Unable to parse message; Reason: ($GT::Mail::POP3::error); Deleting.") if $CFG->{debug};
            dele($num); 
            return;
        }
    }
    GMail->debug("Can't determine to, dumping junk message.") if $CFG->{debug};
    my $u = $GMail::Messages::USER;
    $GMail::Messages::USER = $JUNK_USER;
    my ($size, $mid) = $PLG->dispatch('GMail::Messages::insert_data', sub { GMail::Messages->insert_data(@_) }, $mail, $checksum);
    $PLG->dispatch('GMail::Messages::insert_user', sub { GMail::Messages->insert_user(@_) }, {
        msgtrack_fid     => INBOX,
        msgtrack_userid  => $JUNK_USER->{userid},
        msgtrack_status  => 'New',
        msgtrack_mid     => $mid,
        msgtrack_look    => ($JUNK_USER->{opts}->{default_look} || 'clear'),
        msgtrack_account => 0,
    }, $size);
    $GMail::Messages::USER->{opts}->{update_folders} = 1;
    GMail::Options->save();
    $GMail::Messages::USER = $u;
    return $mid;
}

sub bounce {
# ----------------------------------------------------------------------------
# Write the current message to the bounce folder so outgoing.pl can bounce it
#
    my ($num, $mail, $reason) = @_;
    
    my $h;
    if (!$mail) {
        if (!$CFG->{email}->{bounce}->{no_attach}) {
            $mail = parse_message($num);
            if (!$mail) {
                GMail->debug("Unable to parse message; Reason: ($GT::Mail::POP3::error); Deleting.") if $CFG->{debug};
                dele($num);
                return;
            }
            $h = $mail->top_part();
        }
        else {
            my $raw_head = top($num);
            $h = GT::Mail::Parse->parse_head(\$raw_head) or die $GT::Mail::Parse::error;
        }
    }
    else {
        $h = $mail->top_part();
    }
    GMail->debug("Bouncing message.") if ($CFG->{debug});
    if ($CFG->{email}->{pop} eq 'shared_pipe' and $CFG->{email}->{shared_pipe}->{exit_to_bounce} and defined($CFG->{email}->{shared_pipe}->{exit_bounce})) {
        exit($CFG->{email}->{shared_pipe}->{exit_bounce});
    }

# Don't bounce to postmaster/mailer-daemon
    my $from = lc $h->get('From');
    if ($from =~ /mailer-daemon\@/i or $from =~ /postmaster\@/i) {
    	return 1;
    }

    my $dir = "$CFG->{location}->{path}->{data}/users/ADMIN/bounce";
    if (!-d $dir) {
        GMail->mkdir($dir);
    }
    opendir(DIR, $dir) or die "Could not open $dir; Reason: $!";
    $num = grep { !-d "$dir/$_" and !/^\.?\.$/ } readdir(DIR);
    closedir DIR;
    $num++;
    $h->set('X-Reason' => $reason);

    my $fh = GMail->touch("$dir/$num");
    binmode $fh;
    if (!$CFG->{email}->{bounce}->{no_attach}) {
        my $m = GT::Mail->new();
        if ($ENV_TO) {
            $mail->top_part->set('X-Envelope-To', $ENV_TO);
        }
        $m->top_part($mail->top_part());
        $m->write($fh) or die $GT::Mail::error;
    }
    else {
        if ($ENV_TO) {
            $h->set('X-Envelope-To', $ENV_TO);
        }
        print $fh $h->header_as_string();
    }
    return 1;
}

sub forward {
# ----------------------------------------------------------------------------
# Write the current email to the forward folder so outgoing.pl can send it.
#
    my ($num, $mail) = @_;
    
    unless ($mail) {
        $mail = parse_message($num);
        if (!$mail) {
            GMail->debug("Unable to parse message; Reason: ($GT::Mail::POP3::error); Deleting.") if $CFG->{debug};
            dele($num);
            return;
        }
    }
    GMail->debug("Forwarding message.") if ($CFG->{debug});
    $mail->set('X-Was-To' => scalar($mail->top_part->get('to')));
    $mail->top_part->set(to => $USER->{opts}->{mailbox}->{forward_email_to});

# Add their email to the email so that outgoing.pl knows who's sending it.
    $mail->top_part->set('X-GMail-User' => $USER->{email});

    my $dir = "$CFG->{location}->{path}->{data}/users/ADMIN/forward";
    if (!-d $dir) {
        GMail->mkdir($dir);
    }
    opendir(DIR, $dir) or die "Could not open $dir; Reason: $!";
    $num = grep { !-d "$dir/$_" and !/^\.?\.$/ } readdir(DIR);
    closedir DIR;
    $num++;
    my $m = GT::Mail->new();
    $m->top_part($mail->top_part());
    my $fh = GMail->touch("$dir/$num");
    binmode $fh;
    $m->write($fh) or die $GT::Mail::error;
    return 1;
}

sub autoreply {
# ----------------------------------------------------------------------------
# Write the users autoreply message to the reply folder so outgoing.pl can 
# sed it.
    my ($num, $mail) = @_;
        
    unless ($mail) {
        $mail = parse_message($num);
        if (!$mail) {
            GMail->debug("Unable to parse message; Reason: ($GT::Mail::POP3::error); Deleting.") if $CFG->{debug};
            dele($num);
            return;
        }
    }
# Detection of loops
    my $loop = $mail->top_part->get('X-GLoop') || $mail->top_part->get('X-Loop');
    if ($loop) {
        GMail->debug('Loop detected. Not sending autoreply.');
        return 1;
    }
    GMail->debug ("Sending Autoreply.") if ($CFG->{debug});
    my $from = $mail->top_part->get('reply-to') || $mail->top_part->get('from');

    if (!$from) {
        GMail->debug("No from line in header, no one to autoreply to.");
        return 1;
    }

    my $dir = "$CFG->{location}->{path}->{data}/users/ADMIN/reply";
    if (!-d $dir) {
        GMail->mkdir($dir);
    }
    opendir(DIR, $dir) or die "Could not open $dir; Reason: $!";
    $num = grep { !-d "$dir/$_" and !/^\.?\.$/ } readdir(DIR);
    closedir DIR;
    $num++;

    my $file = "$USER->{dir}/.reply";
    -e $file or return 1;
    -s _ or return 1;
    
    my $parser = GT::Mail::Parse->new(debug => $CFG->{debug}, in_file => $file, crlf => "\012");
    my $head   = $parser->parse();
    $mail      = GT::Mail->new(debug => $CFG->{debug});
    $mail->top_part($head);
    $head->set(to => $from);

# Add their email to the reply email so that outgoing.pl knows who's sending it.
    $head->set('X-GMail-User' => $USER->{email});

    my $fh = GMail->touch("$dir/$num");
    binmode $fh;
    $mail->write($fh) or die $GT::Mail::error;
    return 1;
}

# Clean up after our self
sub END {
    if (!$MBOX) {
        local $!;
        if ($CLEANUP and -e "$CFG->{location}->{path}->{data}/tmp/incoming.lock") {
            unlink ("$CFG->{location}->{path}->{data}/tmp/incoming.lock") 
                or die "Unable to remove lockfile $CFG->{location}->{path}->{data}/tmp/incoming.lock; Reason: ($!)";
        }
    }
    $POP and $POP->quit();
}

# Quit if another copy is already running.
sub lock_me {
    local $!;
    $CFG->load_config();
    if ($CFG->{email}->{pop} ne 'shared_pipe') {
        $CLEANUP = 1;
        if (-e "$CFG->{location}->{path}->{data}/tmp/incoming.lock") {
            if (-M _ > 0.2) {
                unlink "$CFG->{location}->{path}->{data}/tmp/incoming.lock" 
                    or die "Could not unlink $CFG->{location}->{path}->{data}/tmp/incoming.lock. Reason: $!";
            }
            else {
                open (LOCK, "$CFG->{location}->{path}->{data}/tmp/incoming.lock") 
                    or die "Could not open $CFG->{location}->{path}->{data}/tmp/incoming.lock. Reason: $!";
                my $pid  = ;
                $pid =~ /^(\d+)$/ and $pid = $1;
                if (!$pid) {
                    unlink "$CFG->{location}->{path}->{data}/tmp/incoming.lock"
                        or die "Could not unlink $CFG->{location}->{path}->{data}/tmp/incoming.lock. Reason: $!";
                }
                elsif (kill 0, $pid) {
                    $CLEANUP = 0;
                    die "incoming.pl is already running with pid: $pid. Quitting.\n";
                }
                else {
                    unlink "$CFG->{location}->{path}->{data}/tmp/incoming.lock" 
                        or die "Could not unlink $CFG->{location}->{path}->{data}/tmp/incoming.lock. Reason: $!";
                }
                close LOCK;
            }
        }

# Create a lock file.
        open (LOCK, ">$CFG->{location}->{path}->{data}/tmp/incoming.lock") 
            or die "Can't create lockfile $CFG->{location}->{path}->{data}/tmp/incoming.lock: incoming.lock ($!)\n";
        chmod 0666, "$CFG->{location}->{path}->{data}/tmp/incoming.lock"
            or die "Can't chmod lockfile $CFG->{location}->{path}->{data}/tmp/incoming.lock: incoming.lock ($!)\n";
        print LOCK $$;
        close LOCK;
    }
}

sub fatal {
# ----------------------------------------------------------------------------
# Fatal error formatted for terminal
    my ($msg) = @_;
    die $msg if (GT::Base->in_eval());    # Don't do anything if we are in eval.

# Use a custom header if one is defined.
        print qq|A fatal error has occured:
    $msg
|;

    if ($CFG and $CFG->{debug}) {
        my $env = GMail->environment();
        $env =~ s/<\/?B>//gi;
        $env =~ s/<\/?PRE>//gi;
        $env =~ s/
/\n/gi; print $env; } else { print "Please enable debugging to find more information about this error\n"; } exit(255); }