Perl/CGI研究室 'PERL-LABO'

Perl/CGI研究室 'PERL-LABO' TOPへ
戻る(History.Back)

メールの送信

研究内容

フォームから受け取ったデータをメールで送信するやり方を研究しましょう。

結果

受け取るデータは

まず、フォームで入力してもらうものについて考えてみます。 メールを送信するのに必要なデータとして考えると、 送信先メールアドレス、送信元メールアドレス、件名、本文、とりあえずこの4つですが、 でも、メールフォームって基本的に送信先は決まってますよね。 もし送信先を自由に設定できるメール送信CGIプログラムなんかをウェブで公開していたら、 いたずらメールを送られちゃいます。ですから、メール送信CGIプログラムは送信先は固定。 これが基本ですね。 ということで、送信先はCGIプログラムの中で設定するようにして、 変えられないようにします。

ということで、今回メールの送信を研究するにあたって、入力データは 「送信元メールアドレス」「件名」「本文」の3つにしましょう。 いずれも、省略可とします。

データを受け取るには

データを受け取るのは、もちろん作成済みgetformdata関数です。

メールを送るには

必要なデータが揃ったとして、メールを送るにはどうしたらいいかっていうと… sendmailっていうプログラムを利用するんだそうです。 sendmailっていうのはPerlとは関係なくて、メールを送信してくれるというプログラム。 だから、このsendmailっていうのがサーバーの中にないと、このやり方だとメールを 送ることができないですけど、大抵のサーバーにはあるみたいです。

sendmailの使い方

sendmailは次のようにしてPerlの中から使うようです。

open(MAIL, "| (sendmailのパス) -t");
print MAIL (ここにメールデータ);
close MAIL;

さて、openとcloseっていうのが出てきましたね。 openというのはファイルを開くのに使います。 MAILとしているのは、ファイルハンドルといって、ファイルを表す変数のようなものです。 名前はなんでも構いませんが、ここではMAILとしました。 ファイルハンドルは大文字で書くのが通例のようです。

ちょっと難しいのは "| (sendmailのパス) -t" の部分です。 最初の | は、パイプというものを表します。 openでファイルを開くと書きましたが、"| 〜" というようにすると、 パイプを使って〜というプログラムにデータを渡すっていう意味になります。 他のプログラムにデータを渡すっていうのは、なんだか難しそうですよね? でも、Perlでは難しく考える必要は無いらしくて、 パイプを使って他のプログラムをopenしたら、 後はあたかもファイルに読み書きするかのように、 データを他のプログラムに渡すことができるんです。 でもパイプっていうのは、もともとはPerlの機能じゃなくて、 別のプログラムにデータを渡してください、という意味のOSの機能です。 便利な機能ですね。 パイプを使うと、データをもらうこともできますがここではやりません。 で、| の後ろはsendmailのパス。sendmailにデータを渡してくださいっていう意味になります。 その後ろの -t は open とは関係がなくて、sendmailのコマンドラインオプションです。 意味は、後で送るメールデータの中のTo,Cc,Bccを読んでそこにメールを送ってください、というものです。

うーん、このあたり、ちょっと難しいですよね。 このパイプうんぬんとかは、このメール送信のときくらいしか使いませんから、 こういうものなんだ、オマジナイなんだっていうことで次に進んでもオッケーです。

次のprint文はいままでも何度も出てきましたが、少し違いますね。 MAIL というのが付いています。今までは、print文はブラウザに対して出力するという イメージで使っていました。これ、厳密に書くと、少し違います。 print文は、出力先が指定されていない場合は、標準出力っていうのに出力するんです。 標準出力っていうのは、CGIプログラムの場合はウェブサーバーになります。 そんでウェブサーバーがブラウザに出力を転送するんでしたね。 ここでは、print MAIL というように出力先を指定しています。 出力先は、MAILというファイルです。実体は、sendmailですね。 でも、出力先の実体がなんであるかっていうのは気にする必要はありません。 print文がうまいことやってくれます。 ということで、このprint MAIL によってsendmailにデータを渡すことができます。

続くcloseは、ファイルを閉じるのに使います。もう、データは終わりですよという意味も かねています。

メールを送るのにsendmailというプログラムの力を借りているとはいえ、 パイプという機能によってこんなに簡単に行えます。いやー凄い便利。 毎回同じことを言ってる気がしますけど (^^;

メールデータって

sendmailの使い方のところで メールデータ と書きましたが正式名称っていうわけじゃないです。

このメールのデータ、ある決まった書き方に従わなければいけません。 ブラウザへの出力も、HTTPのルールに従った書き方をしなければいけなかったのと同じですね。 で、メールの決まった書き方とは、添付ファイルとか無しで単純に文章を送る場合ですが、 次のようになります。

メールヘッダ(複数行も可)
(空行)
メール本文

HTTPのルールと凄く似ていますね!ヘッダがあって、空行があって、本文です。 これが分かったら、あとは必要なヘッダですが、これは次のような感じになります。

From: (送信元メールアドレス)
To: (送信先メールアドレス)
Subject: (件名)
Content-Transfer-Encoding: 7bit
Content-Type: text/plain; charset="ISO-2022-JP"

必要に応じて、次のヘッダを足すこともできます。

Cc: (コピーを送るメールアドレス)
Bcc: (密かにコピーを送るメールアドレス)
Reply-To: (メールの返信先。Formより優先します)

( ) で囲ったところに情報を入れればOKです。 他のところは、変える必要はありません。 一応意味は、Content-Transfer-Encoding が メール本文のエンコード方式を指定しています。 Content-Type は普通のテキストですよ、HTMLメールじゃありませんよ、 文字コードは日本語ですよ、ということを表しています。

セキュリティのこと コレすごく重要です!

さて、メールを送るには次のデータを送ればいいんだっていうのは分かりました。

From: (送信元メールアドレス)
To: (送信先メールアドレス)
Subject: (件名)
Content-Transfer-Encoding: 7bit
Content-Type: text/plain; charset="ISO-2022-JP"

メール本文

Perlで、メールアドレスや件名が変数に入っていると、次のような感じでデータを送ることになります。

From: $from
To: $to
Subject: $subject
Content-Transfer-Encoding: 7bit
Content-Type: text/plain; charset="ISO-2022-JP"

メール本文

ここで…とても重要なことがあります。このように 単純に変数をメールヘッダに埋め込むと、 セキュリティ上、とても問題があるんです。 というのは、$fromや$to、$subjectに改行が含まれていると、とんでもないことになるんです!

$fromや$toに改行が入っているはずは無い、なーんてことはありません。 改行を含んだメールアドレスや件名なんて存在しませんので盲点なのですが、 改行を含んだメールアドレスや件名をCGIプログラムに送ることはできてしまいます。

では、改行が入っているとどんなことが起きるのか、見てみましょう。例えばこの例で、 $subjectの内容が次のようなものだったとします。

※未承諾広告 XXX売ります(ここで改行\n)
Bcc: user1@xx.co.jp, user2@xx.co.jp, user3@xx.co.jp, user4@xx.co.jp

この$subjectをそのまま入れると、メールヘッダは次のようになります。

From: (送信元メールアドレス)
To: (送信先メールアドレス)
Subject: ※未承諾広告 XXX売ります
Bcc: user1@xx.co.jp, user2@xx.co.jp, user3@xx.co.jp, user4@xx.co.jp
Content-Transfer-Encoding: 7bit
Content-Type: text/plain; charset="ISO-2022-JP"

メール本文

さて、このBccというのは、この人たちにもコッソリメールを送る、というものです。 Toの人には、Bccとして同じメールが誰に送られたのかということは分かりません。 この例で、どのようにとんでもないか、分かりましたか? そう、メール送信CGIプログラムは、注意して作成しないと、 迷惑メールの大量送信に悪用される可能性があるんです! 悪用されないようにToを固定にするのは基本ですが、それで安心しちゃいけないんです。

では、このようなことを避けるにはどのように対策すれば良いのかというと、 単純に、メールヘッダに埋め込む変数については、 改行が含まれている異常なデータだったらエラーにする、 あるいは、改行を取り除く、などの方法があります。 具体的には、改行を取り除く方法なら、次のようにします。

# メールヘッダに埋め込む変数全てでこれを行います
$subject =~ s/\r|\n//g;
$to      =~ s/\r|\n//g;
$from    =~ s/\r|\n//g;

s//というのは文字列の置換をする演算子で、(変数) =~ s/\r|\n//g とすると改行を取り除く処理になります。

メール送信プログラムを作るときは、ヘッダーに埋め込む変数の内容について、 セキュリティ上の対策をする!このことを忘れないようにしましょうね。 (このようにセキュリティ上問題がありえる変数に対して対策をすることを「汚染チェック」といいます。)

メールアドレスの記述ルール

メールヘッダの中にはメールアドレスを書くところがありますが、 メールって、差出人のところにメールアドレスじゃなくて名前が表示されますよね? あれ、どうやってやっているのかと思って、 メールヘッダの中のメールアドレスの書き方のルールを調べてみました。

メールヘッダの中のメールアドレス指定部分は、次のような書式になります。

名前 <メールアドレス>

こうやって、名前を指定することができるんでした。 ということで、差出人のお名前も入力できるようにしましょう! メールアドレスは < > で囲むというのもポイントです。

さあ作りましょう

これで基本的なところはおさえたみたいです。 プログラムを作っちゃいましょう!

作成したCGIプログラム

sendmail.cgi
#!/usr/bin/perl

require 'getformdata.pl';

# sendmailのパス
$sendmail = '/usr/sbin/sendmail';

# フォームデータの取得
%form = plab::getformdata();

# 分かりやすいように、個々の変数にコピーします
$name    = $form{'name'};
$from    = $form{'from'};
$subject = $form{'subject'};
$message = $form{'message'};

# メールの送信先は固定です。
# " でなく ' で囲むことに注意!
$to = 'info@perl-labo.org';

# Fromメールアドレスがからっぽだとエラーになりますので、
# もしからっぽなら仮に送信先アドレスを入れます
if ($from eq "") {
	$from = $to;
}

# sendmailをパイプで書き込みオープンします
if (! open(MAIL, "| $sendmail -t")) 
{
	# sendmailの起動ができませんでした
	# パスが間違っているのかも?
	print "Content-type: text/html\n";
	print "\n";
	print "メールの送信に失敗しました。";

	# CGIプログラムを終了します
	exit;
}

# 汚染チェック
$name    =~ s/\r|\n//g;
$from    =~ s/\r|\n//g;
$to      =~ s/\r|\n//g;
$subject =~ s/\r|\n//g;

# メールデータを作ります
# ヒアドキュメントといいます
$mailtext = << "EOM";
From: $name<$from>
To: <$to>
Subject: $subject
Content-Transfer-Encoding: 7bit
Content-Type: text/plain; charset="ISO-2022-JP"

$message
EOM

# パイプを通してsendmailにデータを渡します
print MAIL $mailtext;

# パイプを閉じます
close MAIL;

print "Content-type: text/html\n";
print "\n";
print "メールを送信しました。ありがとうございました";

# CGIプログラムを終了します
exit;

実行結果

適当にメッセージを入れて送信しちゃってください (^^ 送信先は当サイト管理人ですので、なにかメッセージなど頂けると嬉しいです。

お名前(任意):
メールアドレス(任意):
件名(任意):
本文(任意):
 

解説

動きました

無事動きました。といっても、あらかじめ研究していただけの知識では足りなくて いろいろ試してこうなりました。説明していないところを解説します。

コメント

だいぶプログラムが長くなって分かりにくくなってきました。 そんなに難しいことはしなくていいはずだったんですが、 初心者には結構こんがらがります。 今までプログラムにコメントを入れることは無かったんですが、 結構細かくコメントを入れてみました。 コメントは # から後ろの部分で、この部分はなにを書いても 実行されるときには無視されます。 なので、メモをあれこれ書いておくのに便利ですね。

From:のメールアドレスについて

From:ですが、注意があります。 どうやら、メールアドレスをからっぽにすると メールの送信が正しく行われないみたいなんですね。 でも、メールアドレスは省略可能にしたいですよね。 なので、メールアドレスがからっぽだった場合は、 仮のアドレスとして送信先メールを入れるようにしてみました。

" と ' の違い

これまで、文字列は " で囲むっていうようにしてきました。 でもなぜか、連想配列の名前を指定するときは ' にしてきました。 実際は、この2つは同じように文字列を現すので、 連想配列で " を使ってもいいし、print文で ' を使ってもよかったのでした。

でもこの2つには非常に大きな違いがあります。 " は、中の文字列の中に変数名があると、それを変数の値に自動的に変えてくれます。 一方、' はそのような変換機能はありません。

さて、$to に自分のメールアドレスを入れるところで、" でなく ' を使っています。 これは、気分でそうしたわけではなくて、" だとエラーになってしまうんです。 というのは、" は中に変数名があるとそれを変換してくれますが、 変数というのは $ @ % などの記号で始まります。" で囲んだ中にこれらの文字があると、 それを変数名だと思って、変換してしまうのです。 さてメールアドレスは info@perl-labo.org です。 これを " で囲むと、そうです、@perl のところを変数だと判断して、勝手に メールアドレスを書き換えてしまうのです。これでは困りますので、 メールアドレスは必ず ' で囲むようにしなければいけないのです。

文字列の中にそういった記号が含まれていないなら、" と ' のどちらを使っても 構わないのですが、こういった、意思に反した変換が起きてエラーになるのを防ぐために、 変換が必要のない場合はできるだけ ' を使うようにした方がいいです。 ということで、連想配列の名前を指定するときに ' を使っていたのは、 それなりに正しいことだったと言えそうですね。

if (! open 〜 について

open文でsendmailを開きますが、ここでエラーが起こる場合があるようです。 というのは、最初の方で設定しているsendmailのパスが間違っていた場合です。 エラーが起きたことをどうやって知るかというと、open文の戻り値で判定できます。

openは、成功するとtrue(真)という値を返します。 失敗するとfalse(偽)というものを返します。 この、true、falseというのは真偽値というもので、主に成功/失敗を表すのに 使われます。

次に、! というものですが、これは演算子です。 演算子というのは、値になにか変化を加えて別の値に変える働きを持っていますが、 ! の働きは、true/falseを反転させるというものです。 否定ともよばれます。 !〜 で 〜でない、という意味になります。 !true は true で無いということで false に等しく、 !false は falseで無いということで true に等しいです。

さて、if文は if (〜) という形で、〜のところが 成り立っていたら、という意味でした。成り立っていたら、というのは あまり正確ではなくて、厳密には、〜がtrue(真)ならば、です。 そして if (! 〜) ですが、これは、もし〜がtrueでないならば、 となります。つまり もし〜がfalseならば、です。 そして if (! open 〜 ですが、openは成功したらtrue、 失敗したらfalseを返すので、もしopenがfalseなら、 もしopenが失敗したら、という意味になります。 ということで、openが失敗したらエラーメッセージを表示して 終了するようになっています。

exit

エラーが起きたところで、exit というものがでてきました。 これは、ここでプログラムを終了させる、というものです。 今まで、プログラムは頭から順にファイルの終わりまで 実行されていました。ファイルの途中で終わりたい場合は、 exit を使います。ファイルの終わりで自動的に終了するので、 ファイルの終わりに exit を書く必要はありません。 が、今回はファイルの終わりにも書いてみました。なんとなく。

ヒアドキュメント

途中で、「<< "EOM";」というものがでてきました。 これは、次の行から、EOM という文字列が現れるまでを、改行を含めて、 1つの文字列として扱う、という意味をもつものです。 ヒアドキュメントと呼ばれます。 EOM の部分はなんでも構いません。他のものに変えても問題ないです。 ここでは、End Of Messageという意味で EOM としました。 End Of File で EOF、End Of String で EOS などいろんな流儀があるようです。

今まで、print文で文字列を出力するときは、1行ずつprint文で出力していました。 しかし、今度のようにデータが長い場合(今回は8行なのでそれほどでもありませんが)、 1行ずつprint文を使っていたら大変です。見た目も分かりにくくなってしまいます。 そういうときは、<<を使って、まとめて処理すると楽です。 変数 = << という形で変数に文字列を代入するのにも使えますし、 print << という形でprint文と組み合わせて使うこともできます。

あと「<< "EOM";」と似ているものに「<< 'EOM';」があります。 ヒアドキュメントも1つの文字列を表すという点では " や ' で囲った文字列と なんら変わりありません。ですので、" の場合は中の変数が展開されるけど ' の場合は 展開されないっていうのがここであてはまります。 今回のプログラムでは、中に変数があるので " となりますが、 そうで無い場合は ' を使うのがいいです。

件名のエンコードについて

実はメールの配信ルールでは件名(Subject)には全角文字を入れることができません。 というのは、件名は7bit文字で表さなければならないと決まっているからです。 えっ?日本語の件名のメールもよくするしよくもらうけど、と思いますよね。 それは、実は、BASE64という仕組みを使って、件名を アルファベットと数字の組み合わせにエンコードしてから送っているのです。 そのメールを受信したメールソフトが、件名を元の日本語にデコードしているのです。 ここでは sendmail を使用していますが、sendmailはそこまでは面倒を見てくれないらしく、 本当は、その処理を自分でやらなきゃいけません。…いけないのですが、 テストしたらBASE64にエンコードしなくてもちゃんと日本語の件名のメールが 受信できたので、今回はやっていません (^^;

これで一応、説明していなかった部分も説明が終わりました。 んー大変だった…。

今後の改良予定

メールを送るっていうのも便利な機能です。 今回作ったプログラムはまたどこかで使うことになるのではないでしょうか。 ですから、関数にして、plibパッケージに入れて、 再利用しやすいものにしたいと思っています。 でも、そのためには、どういう形式にしたら一番使いやすいか、汎用的か、 といったことを考えないといけないので、また今度の研究テーマとしてとっておきます。 どうしたら一番便利かなー、と考えるのも楽しいものです。

分かったこと

沢山学びました (^^

  1. メールを送るには、sendmailというプログラムを利用します。
  2. openで | を使ってパイプを通したプログラムオープンができます。
  3. openは成功するとtrue、失敗するとfalseを返します。
  4. sendmailの -t オプションは、送り先をメールヘッダから読み取るように指示するものです。
  5. print ファイルハンドル データ; でそのファイルにデータを出力します。
  6. close ファイルハンドル でそのファイルを閉じます。
  7. ファイルハンドルは自分で名付けますが、大文字で書くのが通例のようです。
  8. メールのデータは、ヘッダ+改行+本文です。
  9. ヘッダには、必要なものとして From, To, Subject, Content-Transfer-Encoding, Content-Typeがあります。
  10. ヘッダは必要に応じて、Cc, Bcc, Reply-To を追加してもいいです。
  11. ヘッダのメールアドレス部分は 名前<メールアドレス> とします。
  12. コメントは # から後ろです。
  13. Fromメールアドレスが空欄だとメールを送れないようです。
  14. Fromメールアドレスにスペースが入っているとなんしかエラーです。
  15. " と ' には中の変数を展開するか否かという違いがあります。$ @ % など。
  16. ! は否定です。
  17. if (〜) は正確には 〜がtrueならば です。
  18. if (! 〜) は 〜でないならば、〜が失敗したならば、です。
  19. exit でプログラムの途中でプログラムを終了させることができます。
  20. << でヒアドキュメントというものが使えます。
  21. ヒアドキュメントのときも " と ' の違いが有効です。
  22. メールの件名はBASE64でエンコードしなくてはいけません。本当は。
Perl/CGI研究室 'PERL-LABO' TOPへ
戻る(History.Back)

Copyright (c) 'PERL-LABO' All Rights Reserved.  リンクフリーです。