#!@PERL@ -sw # # Package: am-utils-6.0 # Author: James Tanis # ############################################################################ # # lostaltmail -- remail files files found alt_mail (or -a argument to hlfsd) to # whomever should receive it. This version is for SMTP varient which # support VRFY as a non-expanding verifier!!! (sendmail V8 is a an # example). # # Usage: lostaltmail [-debug] [-nomail] [-noverify] # # GLOBAL VARIABLES (as if you care :-) ) # Probably a very incomplete list. # # Everything in the config file for this program *and* ... # # $debug: set it from the command line with -debug. Does the obvious # $nomail: set it from the command line with -nomail. *Not* implied by # $debug # $currentTO: The addresss we are currently checking on. Actually this is # left over from an earlier version of lostaltmail and will hopefully # go away. # $noverify: set it from the address line. Avoid verification of $currentTO. # This should be relatively safe as long as you are willing to # endure bounces from mail that cannot be redelivered as opposed to # just getting a warning. UNTESTED (but should work). # # $logopen: state variable indicating weather the log file (should there be # one) is in fact open. # # @allentries: Array of all the directory entries in $MAILDIR # @allnames: Array of all *likely* recipients. It is created from @allentries # sans junk files (see $MAILJUNK and $LOCALMAILJUNK) # @wanderers: Array of all the files associated with a *single* address # which might need remailing. Should lostaltmail die unexpectedly, # it might leave a temporary file containing messages it was # currently trying to deliver. These will get picked and resent # later. # # VRFY: Handle onto SMTP verification channel. Not to be confused with mail # delivery; only verification occurs accross this handle. # ############################################################################ ############################################################################## # # # SMTP_SEND # # # ############################################################################## # # Send a message to the smtp channel. Inserts the necessary NEWLINE if it # does not exist; # I stole this from myself. It shouldn nott be printing errors to STDERR, but # this is a quick hack. # sub smtp_send { local ($msg) = @_; local ($length); $length=length($msg); if ( $msg !~ /^.*\n$/ ) { $msg = $msg . "\n"; $length++; } if ( ! syswrite (VRFY, $msg, $length)) { print STDERR "Failing SMTP write: $msg"; return 0; } return 1; } ############################################################################## # # # SMTP_RECV # # # ############################################################################## # # Read in lines from SMTP connection and return the final # Really hideous -- please excuse. # sub smtp_recv { local ($line,$rin, $win, $ein, $readbuf, $ret); $readbuf = ""; $rin = $win = $ein = ''; # Null fd sets, vec ($rin, fileno(VRFY), 1) = 1; # Stolen straight from the example; $ein = $rin | $win; # This is probably useless LINE_OF_INPUT: while (1) { # Read in all the input if ((select ( $rin, $win, $ein, 600.0))[0] == 0 ) { print "select returned -1" if ($debug); return -1; # timeout } sysread (VRFY, $readbuf, 1024); chop ($readbuf); foreach $line ( split('\n', $readbuf)) { # This loop is actually needed since V8 has a multi-line greet. ( $line =~ /^(\d\d\d).*/ && ($SMTP_retval=$1)) || warn "Badly formed reply from SMTP peer: $line\n"; # Space after return code indicates EOT if ($line =~ /^\d\d\d /) { $ret = $line; # Oddly $line is in a different context here; # and thus we need to export it out of the # while loop via $ret. last LINE_OF_INPUT; } } # End of read. } # End of input. return $ret; } ############################################################################## # # # LOG_INFO # # # ############################################################################## # # # Opens appropriate logging file -- STDOUT (cron) or temp file (mail). # sub Log_info { local($message) = @_; if ( !$logopened ) { if ( $MAILGRUNT eq "" || $debug) { open (LOGFILE, ">-") || die "Unable to open stdout"; } else { # Snarf the log into a tmp file for final mailing to MAILGRUNT $logfile = $LOGFILE . ".$$"; open (LOGFILE, (">". "$logfile")) || die "Unable to create log file"; } } $logopened=1; # Note that the log is now open # Heart of the function. print LOGFILE "$message"; print LOGFILE "\n" if ( index($message,"\n") == -1 ); } ############################################################################## # # # LOCK_FILE # # # ############################################################################## # # Tries to grab a lock on the supplied file name. # Spins for a bit if it can't on the assumption that the lock will be released # quickly. If it times out and it's allowed to requeue, it will defer # until later, other wise write a message to loginfo. # If a recurring error or really unexpected situation arrises, return # ABORT_RESEND # # PARAMETERS # mailfile: path to the file to resend. # should_requeue: BOOLEAN - TRUE if the mailfile should be put on the # queue for a later retry if we can not finish # now. sub Lock_file { local($mailfile,$should_requeue,$i,$new_lost_file) = @_; # We need to rename the current mailbox so that mail can loop back into it if # the resent mail just gets looped right back to us. $new_lost_file = $mailfile . ".$$"; # make a tmpfile name based on mailfile; $lostlockfile = "$mailfile" . "$LOCKEXT"; if ( ! open(LOCKFILE, (">" . $lostlockfile)) ) { printf(STDERR "Could not create lostlockfile for %s: %s\n", $mailfile,$!); return $ABORT_RESEND; } close(LOCKFILE); $maillockfile = "$mailfile" . "$LOCAL_LOCK_EXT"; for ($i=0; $i < $LOCK_RETRIES && ! link ($lostlockfile, $maillockfile); $i++) { sleep(1); } unlink($lostlockfile); # No matter what eliminate our cruft if ( $i == $LOCK_RETRIES ) { &Log_info("Could not grab lock on: " . "$mailfile" . " :timed out"); if ( $should_requeue ) { &Log_info("Requeing " . "$mailfile" . " for later retry"); $retry_list .= " $mailfile"; } else { &Log_info("Giving up on: " . "$mailfile"); } return $ABORT_RESEND; } # We created the link and therefore have the lock if (rename ($mailfile, $new_lost_file) == 0 ){ # Failed to rename file -- this is serious. unlink($maillockfile); return $ABORT_RESEND; } unlink($maillockfile); return $new_lost_file; } ############################################################################## # # # PARSE NEXT MAIL MESSAGE # # # ############################################################################## # # Parameters: # mailfile: handle of mailfile to use. # # Parses the next message in the mail file and inserts it in $current_msg # sub Get_next_msg { local($mailfile,$found_body_delimiter) = @_; # If this is the first message in the spool file, read the first line # otherwise use the MESSAGE_DELIM line from the previous message (which we # were forced to overread). $done=$FALSE; $found_body_delimiter=$FALSE; # This if eats the very first "From " line and should never fire again. if ( ! defined $current_msg ) {<$mailfile>}; undef ($current_msg); # Erase the old message. # Read the mailfile and pass through all the lines up until the next # message delimiter. Kill any previous resend headers. while ( <$mailfile> ) { last if (/$MESSAGE_DELIM/); next if ( !$found_body_delimiter && /[Rr][Ee][Ss][Ee][Nn][Tt]-.+:/); if ( !$found_body_delimiter && /^$HEADER_BODY_DELIM/) { &Splice_in_resent_headers(); $found_body_delimiter=$TRUE; } if (defined($current_msg)) { $current_msg .= $_; } else { $current_msg = $_; } } # Return TRUE when we have hit the end of the file. if (!defined($_) || $_ eq "" ) { return $TRUE; } else { return $FALSE; } } ############################################################################## # # # SPLICE IN RESENT_HEADERS # # # ############################################################################## # # Insert the Resent- headers at the *current location* of the message stream # (In Engish, print out a few Resent-X: lines and return :-) ) # In addition splice in the X-resent-info: header. # # Paremters: None. # Return: None # sub Splice_in_resent_headers { local($date,$utctime,$weekday,$time,$month,$hostname); $current_msg .= "$RESENT_TO" . "$currentTO" . "\n"; $current_msg .= "$RESENT_FROM" . "$SYSTEM_FROM_ADDRESS" . "\n"; # Calculate date and time. It is a bit of a shame to do this each time # the time needs to be acurate. @utctime=gmtime(time); $weekday=(Sun,Mon,Tue,Wed,Thu,Fri,Sat)[$utctime[6]]; # If the minutes or second do not take two columns each, patch em up. if ( $utctime[1] < 10 ) { if ( $utctime[0] < 10 ) { $time=sprintf("%d:0%d:0%d",$utctime[2],$utctime[1],$utctime[0]); } else { $time=sprintf("%d:0%d:%d",$utctime[2],$utctime[1],$utctime[0]); } } else { if ( $utctime[0] < 10 ) { $time=sprintf("%d:%d:0%d",$utctime[2],$utctime[1],$utctime[0]); } else { $time=sprintf("%d:%2d:%2d",$utctime[2],$utctime[1],$utctime[0]); } } $month=(Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec)[$utctime[4]]; # Ensure Y2K format $date=sprintf("%s, %d %s %d %s UTC", $weekday, $utctime[3], $month, $utctime[5]+1900, $time); $current_msg .= "$RESENT_DATE" . $date . "\n"; if ( defined $RESENT_INFO && $RESENT_INFO ne "") { $hostname=`uname -n`; $current_msg .= "$RESENT_INFO" . "Lost mail resent from ". $hostname; } return; } ############################################################################## # # # DO_REMAIL # # # ############################################################################## # # Actually resends the mail. Talks to the process configured as $MAILER # We need better handling. # sub Do_remail { open (MAILER, "| $MAILER $currentTO") || return $ABORT_RESEND; print MAILER $current_msg; close (MAILER); } ############################################################################## # # # CLEAN_UP # # # ############################################################################## # # Clean up my messes. # sub Clean_up { local ($hostname); # Ugly local hack that you should never have seen, but I forgot to # remove. Hopefully it did not kill you (I tried as you see), but you # should eiter remove or update it for yourself. I find the message # subject needs to have the hostname to be useful. # chop ($hostname=`uname -n`); $LOG_SUBJECT="$LOG_SUBJECT from $hostname" if ( $hostname =~ /.*\.cs\.columbia\.edu/ ); # # End of ugly local hack # Mail any log info to MAILGRUNT. if (defined($logfile) && $logfile ne "" ) { close (LOGFILE); # Flush logfile output. if ( -s $logfile ) { open (MAILER, "| $MAILER $MAILGRUNT"); print MAILER "To: $MAILGRUNT\n"; print MAILER "Subject: $LOG_SUBJECT\n"; print MAILER "$HEADER_BODY_DELIM"; open (LOGFILE, "< $logfile"); while () { print MAILER $_; } close (MAILER); close (LOGFILE); } unlink($logfile); } exit(0); } ############################################################################## # # # COLLECT_WANDERERS # # # ############################################################################## # # Collects other files that appear to be mail file for the $currentTO # but were not remailed successfully. # # Parameters: none (but uses $currentTO) # Return: True if a old mail directory is found. False otherwise. # Side effects: $wanderers set. # sub Collect_wanderers { undef (@wanderers); # Slurp in the directory and close. return ($found); } ############################################################################# # # # REMAIL ALL # # # ############################################################################# # # Takes an array of files that all seem to share a common repcipient and # remails them if possible. # # Parameters: None (uses @wanderers). # sub Remail_all { local($file,$i); $i=0; foreach $file (@wanderers) { if ( !open (LOSTFILE, "< $file")) { &Log_info("Could not open " . "$file" . " for remailing"); next; } do { # Power loop! $done = &Get_next_msg(LOSTFILE); # Retrieve the next message... &Do_remail; # and remail it. } until $done; undef ($current_msg); # Erase the final remailed message. close(LOSTFILE); # Tidy up. unlink ($file); # Remove the remailed file $i++; } } ############################################################################# # # # CHECK_USER # # # ############################################################################# # # Checks the password tables for the uid of $currentTO. If the user is # uid 0 (ie *supposed* to get mail in altmail) or unknown the resend is # aborted. # # sub Check_user { local (@passwdinfo); undef (@passwdinfo); if (!$noverify && !&vrfy_user($currentTO) ) { &Log_info("Possible non user mail file: $currentTO"); return $ABORT_RESEND; } @passwdinfo = getpwnam($currentTO); print "Non user mailable mail: Name: $currentTO\n" if ( $debug && ! defined @passwdinfo ); return !$ABORT_RESEND if ( ! defined @passwdinfo ); # A non user but evidently mailable print "Check User(): Name: $currentTO -- UID: $passwdinfo[2]\n" if ($debug); return $ABORT_RESEND if ( $passwdinfo[2] == 0 ); return !$ABORT_RESEND; } ############################################################################# # # # VRFY USER # # # ############################################################################# # # Use SMTP VRFY to insure that argument is in fact a legal mail id. # Boolean: TRUE if mailable account, FALSE if not. sub vrfy_user { local ($mailname,$repl) = @_; if ( !&smtp_send("vrfy $mailname") ) { &Log_info("Failed sending to vrfy smtp command for: $mailname"); return 0; } $repl = &smtp_recv; print "VRFY REPLY: $repl\n" if ($debug); return ( $repl =~ /^2\d\d/ ); } ############################################################################# # # # MAIN PROC # # # ############################################################################# # dummy code to shut up perl -w $debug = 0 if !defined($debug); print $nomail if $debug > 1; print $RESENT_FROM if $debug > 1; print $logopen if $debug > 1; print $LOCAL_LOCK_EXT if $debug > 1; print $RESENT_TO if $debug > 1; print $LOCKEXT if $debug > 1; print $RESENT_DATE if $debug > 1; print $MESSAGE_DELIM if $debug > 1; print $SMTP_retval if $debug > 1; print $found if $debug > 1; print $retry_list if $debug > 1; print $MAILJUNK if $debug > 1; print $noverify if $debug > 1; print $SYSTEM_FROM_ADDRESS if $debug > 1; # BEGIN: stuff $prefix="@prefix@"; $CONFIGDIR="@sysconfdir@"; # Directory where global config lives require "$CONFIGDIR/lostaltmail.conf" if (-f "$CONFIGDIR/lostaltmail.conf"); require "/etc/global/lostaltmail.conf" if (-f "/etc/global/lostaltmail.conf"); require "/etc/os/lostaltmail.conf" if (-f "/etc/os/lostaltmail.conf"); require "/etc/local/lostaltmail.conf" if (-f "/etc/local/lostaltmail.conf"); require "ctime.pl"; use Socket; #require "sys/socket.ph"; # SET some initial state variales $logopen = 0; # # Change to alt_dir # # Important!! This directory should be local. Folks will be responsible # for finding this out for themselves. # if (!defined($MAILDIR) || $MAILDIR eq "") { die "MAILDIR must be defined\n"; } chdir ( $MAILDIR ) || die "Cannot change to $MAILDIR (`x' bit not set?)"; # # slurp in directory # opendir (MAIL, ".") || die "Cannot open $MAILDIR (`r' bit not set?)"; @allentries= readdir (MAIL); closedir (MAIL); @allnames = grep (!/$LOCALMAILJUNK|$MAILJUNK/, @allentries); # Open chanel to SMTP for verification -- unless this option is # configured off. if ( ! $noverify ) { local($addr, $port,$sockaddr); socket (VRFY, &AF_INET, &SOCK_STREAM, 0) || die "Could not create TCP socket (SMTP channel)"; $addr = (gethostbyname($SMTPHOST))[4]; # Just use the first addr die "Could not obtain STMP host ($SMTPHOST) address" if ( $addr eq "" ); $port = (getservbyname('smtp','tcp'))[2]; # Get smtp port. die "Could not obtain SMTP port number" if (!defined($port)); printf("SMTP: address: %s port: $port\n", join ('.',unpack('C4',$addr))) if ($debug); $sockaddr = sockaddr_in($port, $addr); printf("Sockaddr: %s\n", join (' ',unpack('C14',$sockaddr))) if ($debug); connect (VRFY, $sockaddr) || die "Could not connect to SMTP daemon on $SMTPHOST"; print "Establshed SMTP channel\n" if ($debug); &smtp_recv; # Greet wait &smtp_send("helo $SMTPHOST"); # Helo message for picky SMTPs &smtp_recv; # Helo reply # Connection is up and ready to VRFY } # main stuff starts here foreach $currentTO (@allnames) { next if ( &Check_user == $ABORT_RESEND); # just delete the file if too small to be real mail if ((stat($currentTO))[7] < 5) { print "Too small to be real mail, unlinking $currentTO" if $debug; unlink $currentTO; } undef (@wanderers); # Just reset this at each pass. @wanderers=grep (/$currentTO\.\d+/, @allentries); $remail_file = &Lock_file($currentTO,$FALSE); # Need to lock the spool. next if ( $remail_file eq $ABORT_RESEND); # Could not get that lock push (@wanderers, $remail_file); # Try to resend "old" files. print "List to remail: @wanderers\n" if ($debug); # check if there is something to remail &Remail_all if ( defined @wanderers && !$nomail); } # this stuff should run at the end foreach $file (grep (/$LOCALMAILJUNK/,@allentries)) { if ($debug) { print "Would unlink $file\n" if ($debug); } else { unlink $file if (-f $file); } } &Clean_up; # Do a clean exit.