さくらのレンタルサーバーで、実行ファイル添付のメールを削除(その3 完結)

その1, その2 につづいて、実行ファイルチェックをチェックします

その2までで、あとはphpでメールを分解して終了コードに何を渡すかだけなので、もっとよいコードがあると思いますが、最終形 の exemail.php です

<?php
 // エラー表示
 // ini_set('display_errors', 1);

 // ブロックする拡張子
 $match_ext = array( 'com',
      'bat',
      'hta',
      'js',
      'docm',
      'scr',
      'dmg',
      'exe');

 // タイムゾーンとPEARのパス
 date_default_timezone_set('Asia/Tokyo');
 set_include_path("/PEARスクリプトの場所/PEAR");

 // PEAR
 require_once("Mail.php");
 require_once("Mail/mimeDecode.php");

 // メール取得
 $data = file_get_contents("php://stdin"); // 標準入力から
 // $data = file_get_contents("/スクリプトの場所/exemail.eml"); // ファイルから(Webでテストする場合)

 // メール読み込み
 $params['input'] = $data;
 $params['include_bodies'] = true;
 $params['decode_bodies']  = true;
 $params['decode_headers'] = true;
 $params['crlf'] = "\r\n"; 

 // 判定
 $maildata = Mail_mimeDecode::decode($params);
 if( matchExeAttach($maildata) ){
  // echo "1";
  exit(1);
 }else{
  // echo "0";
  exit(0);
 }

// -------------------------------

// メールデータのチェック
function matchExeAttach($maildata){

 if( strtolower($maildata->ctype_primary) === "multipart" && is_array($maildata->parts) ){
  foreach($maildata->parts as $part){
   if( matchExeAttach($part) ){
    return true;
   }
  }
 }else{
  if( is_array($maildata->ctype_parameters) && array_key_exists('name', $maildata->ctype_parameters) ){
   $filename = $maildata->ctype_parameters['name'];

   if( in_array(strtolower(pathinfo($filename, PATHINFO_EXTENSION)), $GLOBALS['match_ext']) ){
    return true;
   }elseif( strcasecmp(pathinfo($filename, PATHINFO_EXTENSION), 'zip') == 0 ){
    // zipファイルは、一時フォルダを作成してその中に保存
    $zipdir = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'tmp_' . strval(time()) . '_' . strval(rand(1000, 9999));
    mkdir($zipdir);

    $zipfilename = $zipdir . DIRECTORY_SEPARATOR . 'tmp_' . strval(time()) . '_' . strval(rand(1000, 9999)) . '.zip';
    if( file_put_contents($zipfilename, $maildata->body) !== false ){
     if( matchExeZip($zipfilename) ){
      remove_directory($zipdir);
      return true;
     }else{
      remove_directory($zipdir);
     }
    }
   }
  }
 }

 return false;
}


// zipファイルのチェック
function matchExeZip($zipfilename){
 try{
  $zip = new ZipArchive();
  if( $zip->open($zipfilename) ){
   for ($i=0; $i < $zip->numFiles; $i++) {
    $filepath = $zip->getNameIndex($i);
   
    if( strcasecmp(pathinfo($filepath, PATHINFO_EXTENSION), 'zip') == 0 ){
     // zipにzipが含まれる場合・展開して再帰
     $zip->extractTo(pathinfo($zipfilename, PATHINFO_DIRNAME), array($filepath));
      if( matchExeZip(pathinfo($zipfilename, PATHINFO_DIRNAME) . DIRECTORY_SEPARATOR . $filepath) ){
       return true;
      }
    }else{
     if( in_array(strtolower(pathinfo($filepath, PATHINFO_EXTENSION)), $GLOBALS['match_ext']) ){
      return true;
     }
    }
   }
  }
 }catch (Exception $e){
  return false;
 }
 return false;
}


// ディレクトリ(中身も)削除
function remove_directory($dir) {
 if ($handle = opendir("$dir")) {
  while (false !== ($item = readdir($handle))) {
   if ($item != "." && $item != "..") {
    if (is_dir("$dir/$item")) {
     remove_directory("$dir/$item");
    } else {
     unlink("$dir/$item");
    }
   }
  }
  closedir($handle);
  rmdir($dir);
 }
}

?>
Mail_mimeDecode へ入れたデータのマルチパートからファイル名を取得します。
zipは、一時ディレクトリを作成して、そこへ展開、中身のチェックを行います。
一時ディレクトリを利用するのは、zipが入れ子になっているパターンが考えられ、ZipArchive はパスごと展開されるため、ひとつのディレクトリに押し込みたいのが理由です。
remove_directory はどっかのサンプルそのまま

最後に、非常に大きなメールでサーバ負荷が上がらないように .mailfilter へサイズ指定もしておくとよいです
if ( $SIZE < 1024000 )
{
 `/usr/local/bin/php /スクリプトの場所/exemail.php`
 if ( $RETURNCODE == 1 )
 {
  to "maildir/.Trash/"
 }
}

【追記】
.mailfilter は、Webコントロールパネルからホワイトリストやブラックリストを追加すると上記スクリプト記述部分に余分なダブルコーテーションが入ったりしますので、追加後は必ず修正しましょう

コメント