mail_to_db.pl
上传用户:tsgydb
上传日期:2007-04-14
资源大小:10674k
文件大小:14k
源码类别:

MySQL数据库

开发平台:

Visual C++

  1. #!/usr/bin/perl -w
  2. # Copyright Abandoned 1998 TCX DataKonsult AB & Monty Program KB & Detron HB
  3. # This file is public domain and comes with NO WARRANTY of any kind
  4. #
  5. # This program is brought to you by Janne-Petteri Koilo with the 
  6. # administration of Michael Widenius.
  7. #
  8. # Rewritten with a lot of bug fixes by Jani Tolonen and Thimble Smith
  9. # 15.12.2000
  10. #
  11. # This program takes your mails and puts them into your database. It ignores
  12. # messages with the same from, date and message text.
  13. # You can use mail-files that are compressed or gzipped and ends with
  14. # -.gz or -.Z.
  15. use DBI;
  16. use Getopt::Long;
  17. $| = 1;
  18. $VER = "2.1";
  19. $opt_help          = 0;
  20. $opt_version       = 0;
  21. $opt_debug         = 0;
  22. $opt_host          = undef();
  23. $opt_port          = undef();
  24. $opt_socket        = undef();
  25. $opt_db            = undef();
  26. $opt_table         = undef();
  27. $opt_user          = undef();
  28. $opt_password      = undef();
  29. $opt_max_mail_size = 65536;
  30. $opt_create        = 0;
  31. $opt_test          = 0;
  32. $opt_no_path       = 0;
  33. $opt_stop_on_error = 0;
  34. my ($dbh, $progname, $mail_no_from_f, $mail_no_txt_f, $mail_too_big,
  35.     $mail_forwarded, $mail_duplicates, $mail_no_subject_f, $mail_inserted);
  36. $mail_no_from_f = $mail_no_txt_f = $mail_too_big = $mail_forwarded =
  37. $mail_duplicates = $mail_no_subject_f = $mail_inserted = 0;
  38. $mail_fixed=0;
  39. #
  40. # Remove the following message-ends from message
  41. #
  42. @remove_tail= (
  43. "n-*nSend a mail to .*n.*n.*$",
  44. "n-*nPlease check .*n.*nnTo unsubscribe, .*n.*n.*nIf you have a broken.*n.*n.*$",
  45. "n-*nPlease check .*n(.*n){1,3}nTo unsubscribe.*n.*n.*$",
  46. "n-*nPlease check .*n.*nnTo unsubscribe.*n.*$",
  47. "n-*nTo request this thread.*nTo unsubscribe.*n.*.*n.*$",
  48. "n -*n.*Send a mail to.*n.*n.*unsubscribe.*$",
  49. "n-*nTo request this thread.*nnTo unsubscribe.*n.*$"
  50. );
  51. # Generate regexp to remove tails where the unsubscribed is quoted
  52. {
  53.   my (@tmp, $tail);
  54.   @tmp=();
  55.   foreach $tail (@remove_tail)
  56.   {
  57.     $tail =~ s/n/n[> ]*/g;
  58.     push(@tmp, $tail);
  59.   }
  60.   push @remove_tail,@tmp;
  61. }
  62. my %months = ('Jan' => 1, 'Feb' => 2, 'Mar' => 3, 'Apr' => 4, 'May' => 5,
  63.       'Jun' => 6, 'Jul' => 7, 'Aug' => 8, 'Sep' => 9, 'Oct' => 10,
  64.       'Nov' => 11, 'Dec' => 12);
  65. $progname = $0;
  66. $progname =~ s/.*[/]//;
  67. main();
  68. ####
  69. #### main sub routine
  70. ####
  71. sub main
  72. {
  73.   my ($connect_arg, @args, $ignored, @defops, $i);
  74.   if (defined(my_which("my_print_defaults")))
  75.   {
  76.     @defops = `my_print_defaults mail_to_db`;
  77.     chop @defops;
  78.     splice @ARGV, 0, 0, @defops;
  79.   }
  80.   else
  81.   {
  82.     print "WARNING: No command 'my_print_defaults' found; unable to readn";
  83.     print "the my.cnf file. This command is available from the latest MySQLn";
  84.     print "distribution.n";
  85.   }
  86.   GetOptions("help","version","host=s","port=i","socket=s","db=s","table=s",
  87.      "user=s","password=s","max_mail_size=i","create","test",
  88.      "no_path","debug","stop_on_error")
  89.   || die "Wrong option! See $progname --helpn";
  90.   usage($VER) if ($opt_help || $opt_version || (!$ARGV[0] && !$opt_create));
  91.   # Check that the given inbox files exist and are regular files
  92.   for ($i = 0; defined($ARGV[$i]); $i++)
  93.   {
  94.     die "FATAL: Can't find inbox file: $ARGV[$i]n" if (! -f $ARGV[$i]);
  95.   }
  96.   $connect_arg = "DBI:mysql:";
  97.   push @args, "database=$opt_db" if defined($opt_db);
  98.   push @args, "host=$opt_host" if defined($opt_host);
  99.   push @args, "port=$opt_port" if defined($opt_port);
  100.   push @args, "mysql_socket=$opt_socket" if defined($opt_socket);
  101.   push @args, "mysql_read_default_group=mail_to_db";
  102.   $connect_arg .= join ';', @args;
  103.   $dbh = DBI->connect("$connect_arg", $opt_user, $opt_password,
  104.      { PrintError => 0})
  105.   || die "Couldn't connect: $DBI::errstrn";
  106.   die "You must specify the database; use --db=" if (!defined($opt_db));
  107.   die "You must specify the table; use --table=" if (!defined($opt_table));
  108.   create_table($dbh) if ($opt_create);
  109.   foreach (@ARGV)
  110.   {
  111.     # Check if the file is compressed
  112.     if (/^(.*).(gz|Z)$/)
  113.     {
  114.       open(FILE, "zcat $_ |");
  115.       process_mail_file($dbh, $1);
  116.     }
  117.     else
  118.     {
  119.       open(FILE, $_);
  120.       process_mail_file($dbh, $_);
  121.     }
  122.   }
  123.   $dbh->disconnect if (!$opt_test);
  124.   $ignored = ($mail_no_from_f + $mail_no_subject_f + $mail_no_txt_f +
  125.       $mail_too_big + $mail_duplicates);
  126.   print "Mails inserted:ttt$mail_insertedn";
  127.   print "Mails ignored:ttt$ignoredn";
  128.   print "Mails without "From:" -field:t$mail_no_from_fn";
  129.   print "Mails without message:tt$mail_no_txt_fn";
  130.   print "Mails without subject:tt$mail_no_subject_fn";
  131.   print "Too big mails (> $opt_max_mail_size):t$mail_too_bign";
  132.   print "Duplicate mails:tt$mail_duplicatesn";
  133.   print "Forwarded mails:tt$mail_forwardedn";
  134.   print "Total number of mails:tt"; 
  135.   print $mail_inserted + $ignored;
  136.   print "n";
  137.   print "Mails with unsubscribe removed:t$mail_fixedn";
  138.   exit(0);
  139. }
  140. ####
  141. #### table creation
  142. ####
  143. sub create_table
  144. {
  145.   my ($dbh) = @_;
  146.   my ($sth, $query);
  147.   $query = <<EOF;
  148. CREATE TABLE $opt_table
  149. (
  150.  mail_id MEDIUMINT UNSIGNED NOT NULL auto_increment,
  151.  date DATETIME NOT NULL,
  152.  time_zone VARCHAR(20),
  153.  mail_from VARCHAR(120) NOT NULL,
  154.  reply VARCHAR(120),
  155.  mail_to TEXT,
  156.  cc TEXT,
  157.  sbj VARCHAR(200),
  158.  txt MEDIUMTEXT NOT NULL,
  159.  file VARCHAR(64) NOT NULL,
  160.  hash INTEGER NOT NULL,
  161.  KEY (mail_id),
  162.  PRIMARY KEY (mail_from, date, hash))
  163.  TYPE=MyISAM COMMENT=''
  164. EOF
  165.   $sth = $dbh->prepare($query) or die $DBI::errstr;
  166.   $sth->execute() or die "Couldn't create table: $DBI::errstrn";
  167. }
  168. ####
  169. #### inbox processing
  170. ####
  171. sub process_mail_file
  172. {
  173.   my ($dbh, $file_name) = @_;
  174.   my (%values, $type, $check);
  175.   $file_name =~ s/.*[/]// if ($opt_no_path);
  176.   %values = ();
  177.   $type = "";
  178.   $check = 0;
  179.   while (<FILE>)
  180.   {
  181.     chop;
  182.     if ($type ne "message")
  183.     { 
  184.       if (/^Reply-To: (.*)/i)
  185.       {
  186. $type = "reply";
  187. $values{$type} = $1;
  188.       }
  189.       elsif (/^From: (.*)/i)
  190.       {
  191. $type = "from";
  192. $values{$type} = $1;
  193.       }
  194.       elsif (/^To: (.*)/i)
  195.       {
  196. $type = "to";
  197. $values{$type} = $1;
  198.       }
  199.       elsif (/^Cc: (.*)/i)
  200.       {
  201. $type = "cc";
  202. $values{$type} = $1;
  203.       }
  204.       elsif (/^Subject: (.*)/i)
  205.       {
  206. $type = "subject";
  207. $values{$type} = $1;
  208.       }
  209.       elsif (/^Date: (.*)/i)
  210.       {
  211. date_parser($1, %values, $file_name);
  212. $type = "rubbish";
  213.       }
  214.       elsif (/^[wW-]+:s/)
  215.       {
  216. $type = "rubbish";  
  217.       }
  218.       elsif ($_ eq "")
  219.       { 
  220. $type = "message";
  221. $values{$type} = "";
  222.       }
  223.       else
  224.       {
  225. s/^s*/ /;
  226. $values{$type} .= $_;
  227.       }
  228.     }
  229.     elsif ($check != 0 && $_ ne "") # in case of forwarded messages
  230.     {
  231.       $values{$type} .= "n" . $_;
  232.       $check--;
  233.     }
  234.     elsif (/^From .* dd:dd:ddsdddd$/)
  235.     {
  236.       $values{'hash'} = checksum("$values{'message'}");
  237.       update_table($dbh, $file_name, %values);
  238.       %values = ();
  239.       $type = "";
  240.       $check = 0;
  241.     }
  242.     elsif (/-* forwarded message .*-*/i) # in case of forwarded messages
  243.     {
  244.       $values{$type} .= "n" . $_;
  245.       $check++;
  246.       $mail_forwarded++;
  247.     }
  248.     else
  249.     {
  250.       $values{$type} .= "n" . $_;
  251.     }
  252.   }
  253.   $values{'hash'} = checksum("$values{'message'}");
  254.   update_table($dbh, $file_name, %values);
  255. }
  256. ####
  257. #### get date and timezone
  258. ####
  259. sub date_parser
  260. {
  261.   my ($date_raw, $values, $file_name, $tmp) = @_;
  262.   # If you ever need to change this test, be especially careful with
  263.   # the timezone; it may be just a number (-0600), or just a name (EET), or
  264.   # both (-0600 (EET), or -0600 (EET GMT)), or without parenthesis: GMT.
  265.   # You probably should use a 'greedy' regexp in the end
  266.   $date_raw =~ /^D*(d{1,2})s+(w+)s+(d{2,4})s+(d+:d+)(:d+)?s*(S+.*)?/;
  267.   if (!defined($1) || !defined($2) || !defined($3) || !defined($4) ||
  268.       !defined($months{$2}))
  269.   {
  270.     if ($opt_debug || $opt_stop_on_error)
  271.     {
  272.       print "FAILED: date_parser: 1: $1 2: $2 3: $3 4: $4 5: $5n";
  273.       print "months{2}: $months{$2}n";
  274.       print "date_raw: $date_rawn";
  275.       print "Inbox filename: $file_namen";
  276.     }
  277.     exit(1) if ($opt_stop_on_error);
  278.     $values->{'date'} = "";
  279.     $values->{'time_zone'} = "";
  280.     return;
  281.   }
  282.   $tmp = $3 . "-" . $months{$2} . "-" . "$1 $4";
  283.   $tmp.= defined($5) ? $5 : ":00";
  284.   $values->{'date'} = $tmp;
  285.   print "INSERTING DATE: $tmpn" if ($opt_debug);
  286.   $values->{'time_zone'} = $6;
  287. }
  288. ####
  289. #### Insert to table
  290. #### 
  291. sub update_table
  292. {
  293.   my($dbh, $file_name, $values) = @_;
  294.   my($q,$tail,$message);
  295.   if (!defined($values->{'subject'}) || !defined($values->{'to'}))
  296.   {
  297.     $mail_no_subject_f++;
  298.     return; # Ignore these
  299.   }
  300.   $message=$values->{'message'};
  301.   $message =~ s/^s*//; #removes whitespaces from the beginning 
  302.  restart:
  303.   $message =~ s/[sn>]*$//; #removes whitespaces and '>' from the end
  304.   $values->{'message'}=$message;
  305.   foreach $tail (@remove_tail)
  306.   {
  307.     $message =~ s/$tail//;
  308.   }
  309.   if ($message ne $values->{'message'})
  310.   {
  311.     $message =~ s/s*$//; #removes whitespaces from the end
  312.     $mail_fixed++;
  313.     goto restart;   # Some mails may have duplicated messages
  314.   }
  315.   $q = "INSERT INTO $opt_table (";
  316.   $q.= "mail_id,";
  317.   $q.= "date,";
  318.   $q.= "time_zone,";
  319.   $q.= "mail_from,";
  320.   $q.= "reply,";
  321.   $q.= "mail_to,";
  322.   $q.= "cc,";
  323.   $q.= "sbj,";
  324.   $q.= "txt,";
  325.   $q.= "file,";
  326.   $q.= "hash";
  327.   $q.= ") VALUES (";
  328.   $q.= "NULL,";
  329.   $q.= "'" . $values->{'date'} . "',";
  330.   $q.= (defined($values->{'time_zone'}) ?
  331. $dbh->quote($values->{'time_zone'}) : "NULL");
  332.   $q.= ",";
  333.   $q.= defined($values->{'from'}) ? $dbh->quote($values->{'from'}) : "NULL";
  334.   $q.= ",";
  335.   $q.= defined($values->{'reply'}) ? $dbh->quote($values->{'reply'}) : "NULL";
  336.   $q.= ",";
  337.   $q.= defined($values->{'to'}) ? $dbh->quote($values->{'to'}) : "NULL";
  338.   $q.= ",";
  339.   $q.= defined($values->{'cc'}) ? $dbh->quote($values->{'cc'}) : "NULL"; 
  340.   $q.= ","; 
  341.   $q.= $dbh->quote($values->{'subject'});
  342.   $q.= ",";
  343.   $q.= $dbh->quote($message);
  344.   $q.= ",";
  345.   $q.= $dbh->quote($file_name);
  346.   $q.= ",";
  347.   $q.= "'" . $values->{'hash'} . "'";
  348.   $q.= ")";
  349.   # Don't insert mails bigger than $opt_max_mail_size
  350.   if (length($message) > $opt_max_mail_size)
  351.   {
  352.     $mail_too_big++;
  353.   }
  354.   # Don't insert mails without 'From' field
  355.   elsif (!defined($values->{'from'}) || $values->{'from'} eq "")
  356.   {
  357.     $mail_no_from_f++;
  358.   }
  359.   elsif ($opt_test)
  360.   {
  361.     print "$qn";
  362.     $mail_inserted++;
  363.   }
  364.   # Don't insert mails without the 'message'
  365.   elsif ($message eq "") 
  366.   {
  367.     $mail_no_txt_f++;
  368.   }
  369.   elsif ($dbh->do($q))
  370.   {
  371.     $mail_inserted++;
  372.   }
  373.   # This should never happen. This means that the above q failed,
  374.   # but it wasn't because of a duplicate mail entry
  375.   elsif (!($DBI::errstr =~ /Duplicate entry /))
  376.   {
  377.     die "FATAL: Got error :$DBI::errstrnAttempted query was: $qn";
  378.   }
  379.   else
  380.   {
  381.     $mail_duplicates++;
  382.     print "Duplicate mail: query: $qn" if ($opt_debug);
  383.   }
  384.   $q = "";
  385. }
  386. ####
  387. #### In case you have two identical messages we wanted to identify them
  388. #### and remove additionals;  We do this by calculating a hash number of the
  389. #### message and ignoring messages with the same from, date and hash.
  390. #### This function calculates a simple 32 bit hash value for the message.
  391. ####
  392. sub checksum
  393. {
  394.   my ($txt)= @_;
  395.   my ($crc,$i,$count);
  396.   $count = length($txt);
  397.   for ($crc = $i = 0; $i < $count ; $i++)
  398.   {
  399.     $crc = (($crc << 1) + (ord (substr ($txt, $i, 1)))) +
  400.       (($crc & (1 << 30)) ? 1 : 0);
  401.     $crc &= ((1 << 31) -1);
  402.   }
  403.   return $crc;
  404. }
  405. ####
  406. #### my_which is used, because we can't assume that every system has the
  407. #### which -command. my_which can take only one argument at a time.
  408. #### Return values: requested system command with the first found path,
  409. #### or undefined, if not found.
  410. ####
  411. sub my_which
  412. {
  413.   my ($command) = @_;
  414.   my (@paths, $path);
  415.   return $command if (-f $command && -x $command);
  416.   @paths = split(':', $ENV{'PATH'});
  417.   foreach $path (@paths)
  418.   {
  419.     $path = "." if ($path eq "");
  420.     $path .= "/$command";
  421.     return $path if (-f $path && -x $path);
  422.   }
  423.   return undef();
  424. }
  425. ####
  426. #### usage and version
  427. ####
  428. sub usage
  429. {  
  430.   my ($VER)= @_;
  431.   
  432.   if ($opt_version)
  433.   {
  434.     print "$progname version $VERn";
  435.   } 
  436.   else
  437.   {
  438.     print <<EOF;
  439. $progname version $VER
  440. Description: Insert mails from inbox file(s) into a table.
  441. This program can read group [mail_to_db] from the my.cnf
  442. file. You may want to have db and table set there at least.
  443. Usage: $progname [options] file1 [file2 file3 ...] [>& /path/to/log.txt]
  444. or:    $progname [options] --create [file1 file2...] [>& /path/to/log.txt]
  445. Options:
  446. --help             Show this help and exit.
  447. --version          Show the version number and exit.
  448. --debug            Print some extra information during the run.
  449. --host=...         Hostname to be used. (Using: $opt_host)
  450. --port=#           TCP/IP port to be used with connection. (Using: $opt_port)
  451. --socket=...       MySQL UNIX socket to be used with connection.
  452.                    (Using: $opt_socket)
  453. --db=...           Database to be used.     (Using: $opt_db)
  454. --table=...        Table name for mails.    (Using: $opt_table)
  455. --user=...         Username for connecting. (Using: $opt_user)
  456. --password=...     Password for the user.
  457. --max_mail_size=#  Maximum size of a mail.
  458.                    Beware of the downside letting this variable be too big;
  459.                    you may easily end up inserting a lot of attached 
  460.                    binary files (like MS Word documents etc), which take
  461.                    space, make the database slower and are not really
  462.                    searchable anyway. (Default: $opt_max_mail_size)
  463. --create           Create the mails table. This can be done with the first run.
  464. --test    Dry run. Print the queries and the result as it would be.
  465. --no_path          When inserting the file name, leave out any paths of
  466.                    the name.
  467. --stop_on_error    Stop the run, if an unexpected, but not fatal error occurs
  468.                    during the run. Without this option some fields may get
  469.                    unwanted values. --debug will also report about these.
  470. EOF
  471.   }
  472.   exit(0);
  473. }