EmacsでGnusを使ってGmailにアクセスしようとしたら起動と同時にEmacsが強制終了する

メールにはOutlookThunderbirdを使っていましたが、テキスト関連はEmacsでこなせるようになりたいと思い、Gnusを使ってみることにしました。
そこで、下記リンクを参考にGmailへのアクセスを試みましたが、

強制終了してしまう

M-x gnus とすると、Gnusのバッファは表示されるんですが、 以下のメッセージがミニバッファに表示され、Emacs自体が強制終了してしまいました。

nnimap (gmail) open error: ''.  Continue? (y or n)

f:id:aki2o:20140115101648p:plain

調査

とりあえずメッセージを出してる所を探す

メッセージを表示している関数は、gnus-start-news-serverでした。
ソースやネットで調べながら、関係ありそうな設定を変更したりして試行錯誤。
この時点で、ニュースリーダーの設定でならGnusが起動することは確認できましたが、それ以上進展せず。

強制終了する関数の特定

上記メッセージ以外のエラー情報がわからないため、edebugを使って関数呼び出しを調べると、

gnus-start-news-server → gnus-open-server → nnimap-open-server → nnimap-open-connection → nnimap-open-connection-1 → open-network-stream → network-stream-open-tls → open-gnutls-stream → open-network-stream → make-network-process → 強制終了となっていました。

make-network-processを調べても進展なし・・・。手詰まりな感じです。
コネクション張るのに、make-network-processは使えないってことか?他の方法・・・
などと考えながら、上記の呼び出し経路を辿っていたら、以下の箇所が目に留まりました。

(defun network-stream-open-tls (name buffer host service parameters)
  (with-current-buffer buffer
    (let* ((start (point-max))
       (use-builtin-gnutls (and (fboundp 'gnutls-available-p)
                    (gnutls-available-p)))
       (stream
        (funcall (if use-builtin-gnutls
             'open-gnutls-stream
               'open-tls-stream)
             name buffer host service))
・・・

gnutls-available-pという関数がtを返すことによって、open-gnutls-streamが呼び出されてます。
処理内容はよくわかりませんが、open-tls-streamを呼び出すようにしても問題なさそう。
で、open-tls-streamは見たところmake-network-processは使ってないようでしたので、
以下のアドバイスをかますことでopen-tls-streamが実行されるようにして、 M-x gnus を実行してみました。

(defadvice gnus (around disable-gnutls activate)
  (flet ((gnutls-available-p () nil))
    ad-do-it))

すると、以下のメッセージが表示され、今度は強制終了しませんでした。

nnimap (gmail) open error: 'depth=1 OU = generated by avast! antivirus for SSL scanning, O = avast! Mail Scanner, CN = avast! Mail Scanner Root'.  Continue? (y or n)

Avast!がエラーを吐いているようでした(Avast!はセキュリティソフト)。
あぁ~!セキュリティが邪魔してる系かぁ。よくあるパターンね!と、Avast!の保護を無効にしました。

解決かと思いきや・・・

今度は以下のメッセージが表示されました。

nnimap (gmail) open error: 'depth=2 C = US, O = GeoTrust Inc., CN = GeoTrust Global CA'.  Continue? (y or n)

・・・意味不明です。
とりあえずyとしてみると、Gnusは起動しましたが以下の警告が表示され、

Warning: Opening nnimap server on gmail...failed: depth=2 C = US, O = GeoTrust Inc., CN = GeoTrust Global CA
; Server nnimap+gmail previously determined to be down; not retrying; Opening nnimap server on gmail...failed: depth=2 C = US, O = GeoTrust Inc., CN = GeoTrust Global CA
; Server nnimap+gmail previously determined to be down; not retrying

サーバモードで「{nnimap:gmail}」を選択しても、以下のエラーが表示されGmailのラベルは表示されません。

f:id:aki2o:20140115133813p:plain

やはり、このメッセージが出力されるのはダメなようです。

調査継続

とりあえずメッセージをググってみると、opensslコマンドを使ったサーバ接続についてのWebページなどが出てきたので、 opensslでエラーなのかと思い、open-tls-streamが実行しているのと全く同じコマンドを、 コマンドプロンプトで実行してみました。

C:\>c:\App\cygwin\bin\bash.exe -c "openssl s_client -connect imap.gmail.com:993 -no_ssl2 -ign_eof"
CONNECTED(00000003)
depth=1 OU = generated by avast! antivirus for SSL scanning, O = avast! Mail Scanner, CN = avast! Ma
il Scanner Root
verify error:num=19:self signed certificate in certificate chain
verify return:0
---
Certificate chain
 0 s:/C=US/ST=California/L=Mountain View/O=Google Inc/CN=imap.gmail.com
・・・
* OK Gimap ready for requests from 60.238.174.26 e6if1770548oac.180

と表示され、正常に接続されていました。
edebugでopen-tls-streamをステップ実行してみましたが、やはり正常に接続されているようでした。
にも関わらず、gnus-open-serverがnilを返してしまうので上記メッセージが表示されます。
上記呼び出し経路を再度辿り、どこでnilになってしまうのか調べてみると、
nnimap-open-connection-1で、greeting変数の値が"[*.] \(OK\|PREAUTH\)"にマッチしていない場合、 接続できていないと判断してnilを返すようになっており、
今回の場合、greetingの値が「depth=1 OU = generated by avast! ...」でマッチしていないのが原因でした。

greetingに入るべき値

greetingという名称からして、サーバに接続した時のレスポンスを想定していると思われるんですが、 network-stream-open-tlsが返しているのは最初の一行だけのようでした。
しかし、実際にはopensslで接続した時のレスポンスは上記のようにとても長くなっており、接続をチェックする正規表現にマッチするのは最後の「* OK Gimap ready for requests from ...」の部分です。
そこで、とりあえず以下のアドバイスを定義することで、network-stream-open-tlsが返すgreetingの値をレスポンス全体に変更してみました。

(defadvice network-stream-open-tls (after replace-greeting-of-openssl activate)
  (let* ((proc (car ad-return-value))
         (greeting (or (ignore-errors
                         (with-current-buffer (process-buffer proc)
                           (buffer-string)))
                       (nth 1 ad-return-value)))
         (capabilities (nth 2 ad-return-value))
         (type (nth 3 ad-return-value)))
    (setq ad-return-value (list proc greeting capabilities type))))

今度こそ解決

M-x gnus すると、imapユーザ名を聞かれ、質問に答えていくとGmailのラベル取得に成功しました。

まとめ

Gnus関連の設定の最後に、以下の設定を加えることで解決しました。

(defun gnutls-available-p ()
  nil)

(defadvice network-stream-open-tls (after replace-greeting-of-openssl activate)
  (let* ((proc (car ad-return-value))
         (greeting (or (ignore-errors
                         (with-current-buffer (process-buffer proc)
                           (buffer-string)))
                       (nth 1 ad-return-value)))
         (capabilities (nth 2 ad-return-value))
         (type (nth 3 ad-return-value)))
    (setq ad-return-value (list proc greeting capabilities type))))

設定内容について

必要なのは、GnusからGmailにアクセスする際に以下の挙動をするようにすることです。

  • gnutls-available-pがnilを返す
  • network-stream-open-tlsの戻り値の2番目の値に、レスポンスの最後の「* OK Gimap ready ...」の部分を含める

当初、M-x gnus した時だけ挙動が変われば大丈夫かと思ったんですが、 Gnusのバッファから実行される他のコマンドでもGmailにアクセスしていてダメだったので、 とりあえず、常に挙動を変えるようにしてしまいました。

つまり、上記設定だと、GnusがGmailにアクセスする以外(例えば、全く別の拡張)で上記関数が使われた場合に、 期待しない動作になるかも知れないということです。

本当なら、GmailにアクセスするGnusの関数を調べて、 その各関数の実行時のみ上記の挙動をするように設定するべきですが、 私は面倒臭かったので現時点ではしませんでした。

その他

リンク

あとがき

ちなみに、network-stream-open-tlsってバグってるような気がするんですが・・・。

(defun network-stream-open-tls (name buffer host service parameters)
  (with-current-buffer buffer
    (let* ((start (point-max))
       (use-builtin-gnutls (and (fboundp 'gnutls-available-p)
                    (gnutls-available-p)))
       (stream
        (funcall (if use-builtin-gnutls
             'open-gnutls-stream
               'open-tls-stream)
             name buffer host service))
       (eoc (plist-get parameters :end-of-command)))
      (if (null stream)
      (list nil nil nil 'plain)
    ;; If we're using tls.el, we have to delete the output from
    ;; openssl/gnutls-cli.
    (when (and (null use-builtin-gnutls)
           eoc)
      (network-stream-get-response stream start eoc)
      (goto-char (point-min))
      (when (re-search-forward eoc nil t)
        (goto-char (match-beginning 0))
        (delete-region (point-min) (line-beginning-position))))
    (let* ((capability-command (plist-get parameters :capability-command)))
      (list stream
        (network-stream-get-response stream start eoc)
        (network-stream-command stream capability-command eoc)
        'tls))))))

処理の最初でpoint-maxをstart変数に保存してるってことは、空じゃないバッファを考慮してるんだよな。
で、「tls.elを使った場合はopenssl/gnutls-cliの出力を消去する」というコメントの処理。
ここで、point-minを起点にしてdelete-regionしてるけど、point-minじゃなくてstart変数を 起点にしないといけないんじゃなかろうか。
そして、delete-regionしたら、start変数は更新しないといけないんでは。

と、今日見ただけの奴がのたまってみる。
とりあえず解決したから良いか。

しかし、Gnus起動するだけで、このハマりよう・・・。
使い続けられる気がしないお・・・。