こんにちは
おそらく、SMTPを介してPHPコードからメールを送信する必要があった人は誰でも、
PHPMailerクラスに精通している
でしょう 。
この記事では、追加パラメーターとして送信したいネットワークインターフェースのIPアドレスを受け入れるようにPHPMailerに教える方法について説明します。 当然、この機能は複数の白いIPアドレスを持つサーバーでのみ役立ちます。 また、ちょっとした追加として、PHPMailerコードからのやや不快なバグをキャッチします。
PHPMailerアーキテクチャの概要
PHPMailerパッケージは、同じ名前のフロントエンド(PHPMailerクラス)と、POP3事前認証など、SMTP経由でメールを送信する機能を実装するいくつかのプラグインクラスで構成されています。
PHPMailerフロントエンドは、メッセージパラメータ(localhost、return-path、AddAdress()、body、fromなど)を設定し、送信方法と認証方法(SMTPSecure、SMTPAuth、IsMail()、IsSendMail()、IsSMTP( )など)、およびSend()メソッド。
レターのパラメーターを設定し、送信方法(mail、sendmail、qmail、smtpから選択可能)を指定したら、PHPMailer Send()クラスメソッドを呼び出す必要があります。このメソッドは、何らかの方法でメールを送信する内部メソッドに呼び出しを委任します。 。 SMTPに興味があるため、主にclass.smtp.phpファイルのSMTPプラグインを検討します。
PHPMailer :: IsSMTP()メソッドを使用する場合、PHPMailer :: Send()メソッドは、保護されたPHPMailer :: SmtpSend($ header、$ body)メソッドを呼び出し、生成されたヘッダーとメッセージ本文を渡します。
PHPMailer :: SmtpSend()メソッドは、受信者のリモートSMTPサーバーへの接続を試み(これがPHPMailerオブジェクトによって送信された最初のメッセージでない場合、おそらく接続が既に確立されており、このステップはスキップされます)、標準SMTPセッションを開始します(HELLO / EHLO、MAIL TO、RCPT、DATAなど)。
SMTPサーバーへの接続は、パブリックメソッドPHPMailer :: SmtpConnect()で行われます。 一度に1つのドメインに対して異なる優先順位を持つ複数のMXレコードが存在する可能性があるため、PHPMailer :: SmtpConnect()メソッドは、PHPMailerの構成中に指定された各SMTPサーバーに順番に接続しようとします。
コードのバグ
次に、PHPMailer :: SmtpConnect()コードをよく見てください。
public function SmtpConnect() { if(is_null($this->smtp)) { $this->smtp = new SMTP(); } $this->smtp->do_debug = $this->SMTPDebug; $hosts = explode(';', $this->Host); $index = 0; $connection = $this->smtp->Connected();
コードで$ this-> smtpはSMTPプラグインクラスのオブジェクトです。
著者が何を念頭に置いていたかを理解しようとします。 まず、SMTPで機能する内部オブジェクトが作成されているかどうかを確認し、これがPHPMailerクラスのオブジェクトのSmtpConnect()メソッドの最初の呼び出しである場合に作成されます(実際、PHPMailer :: Close()メソッドは$ this-> smtpに変換できますnull)。
次に、PHPMailer :: Hostフィールドは、区切り文字「;」で分割されます 結果は、受信者ドメインのMXレコードの配列です。 Hostにエントリが1つしかない場合(「smtp.yandex.ru」など)、配列には要素が1つしかありません。
次に、受信者サーバーに既に接続されているかどうかを確認します。 これがSmtpConnect()の最初の呼び出しである場合、$ connectionがfalseであることは明らかです。
だから、私たちは最も興味深いことに到達しました。 サイクルはすべてのMXレコードで開始され、各反復で次のMXへの接続が試行されます。 しかし、このサイクルのアルゴリズムを頭の中で実行すると、最初のMXレコードについて次のように想像するとどうなりますか?($ this-> smtp-> Connect(($ ssl? 'Ssl://': '')。$ Host、$ port、$ this-> Timeout))falseを返しましたか? サイクルは、サイクルの後に既にキャッチされる例外をスローすることがわかります。 つまり 他のすべてのMXレコードの可用性はチェックされず、例外がキャッチされます。
しかし、これは最も不快ではありません。 PHPMailerは2つのモードで動作します-例外をスローするか、ErrorInfoフィールドにエラーメッセージを書き込むことで静かに死にます。 サイレントモードを使用する場合($ this-> exceptions == false、これがデフォルトモード)、SmtpConnect()はtrueを返します!
一般に、このバグには時間がかかり、開発者に
通知されます。 バージョン5.2.1で気付きましたが、古いバージョンでも同じように動作します。
先に進む前に、クイックフィックスを紹介します。 開発者からの公式リリースまで、私は彼と一緒に住んでいます。 今月は飛行が正常です。
public function SmtpConnect() { if(is_null($this->smtp)) { $this->smtp = new SMTP(); } $this->smtp->do_debug = $this->SMTPDebug; $hosts = explode(';', $this->Host); $index = 0; $connection = $this->smtp->Connected();
PHPMailerを拡張して、複数のネットワークインターフェイスで動作するようにします
PHPMailerのSMTPプラグインは、fsockopen、fputs、fgetsを介してネットワークで動作します。 マシン上にインターネットに接続するネットワークインターフェイスが複数ある場合、fsockopenはいずれの場合も最初の接続でソケットを作成します。 何でも作成できる必要があります。
最初に思いついたのは、標準的なソケットsocket_create、socket_bind、socket_connectの標準的な束を使用することでした。これにより、socket_bindで、IPアドレスにソケットを関連付けるネットワークインターフェイスを指定できます。 結局のところ、このアイデアは完全に成功しているわけではありません。 その結果、fputsとfgetsはsocket_createによって作成されたリソースを使用できないため、PHPMailer SMTPプラグインのほぼ全体を書き換えて、fputsとfgetsをsocket_readとsocket_writeに置き換えなければなりませんでした。 獲得したが、魂は堆積物のままだった。
次の考えがより成功したことが判明しました。 fgetsが安全に読み取れるストリームソケットを作成するstream_socket_client関数があります! その結果、SMTPプラグインの1つのメソッドを置き換えるだけで、PHPMailerにネットワークインターフェースの明示的な指示でメールを送信するように教えることができ、同時に実際には開発者のコードに手を触れません。
プラグインは次のとおりです。
require_once 'class.smtp.php'; class SMTPX extends SMTP { public function __construct() { parent::__construct(); } public function Connect($host, $port = 0, $tval = 30, $local_ip) {
実際、Connect()メソッドの実装も最小限に変更されています。 ソケットを直接作成する文字列のみが置き換えられ、署名に別のパラメーター(ネットワークインターフェイスのIPアドレス)が追加されます。
このプラグインを使用するには、PHPMailerクラスを次のように拡張する必要があります。
require_once 'class.phpmailer.php'; class MultipleInterfaceMailer extends PHPMailer { public $Ip = ''; public function __construct($exceptions = false) { parent::__construct($exceptions); } public function IsSMTPX($ip = '') { if ('' !== $ip) $this->Ip = $ip; $this->Mailer = 'smtpx'; } protected function PostSend() { if ('smtpx' == $this->Mailer) { $this->SmtpSend($this->MIMEHeader, $this->MIMEBody); return; } parent::PostSend(); } protected function SmtpSend($header, $body) { require_once $this->PluginDir . 'class.smtpx.php'; $bad_rcpt = array(); if(!$this->SmtpConnect()) { throw new phpmailerException($this->Lang('connect_host'), self::STOP_CRITICAL); } $smtp_from = ($this->Sender == '') ? $this->From : $this->Sender; if(!$this->smtp->Mail($smtp_from)) { throw new phpmailerException($this->Lang('from_failed') . $smtp_from, self::STOP_CRITICAL); }
MultipleInterfaceMailerクラスに新しいパブリックIpフィールドが追加されました。これは、メールを送信するネットワークインターフェイスのIPアドレスの文字列表現によって設定する必要があります。 また、IsSMTPX()メソッドが追加され、新しいプラグインを使用して文字を送信する必要があることを示しています。 PostSend()、SmtpSend()、およびSmtpConnect()メソッドも、SMTPXプラグインを使用するためにやり直されました。 この場合、使用手順もクラスインターフェイスも変更されていないため、MultipleInterfaceMailerクラスのオブジェクトを既存のクライアントコードで安全に使用できます。たとえば、sendmailまたは元のSMTPプラグインを介してメールを送信します。
以下は、新しいクラスを使用する小さな例です。
function getSmtpHostsByDomain($sRcptDomain) { if (getmxrr($sRcptDomain, $aMxRecords, $aMxWeights)) { if (count($aMxRecords) > 0) { for ($i = 0; $i < count($aMxRecords); ++$i) { $mxs[$aMxRecords[$i]] = $aMxWeights[$i]; } asort($mxs); $aSortedMxRecords = array_keys($mxs); $sResult = ''; foreach ($aSortedMxRecords as $r) { $sResult .= $r . ';'; } return $sResult; } }
おわりに
要約すると:
- PHPMailerのバグを修正しました。これは、SMTPサーバーへの接続に失敗した場合でも、SmtpConnect()が常にtrueを返すためです。
- SmtpConnect()は、最初に成功する前に渡されたすべてのMXレコードを正直にチェックし始めました。
- 新しいプラグインが作成されました。このプラグインを使用すると、SMTP経由でメールを送信し、使用する送信サーバーのネットワークインターフェイスを明示的に指定できます。
- PHPMailerは、古いクライアントコードが新しいSMTPXプラグインを使用するために簡単に拡張されます。
お友達、頑張ってください!