Aug 31

はじめまして。
KLabにインターンでお邪魔させていただいている nakatani-s です。

インターンで製作しているアプリの中で、GmailのSMTPサーバにOAuth認証で入る部分があります。

こちら
にあるように、GmailはIMAP・SMTPともにOAuth認証に対応しているのですが、OAuth+IMAPのサンプルコードはあってもOAuth+SMTPのサンプルって中々見つからないんです。
そこで、OAuth+SMTP in Gmail のシンプルなPHPコードを公開して、少しでも世のお役に立てたらなと思うわけです。
いえ、僕が探した範囲なので本当はあるかもしれませんよ?でも恥ずかしいので知っている方も生温かい目でスルーしてください。

開発中にたくさんの助言・アイディアを下さり、インターンの身にこのような機会を与えてくださったKLabの方々には頭が上がらない思いです。

目次

  1. 必要な環境
  2. 下準備
  3. 動作確認
  4. コードと解説
  5. まとめ
  6. 参考

必要な環境

PHPが動くサーバが必要です。サーバのルートに自分でファイルが置ける必要もあります。

下準備

まずはGoogleに、サーバをOAuthのConsumerとして利用するための申請をし
ましょう。これはこちらのページを参考にしました。
OAuth Consumer Key と OAuth Consumer Secret は後で使うので、忘れないようにしておいて下さい。

次に、GoogleオフィシャルのOAuth+IMAPのプログラムを動かしてみます。
Gmail IMAP and SMTP using OAuth - Libraries and SamplesのPHP Sampleをダウンロード・解凍し、 xoauth-php-samples/common.php を開きます。
先ほど仕入れた OAuth Consumer Key と OAuth Consumer Secret の情報を、以下の部分に加えて下さい。

$THREE_LEGGED_CONSUMER_KEY = 'your_consumer_key';  // ここと
$THREE_LEGGED_SIGNATURE_METHOD = 'HMAC-SHA1';
$THREE_LEGGED_CONSUMER_SECRET_HMAC = 'your_consumer_secret';  // ここ

それが終わったら xoauth-php-samples/ ディレクトリ以下のファイルを全て自分のサーバにアップロードし、ブラウザ上から three-legged.php にアクセスしてみて下さい。
表示されたページでエディットボックスにご自分のGmailアドレスを入力し、実行ボタンを押すと、GoogleのOAuth認証画面にリダイレクトされます。

これで、”Total messages: X” と表示されたら、ここまではOKです。

ちなみに、僕はここで入力するアドレスをtypoしたせいで、社員の方を巻き込み6時間は悩みましたw

動作確認

こちらのPHPコードをダウンロードしていただき、エディタで開いて下さい。

190-195行目を、ご自分の環境に合わせて編集します。

  $from = 'your_account@gmail.com'; /* OAuth認証で使ったアカウントを指定 */
  $namefrom = 'your_name';          /* ご自由に */
  $to = 'someone@someserver.foo';       /* 送り先 */
  $nameto = 'someone';              /* 送り相手の名前 */
  $subject = 'test mail';           /* 件名 */
  $message = 'OAuth+SMTP test';     /* 本文 */

これが終わったら、サーバ上の common.php や three-legged.php と同じディレクトリ上に置いて下さい。ブラウザから three-legged-smtp.php にアクセスすると、指定した送信先にメールが届くはずです。

コードと解説

three-legged-smtp.php の解説をさせていただきます。
お気づきな方はお気づきでしょうが、ほぼ three-legged.php と同内容です。
内部で行われている処理の流れを簡単に説明すると、

  1. OAuth認証をし、Googleから認証情報をもらう(1-113行目)
  2. もらった認証情報を使ってssl通信を開始し、SMTPプロトコルで通信する権利を得る(114-187行目)
  3. SMTPプロトコルでメールを送る(190-212行目)
  4. Googleとのコネクションをクローズする(124-126行目)

という感じです。
この「OAuth認証をし、Googleから認証情報をもらう(1-113行目)」と「もらった認証情報を使ってssl通信を開始し、SMTPプロトコルで通信する権利を得る(114-187行目)」の部分が、OAuth+IMAP通信を行っていた three-legged.phpとほぼ共通なので、ほぼ同内容となっています。

さて、元のコードから追加・変更・削除した部分を説明します。

22-23行目: インクルードファイルの変更

require_once 'Zend/Mail/Protocol/Smtp.php'; /* Imap.php -> Smtp.php */
// require_once 'Zend/Mail/Storage/Imap.php';  /* 必要無し */

121-123行目: ssl通信時に送るURLを、SMTP用のものに変更

  $url = 'https://mail.google.com/mail/b/' .
       $email_address .
       '/smtp/';                /* imap -> smtp */

167-216行目: コメント参照。
要は、Googleのドキュメントに従ってssl通信をしているだけです。

  /* GmailのSMTPサーバと通信するためのsocketを開く */
  $smtpServer = "tls://smtp.gmail.com";
  $port = "465";                /* 465番ポートでうまくいかなかったら587番も試してみてください */
  $timeout = "45";
  $localhost = $_SERVER['REMOTE_ADDR'];
  $newLine = "\r\n";

  $smtpConnect = fsockopen($smtpServer, $port, $errno, $errstr, $timeout);

  $smtpResponse = fgets($smtpConnect, 4096);

  /* SMTP+OAuthの認証手続き */
  /* http://code.google.com/intl/ja/apis/gmail/oauth/protocol.html に規格があります */
  fputs($smtpConnect, "HELO $localhost". $newLine);
  $smtpResponse = fgets($smtpConnect, 4096);

  fputs($smtpConnect,"AUTH XOAUTH" . $newLine);
  $smtpResponse = fgets($smtpConnect, 4096);

  fputs($smtpConnect, $initClientRequestEncoded . $newLine);
  $smtpResponse = fgets($smtpConnect, 4096);

  /* 認証終了後にメールを送る */
  $from = 'your_account@gmail.com'; /* OAuth認証で使ったアカウントを指定 */
  $namefrom = 'your_name';          /* ご自由に */
  $to = 'someone@someserver.foo';       /* 送り先 */
  $nameto = 'someone';              /* 送り相手の名前 */
  $subject = 'test mail';           /* 件名 */
  $message = 'OAuth+SMTP test';     /* 本文 */

  fputs($smtpConnect, "MAIL FROM: <$from>" . $newLine);
  $smtpResponse = fgets($smtpConnect, 4096);

  fputs($smtpConnect, "RCPT TO: <$to>" . $newLine);
  $smtpResponse = fgets($smtpConnect, 4096);

  fputs($smtpConnect, "DATA" . $newLine);
  $smtpResponse = fgets($smtpConnect, 4096);

  $headers = "MIME-Version: 1.0" . $newLine;
  $headers .= "Content-type: text/html; charset=iso-8859-1" . $newLine; /* charsetはお好みで */
  $headers .= "To: $nameto $to1" . $newLine;
  $headers .= "From: $namefrom <$from>" . $newLine;

  fputs($smtpConnect, "To: <$to>\r\nFrom: $from\r\nSubject: $subject\r\n$headers\r\n\r\n$message\r\n.\r\n");
  $smtpResponse = fgets($smtpConnect, 4096);

  fputs($smtpConnect,"QUIT" . $newLine);
  $smtpResponse = fgets($smtpConnect, 4096);
  fclose($smtpConnect);

218-245行目: OAuth+IMAPでのメール受信に固有の部分をコメントアウト

  /* IMAP用の認証なので必要無し */
  /**
   * Make the IMAP connection and send the auth request
   */
  /* $imap = new Zend_Mail_Protocol_Imap('imap.gmail.com', '993', true); */
  /* $authenticateParams = array('XOAUTH', $initClientRequestEncoded); */
  /* $imap->requestAndResponse('AUTHENTICATE', $authenticateParams); */

  /* 以下、GMailからメールを受信して表示する部分なので必要無し */
  /**
   * Print the INBOX message count and the subject of all messages
   * in the INBOX
   */
  /* $storage = new Zend_Mail_Storage_Imap($imap); */

  /* include 'header.php';  */
  /* echo '<h1>Total messages: ' . $storage->countMessages() . "</h1>\n"; */

  /**
   * Retrieve first 5 messages.  If retrieving more, you'll want
   * to directly use Zend_Mail_Protocol_Imap and do a batch retrieval,
   * plus retrieve only the headers
   */
  /* echo 'First five messages:
    '; */ /* for ($i = 1; $i <= $storage->countMessages() && $i <= 5; $i++ ){ */ /* echo '
  • ' . htmlentities($storage->getMessage($i)->subject) . "
  • \n"; */ /* } */ /* echo '
'; */

まとめ

いかがでしたか? Google Code に置いてあるコードをベースに、OAuth+SMTP認証ができました。
しかし、このコードでは Google からOAuth認証情報を得る部分が PHP のZend Framework の内部に隠されてしまっています。
インターン中は時間がないのですが、なるべくいろいろな言語に流用できるように認証情報取得部分もホワイトボックス化したいと思います。
もしできたら、僕の個人ブログにでもコードを書きたいと思っています。

最後までお読み下さりありがとうございました。

ソースコード

http://lab.klab.org/young/wp-content/uploads/data/code/three-legged-smtp.php.txt

参考

Oct 31

takada-atです。こんにちは。
amo-k先輩に「自分SMTPコマンドなんて打ったことないッス」と言うと、「おまえも打てよ、な?」と、まるで後輩に煙草を薦める不良の先輩のような調子で、SMTPコマンドを打つようにすごまれました。
というわけで今日は、telnetとSMTPコマンドを使い、メーラーになった気持ちでメールを送信してみます。

SMTPといっても何だかわからないという方もおられるでしょうが、SMTPは「Simple Mail Transfer Protocol(単純なメール転送プロトコロル)」の略であり、メール送信のために定められた手続きのことです。要するに「この決まりを守っていればメールを送受信できるよー」というきまりのことです。
どんなメーラーもメールサーバーも基本的には、SMTPに従った動作を実装しています。
Wikipediaの記事にリンクをはっておきます。
-Simple Mail Transfer Protocol - Wikipedia

最新のSMTPプロトコルは、RFC 5321で標準化されています。
わたしも全部読んだことはないのですが、以下にリンクを載せておきます。RFC5321の日本語訳は見つけられませんでしたが、旧版のRFC 821には複数の日本語訳があるようです。
-RFC 5321 - Simple Mail Transfer Protocol
-RFC日本語版リスト

SMTPでメールを送信するには、メールサーバーにtelnetでアクセスし、SMTPコマンドを送っていけばよいようです。
実際にやってみましょう。
同期の honda-h に「ランチに行きませんか」というメールを出してみます。
↓honda-h

以下入力したコマンドを、C:からはじまる行に、サーバーからの返答をH:からはじまる行に書きます。

まずtelnetコマンドを使いmail.example.comの25番ポートにログインします。
(アドレスはすべて架空のものです)。

# telnet mail.example.com 25
> 220 mail.example.com ESMTP

HELOコマンドを入力し、こちらのサーバー名を伝えます。

C:HELO localhost
H:250 mail.example.com

MAILコマンドを利用し、差し出しアドレスを伝えます。

C:MAIL FROM:takada-at@example.com
H:250 ok

RCPTコマンドを利用し、送り先アドレスを伝えます。

C:RCPT TO:honda-h@example.com
H:250 ok

メールの本文はDATAコマンドを使って送ります。「.」だけで終る行がメッセージの終了を意味します。
今回はマルチバイト文字を使わず、ローマ字で送ってみることにします。

C:DATA
H:354 Please start mail input.
C:
Subject: lunch
From: takada-at@example.com

honda-h san. gohan tabe ni ikimasyou.
.

H:250 Mail queued for delivery.

最後にQUITコマンドを利用し、コネクションを切断します。メールサーバーはちゃんと挨拶ができる子のようです。

C:QUIT
H:221 Closing connection. Good bye.

もう少し実験してみましょう。
HELO の際に、nothing.example.com と答え、noone@example.com という存在しないアドレスにメールを送ります。
DATA コマンド中の From: の値も、noone@example.com にしておきます。

220 mail.example.com ESMTP
HELO nothing.example.com
250 mail.example.com
MAIL FROM: takada-at@example.com
250 ok
RCPT TO: noone@example.com
250 ok
DATA:
354 Please start mail input.
Subject: aaa
From: noone@example.com
cccc

.
250 Mail queued for delivery.
QUIT
221 Closing connection. Good bye.

こういう入力の仕方でも、メーラーデーモンからのリプライが takada-at@example.com に届きました。
デーモンからの返信先は必ず MAIL FROMで指定したアドレスとなるようです。

以上です。簡単ながら、自分で打ってみると、ブラックボックスに見えていたメール送信の仕組みが、心で理解できた気がします。やったことのない方はぜひ一度試してみるとよいのではないでしょうか。

Oct 31

amo-kです。
さて、SMTPクライント絡みでSMTPの話題です。
聞くところによるとtakada-at以外はSMTPコマンドを打ったことがあるそうです。
ということで今回はtakada-atにSMTPコマンドを使って手動でメールを送ってもらうことにします。

では、takada-at、よろしくお願いします~

Oct 18

amo-kです。phpでSMTPクライアント書きました~

書いてみたきっかけ

PHPだとmail()mb_send_mail()でメール送信できちゃうけどよくよく見てみると、SMTPコマンドのHELOコマンドやMAIL FROMコマンドに値を指定できないぽい。最後の引数に、MTAに渡すコマンドラインオプションを指定可能だが、これはMTAに依存するということだ。MAIL FROMコマンドの値を指定したくても、この最後の引数に指定するしかないということになる。

では、何処でHELOコマンドやMAIL FROMコマンドの値を設定しているかというと、php.iniのSMTPディレクティヴやsendmail_fromディレクティヴの値となる。(Windows版phpのみ)つまり、アプリケーションレベルでこれ等の値を指定したい場合に、明確に指定するIFが無いということだ。

あれやこれやと考える時間がもったいないので、この前HTTPクライアント書いたし、せっかくなのでSMTPクライアントも書いて見たw

コード:
Continue reading »