Outlook向け Zabbixメール通知拡張

#メール通知拡張


## 目的

+ 大前提　「Outlook」を利用する（会社指定だから仕方ないね）
+ Zabbixからのメール通知の異常発生、復旧をツリー化したい
+ 通知メールが異常発生なのか復旧なのかひと目で分かるように  
デザインを取り込みたい
+ メーリングリストを使って通知したい
+ メール優先度で障害レベルが知りたい

## 実装

https://github.com/130cmWolf/plugins/tree/master/notification/sendmessage-smtp-php
思いつきで対応していたので比較的適当な実装になってます
もちろん勝手に改造していただいてよいです。

## ソース解説

解説はいいから使い方を教えろって方は→の目次からドゾ

比較的よく使われていそうなZabbixメール送信拡張用のプラグインスクリプトの
https://github.com/zabbix-jp/plugins
からsendmessage-smtp-phpを利用しています

とは言え最終的にはほぼすべて書き直しに近い実装になってしまったが…

UTF-8を利用したほうが実装が楽だったのでiso-2022-jpやPHPMailer_JPクラスは削除しています。

### phpmailer の更新

まあ、サラッと追加。

```
composer require phpmailer/phpmailer
```

記載時のバージョンは 5.2.25 でした
$ cat vendor/phpmailer/phpmailer/VERSION
5.2.25  

vendorを読み込んでおきます。

```php:sendmessage_smtp_php.sh
require 'vendor/autoload.php';
```


### Markdown対応

Zabbix内でのメール書式をMarkdownで書いたほうが楽かもしれないと思ったので、
Markdownプラグインも乗せています。  

```
composer require michelf/php-markdown
```

```php:sendmessage_smtp_php.sh
use Michelf\MarkdownExtra;


// HTMLメール対応
$MAIL_MESSAGE = <<<EOD
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" />
<html xmlns="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Language" content="ja" />
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
    <meta name="viewport" content="initial-scale=1.0,width=device-width" />
    <title>$MAIL_SUBJECT</title>
    <style type="text/css">
h1,h2 {
  font-size: 110%
}
blockquote {
  margin-left: 0.5em;
  padding-left: 0.5em;
  border-left: 1px solid #CCCCCC;
}
body {
  font-size: 90%;
}
pre{
  font-size: 90%
  display: block;
  padding: 0.5em;
  width: 70%;
  background-color: #DDDDDD;
  border: 1px dotted #666666;
}
code{
font-size: 90%
}
  </style>
  </head>
EOD;
// Markdownフォーマットの展開
$MAIL_MESSAGE .= "<body>" . MarkdownExtra::defaultTransform($argv[3]) . "</body>";
$MAIL_MESSAGE .= "</html>";
```

### SMTP認証の改良

そのままでも問題無いと思いますが、
ポート等の変数を分解しています。

```php:sendmessage_smtp_php.sh
$MAIL_SMTP_HOST = 'smtp.example.com';
$MAIL_SMTP_PORT = 25;
$MAIL_SMTP_SEC  = "ssl";

$mailer->Host = $MAIL_SMTP_HOST;
$mailer->Port = $MAIL_SMTP_PORT;
$mailer->SMTPAuth = true;
$mailer->SMTPSecure = $MAIL_SMTP_SEC;
$mailer->Username = $MAIL_SMTP_USER;
$mailer->Password = $MAIL_SMTP_PASS;
```

### 優先度対応

特に難しいことはしていません。

```php:sendmessage_smtp_php.sh
// 優先度設定
$PRIORITY = 3;
switch ($m[3]) {
    case "0":
        // 分類無し
        $PRIORITY = 3;
        break;
    case "1":
        // 情報
        $PRIORITY = 3;
        break;
    case "2":
        // 警告
        $PRIORITY = 3;
        break;
    case "3":
        // 障害
        $PRIORITY = 1;
        break;
    case "4":
        // 重度障害
        $PRIORITY = 1;
        break;
    case "5":
        // 致命的な障害
        $PRIORITY = 1;
        break;
}


$mailer->Priority = $PRIORITY;
```

### メールツリー化用リプライ設定

ツリー化するためのリプライ設定を追加します。
管理ファイルの持ち方等は [ZABBIXの通知メールをスレッド化してみる](http://pocketstudio.jp/log3/2013/06/07/zabbix-email-alerts-threadin/) を参考にしています

```php:sendmessage_smtp_php.sh
    $mailer->addCustomHeader('In-Reply-To', '<APPLI.' . $id . '.' . $rnd . '@' . gethostname() . '>');
    $mailer->addCustomHeader('References', '<APPLI.' . $id . '.' . $rnd . '@' . gethostname() . '>');
```

### Outlook用ツリー化リプライ設定、メーリングリスト対応

こいつが曲者

getNewThreadIndex()でメール新規発行用のThread-Indexを生成して、
管理ファイルへ保存しています。

リプライ用にgetNextThreadIndex($parent)に対しツリー親のThread-Indexを渡すとツリー子用のThread-Indexを発行します。

Thread-Indexについての解説は [Outlook Thread-Index Value Analysis](https://www.solutionary.com/resource-center/blog/2014/04/thread-index-value-analysis/) を参考に。

子用のThread-Indexの算出があってるかどうかわからないが、
動いたからいいかなと思っている。

多分孫メール用のThread-Indexは正しく生成できないと思われるが、Zabbixで動く分には問題ないためこの実装ですすめる。

```php:sendmessage_smtp_php.sh
function GUID()
{
    if (function_exists('com_create_guid') === true)
    {
        return trim(com_create_guid(), '{}');
    }
 
    return sprintf('%04X%04X-%04X-%04X-%04X-%04X%04X%04X', mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(16384, 20479), mt_rand(32768, 49151), mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(0, 65535));
}
function getNewThreadIndex()
{
    // Outlook独自ヘッダー Thread-Index 生成
    $offset = ((new DateTime())->getTimeStamp() - (new DateTime("1601-01-01T00:00:00.000Z"))->getTimeStamp());
    $hex = hex2bin(substr(sprintf("%016X",$offset *10 *1000 *1000), 0, 12) . str_replace ('-', '', GUID()));
    return base64_encode($hex);
}
function getNextThreadIndex($parent)
{
    // Outlook独自ヘッダー Thread-Index リプライ生成
    $parenthex = bin2hex(base64_decode($parent));
    
    $parentdate = new DateTime("1601-01-01T00:00:00.000Z");
    $parentdate->modify("+". sprintf("%d",hexdec(substr($parenthex, 0, 12) ."0000")/10 /1000/1000). " seconds" );
    
    $c_time_offset  = ((new DateTime())->getTimeStamp() - $parentdate->getTimeStamp()) *10 *1000 *1000;
    $time_diff  = sprintf("%064s",decbin($c_time_offset)) ."\n";
    $binary = substr($time_diff, 23, 31);
    return base64_encode(hex2bin($parenthex . sprintf("%010X", intval($binary,2))));
}



    // 初期ヘッダ作成
    $mailer->addCustomHeader('Thread-Topic', $thr);
    $mailer->addCustomHeader('Thread-Index', $thi);


    $mailer->addCustomHeader('Thread-Topic', $thr);
    $mailer->addCustomHeader('Thread-Index', getNextThreadIndex($thi)); // Thread-Indexリプライ設定

```

## 使い方

### サーバー内

Zabbix用スクリプトの配置

```
/usr/lib/zabbix/alertscripts/sendmessage_smtp_php/sendmessage_smtp_php.sh
```

phpmailerとphp-markdownを取得

```
cd /usr/lib/zabbix/alertscripts/sendmessage_smtp_php/
chmod +x sendmessage_smtp_php.sh
composer require phpmailer/phpmailer
composer require michelf/php-markdown
```

### Zabbix 設定

#### メディアタイプ

「管理＞メディアタイプ」から「メディアタイプの作成」  
![mediatype.png](https://qiita-image-store.s3.amazonaws.com/0/150940/83ac9742-4ded-6f4e-4e82-f1016a9346ab.png)  
一般的な設定で作成

#### ユーザー設定

ユーザーに対応するメディアタイプにメール設定を追加

#### アクション設定の変更

件名

```
[Zabbix Alert:{EVENT.ID}] {HOST.NAME} 障害発生  ###{EVENT.ID}  {TRIGGER.NSEVERITY}
```
「###」をキーとしてその後ろにイベントIDと障害レベルをスクリプトに通知しています

メッセージ

```Markdown
<font color="Red">システム障害発生</font>
======

* 環境  
{HOST.NAME1} / {HOST.IP}
* 障害発生時刻  
{DATE} {TIME}

[障害情報](http://zabbix.server/zabbix/tr_events.php?triggerid={TRIGGER.ID}&eventid={EVENT.ID})

-----

* このメールはシステムより自動配信されています。  
* 監視停止再開メールはスレッド化されて配信されます。

-----
```
等、Markdownで自由に記載します。

実行内容も作成したメディアタイプに対して送信します。

#### 復旧メール設定

件名

```
[Zabbix Alert:{EVENT.ID}] {HOST.NAME} 障害発生  ###{EVENT.ID}  {TRIGGER.NSEVERITY}
```
「###」をキーとしてその後ろにイベントIDと障害レベルをスクリプトに通知しています
注：障害メールと同じ件名にしないとOutlookの仕様では同じツリーとみなされずばらばらになってしまいます。
タイトル先頭に 「re:」「復旧:」 は同じツリー内メールとしてみなされたため(Outlook2010)付けるのはありだと思う。

メッセージ

```Markdown
<font color="Blue">システム復旧</font>
======

* 環境  
{HOST.NAME1} / {HOST.IP}
* 復旧時刻  
{DATE} {TIME}

[障害情報](http://zabbix.server/zabbix/tr_events.php?triggerid={TRIGGER.ID}&eventid={EVENT.ID})

-----

* このメールはシステムより自動配信されています。  
* 監視停止再開メールはスレッド化されて配信されます。

-----
```
等、Markdownで自由に記載します。

実行内容も作成したメディアタイプに対して送信します。

# 参考

http://pocketstudio.jp/log3/2013/06/07/zabbix-email-alerts-threadin/
ツリー化の参考情報

https://www.solutionary.com/resource-center/blog/2014/04/thread-index-value-analysis/
唯一のthreadindex解説だった

# 追加

https://github.com/130cmWolf/SampleSource/blob/master/Thread-Index/getThreadIndex.php
Thread-Index周りのサンプルコードを作成しました。
親、子、孫…関係のThread-Indexが生成できます。