diff options
Diffstat (limited to 'contrib/mailqueue.pl')
-rw-r--r-- | contrib/mailqueue.pl | 420 |
1 files changed, 420 insertions, 0 deletions
diff --git a/contrib/mailqueue.pl b/contrib/mailqueue.pl new file mode 100644 index 00000000..50acb831 --- /dev/null +++ b/contrib/mailqueue.pl @@ -0,0 +1,420 @@ +#!/usr/bin/perl +# This is script will connect to your isp (if not already connected), +# send any outgoing mail and retrieve any incoming mail. If this +# program made the connection, it will also break the connection +# when it is done. +# +# Bill Adams +# bill@evil.inetarena.com +# +# Revision History +# 1.0.1 05 Sep 1998 baa Massive updates to work with fetchmail. +# +# Get the latest version from my home-page: +# http://www.inetarena.com/~badams/computerstuff.html +# following the 'Stuff I Have Written' link. +# +# License: GNU, but tell me of any improvements or changes. +# +use strict; + +my $suck; +my $rdate; +my ($my_syslog, $debug, $verbose); + +my $start_time = time; +my $mailhost = 'mail'; +my $sendmail_queue_dir = '/var/spool/mqueue/'; #Need trailing slash! +my $interface = 'ppp0'; #Watch this interface +my $max_tries = 1; #How many times to try and re-dial +my $retry_delay = 300; #How long to wait to retry (in seconds) +my $connect_timeout = 45; #How long to wait for connection + +#For the log file, be sure to put the >, >>, or | depending on +# what you want it to do. I have also written a little program +# called simple_syslog that you can pipe the data to. +my $log_file = '>/dev/null'; #Where to put the data. +$log_file = '>>/var/log/mailqueue.pl'; +#$log_file = '>/dev/console'; + +my $this_hour = +[localtime()]->[2]; + +#Define this to get mail between midnight and 5 am +#$suck = '/var/spool/suck/get.news.inn'; + +#Define this to set the time to a remote server +#$rdate = '/usr/bin/rdate -s clock1.unc.edu'; + +#Where are the programs are located. You can specify the full path if needed. +my $pppd = 'pppd'; +my $fetchmail = 'fetchmail'; #'etrn.pl'; +my $sendmail = 'sendmail'; + +#Where is the etrn/fetchmail pid +my $fetchmail_pid = '/var/run/fetchmail.pid'; + + +#Set the path to where we think everything will live. +$ENV{'PATH'} = ":/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:"; +my $lockfile = "/var/run/mailqueue.lock"; #lockfile for this program +my $space = ' '; #Never know when you might need space +my $program_name = $0; +$program_name = substr ($program_name, (rindex ($program_name, '/') + 1)); +open SYSLOG, $log_file or die "Could not open $log_file\n\t"; + +sys_log ("Started by UID $<"); +#$< = 0; #suid root + +#Other global vars +my $pppd_pid; + +#Make sure we are root. This has to be the case for everything +# to work properly. +if ($< != 0) { + sys_log ("Not root...exit"); + print STDERR "You are not root...sorry cannot run.\n"; + exit (1); +} + +sub sys_log { + #Writes a message to the log file. + my ($message) = @_; + print SYSLOG join(' ', + $program_name, + ''.localtime(), #The '' puts it in a scaler context. + $message)."\n"; + print STDERR $message, "\n" if $debug; +} + +#Get the command line args. +$verbose = 1; +for (my $i = 0; $i <= $#ARGV; $i++) { + if ($ARGV[$i] eq '-v' || $ARGV[$i] eq '-verbose') { + $verbose++; + print "Running in verbose mode level ($verbose).\n"; + } elsif ($ARGV[$i] eq '-d' || $ARGV[$i] eq '-debug') { + $debug++; + $verbose = 10; #Some high value so everything gets printed + print STDERR "Running in debug mode.\n"; + } elsif ($ARGV[$i] eq '-q' || $ARGV[$i] eq '-quiet') { + $debug = 0; + $verbose = 0; + } elsif ($ARGV[$i] eq '-max_tries') { + if (not defined $ARGV[$i + 1]) { + printf STDERR "$0: Error: option -max_tries requires a value.\n"; + &usage; + } else { + $max_tries = $ARGV[$i + 1]; + } + } elsif ($ARGV[$i] eq '-retry_delay') { + if (not defined $ARGV[$i + 1]) { + printf STDERR "$0: Error: option -retry_delay requires a value.\n"; + &usage; + } else { + $max_tries = $ARGV[$i + 1]; + } + } elsif ($ARGV[$i] eq '-interface') { + if (not defined $ARGV[$i + 1]) { + printf STDERR "$0: Error: option -interface requires a value.\n"; + &usage; + } else { + $max_tries = $ARGV[$i + 1]; + } + } elsif ($ARGV[$i] eq '-mailhost') { + if (not defined $ARGV[$i + 1]) { + printf STDERR "$0: Error: option -mailhost requires a value.\n"; + &usage; + } else { + $mailhost = $ARGV[$i + 1]; + } + } else { + print STDERR "Unknown command line option: [". $ARGV[$i]."]\n"; + &usage; + } +} + + +$| = 1 if $verbose; #Output un-buffered if we are verbose + + +#Do some checking for programs +&check_program ($my_syslog) || die "$0 -> Error: $my_syslog is required\n"; +($fetchmail = &check_program ($fetchmail)) + || die "$0 -> Error: Could not find fetchmail/etrn\n"; +($pppd = &check_program ($pppd)) + || die "$0 -> Error: Could not find pppd\n"; +(-d $sendmail_queue_dir) || die "$0 -> Error: The sendmail queue directory\n\t[$sendmail_queue_dir] does not exist or is not a directory.\n"; +($sendmail = &check_program ($sendmail)) + || die "$0 -> Error: Could not find $sendmail\n"; + + +#Do some process locking. This kills any already running processes. +if (-s $lockfile) { + my $pid = `cat $lockfile`; chop $pid; + if (not &process_is_dead ($pid)) { + print STDERR "$0 -> Process locked by pid $pid killing it.\n" + if $verbose; + kill 15, $pid; + waitpid ($pid, 0); #This has no effect. + } + sys_log ("Removing stale lock for pid $pid") if $verbose; + unlink ($lockfile) || die $!; +} +open (LOCK, '>'.$lockfile) || die "$0: Could not create lockfile $lockfile\n"; +print LOCK $$, "\n"; +close LOCK; + +#print out some info if needed. +if ($debug) { + print STDERR " Max tries: $max_tries\n"; + print STDERR " Dial Retry Delay: $retry_delay seconds.\n"; + print STDERR "Interface set to watch: $interface\n"; + print STDERR " Mailhost set to watch: $mailhost\n"; + print STDERR " Connection timeout: $connect_timeout\n"; + print STDERR " Sendmail: $sendmail\n"; + print STDERR " pppd: $pppd\n"; + print STDERR " fetchmail/etrn.pl: $fetchmail\n"; + print STDERR "\n\n"; +} +((-x $pppd) && (-x $sendmail) && (-x $fetchmail)) + || die "Still some problem with programs.\n\tRun with -d to see if the path is specified for sendmail,\n\tpppd and fetchmail/etrn.pl"; + +while ($max_tries--) { + my $child_pid; + unless ($child_pid = fork) { + #This is the child process that waits for a connection to be made + # and then sends the local mail queue and then sends a request to + # get the remote mail queue + my $count = $connect_timeout; + while (&interface_is_down ($interface) && $count--) {sleep (1)} + if ($count < 1) {exit (1)} + + #Send any queued mail. I had another routine that would + # fork and watch sendmail with a timeout, but that is kinda + # flaky depending on how big your queue size is. So + # now just call it and wait for it to return. If you have bad + # messages in your queue, this can hang. + sys_log ("Have connection->sending any local mail.") if $verbose; + system("$sendmail -q"); + + sys_log ("Checking remote queue on ($mailhost)"); + + my $result; + my $daemon = 0; + my $pid; + #In case we have a pid, read it and find out if it is + # still valid or not. + if (defined $fetchmail_pid and -f $fetchmail_pid) { + if (not open PID, $fetchmail_pid) { + sys_log("Could not open $fetchmail_pid"); + die} + $pid = <PID>; + if ($pid =~ m|([0-9]+)\s+([0-9]*)|) { + $pid = $1; + $daemon = $2; + } + close PID; + sys_log("Have PID file ($fetchmail_pid) with PID $pid $daemon"); + #In the case of fetchmail, we need to see if it is + # still running in case there is a stale lock file. + if (&process_is_dead($pid)) { + sys_log(" It is no longer running"); + $daemon = 0; $pid = 0} + } + if (not $pid or ($pid and $daemon)) { + #Either it is not running or it is running and a daemon. + sys_log("Running $fetchmail [$daemon]"); + my $result = (system ($fetchmail))/256; + sys_log($fetchmail.' exited with status '.$result) if $debug; + } else { + sys_log("$fetchmail already running..."); + } + + #Watch the directory for n seconds of inactivity. + sys_log("Fetchmail done...watching $sendmail_queue_dir"); + &watch_dir ($sendmail_queue_dir, 10); + sys_log ("Done polling for mail"); + + if (-f $fetchmail_pid and not $daemon) { + #In case something went wrong and the fetchmail is still + # running (and not a daemon).... + my $result = `$fetchmail -q`; chop $result; + sys_log($result); + } + exit (0); + } + #If a connection is needed, make it. + if (&interface_is_down ($interface) && $pppd_pid == 0) { + sys_log ("Try to connect with pppd") if $debug; + # Fork pppd with a pid we can track. + unless ($pppd_pid = fork) { + exec ($pppd.' -detach'); + } + } + #Wait for the child to exit and check for errors + waitpid ($child_pid, 0); + my $child_status = ($? / 256); + my $child_kill = $? % 256; + if ($child_status == 0) { + if ($this_hour <= 4 and defined $suck) { + sys_log ("Calling suck..."); + print `$suck`; + } + if (defined $rdate) { + sys_log ("Calling rtime..."); + print `$rdate`; + } + if ($pppd_pid) { #If we ran pppd, kill it + sys_log ("Killing pppd (pid $pppd_pid)"); + kill 15, $pppd_pid; + waitpid ($pppd_pid, 0); #Wait for clean exit of child + } + sys_log ("Finished with cycle."); + unlink ($lockfile); + sys_log ("Total time: ".(time-$start_time)." seconds") if $debug; + exit (0); + } + # Reset to pppp_pid to zero if pppd is not running. + if ($pppd_pid && &process_is_dead ($pppd_pid)) {$pppd_pid = 0} + sys_log (join ('', "Warn: Did not connect -> Try ", + $max_tries, " more times...after ", + $retry_delay, " seconds")); + if (not $max_tries) { + sys_log ("Giving up..."); + exit (1); + } + sleep ($retry_delay); + sys_log ("ok...trying again."); +} + +sub check_program { + #See if a program is in the path + my ($program) = @_; + my $exists = 0; + my $path_specified = 0; + my $path; + + #catch the case where there is already a slash in the argument. + + if ($program =~ /\//) { + $path_specified = 1; + if (-x $program) {$exists = $program} + } + + my $exists; + foreach $path (split(/:/, $ENV{'PATH'})) { + next if length ($path) < 3; #skip bogus path entries + #be sure the there is a trailing slash + if (substr ($path, -1, 1) ne '/') {$path .= '/'} + #Check to see if it exists and is executable + if (-x $path.$program) {$exists = $path.$program; last} + } + if (not $exists) { + if ($path_specified) { + print STDERR "$0 -> Warn: ". $program. + " is not executable or does not exist.\n"; + } else { + print STDERR "$0 -> Warn: [$program] was not found in path\n\t". + $ENV{'PATH'}."\n"; + } + } + return ($exists); +} + + +sub process_is_dead { + #This is a cheap way to check for running processes. I could use + # the /proc file-system in Linux but that would not be very + # friendly to other OS's. + # + #return 1 if pid is not in process list + # This expects ps to return a header line and then another line if + # the process is running. Also check for zombies + my ($pid) = @_; + my @results = split (/\n/, `ps $pid 2>/dev/null`); + if (not defined $results[1]) {return 1} + if ($results[1] =~ /zombie/i) {return 1} + return 0; +} + + +sub interface_is_down { + # return 1 (true) if the ip is down + my ($interface) = @_; + if (`ifconfig $interface` =~ /UP/) { + return 0; + } else { + return 1; + } +} + +sub watch_dir { + #Watch the mailqueue directory for incoming files. + # The 'xf' files are the transfer (xfer) files on my system. + # If you find this is not the case, please email me. To be safe, + # I check the latest mod time as long as xf files exist. If no + # data has made it over in n seconds, we will assume that an + # error has occured and give up. + + my $files_like = '^(xf.*)'; #Regexp + my $dir_to_watch = shift; + my $delay = shift; + my $timeout = 120; #Give it 120 seconds to get data. + my $loop_delay = 1; #How long between each loop. Do not make 0! + + #Make sure there is a trailing slash. + if ($dir_to_watch !~ m|/$|) {$dir_to_watch .= '/'} + + #How long to wait for transfer of data. This gets reset + # each time the mod time falls below a certain time. + my $flag = $delay; + my $last_total = 0; + + while (($flag -= $loop_delay) > 0) { + sleep $loop_delay; + opendir (DIR, $dir_to_watch); + my $file_count = 0; + my $last_data_dl = 500000; #Big Number + foreach my $file (readdir (DIR)) { + next if not -f $dir_to_watch.$file; #Only files. + my @stats = stat($dir_to_watch.$file); + my $m_time = time - $stats[9]; + #Here, if we have a recent file, reset the timeout. + if ($m_time < $last_data_dl) {$last_data_dl = $m_time} + + #If we have an xfer file, up the delay. + if ($file =~ m|$files_like|) { + sys_log("$file is like $files_like"); + $flag = $delay; + } + } + closedir (DIR); + sys_log ("Watch_dir: $flag ($last_data_dl)") if $debug; + + #In the case of now data downloaded... + if ($last_data_dl > $timeout and $flag == $delay) { + sys_log("Watch_dir: Timed out after $timeout seconds."); + $flag = 0; + } + } + sys_log ("Watch_dir: Done."); +} + +sub usage { + #print the usage + print join ("\n", + 'mailqueue.pl -- A program to send and receive mail form a sendmail spooler.', + ' Requires that you ISP is running sendmail, at lease version 8.6.?.', + ' Also requires that you have fetchmail or etrn.pl installed on this system.', + '', 'Command line args (Default in parrens):', + ' -v -verbose Run in verbose mode. Can use this arg multiple times.', + ' -d -debug Run in debug mode. Sets verbose level to 10', + ' -max_tries N Sets the maximum number of connect retries to N. ('.$max_tries.')', + ' -retry_delay N Sets the delay between retrying to N seconds. ('. $retry_delay.')', + " -connect_timeout N Sets the connection timeout to N seconds. (". $connect_timeout. ')', + ' -interface STR Sets the default interface to STR. ('. $interface. ')', + ' -mailhost STR Sets the mailhost to STR. ('. $mailhost.')', + ''); + exit (1); +} + |