mailqueue.pl
上传用户:xxcykj
上传日期:2007-01-04
资源大小:727k
文件大小:14k
源码类别:

Email客户端

开发平台:

Unix_Linux

  1. #!/usr/bin/perl
  2. # This is script will connect to your isp (if not already connected),
  3. #   send any outgoing mail and retrieve any incoming mail.  If this
  4. #   program made the connection, it will also break the connection
  5. #   when it is done.
  6. #
  7. # Bill Adams
  8. # bill@evil.inetarena.com
  9. #
  10. # Revision History
  11. # 1.0.1   05 Sep 1998  baa  Massive updates to work with fetchmail.
  12. # Get the latest version from my home-page:
  13. #  http://www.inetarena.com/~badams/computerstuff.html
  14. #  following the 'Stuff I Have Written' link.
  15. #
  16. # License: GNU, but tell me of any improvements or changes.
  17. #
  18. use strict;
  19. my $suck;
  20. my $rdate;
  21. my ($my_syslog, $debug, $verbose);
  22. my $start_time = time;
  23. my $mailhost = 'mail';
  24. my $sendmail_queue_dir = '/var/spool/mqueue/'; #Need trailing slash!
  25. my $interface = 'ppp0';         #Watch this interface
  26. my $max_tries = 1; #How many times to try and re-dial
  27. my $retry_delay = 300;         #How long to wait to retry (in seconds)
  28. my $connect_timeout = 45; #How long to wait for connection
  29. #For the log file, be sure to put the >, >>, or | depending on
  30. #  what you want it to do.  I have also written a little program
  31. #  called simple_syslog that you can pipe the data to.
  32. my $log_file = '>/dev/null';    #Where to put the data.
  33. $log_file = '>>/var/log/mailqueue.pl';
  34. #$log_file = '>/dev/console';
  35. my $this_hour = +[localtime()]->[2];
  36. #Define this to get mail between midnight and 5 am
  37. #$suck = '/var/spool/suck/get.news.inn';
  38. #Define this to set the time to a remote server
  39. #$rdate = '/usr/bin/rdate -s clock1.unc.edu';
  40. #Where are the programs are located.  You can specify the full path if needed.
  41. my $pppd = 'pppd';
  42. my $fetchmail = 'fetchmail'; #'etrn.pl';
  43. my $sendmail = 'sendmail';
  44. #Where is the etrn/fetchmail pid
  45. my $fetchmail_pid = '/var/run/fetchmail.pid';
  46. #Set the path to where we think everything will live.
  47. $ENV{'PATH'} = ":/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:";
  48. my $lockfile = "/var/run/mailqueue.lock"; #lockfile for this program
  49. my $space = ' '; #Never know when you might need space
  50. my $program_name = $0;
  51. $program_name = substr ($program_name, (rindex ($program_name, '/') + 1));
  52. open SYSLOG, $log_file or die "Could not open $log_filent";
  53. sys_log ("Started by UID $<");
  54. #$< = 0; #suid root
  55. #Other global vars
  56. my $pppd_pid;
  57. #Make sure we are root.  This has to be the case for everything
  58. #  to work properly.
  59. if ($< != 0) {
  60.     sys_log ("Not root...exit");
  61.     print STDERR "You are not root...sorry cannot run.n";
  62.     exit (1);
  63. }
  64. sub sys_log {
  65.     #Writes a message to the log file.
  66.     my ($message) = @_;
  67.     print SYSLOG join(' ', 
  68.       $program_name,
  69.       ''.localtime(), #The '' puts it in a scaler context.
  70.       $message)."n";
  71.     print STDERR $message, "n" if $debug;
  72. }
  73. #Get the command line args.
  74. $verbose = 1;
  75. for (my $i = 0; $i <= $#ARGV; $i++) {
  76.     if ($ARGV[$i] eq '-v' || $ARGV[$i] eq '-verbose') {
  77. $verbose++;
  78. print "Running in verbose mode level ($verbose).n";
  79.     } elsif ($ARGV[$i] eq '-d' || $ARGV[$i] eq '-debug') {
  80. $debug++;
  81. $verbose = 10; #Some high value so everything gets printed
  82. print STDERR "Running in debug mode.n";
  83.     } elsif ($ARGV[$i] eq '-q' || $ARGV[$i] eq '-quiet') {
  84. $debug = 0;
  85. $verbose = 0; 
  86.     } elsif ($ARGV[$i] eq '-max_tries') {
  87. if (not defined $ARGV[$i + 1]) {
  88.     printf STDERR "$0: Error: option -max_tries requires a value.n";
  89.     &usage;
  90. } else {
  91.     $max_tries = $ARGV[$i + 1];
  92. }
  93.     } elsif ($ARGV[$i] eq '-retry_delay') {
  94. if (not defined $ARGV[$i + 1]) {
  95.     printf STDERR "$0: Error: option -retry_delay requires a value.n";
  96.     &usage;
  97. } else {
  98.     $max_tries = $ARGV[$i + 1];
  99. }
  100.     } elsif ($ARGV[$i] eq '-interface') {
  101. if (not defined $ARGV[$i + 1]) {
  102.     printf STDERR "$0: Error: option -interface requires a value.n";
  103.     &usage;
  104. } else {
  105.     $max_tries = $ARGV[$i + 1];
  106. }
  107.     } elsif ($ARGV[$i] eq '-mailhost') {
  108. if (not defined $ARGV[$i + 1]) {
  109.     printf STDERR "$0: Error: option -mailhost requires a value.n";
  110.     &usage;
  111. } else {
  112.     $mailhost = $ARGV[$i + 1];
  113. }
  114.     } else {
  115. print STDERR "Unknown command line option: [". $ARGV[$i]."]n";
  116. &usage;
  117.     }
  118. }
  119. $| = 1 if $verbose; #Output un-buffered if we are verbose
  120. #Do some checking for programs
  121. &check_program ($my_syslog) || die "$0 -> Error: $my_syslog is requiredn";
  122. ($fetchmail = &check_program ($fetchmail)) 
  123.     || die "$0 -> Error: Could not find fetchmail/etrnn";
  124. ($pppd = &check_program ($pppd))
  125.     || die "$0 -> Error: Could not find pppdn";
  126. (-d $sendmail_queue_dir) || die "$0 -> Error: The sendmail queue directorynt[$sendmail_queue_dir] does not exist or is not a directory.n";
  127. ($sendmail = &check_program ($sendmail)) 
  128.     || die "$0 -> Error: Could not find $sendmailn";
  129. #Do some process locking.  This kills any already running processes.
  130. if (-s $lockfile) {
  131.     my $pid = `cat $lockfile`; chop $pid;
  132.     if (not &process_is_dead ($pid)) {
  133. print STDERR "$0 -> Process locked by pid $pid killing it.n" 
  134.     if $verbose;
  135. kill 15, $pid;
  136. waitpid ($pid, 0); #This has no effect.
  137.     }
  138.     sys_log ("Removing stale lock for pid $pid") if $verbose;
  139.     unlink ($lockfile) || die $!;
  140. }
  141. open (LOCK, '>'.$lockfile) || die "$0: Could not create lockfile $lockfilen";
  142. print LOCK $$, "n";
  143. close LOCK;
  144. #print out some info if needed.
  145. if ($debug) {
  146.     print STDERR "             Max tries: $max_triesn";
  147.     print STDERR "      Dial Retry Delay: $retry_delay seconds.n";
  148.     print STDERR "Interface set to watch: $interfacen";
  149.     print STDERR " Mailhost set to watch: $mailhostn";
  150.     print STDERR "    Connection timeout: $connect_timeoutn";
  151.     print STDERR "              Sendmail: $sendmailn";
  152.     print STDERR "                  pppd: $pppdn";
  153.     print STDERR "     fetchmail/etrn.pl: $fetchmailn";
  154.     print STDERR "nn";
  155. }
  156. ((-x $pppd) && (-x $sendmail) && (-x $fetchmail)) 
  157. || die "Still some problem with programs.ntRun with -d to see if the path is specified for sendmail,ntpppd and fetchmail/etrn.pl";
  158. while ($max_tries--) {
  159.     my $child_pid;
  160.     unless ($child_pid = fork)  {
  161. #This is the child process that waits for a connection to be made
  162. #  and then sends the local mail queue and then sends a request to
  163. #  get the remote mail queue
  164. my $count = $connect_timeout;
  165. while (&interface_is_down ($interface) && $count--) {sleep (1)}
  166. if ($count < 1) {exit (1)}
  167. #Send any queued mail.  I had another routine that would
  168. #  fork and watch sendmail with a timeout, but that is kinda
  169. #  flaky depending on how big your queue size is. So
  170. #  now just call it and wait for it to return.  If you have bad
  171. #  messages in your queue, this can hang.
  172. sys_log ("Have connection->sending any local mail.") if $verbose;
  173. system("$sendmail -q");
  174. sys_log ("Checking remote queue on ($mailhost)");
  175. my $result;
  176. my $daemon = 0;
  177. my $pid;
  178. #In case we have a pid, read it and find out if it is
  179. #  still valid or not.
  180. if (defined $fetchmail_pid and -f $fetchmail_pid) {
  181.     if (not open PID, $fetchmail_pid) {
  182. sys_log("Could not open $fetchmail_pid");
  183. die}
  184.     $pid = <PID>;
  185.     if ($pid =~ m|([0-9]+)s+([0-9]*)|) {
  186. $pid = $1;
  187. $daemon = $2;
  188.     }
  189.     close PID;
  190.     sys_log("Have PID file ($fetchmail_pid) with PID $pid $daemon");
  191.     #In the case of fetchmail, we need to see if it is
  192.     #  still running in case there is a stale lock file.
  193.     if (&process_is_dead($pid)) {
  194. sys_log("  It is no longer running");
  195. $daemon = 0; $pid = 0}
  196. }
  197. if (not $pid or ($pid and $daemon)) {
  198.     #Either it is not running or it is running and a daemon.
  199.     sys_log("Running $fetchmail [$daemon]");
  200.     my $result = (system ($fetchmail))/256;
  201.     sys_log($fetchmail.' exited with status '.$result) if $debug;
  202. } else {
  203.     sys_log("$fetchmail already running...");
  204. }
  205. #Watch the directory for n seconds of inactivity.
  206. sys_log("Fetchmail done...watching $sendmail_queue_dir");
  207. &watch_dir ($sendmail_queue_dir, 10);
  208. sys_log ("Done polling for mail");
  209. if (-f $fetchmail_pid and not $daemon) {
  210.     #In case something went wrong and the fetchmail is still 
  211.     # running (and not a daemon)....
  212.     my $result = `$fetchmail -q`; chop $result;
  213.     sys_log($result);
  214. }
  215. exit (0);
  216.     }
  217.     #If a connection is needed, make it.
  218.     if (&interface_is_down ($interface) && $pppd_pid == 0) {
  219. sys_log ("Try to connect with pppd") if $debug;
  220. # Fork pppd with a pid we can track.
  221. unless ($pppd_pid = fork) {
  222. exec ($pppd.' -detach');
  223. }
  224.     }
  225.     #Wait for the child to exit and check for errors
  226.     waitpid ($child_pid, 0);
  227.     my $child_status = ($? / 256);
  228.     my $child_kill = $? % 256;
  229.     if ($child_status == 0) {
  230. if ($this_hour <= 4 and defined $suck) {
  231.     sys_log ("Calling suck...");
  232.     print `$suck`;
  233. }
  234. if (defined $rdate) {
  235.    sys_log ("Calling rtime...");
  236.    print `$rdate`;
  237. }
  238. if ($pppd_pid) {  #If we ran pppd, kill it
  239.     sys_log ("Killing pppd (pid $pppd_pid)");
  240.     kill 15, $pppd_pid;
  241.     waitpid ($pppd_pid, 0); #Wait for clean exit of child
  242. }
  243. sys_log ("Finished with cycle.");
  244. unlink ($lockfile);
  245. sys_log ("Total time: ".(time-$start_time)." seconds") if $debug;
  246. exit (0);
  247.     }
  248.     # Reset to pppp_pid to zero if pppd is not running.
  249.     if ($pppd_pid && &process_is_dead ($pppd_pid)) {$pppd_pid = 0}
  250.     sys_log (join ('', "Warn: Did not connect -> Try ",
  251.    $max_tries, " more times...after ",
  252.    $retry_delay, " seconds"));
  253.     if (not $max_tries) {
  254. sys_log ("Giving up...");
  255. exit (1);
  256.     }
  257.     sleep ($retry_delay);
  258.     sys_log ("ok...trying again.");
  259. }
  260. sub check_program {
  261.     #See if a program is in the path
  262.     my ($program) = @_;
  263.     my $exists = 0;
  264.     my $path_specified = 0;
  265.     my $path;
  266.     
  267.     #catch the case where there is already a slash in the argument.
  268.     if ($program =~ ///) {
  269. $path_specified = 1;
  270. if (-x $program) {$exists = $program}
  271.     }
  272.     my $exists;
  273.     foreach $path (split(/:/, $ENV{'PATH'})) {
  274. next if length ($path) < 3; #skip bogus path entries
  275. #be sure the there is a trailing slash
  276. if (substr ($path, -1, 1) ne '/') {$path .= '/'}
  277. #Check to see if it exists and is executable
  278. if (-x $path.$program) {$exists = $path.$program; last}
  279.     }
  280.     if (not $exists) {
  281. if ($path_specified) {
  282.     print STDERR "$0 -> Warn: ". $program. 
  283. " is not executable or does not exist.n";
  284. } else {
  285.     print STDERR "$0 -> Warn: [$program] was not found in pathnt".
  286. $ENV{'PATH'}."n";
  287. }
  288.     }
  289.     return ($exists);
  290. }
  291. sub process_is_dead {
  292.     #This is a cheap way to check for running processes.  I could use
  293.     #  the /proc file-system in Linux but that would not be very 
  294.     #  friendly to other OS's.
  295.     #
  296.     #return 1 if pid is not in process list
  297.     # This expects ps to return a header line and then another line if
  298.     #  the process is running.  Also check for zombies
  299.     my ($pid) = @_;
  300.     my @results = split (/n/, `ps $pid 2>/dev/null`);
  301.     if (not defined $results[1]) {return  1}
  302.     if ($results[1] =~ /zombie/i) {return 1}
  303.     return 0;
  304. }
  305. sub interface_is_down {
  306.     # return 1 (true) if the ip is down
  307.     my ($interface) = @_;
  308.     if (`ifconfig $interface` =~ /UP/) {
  309. return 0;
  310.     } else {
  311. return 1;
  312.     }
  313. }
  314. sub watch_dir {
  315.     #Watch the mailqueue directory for incoming files.
  316.     #  The 'xf' files are the transfer (xfer) files on my system.
  317.     #  If you find this is not the case, please email me.  To be safe,
  318.     #  I check the latest mod time as long as xf files exist.  If no
  319.     #  data has made it over in n seconds, we will assume that an
  320.     #  error has occured and give up.
  321.     my $files_like = '^(xf.*)'; #Regexp
  322.     my $dir_to_watch = shift;
  323.     my $delay = shift;
  324.     my $timeout = 120;  #Give it 120 seconds to get data.
  325.     my $loop_delay = 1; #How long between each loop. Do not make 0!
  326.     #Make sure there is a trailing slash.
  327.     if ($dir_to_watch !~ m|/$|) {$dir_to_watch .= '/'}
  328.     #How long to wait for transfer of data.  This gets reset
  329.     #  each time the mod time falls below a certain time.
  330.     my $flag = $delay;
  331.     my $last_total = 0;
  332.     
  333.     while (($flag -= $loop_delay) > 0) {
  334. sleep $loop_delay;
  335. opendir (DIR, $dir_to_watch);
  336. my $file_count = 0;
  337. my $last_data_dl = 500000; #Big Number
  338. foreach my $file (readdir (DIR)) {
  339.     next if not -f $dir_to_watch.$file; #Only files.
  340.     my @stats = stat($dir_to_watch.$file);
  341.     my $m_time = time - $stats[9];
  342.     #Here, if we have a recent file, reset the timeout.
  343.     if ($m_time < $last_data_dl) {$last_data_dl = $m_time}
  344.     
  345.     #If we have an xfer file, up the delay.
  346.     if ($file =~ m|$files_like|) {
  347. sys_log("$file is like $files_like");
  348. $flag = $delay;
  349.     }
  350. }
  351. closedir (DIR);
  352. sys_log ("Watch_dir: $flag ($last_data_dl)") if $debug;
  353. #In the case of now data downloaded...
  354. if ($last_data_dl > $timeout and $flag == $delay) {
  355.     sys_log("Watch_dir: Timed out after $timeout seconds.");
  356.     $flag = 0;
  357. }
  358.     }
  359.     sys_log ("Watch_dir: Done.");
  360. }
  361. sub usage {
  362.     #print the usage
  363.     print join ("n",
  364. 'mailqueue.pl -- A program to send and receive mail form a sendmail spooler.',
  365. '  Requires that you ISP is running sendmail, at lease version 8.6.?.',
  366. '  Also requires that you have fetchmail or etrn.pl installed on this system.',
  367. '', 'Command line args (Default in parrens):',
  368. '  -v -verbose         Run in verbose mode.  Can use this arg multiple times.',
  369. '  -d -debug           Run in debug mode.  Sets verbose level to 10',
  370. '  -max_tries N        Sets the maximum number of connect retries to N.  ('.$max_tries.')',
  371. '  -retry_delay N      Sets the delay between retrying to N seconds.  ('. $retry_delay.')',
  372.         "  -connect_timeout N  Sets the connection timeout to N seconds. (". $connect_timeout. ')',
  373. '  -interface STR      Sets the default interface to STR.  ('. $interface. ')',
  374. '  -mailhost STR       Sets the mailhost to STR. ('. $mailhost.')',
  375. '');
  376.     exit (1);
  377. }