SIMMなしIS01を入手。LAN内に2ch偽装サーバを立ててみた

2011/12/14
back

経緯

つきあいてきな都合でIS01を買わざるをえなくなりチョット困っていたんですが、 客先オフィス近くのSofm○pみたら5000円割ってる機体が偶然みつかったので、 まぁこれでいいや、と所望してしまったわけなのです。
工人舎PAを使ってる身なため 実は必要というわけでもなかったんですが、PAはプライベートな用途以外には使いたくないのと 仕事上の付き合いの都合で「愛用してます」みたいな姿勢を見せるのもあり、 メモ取り用のデジタルがジェットとして使うことにしました。

せっかくあるんだから、移動中の暇つぶし用に、 やる夫シリーズのAA紙芝居物語() を読める環境を構築して、中にいれて楽しもうとか思ってみたわけです。
やる夫シリーズは全てテキストデータだからデータ量のわりにサイズも小さいし、圧縮も効くし、 自分で加工するのもラクだしネー。

実装を検討

この手のAAはにMS Pゴシック12ポイントに最適化されているため、プラットフォームが Windowsじゃないと、扱いが変わってしまう。
案の定IS01上のHTMLビューワやブラウザでは、かなり崩れてしまい話になりません。
IS01の内部実装はLinuxでMS PゴシックはTTFだから、手元のWindowsマシンから移植しようと 思えば簡単にもってこれるハズですが、ライセンス的に危ないので、あまりやりたくない。
何かないかなー?と調べてみると、フリーの「モナーフォント」というフォントが、 MS Pゴシック準拠でAAが崩れないように調整されてフリーで出回ってるらしい。

これを導入しよう、と思ったところで、En2chというモナーフォント適用済みの2chブラウザが Android1.6用で存在することを知った。

これでいいじゃん。

En2chを導入してみる

まず導入して、適当に2chのログを取得してみた。うん、実用十分。

導入目的であるやる夫シリーズは、実は2ch本体ではなく、2ch形式の別実装掲示板である したらばというサーバに設置してあるものが殆ど。
というわけで、したらばのサーバを読み込めるように板を追加すべく設定を探してみる。
すると、どうでしょう。En2chには、外部板を登録する機能が無いじゃないですか。

バイナリに触らない範囲でハック

しかし、En2ch以外に、単体でAAが崩れず読めてAndroid1.6で動作する2chブラウザは 見つからない。
自分で作れないこともないけど面倒だなー、ということで、En2chの外部仕様をハックして 何とか誤魔化せないか調べてみることにした。

板リストの偽装

まず目をつけたのが、「板リスト取得」機能。
LANにつないでパケットダンプしてみると、どうも、2chのトップページから入ったフレームの 左側に表示されるHTMLを取得して、HTMLの構造解析をして、カテゴリと板名とを 取得しているようだ。
このURLはハードコードされてるんだろうなぁ、と半分ダメモトで、板リスト取得関連の設定を 探してみました。
すると、何ということでしょう。板リストURLは設定はできないけど、選択はできるのです。
設定の一番後ろにコッソリと「2ch menu URL」という項目があり、そこから、 色々なサーバからbbsmenu.html というファイルを取得しにいっている。
DocumentRoot直下固定かぁ、と思いながら見てみると、1件だけ、xrea.com上から 2channel.brd というファイルを取る設定を発見しました。
念のため手動で取得してソースを見てみますと、bbsmenu.htmlはHTMLの構造解析が必要だけど、 2channel.brdは1データ1行で水平タブ区切りのtsvファイル形式であることが分かりました。 これを偽装するのが一番ラクそうです。

とすれば、次に行うべきは、2channel.brdの存在するホストを偽造すること。
一番簡単なのはhostsファイルの編集、次はDNS情報の偽装、一番面倒くさいのはルーティングの 細工でしょうか。
hostsファイルがAndroidに存在するか分からないし、きっとroot権限が必要。面倒くさい。
じゃぁDNS偽装するかー。
と思ったんですが、内部のDNSサーバはだいぶ前に撤廃して外から名前引いてるので、 今更立て直すのも面倒です。
一番ラクな手段をとりたいので、どうせLAN内からしか偽装しないしーってことで、 HTTPのプロキシサーバを立ててEn2chはプロキシサーバを経由させ、プロキシサーバの/etc/host 上で偽装することにしました。

この用途でのHTTPのプロキシにはキャッシュ機能は不要です。delegateやsquidを立てるのは 面倒くさいし設定も手間。サーバ側のリソースも食う。
というわけで、 stoneで簡易プロキシを 立てて誤魔化すことにしました。
3128番ポートにstoneでHTTPプロキシを待ちうけさせ、サーバ機の/etc/hosts上に
vieno@Lecoa:~/$ cat /etc/hosts
127.0.0.1       localhost
127.0.0.1       xxxxxxx.xxx.xrea.com
こんなかんじで名前解決の偽装を施します。
これで、サーバ機のHTTPプロキシを経由したリクエストは、全てこの偽装の影響を受けるようになりました。
自宅サーバのApache上にバーチャルホストを切り、xrea.com としてアクセスされた場合には 偽装済みの2channel.brdを返すようにも細工しておきます。

2channel.brdから1件削除したり順番を入れ替えたりして期待通りに偽装できているのを確認後、 自サーバ上のパスを「板」として登録してみます。
なんか、何度やってもうまくいかない。
オリジナルの2channel.brdをバイナリダンプしてみると、改行コードがCRLFです。
私の作った2channnel.brdは改行コードがLF。
このせいか?と思って調整してみるが、症状かわらず。
どうやら、登録するホストのドメイン部分が 2ch.net じゃないといけないようです。
例によって /etc/hosts を使い、自分の名前として dummyhost.2ch.net という架空のFQDNを でっちあげ、これを登録することで、板リストの偽装は今度こそ成功。

件名一覧を取得

次は件名一覧の取得です、
色々迂回して自分のApacheに到達するようになったので、エラーになるのは承知で、まず 自分のサーバから件名一覧取得を試みてみる。
すると、/(パス)/subject.txt というパスへのリクエストがありました。
本物の2ちゃんねるから適当なsubject.txtを取得。
どうやら、文字コードcp932の、
[0-9]+\.dat<>件名の文字列([0-9]{1,4})\r\n
という形式のようです。
DATの名前はunixtimeかな?
同じ形式で適当に作ってみて、読み込めることを確認。すんなり成功。

文書データの取得

次に、件名一覧と同じやりかたで、データの取得パスを調査。
データそのものは
/(パス)/dat/[0-9]+\.dat
から取得されることがわかりました。
本物の2ちゃんねるから適当にDATファイルをもらってきて、当該パスに設置し、テスト。
うん、読み込める。表示できる。AAも崩れない。OK、OK!

したらばのホストを2chに偽装しようとして

2channel.brdに記録できる形式の名前で、subject.txtとdatを目的のパスで取得できれば、 したらばのホストを2chと偽装できることになります。
ちょっと調べてみますと、 2chブラウザ開発者向けドキュメント がmonazilla.orgにありました。
ふむふむ、かなり違うな。
パスもフォーマットも文字コードも違うから、これは単純にURI偽装じゃ通らないね。

subject.txtの偽装

とりあえずEz2chの認識する「板」は自分のApacheに向けるととする。
要は、
  1. subject.txt という名前で呼び出され
  2. したらばの件名一覧を取得し
  3. 2chのsubject.txt形式にフォーマットして標準出力に吐く
ようなCGIがあればいいわけだ。

というわけで、perlでサクっと試作。
内部実装は単純で、呼び出されたらしたらばの件名一覧もってきて、 foreach でまわして1行ごとに整形して、STDOUTに書き込むだけ。
自力でソケット開くの面倒なので wget の力を借りました。
便利便利、らくちんらくちん。

Apache上のほかのコンテンツに影響を与えずに subject.txt という名前でCGIを実行するために、 コンテンツは全て /cgi-bin/ 上に設置することにしました。

datの偽装

やることは基本的に subject.txt と一緒。
  1. プログラム名は dat とし、
  2. dat/(数字の羅列).dat という名前で呼び出され
  3. 環境変数 PATH_INFO からdatの名前を取得し
  4. したらばのrawmode.cgi経由で目的のdatファイルを読み込み
  5. 2ch形式に整形して標準出力に吐く
ようなCGIを作ります。
やっぱりPerl+wgetでサクっと試作。割りと一瞬でできる。この手の書き捨て・使い捨ての スクリプトにPerlは本当に便利ですねぇ。
STDOUTに書き込むにあたって、文字コードは cp932 に変換しておきます。

このスクリプトは dat という拡張子なしの名前にして、やっぱり /cgi-bin 直下に置きました。

ここまで作ってからFireFoxからアクセスし、「うん、うまくいってる」と思ったんです。
で、いざEn2chから取得しようとすると……。
読み込みは行くんです。
Apacheのログにも、GETでリクエストがきたのがはっきりしてる。
CGIのログも正常に終わってる。
でも、En2chではデータを表示できない。どういうことだ?
問題切り分けのため、datスクリプトを差し替え、取得済みの本物の2chのログをそのまま 読み込んで出力するだけの仕組みに入れ替えました。
ブラウザ経由で取得の上で本物とdiffを取ってみて、全く同じものであることを確認。
しかし、やっぱりEn2chは動作しない。
HTTPのボディ部分が同じなのにダメってことは、文書中の制御コードの問題じゃない。
出力やURLが同じであればクライアント側から動的コンテンツか静的コンテンツかを判断する 手段は無いから、そのせいでもない。
じゃぁ何が違うのか?と思い、telnet で直接HTTPを喋って比較してみました。

まず、ダミーの(正常に取り扱いできる)静的DAT。
vieno@Lecoa:~/$ telnet localhost 80
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
HEAD /xxxxxx/dat/2000000000.dat HTTP/1.0

HTTP/1.1 200 OK
Date: Wed, 14 Dec 2011 02:34:02 GMT
Server: Apache/2.2.3 (Debian) PHP/5.2.0-8+etch11
Last-Modified: Tue, 13 Dec 2011 11:10:28 GMT
ETag: "220fe-28a8c-4c76f100"
Accept-Ranges: bytes
Content-Length: 166540
Connection: close
Content-Type: application/x-ns-proxy-autoconfig

Connection closed by foreign host.
vieno@Lecoa:~/$
次に、動的DAT。
vieno@Lecoa:~/$ telnet localhost 80
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
HEAD /cgi-bin/dat/2000000000.dat HTTP/1.0

HTTP/1.1 200 OK
Date: Wed, 14 Dec 2011 02:36:56 GMT
Server: Apache/2.2.3 (Debian) PHP/5.2.0-8+etch11
Connection: close
Content-Type: text/plain; charset=EUC_jp

Connection closed by foreign host.
vieno@Lecoa:~/$
当然だけど、出力するヘッダが違いますね。

まずは、完全に同じヘッダを吐くように細工してみる。
うん、成功。ヘッダ情報に矛盾はあるけど、En2chでデータが扱えた。
次に、1つ1つヘッダを削除していき、何が原因かつきとめる。
その結果、Last-Modified ヘッダさえあれば処理できることが分かった。
元ファイルの更新日時から作ってもいいけど、面倒くさいし、
$date = `date +\"%a, %d %b %Y %H:%M:%S %Z\"`;
こうやって作ってみる。
すると処理できない。何が悪いんだろう。GMTじゃないといけないのかな?
タイムゾーン変化させるのは可能だけど、正確な更新時刻やらデータが必要なのじゃなく、 フォーマット的にあっていさえすればとりあえずは使えるシステムだ。 ここで正確を期してもあまり意味がない。
というわけで、単純に
$date = `date +\"%a, %d %b %Y %H:%M:%S GMT\"`;
とタイムゾーンで嘘をついて、誤魔化す。
今度こそEn2chで処理できるようになった。

実用にむけて調整

あとは、Apacheの設定やらhostsの細工やらを使って、複数のしたらばの板を 呼び出せるように調整。
識別に使ったのは、環境変数 HTTP_HOST。
Apacheのバーチャルホストの機能でやってもよかったんだけど、したらばの板の数だけ 立てるのも面倒だし、追加やメンテナンスのたびにApacheを起こしなおすのは手間ですしね。

というわけで、無事に、2ch内部の板を偽装してしたらばからAA物語のログを取得できる環境ができました。
LANの中からしか使えないけど、どうせSIMMなしのIS01だから、きっと問題ないと思う。
色々偽装しまくってるけど、自分の管理するLANの中で完結してるから、法にも倫理にも触れない。うん、かんぺき。

外部仕様がわかったら、活用したくなる

En2chの外部仕様をハックした仮定で、2ch形式掲示板としたらば掲示板の データ形式や外部仕様が分かった、
新しいノウハウが手に入ったら、使ってみたくなるのが人情というもの。

自分用まとめサイトもどき

やる夫シリーズのまとめサイトはいっぱいあるけど、 バナーや広告が大量にあったり、バックグラウンドで通信を試みたり、 インラインフレームが必要だったり、 サイト内案内のためのヘッダ・フッタ・サイドバー情報が大量にあったりする。
こででは、せっかくデータ量として軽量なAA紙芝居のメリットが薄い。プアな端末に入れて持ち歩くには辛い。

自分の読みたい作品のオリジナルのdatを用意してそれだけを集めた「板」を作ったら、 そして自分がそれを自由に編集できるインターフェイスを容易すれば、持ち運び用の まとめサイトのミラーのかわりになるんじゃない?

試作

ちょっと古い作品だけど、ボリューム的に適切でギャグも面白く良質な ローゼンファイブ を、手元においてみよう。
というわけで、システム設計・試作する。

オリジナルのDAT入手

まずは、ローゼンファイブの、まとめサイトの元になったdatを入手しよう。
とりあえず第一話のはいったやつだけ。
google検索でdatを見つけ、最初のスレッドのURLを確認。
Windows機にJane Styleを適当にインストールして外部板登録して扱えるようにし、一度読み込ませてから「dat取得」機能でdat形式に再構築する。
で、それをLinuxサーバに移動して、datを静的に配置。
subject.txtは、今後を考えて、手で書くのではなく dat から再構築するようなスクリプトを用意しておく。

うん、En2chで処理できる。かんぺき。

第二話以降のDATを処理できない

第一話がうまく処理できたから、第二話〜最終話までのスレッドのdatも同様に取得する。
設置してみたら…全て読み込んだ後に、En2chが落ちる。
内部的に RuntimeException 系の例外踏んでるねコレ。
サーバ側のログに不審な点はないから、クライアント側の問題?
とはいえ、クライアントのソースや開発環境が無いので、問題があったとしても避けて通るしかない。

処理できないDATがある件を追跡

とりあえず、原因究明。
全部読み込んでレンダリングまでして、そので落ちるということは、datの中にEn2chでは扱えない何かが混在しているんだろう。
真っ先に考え付くのはunicodeの実体参照かな?
でも精査してみても落ちるログに問題ありそうな実体参照定義は無い。

あんまし複雑に考えてもアレなので、(処理できるデータのサンプルが手元にあることだし)、 落ちるDATをバイナリサーチ式に詰めていって、どのレス番号を踏んだら落ちるかを調査してみよう。
まず後半500を切り捨てて前半500で取得。落ちる。
次に残りの後ろ半分を切って1〜250だけ、落ちる。
125まで。落ちる。
バイナリサーチを繰り返して、問題の箇所発見。
アンカー(>>1みたいなの)の行で落ちてるようだ。
しかしそのアンカー行の表示はされていて、そのに落ちてるってことは、 En2chがレンダリング後に何か特別な処理を行っていてそれがうまくいかないってことかな?

アンカー関連の設定がないか見てみる

たちかえって、En2ch側の設定をチェック。
すると、ありました。逆アンカー表示機能。これをOFFにしてみよう。
しかし、落ちる。どうやら機能のON/OFFは表示だけに関わっていて内部的には設定なんか気にせずに処理はしているようだ。

ここでよくよく考えると、落ちない第一話のDATにも、アンカーはあったはず。
ってことはアンカーが原因ではない?または何か違う原因がある?
よし、データの比較だ!

タグ記述の問題だった

比較してみたら、一発で違いがわかった。
第一はアンカーが &gt;&gt;1 の形で記述されてるけど、第二のほうは<a href="#1">&gt;&gt;1</a> とタグ形式で書かれている。これが原因っぽい。
気になって取得元を見てみると、 第一話のスレッドは、 http://yy700.60.kg/test/read.cgi/yaruo/1249731213/。
第二話のスレッドは、http://jbbs.livedoor.jp/bbs/read.cgi/otaku/12766/1250519044/ 。
設置してるサーバが違う。
ちなみに第三スレッド以降も第二と同じでした。
で、yy700.60.kg ホストは2ch形式のデータで、jbbsはしたらば形式。

Jane Styleを経由するとしたらばのデータを2ch形式のdatにできるけど、その過程で、 (おそらくJane側の処理の都合で)アンカーをタグに置き換えて保存しているんだろう。

完成

というわけで、dat内にHTMLのタグ形式のアンカーがあったら除去するフィルタを作成し、 全ログに対して一度フィルタを通してから再設置したところ、問題なく閲覧できるようになりました。
これで、LAN内のホスト上にdatを配置すれば好きなコンテンツを好きな順序で取り扱えるようになった。

TODO

あとは、ローカル上のコンテンツを編集する機能だな。
あと、新規入力時に2chのDAT形式に自動で整形して保存した上で subject.txt を更新するような仕組み。

まぁ、これはハックするような事もないし、簡単に作れるから、帰省中の暇つぶしにとっておこう。

あと何かやるとすれば、まとめサイトを構文解析して本文部分だけ抽出しdatに再構築するような、まとめサイトからdatに逆変換するフィルターつきロボットとかかな。
まとめサイトはサイトごとに構造が違うから、やるなら、対象サイトごとに専用にロボットつくらなきゃいけない。
ロボットに組み合わせた構造解析とテキスト処理は私の専門分野に近いし、UNIXホスト1台あれば特別な設備も要らない。
だからやろうと思えばなんぼでもできるんだけど、仕事でもないのにそこまでするのも大げさだなぁ。
まぁ、大手有名どころだけ2〜3サイト分作ってみるか。帰省中にでも。

back inserted by FC2 system