電子メールの送信


閲覧者がFORMに書いた内容や、それの処理された結果を 閲覧者やページ作者に電子メールで通知できると便利です。

この章ではUNIXのCGIから電子メールを送信する方法をいくつか説明します。 これらの方法はUNIX以外でのOSでのCGIではまったく使えません。


● CGIでメールを送る際の要注意事項

CGIでメールを送る方法の説明の前に、その潜在的危険性を説明しておきます。

CGIは通常 nobodyの権限で実行されます。そのCGIがメールを送信すると その差出人も nobodyになります。つまり誰がメールを送ったのかが 分からないメールが送られるのです。これは犯罪になります。
この犯罪の犯人の特定は、システム管理者ならばメール送信記録と ホームページアクセス記録を参照することで、送信CGIをわりだして 犯人を特定することができます。

他方、S-bitを立てたCGIや、SuExec環境下でのCGIでは メールの差出人は、そのCGIの所有者、つまりあなたの名義のメールになります。 すると今度は、世界中の人があなたの名義でメールを送る事ができるので その送り先や内容が意図せず変更されないように気を付けてください。

このような事実があることをよく認識してCGIによるメール送信を 行ってください。


● popen() 関数で mail コマンドとパイプ接続する

UNIX にはメールを送る簡単なコマンドに mailコマンドがあります。

% cat content.txt | mail -s 'Test Mail' someone@somewhere とすることで、ファイル content.txt の内容がメール本文になって 題名 Test Mail で someone@somewhere 宛に送られます。 ただし、日本語のメールを送る場合は漢字変換が必要です。後で説明します。

mailコマンドを利用してCGIからメールを送信します。

プログラム中から他プログラムを実行する方法は幾つかありますが mailコマンドの場合はメールの内容をmailコマンドの 標準入力に流し込まなくてはなりません。 それにはpopen()関数が便利です。

#define SUBJECT "Test Mail" #define ADDRESS "me@mydomain" char command[256]; snprintf( command, sizeof(command), "/usr/bin/mail -s '%s' %s", SUBJECT, ADDRESS ); FILE *pipe = popen( command, "w" ); SUBJECTとADDRESSの部分は各自で適切に調整してください。 なお、メールを自分に送る場合でも addressには domain名も含めた full address で記述するほうが無難です。

FORMなどから得られるデータから、 subject や address などの 実行コマンド名になる文字列を作る場合には十分に注意してください。 致命的なsecurity holeとなることがあります。 例えば次のようにFORMの第1項目と第2項目のデータで subject と address を含めたコマンド名を作るとしましょう。

snprintf( command, sizeof(command), "/usr/bin/mail -s '%s' %s", vvalue[0], vvalue[1] );

もし利用者が vvalue[1] に "someone@somewhere; do other thing" と なるようにFORMに書き込んだら、このCGIは "do other thing" のコマンドを 実行してしまいます。もしこのコマンドが、システムのパスワードファイルを その利用者に密告するものだったり、ファイルを改竄、破壊するものだったり、 はたまたサーバーに裏口ログインするためのデーモンを起動したりと、 非常に大変な災いを招きます。
このような危険性があるので、コマンドの引数には プログラム実行時に変更されないように固定した文字列を与えるべきです。 もしくはセミコロン;のような危険な文字が含まれていないかチェックするべきです。

これで mailコマンドがCGIのバックグランドで実行され、その標準入力 のストリームが変数 pipe に渡されます。つまりこの pipe ストリームに 書き込むことは mailコマンドに打ち込むこととほぼ同等なのです。
popen()が正常に実行されていないとpipeがNULLになっているので チェックしたらプログラムを緊急終了させてください。

ちなみに mailコマンドをCGIではなく普通にコマンドラインで 実行する場合には、メールの本文を書きこむ際に他のコマンドを実行 することができます。しかし、CGIのようなプロセスと繋げて起動する 場合にはそのようなことはできないようになっているはずです。 なぜなら、もしCGIからの入力でmailコマンドが任意のコマンドを 実行できたら重大なセキュリュティーホールとなるからです。

次にメール本文を作成して pipe に書き込みます。 これはファイルに書き込むのとまったく同じ操作でできます。

fprintf( pipe, "This mail is sent from CGI.\n" );

高水準ファイル書き込み関数がすべて使用できます。

メール本文を pipeに書き込み終えたら、 最後に . だけの行を書き込みます。

fprintf( pipe, ".\n" ); そして pclose() 関数で pipe を閉じます。

pclose( pipe );

これで自動的にメールが送信されます。
メールを他人に送る場合には礼儀としてページ作成者の名前を名乗るように してください。


● 日本語のメールを送信する

メールの中身で使える文字は7bitの文字だけなので、 日本語の文字はJISコードでなければなりません。前の章で漢字コード変換の 関数の作り方を解説しましたが、今回は nkf コマンドで済ませましょう。 nkfコマンドとmailコマンドを2重にパイプ接続して実行するのをC言語プログラムで 記述するのは面倒なので、nkf と mail をまとめた以下の簡単な sh script jmailを用意しましょう。

#!/bin/sh /usr/local/bin/nkf -j | /usr/bin/mail $* nkfとmailのPATHがこれで良いか確認しておいてください。このjmail コマンドをmailコマンドの代わりにpopen関数で実行すれば日本語の メールも簡単に送信できます。


● fork(),execl() 関数で mail コマンドと接続する

先のpopen()の方法は"初心者"むけの方法です。確かに動くのですが、 実行速度や効率、プログラマとして技術の向上を考えるなら別の方法を 使うべきです。

CGIのプロセスから子プロセスを派生し、そのプロセスをmailコマンドに したて、親プロセスであるCGIプロセスからパイプを繋げてメール本文を 渡すことにします。

以下のプログラムを参考にしてください。種々の関数の説明は省略します。

#define SUBJECT "Test Mail" #define ADDRESS "me@mydomain" int pid; int pipefds[2]; pipe( pipefds ); // Prepare pipe descriptors. if ( (pid=fork()) == 0 ){ // Child executes this region. dup2( pipefds[0], 0 ); // Copy the input-side of the pipe to the stdin. close(pipefds[0]); close(pipefds[1]); // Close descriptors. // Change process to the mail process. execl("/usr/bin/mail", "mail", "-s", SUBJECT, ADDRESS, 0 ); // This line is executed if execl faults. printf("Error: Can not exec mail.\n"); exit(1); } // The parent process executes below. close(pipefds[0]); // Close the input-side of the pipe. // Convert the output pipe descriptor // to pipe stream. stream is easy for // many manuplations. FILE* pipe = fdopen( pipefds[1], "w" ); fprintf( pipe, "This mail is sent from CGI.\n" ); fprintf( pipe, ".\n" ); fclose( pipe ); waitpid(pid, NULL, 0); // Wait until the child exits.

● mailコマンドの代りにsendmail コマンドを使う

SendmailはUNIXのメール送信の根幹を担うプログラムです。 通常、sendmail はデーモンとして常駐し内外のメールの配送を管理しています。 そして mail コマンドなどの単純で使いやすいコマンドが ユーザーと sendmail との間を仲介してくれています。

sendmailをコマンドとしてメールの送信に使うこともできます。 mailコマンドを介さないだけ効率的です。相手に届くメールに付くヘッダー も少しまともになります。コマンドとしての使い方の違いは題名の指定の 仕方だけです。sendmailのコマンド引数には宛先のみ指定します。

execl("/usr/sbin/sendmail", "sendmail", ADDRESS, 0 );

入力の冒頭で題名を指定します。

fprintf( pipe, "Subject: " SUBJECT "\n"); 以後は mail コマンドと同じです。


● TCP/IP ソケットで sendmail デーモンと通信する

メールを送る方法の中でもっとも高速で華麗な技です。 mail server で走る sendmail daemon のプロセスと internet domain socket で接続して、SMTPのプロトコルに 従って交信してメールを送信します。

ただしその扱いは非常に面倒です。しかも高度な技術なだけに 重大なネットワーク犯罪に応用できます。
この技術を用いた犯罪の犯人を特定することはシステム管理者 ならばなんとか可能ですが、被害者が犯罪に巻き込まれている ことに気が付きにくい悪質な犯罪が可能です。
そのような技術なのでこの場でその方法を紹介するわけには いきません。秘密にします。あしからず。m(_ _)m


目次

Copyright(C) by Naoki Watanabe. Oct 21st, 1995.
渡辺尚貴 naoki@cms.phys.s.u-tokyo.ac.jp