James君!〜ブログに投稿する


概説

さて、このネタは「MovableTypeを制覇する!」の関連企画だ。ブログと MovableType に詳しくなければ、まずそっちを読んでくれ。

で、ここでは2つの Mailet を作るが、両方とも「MovableTypeを制覇する〜Java だとこうなる」で紹介している、筆者作成の bloglib.jar を使う。これは、

から持ってきてくれたまえ。ここではこのライブラリのAPIはきっちり紹介しない。

作る Mailet は次の2つだ。

  1. PostMt:1件だけブログを登録し、メールを受けたらその内容を指定のブログに投稿できる Mailet
  2. MoblogServer: 何件でもブログを登録でき、ID に対応したブログに送られたメールの内容を投稿する。汎用的なモブログサーバを実現する Mailet。

2つ作ったのは、要するに、

携帯からブログ投稿はしたいのだけど、既存のモブログサーバに登録して投稿できるようにすると、個人情報とかセキュリティとか、気にかかる...

というあたりを心配する向きがあるのでは?ということである。「専用」ならばそういうことを気にしなくてもいいし、また「専用」ならばこそのサービスも可能になる。

で、メールの形式はこういうかたちになる。

Return-Path: <sug@shopmail.jp>
Received: from localhost ([127.0.0.1])
          by bizet (JAMES SMTP Server 2.2.0) with SMTP ID 330
          for <sug-mt@mydomain.jp>;
          Tue, 25 Apr 2006 14:13:27 +0900 (GMT+09:00)
Subject: Post from mailet
Date: Tue, 25 Apr 2006 14:13:27 +0900 (GMT+09:00)
From: sug@shopmail.jp
Content-Transfer-Encoding: 7bit
Content-Type: text/plain; charset=us-ascii
Mime-Version: 1.0
Message-ID: <24054760.1145942017001.JavaMail.sug@bizet>
                          ←ヘッダとメール本文を区切る空行
user: sug                 ←ユーザ名の指定
passwd: Bach1750          ←パスワードの指定
                          ←必須空行
This is a Post to Movabletype from Mail.  以下投稿本文
OK? Received at MobableType???

で、メールを受け付ければ、Bounce でその結果を知らせる。成功すればこういうメールが返る。

Return-Path: <>
X-Original-To: sug@shopmail.jp
Delivered-To: sug@shopmail.jp
Received: from bizet (localhost [127.0.0.1])
        by bizet.kobe-du.ac.jp (Postfix) with SMTP id 2731F6FD27
        for <sug@shopmail.jp>; Tue, 25 Apr 2006 14:13:37 +0900 (JST)
Message-ID: <28724842.1145942016986.JavaMail.sug@bizet>
Date: Tue, 25 Apr 2006 14:13:36 +0900 (GMT+09:00)
From: owner-PostMt@mydomain.jp  ←設定可能
To: sug@shopmail.jp             ←メール送り元
Subject: Re: Post from mailet   ←送信メールのタイトル
Mime-Version: 1.0
Content-Type: multipart/mixed;
        boundary="----=_Part_2_22549907.1145942016978"
Status: R

------=_Part_2_22549907.1145942016978
Content-Type: multipart/alternative;
        boundary="----=_Part_3_32533955.1145942016978"

------=_Part_3_32533955.1145942016978
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit

Post MovableType SUCCESS!   ←結果
------=_Part_3_32533955.1145942016978--

------=_Part_2_22549907.1145942016978  ←以下送信メールの控え
Content-Type: message/rfc822; name="Post from mailet"
Content-Disposition: attachment; filename="Post from mailet"

Return-Path: <sug@shopmail.jp>
Received: from localhost ([127.0.0.1])
          by bizet (JAMES SMTP Server 2.2.0) with SMTP ID 330
          for <sug-mt@mydomain.jp>;
          Tue, 25 Apr 2006 14:13:27 +0900 (GMT+09:00)
Subject: Post from mailet
Date: Tue, 25 Apr 2006 14:13:27 +0900 (GMT+09:00)
From: sug@shopmail.jp
Content-Transfer-Encoding: 7bit
Content-Type: text/plain; charset=us-ascii
Mime-Version: 1.0
Message-ID: <24054760.1145942017001.JavaMail.sug@bizet>

user: sug
passwd: Bach1750

This is a Post to Movabletype from Mail.
OK? Received at MobableType???
------=_Part_2_22549907.1145942016978--

ざっとこんなものである。

で、この Mailet は本質的には、

  1. init() でオプションを処理し、
  2. service() で受けたメールを投稿用ヘッダと本文に分離し、投稿用ヘッダを検証したのち、
  3. bloglib.jar 内の WeblogFactory.getEntryForm() で適切な投稿クライアントを取得して XML-RPC で送信し、
  4. その結果を Bounce で返信する。

という程度の流れのものである。プログラム的に見て面白いところは特にない。だから、ソースは掲示せずに、config.xml の書き方程度にとどめる。

あと、コンパイルだが、この Mailet は bloglib.jar を必須とする他に、実行時に

  1. xmlrpc-2.0.jar(XML-RPC 本体)
  2. xerces-2.4.0.jar(XMLパーサ)
  3. commons-codec-1.3.jar(Jakarta Commons の一つ。バイナリデータのデコードに要るようだ)

が必要になる。James なので、これらのライブラリは、build/SAR-INF/lib に入っていて、一緒に james.sar に固められる必要がある。だから、この Mailet はオプションであり、

<project name="james" default="compile" basedir=".">
   <!-- Caution!  PostMt.java and MoblogServer.java needs to compile bloglib.jar,
   and need to execute depends on xmlrpc-*.jar,xerces-2.*.jar, commons-codec-1.*.jar
    so default, 
   NOT COMPILE THESE!  If you compile PostMt and MoblogServer with jars, you MUST
   COMMENT OUT this line! -->
   <!-- 注意: PostMt.java と MoblogServer.java はコンパイルに bloglib.jar
   を要求し、実行時に xmlrpc-*.jar, xerces-2.*.jar, commons-codec-1.*.jar を
   必要とする。だから、デフォルトではこれらをコンパイルしない。
   もし、コンパイルするのならば、次行をコメントアウトすること-->
   <property name="src.excludes" 
     value="jp/or/nurs/sug/bayes/parser/SenParser.java,jp/or/nurs/sug/ja
mes/mailets/MoblogServer.java,jp/or/nurs/sug/james/mailets/PostMt.java" />

としてある。だから、必要なライブラリをゲットして、build/SAR-INF/lib に放りこんだ後、property 行をコメントにして、コンパイルしてくれ。

PostMt Mailet

この Mailet は1件だけのブログを、直接諸元を config.xml に書いて投稿できるようにするものだ。

config.xml の設定は、

<mailet match="RecipientIs=sug-mt@mydomain.jp" class="PostMt">
  <url>http://www.nurs.or.jp/~sug/mt/mt-xmlrpc.cgi</url>
  <api>MovableType</api>
  <blogId>1</blogId>
  <user>sug</user>
  <passwd>hogehoge</passwd>
  <setUserPasswd>true</setUserPasswd>
  <allowCreateCategory>true</allowCreateCategory>
  <passThrough>false</passThrough>
  <sender>owner-PostMt@mydomain.jp</sender>
</mailet> 
url
XML-RPC の受付URL。api が autodetect の場合は、ブログのトップページのURLでも良い。ただし、そのページ内に rsd.xml などをポイントした link タグがなければならない。ここらへんについては、「MovableTypeを制覇する!〜APIを自動判定しちゃう」を参照のこと。
api
XML-RPC のAPI。これは WeblogFactory で定義された識別子を使う。現状、MovableType、MetaWeblog、Blogger、autodetect が使える。もし、これが何のことか判らなければ、「MovableTypeを制覇する!」をしっかり読みなおしてくれ。省略時 MetaWeblog。autodetect はあまり推奨ではないが、一応使える。ただし、autodetect だとメールごとに RSD ファイルを読みにいくので、効率はめちゃくちゃ悪い。
blogId
ブログ識別子。デフォルト「1」。ただし api が autodetect の場合はこの値は使われない。
user
投稿ユーザ名。必須。メールの user 項目と突き合わせ、両者が一致していなければエラーにする。
passwd
投稿ユーザのパスワード(オプション)。これが有効になるのは、setUserPasswd が true の場合のみ。
setUserPasswd
true ならば、メールにパスワードを書かずに、ここで書いたパスワードを使う。false ならばメールの passwd 項目が必須であり、メールの passwd 項目と突き合わせて一致しなければエラーにする。デフォルト false
allowCreateCategory
もし杉浦がハックした sug.createCategory をそのMovableTypeがサポートしているのならば、新規カテゴリの生成を許可する。デフォルトは false。勿論、API が MovableType でない場合、MovableType でも createCategory のサポートがない場合には、このオプションは何の影響もない。
passThrough
false==パイプライン処理を打ち切る。true== 継続。デフォルト false。
sender
結果を送り元に bounce する際の Sender。省略すれば config.xml の postmaster。

で、この Mailet は config.xml の設定と、メール本文に書かれるメールごとの送信オプションの関係がややこしいので表にまとめる。
プロパティ名設定メールメールによる上書き説明
url××MT の mt-xmlrpc.cgi のURL
api××XML-RPC の使用 API
blogId××MT のブログ番号
user同一チェックMT の投稿ユーザ名。設定とメールとが一致しないと拒絶
passwd条件付で○MT の投稿パスワード。メールにパスワードを書きたくない時は、設定の setUserPasswd を true にすれば、設定の passwd を使うことになる。
setUserPasswd××設定の passwd をログインに使うか
allowCreateCategory双方trueカテゴリが存在しない場合、作成を許すか。設定&メール双方 true でないと、カテゴリ作成は許可しない
passThrough××お馴染みのもの

だから、メールの送信内容はフルオプションの場合次のようなかたちになる。

Subject: title 項目がなければこれを投稿タイトルとして使う 

title: 真のタイトル(省略時は Subject: ヘッダ)ただし、API が Blogger だったらセットできない。
user: ユーザ名(必須。config.xml 内の設定項目と一致する必要がある)
passwd: パスワード。もし config.xml のsetUserPasswd が true ならば、省略可。
category: カテゴリ名(もしAPIの上で有効でなければ無視)
allowCreateCategory: 新カテゴリだった場合に作成するか否か。デフォルト false。

投稿内容本文。

だから、メールの最小は、config.xml の setUserPasswd == true で既にパスワードが書かれている状態で、

Subject: title 項目がなければこれを投稿タイトルとして使う 

user: ユーザ名(必須。config.xml 内の設定項目と一致する必要がある)

投稿内容本文。

になる。

まあ、現実に運用するケースだと、メール送信元(sender)や、From: ヘッダの値などを Matcher で検証してハネる処理が必要になろう。そこらへんは Matcher を駆使してパイプラインを書いてくれ。

MoblogServer Mailet

これはDBを使って、複数のブログに対する投稿サービスを提供するための Mailet である。要するに、これ一つで「モブログサーバ」を運用できるようになるわけだ。

だから、この Mailet は以前作った「DB連携Mailet」の基底クラス AbstractDBMailet のサブクラスとして作る。細かいフィーチャーなどはそっちを参照してくれ。

DBの仕様はこんなところだろう。

create database moblog;
use moblog;
create table moblog (
    -> id varchar(20),      # Mailet の利用ID
    -> passwd varchar(20),  # Mailet の認証用パスワード
    -> url varchar(100),    # ブログのURL
    -> api varchar(20),     # ブログのAPI
    -> blogId varchar(20),  # ブログの識別子
    -> user varchar(30),       # ブログの投稿ユーザ名
    -> blogpasswd varchar(30), # ブログのユーザパスワード
    -> sender varchar(80),  # メールの送信元ホスト名指定
    -> account varchar(80), # メールを送信したメールアカウント
    -> regdate TIMESTAMP );
grant select on moblog.moblog to moblog@localhost identified by 'moblog';
insert into moblog values( 'idsug', 'hogehoge', 'http://www.nurs.or.jp/~sug/mt/mt-xmlrpc.cgi', 
    -> 'MetaWeblog', '1', 'sug', 'hoge1', 'mail.somedomain.jp', 
    -> 'sug@somedomain.jp', '2006-04-18 16:03:24' );

まあ、こういうサービスの場合は、api を autodetect にすべきではなかろう。登録の Webアプリの上で、

ブログのトップページ
もしくは...
ブログのXML-RPC の受け付けURL(CGIなど)
ブログの利用API
ブログの識別子(blogID)
登録

という風なかたちで登録者に尋ねて、そこで autodetect すべきだな。

なので、XML-RPC 関連の設定項目は、すべて正しい内容で DB に保存されていることを前提とする。それらは DB から引くだけだ。だから config.xml はかなり単純だ。

<mailet match="RecipientIs=moblog@mydomain.jp" class="MoblogServer">
  <datasource>moblog</datasource>
  <table>moblog</table>
  <key>nop</key>
  <attribute>result</attribute>
  <allowCreateCategory>true</allowCreateCategory>
  <passThrough>false</passThrough>
  <sender>owner-PostMt@mydomain.jp</sender>
</mailet>
datasource
使うデータソース(data-sources ブロックで定義されたもの)
table
テーブル名
key
特に使わず
attribute
結果ステータスを保存する Mail Attribute 名
allowCreateCategory
もし杉浦がハックした sug.createCategory をそのMovableTypeがサポートしているのならば、新規カテゴリの生成を許可する。デフォルトは false。勿論、API が MovableType でない場合、MovableType でも createCategory のサポートがない場合には、このオプションは何の影響もない。
passThrough
false==パイプライン処理を打ち切る。true== 継続。デフォルト false。
sender
結果を送り元に bounce する際の Sender。省略すれば Postmaster

で、アプリユーザ&パスワードと、ブログユーザ&ブログパスワードとは別立ての方がよかろう...ということで、DB 項目が id & passwd、user & blogpasswd の2本立てになっている。だから、ブログ側のユーザ名およびブログのパスワードはメール内容には現れない。こういうメールになる。

Subject: title 項目がなければこれを投稿タイトルとして使う 

title: 真のタイトル(省略時は Subject: ヘッダ)ただし、API が Blogger だったらセットできない。
id: モブログサーバのユーザID
passwd: モブログサーバのパスワード
category: カテゴリ名(もしAPIの上で有効でなければ無視)
allowCreateCategory: 新カテゴリだった場合に作成するか否か。デフォルト false。

投稿内容本文。

省略不可は結局「id」と「passwd」だけである。まあ、本当はセキュリティに気をつけるならば、メール本文の暗号化を更に考えるべきだろう。とりあえず、DBに保存される登録項目で、SMTP の HELO で与えられる「メールの送信元ホスト」と、SMTP の MAIl FROM: で与えられる「メールの送信元アカウント」について、チェックが入っているので多少は大丈夫だが、ここらへん詐称が不可能ではないので、少し問題があるのだが...まあ、その検証コードは、SenderIs, RemoteAddrInNetwork Matcher をカンニングして書くと、こんな感じになっている。

   String sender = rs.getString( "sender" );
   String account = rs.getString( "account" );
	    
   // 送信元アドレスの検証
   if( ! account.equals( mail.getSender().toString() ) ) {
       throw new MessagingException( "メールが登録アカウントで送信されていません" );
   }

  // 送信元ホストの検証
   NetMatcher authorizedNetworks = new NetMatcher() {
       protected void log(String s) {
            MoblogServer.this.log(s);
       }
   };
	    
   ArrayList list = new ArrayList();
   list.add( sender );
   authorizedNetworks.initInetNetworks( list );
   if( ! authorizedNetworks.matchInetNetwork( mail.getRemoteAddr() ) ) {
       throw new MessagingException( "メールが登録メールサーバから送信されていません" );
   }



copyright by K.Sugiura, 1996-2006