メール受信からのシェル機能実行

空メール送信での会員登録や、メールに添付されたファイルをサーバに自動でアップロードなど、メール受信からのスクリプト実行というのは様々なシーンで用途があると思います。

今回はそれをCakePHP 1.2系のシェル機能を使って実現してみました。(1.2.0.6311-betaで確認)

使用するもの

メールサーバはqmail、メールの解析処理はPEARのMail_mimeDecodeを使用します。

PEARを使用できるようにセットアップ

まずapp/vendorsの下にPEARフォルダを作り、PEARのコアクラス(PEAR.php)とMail_Mimeパッケージのクラスを置く。

次に、以下のソースをapp/vendors/pear_ini.phpとして保存。

<?php
define('PEAR_PATH', dirname(__FILE__) . DS . 'PEAR');
set_include_path(PEAR_PATH . PATH_SEPARATOR . get_include_path()); 
?>


セットアップ後の配置は上のスクリーンショットの様になっていればOKです。

シェル機能の実装

Mail_mimeDecodeを使ったスクリプトは、2008-01-23に掲載されているスクリプトをベースにさせて頂きました。

以下のソースをapp/vendors/shells/receiver.phpとして保存。

<?php
vendor('pear_ini');
require_once('Mail' . DS . 'mimeDecode.php');
class ReceiverShell extends Shell {
	
	function receiveMail() {
		$mail = "";
		$image_filename = "";
		$from = array();
		$headers = array();

		// 標準入力から受け取ったメールを取得
		$stdin=$this->Dispatch->stdin;
		while( !feof($stdin) ){
			$mail .= fgets($stdin,4096);
		}
	    
		// デコード方法の指定
		$params['include_bodies'] = true;
		$params['decode_bodies']  = true;
		$params['decode_headers'] = true;
		// デコード処理
		$decoder = new Mail_mimeDecode($mail);
		$decoded = $decoder->decode($params);

		// 添付ファイルの有無で処理を分岐
		if ( !empty($decoded->parts) ) {
			// bodyを取得
			$body = $decoded->parts[0]->body;
			// 添付ファイルの数だけループ
			for($idx = 1; $idx < count($decoded->parts); $idx++) {
				$file = $decoded->parts[$idx]->body;
				$filetype = $decoded->parts[$idx]->ctype_secondary;
				/**********************************************************
				 * ファイルタイプのチェックしたり、指定したディレクトリに *
				 * 添付ファイルを保存したりの処理をここでする             *
				 **********************************************************/
			}
		} else {
			// 添付ファイルが無い場合はbodyを取得するだけ
			$body = $decoded->body;
		}
		// ヘッダ部分を取得して漢字コードを変換する
		$headers = $decoded->headers;
		$from_text = mb_convert_encoding($headers['from'], mb_internal_encoding(),'ISO-2022-JP');
		ereg("[0-9a-zA-Z_\.\-]+@[0-9a-zA-Z_\.\-]+",$from_text,$from);
		$from = $from[0];
		$subject = mb_convert_encoding($headers['subject'], mb_internal_encoding(),'ISO-2022-JP');
		$body = mb_convert_encoding($body, mb_internal_encoding(),'ISO-2022-JP');
		/**********************************************************
		 * DBに格納したり、メールを送り返したりの処理をここでする *
		 **********************************************************/
	}
	
}
?>

シェルスクリプトファイル、.qmailファイルの作成

上記で実装したCakePHPのメール受信スクリプトを呼ぶシェルスクリプト(receiver.sh)は以下のように書きました。

#!/bin/sh
cd /appフォルダまでのパス/app
../cake/console/cake receiver receiveMail

そして、メール受信時にそのシェルスクリプトを呼ぶように.qmailファイルは

|/シェルスクリプトまでのパス/receiver.sh

として、しかるべき場所に配置します。

以上で、

メール受信を契機に上記のスクリプトが実行されるコトが確認できました。

あとは用途によって、添付ファイルの保存やDBへのデータ格納など実装すれば良いかと。

ハマッた点

標準入力のオープン・クローズを以下のように自前でやると、標準入力からメールがうまく読み込めてなくてしばらくハマってしまいました。

$stdin=fopen("php://stdin",'r');
while( !feof($stdin) ){
    $mailtext .= fgets($stdin,4096);
}
fclose($stdin);

ソースを追っていたら、ShellDispatcherクラスの__initEnvironmentメソッドで標準入力をオープンして$stdinにセット⇒ShellDispatcherのインスタンスがShellクラスの$Dispatchにセットされている、という事が分かったので標準入力は自前で開かずに$this->Dispatch->stdinを使ってみると、うまく読み込むことが出来ました。