mail_to_db.pl
上传用户:romrleung
上传日期:2022-05-23
资源大小:18897k
文件大小:15k
源码类别:

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