プログラムによるメイル受信

たとえば特定の宛先にメイルを送ると 商品の資料が返送されたりするサービスがある。 これはもちろん人間が送り返しているのではなく, プログラムによって自動的に返送処理がされている。 どこかから発信された電子メイルが届くとき, そのメッセージがファイルとして保存されるだけではなく, 指定したプログラムを起動し,メッセージの内容を標準入力として 読み取らせることもできる。 この仕組みを利用し,メイル配送時に自動的に起動され, 受信メッセージを読み取りつつ動くプログラムを作成してみよう。

電子メイル配送のしくみ

電子メイルは,送信者がクライアントソフトウェアを用いてメッセージを送ると 送信者側のメイルサーバを経て受信者側のメイルサーバに届く(下図参照)。

Image of message processing via SMTP

中継点となるメイルサーバが複数あったり,ない場合もあるが, 最終的には受信者のメイルボックスのあるメイルサーバで, ファイル単位の書き込み処理がされる。

ここでは,MDA(Mail Delivery Agent)が,送られたメッセージをファイルに 書き込む時に自動処理を行なわせる方法を考える。 本講ではメイル配送下でプログラムを動かすときの制御の自由度のもっとも高い qmail をローカル配送プログラムに利用する例を示す。 qmail では拡張アドレス機能によって個人ユーザのメイルアドレスを何個でも作れ, なおかつ配送時に起動するプログラムに対して十分な情報を環境変数に 設定する機能があるためスクリプト作成が非常に効率的に行なえる。

dot-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個目のハイフン以降
DEFAULTdefault でマッチした文字列

たとえば,以下のような送信が行なわれた場合を考えよう。

差出人のアドレスhanako@example.com
宛先のアドレスtaro-foo@example.co.jp

受取側の taro-foo@example.co.jp のうち「-foo」は拡張子で, 受信者となるユーザは taro である。この場合の配送先は ~taro/.qmail-foo ファイルで決定され, そこには

| program args...

のように書いてあったとする。 以上の条件で,program が起動するときの環境変数は 以下のように設定される。

環境変数
SENDERhanako@example.com
RECIPIENTtaro-foo@example.co.jp
USERtaro
HOSTexample.co.jp
LOCALtaro-foo
EXTfoo

注意すべきは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 '| ./pf3/thankyou.rb' > ~/.qmail-39

ホームディレクトリから見て ./pf3/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付の日本語メッセージを送る簡単な プログラムは以下のようになる。

sendjpex.rb

#!/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 から取得し,そこに送信するのが簡単である。

プログラムは以下の流れで作成する。

環境変数は,Rubyの変数 ENV にハッシュとして 入っている。たとえば,ENV["FOO"] は,環境変数 FOO が定義されている場合はその値が,定義されていない場合は nil が返る。

thankyou.rb

#!/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

メイル自動応答プログラム作成時の注意

メイル配送により自動起動するプログラムはエラーで停止してはならない。 デバッグする際はメイル配送時と同じ状況を作り出してプログラムを起動する。 このスクリプトの例では,

の2点を満たしつつプログラムを起動する。適当なメイル格納ファイルが ~/Mail/inbox/1 にあったとすると,

cat ~/Mail/inbox/1 | \
  SENDER=自分のメイルアドレス RECIPIENT=スクリプトの受信アドレス \
  ./pf3/thankyou.rb

のようにして確認する。

念入りに起動実験して問題がなければ,実際に電子メイル経由で起動してみる。

echo test|Mail -s test-mail スクリプトの受信アドレス

以上のように起動しても,期待した動きが見られない場合には システムのメイル送信キューに溜っていないか確認する。

sudo mailq

スクリプト起動時にエラーとなっている場合のエラーメッセージは maillog ファイルに記録されている。典型的な場所は /var/log/maillog であるのでこれを確認するとよい。


本日の目次

yuuji@e.koeki-u.ac.jp