#!/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); }