procmailで特定アドレスからのメール本文を解析

Pocket

特定アドレスから送られてきたメール本文を解析したい時ってありますよね

私の場合、とあるメールアドレスから届くメールの本文に記述してあるリンクに自動でアクセスしたい時がありました

その場合の対応方法を記載しておきます

 

使ったプログラムについては、下記の通りです

メール取得については fetchmail
メールのアドレス解析と振り分けについては procmail
メール本文のパースに関しては php と simple_html_dom.php
メールの URL へのアクセスに関しては casperjs

なので、既にこれらのプログラムが動作する前提で話を進めます

 

fetchmail の設定

設定は ~/.fetchmailrc に記述します
記述例としては下記のようになります

set postmaster root # 最終的なメールの送信先を root にする
set no bouncemail # エラーメールをpostmasterに送る
set syslog # ログを/var/log/maillogに記録する

# 共通設定
defaults
protocol auto
no mimedecode
no fetchall # 全部取得せず、新規のみ処理する
keep # メールをサーバーに残しておく

poll pop3.example.com # プロバイダ受信メールサーバー名
protocol pop3
port 995 # 今回は 995 のポート
username "hoge@example.com" # ユーザ名
password "example_password" # パスワード
ssl # ssl での接続の場合
mda "/usr/bin/procmail" # メールをprocmailに処理させる

fetchmail はメールの取得の管理まで行っています

.fetchmailrc で指定する no fetchall で全部取得しないようになっていますが、
どこまで取得したかは~/.fetchids というファイルで管理してるようです

最終的に mda の部分で /usr/bin/procmail に指定した procmail 処理をさせます

procmail の設定

procmail は、レシピ(設定)通りに色々なことができます
例えば、メールを削除したり、他のディレクトリに保存したり、さらには別のプログラムに処理させたり

今回は、check@example.com から、メールが届いてきた場合に、本文に記載してある URL を叩くことを想定してます

PATH=/bin:/usr/bin:/usr/local/bin
MAILDIR=$HOME/Maildir_Other # procmail で処理されるメールの保存先
DEFAULT=$MAILDIR/.default/ # procmail で処理されたメールのうち、レシピで処理されないメールの保存先
LOGFILE=$MAILDIR/procmaillog # procmail のログ
LOCKFILE=$HOME/.lockmail

# check@example.com からのメールを処理する
# b は本文だけを処理に渡す
:0 b
* ^From:.*check@example.com
{
    # 渡されたメール本文を check.php にパイプで渡し、さらにその結果を check.js に渡す
    :0 c
    | php /usr/local/bin/check.php | casperjs /usr/local/bin/check.js >> /tmp/check.log 2>&1

    # 一応、確認用として保存しておく
    :0
    $MAILDIR/check
}

check@example.com から送信されたメールを解析し、HTML メールから URL だけを取り出す check.php に本文をパイプで渡します

php で HTML メールの解析

ここでは、check@example.com にサイトをチェックしたい URL が
HTML メールで送られてくるとします
念のため、テキストメールで一行で送られてくる場合も簡単に作ってあります

<?php

    include_once("/usr/local/bin/simple_html_dom.php");

    function convertEOL($string, $to = "\n") {</pre>
<pre>        return str_replace(array("\r\n", "\r", "\n"), $to, $string);</pre>
<pre>    }

    function parsePLAIN($body) {
        $list = explode("\n", $body);
        foreach( $list as $elem) {
            if ( strpos($elem, "http://example.com/") !== false ) {
                echo $elem;
                exit(0);
            }
        }
    }

    function parseHTML($body) {
        $dom = str_get_html($body);
        foreach( $dom->find('a') as $elem) {
            if ( strpos($elem->href, "http://example.com/") !== false) {
                echo $elem->href;
                exit(0);
            }
        }
    }

    $html = file_get_contents("php://stdin");
    $html = convertEOL($html);
    if ( strpos(strtolower($html), "<html>") !== false) {
        parseHTML($html);
    } else {
        parsePLAIN($html);
    }
?>

HTML のパース自体は simple_html_dom.php に任せています
ここでは、求めた URL だけを echo で返してます
なぜなら、次の casperjs にも パイプで渡しているためです

casperjs で URL にアクセス

最後に URL にアクセスします
ここでは casperjs を使ってます

casperjs のメリットとして、ajax バリバリのページとかでも解析できます
また、アクセスしたページを画像としても保存できます

var casper = require('casper').create();
var system = require('system');

casper.userAgent('Mozilla/5.0 (X11; Linux i686; rv:24.0) Gecko/20140611 Firefox/24.0 Iceweasel/24.6.0');

// printf の代用
function strFormat(num, repeat) {
    var fstring = new Array(repeat + 1).join("0");
     return fstring.substr(0, fstring.length-String(num).length) + num;
}

// YYYYMMDD_HHMMSS を作成する
function getTimeToDate() {
     dd = new Date();
     return dd.getFullYear() + strFormat(dd.getMonth() + 1, 2) + strFormat(dd.getDate(), 2) + '_' + strFormat(dd.getHours(), 2) + strFormat(dd.getMinutes(), 2) + strFormat(dd.getSeconds(), 2);
}

// stdin から URL を取得
var startUrl = system.stdin.readLine();

if ( startUrl) {
     casper.start(startUrl);
     casper.then(function() {
         // 画像として保存
         this.capture(getTimeToDate() + '.png');
         this.echo(this.getTitle());
     });
     casper.run();
}

最終的に yyyymmdd_hhmmss.png というファイル名でサイトの結果を保存してます

まとめ

fetchmail、procmail、casperjs を連携させれば、
サーバーからエラーメールが送られてきたウェブサイトの状態とか保存するとか応用がききます

まぁ、もっと簡単な方法があるかも知れませんが・・・

コメントを残す