ウチで稼働しているサーバーは、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:dummy … filter 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というコマンドを知ったのは良かった。
これは後始末に最適、とっても便利に使えそう。
コメントはこちらから お気軽にどうぞ ~ 投稿に関するご意見・感想・他