Ubuntu

Ubuntu20.04 Postfixでメールを受けたらスクリプトを実行する

ウチで稼働しているサーバーは、Logcheckでレポートをメールで送信するようにしているので、Postfixが動いている。
これらのサーバーに空メールを送信したら、スクリプトを実行するような仕掛けを作りたい。



広告


メールに関して、過去にやりたいなーと思うことが色々とあった。
でも、メーラーでメールを受けて処理したり、Ubuntuで提供されているパッケージを適用したりするのがせいぜいだった。

今回は、Postfixでメールを受けた時点で処理をする。master.cfの扉を少しだけ開けてみる感じ。

コンテンツフィルタとして実行

簡単なものと高度のものがあると書かれている。
Postfix キューに入った後のコンテンツフィルタ

サービスを常駐させて、ポートを開いて待ち受ける。受けたメールに対して何らかの処理をして、Postfixに戻す。高度なものはこうして作るっぽいけれど、今やりたいことは単純なので、簡単なものを作ってみる。

スクリプト

メールはパイプで渡されるとのことなので、単純なスクリプトを書いてみる。

試してみよう!ということで、Postfixが教えてくれたスクリプトをほぼそのまま書いてみた。
/home/rohhie/filter.sh ※場所も名前も適当だけど…

#!/bin/sh

# Simple shell-based filter. It is meant to be invoked as follows:
#       /path/to/script -f sender recipients...

# Localize these.
# 作業ディレクトリ、メールを書き戻すコマンド
INSPECT_DIR=/tmp
SENDMAIL="/usr/sbin/sendmail -i"

# Exit codes from <sysexits.h>
EX_TEMPFAIL=75
EX_UNAVAILABLE=69

# Clean up when done or when aborting.
# 処理が終了したときにクリーンアップする。テストでメールを残したければ、コメント化しておく。
trap "rm -f in.$$" 0 1 2 3 15

# Start processing.
# 作業用のディレクトリに移動する。
cd $INSPECT_DIR || {
    echo $INSPECT_DIR does not exist; exit $EX_TEMPFAIL; }

# 標準入力をそのままin.<シェルのプロセスID>という名前のファイルに出力する。
cat >in.$$ || {
    echo Cannot save mail to file; exit $EX_TEMPFAIL; }

# Specify your content filter here.
# filter <in.$$ || {
#   echo Message content rejected; exit $EX_UNAVAILABLE; }
# 動作したことをファイルに残しておこう。テストとして。
echo `date +%Y-%m-%d-%H-%M-%S` $@ >> /home/rohhie/test/filter.log

# 受け取ったメールをsendmailで送信。
# -iが指定されているので . だけの単独行が無視される。
# $@はmaster.cfで定義する。
$SENDMAIL "$@" <in.$$

exit $?

実行権限を付けて動かしてみる。

$ chmod +x filter.sh
$ ./filter.sh -f rohhie@work.hogeserver.hogeddns.jp -- rohhie@rohhie.net </var/mail/rohhie
                 <Fromメールアドレス>                 <Toメールアドレス> <この時メールが1通だけ入っていたファイル>

※/var/mail/rohhieに入っていたメールは、rohhie.net → work.hogeserver.hogeddns.jp にメールを送信したもの。

コマンド実行したところ、rohhie@rohhie.netにメールが送られてきた。
ちょうどaliasesで別のアドレスでメールを受け取ったのに似ているように思った。

メールを受信したら起動

とりあえず動作だけしそうなスクリプトができたので、メール受信時に呼び出されるようにしてみる。

/etc/postfix/master.cf

# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (no)    (never) (100)
# ==========================================================================
smtp      inet  n       -       y       -       -       smtpd
  -o content_filter=filter:dummyfilter    unix  -       n       n       -       10      pipe
  flags=Rq user=rohhie argv=/home/rohhie/filter.sh -f ${sender} -- ${recipient}

content_filterは transport:nexthop で定義する模様。
この定義により、SMTPで処理するメールにはこのオプションが付く。

transportとして、この後でfilterを定義しようとしていて、そこにdummyという値を渡していると想定。
filterはmaster.cfの中でpipeで起動するが、そのときに${nexthop}でこの値をfilterに渡すことができそうだ。

設定を反映した後、メールを送ってみる。

$ sudo systemctl reload postfix

宛先が見つかるとメールがキューに入り、フィルタが呼び出され、中身がスクリプトに渡される。
そして、スクリプトで処理をした後で、スクリプトからsendmailコマンドを使ってメールがメールボックスに放り込まれて処理終了。

スクリプトでsendmailに-iのパラメータを与えているが、こちらでセットしても良いのかもしれない。

pipeはコマンドで、${sender}とか${recipient}を渡しているが、他にもたくさん渡せる情報がある模様。
ubuntu manuals / pipe – Postfix delivery to external command

受信メールアドレスを限定して起動

今まで書いたやり方だと、smtpでメールを受けると、content_filterで指定したfilterが起動することになる。
一般的なフィルタ機能を考えると、これが普通。

今回は、特定のメールアドレスだけに届いたメールに限定してスクリプトを起動したいので、設定をちょっと変える。
TheCodingMachine / GETTING STARTED WITH A BASIC POSTFIX FILTER

まず、smtpで受け取ったものをすべてフィルタに通す設定を止める。
/etc/postfix/master.cf

# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (no)    (never) (100)
# ==========================================================================
smtp      inet  n       -       y       -       -       smtpd
#  -o content_filter=filter:dummy

…
filter    unix  -       n       n       -       10      pipe
  flags=Rq user=rohhie argv=/home/rohhie/filter.sh -f ${sender} -- ${recipient}

※main.cfでdummyを渡しているのは、filterの起動でその値を使用していないからだと分かる。

受信者を限定してフィルタを通すため、まずは受信者のメールアドレスを書くファイルを定義。
hashを使うことができるけれど、ここでは正規表現を利用している。
/etc/postfix/main.cf

smtpd_recipient_restrictions = check_recipient_access regexp:/etc/postfix/access

※追加する。

受信者のメールアドレスと、呼び出すフィルタを定義。
/etc/postfix/access

/^rohhie@work\.hogeserver\.hogeddns\.jp$/ FILTER filter:dummy

空メールを受けたらスクリプトを起動、ということで、メールを保管しないつもりなので、sendmailコマンドを止めてしまう。
/home/rohhie/filter.sh ※場所も名前も適当だけど…

…
# 受け取ったメールをsendmailで送信。
# -iが指定されているので . だけの単独行が無視される。
#$SENDMAIL "$@" <in.$$

#exit $?
exit 0

設定を反映。

$ sudo systemctl reload postfix

これで、特定のメールアドレスが宛先になっているときだけ、スクリプトを起動することができた。

ログを残す

動作ログを/var/log/mail.logに残そうと考えた。
postlogというコマンドに情報を渡すと、ログを出力してくれるようだ。

先程のスクリプトの中で、このようなコマンドを入れると…

/usr/sbin/postlog -i -p info -t postfix/orgfilt Parameter:"$@"
/usr/sbin/postlog -i -p error -t postfix/orgfilt Parameter:"$@"

このようなログが出力される。

/var/log/mail.log

Dec  1 08:38:01 work postfix/orgfilt[2344]: Parameter:-f rohhie@rohhie.net -- rohhie@work.hogeserver.hogeddns.jp
Dec  1 08:38:01 work postfix/orgfilt[2345]: error: Parameter:-f rohhie@rohhie.net -- rohhie@work.hogeserver.hogeddns.jp

/var/log/mail.err

Dec  1 08:38:01 work postfix/orgfilt[2345]: error: Parameter:-f rohhie@rohhie.net -- rohhie@work.hogeserver.hogeddns.jp

ここにログを出しておけばローテートされるし、必要になったらgrepすれば必要な情報も見つかるだろう。

ユーザーの変更

master.cfでユーザーを指定しているので、nobodyなど他のユーザーを指定したところ、上手く動作した。

では、ということでrootに変えてみた。

/var/log/mail.log

Dec  1 19:59:50 work postfix/pipe[2047]: fatal: user= command-line attribute specifies root privileges
Dec  1 19:59:51 work postfix/qmgr[2020]: warning: private/filter socket: malformed response
Dec  1 19:59:51 work postfix/qmgr[2020]: warning: transport filter failure -- see a previous warning/fatal/panic logfile record for the problem description
Dec  1 19:59:51 work postfix/master[1802]: warning: process /usr/lib/postfix/sbin/pipe pid 2047 exit status 1
Dec  1 19:59:51 work postfix/master[1802]: warning: /usr/lib/postfix/sbin/pipe: bad command startup -- throttling
Dec  1 19:59:51 work postfix/error[2048]: 4D157200D2: to=<rohhie@work.hogeserver.hogeddns.jp>, relay=none, delay=1, delays=0.02/1/0/0.01, dsn=4.3.0, status=deferred (unknown mail transport error)

結果として、メール受信時にエラーログが表示され、スクリプトが起動することもなかった。
メールが保管されることもなかった。

パイプでスクリプトを実行

メール着信時にスクリプトを起動しようと思って検索を掛けたら、一番最初に見つかった方法がこれだった。
情シスハック / メール受信で自動的にスクリプトを実行する

なるほどー、このような方法があるのかー。
Qiita / postfixでスクリプトを動かしてあれこれ

起動するユーザーがデフォルトでは nobody:nogroup になり、複数のスクリプトを別のユーザーで動かすような細かな制御はできそうもないけれど、手っ取り早くスクリプトを実行できる。

スクリプトの作成

試してみよう!ということで、こんなスクリプトを書いてみた。
/home/rohhie/test.sh ※場所も名前も適当だけど…

#!/bin/bash
while read buff; do
    echo $buff >> /home/rohhie/test/test.txt
done

実行権限を付けて、テスト用のディレクトリを作る。

$ chmod +x test.sh
$ mkdir /home/rohhie/test
$ chmod 777 /home/rohhie/test

パイプの設定

エイリアス設定を作って、スクリプトを呼び出すようにする。
shellという存在しない宛先を使ってみる。

/etc/aliasesに加える運用が一番素直なように思うけれども、あえてファイルを分けてみた。

/etc/postfix/shell

shell: "| /home/rohhie/test.sh"

今回は、スクリプトを起動するために空メールを送るので、メールを保管しない。
送られてきたメールを保管したい場合は、カンマで区切ってメールアドレスを追加すればよい。

$ sudo postalias /etc/postfix/shell

これを、エイリアスに加える。
/etc/postfix/main.cf

alias_maps = hash:/etc/aliases hash:/etc/postfix/shell

設定を反映させる。

$ sudo systemctl reload postfix

メールを送ってみる

shell@hogeserver.hogeddns.jp … 宅内ではこれでメールが届くようになっている。

結果はこれ。

$ ll test
total 12
drwxrwxrwx 2 rohhie rohhie  4096 Nov 28 01:49 ./
drwxr-xr-x 8 rohhie rohhie  4096 Nov 28 08:03 ../
-rw------- 1 nobody nogroup 1478 Nov 28 01:58 test.txt

転送そのものはchrootされていない状態で実行されているのだなぁ。

test.txt

From rohhie@rohhie.net Sun Nov 28 08:09:37 2021
Return-Path: <rohhie@rohhie.net>
X-Original-To: shell@work.hogeserver.hogeddns.jp
Delivered-To: shell@work.hogeserver.hogeddns.jp
Received: from rohhie.net (rohhie.net [192.168.100.100])
<省略>
Received: by rohhie.net (kopano-spooler) with MAPI; Sun, 28 Nov 2021 08:09:37
+0900
Subject: subject test
From: "Rohhie" <rohhie@rohhie.net>
To: =?us-ascii?Q?shell=40work=2Ehogeserver=2Ehogeddns=2Ejp?=
<shell@work.hogeserver.hogeddns.jp>
Date: Sat, 27 Nov 2021 23:09:37 +0000
Mime-Version: 1.0
Content-Type: text/plain; charset=iso-2022-jp
Content-Transfer-Encoding: quoted-printable
X-Mailer: Kopano 11.0.2
Thread-Index: Adfj48gUNF8Se+meTAaZTI4HpnNjCw==
Message-Id: <kcEE.e0+cTjsOSI6rt8y81knNeQ.gI4X2ePj1wE@rohhie.net>

Body test.

転送されたことが記録され、インデントはすべて取り外された状態で保管された。
メールそのものが標準入力から渡されるようで、すべての情報が取り出せる。

ユーザーの変更

実行ユーザーは、default_privsで変えることができた。

そこで、rootが指定できるのか試してみたところ、Postfixの読み込み時点でエラーが出る。
そして、メールを受け取ってもエラーが出る。

/var/log/mail.err

Nov 28 09:11:34 work postmulti[23497]: fatal: file /etc/postfix/main.cf: parameter default_privs: user root has privileged user ID
Nov 28 09:14:57 work postfix/smtpd[23558]: fatal: file /etc/postfix/main.cf: parameter default_privs: user root has privileged user ID

さいごに

ポートを開いてメールの情報を受け取って処理する格好いいものが作れたら良いな。
でも、今やりたいことだけを考えたら格好が良くてもオーバースペックなので、このくらいにして、やるだけやってしまえ、とも思うのだった。

それと、trapというコマンドを知ったのは良かった。
これは後始末に最適、とっても便利に使えそう。

コメントはこちらから お気軽にどうぞ ~ 投稿に関するご意見・感想・他