3 # Package: am-utils-6.0
4 # Author: James Tanis <jtt@cs.columbia.edu>
7 ############################################################################
9 # lostaltmail -- remail files files found alt_mail (or -a argument to hlfsd) to
10 # whomever should receive it. This version is for SMTP varient which
11 # support VRFY as a non-expanding verifier!!! (sendmail V8 is a an
14 # Usage: lostaltmail [-debug] [-nomail] [-noverify]
16 # GLOBAL VARIABLES (as if you care :-) )
17 # Probably a very incomplete list.
19 # Everything in the config file for this program *and* ...
21 # $debug: set it from the command line with -debug. Does the obvious
22 # $nomail: set it from the command line with -nomail. *Not* implied by
24 # $currentTO: The addresss we are currently checking on. Actually this is
25 # left over from an earlier version of lostaltmail and will hopefully
27 # $noverify: set it from the address line. Avoid verification of $currentTO.
28 # This should be relatively safe as long as your are willing to
29 # endure bounces from mail that cannot be redelivered as opposed to
30 # just getting a warning. UNTESTED (but should work).
32 # $logopen: state variable indicating weather the log file (should there be
33 # one) is in fact open.
35 # @allentries: Array of all the directory entries in $MAILDIR
36 # @allnames: Array of all *likely* recipients. It is created from @allentries
37 # sans junk files (see $MAILJUNK and $LOCALMAILJUNK)
38 # @wanderers: Array of all the files associated with a *single* address
39 # which might need remailing. Should lostaltmail die unexpectedly,
40 # it might leave a temporary file containing messages it was
41 # currently trying to deliver. These will get picked and resent
44 # VRFY: Handle onto SMTP verification channel. Not to be confused with mail
45 # delivery; only verification occurs accross this handle.
47 ############################################################################
49 ##############################################################################
53 ##############################################################################
55 # Send a message to the smtp channel. Inserts the necessary NEWLINE if it
57 # I stole this from myself. It shouldn nott be printing errors to STDERR, but
58 # this is a quick hack.
66 if ( $msg !~ /^.*\n$/ ) {
72 if ( ! syswrite (VRFY, $msg, $length)) {
73 print STDERR "Failing SMTP write: $msg";
80 ##############################################################################
84 ##############################################################################
86 # Read in lines from SMTP connection and return the final
87 # Really hideous -- please excuse.
90 local ($line,$rin, $win, $ein, $readbuf, $ret);
93 $rin = $win = $ein = ''; # Null fd sets,
94 vec ($rin, fileno(VRFY), 1) = 1; # Stolen straight from the example;
95 $ein = $rin | $win; # This is probably useless
99 while (1) { # Read in all the input
101 if ((select ( $rin, $win, $ein, 600.0))[0] == 0 ) {
102 print "select returned -1" if ($debug);
105 sysread (VRFY, $readbuf, 1024);
108 foreach $line ( split('\n', $readbuf)) {
110 # This loop is actually needed since V8 has a multi-line greet.
112 ( $line =~ /^(\d\d\d).*/ && ($SMTP_retval=$1)) ||
113 warn "Badly formed reply from SMTP peer: $line\n";
115 # Space after return code indicates EOT
117 if ($line =~ /^\d\d\d /) {
118 $ret = $line; # Oddly $line is in a different context here;
119 # and thus we need to export it out of the
120 # while loop via $ret.
132 ##############################################################################
136 ##############################################################################
139 # Opens appropriate logging file -- STDOUT (cron) or temp file (mail).
142 local($message) = @_;
145 if ( $MAILGRUNT eq "" || $debug) {
146 open (LOGFILE, ">-") || die "Unable to open stdout";
149 # Snarf the log into a tmp file for final mailing to MAILGRUNT
150 $logfile = $LOGFILE . ".$$";
151 open (LOGFILE, (">". "$logfile")) || die "Unable to create log file";
155 $logopened=1; # Note that the log is now open
157 # Heart of the function.
158 print LOGFILE "$message";
160 print LOGFILE "\n" if ( index($message,"\n") == -1 );
163 ##############################################################################
167 ##############################################################################
170 # Tries to grab a lock on the supplied file name.
171 # Spins for a bit if it can't on the assumption that the lock will be released
172 # quickly. If it times out and it's allowed to requeue, it will defer
173 # until later, other wise write a message to loginfo.
175 # If a recurring error or really unexpected situation arrises, return
179 # mailfile: path to the file to resend.
180 # should_requeue: BOOLEAN - TRUE if the mailfile should be put on the
181 # queue for a later retry if we can not finish
186 local($mailfile,$should_requeue,$i,$new_lost_file) = @_;
188 # We need to rename the current mailbox so that mail can loop back into it if
189 # the resent mail just gets looped right back to us.
190 $new_lost_file = $mailfile . ".$$";
192 # make a tmpfile name based on mailfile;
193 $lostlockfile = "$mailfile" . "$LOCKEXT";
195 if ( ! open(LOCKFILE, (">" . $lostlockfile)) ) {
196 printf(STDERR "Could not create lostlockfile for %s: %s\n", $mailfile,$!);
197 return $ABORT_RESEND;
201 $maillockfile = "$mailfile" . "$LOCAL_LOCK_EXT";
203 for ($i=0; $i < $LOCK_RETRIES && ! link ($lostlockfile, $maillockfile);
208 unlink($lostlockfile); # No matter what eliminate our cruft
210 if ( $i == $LOCK_RETRIES ) {
211 &Log_info("Could not grab lock on: " . "$mailfile" . " :timed out");
212 if ( $should_requeue ) {
213 &Log_info("Requeing " . "$mailfile" . " for later retry");
214 $retry_list .= " $mailfile";
217 &Log_info("Giving up on: " . "$mailfile");
220 return $ABORT_RESEND;
223 # We created the link and therefore have the lock
225 if (rename ($mailfile, $new_lost_file) == 0 ){
226 # Failed to rename file -- this is serious.
227 unlink($maillockfile);
228 return $ABORT_RESEND;
231 unlink($maillockfile);
232 return $new_lost_file;
236 ##############################################################################
238 # PARSE NEXT MAIL MESSAGE #
240 ##############################################################################
243 # mailfile: handle of mailfile to use.
245 # Parses the next message in the mail file and inserts it in $current_msg
248 local($mailfile,$found_body_delimiter) = @_;
250 # If this is the first message in the spool file, read the first line
251 # otherwise use the MESSAGE_DELIM line from the previous message (which we
252 # were forced to overread).
255 $found_body_delimiter=$FALSE;
257 # This if eats the very first "From " line and should never fire again.
258 if ( ! defined $current_msg ) {<$mailfile>};
259 undef ($current_msg); # Erase the old message.
262 # Read the mailfile and pass through all the lines up until the next
263 # message delimiter. Kill any previous resend headers.
264 while ( <$mailfile> ) {
265 last if (/$MESSAGE_DELIM/);
266 next if ( !$found_body_delimiter && /[Rr][Ee][Ss][Ee][Nn][Tt]-.+:/);
267 if ( !$found_body_delimiter && /^$HEADER_BODY_DELIM/) {
268 &Splice_in_resent_headers();
269 $found_body_delimiter=$TRUE;
271 if (defined($current_msg)) {
278 # Return TRUE when we have hit the end of the file.
279 if (!defined($_) || $_ eq "" ) {
286 ##############################################################################
288 # SPLICE IN RESENT_HEADERS #
290 ##############################################################################
292 # Insert the Resent- headers at the *current location* of the message stream
293 # (In Engish, print out a few Resent-X: lines and return :-) )
294 # In addition splice in the X-resent-info: header.
300 sub Splice_in_resent_headers {
301 local($date,$utctime,$weekday,$time,$month,$hostname);
303 $current_msg .= "$RESENT_TO" . "$currentTO" . "\n";
304 $current_msg .= "$RESENT_FROM" . "$SYSTEM_FROM_ADDRESS" . "\n";
306 # Calculate date and time. It is a bit of a shame to do this each time
307 # the time needs to be acurate.
309 @utctime=gmtime(time);
311 $weekday=(Sun,Mon,Tue,Wed,Thu,Fri,Sat)[$utctime[6]];
314 # If the minutes or second do not take two columns each, patch em up.
315 if ( $utctime[1] < 10 ) {
316 if ( $utctime[0] < 10 ) {
317 $time=sprintf("%d:0%d:0%d",$utctime[2],$utctime[1],$utctime[0]);
320 $time=sprintf("%d:0%d:%d",$utctime[2],$utctime[1],$utctime[0]);
324 if ( $utctime[0] < 10 ) {
325 $time=sprintf("%d:%d:0%d",$utctime[2],$utctime[1],$utctime[0]);
328 $time=sprintf("%d:%2d:%2d",$utctime[2],$utctime[1],$utctime[0]);
332 $month=(Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec)[$utctime[4]];
335 $date=sprintf("%s, %d %s %d %s UTC", $weekday, $utctime[3], $month, $utctime[5]+1900, $time);
337 $current_msg .= "$RESENT_DATE" . $date . "\n";
339 if ( defined $RESENT_INFO && $RESENT_INFO ne "") {
340 $hostname=`uname -n`;
341 $current_msg .= "$RESENT_INFO" . "Lost mail resent from ". $hostname;
347 ##############################################################################
351 ##############################################################################
353 # Actually resends the mail. Talks to the process configured as $MAILER
354 # We need better handling.
357 open (MAILER, "| $MAILER $currentTO") || return $ABORT_RESEND;
358 print MAILER $current_msg;
362 ##############################################################################
366 ##############################################################################
368 # Clean up my messes.
373 # Ugly local hack that you should never have seen, but I forgot to
374 # remove. Hopefully it did not kill you (I tried as you see), but you
375 # should eiter remove or update it for yourself. I find the message
376 # subject needs to have the hostname to be useful.
378 chop ($hostname=`uname -n`);
379 $LOG_SUBJECT="$LOG_SUBJECT from $hostname" if ( $hostname =~ /.*\.cs\.columbia\.edu/ );
381 # End of ugly local hack
383 # Mail any log info to MAILGRUNT.
384 if (defined($logfile) && $logfile ne "" ) {
385 close (LOGFILE); # Flush logfile output.
387 open (MAILER, "| $MAILER $MAILGRUNT");
389 print MAILER "To: $MAILGRUNT\n";
390 print MAILER "Subject: $LOG_SUBJECT\n";
391 print MAILER "$HEADER_BODY_DELIM";
393 open (LOGFILE, "< $logfile");
408 ##############################################################################
410 # COLLECT_WANDERERS #
412 ##############################################################################
415 # Collects other files that appear to be mail file for the $currentTO
416 # but were not remailed successfully.
418 # Parameters: none (but uses $currentTO)
419 # Return: True if a old mail directory is found. False otherwise.
420 # Side effects: $wanderers set.
422 sub Collect_wanderers {
426 # Slurp in the directory and close.
431 #############################################################################
435 #############################################################################
438 # Takes an array of files that all seem to share a common repcipient and
439 # remails them if possible.
441 # Parameters: None (uses @wanderers).
447 foreach $file (@wanderers) {
448 if ( !open (LOSTFILE, "< $file")) {
449 &Log_info("Could not open " . "$file" . " for remailing");
454 $done = &Get_next_msg(LOSTFILE); # Retrieve the next message...
455 &Do_remail; # and remail it.
457 undef ($current_msg); # Erase the final remailed message.
459 close(LOSTFILE); # Tidy up.
461 unlink ($file); # Remove the remailed file
467 #############################################################################
471 #############################################################################
474 # Checks the password tables for the uid of $currentTO. If the user is
475 # uid 0 (ie *supposed* to get mail in altmail) or unknown the resend is
483 if ( !&vrfy_user($currentTO) ) {
484 &Log_info("Possible non user mail file: $currentTO");
485 return $ABORT_RESEND;
488 @passwdinfo = getpwnam($currentTO);
490 print "Non user mailable mail: Name: $currentTO\n"
491 if ( $debug && ! defined @passwdinfo );
493 return !$ABORT_RESEND if ( ! defined @passwdinfo ); # A non user but evidently mailable
495 print "Check User(): Name: $currentTO -- UID: $passwdinfo[2]\n" if ($debug);
497 return $ABORT_RESEND if ( $passwdinfo[2] == 0 );
500 return !$ABORT_RESEND;
503 #############################################################################
507 #############################################################################
509 # Use SMTP VRFY to insure that argument is in fact a legal mail id.
510 # Boolean: TRUE if mailable account, FALSE if not.
514 local ($mailname,$repl) = @_;
516 if ( !&smtp_send("vrfy $mailname") ) {
517 &Log_info("Failed sending to vrfy smtp command for: $mailname");
523 print "VRFY REPLY: $repl\n" if ($debug);
525 return ( $repl =~ /^2\d\d/ );
531 #############################################################################
535 #############################################################################
537 # dummy code to shut up perl -w
538 $debug = 0 if !defined($debug);
539 print $nomail if $debug > 1;
540 print $RESENT_FROM if $debug > 1;
541 print $logopen if $debug > 1;
542 print $LOCAL_LOCK_EXT if $debug > 1;
543 print $RESENT_TO if $debug > 1;
544 print $LOCKEXT if $debug > 1;
545 print $RESENT_DATE if $debug > 1;
546 print $MESSAGE_DELIM if $debug > 1;
547 print $SMTP_retval if $debug > 1;
548 print $found if $debug > 1;
549 print $retry_list if $debug > 1;
550 print $MAILJUNK if $debug > 1;
551 print $noverify if $debug > 1;
552 print $SYSTEM_FROM_ADDRESS if $debug > 1;
556 $CONFIGDIR="@sysconfdir@"; # Directory where global config lives
557 require "$CONFIGDIR/lostaltmail.conf" if (-f "$CONFIGDIR/lostaltmail.conf");
558 require "/etc/global/lostaltmail.conf" if (-f "/etc/global/lostaltmail.conf");
559 require "/etc/os/lostaltmail.conf" if (-f "/etc/os/lostaltmail.conf");
560 require "/etc/local/lostaltmail.conf" if (-f "/etc/local/lostaltmail.conf");
565 #require "sys/socket.ph";
567 # SET some initial state variales
573 # Important!! This directory should be local. Folks will be responsible
574 # for finding this out for themselves.
576 chdir ( $MAILDIR ) || die "Cannot change to $MAILDIR (`x' bit not set?)";
581 opendir (MAIL, ".") || die "Cannot open $MAILDIR (`r' bit not set?)";
582 @allentries= readdir (MAIL);
584 @allnames = grep (!/$LOCALMAILJUNK|$MAILJUNK/, @allentries);
586 # Open chanel to SMTP for verification -- unless this option is
590 local($addr, $port,$sockaddr);
592 socket (VRFY, &AF_INET, &SOCK_STREAM, 0) ||
593 die "Could not create TCP socket (SMTP channel)";
595 $addr = (gethostbyname($SMTPHOST))[4]; # Just use the first addr
597 die "Could not obtain STMP host ($SMTPHOST) address"
600 $port = (getservbyname('smtp','tcp'))[2]; # Get smtp port.
601 die "Could not obtain SMTP port number" if (!defined($port));
603 printf("SMTP: address: %s port: $port\n",
604 join ('.',unpack('C4',$addr))) if ($debug);
606 $sockaddr = sockaddr_in($port, $addr);
608 printf("Sockaddr: %s\n", join (' ',unpack('C14',$sockaddr))) if ($debug);
610 connect (VRFY, $sockaddr) ||
611 die "Could not connect to SMTP daemon on $SMTPHOST";
613 print "Establshed SMTP channel\n" if ($debug);
615 &smtp_recv; # Greet wait
616 &smtp_send("helo $SMTPHOST"); # Helo message for picky SMTPs
617 &smtp_recv; # Helo reply
619 # Connection is up and ready to VRFY
622 # main stuff starts here
623 foreach $currentTO (@allnames) {
624 next if ( &Check_user == $ABORT_RESEND);
626 undef (@wanderers); # Just reset this at each pass.
627 @wanderers=grep (/$currentTO\.\d+/, @allentries);
629 $remail_file = &Lock_file($currentTO,$FALSE); # Need to lock the spool.
631 next if ( $remail_file eq $ABORT_RESEND); # Could not get that lock
633 push (@wanderers, $remail_file); # Try to resend "old" files.
634 print "List to remail: @wanderers\n" if ($debug);
635 # check if there is something to remail
636 &Remail_all if ( defined @wanderers && !$nomail);
639 # this stuff should run at the end
640 foreach $file (grep (/$LOCALMAILJUNK/,@allentries)) {
643 print "Would unlink $file\n" if ($debug);
645 unlink $file if (-f $file);
649 &Clean_up; # Do a clean exit.