解説
SMTP認証
SPAMメールや迷惑メールなどの対策として、メール送信時にSMTP認証が必要とされるケースが多くなっています。従来多用されていた方式はPOP Before SMTP方式で、SMTP送信前にメールサーバーにPOP3アクセスを行った後(すなわちPOP3での認証後)に、メールを送信を行う方法でした。
しかし、大手のインターネット・サービス・プロバイダ(以下、ISP)を中心に迷惑メールの対策として、OutBound Port25 Blockingを実施して接続元ISPのメールサーバーを利用しないメール送信(Port25)を拒否するケースが増えてきています。もちろん、こうしたISPの多くは、モバイル環境や外部の異なる環境からのメール送信を一切禁止するのではなく、異なるポートでSMTPプロトコルにいくつかの拡張を行ったSMTP認証である「Simple Authentication and Security Layer」(以下、SASL)を行うことでメール送信を可能としています。
Indy 10は、SASLとしてLOGINのみではなく、CRAM-MD5やMD5-DIGESTといった暗号化したロジックを用いた認証方式もサポートするようになりました。SASLの実装には、TIdSMTPの他にTIdSASLLogin、TIdUserPassProviderコンポーネントが必要になります。Indy 10ではコンポーネントパレットに「Indy SASL」というタブが加わり、この中に上記のコンポーネントが含まれています。具体的には次のように記述します。
SMTP := TIdSMTP.Create(nil); Login := TIdSASLLogin.Create(SMTP); Provider := TIdUserPassProvider.Create(Login); Login.UserPassProvider := Provider; Provider.UserName := 'xxxxxxxx'; Provider.Password := '*******'; SMTP.SASLMechanisms.Add.SASL := Login; SMTP.AuthType := atSASL;
認証に使用されるUsernameとPasswordは、TIdUserPassProviderのプロパティに記述します。また、TIdSMTPのオブジェクトインスペクタ、プロパティタブのSASLMechanismをダブルクリックすると[Editing SASL List for SMTP]が開き、SASL認証順位の編集が可能です。上記の実装例では下から2行目で.Add.SASLとしてLogin(すなわち、TIdSASLLogin.Create(SMTP))を指定しています。
SMTP over SSL/TLS
SMTP over SSL/TLSとは、メールの送信時における「メール通信経路上の暗号化」を実現する仕組みとして、経路上(ユーザーのPCからメールサーバーまで)をSSL(Secure Socket Layer)によって暗号化して通信を行う機能です。SSL/TLSプロトコルを自分の手で実装しようとするには大変な労力と時間が必要となりますが、OpenSSLというフリーのライブラリを使うことによって容易にSSL/TLSの利用環境を構築できます。本稿では、IndyでSSL/TLS機能を実現するために、以下のDLLバイナリを利用します。
上記Zipファイルから「Libeay32.dll」と「ssleay32.dll」という2つのDLLが得られるので、プログラム実行フォルダにコピーします。なお、OpenSSLの最新バージョンでビルドする場合には、上記ZIP内の「New SSL libraries.txt」を参考にしてバイナリを生成してください。Indy 10のTIdSMTPのUseTLSプロパティは、SSL/TLSを使用するか否かの他、使用する際のハンドシェークの方式について、utUseExplicitTLSモードとutUseImplicitTLSモードが選択できます。
- ExplictTLSモードは、クライアントからSMTPサーバーへの応答要求とその応答までを平文で通信を行い、サーバーからの応答受信後にクライアントからSSL/TLSハンドシェイクを開始して、SSL/TLSセッション確立後にログインを開始する方式です。すなわち、非暗号化状態で接続を開始し、ユーザー名とパスワードを検証する直前にセキュアなデータ接続を行う方式です。
- ImplicitTLSモードは、いきなりクライアントからSSL/TLSハンドシェイクを開始して、SSL/TLSセッションを確立後にログインを開始する方式です。すなわち、クライアントはサーバーへの要求をセキュアな状態で送信します。
メールサーバーが両方のモードをサポートしているとは限らないことから、実際にはUser ⇔メールサーバー間での動作確認が必要ですが、Indy 10のヘルプで確認する限りでは、IETF(Internet Engineering Task Force)は、Explicit TLSの使用を推奨しているとの記述がなされています。
日本語の処理
Indyには、TIdMessageというSMTPやPOP3といったメッセージベースのプロトコルで使われるクラスがあり、このTIdMessageは、MIME(Multipurpose Internet Mail Extension)をサポートしています。しかしながら、基本的に日本語文字コードの変換処理はサポートしていません。すなわち、ヘッダ内の日本語処理では、MIMEのB encodeやQ encode、RFC2231形式への変換処理が、また、本文(Body)の日本語処理には、ShiftJIS→JISの変換が必要です。
また、半角カタカナ文字やマル数字などのISO-2022-JPで使えない文字の処理、あるいは、1行の文字数の処理もメール送信側の処理として必要になります。本稿のプログラムでは、B encodeへの変換処理とShiftJIS→JIS変換処理については、既存のライブラリユニットであるjconvert.pas、およびコンポーネントTPzConvを用いて動作確認を行いました。なお、1行の桁数処理については一部(後述)を除いてサポートをしていません。
以下は、jconvert.pasの利用した場合の利用例です。
Msg := TIdMessage.Create(SMTP); Msg.From.Name := jConvert.CreateHeaderString(edtFrom.Text); Msg.From.Address := jConvert.CreateHeaderString(edtReply.Text); //※注1 Msg.Recipients.EMailAddresses := edtTO.Text; Msg.CCList.EMailAddresses := edtCC.Text; Msg.BccList.EMailAddresses := edtBCC.Text; Msg.Subject := jConvert.CreateHeaderString(edtSubject.Text); //Msg.Body.Text := memBody.Lines.Text; // ※注2 Msg.Body.Text := jConvert.ConvertJCode(memBody.Lines.Text, JIS_OUT);
ヘッダ部分で、日本語コードあるいは日本語コードである可能性のある項目については、B encode変換を行うCreateHeaderStringメソッドを使用し、本文(Body)部分はShiftJIS→JIS変換を行うConvertJCodeメソッドを使用して変換処理をしています。また、TPzConvを使用する際には、B encode処理にはMIMEHeaderEncodeメソッドを、JIS変換にはSJisToJisメソッドを使用します。
注1は送信者(From)のメールアドレスであり、本来日本語コードとはならない箇所ですが、TIdMessageが生成するヘッダーは、「NAME<MAIL ADDRESS>」の形式に合成されることから、NAME部分と同様の処理をしておくことにしました。また、注2のようにJIS変換処理を行わないでメールを送信すると、本文は「Content-Type: text/plain Content-Transfer-Encoding: 8bit」として処理されます。
ESMTP(Extended SMTP)の存在や多くのメールクライアントソフトウェア(以下、メーラー)では、8bitでも文字化けせずに表示ができてしまうため、Windows上での使用に際しては問題なく使えてしまう可能性もあります。しかし、LAN限定ならばともかくも、インターネット上での送信であればルールに従い、JISコードの変換処理を行うべきでしょう。JISコード変換を行った場合には、「Content-Type: text/plain Content-Transfer-Encoding: 7bit」と処理されるようになります。ただし、「charset = ISO-2022-JP」は明示されていません。そこで、上記例に別途、Msg.CharSet := 'ISO-2022-JP';を加えて試してみましたが、ヘッダ部分に「charset = ISO-2022-JP」は明示されませんでした。
CreateStringHeader2という拡張されたメソッドが含まれていました。また、III.から得られるjconvert.pasは独自に開発されたアプリケーションの中で改変されたものがソースとして添付されており、携帯端末へのメール送信が考慮され、特に1行の文字数(桁数)の処理について配慮がなされています。1行の桁数が多くなるようなケースでは、III.から得られるユニットを使用するとよいでしょう。日本語名の添付ファイルの処理
ヘッダ部分の日本語コードの使用は、MIMEのB encodeやQ encodeへ変換することで対応できましたが、パラメータ部分に日本語コードは使用できません。ここで問題となるのが、添付ファイルの名前に日本語を使った場合です。MIMEでは、Content-Disposition:フィールドのfilename=パラメータでファイル名を示します。1997年に、パラメータにASCII以外の文字コードの使用を規定したRFC2231が公開されましたが、多くのメーラーが添付ファイル名における日本語の取り扱いに関して、ISO-2022-JPやShiftJIS、あるいはMIME B encodeを使って日本語のファイル名を設定しています。受信側のメーラーがどの方式に対応しているかにもよりますが、添付ファイルの日本語処理はRFC2231方式に従うことが今後必要になってくると思われます。
Indyにおける添付ファイルの送信についての記述は次のようなコードになります。
hdAttach := edtAttach.Text; // 添付ファイル名(フルパス) // 添付ファイルの処理 with TIdAttachmentFile.Create(Msg.MessageParts,hdAttach) do begin FileName := jConvert.CreateHeaderString(ExtractFileName(hdAttach)); end;
上記は、jconvertユニットのCreateHeaderStringメソッドを利用してMIMEのB encodeで日本語ファイル名を処理したものです。前述したようにRFC2231の規定に基づけば誤った方法であることは承知の上ですが、B encode方式の変換を行ったケースが最も多くのメーラーで問題なくデコードすることができました。ちなみに、FileNameを何らかの文字コード変換をせず(つまり、ShiftJISのまま、ファイル名「日本太郎.txt」を例として)設定した場合、MIMEの表示は次のような状況でした。
Content-Type: application/octet-stream;
name="日本太郎.txt"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
filename="日本太郎.txt"
日本語のファイル名はShiftJISのままであり、Indyではパラメータの部分においても文字コードの変換が必要であることが分かります。RFC2231方式の変換をサポートするDelphi用コンポーネントも散見されますが、日本国内のメーラーの対応がマチマチであり、相手方のメーラーを確認する手段がないケースでは、添付ファイルには日本語のファイル名を使わない方が無難と考えられます。
Indy 10における問題点
本稿に添付したプログラムでメールを送信してみると判明しますが、ヘッダ内のDateの表示に問題があります。問題となるのはUT(GMT)からのオフセットを示すプラスマイナスの数値文字列(例えば、JSTであれば +0900)が常に+0000と表記されるという点です。
TIdMessageにはDateプロパティがあり、任意にDateを記述できる一方、UseNowForDateプロパティをTrue(デフォルト)にすることで、カレントのローカルタイムを自動的に取り込むようになっています。従って本来ならばDateに関して特別にコードを記述する必要は無く、日本国内のパソコンから送信を行えば +0900 の記述を伴ったDate表示になるはずです。
ですが、残念ながらうまくいきませんでした。+0000となる主な弊害は、受信側のメーラーで当該メールが表示された場合の日時が9時間進んで表示されてしまう可能性がある点です。この問題はJSTだけではなく他の国からも同様な問題点が指摘されており、開発元であるIndy Projectの改善が待たれています。
Synapseを利用したSMTP機能の実現
Synapseは機能的にはIndyと非常に類似したフリーのライブラリで、ユニットの集合で提供されています。Zipファイルで提供されますが適当なフォルダに解凍して、当該フォルダをDelphiのライブラリパスに追加します
上記のIndyと同様な機能を得るために、以下のSynapseユニットを使います。
- SMTPSend.pas
- MimeMess.pas
- SynaChar.pas
- ssl_openssl.pas
これらのユニットをプログラムの実行フォルダにコピーします。そしてプロジェクトに追加し、Uses節にユニット名を記述してください。
また、SSL/TLS機能を実現するためにOpenSSL環境を構築しておく必要があります。Windows用のインストーラである「Win32_OpenSSL_v0.9.7i.exe」を起動し、デフォルトのままインストールを実行します。
Synapseでは日本語文字コードの変換にiconv.dllを使用するので、プロジェクトの起動フォルダにコピーしておきます。
以上でSynapse上での必要な機能は使えるようになるはずです。上述のIndy 10での実装方法と比較してみましょう。メール送信のプロシージャを以下に示します。
// ----- メール送信 ----- procedure TForm1.MailSender; var StrList : TStringList; hdAttach : string; Mime : TMimeMess; SMTP : TSmtpSend; begin StrList := TStringList.Create; Mime := TMimeMess.Create; SMTP := TSmtpSend.Create; try SMTP.FullSSL := True; // Explicit ※注1 //SMTP.AutoTLS := TRUE; // implicit SMTP.TargetPort := edtPort.Text; //SSL:465 Synapseでは文字列 ※注2 SMTP.UserName := edtUserID.Text; SMTP.Password := edtPassword.Text; SMTP.TargetHost := edtServer.Text; //SMTP LOGIN if not SMTP.Login then exit; //※注3 StrList.Add(memBody.Lines.Text); //本文 //※注4 Mime.Header.CharsetCode := ISO_2022_JP; //※注5 //送信者名<メールアドレス>形式で記述 //※注6 Mime.Header.From := edtFrom.Text; Mime.Header.ReplyTo := edtReply.Text; Mime.Header.ToList.Add(edtTo.Text); Mime.Header.CCList.Add(edtCC.Text); Mime.Header.Subject := edtSubject.Text; Mime.AddPartMultipart('', Nil); Mime.AddPartText(StrList, Mime.MessagePart); //添付ファイル hdAttach := edtAttach.Text; if hdAttach <> '' then Mime.AddPartBinaryFromFile(hdAttach, Mime.MessagePart); //※注7 // MIMEエンコード Mime.EncodeMessage; //SMTP.送信 if not SMTP.MailFrom(edtFrom.Text, Length(Mime.Lines.Text)) then exit; if not SMTP.MailTo(edtTo.Text) then exit; if not SMTP.MailTo(edtBCC.Text) then exit; // BCCはここに入れる ※注8 if not SMTP.MailData(Mime.Lines) then exit; //SMTP.Logout; finally SMTP.Logout; StrList.Free; Mime.Free; SMTP.Free; end; end;
注1の箇所ではSSL/TLSの方式を設定します。
Implicitモードであれば、SMTP.AutoTLS := True;、Explicit モードであれば、SMTP.FullSSL := True;とします。
ただし、Win32 OpenSSL環境が必要ですが、特にDLLなどを用意する必要はなく、Windowsの環境変数にWin32 OpenSSLへのパスが通っていれば問題なくコンパイル可能です。
注2はポートの設定です。
Synapseでは数値ではなく文字列で指定します。
注3の箇所でログインしています。
SMTP認証はSynapseがすべて自動的に処理します。具体的にはSMTPsend.pasのfunction Login: Booleanが司る機能であり、SMTPのセッションを行うにあたり、SMTPかESMTPか、Authコマンドを用いてAuth認証が可能か否か、ログインの方式としてどの形式(例えば、CRAM-MD5)で認証できるのか、といった一連の操作を自動的に行います。従って、プログラムとしては、Booleanの戻り値でログインできたか(True)、できなかった(False)を管理すればよいことになります。
注5はISO-2022-JPを指定しています。
プログラム的には日本語文字について何らの変換処理もしていません。Synapseは日本語文字コードの変換にiconv.dllを使用します。iconv.dllは日本語のみならず多くの文字コードに対応しており、また、メールに関連した機能の中でも暗号化(例えば、GnuPG)などにも利用されています。日本語処理の実際ですが、Synapseでは基本的に日本語文字コードの変換が自動的に行われます。ただし、ヘッダではMIME Q encodeに変換しています。本文は、自動的にJIS変換が行われ、「Content-Type: text/plain charset = ISO-2022-JP」と明示されます。従って、受信側で特にMIME Q encode のサポートに問題が発生しないのであれば、Synapseでは日本語文字コードの変換を意識しないでコーディングすることが可能です。
注6は差出人の指定とReplyのパスの指定です。
Header.Fromは、「氏名(ニックネーム)<メールアドレス>」という記述方法がサポートされています。例えば、「送信試験<test@xxxxxx.com>」という日本語文字列、さらに半角スペースを入れた「送信 試験<test@xxxxxx.com>」といった記述をしてもQ encodeで正常に変換されました。
注7は添付ファイルの指定です。
日本語の添付ファイル名については、残念ながらRFC2231方式にはならず、Q encode変換を自動的に行っていました。
注8はBCCの設定です。
Indy 10ではMIMEにBCCを記述しますが、SynapseではMIMEにBCCのリストが組み込めません。Indy 10のようにBCCもMIMEに組み入れてしまえば、メッセージのソースにBCC情報が含まれたまま相手方に到達することになります。つまり、送信側でメッセージソースを管理するには便利かもしれませんが、受信側でBlindになるか否かは受信側のメーラーに委ねることになります。
一方、SynapseではMIMEにはBCCを組み入れずに、送信時にTO/CCとは別にBCC宛送信を行うという方法でBCCに対応しています。edtBCC.Textがブランクであっても、MailTo(BCC)をスキップさせる必要もありませんでした。
