仙石浩明の日記

2010年11月13日

iモード.net と imoten を使って iモードメールを自動送信する 〜 SubEtha SMTP のバグ hatena_b

iモードメールを Android 端末で送受信するには、 sp モードiモード.net を利用する。 前者は sp モード専用の APN (Access Point Name) spmode.ne.jp に接続しないと利用できない (つまり無線LAN 経由では使えない) 上に、 ドコモが IMEI (International Mobile Equipment Identity, 端末識別番号) をチェックしていて、 契約端末でないとこの APN に接続できない (らしい)。 なので私は後者を利用している。

iモード.net は、 PC から iモードメールを読み書きできる Webメールサービス (月額210円)。 PC の代りに Android 端末上のアプリ (iMoNi が有名) からアクセスすれば、 Android 端末で iモードメールを読み書きできる。 あるいは iモード.net からメールを読み取って別のメールアドレス (例えば Gmail) へ転送するプログラムを (どこかのサーバで) 動かしておけば、 iモードメールを Android 端末で普通のメールとして受信できる。

前者の方法は、 メールの新着をどうやって知るか、 という問題がある。 Android 端末上のアプリが定期的に iモード.net にアクセスして新着をチェックすることになるが、 電池の消耗を考えればせいぜい 15分に一度チェックするのが限界で、 それ以上の頻度でチェックするのは現実的ではない。 つまり最大 15分間は新着を知るのが遅れる。 iMoNi にはドコモから送られてくる WAP PUSH を検知してメールをチェックしにいく機能もあるが、 3G 接続中は (なぜか) WAP PUSH を受け取れないらしい。

それに対し後者の方法は、 転送プログラムは Android 端末上で動くわけではないので、 電池のことを心配せずに iモード.net を頻繁にチェックできる。 iモード.net で取得したメールを Gmail へ転送するようにすれば、 Android 端末は Gmail の着信をリアルタイムで知ることができる。

前者の方法は Android 端末だけで iモードメールを読み書きできるのに対し、 後者の方法は転送プログラムを動かすサーバ (24時間稼働が望ましい) が必要になるので万人向けではないが、 そういったサーバを確保できるのなら、 iモードメールを普通のメールと同様に扱える後者の方法が圧倒的に便利。

というわけで、 私は imoten (imode.netのメールをSMTPで転送するプログラム) を使っている (まだ使い始めて 2日だが)。 1分も遅れずに Android 端末で着信を知ることができるので、 とても便利。

imoten には iモードメールを送信する機能もある。 すなわち imoten が SMTP サーバとして振る舞い、 受け取ったメールを iモード.net へ転送する。

ということはつまり Android 端末から iモードメールを送ることができるのはもちろん、 PC 上のメーラや任意のメール配信プログラムから iモードメールが送ることが可能。 試しにテストプログラムを書いてみた:

#!/usr/bin/perl
use strict;
use warnings;
use Net::SMTP;

my $smtp = Net::SMTP->new('senri.gcd.org', Port => 42525, Debug => 1);
$smtp->auth('imoten', 'xxxxxxxx');
$smtp->mail('sengoku');
$smtp->to('sengoku@gcd.org');
$smtp->data();
$smtp->datasend("To: sengoku\@gcd.org\n");
$smtp->datasend("\n");
$smtp->datasend("A test message\n");
$smtp->dataend();

imoten を動かしているサーバ senri.gcd.org の 42525 番ポートに SMTP 接続してメールを送信するプログラム。 imoten は SMTP PLAIN 認証をサポートしているので、
「$smtp->auth('imoten', 'xxxxxxxx');」 で、
ユーザ 「imoten」、 パスワード 「xxxxxxxx」 (伏字) を指定している。

ところが!

実行すると imoten が SMTP 認証エラー 「535 Authentication failure.」 を返してきた。 imoten のログ logs/imoten.log は以下のような感じ:

2010-11-12 19:35:01,150 [org.subethamail.smtp.server.Session-/192.168.1.15:59221] WARN  SendMailBridge  - SMTP 認証エラー User moten/ Pass imoten♦xxxxxxxx
2010-11-12 19:35:01,152 [org.subethamail.smtp.server.Session-/192.168.1.15:59221] WARN  MyWiser  - SMTP認証が必要です

「User moten/ Pass imoten♦xxxxxxxx」 (「♦」 は値が 0 の 1バイトデータ) と出力しているのが意味不明。 正しくは 「User imoten/ Pass xxxxxxxx」 と出力されるべきところ。

クライアント (上記テストプログラム) と、 サーバ (imoten) 間の SMTP セッションを調べてみると、

<<< 220 senri.gcd.org ESMTP SubEthaSMTP
>>> EHLO localhost.localdomain
<<< 250-senri.gcd.org
<<< 250-8BITMIME
<<< 250-AUTH PLAIN LOGIN
<<< 250 Ok
>>> AUTH PLAIN aW1vdGVuAGltb3RlbgB4eHh4eHh4eA==
<<< 535 Authentication failure.

クライアントが送信している 「aW1vdGVuAGltb3RlbgB4eHh4eHh4eA==」 は、 以下のように 「imoten♦imoten♦xxxxxxxx」 を base64 エンコードしたものなので RFC 4616 で規定されている PLAIN SASL 的に正しいと思われる。

% echo -n aW1vdGVuAGltb3RlbgB4eHh4eHh4eA== | nkf -mB
imoten♦imoten♦xxxxxxxx

すると怪しいのはサーバ側なのだが、 imoten は subethasmtp (SubEtha SMTP is an easy-to-use server-side SMTP library for Java) を利用して SMTP サーバとしての機能を実現している。 ということは SubEtha SMTP のバグ?

SubEtha SMTP のソースをざっと眺めてみると、 src/org/subethamail/smtp/auth/PlainAuthenticationHandlerFactory.java で、

    byte[] decodedSecret = Base64.decode(secret);
        ...
    this.username = new String(decodedSecret, 1, usernameStop - 1);
    this.password = new String(decodedSecret, usernameStop + 1,
                               decodedSecret.length - usernameStop - 1);

などと書いてある。 あちゃ〜 「String(decodedSecret, 1, ...」 などと 「1」 をハードコーディングしてる〜。 だから 「User moten」 などと最初の一文字が欠けたのか。 decodedSecret が
「♦imoten♦xxxxxxxx」 などと 1バイト目が ♦ であることを前提としているようだ。
「♦imoten♦xxxxxxxx」 という形式も RFC 4616 的に間違いではないが、
「imoten♦imoten♦xxxxxxxx」 という形式を解釈しないのは明らかにバグ。

ちょこっと書き換えてみた:

diff -ur subethasmtp-read-only/src/org/subethamail/smtp/auth/PlainAuthenticationHandlerFactory.java subethasmtp/src/org/subethamail/smtp/auth/PlainAuthenticationHandlerFactory.java
--- subethasmtp-read-only/src/org/subethamail/smtp/auth/PlainAuthenticationHandlerFactory.java        2010-11-12 20:27:18.421299702 +0900
+++ subethasmtp/src/org/subethamail/smtp/auth/PlainAuthenticationHandlerFactory.java        2010-11-12 20:54:28.928012476 +0900
@@ -82,8 +82,17 @@
                         if (decodedSecret == null)
                                 throw new RejectException();
 
+                        int authnameStop = -1;
+                        for (int i = 0; (i < decodedSecret.length) && (authnameStop < 0); i++)
+                        {
+                                if (decodedSecret[i] == 0)
+                                {
+                                        authnameStop = i;
+                                }
+                        }
+
                         int usernameStop = -1;
-                        for (int i = 1; (i < decodedSecret.length) && (usernameStop < 0); i++)
+                        for (int i = authnameStop+1; (i < decodedSecret.length) && (usernameStop < 0); i++)
                         {
                                 if (decodedSecret[i] == 0)
                                 {
@@ -91,7 +100,7 @@
                                 }
                         }
 
-                        this.username = new String(decodedSecret, 1, usernameStop - 1);
+                        this.username = new String(decodedSecret, authnameStop + 1, usernameStop - authnameStop - 1);
                         this.password = new String(decodedSecret, usernameStop + 1,
                                         decodedSecret.length - usernameStop - 1);
                         try

最初の 「♦」 までは authname として読み飛ばし、 その次 (authnameStop + 1) から username として読み込む。

SubEtha SMTP をビルドして、 できた target/subethasmtp-UNVERSIONED.jar を imoten の immf/lib にコピーし、 immf/build.xml を変更して imoten をビルド。 できた imoten.jar を実行して前述のテストプログラムを走らせてみる:

<<< 220 senri.gcd.org ESMTP SubEthaSMTP null
>>> EHLO localhost.localdomain
<<< 250-senri.gcd.org
<<< 250-8BITMIME
<<< 250-AUTH PLAIN LOGIN
<<< 250 Ok
>>> AUTH PLAIN aW1vdGVuAGltb3RlbgB4eHh4eHh4eA==
<<< 235 Authentication successful.
>>> MAIL FROM:<sengoku>
<<< 250 Ok
>>> RCPT TO:<sengoku@gcd.org>
<<< 250 Ok
>>> DATA
<<< 354 End data with <CR><LF>.<CR><LF>
>>> To: sengoku@gcd.org
>>> 
>>> A test message
>>> .
<<< 250 Ok

無事 SMTP 認証が成功して、 ドコモケータイから送信した iモードメールと全く見分けがつかないメール (送信者アドレスは伏字にした) が宛先 sengoku@gcd.org に届いた:

Return-Path: <xxxxxxxxxxxxxxxx@docomo.ne.jp>
Received: from mail123.docomo.ne.jp (HELO docomo.ne.jp) (203.138.203.197)
  by senri.gcd.org with SMTP; 12 Nov 2010 12:10:04 +0000
Received-SPF: pass (senri.gcd.org: SPF record at docomo.ne.jp designates 203.138.203.197 as permitted sender)
X-P0F-Info: 203.138.203.197 is-a HP-UX [11.00-11.11], link "ethernet/modem", 11 hops away
Date: Fri, 12 Nov 2010 21:10:00 +0900 (JST)
From: xxxxxxxxxxxxxxxx@docomo.ne.jp
To: sengoku@gcd.org
Subject: 
Message-ID: <IMTB1uBD14508a510B$q@docomo.ne.jp>
MIME-Version: 1.0
Content-Type: text/plain; charset="iso-2022-jp"
Content-Transfer-Encoding: 7bit

A test message
Filed under: Android — hiroaki_sengoku @ 08:19

No Comments

No comments yet.

RSS feed for comments on this post.

Sorry, the comment form is closed at this time.