Home | History | Annotate | Line # | Download | only in scripts
      1 #!@PERL@ -sw
      2 #
      3 # Package:	am-utils-6.x
      4 # Author:	James Tanis <jtt (at] cs.columbia.edu>
      5 #
      6 
      7 ############################################################################
      8 #
      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
     12 # example).
     13 #
     14 # Usage: lostaltmail [-debug] [-nomail] [-noverify]
     15 #
     16 # 		GLOBAL VARIABLES (as if you care :-) )
     17 # Probably a very incomplete list.
     18 #
     19 # Everything in the config file for this program *and* ...
     20 #
     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
     23 #	 $debug
     24 # $currentTO: The addresss we are currently checking on.  Actually this is
     25 #	left over from an earlier version of lostaltmail and will hopefully
     26 #	go away.
     27 # $noverify: set it from the address line. Avoid verification of $currentTO.
     28 #	This should be relatively safe as long as you are willing to
     29 #	endure bounces from mail that cannot be redelivered as opposed to
     30 #	just getting a warning. UNTESTED (but should work).
     31 #
     32 # $logopen: state variable indicating weather the log file (should there be
     33 #	one) is in fact open.
     34 #
     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
     42 #	later.
     43 #
     44 # VRFY: Handle onto SMTP verification channel.  Not to be confused with mail
     45 #	delivery; only verification occurs accross this handle.
     46 #
     47 ############################################################################
     48 
     49 ##############################################################################
     50 #									     #
     51 #				SMTP_SEND				     #
     52 #								     	     #
     53 ##############################################################################
     54 #
     55 # Send a message to the smtp channel. Inserts the necessary NEWLINE if it
     56 # does not exist;
     57 # I stole this from myself. It shouldn nott be printing errors to STDERR, but
     58 # this is a quick hack.
     59 #
     60 sub smtp_send {
     61     local ($msg) = @_;
     62     local ($length);
     63 
     64     $length=length($msg);
     65 
     66     if ( $msg !~ /^.*\n$/ ) {
     67 	$msg = $msg . "\n";
     68 	$length++;
     69     }
     70 
     71 
     72     if ( ! syswrite (VRFY, $msg, $length)) {
     73 	print STDERR "Failing SMTP write: $msg";
     74 	return 0;
     75     }
     76 
     77     return 1;
     78 }
     79 
     80 ##############################################################################
     81 #									     #
     82 #				SMTP_RECV				     #
     83 #								     	     #
     84 ##############################################################################
     85 #
     86 # Read in lines from SMTP connection and return the final
     87 # 	Really hideous -- please excuse.
     88 #
     89 sub smtp_recv {
     90     local ($line,$rin, $win, $ein, $readbuf, $ret);
     91     $readbuf = "";
     92 
     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
     96 
     97 
     98 LINE_OF_INPUT:
     99     while (1) {			# Read in all the input
    100 
    101 	if ((select ( $rin, $win, $ein, 600.0))[0]  == 0 ) {
    102 	    print "select returned -1" if ($debug);
    103 	    return -1;	# timeout
    104 	}
    105 	sysread (VRFY, $readbuf, 1024);
    106 	chop ($readbuf);
    107 
    108 	foreach $line (	split('\n', $readbuf)) {
    109 
    110 	    # This loop is actually needed since V8 has a multi-line greet.
    111 
    112 	    ( $line =~ /^(\d\d\d).*/ && ($SMTP_retval=$1)) ||
    113 		warn "Badly formed reply from SMTP peer: $line\n";
    114 
    115 	    # Space after return code indicates EOT
    116 
    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.
    121 		last LINE_OF_INPUT;
    122 	    }
    123 	} # End of read.
    124     } # End of input.
    125 
    126     return $ret;
    127 }
    128 
    129 
    130 
    131 
    132 ##############################################################################
    133 #									     #
    134 #				LOG_INFO				     #
    135 #								     	     #
    136 ##############################################################################
    137 #
    138 #
    139 # Opens appropriate logging file -- STDOUT (cron) or temp file (mail).
    140 #
    141 sub Log_info {
    142     local($message) = @_;
    143 
    144     if ( !$logopened )  {
    145 	if ( $MAILGRUNT eq "" || $debug) {
    146 	    open (LOGFILE, ">-") || die  "Unable to open stdout";
    147 	}
    148 	else {
    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";
    152 	}
    153     }
    154 
    155     $logopened=1;		# Note that the log is now open
    156 
    157     # Heart of the function.
    158     print LOGFILE "$message";
    159 
    160     print LOGFILE "\n" if ( index($message,"\n") == -1 );
    161 }
    162 
    163 ##############################################################################
    164 #									     #
    165 #				LOCK_FILE				     #
    166 #									     #
    167 ##############################################################################
    168 
    169 #
    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.
    174 
    175 # If a recurring error or really unexpected situation arrises, return
    176 # 	ABORT_RESEND
    177 #
    178 #  PARAMETERS
    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
    182 # now.
    183 
    184 sub Lock_file {
    185 
    186     local($mailfile,$should_requeue,$i,$new_lost_file) = @_;
    187 
    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 . ".$$";
    191 
    192 #  make a tmpfile name based on mailfile;
    193     $lostlockfile = "$mailfile" . "$LOCKEXT";
    194 
    195     if ( ! open(LOCKFILE, (">" . $lostlockfile)) ) {
    196 	printf(STDERR "Could not create lostlockfile for %s: %s\n", $mailfile,$!);
    197 	return $ABORT_RESEND;
    198     }
    199     close(LOCKFILE);
    200 
    201     $maillockfile = "$mailfile" . "$LOCAL_LOCK_EXT";
    202 
    203     for ($i=0; $i < $LOCK_RETRIES && ! link ($lostlockfile, $maillockfile);
    204 	 $i++) {
    205 	sleep(1);
    206     }
    207 
    208     unlink($lostlockfile);	# No matter what eliminate our cruft
    209 
    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";
    215 	}
    216 	else {
    217 	    &Log_info("Giving up on: " . "$mailfile");
    218 	}
    219 
    220 	return $ABORT_RESEND;
    221     }
    222 
    223     # We created the link and therefore have the lock
    224 
    225     if (rename ($mailfile, $new_lost_file) == 0 ){
    226 	# Failed to rename file -- this is serious.
    227 	unlink($maillockfile);
    228 	return $ABORT_RESEND;
    229     }
    230 
    231     unlink($maillockfile);
    232     return $new_lost_file;
    233 
    234 }
    235 
    236 ##############################################################################
    237 #									     #
    238 #			PARSE NEXT MAIL MESSAGE				     #
    239 #									     #
    240 ##############################################################################
    241 #
    242 # Parameters:
    243 #  mailfile: handle of mailfile to use.
    244 #
    245 # Parses the next message in the mail file and inserts it in $current_msg
    246 #
    247 sub Get_next_msg {
    248     local($mailfile,$found_body_delimiter) = @_;
    249 
    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).
    253 
    254     $done=$FALSE;
    255     $found_body_delimiter=$FALSE;
    256 
    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.
    260 
    261 
    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;
    270 	}
    271 	if (defined($current_msg)) {
    272 	    $current_msg .= $_;
    273 	} else {
    274 	    $current_msg = $_;
    275 	}
    276     }
    277 
    278     # Return TRUE when we have hit the end of the file.
    279     if (!defined($_) || $_ eq "" ) {
    280 	return $TRUE;
    281     } else {
    282 	return $FALSE;
    283     }
    284 }
    285 
    286 ##############################################################################
    287 #									     #
    288 #			SPLICE IN RESENT_HEADERS			     #
    289 #									     #
    290 ##############################################################################
    291 #
    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.
    295 
    296 #
    297 # Paremters: None.
    298 # Return: None
    299 #
    300 sub Splice_in_resent_headers {
    301     local($date,$utctime,$weekday,$time,$month,$hostname);
    302 
    303     $current_msg .= "$RESENT_TO" . "$currentTO" . "\n";
    304     $current_msg .= "$RESENT_FROM" . "$SYSTEM_FROM_ADDRESS" . "\n";
    305 
    306     # Calculate date and time.  It is a bit of a shame to do this each time
    307     # the time needs to be acurate.
    308 
    309     @utctime=gmtime(time);
    310 
    311     $weekday=(Sun,Mon,Tue,Wed,Thu,Fri,Sat)[$utctime[6]];
    312 
    313 
    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]);
    318 	}
    319 	else {
    320 	    $time=sprintf("%d:0%d:%d",$utctime[2],$utctime[1],$utctime[0]);
    321 	}
    322     }
    323     else {
    324 	if ( $utctime[0] < 10 ) {
    325 	    $time=sprintf("%d:%d:0%d",$utctime[2],$utctime[1],$utctime[0]);
    326 	}
    327         else {
    328 	    $time=sprintf("%d:%2d:%2d",$utctime[2],$utctime[1],$utctime[0]);
    329 	}
    330     }
    331 
    332     $month=(Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec)[$utctime[4]];
    333 
    334     # Ensure Y2K format
    335     $date=sprintf("%s, %d %s %d %s UTC", $weekday, $utctime[3], $month, $utctime[5]+1900, $time);
    336 
    337     $current_msg .= "$RESENT_DATE" . $date . "\n";
    338 
    339     if ( defined $RESENT_INFO && $RESENT_INFO ne "") {
    340 	$hostname=`uname -n`;
    341 	$current_msg .= "$RESENT_INFO" . "Lost mail resent from ". $hostname;
    342     }
    343 
    344     return;
    345 }
    346 
    347 ##############################################################################
    348 #									     #
    349 #				DO_REMAIL				     #
    350 #									     #
    351 ##############################################################################
    352 #
    353 # Actually resends the mail.   Talks to the process configured as $MAILER
    354 # We need better handling.
    355 #
    356 sub Do_remail {
    357     open (MAILER, "| $MAILER $currentTO") || return $ABORT_RESEND;
    358     print MAILER $current_msg;
    359     close (MAILER);
    360 }
    361 
    362 ##############################################################################
    363 #									     #
    364 #				CLEAN_UP				     #
    365 #									     #
    366 ##############################################################################
    367 #
    368 # Clean up my messes.
    369 #
    370 sub Clean_up {
    371     local ($hostname);
    372 
    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.
    377     #
    378     chop ($hostname=`uname -n`);
    379     $LOG_SUBJECT="$LOG_SUBJECT from $hostname" if ( $hostname =~ /.*\.cs\.columbia\.edu/ );
    380     #
    381     # End of ugly local hack
    382 
    383     # Mail any log info to MAILGRUNT.
    384     if (defined($logfile) && $logfile ne "" ) {
    385 	close (LOGFILE);	# Flush logfile output.
    386 	if ( -s $logfile ) {
    387 	    open (MAILER, "| $MAILER $MAILGRUNT");
    388 
    389 	    print MAILER "To: $MAILGRUNT\n";
    390 	    print MAILER "Subject: $LOG_SUBJECT\n";
    391 	    print MAILER "$HEADER_BODY_DELIM";
    392 
    393 	    open (LOGFILE, "< $logfile");
    394 
    395 	    while (<LOGFILE>) {
    396 		print MAILER $_;
    397 	    }
    398 	    close (MAILER);
    399 	    close (LOGFILE);
    400 	}
    401 
    402 	unlink($logfile);
    403     }
    404     exit(0);
    405 }
    406 
    407 
    408 ##############################################################################
    409 #									     #
    410 #				COLLECT_WANDERERS			     #
    411 #									     #
    412 ##############################################################################
    413 
    414 #
    415 # Collects other files that appear to be mail file for the $currentTO
    416 # but were not remailed successfully.
    417 #
    418 # Parameters: none (but uses $currentTO)
    419 # Return:  True if a old mail directory is found. False otherwise.
    420 # Side effects: $wanderers set.
    421 #
    422 sub Collect_wanderers {
    423 
    424     undef (@wanderers);
    425 
    426     # Slurp in the directory and close.
    427 
    428     return ($found);
    429 }
    430 
    431 #############################################################################
    432 #									    #
    433 #				REMAIL ALL				    #
    434 #									    #
    435 #############################################################################
    436 
    437 #
    438 # Takes an array of files that all seem to share a common repcipient and
    439 # remails them if possible.
    440 #
    441 # Parameters: None (uses @wanderers).
    442 #
    443 sub Remail_all {
    444     local($file,$i);
    445 
    446     $i=0;
    447     foreach $file (@wanderers) {
    448 	if ( !open (LOSTFILE, "< $file"))  {
    449 	    &Log_info("Could not open " . "$file" . " for remailing");
    450 	    next;
    451 	}
    452 
    453 	do {			# Power loop!
    454 	    $done = &Get_next_msg(LOSTFILE); # Retrieve the next message...
    455 	    &Do_remail;		# and remail it.
    456 	} until $done;
    457 	undef ($current_msg);	# Erase the final remailed message.
    458 
    459 	close(LOSTFILE);	# Tidy up.
    460 
    461 	unlink ($file);		# Remove the remailed file
    462 	$i++;
    463     }
    464 
    465 }
    466 
    467 #############################################################################
    468 #									    #
    469 #				CHECK_USER				    #
    470 #									    #
    471 #############################################################################
    472 
    473 #
    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
    476 # aborted.
    477 #
    478 #
    479 sub Check_user {
    480     local (@passwdinfo);
    481     undef (@passwdinfo);
    482 
    483     if (!$noverify && !&vrfy_user($currentTO) ) {
    484     	&Log_info("Possible non user mail file: $currentTO");
    485 	return $ABORT_RESEND;
    486     }
    487 
    488     @passwdinfo = getpwnam($currentTO);
    489 
    490     print "Non user mailable mail: Name: $currentTO\n"
    491 	if ( $debug && ! defined @passwdinfo );
    492 
    493     return !$ABORT_RESEND if ( ! defined @passwdinfo ); # A non user but evidently mailable
    494 
    495     print "Check User(): Name: $currentTO  -- UID: $passwdinfo[2]\n" if ($debug);
    496 
    497     return $ABORT_RESEND if ( $passwdinfo[2] == 0 );
    498 
    499 
    500     return !$ABORT_RESEND;
    501 }
    502 
    503 #############################################################################
    504 #									    #
    505 #				VRFY USER				    #
    506 #									    #
    507 #############################################################################
    508 #
    509 # Use SMTP VRFY to insure that argument is in fact a legal mail id.
    510 #  Boolean: TRUE if mailable account, FALSE if not.
    511 
    512 sub vrfy_user {
    513 
    514 	local ($mailname,$repl) = @_;
    515 
    516 	if ( !&smtp_send("vrfy $mailname") ) {
    517 	    &Log_info("Failed sending to vrfy smtp command for: $mailname");
    518 	    return 0;
    519 	}
    520 
    521 	$repl = &smtp_recv;
    522 
    523 	print "VRFY REPLY: $repl\n" if ($debug);
    524 
    525 	return ( $repl =~ /^2\d\d/ );
    526 
    527 
    528 }
    529 
    530 
    531 #############################################################################
    532 #									    #
    533 #				MAIN PROC				    #
    534 #									    #
    535 #############################################################################
    536 
    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;
    553 
    554 # BEGIN: stuff
    555 $prefix="@prefix@";
    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");
    561 
    562 
    563 require "ctime.pl";
    564 use Socket;
    565 #require "sys/socket.ph";
    566 
    567 # SET some initial state variales
    568 $logopen = 0;
    569 
    570 #
    571 # Change to alt_dir
    572 #
    573 # Important!! This directory should be local.  Folks will be responsible
    574 # for finding this out for themselves.
    575 #
    576 if (!defined($MAILDIR) || $MAILDIR eq "") {
    577     die "MAILDIR must be defined\n";
    578 }
    579 chdir ( $MAILDIR ) || die "Cannot change to $MAILDIR (`x' bit not set?)";
    580 
    581 #
    582 # slurp in directory
    583 #
    584 opendir (MAIL, ".") || die "Cannot open $MAILDIR (`r' bit not set?)";
    585 @allentries= readdir (MAIL);
    586 closedir (MAIL);
    587 @allnames = grep (!/$LOCALMAILJUNK|$MAILJUNK/, @allentries);
    588 
    589 # Open chanel to SMTP for verification -- unless this option is
    590 # configured off.
    591 
    592 if ( ! $noverify ) {
    593     local($addr, $port,$sockaddr);
    594 
    595     socket (VRFY, &AF_INET, &SOCK_STREAM, 0) ||
    596 	die "Could not create TCP socket (SMTP channel)";
    597 
    598     $addr = (gethostbyname($SMTPHOST))[4]; # Just use the first addr
    599 
    600     die "Could not obtain STMP host ($SMTPHOST) address"
    601 	if ( $addr eq "" );
    602 
    603     $port = (getservbyname('smtp','tcp'))[2]; # Get smtp port.
    604     die "Could not obtain SMTP port number" if (!defined($port));
    605 
    606     printf("SMTP: address: %s   port: $port\n",
    607 	   join ('.',unpack('C4',$addr))) if ($debug);
    608 
    609     $sockaddr = sockaddr_in($port, $addr);
    610 
    611     printf("Sockaddr: %s\n", join (' ',unpack('C14',$sockaddr))) if ($debug);
    612 
    613     connect (VRFY, $sockaddr) ||
    614 	die "Could not connect to SMTP daemon on $SMTPHOST";
    615 
    616     print "Establshed SMTP channel\n" if ($debug);
    617 
    618     &smtp_recv;	# Greet wait
    619     &smtp_send("helo $SMTPHOST"); # Helo message for picky SMTPs
    620     &smtp_recv;		# Helo reply
    621 
    622     # Connection is up and ready to VRFY
    623 }
    624 
    625 # main stuff starts here
    626 foreach $currentTO (@allnames) {
    627     next if ( &Check_user == $ABORT_RESEND);
    628 
    629     # just delete the file if too small to be real mail
    630     if ((stat($currentTO))[7] < 5) {
    631 	print "Too small to be real mail, unlinking $currentTO" if $debug;
    632 	unlink $currentTO;
    633     }
    634 
    635     undef (@wanderers);	# Just reset this at each pass.
    636     @wanderers=grep (/$currentTO\.\d+/, @allentries);
    637 
    638     $remail_file = &Lock_file($currentTO,$FALSE); # Need to lock the spool.
    639 
    640     next if ( $remail_file eq $ABORT_RESEND); # Could not get that lock
    641 
    642     push (@wanderers, $remail_file); # Try to resend "old" files.
    643     print "List to remail: @wanderers\n" if ($debug);
    644     # check if  there is something to remail
    645     &Remail_all if ( defined @wanderers && !$nomail);
    646 }
    647 
    648 # this stuff should run at the end
    649 foreach $file (grep (/$LOCALMAILJUNK/,@allentries)) {
    650 
    651     if ($debug) {
    652 	print "Would unlink $file\n" if ($debug);
    653     } else {
    654 	unlink $file  if (-f $file);
    655     }
    656 
    657 }
    658 &Clean_up;			# Do a clean exit.
    659