Email::SenderでSMTPサーバから外部ドメインへメール送信できなかった件

http://gihyo.jp/dev/serial/01/modern-perl/0020?page=3
※こちらの内容を元にしたコードを使っていると仮定

Email::Sender::Transport::SMTP を使って「外部ドメイン」へ送信したら…

そのままだとエラーで送信出来なかった。
メールサーバ*1での設定でなんとかなるみたいだったのだが、
そっちはいじりたくなかったので認証使ってどうにかしようとしたときのメモメモ。



まずはEmail::Sender::Transport::SMTPで普通に認証

ユーザ名とパスワードを設定したけど、認証エラーで送信出来ない。

    my $transport = Email::Sender::Transport::SMTP->new(
      host => '192.168.0.xx',
      port => 587,
      sasl_username => $user,
      sasl_password => $pass,
    );

まずは telnetSMTP接続してみる

しかし、そこでユーザ/パスワード入れても最初は失敗><

失敗原因その1

ユーザ名が「ユーザ名@ドメイン」という形式でないとダメだった。

失敗原因その2

base64エンコードを以下のコードで実行していたが

perl -MMIME::Base64 -e 'print encode_base64("tori243@example.com\0tori243@example.com\0pass");'

これだと「"」で囲っているため、
「@」が配列とみなされていた。かなり初歩的なミス…
とりあえずこの2つの原因を取り除いたら
telnetではうまく行った!*2

Net::SMTPから接続してみる

perlから最小限コードでSMTP認証してみたところ、
こちらは依然失敗してしまう。
ログを確認すると「CRAM-MD5」認証で失敗している様子。




※ここで盛大にハマる




まず、大きな勘違い

Windowsのメールクライアントからは「CRAM-MD5認証」のチェックが入っていて問題なく送信されていた。
なので、そもそもメールサーバが「CRAM-MD5認証」できていると思い込んでいた。が…

Windowsメールクライアントのプロトコルログを確認

すると、CRAM-MD5の認証は「失敗」してた!
メールクライアントの挙動を見てみると
「CRAM-MD5失敗」→「再度plainログイン」
という動作をしていたようだ。
 

Email::Sender::Transport::SMTPはNet::SMTPを利用しているので

「Net::SMTPからPLAIN認証したい」のだが、ソースを見てみると
Authen::SASLのmechanismを指定する所がなさそうなんですが…(自動判定っぽい)
http://cpansearch.perl.org/src/GBARR/libnet-1.22/Net/SMTP.pm

sub auth {
  my ($self, $username, $password) = @_;

  eval {
    require MIME::Base64;
    require Authen::SASL;
  } or $self->set_status(500, ["Need MIME::Base64 and Authen::SASL todo auth"]), return 0;

  my $mechanisms = $self->supports('AUTH', 500, ["Command unknown: 'AUTH'"]);
  return unless defined $mechanisms;

  my $sasl;

  if (ref($username) and UNIVERSAL::isa($username, 'Authen::SASL')) {
    $sasl = $username;
    $sasl->mechanism($mechanisms);
  }
  else {
    die "auth(username, password)" if not length $username;
    $sasl = Authen::SASL->new(
      mechanism => $mechanisms,
      callback  => {
        user     => $username,
        pass     => $password,
        authname => $username,
      }
    );
  }

...(以下略)


なので、今回は直接Net::SMTPのソースをいじって対応しました。*3

これで

Email::Sender::Transport::SMTPから認証がうまく行ったよ!
やほい


※参考URL
http://nai.homelinux.net/telnet_smtp_auth.html
http://yoosee.net/d/archives/2004/08/25/002.html

*1:qmail

*2:plain認証

*3:本当はダメダメなやり方なんだけど、うまい方法思いつかなかった…