We are ACORN!

プログラムやサーバーの設定など、技術的な記事を載せております。

CentOS PHP qmail Server プログラミング

【qmail】携帯キャリアのメールに添付を削除して転送する

投稿日:2019-01-14 更新日:

qmailでメールを携帯に転送して、メール通知に使っておられる方は結構多いんじゃないでしょうか。
ただ、大きなファイルが添付されていると、エラーメールが送信元に戻ってしまいます。
つまり、クライアントが大きなファイルを添付してきた場合、エラーメールがクライアントに戻ってしまうことになります。

やっぱりそれだとマズイです。添付ファイルが勝手に他者に転送されている訳ですからね。
それで、転送する際に添付ファイルを落としてから、転送したほうが無難です。

転送するためのスクリプトをPHPで作ってやることにしました。


インストールされているパッケージは以下の通り。

php
php-cli
php-common
php-gd
php-intl
php-mbstring
php-pear
php-pear-Mail-Mime
php-pear-Mail-mimeDecode
php-process
php-xml

php-cli、php-mbstring、php-pear、php-pear-Mail-Mime、php-pear-Mail-mimeDecode
以上が必要パッケージですが、依存性でその他もインストールされるはずです。

PHPは最低でも以下のような設定が必要です。

[Date]
date.timezone = Asia/Tokyo
[mbstring]
mbstring.language = Japanese
mbstring.internal_encoding = UTF-8
mbstring.http_input = pass
mbstring.http_output = pass
mbstring.encoding_translation = Off
mbstring.detect_order = auto
mbstring.substitute_character = none
mbstring.func_overload = 0
mbstring.strict_detection = Off

vpopmailの実行ファイルがあるところにスクリプトを置きます。

#!/usr/bin/php
<?php
/**
 * あらかじめ添付ファイルを叩き落として、メッセージだけを転送するスクリプト
 * 
 * @author Digital Acorn
 * @created 2015-06-15
 */

mb_internal_encoding('UTF-8');

$res = fopen('php://stdin','r');
if (!$res) {
  exit(0);
}

$output = '';
$boundary = '';
$attached_deleted = false;

// ヘッダー部分でboundaryを探す
while($line = fgets($res)) {
  // content-type句を得る。もしcontent-typeが無いような失礼なメールの場合、
  // 全てがoutputに回るはず。そしてboundaryがないので、全部出力される。
  if (preg_match('/content-type:\s+multipart\/[a-z]+;/i',$line)) {
    // mixedでない場合は全て出力
    if (!preg_match('/multipart\/mixed/i', $line)) {
      $output .= $line;
      while($line = fgets($res)) echo $output .= $line;
      break;
    }
    else {
      if (!preg_match('/boundary=/i',$line)) {
        $line .= fgets($res);
      }
      $boundary = array();
      if (!preg_match('/boundary="*([^"\s]+)"*$/i',$line,$boundary)) {
        exit(0);
      }
      else {
        $boundary = $boundary[1];
      }
      $output .= $line;
      break;
    }
  }
  $output .= $line;
}

if (!empty($boundary)) {
  // 最初のboundaryまで送る
  while($line = fgets($res)) {
    // boundaryを発見
    if (strpos($line, $boundary)!==false) {
      break;
    }
    $output .= $line;
  }

  while(true) {
    $saved = array($line);
    $attached = false;
    while($line = fgets($res)) {
      // boundaryが登場するまで回す
      if (strpos($line, $boundary)!==false) {
        break;
      }
      // content-type句が出てきた
      if (preg_match('/^content-type/i',$line)) {
        if (preg_match('/text\/plain|text\/html|multipart\/alternative/i',$line)) {
          // テキストメッセージ
          $attached = false;
        }
        else {
          // 添付ファイル
          $attached = true;
          $attached_deleted = true;
        }
      }
      if (!$attached) {
        // テキストメッセージなら保存
        $saved[] = $line;
      }
    }
    if (!$attached) {
      // テキストメッセージなら出力する
      foreach ($saved as $value) {
        $output .= $value;
      }
    }
    // 終端のboundaryなら最後まで出力して終了
    if (strpos($line, $boundary."--")!==false) {
      $output .= $line;
      while($line = fgets($res)) $output .= $line;
      break;
    }
    // もし最後のboundaryが無いような尻切れのメール場合、出力を終了する。
    if ($line===false) break;
  }  
}

require_once('/usr/share/pear/Mail/mimeDecode.php');

// qmailが環境変数に値をセットしてくる

// .qmailのあるディレクトリにenv.phpを置いて、そこに転送先を書いてある。
// $mail = "test@example.jp" みたいに。

// 現在のディレクトリ
$dir = getenv('PWD');
$mail = '';
@include($dir.'/env.php');
if (empty($mail)) {
  exit(0);
}
// 転送前の送り主
$sender = getenv('SENDER');
// メールを受け取ったドメイン名(バーチャルドメインかも)
$host   = getenv('HOST');

// デコード
$params['include_bodies'] = true;
$params['decode_bodies']  = true;
$params['decode_headers'] = true;
$decoder = new Mail_mimeDecode($output);
$structure = $decoder->decode($params);

$from    = mb_convert_encoding($structure->headers['from'],    'UTF-8', 'auto');
$subject = mb_convert_encoding($structure->headers['subject'], 'UTF-8', 'auto');
$date    = $structure->headers['date'];

// スパム判定なら転送しない
if (preg_match('/^\s*\[SPAM\]/', $subject)) {
  exit;
}

// ボディが奥の方にある場合があるので、ストラクチャを下がっていく
// この方法でいいのか、疑問。text部分が必ず最初に来るのか?
if (!($body = @$structure->body)) {
  if (!($body = @$structure->parts[0]->body)) {
    if (!($body = @$structure->parts[0]->parts[0]->body)) {
      $body = 'HTMLだけのメールです';
    }
  }
}
$body = mb_convert_encoding($body, 'UTF-8', 'auto');

$new_body = <<<END_OF_BODY
{$host}メールサーバーです。
サーバーに到着したメールを転送します。
このメールに直接返信しないでください。


END_OF_BODY;
$new_body .= ($attached_deleted)?"※ 添付ファイルが削除されました。\n":"";
$new_body .= <<<END_OF_BODY
----- Original Message -----
From: $from
Date: $date
Subject: $subject

$body
END_OF_BODY;

$header = 'From: <postmaster@'.$host.">";

mb_send_mail($mail, "Fw: ".$subject, $new_body, $header);
exit(0);

オーナーを変更し、実行権限を付けます。

chown vpopmail.vchkpw /home/vpopmain/bin/mailTransfer
chmod 711 /home/vpopmain/bin/mailTransfer

各ユーザーの環境設定ファイルを作ります。
下記例だと、example.jpドメインのfooさんの環境設定です。

<?php
$mail = 'example@docomo.ne.jp';

その後、.qmailを編集します。

|spamc |mailTransfer
|spamc |tomaildir ./Maildir

こうしておくと、spamassassinの結果で転送をコントロールできます。
スパムだった場合は、転送されずメールボックスには、件名に [SPAM] という接頭子が付いたメールが入ります。
(下の記事の、おまけ部分をインストールしてあることが前提です)
qmail(with smtp auth) + vpopmail + ezmlm-idx + dovecot + qmailadminなサーバ構築

以上です。

-CentOS, PHP, qmail, Server, プログラミング


comment

メールアドレスが公開されることはありません。

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)

関連記事

MySQL VIEWの一覧

MySQLでVIEWの一覧が欲しいなぁ・・・と思ったら、以下のSQLで可能。 SELECT TABLE_SCHEMA,TABLE_NAME FROM information_schema.views …

Google Bookmarksを利用するChrome拡張機能

暇なときにGoogle Bookmarksを利用するプラグインを作ってみました。 最低限のことしか出来ないのですが、自分で使う分にはこれで必要十分です。 Gブックマーク もしよろしかったら使ってみてく …

【PHP】WSL vs. Docker vs. Win32 環境別速度

PHPが複数環境で実行できるようになったので、速度を測定してみました。 マシンスペックは マシン Lenovo Thinkpad E460 プロセッサ Core i5-6200U @ 2.40GHz …

【qmail】 SPFの不正を防御する

スパムでSPFがpassになるのが多いなあ?、と思ってググりました。 ありました(泣) SPF (Sender Policy Framework) チェックをパスしてしまう迷惑 (スパム) メールが増 …

WordPressのメインループ&サブループ

WordPressのテンプレートを作っていると、ループの構造で混乱してくる人がいます。 ですので、不要なHTMLを除いた、単純な構造を書いておこうと思います。 解説はコメントに書いています。 < …