たとえば特定の宛先にメイルを送ると 商品の資料が返送されたりするサービスがある。 これはもちろん人間が送り返しているのではなく, プログラムによって自動的に返送処理がされている。 どこかから発信された電子メイルが届くとき, そのメッセージがファイルとして保存されるだけではなく, 指定したプログラムを起動し,メッセージの内容を標準入力として 読み取らせることもできる。 この仕組みを利用し,メイル配送時に自動的に起動され, 受信メッセージを読み取りつつ動くプログラムを作成してみよう。
電子メイルは,送信者がクライアントソフトウェアを用いてメッセージを送ると 送信者側のメイルサーバを経て受信者側のメイルサーバに届く(下図参照)。
中継点となるメイルサーバが複数あったり,ない場合もあるが, 最終的には受信者のメイルボックスのあるメイルサーバで, ファイル単位の書き込み処理がされる。
ここでは,MDA(Mail Delivery Agent)が,送られたメッセージをファイルに 書き込む時に自動処理を行なわせる方法を考える。 本講ではメイル配送下でプログラムを動かすときの制御の自由度のもっとも高い qmail をローカル配送プログラムに利用する例を示す。 qmail では拡張アドレス機能によって個人ユーザのメイルアドレスを何個でも作れ, なおかつ配送時に起動するプログラムに対して十分な情報を環境変数に 設定する機能があるためスクリプト作成が非常に効率的に行なえる。
一般的なメイルサーバプログラムでは,ユーザ宛てメッセージの処理を
「ファイルへの保存」,「他のアドレスへの転送」,「コマンド起動」
のいずれかから選べる。
qmail の場合,一般ユーザ宛メッセージの最終配送先をどうするかは
~user/.qmail
ファイルの内容で決定する。
このファイルは dot-qmail(5) ファイル形式で記述する。1行1エントリで
以下のいずれかを何個でも書ける。
行頭文字 | 種類 | 例 |
---|---|---|
# | コメント | # メモ |
| | プログラム | |./program arg |
. または / (スラッシュで終わらない) |
mbox形式のファイル | ./mbox |
. または / (スラッシュで終わる) |
maildir形式のディレクトリ | ./maildir/ |
& | 転送メイルアドレス | &taro@example.ac.jp |
また,dot-qmail ファイルとして
~user/.qmail-ext
があると,
user-ext という宛先の配送先として利用する。
これを拡張アドレスといい,ext の部分を
拡張子という。また,ext を "default" にした
~user/.qmail-default
というファイルを作ると,対応する dot-qmail ファイルがない場合の
デフォルトの宛先となる。つまり,user-abc,
user-xyz, user-hoge, user-foo, ...
など任意の拡張子を持つローカルな宛先に対して,
対応する dot-qmail ファイルがない場合はすべて
~user/.qmail-default
で定義される宛先に配送される。
dot-qmail ファイル中の |
で始まる行は,
すぐ後ろに書いたプログラムを
その記述のとおりに起動する。このとき以下の環境変数が自動的に
設定された状態でプログラムが呼ばれる。
環境変数 | 値の意味 |
---|---|
SENDER | エンベロープsender |
RECIPIENT | 受信者アドレス |
USER | 受信者のユーザ名 |
HOME | 受信者のホームディレクトリ |
HOST | 受信アドレスのドメイン部 |
LOCAL | 受信アドレスのローカル部 |
EXT | 拡張子部分(ユーザ名以降最初のハイフン以降) |
EXT2 | $EXT の1個目のハイフン以降 |
EXT3 | $EXT の2個目のハイフン以降 |
EXT4 | $EXT の3個目のハイフン以降 |
DEFAULT | default でマッチした文字列 |
たとえば,以下のような送信が行なわれた場合を考えよう。
差出人のアドレス | hanako@example.com |
宛先のアドレス | taro-foo@example.co.jp |
受取側の taro-foo@example.co.jp のうち「-foo
」は拡張子で,
受信者となるユーザは taro である。この場合の配送先は
~taro/.qmail-foo
ファイルで決定され,
そこには
| program args...
のように書いてあったとする。 以上の条件で,program が起動するときの環境変数は 以下のように設定される。
環境変数 | 値 |
---|---|
SENDER | hanako@example.com |
RECIPIENT | taro-foo@example.co.jp |
USER | taro |
HOST | example.co.jp |
LOCAL | taro-foo |
EXT | foo |
注意すべきはRECIPIENT変数の値で, これは実際に配送される宛先のアドレスに なるのであって,メッセージに書かれている To: アドレスの値になるとは限らない。 たとえば,
To: hogehoge@example.co.jp Cc: taro-foo@example.co.jp
のように出されたメッセージでも,taro-foo に届くときは RECIPIENT=taro-foo@example.co.jp になる。
上記の環境変数をうまく利用すればメイル処理プログラムが効率的に作れる。 簡単な例として,送信者に「ありがとう」とだけ返すプログラムを作る。
メイルアドレスは user-39
で作る。
この配送先は ~/.qmail-39
ファイルに書き込んで作成する。
echo '| ./gd/thankyou.rb' > ~/.qmail-39
ホームディレクトリから見て ./gd/thankyou.rb
の名前で送信者($SENDER
)にメイルを送るRubyプログラムを
作る。その前にメイルを送信するコマンドについて調べる。
メイルを送信するための原始的なプログラム sendmail を利用すると自動的なメイル送信が容易に行なえる(qmail や Postfix にも同名の互換コマンドが用意されている)。
sendmail -f from@add.ress to@reci.pie.nt...
これで標準入力を読んだ結果を送信する。ヘッダと本文を正しく入れて 送信してみる。
sendmail -f 自分のアドレス 自分のアドレス
To: 自分のアドレス
From: 自分のアドレス
Subject: test テスト
Hello!
はろー!
[C-d]
実際に 自分のアドレス に届いたメイルを確認してみよう。 使用するソフトウェアにもよるが,規格どおりに作ってある正しいソフトウェア では以下のように化けた内容になるはずである。
Subject: test 繝??せ繝 Hello! 縺??繧阪!
メイルのヘッダや本文に日本語を入れたい場合はどの文字コードを使うかを 宣言する宣言文を入れた上で,その文字コードに正しく変換したものを 送出しなければならない。 ヘッダのフィールド値に日本語を入れたい場合はMIMEエンコード する必要がある。また,本文に入れる場合はヘッダ内に利用する文字コードの 形式を入れる。本文でJISコード(ISO-2022-JP)を用いる場合は, 以下のような記述をヘッダに追加する。
MIME-Version: 1.0 Content-type: text/plain; charset=iso-2022-jp
文字コードは nkf ライブラリで変換できる。元の文字列をヘッダの値向けにMIMEエンコードするには
NKF.nkf('-jM', String)
とし,本文向けにJISコードに変換するには
NKF.nkf('-j', String)
とする。日本語Subject付の日本語メッセージを送る簡単な プログラムは以下のようになる。
#!/usr/bin/env ruby # -*- coding: utf-8 -*- require 'nkf' subj = '日本語サブジェクト' body = 'こんにちは, さようなら。' if ARGV[0] == nil then STDERR.puts "送り先アドレスを指定してください。" STDERR.puts " 例: #{$0} toaddress@example.jp" exit 1 end command = sprintf("| sendmail %s", ARGV[0]) header = sprintf("To: %s Subject: %s Content-type: text/plain; charset=iso-2022-jp Mime-Version: 1.0\n\n", ARGV[0], NKF.nkf('-jM', subj)) open(command, "w") do |mail| mail.print header mail.print NKF.nkf('-j', body) end
以下のように起動して日本語込みのメッセージが届くことを確認しよう。
./sendjpex.rb 送信先@アドレス
元の課題に戻ろう。~/.qmail-39
に指定したプログラムで,
「送信者」に「ありがとう」とだけ返すプログラムを作成してみる。
「送信者」つまり送り返すべき宛先はスクリプト起動時の環境変数
SENDER
から取得し,そこに送信するのが簡単である。
プログラムは以下の流れで作成する。
メイルが届くと ~/.qmail-39
に書かれたとおりに
プログラムが自動的に起動される。このとき標準入力を読むと
送信メッセージの内容を1行目から順に得ることができる。
プログラム起動のきっかけとなったメイルの「送信者」
のアドレスが環境変数 SENDER
に入っているので,これを自動返信メイルの宛先とする。
プログラム起動のきっかけとなった受信アドレスが
環境変数 RECIPIENT
に入っているので,
これをスクリプトから送るメイルの送信者アドレスとする。
SubjectはMIMEエンコードし,本文はJISコード変換してから送る。
環境変数は,Rubyの変数 ENV
にハッシュとして
入っている。たとえば,ENV["FOO"]
は,環境変数
FOO
が定義されている場合はその値が,定義されていない場合は
nil
が返る。
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
require 'nkf'
sender = ENV['SENDER'] # 環境変数SENDERの値の取得
rcpt = ENV['RECIPIENT'] # 環境変数RECIPIENTの値の取得
if sender == nil || rcpt == nil then
STDERR.puts "$SENDER and $RECIPIENT not set. exit."
exit 0 # メイル用プログラムはエラーでも exit 0 すべき
elsif /.*@.*/ !~ sender then # メイルアドレス形式でない場合
STDERR.puts "SENDER address invalid"
exit 0
end
to = sender
from = rcpt
subject = NKF.nkf("-jM", 'メイル受信しました')
header = sprintf("To: %s
From: %s
Subject: %s
Content-type: text/plain; charset=iso-2022-jp
Mime-Version: 1.0\n\n", to, from, subject)
message = NKF.nkf("-j", "ありがとう\n")
program = sprintf("| sendmail -f %s %s", from, to)
open(program, "w") do |mail|
mail.print header
mail.print message
end
メイル配送により自動起動するプログラムはエラーで停止してはならない。 デバッグする際はメイル配送時と同じ状況を作り出してプログラムを起動する。 このスクリプトの例では,
SENDER
と RECIPIENT
に値をセットするの2点を満たしつつプログラムを起動する。適当なメイル格納ファイルが
~/Mail/inbox/1
にあったとすると,
cat ~/Mail/inbox/1 | \
SENDER=自分のメイルアドレス RECIPIENT=スクリプトの受信アドレス \
./gd/thankyou.rb
のようにして確認する。
念入りに起動実験して問題がなければ,実際に電子メイル経由で起動してみる。
echo test|Mail -s test-mail スクリプトの受信アドレス
以上のように起動しても,期待した動きが見られない場合には システムのメイル送信キューに溜っていないか確認する。
sudo mailq
スクリプト起動時にエラーとなっている場合のエラーメッセージは
maillog ファイルに記録されている。典型的な場所は
/var/log/maillog
であるのでこれを確認するとよい。