アラートメールをフィルタリングして転送する

Unix/Linux 系 OS ではシステムのセルフチェックを行い、結果を管理者にメールで通知するといった仕組みが動いています。また、システム管理ツールがサーバ監視を行い、異常時にはアラートメールを送信するように設定しているケースも多くあります。しかし、管理しているサーバー数が多いとメールの数も膨大となり、すべてに目を通すことは現実的ではありません。そのため、受信したメールのうち警告の通知だけを読みたい、もしくは障害通知だけ受け取れればよいといった要望があります。

今回は、受信したメールのうち差出人やタイトルが一定のパターンに一致するものをフィルタリングし、転送するジョブフローを作成します。


動作確認環境

ソフトウェア バージョン
Kompira Enterprise 1.4.10.post10
OS CentOS 6.10

または

ソフトウェア バージョン
Kompira Enterprise 1.5.5.post7
OS CentOS 7.8.2003

または

ソフトウェア バージョン
Kompira Enterprise 1.6.2.post4
OS CentOS 8.2.2004

または

ソフトウェア バージョン
Kompira Enterprise 1.6.8
OS CentOS Stream 8

メール転送の設定

ここでは、メールサーバーより IMAP4/POP3 プロトコルを通じて、メールを取り込む「メールチャネル」を利用します。取得の際、メールはサーバーから削除されます。また Kompira Enterprise のメールチャネルがチェックできるアカウントは1つであるため、メールサーバーに Kompira Enterprise 用のメールアカウントを作成し、そのアカウントにメールが転送されるように設定を行ってください。

メールチャネルの設定

まず、メール受信を行うためのメールチャネルを作成します。
先程設定した転送先のメールアドレス宛のメールを受信する、”メール受信チャネル” という名前のメールチャネルを作成します。メールチャネルの詳細については、「メール受信をトリガーにしてジョブフローを実行する」にて紹介しております。

メールを処理するジョブフローの作成

ここでは
(1) メールアドレスを抽出するジョブフロー「アドレス抽出」
(2) メールを転送するジョブフロー「メール転送」
(3) メールサーバーから受信し、他のジョブフローを呼び出すジョブフロー「フィルタリング転送」
の3つを作成します。

(1) メールアドレスを抽出するジョブフロー「アドレス抽出」

メールアドレスの形式としては “taro@example.jp” といったアドレスだけを記載したものと、”コンピラ太郎  <taro@example.jp>” のようにアカウントの名前が併記されているものがあります。アカウント名が併記されている場合には From 行からアドレス以外の項目を除外します。

次のジョブフローを「アドレス抽出」という名前で作成します。

| s = "Mail to Kompira<kompira@example.jp>" |
 
[dict = pattern('.*<([^>]+)>', typ="r").match(s)] -> # 正規表現によるマッチ
{ if dict == false |
  then: 
    print(s) -> 
    [addr = s]              # アドレスが単独で記載されたケース
  else: 
    print(dict.groups[0]) -> 
    [addr = dict.groups[0]] # < > 付きでアドレスが記載されたケース
} ->
return(addr)

3行目では、正規表現を用いて < > で囲まれた部分をメールアドレスとして取り出すためのパターンを記述しています。パターン型データのメソッド match( ) は、 文字列と正規表現がマッチすればその情報を辞書型にして返し、マッチしなければ false を返します。ここでは < > で囲まれていなかった場合は全文を、囲まれていた場合はその中身を取得しています。

(2) メールを転送するジョブフロー「メール転送」

メールの転送は mailto() 関数を使って行います。詳細については「メールを送信する」を参照してください。メールが平文の場合は mailto() 関数でそのまま送信することができますが、メッセージが HTML 形式のものなどは追加の処理が必要になる場合があります。

最初に、受信メールを扱いやすくするために mail_parse() 関数を用いて辞書型データに変換します。本文の内容はキーワード “Body” で取得できます。メールが平文の場合 (Content-Type が “text/plain;” の場合) は

{
    'Body': u'disk usage: 100%\r\n',
    'Is_Multipart': False,
    'Content-Type': 'text/plain'
    ...
}

のようになりますが、HTML 形式のメッセージと一緒に送られる場合 (Content-Type が “multipart/alternative;” の場合など) は

{
    'Body':[
        {
            'Body': u'disk usage: 100%\r\n', 
            'Is-Multipart': False, 
            'Content-Type': 'text/plain'
            ...
        }, 
        {
            'Body':'< div dir="ltr" > disk usage: 100% </div>\r\n', 
            'Is-Multipart': False, 
            'Content-Type': 'text/html' 
            ...
        }
    ],
    'Is-Multipart': True,
    'Content-Type': 'multipart/alternative'
    ...
}

のように平文と HTML がそれぞれ配列の要素として格納されます。 このため、Body 要素で渡されたデータが文字列の場合はそのまま処理を行い、配列の場合はそれぞれの要素を抽出して処理を行います。 この処理を行うジョブフロー「メール転送」を次のように作成します。

| param_to |      # 転送先メールアドレス
| param_from |    # 差出人メールアドレス
| param_subject | # メールのタイトル
| param_body |    # メールの本文
 
{ if type(param_body) == "String" |
  then: 
    print("Plain Text メールです。") ->
    mailto(
        to=param_to,
        from=param_from,
        subject=param_subject,
        body=param_body
    )
  else:
    print("HTML を含むメールです。" ) ->
    mailto(
        to=param_to,
        from=param_from,
        subject=param_subject, 
        body=param_body[0].Body,
        html_content=param_body[1].Body
    )
}

このジョブフローでは type() 関数を用いて、本文の型をチェックしています。平文の場合には “String” という文字列が返りますので、このまま mailto() 関数の body パラメータに渡します。それ以外の場合は配列と判断し、平文部分を body パラメータに、HTML 部分を html_content パラメータに渡します。

(3) メールサーバーから受信し、他のジョブフローを呼び出すジョブフロー「フィルタリング転送」

ここでは、作成したメールチャネルとジョブフローを組み合わせ、実際に転送を行うジョブフロー「フィルタリング転送」を作成します。
条件として

1. タイトルに “ALERT” または “WARNING” という文字列を含む
2. 差出人が “loginaccount@system.local” である

の両方を満たす場合にメールを転送するものとします。
メールの差出人は適宜変更してください。
条件を満たさない場合には ”スキップします” というメッセージを表示して次のメールを処理します。

| forward_addr = "admin@example.jp" |  # 転送先アドレス

<./メール受信チャネル> ->
[msg_dict = mail_parse($RESULT)] ->
 
# 送信者アドレス抽出
[./アドレス抽出: msg_dict.From] ->
[sender_addr = $RESULT] ->

# タイトル抽出
[subject = msg_dict.get_item("Subject", "")] -> 

# タイトルに指定の文字列が含まれるか確認
[dict = pattern('.*(ALERT|WARNING)', typ="r").match(subject)] ->
 
# タイトルに含まれていて、送信元が一致する場合は転送
{ if dict != false and sender_addr == "loginaccount@system.local" |
  then: 
    [./メール転送: 
        param_to=forward_addr,
        param_from=sender_addr, 
        param_subject=subject,
        param_body=msg_dict.Body
    ]
  else:
    print("条件に一致しないためスキップします。: ", sender_addr, subject)
} ->
self() # ジョブフローの先頭に戻る

転送条件として各ヘッダーの項目や本文の内容などを用いることが出来ます。例えば受信日時は msg_dict.Date で参照することができます。本文を処理する際にはジョブフロー「メール転送」で見てきたように、HTML 形式のメッセージの扱いにご注意ください。システムから送信されるメールが対象の場合はほぼ全てが平文のメールであるため、HTML を含むメールを無視しても問題は少ないと思われますが、処理する対象によっては重要となります。

また、本文の構造が複雑な場合には、ジョブフローのパターンマッチングだけでは難しい場合があります。その場合は Kompira Enterprise のライブラリ型オブジェクトを使うことで、Python 言語の文字列処理関数を利用できます。ライブラリ型オブジェクトについては、「Python で記述された処理を Kompira から呼び出す」を参考にしてみてください。

TOP