Source for file filters.php

Documentation is available at filters.php

  1. <?php
  2.  
  3. /**
  4.  * Message and Spam Filter Plugin - Filtering Functions
  5.  *
  6.  * @copyright &copy; 1999-2006 The SquirrelMail Project Team
  7.  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  8.  * @version $Id: filters.php,v 1.92 2006/08/05 14:49:48 kink Exp $
  9.  * @package plugins
  10.  * @subpackage filters
  11.  */
  12.  
  13. /**
  14.  * do not allow to call this file directly
  15.  */
  16. if ((isset($_SERVER['SCRIPT_FILENAME']&& $_SERVER['SCRIPT_FILENAME'== __FILE__||
  17.      (isset($HTTP_SERVER_SERVER['SCRIPT_FILENAME']&& $HTTP_SERVER_SERVER['SCRIPT_FILENAME'== __FILE__) ) {
  18.     header("Location: ../../src/login.php");
  19.     die();
  20. }
  21.  
  22. /** load globals */
  23. global $UseSeparateImapConnection,
  24.     $AllowSpamFilters$SpamFilters_YourHop$SpamFilters_ShowCommercial,
  25.     $SpamFilters_DNScache$SpamFilters_BulkQuery$SpamFilters_SharedCache,
  26.     $SpamFilters_CacheTTL;
  27.  
  28. /**
  29.  * load required functions. Plugin depends on IMAP functions and they are not
  30.  * loaded in src/webmail.php
  31.  */
  32. include_once (SM_PATH 'functions/imap.php');
  33.  
  34. /** load default config */
  35. if (file_exists(SM_PATH 'plugins/filters/config_default.php')) {
  36.     include_once (SM_PATH 'plugins/filters/config_default.php');
  37. else {
  38.     // default config was removed.
  39.         $UseSeparateImapConnection false;
  40.     $AllowSpamFilters true;
  41.     $SpamFilters_YourHop ' ';
  42.     $SpamFilters_ShowCommercial false;
  43.     $SpamFilters_DNScache array();
  44.     $SpamFilters_BulkQuery '';
  45.     $SpamFilters_SharedCache true;
  46.     $SpamFilters_CacheTTL 7200;
  47. }
  48.  
  49. if (file_exists(SM_PATH 'config/filters_config.php')) {
  50.     include_once (SM_PATH 'config/filters_config.php');
  51. elseif (file_exists(SM_PATH 'plugins/filters/config.php')) {
  52.     include_once (SM_PATH 'plugins/filters/config.php');
  53. }
  54.  
  55. /**
  56.  * Register option blocks
  57.  * @access private
  58.  */
  59. function filters_optpage_register_block({
  60.     global $optpage_blocks$AllowSpamFilters;
  61.  
  62.     $optpage_blocks[array(
  63.         'name' => _("Message Filters"),
  64.         'url'  => SM_PATH 'plugins/filters/options.php',
  65.         'desc' => _("Filtering enables messages with different criteria to be automatically filtered into different folders for easier organization."),
  66.         'js'   => false
  67.     );
  68.  
  69.     if ($AllowSpamFilters{
  70.         $optpage_blocks[array(
  71.             'name' => _("SPAM Filters"),
  72.             'url'  => SM_PATH 'plugins/filters/spamoptions.php',
  73.             'desc' => _("SPAM filters allow you to select from various DNS based blacklists to detect junk email in your INBOX and move it to another folder (like Trash)."),
  74.             'js'   => false
  75.         );
  76.     }
  77. }
  78.  
  79. /* Receive the status of the folder and do something with it */
  80. function filters_folder_status($statusarr{
  81.  
  82.     global $filter_inbox_count;
  83.     if (empty($filter_inbox_count)) $filter_inbox_count=0;
  84.  
  85.     if ($statusarr['MAILBOX'== 'INBOX')
  86.     {
  87.      if (!empty($statusarr['MESSAGES'])) $filter_inbox_count=$statusarr['MESSAGES'];
  88.     }
  89. }
  90.  
  91. /**
  92.  * Saves the DNS Cache to disk
  93.  * @access private
  94.  */
  95. function filters_SaveCache ({
  96.     global $data_dir$SpamFilters_DNScache;
  97.  
  98.     if (file_exists($data_dir '/dnscache')) {
  99.         $fp fopen($data_dir '/dnscache''r');
  100.     else {
  101.         $fp false;
  102.     }
  103.     if ($fp{
  104.         flock($fp,LOCK_EX);
  105.     else {
  106.        $fp fopen($data_dir '/dnscache''w+');
  107.        fclose($fp);
  108.        $fp fopen($data_dir '/dnscache''r');
  109.        flock($fp,LOCK_EX);
  110.     }
  111.     $fp1 fopen($data_dir '/dnscache''w+');
  112.  
  113.     foreach ($SpamFilters_DNScache as $Key=> $Value{
  114.        $tstr $Key ',' $Value['L'',' $Value['T'"\n";
  115.        fputs ($fp1$tstr);
  116.     }
  117.     fclose($fp1);
  118.     flock($fp,LOCK_UN);
  119.     fclose($fp);
  120. }
  121.  
  122. /**
  123.  * Loads the DNS Cache from disk
  124.  * @access private
  125.  */
  126. function filters_LoadCache ({
  127.     global $data_dir$SpamFilters_DNScache;
  128.  
  129.     if (file_exists($data_dir '/dnscache')) {
  130.         $SpamFilters_DNScache array();
  131.         if ($fp fopen ($data_dir '/dnscache''r')) {
  132.             flock($fp,LOCK_SH);
  133.             while ($data fgetcsv($fp,1024)) {
  134.                if ($data[2time()) {
  135.                   $SpamFilters_DNScache[$data[0]]['L'$data[1];
  136.                   $SpamFilters_DNScache[$data[0]]['T'$data[2];
  137.                }
  138.             }
  139.             flock($fp,LOCK_UN);
  140.         }
  141.     }
  142. }
  143.  
  144. /**
  145.  * Uses the BulkQuery executable to query all the RBLs at once
  146.  * @param array $filters Array of SPAM Fitlers
  147.  * @param array $IPs Array of IP Addresses
  148.  * @access private
  149.  */
  150. function filters_bulkquery($filters$IPs{
  151.     global $attachment_dir$username,
  152.            $SpamFilters_CacheTTL;
  153.  
  154.     if (count($IPs0{
  155.         $rbls array();
  156.         foreach ($filters as $key => $value{
  157.             if ($filters[$key]['enabled']{
  158.                 if ($filters[$key]['dns']{
  159.                     $rbls[$filters[$key]['dns']] true;
  160.                 }
  161.             }
  162.         }
  163.  
  164.         $bqfil $attachment_dir $username '-bq.in';
  165.         $fp fopen($bqfil'w');
  166.         fputs ($fp$SpamFilters_CacheTTL "\n");
  167.         foreach ($rbls as $key => $value{
  168.             fputs ($fp'.' $key "\n");
  169.         }
  170.         fputs ($fp"----------\n");
  171.         foreach ($IPs as $key => $value{
  172.             fputs ($fp$key "\n");
  173.         }
  174.         fclose ($fp);
  175.         $bqout array();
  176.         exec ($SpamFilters_BulkQuery ' < ' $bqfil$bqout);
  177.         foreach ($bqout as $value{
  178.             $Chunks explode(','$value);
  179.             $SpamFilters_DNScache[$Chunks[0]]['L'$Chunks[1];
  180.             $SpamFilters_DNScache[$Chunks[0]]['T'$Chunks[2time();
  181.         }
  182.         unlink($bqfil);
  183.     }
  184. }
  185.  
  186. /**
  187.  * Starts the filtering process
  188.  * @param array $hook_args (since 1.5.2) do hook arguments. Is used to check
  189.  *  hook name, array key = 0.
  190.  * @access private
  191.  */
  192. function start_filters($hook_args{
  193.     global $imapServerAddress$imapPort$imap_stream$imapConnection,
  194.            $UseSeparateImapConnection$AllowSpamFilters$filter_inbox_count,
  195.            $username;
  196.  
  197.     /**
  198.      * check hook that calls filtering. If filters are called by right_main_after_header,
  199.      * do filtering only when we are in INBOX folder.
  200.      */
  201.     if ($hook_args[0]=='right_main_after_header' &&
  202.         (sqgetGlobalVar('mailbox',$mailbox,SQ_FORM&& $mailbox!='INBOX')) {
  203.         return;
  204.     }
  205.  
  206.     $filters load_filters();
  207.  
  208.     // No point running spam filters if there aren't any to run //
  209.     if ($AllowSpamFilters{
  210.         $spamfilters load_spam_filters();
  211.  
  212.         $AllowSpamFilters false;
  213.         foreach($spamfilters as $filterskey=>$value{
  214.             if ($value['enabled'== 'yes'{
  215.                 $AllowSpamFilters true;
  216.                 break;
  217.             }
  218.         }
  219.     }
  220.  
  221.     if (!$AllowSpamFilters && empty($filters)) {
  222.         return;
  223.     }
  224.  
  225.  
  226.     // Detect if we have already connected to IMAP or not.
  227.     // Also check if we are forced to use a separate IMAP connection
  228.     if ((!isset($imap_stream&& !isset($imapConnection)) ||
  229.         $UseSeparateImapConnection {
  230.             $stream sqimap_login($usernamefalse$imapServerAddress,
  231.                                 $imapPort10);
  232.             $previously_connected false;
  233.     else if (isset($imapConnection)) {
  234.         $stream $imapConnection;
  235.         $previously_connected true;
  236.     else {
  237.         $previously_connected true;
  238.         $stream $imap_stream;
  239.     }
  240.  
  241.     if (!isset($filter_inbox_count)) {
  242.         $aStatus sqimap_status_messages ($stream'INBOX'array('MESSAGES'));
  243.         if (!empty($aStatus['MESSAGES'])) {
  244.             $filter_inbox_count=$aStatus['MESSAGES'];
  245.         else {
  246.             $filter_inbox_count=0;
  247.         }
  248.     }
  249.  
  250.     if ($filter_inbox_count 0{
  251.         sqimap_mailbox_select($stream'INBOX');
  252.         // Filter spam from inbox before we sort them into folders
  253.         if ($AllowSpamFilters{
  254.             spam_filters($stream);
  255.         }
  256.  
  257.         // Sort into folders
  258.         user_filters($stream);
  259.     }
  260.  
  261.     if (!$previously_connected{
  262.         sqimap_logout($stream);
  263.     }
  264. }
  265.  
  266. /**
  267.  * Does the loop through each filter
  268.  * @param stream imap_stream the stream to read from
  269.  * @access private
  270.  */
  271. function user_filters($imap_stream{
  272.     global $data_dir$username;
  273.     $filters load_filters();
  274.     if ($filtersreturn;
  275.     $filters_user_scan getPref($data_dir$username'filters_user_scan');
  276.  
  277.     $expunge false;
  278.     // For every rule
  279.     for ($i=0$num count($filters)$i $num$i++{
  280.         // If it is the "combo" rule
  281.         if ($filters[$i]['where'== 'To or Cc'{
  282.             /*
  283.             *  If it's "TO OR CC", we have to do two searches, one for TO
  284.             *  and the other for CC.
  285.             */
  286.             $expunge filter_search_and_delete($imap_stream'TO',
  287.                   $filters[$i]['what']$filters[$i]['folder']$filters_user_scan$expunge);
  288.             $expunge filter_search_and_delete($imap_stream'CC',
  289.                   $filters[$i]['what']$filters[$i]['folder']$filters_user_scan$expunge);
  290.         else if ($filters[$i]['where'== 'Header and Body'{
  291.             $expunge filter_search_and_delete($imap_stream'TEXT',
  292.                   $filters[$i]['what']$filters[$i]['folder']$filters_user_scan$expunge);
  293.         else if ($filters[$i]['where'== 'Message Body'{
  294.             $expunge filter_search_and_delete($imap_stream'BODY',
  295.                   $filters[$i]['what']$filters[$i]['folder']$filters_user_scan$expunge);
  296.         else {
  297.             /*
  298.             *  If it's a normal TO, CC, SUBJECT, or FROM, then handle it
  299.             *  normally.
  300.             */
  301.             $expunge filter_search_and_delete($imap_stream$filters[$i]['where'],
  302.                  $filters[$i]['what']$filters[$i]['folder']$filters_user_scan$expunge);
  303.         }
  304.     }
  305.     // Clean out the mailbox whether or not auto_expunge is on
  306.     // That way it looks like it was redirected properly
  307.     if ($expunge{
  308.         sqimap_mailbox_expunge($imap_stream'INBOX');
  309.     }
  310. }
  311.  
  312. /**
  313.  * Creates and runs the IMAP command to filter messages
  314.  * @param string $where Which part of the message to search (TO, CC, SUBJECT, etc...)
  315.  * @param string $what String to search for
  316.  * @param string $where_to Folder it will move to
  317.  * @param string $user_scan Whether to search all or just unseen
  318.  * @param string $should_expunge 
  319.  * @param boolean $where Which part of location to search
  320.  * @access private
  321.  */
  322. function filter_search_and_delete($imap_stream$where$what$where_to$user_scan,
  323.                                   $should_expunge{
  324.     global $languages$squirrelmail_language$allow_charset_search$imap_server_type;
  325.  
  326.     //TODO: make use of new mailbox cache. See mailbox_display.phpinfo
  327.  
  328.     if (strtolower($where_to== 'inbox'{
  329.         return array();
  330.     }
  331.  
  332.     if ($user_scan == 'new'{
  333.         $category 'UNSEEN';
  334.     else {
  335.         $category 'ALL';
  336.     }
  337.     $category .= ' UNDELETED';
  338.  
  339.     if ($allow_charset_search &&
  340.         isset($languages[$squirrelmail_language]['CHARSET']&&
  341.         $languages[$squirrelmail_language]['CHARSET']{
  342.         $search_str 'SEARCH CHARSET '
  343.                     . strtoupper($languages[$squirrelmail_language]['CHARSET'])
  344.                     . ' ' $category;
  345.     else {
  346.         $search_str 'SEARCH CHARSET US-ASCII ' $category;
  347.     }
  348.     if ($where == 'Header'{
  349.         $what  explode(':'$what);
  350.         $where strtoupper($where);
  351.         $where trim($where ' ' $what[0]);
  352.         $what  addslashes(trim($what[1]));
  353.     }
  354.  
  355.     // see comments in squirrelmail sqimap_search function
  356.     if ($imap_server_type == 'macosx' || $imap_server_type == 'hmailserver'{
  357.          $search_str .= ' ' $where ' ' $what;
  358.         /* read data back from IMAP */
  359.         $read sqimap_run_command($imap_stream$search_strtrue$response$messageTRUE);
  360.     else {
  361.         $search_str .= ' ' $where ' {' strlen($what"}";
  362.         $sid sqimap_session_id(true);
  363.         fputs ($imap_stream$sid ' ' $search_str "\r\n");
  364.         $read2 sqimap_fgets($imap_stream);
  365.         # server should respond with Ready for argument, then we will send search text
  366.         #echo "RR2 $read2<br>";
  367.         fputs ($imap_stream"$what\r\n");
  368.         #echo "SS $what<br>";
  369.         $read2 sqimap_fgets($imap_stream);
  370.         #echo "RR2 $read2<br>";
  371.         $read[]=$read2;
  372.         $read3 sqimap_fgets($imap_stream);
  373.         #echo "RR3 $read3<br>";
  374.         list($rtag,$response,$message)=explode(' ',$read3,3);
  375. ##        $read2 = sqimap_retrieve_imap_response($imap_stream, $sid, true,
  376. ##              $response, $message, $search_str, false, true, false);
  377.         #echo "RR2 $read2 / RESPONSE $response<br>";
  378.     }
  379.  
  380.     if (isset($read[0])) {
  381.         $ids array();
  382.         for ($i 0$iCnt count($read)$i $iCnt++$i{
  383.             if (preg_match("/^\* SEARCH (.+)$/"$read[$i]$regs)) {
  384.                 $ids += preg_split("/ /"trim($regs[1]));
  385.             }
  386.         }
  387.         if ($response == 'OK' && count($ids)) {
  388.             if (sqimap_mailbox_exists($imap_stream$where_to)) {
  389.                  $should_expunge true;
  390.                  sqimap_msgs_list_move ($imap_stream$ids$where_tofalse);
  391.             }
  392.         elseif ($response != 'OK'{
  393.             $query $search_str "\r\n".$what ."\r\n";
  394.             if ($response == 'NO'{
  395.                 if (strpos($message,'BADCHARSET'!== false ||
  396.                     strpos($message,'character'!== false{
  397.                     sqm_trigger_imap_error('SQM_IMAP_BADCHARSET',$query$response$message);
  398.                 else {
  399.                     sqm_trigger_imap_error('SQM_IMAP_ERROR',$query$response$message);
  400.                 }
  401.             else {
  402.                 sqm_trigger_imap_error('SQM_IMAP_ERROR',$query$response$message);
  403.             }
  404.         }
  405.     }
  406.     return $should_expunge;
  407. }
  408.  
  409. /**
  410.  * Loops through all the Received Headers to find IP Addresses
  411.  * @param stream imap_stream the stream to read from
  412.  * @access private
  413.  */
  414. function spam_filters($imap_stream{
  415.     global $data_dir$username;
  416.     global $SpamFilters_YourHop;
  417.     global $SpamFilters_DNScache;
  418.     global $SpamFilters_SharedCache;
  419.     global $SpamFilters_BulkQuery;
  420.     global $SpamFilters_CacheTTL;
  421.  
  422.     $filters_spam_scan getPref($data_dir$username'filters_spam_scan');
  423.     $filters_spam_folder getPref($data_dir$username'filters_spam_folder');
  424.     $filters load_spam_filters();
  425.  
  426.     if ($SpamFilters_SharedCache{
  427.        filters_LoadCache();
  428.     }
  429.  
  430.     $run false;
  431.  
  432.     foreach ($filters as $Key => $Value{
  433.         if ($Value['enabled']{
  434.             $run true;
  435.             break;
  436.         }
  437.     }
  438.  
  439.     // short-circuit
  440.     if (!$run{
  441.         return;
  442.     }
  443.  
  444.     // Ask for a big list of all "Received" headers in the inbox with
  445.     // flags for each message.  Kinda big.
  446.  
  447.     if ($filters_spam_scan == 'new'{
  448.         $search_array array();
  449.         $read sqimap_run_command($imap_stream'SEARCH UNSEEN'true$response$messageTRUE);
  450.         if (isset($read[0])) {
  451.             for ($i 0$iCnt count($read)$i $iCnt++$i{
  452.                 if (preg_match("/^\* SEARCH (.+)$/"$read[$i]$regs)) {
  453.                     $search_array preg_split("/ /"trim($regs[1]));
  454.                 break;
  455.                 }
  456.             }
  457.         }
  458.     }
  459.     if ($filters_spam_scan == 'new' && count($search_array)) {
  460.         $headers sqimap_get_small_header_list ($imap_stream$search_arrayarray('Received'),array());
  461.     else if ($filters_spam_scan != 'new'{
  462.         $headers sqimap_get_small_header_list ($imap_streamnull array('Received'),array());
  463.     else {
  464.         return;
  465.     }
  466.     if (!count($headers)) {
  467.         return;
  468.     }
  469.     $bulkquery (strlen($SpamFilters_BulkQuerytrue false);
  470.     $IPs array();
  471.     $aSpamIds array();
  472.     foreach ($headers as $id => $aValue{
  473.         if (isset($aValue['UID'])) {
  474.             $MsgNum $aValue['UID'];
  475.         else {
  476.             $MsgNum $id;
  477.         }
  478.         // Look through all of the Received headers for IP addresses
  479.         if (isset($aValue['RECEIVED'])) {
  480.             foreach ($aValue['RECEIVED'as $received{
  481.                 // Check to see if this line is the right "Received from" line