2007年08月
ノートPC を持ち歩いていると、たまに起動ディスクが欲しくなる。 カーネルや起動スクリプト、あるいはブートローダなどをいじっていて、 起動しなくなることが (しばしば ^^;) あるからだ。 ハードディスクから起動しなくなってしまうと、 起動ディスクが最後の手段となる (昔のノートPC では、 分解してハードディスクを取り出して内容を修正する、 という手段を使ったこともあったが、 最近のノートだと「ハードディスク保護」の仕掛けがあるようなので難しいだろう)。
昔は起動ディスクと言えばフロッピーディスクだった。 slackware の救急用ディスクを常備していた人も多いだろう。 そして今でも最後の最後の手段としてフロッピーディスクは万能である (実はつい最近、昔の VAIO C1 (無印) をいじっていたら起動しなくなって、 フロッピーディスクのお世話になってしまった)。 最近だとフロッピーディスクドライブがついているノート PC は、 ほとんど皆無だろう。 大抵のノートPC は、 USB 接続のフロッピーディスクドライブを使って起動できるとは思うが、 非常用起動ディスクのためだけにドライブを持ち歩くのもあまり現実的ではない。
フロッピーディスクの次に確実なブート手段というと、 CD/DVD ROM だろう。 CD からブートできない PC は、 さすがに最近ではほとんど見かけなくなった。 前述の VAIO C1 も CD からもブートできるのだが、 ドライブが悪いのか CD が悪いのか分からないが、 読み取りエラーが多発してブートできなかった。 滅多に使わないドライブだと光学系が経年劣化してしまうのか、 いつのまにか使えなくなってしまうので注意が必要である。
CD/DVD ドライブが内蔵のノートであれば、 Knoppix などの 1CD Linux を持ち歩けばよい。 12cm CD を常に持ち運ぶのが面倒という場合は、 名刺サイズの CD-R もある。 職場や自宅に何枚か 1CD Linux を常備している人も多いだろう。
私が普段持ち歩いているノートPC は、 レッツノート CF-R6MWVAJP (CF-W2FW6AXR から乗り換えた) なので CD/DVD ドライブが外付けである。 長期の旅行などでなければドライブを持ち歩くことはない。 なので、CF-W2 から CF-R6 へ乗り換えたのを機に、 非常用起動ディスクとして USB フラッシュメモリを使うようになった。 CD に比べコンパクトな上に起動時間も早いし、 (ライトワンスな CD-R と比べて) 内容の変更も手軽に行なえるので便利である。
しかし便利になると、もっと便利さを求めたくなるのがサガというもので、 USB メモリを常に持ち歩くのが面倒になってきた。 できたら常に持ち歩いているケータイを起動ディスクにできないだろうか?
幸い、私が使っているケータイ Advanced/W-ZERO3[es] (アドエス) は、 WM5torage を使うと、 アドエスを USB 大容量記憶デバイス (USB Mass Storage device) として使うことができるようなので、 syslinux を使って、 アドエスにブートローダを書込んでみると、 あっさり起動ディスクとして使えてしまった。 アドエスには 2GB の microSD を入れてあるので、 その片隅に 1CD Linux を入れておいても容量的には大して問題にならない。 常に持ち歩いているケータイをそのまま非常用の起動ディスクとして使えるので、 とても便利である。
と、これだけでは内容が無い (^^;) ので、 1CD Linux を USB メモリに入れて使う方法の解説などを... USB メモリへの書込むためのスクリプトを用意している 1CD Linux もあるようだが、 以下に説明するように 1CD Linux の起動の仕組みさえ理解していれば、 必要なファイル (基本的には、 カーネルと initrd と圧縮ディスクイメージの 3 つ) を USB メモリへコピーするだけの作業である。
むしろ CD に書込んで (仮想マシンを使えば書込まずに済むが、 面倒であることにはかわり無い) 1CD Linux をブートさせ、 書込みスクリプトを実行するよりは、 1CD Linux の ISO イメージから必要なファイルを取り出してコピーする方が、 手軽と言えるのではないだろうか? (もちろん万人向けの方法ではないが)
1CD Linux の起動は、おおむね次のようなステップを経る:
- BIOS が CD メディア上のブートローダを実行
- ブートローダが起動メニュー等を表示
ユーザがブートパラメータを必要に応じて編集 - ブートローダがカーネル (vmlinuz など) と initrd を読み込む
CD からの読み込みは、BIOS 経由 - ブートローダがカーネルを起動
- カーネルが initrd を RAM ディスクに展開し、ルートとしてマウント
- カーネルが RAM ディスク上の /linuxrc を実行
- /linuxrc がブロックデバイスをスキャン
圧縮ディスクイメージ (/KNOPPIX/KNOPPIX など) が存在する ブロックデバイスを探す - /linuxrc が見つけたブロックデバイスをマウント
- /linuxrc がマウントしたファイルシステム上の圧縮ディスクイメージをマウント
- /linuxrc の実行が終了すると、 通常の起動プロセス (つまり init の実行) が始まる
1CD Linux を USB メモリに入れるには、 上記ステップ 1. と 8. において、CD ではなく USB メモリが対象となるようにすればよい。 ステップ 1. は、BIOS による CD メディアの認識であり、 ステップ 8. は、カーネルによる CD メディアの認識である。 同じ CD メディアではあるが、認識を行なうプログラムが異なるので、 ステップ 7. で認識可能な全てのブロックデバイスをスキャンして、 自メディアを見つける必要がある。
なお、Puppy Linux の場合は、 ブートパラメータとして PMEDIA= を指定することにより、 スキャンするデバイスの種類を限定できる。 USB メモリに限定したいときは、「PMEDIA=usbflash」を指定すればよい。
スキャンしたデバイスが、 自メディアか否かを確認する方法は様々であるが、 おおむね特定のファイルが存在するか否かで判断している。 例えば、以下のような感じ:
| 1CD Linux | 確認方法 | |
|---|---|---|
| Knoppix | /KNOPPIX/KNOPPIX | knoppix_dir=, knoppix_name= で変更可能 |
| Damn Small Linux | /KNOPPIX/KNOPPIX | knoppix_dir=, knoppix_name= で変更可能 |
| INSERT | /INSERT/INSERT | insert_dir=, insert_name= で変更可能 |
| Puppy Linux | /pup_216.sfs | 「216」の部分はバージョン番号 |
| SLAX | /livecd.sgn |
ほとんどの 1CD Linux で、 圧縮ディスクイメージ (KNOPPIX, INSERT, pup_216.sfs など) の存在を 調べることによって自メディアを探し出しているが、 SLAX (Linux Live system) のように、 チェック用ファイル /livecd.sgn を CD に置いているケースもある。 /livecd.sgn は 243 バイトほどのファイルで、 以下のような内容である:
All available discs and CDROMs are mounted during the boot process. When done, linuxrc script is looking for this livecd.sgn file. It tells linuxrc where to mount the live filesystem from. Don't delete this file, else your LiveCD won't work.
「Don't delete this file, else your LiveCD won't work.」と書いてある通り、 このファイルが存在しないと、 /linuxrc が SLAX のメディアを見つけられなくなってしまう。
これらの特定のファイルが存在するメディアであれば、 それがブートローダを読み込んだメディアと異なるメディアであっても、 構わず圧縮ディスクイメージをマウントして Linux が起動する。 だから例えば PXE でネットワーク経由 (tftp) で Knoppix のカーネルと initrd を読み込んで、 Knoppix CD メディアから起動させる、といったようなこともできる。 つまり、 カーネルが起動するまで認識できないデバイス (BIOS が USB 接続された CD/DVD ドライブを認識できない場合など) から起動させることが可能になる。
どのように自メディアを確認しているかは initrd の中の /linuxrc を読めば分かるのだが、 サイズ削減のためにカーネルの機能を絞っている場合があるので注意が必要である。 例えばINSERT v1.3.9b の場合、 カーネルが VFAT ファイルシステムをサポートしているにもかかわらず、 「Codepage 437」(VFAT ファイルシステムのデフォルト) をサポートしていないので、 VFAT ファイルシステムをマウントしようとしても失敗してしまう。 つまり圧縮ディスクイメージを VFAT ファイルシステム上に置くことができない。 /linuxrc 中には、
case "$fs" in vfat) # We REALLY need this for INSERT on DOS-filesystems shortname="shortname=winnt" [ -n "$options" ] && options="$options,$shortname" || options="-o $shortname" ;; esac mount -t $fs $options $1 $2 >/dev/null 2>&1 && return 0
「INSERT on DOS-filesystems」という記述があるにもかかわらず、 カーネルに「Codepage 437」機能を組み込んでいないのはバグだと思われる。 このバグを回避するため、INSERT を USB メモリに入れるのであれば、 (1) USB メモリ全体を (FAT ではなく) ext2 でフォーマットするか、あるいは (2) USB メモリに複数のパーティションを作って、 圧縮ディスクイメージ INSERT を置くパーティションは ext2 でフォーマットする、 などとして カーネルがマウントできるファイルシステム (つまり ext2) 上に、 圧縮ディスクイメージを置く必要がある。 (1) の場合は、ブートローダとして extlinux を使えばよい。 ただし PC によっては、 FAT 以外でフォーマットした USB メモリではブートできない場合もあるので、 (2) のほうがお勧め。
さて、 ステップ 1. の BIOS によるブートローダの実行であるが、 大半の 1CD Linux では CD のブートローダとして isolinux が使われている。 isolinux というのは syslinux に含まれる、 CD-ROM 用のブートローダ。 syslinux には以下のブートローダが含まれる:
| ブートローダ | ブートメディア |
|---|---|
| syslinux | MS-DOS/Windows FAT ファイルシステム |
| pxelinux | PXE ネットワークブート |
| isolinux | ISO9660 CD-ROM |
| extlinux | Linux ext2/ext3 ファイルシステム |
各ブートローダは、 設定ファイル (isolinux の場合であれば isolinux.cfg) の書式が共通なので、 ブートメディアを変更しても、 設定ファイルはほとんど修正する必要がない。 1CD Linux を USB メモリ (FAT ファイルシステム) に入れる場合であれば、 設定ファイルのファイル名を isolinux.cfg から syslinux.cfg に変更するだけでよい。
1CD Linux から必要なファイルを USB メモリへコピーし、 syslinux コマンドを使ってブートローダを USB メモリへ書込めば、 Linux ブート可能な USB メモリを作ることができる。
参考までに、 私が SLAX 日本語版 を アドエスに入れた例を紹介する。 まず以下のファイルを SLAX 日本語版 CD-ROM からアドエスの microSD へ (WM5torage を activate した状態で) コピーした (もちろん memtest は、使わないならコピーする必要はないし、 起動画面の画像やヘルプ画面も、表示する必要がなければ不要)。
| SLAX CD-ROM | アドエス microSD | |
|---|---|---|
| /isolinux.cfg | /boot/syslinux/syslinux.cfg | syslinux 設定ファイル |
| /boot/memtest | /boot/syslinux/memtest | memtest プログラム |
| /boot/vmlinuz | /boot/syslinux/slax/vmlinuz | カーネル |
| /boot/initrd.gz | /boot/syslinux/slax/initrd | initrd |
| /boot/splash.cfg | /boot/syslinux/slax/splash.cfg | SLAX 起動画面 |
| /boot/splash.lss | /boot/syslinux/slax/splash.lss | SLAX 起動画面の画像 |
| /boot/splash.txt | /boot/syslinux/slax/splash.txt | F1 を押したときのヘルプ画面 |
| /boot/splash2.txt | /boot/syslinux/slax/splash2.txt | F2 を押したときのヘルプ画面 |
| /livecd.sgn | /livecd.sgn | SLAX チェック用ファイル |
| /base/* | /base/* | SLAX のベースモジュール |
| /modules/* | /modules/* | SLAX のモジュール |
syslinux 3.35 から、 設定ファイルをメディアのルートディレクトリだけでなく、 /boot/syslinux ないし /syslinux にも 置くことができるようになった。 microSD のルートディレクトリにたくさんのファイルを置くと混乱の元なので、 /boot/syslinux に置くことにした。 この場合、設定ファイルに相対パス名を書くときは、 設定ファイルを置いたディレクトリからの相対パスとなる。
SLAX CD-ROM の isolinux.cfg は、 カーネルのパス名などを相対パス名で指定しているので適宜修正する。 修正ついでということで、 SLAX 関係のファイルを /boot/syslinux/slax にまとめることにした。 こうすることにより SLAX 以外の 1CD Linux を同居させる際に混乱せずに済む (Knoppix, Damn Small Linux, Puppy Linux, SLAX を全て一本の USB メモリに 入れることも可能)。
また、SLAX 起動画面 splash.cfg は、 ファイル中に起動画面の画像 (四葉のクローバーの絵) splash.lss へのパスを指定しているので、 これも適宜修正する。
ブートローダの書き込みは、 Windows マシンで以下のように実行:
syslinux -ma -d /boot D:
「-ma」は、 「D:」ドライブ (USB フラッシュメモリのドライブ名) に MBR (マスターブートレコード) を書込むための「-m」オプションと、 そのパーティションをアクティブにするための「-a」オプションの指定。 Linux 上で syslinux コマンドを使ってブートローダを書込む場合は、 「-m」オプションが指定できないので、 別途 (dd コマンド等で) MBR を書込む必要がある。
「-d /boot」はブートローダ本体である「ldlinux.sys」を 書込むディレクトリの指定。 どこでもいいのであるが、まあ /boot あたりが無難だろう。 このコマンドを実行すると、 「/boot/ldlinux.sys」というファイルが作られる。
このブートローダは、設定ファイル syslinux.cfg を、 (/, /boot/syslinux, /syslinux の中から) 自動的に探してくれるので、 syslinux.cfg を編集したとしてもブートローダを再度書込む必要はない。 だから、別の 1CD Linux をこの USB メモリに同居させようとする場合は、 カーネルと initrd と圧縮ディスクイメージを適当なディレクトリへコピーし、 syslinux.cfg に必要な変更を加えるだけでよい。
私の自宅サイト GCD (と勤務先の KLab) では、 qmail にパッチをあてて、 外部から届くメールのヘッダに SPF (Sender Policy Framework) チェックの結果を挿入するようにしている。 例えば以下のような感じ:
Received-SPF: pass (senri.gcd.org: SPF record at thelobstershoppe.com designates 89.215.246.95 as permitted sender) Message-ID: <34f301c7df7d$2e65ece5$5ff6d759@unknown.interbgc.com> From: "Sales Department" <sales@thelobstershoppe.com>
「Received-SPF: pass」というのは、 このメール (実は迷惑メール) の送信者のドメイン thelobstershoppe.com では、 メールを送信する「正規」の IP アドレスの集合 (SPF レコード) を公表していて、 その中にこのメールの送信元 IP アドレス 89.215.246.95 が、 含まれていることを意味する。
つまり thelobstershoppe.com ドメイン管理者の公認 IP アドレスから 迷惑メールが送信されてきたわけで、 (1) ドメイン管理者の意図に反して不正なメール送信が行なわれた (つまり管理に不備がある) か、 (2) ドメイン管理者に意図に沿って迷惑メール送信が行なわれた (つまりドメインの管理者がスパマー)、 ということになる。
ドメイン管理者の意図がどちらであるかは実際に話を聞いてみない限りは 厳密には判別不能であろうが、 仮に (1) であったとしても、 不正なメール送信を許すような管理体制のドメインからのメールは、 なるべく受取りたくないものである。
「Received-SPF: pass」な迷惑メールが送られてきたら、 基本的にはそのドメインからのメールは今後受取らないように、 送信者メールアドレスのドメインのブラックリストを更新してきた。 ところが、最近、「Received-SPF: pass」な迷惑メールを受取る機会が 妙に増えたような気がする。 いったいどんな SPF レコードなのかと調べてみると...
% foreach dom (thelobstershoppe.com scottjanos.com bode-research.com \ ambiguouslyblack.com onairrewards.com mailtong.org artoffurnitureworkshop.com) foreach? host -t txt $dom foreach? end thelobstershoppe.com descriptive text "v=spf1 +all" scottjanos.com descriptive text "v=spf1 +all" bode-research.com descriptive text "v=spf1 +all" ambiguouslyblack.com descriptive text "v=spf1 +all" onairrewards.com descriptive text "v=spf1 +all" mailtong.org descriptive text "v=spf1 mx ip4:125.187.32.0/16 ~all" artoffurnitureworkshop.com descriptive text "v=spf1 +all"
なんと、「Received-SPF: pass」な迷惑メールのドメインの大半が、 「+all」を指定していた。 つまり、 全ての IP アドレスがメール送信 IP アドレスとして「正規」なものである、 と主張しているわけである。 こんな主張は SPF の主旨から考えると全くナンセンスであるから、 対抗措置をとらねばなるまい。
SPF レコードに「+all」を含むドメインは、 自動的に迷惑メール送信元と判断するようにしてしまおうかと (一瞬 ;-) 考えたのであるが、 設定ミス等で「+all」を指定してしまっているケースもあるかもしれない。 そこで、
diff -u spf.c.org spf.c
--- spf.c.org 2007-08-20 16:09:13.000000000 +0900
+++ spf.c 2007-08-20 16:11:43.000000000 +0900
@@ -543,7 +543,7 @@
unsigned int filldomain : 1;
int defresult : 4;
} mechanisms[] = {
- { "all", 0, 0,0,0,0,SPF_OK }
+ { "all", 0, 0,0,0,0,-1 }
, { "include", spf_include,1,0,1,0,0 }
, { "a", spf_a, 1,1,1,1,0 }
, { "mx", spf_mx, 1,1,1,1,0 }
@@ -784,6 +784,10 @@
q = spfmech(spf.s + begin, 0, 0, domain->s);
}
+ if (q == -1) {
+ if (prefix == SPF_OK) q = SPF_UNKNOWN;
+ else q = SPF_OK;
+ }
if (q == SPF_OK) q = prefix;
switch(q) {
という修正を行なってみた (qmail-1.03 + qmail-spf-rc5.patch に対するパッチ)。 これで、IP アドレスが「+all」にマッチした場合は、 「SPF_OK」の代わりに「SPF_UNKNOWN」を返すようになるので、 ヘッダには「Received-SPF: neutral」が挿入される。
例えば、haywardins.com の SPF レコードは、「v=spf1 +all」であるが、 以下のように「Received-SPF: neutral」が挿入される。
Received: from unknown (HELO 6.124.18.84.in-addr.arpa) (84.18.124.6) by senri.gcd.org with SMTP; 20 Aug 2007 10:22:30 +0000 X-Country: RU 84.18.124.6 Received-SPF: neutral (senri.gcd.org: 84.18.124.6 is neither permitted nor denied by SPF record at haywardins.com) Message-ID: <456801c7e313$07cb3524$067c1254@6.124.18.84.in-addr.arpa>
上記 qmail-spf-rc5.patch もそうだが、 SPF 実装の多くが、 デフォルトで Trusted Forwarder ホワイトリストを参照する設定を推奨している。 例えば qmail-spf-rc5.patch の場合であれば、
- spfrules
- You can specify a line with local rules.
Local rules means: Rules that are executed before the real SPF rules for a domain would fail (fail, softfail, neutral).
They are also executed for domains that don't publish SPF entries.
- I suggest adding
include:spf.trusted-forwarder.org.
You can also add mechanisms to trust known mail servers like backup MX servers, though I suggest that you should at least also use tcprules (to modify SPFBEHAVIOR).
などと、 「/var/qmail/control/spfrules」に、 「include:spf.trusted-forwarder.org」を追加することを勧めている。 つまり、spf.trusted-forwarder.org の SPF レコードを問合わせよ、 ということだが、実際に問合わせてみると、
% host -t txt spf.trusted-forwarder.org
spf.trusted-forwarder.org descriptive text "v=spf1 exists:%{ir}.wl.trusted-forwarder.org exists:%{p}.wl.trusted-forwarder.org"
というレスポンスが返ってくる。 つまり「%{ir}.trusted-forwarder.org」か「%{p}.wl.trusted-forwarder.org」の どちらかが存在していれば、 ホワイトリストに登録されていることを意味する。 すなわちその IP アドレスは「あらゆる」送信者アドレスのメールを送ることができる 「信頼されたフォワーダ」ということになる。 ここで「%{ir}」は IP アドレスを逆順にした文字列、 「%{p}」は IP アドレスを逆引きして得られるホスト名である。
どんな送信者アドレスのメールも送れる「万能」の IP アドレスというのも、 タイガイにしてほしいと思うが、 The Trusted Forwarder SPF Global Whitelist によれば、 SPF 導入初期のためのものだったようだ。 2004年ごろは、ほとんどのサイトが SPF レコードを公表していなかったわけで、 「信頼されたフォワーダ」のホワイトリストを保持しておく理由があったのだろう (いまいちその必要性がピンとこないが)。
同ページによれば、 このホワイトリストは既に更新されておらず、 早晩リストの内容が削除されるようだ。 「*.wl.trusted-forwarder.org」への無用な問合わせを避けるためにも、 「/var/qmail/control/spfrules」から 「include:spf.trusted-forwarder.org」の記述を削除すべきだろう (qmail の場合)。
自前のブラックリストを用いて迷惑メール (spam, UBE) を排除する方法について、 「迷惑メール送信者とのイタチごっこを終わらせるために (1)」で説明した。 メールの送信元 IP アドレスが DNS で逆引きできない場合に、 その IP アドレスがブラックリストに載っているか否かを調べ、 もし載っているならその IP アドレスを 「ダイアルアップ IP アドレス」に準じる扱いにする、 という方法である。
迷惑メールを送ってきた実績 (?) がある IP アドレスブロックであれば、 ためらうこと無くブラックリストに入れてしまえるのであるが、 初めてメールを送ってきた IP アドレスブロックを、 逆引きできないという理由だけでダイアルアップ IP アドレス扱いするのは、 少々乱暴だろう。 そこで、 接続元 IP アドレスが属する国のコードをメールヘッダに挿入する仕掛けを MTA (Message Transfer Agent, メールサーバ) に作り込んでみた。例えば、
Received: from unknown (HELO unknown.interbgc.com) (89.215.246.95) by senri.gcd.org with SMTP; 15 Aug 2007 20:46:15 +0000 X-Country: BG 89.215.246.95 Received-SPF: pass (senri.gcd.org: SPF record at thelobstershoppe.com designates 89.215.246.95 as permitted sender) Message-ID: <34f301c7df7d$2e65ece5$5ff6d759@unknown.interbgc.com>
といった感じで、「X-Country: 」フィールドが挿入される。 「89.215.246.95」がこのメールを送ってきたマシンの IP アドレスであり、 その前の「BG」が、 この IP アドレスが属する国 (この例ではブルガリア) の ISO 3166 コード である。
ブルガリアに知り合いがおらず、 かつこのメールがメーリングリスト宛でなく個人アドレス宛であるならば、 MUA (Message User Agent, メーラー) の設定で、 このメールを迷惑メールとして排除することが可能だろう。 あるいは逆に、 「X-Country: JP」である場合は、 迷惑メール判定の結果にかかわらず排除しないという設定にして、 必要なメールを誤って排除するのを防止することもできるだろう (日本語の迷惑メールも、大半は海外の IP アドレスから送信されている)。
IP アドレスから国コード (ISO 3166 コード) を調べるサービスはいろいろあるが、 メールを受信するたびに外部のサイトへ通信するのはあまり感心しない。 ネットワークないし外部のサイトの状況の影響を受けてしまうし、 あるいは逆に大量のメールを一時に受信したときなど、 そのサイトに迷惑をかけてしまう恐れもある。 集中して問合わせを行なってしまった、などの理由で濫用と判断され、 サービスの提供が受けられなくなってしまう可能性もある。
したがって、IP アドレスから国コードを検索するためのデータベースを 自前で持つことが望ましい。 例えば CPAN には、 IP アドレスから国コードを検索するモジュール 「IP::Countryが 登録されている。 このモジュールをインストールすると、 「/usr/lib/perl5/site_perl/5.8.8/IP/Country/Fast」ディレクトリに、 「ip.gif」と「cc.gif」というファイルがインストールされる。
% ls -l /usr/lib/perl5/site_perl/5.8.8/IP/Country/Fast total 256 -r--r--r-- 1 perl perl 681 Feb 2 2007 cc.gif -r--r--r-- 1 perl perl 252766 Feb 2 2007 ip.gif
「ip.gif」が、IP アドレスから国番号を検索するためのデータベースであり、 「cc.gif」が、国番号から国コード (ISO 3166 コード) への変換テーブルである。
メールを受信するたびにメールサーバで perl スクリプトを実行するのは、 メールサーバの負荷などの観点からあまり望ましくない (私のサイトではメールサーバを chroot 環境で動かしていて、 その chroot 環境には perl をインストールしていない、 というセキュリティ上の理由もある) ので、 IP::Country::Fast の perl スクリプトを C 言語で書き直してみた。 ほとんど perl スクリプトをそのまま C に置き換えただけなので、 説明は不要だろう。 inet_ntocc 関数に限ると、 C 版のほうが perl 版より簡潔に書けてしまっている点が興味深い。 コメントは、IP/Country/Fast.pm スクリプトのコメントをそのまま入れてある。
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#ifndef DBDIR
#define DBDIR "/usr/lib/perl5/site_perl/5.8.8/IP/Country/Fast"
#endif
#define CC_MAX 256 /* # of countries */
int inet_ntocc(u_char *ip_db, u_long inet_n) {
/*
FORMATTING OF EACH NODE IN $ip_db
bit0 - true if this is a country code, false if this
is a jump to the next node
country codes:
bit1 - true if the country code is stored in bits 2-7
of this byte, false if the country code is
stored in bits 0-7 of the next byte
bits 2-7 or bits 0-7 of next byte contain country code
jumps:
bytes 0-3 jump distance (only first byte used if
distance < 64)
*/
u_long mask = (1 << 31);
const u_long bit0 = 0x80;
const u_long bit1 = 0x40;
int pos = 4;
u_char byte_zero = ip_db[pos];
/* loop through bits of IP address */
while (mask) {
if (inet_n & mask) {
/* bit[$i] is set [binary one]
- jump to next node
(start of child[1] node) */
if (byte_zero & bit1) {
pos = pos + 1 + (byte_zero ^ bit1);
} else {
pos = pos + 3 + ((ip_db[pos] << 8 | ip_db[pos+1]) << 8
| ip_db[pos+2]);
}
} else {
if (byte_zero & bit1) {
pos = pos + 1;
} else {
pos = pos + 3;
}
}
/*
all terminal nodes of the tree start with zeroth bit
set to zero. the first bit can then be used to indicate
whether we're using the first or second byte to store the
country code */
byte_zero = ip_db[pos];
if (byte_zero & bit0) {
if (byte_zero & bit1) {
/* unpopular country code - stored in second byte */
return ip_db[pos+1];
} else {
/* popular country code - stored in bits 2-7
(we already know that bit 1 is not set, so
just need to unset bit 1) */
return byte_zero ^ bit0;
}
}
mask = (mask >> 1);
}
return -1;
}
u_char *getdb(char *file, int *fdp, int *lenp) {
char path[PATH_MAX+1];
int fd;
struct stat st;
int length;
u_char *db;
snprintf(path, PATH_MAX, "%s/%s", DBDIR, file);
path[PATH_MAX] = '\0';
fd = open(path, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "Can't open: %s err=%d\n", path, errno);
exit(1);
}
if (fdp) *fdp = fd;
if (fstat(fd, &st) < 0) {
fprintf(stderr, "Can't stat: %s fd=%d err=%d\n", path, fd, errno);
exit(1);
}
length = st.st_size;
if (lenp) *lenp = length;
db = (u_char*)mmap((void*)0, length, PROT_READ, MAP_SHARED, fd, 0);
if (db == MAP_FAILED) {
fprintf(stderr, "Can't map: %s fd=%d len=%d err=%d\n",
path, fd, length, errno);
exit(1);
}
return db;
}
int main(int argc, char *argv[]) {
int i;
u_char *ip_db = getdb("ip.gif", NULL, NULL);
const char *_cc =
"USDEGBNL--FREUBEITESCACHRUSEAUAT"
"PLCZIEFIJPDKNOUAZANGILROGRCNPTHU"
"INTRIQSGHKCYIRLTNZKRLUBGAEARBRSI"
"IDCLSKTWSAMYTHYUMXLVCOPHLBPKKZGH"
"EETZKWKERSBDHRDZEGVECMPEECLIPRGE"
"ISMEAPBYMGMDAOCIMTSOPAZWVNBANEBH"
"PSJOCDMZAZMAUZDOBJAMUGSLCGGNZMMU"
"TJMCANMWJMGIMKCRBMLRBOUYGTBWLKGP"
"ALGAMQKGTTNASVLYMNMRAFNPSNKHBBQA"
"CUGLBNOMMOPYSYPGNISMMLSCDJSZLSBF"
"CFGUNCVGHNFJPFTDLAYEFOBISDGQRWKY"
"**BSSRADGMCVGDKNETERRETNTMTGYTMV"
"VIHTKMSTGWAGVABZBTNRTOFKKIVUMPWS"
"MMAWSBJEGFAIAQIOGYNFLCPWCKDMAXFM"
"TVNUAS--------------------------"
"--------------------------------";
#ifdef CHECKCC
int cc_fd;
int cc_len;
int cc_num;
u_char *cc_db = getdb("cc.gif", &cc_fd, &cc_len);
char cc[CC_MAX * 2 + 1];
cc_num = cc_len / 3;
if (cc_num < 0 || CC_MAX <= cc_num) {
fprintf(stderr, "Can't happen: irregular CC DB cc_num=%d\n", cc_num);
exit(1);
}
for (i=0; i < CC_MAX; i++) {
cc[i*2] = '-';
cc[i*2+1] = '-';
}
cc[i*2] = '\0';
for (i=0; i < cc_num; i++) {
u_char c = cc_db[i*3];
cc[c*2] = cc_db[i*3+1];
cc[c*2+1] = cc_db[i*3+2];
}
munmap(cc_db, cc_len);
close(cc_fd);
if (strcmp(cc, _cc) != 0) {
for (i=0; i < CC_MAX; i+=16) {
int j;
printf("\"");
for (j=0; j < 16; j++) {
printf("%c%c", cc[(i+j)*2], cc[(i+j)*2+1]);
}
printf("\"\n");
}
}
#else
const char *cc = _cc;
#endif
for (i=1; i < argc; i++) {
u_long in = ntohl(inet_addr(argv[i]));
int c = inet_ntocc(ip_db, in);
if (c < 0) {
printf("UNKNOWN %s\n", argv[i]);
} else {
printf("%c%c %s\n", cc[c*2], cc[c*2+1], argv[i]);
}
}
return 0;
}
国コードへの変換テーブル「cc.gif」は、 変更頻度もさほど高くないだろうと思われたので、 プログラム中に固定文字列として定義している。 コンパイル時に「-DCHECKCC」を指定することにより、 cc.gif と内蔵の変換テーブルが一致するかチェックできる。
あとは、このコードを MTA に組み込むだけ。 私のサイトでは qmail を 使っているので、 qmail-smtpd.c にこのコードを組み込んだ。
「人の「意識」は心の中心ではなく、脳の様々な活動のロガーに過ぎなかった!」で、
脳はなぜ「心」を作ったのか
「私」の謎を解く受動意識仮説
前野 隆司 著
を紹介したら、前野先生の最新作である次の本を頂いた:
脳の中の「私」はなぜ見つからないのか?
ロボティクス研究者が見た脳と心の思想史 (ハードカバー)
前野 隆司 著
頂いたから言うのではないが (^^;)、この本は素晴らしい。
なにが素晴らしいかというと、
第5章 哲学者との対話
- 1 現象学
- 斎藤慶典 (慶応義塾大学文学部哲学科教授)
- 2 生態学的心理学
- 河野哲也 (玉川大学文学部人間学部准教授)
目次から引用
といった感じで、第一線で活躍中の哲学者との対談を行なっている点。
「心」とは何かを探求するのは哲学者、思想家、 そして宗教家の専売だったわけであるが、 科学技術、特にコンピュータの発展にともなって、 情報科学や認知心理学、脳科学の分野からのアプローチが可能になった。 従来のアプローチである哲学と、 新しいアプローチである情報科学とが、 どのような位置関係にあるのか、 対談という形で明らかにしようとする本書は、 とても分かりやすいし、納得感がある。
そもそも、 脳が純粋に物理的な存在 (つまり霊魂とかは含まれていない) であると信じる限り、 「心」もまた物理現象として説明できる日が来るだろうことは疑いようがない。 そして、 オッカムの剃刀を進化論に適用すれば、 「意識」が受動的なものであることは必然の帰結だろう。 つまり、ヒトのみが「自由意志」を (極めて集中的な) 突然変異によって獲得したという (不自然な) 仮説は切り捨てるべきで、 「エピソード記憶」の能力が進化して 「意識」がより強固な「自我意識」へと進化したと考えるべきだろう。
進化の連続性を考えれば、 「意識」は決してヒト特有の特別な能力ではなく、 充分に進化した脳を持っている動物 (例えば霊長類) ならば、 少なくともヒトの赤ん坊と同程度の意識はあるということになる。 もっとも、赤ん坊にどの程度の意識があるのか、よくわからない (^^;) し、 さらに言えば、 ヒト特有の確固たる自我の意識が生まれるのは、 もっと成長してからのような気がする。 反復説 (個体発生は系統発生を繰り返す) を発生後にまで適用可能ならば、 ヒトと類縁の動物は、ヒトの子供と同程度の「自意識」を持っているのかも知れない。
ちなみに、ヒトの脳には一千億個ほどの脳細胞 (ニューロン) がある。 一千億個というと途方もない数のように思ったこともあったが、 いま私が使っているノート PC のハードディスクの容量が、 ちょうど一千億バイト (100GB) である ;-)。 もちろんただ単にデータを記憶しているだけのハードディスクと、 相互に信号を送受信できるニューロンとを単純に比較することはできないが、 ニューロンだって結線を変えようとすれば結構な時間がかかり、 おそらく大多数のニューロンはただ単に過去の記憶を保持しているだけだろうから、 「意識」という機能の「実装」が想像もつかないほど複雑であると考えるのは 無理がある。
だから少なくとも私にとって「受動意識仮説」は、 「霊魂が存在しない仮説」と同じくらい「確からしい」仮説なのであり、 前野先生が高らかに「受動意識仮説」の妥当性を主張し、 伝統的な宗教家の多くが信じている心身二元論を批判するのを読むと、 やや食傷気味になる。
同じような感覚は、ドーキンスの著作を読んでいても感じる。 もう宗教批判は充分だから、もっと発展的なことを考えようよ、 と言いたくなるのは私だけだろうか?
利己的な遺伝子 <増補新装版> (単行本)
リチャード・ドーキンス (著),
日高 敏隆 (翻訳), 岸 由二 (翻訳), 羽田 節子 (翻訳), 垂水 雄二 (翻訳)
というわけで、ややうんざりしながら 前野先生のチャーマーズ批判を読み進めていたのだが、 「第5章 哲学者との対話」に至って、 この本に対する私の評価は 180度転換した。 第1章〜第4章は、 工学者である前野先生の言葉で語った他の思想の説明であり批判であるのに対し、 第5章は哲学者の言葉で語った哲学者の思想の説明である。 しかも聞き手は前野先生という工学者である。
実は私は大学生のころ、哲学に興味を持って、 いろんな哲学書を読んでみたことがある。 しかしそのほとんどが、哲学者に対する説明か、 あるいは一般人に向けたものであった。 前者は難しすぎて理解できないし、 かといって後者は表層すぎて本質には程遠かった。 そんなわけで一度は哲学を理解するのを断念したのであるが、 本書における、 情報科学からの哲学へのアプローチと、 哲学者と工学者との対話によって、 私自身の哲学に対する理解が大いに進んだように思う。 難攻不落に思えた哲学が、 情報科学という「武器」を用いることによって、 攻略可能であるように思えてきたのである。
GCD の バックアップ回線は、 (費用節約のため ^^;) 固定IPアドレス契約をしていない。 また出先でノートPC を使うときなども IPアドレスは固定でないわけで、 そういったときにもホスト名を持たせられる ダイナミックDNSサービス が便利。
ダイナミックDNSサービスというと、 DynDNS.org, freedns.afraid.org, ZoneEdit.com, No-IP.com, ieServer.Net, ddo.jp などが有名であるが、 いろいろ実験したいときなど、 自前のダイナミックDNSサービスがあると何かと便利なので立ち上げてみた。
大抵のダイナミックDNSサービスは、濫用防止の仕掛けがあって、 アドレス更新頻度が高いとすぐサービス利用を拒否されてしまう。 実験の時など意図せず何度も更新へ行ってしまうこともあり得るわけで、 そのたびに利用を拒否されては実験が滞ってしまうし、 そもそも人様のサーバを実験につきあわせてしまっては申し訳ない。
http://ddns.gcd.jp/register に アクセスして、 ホスト名とパスワード (と captcha) を入力して、 「register」ボタンを押すと、 「ホスト名.gcd.jp」という FQDN を DNS に登録する。
そして、 http://ddns.gcd.jp/update へ
| ユーザ名: | 登録したホスト名 |
|---|---|
| パスワード: | 登録したパスワード |
でアクセスすると、 アクセス元のグローバル IP アドレスが登録される。 アクセス元と異なる IP アドレスを登録したい時は、 http://ddns.gcd.jp/update?ip=127.0.0.1 などと「ip」をパラメータとして指定すればよい。 もちろんこのサービスは実験目的で立ち上げたものであり、 安定運用を保証するものではない。 予告無くサービスを停止あるいは制限を加えることを 了承いただけるかたにのみ利用を許諾する。
GnuDIP など、 ダイナミックDNS サービスを実現するソフトウェアはいくつかあるようであるが、 Web アプリケーションとして行なうべき事は極めて単純 (ホスト名登録と IPアドレス更新のページだけ) なので、 ざくっと php で書いてみた(わずか 250行)。
ネームサーバは MyDNS を使用している。 MyDNS というのは MySQL (あるいは PostgreSQL) のレコード (一例を以下に示す) を そのままゾーンレコードとして扱えるネームサーバ。 つまり php スクリプトから MySQL データベースを更新するだけで ダイナミックDNS サービスを実現できる。
mysql> select * from rr;
+-----+------+-------------+-------+----------------+-----+-------+
| id | zone | name | type | data | aux | ttl |
+-----+------+-------------+-------+----------------+-----+-------+
| 1 | 1 | | NS | ns.gcd.jp. | 0 | 14400 |
| 2 | 1 | | A | 60.32.85.216 | 0 | 14400 |
| 3 | 1 | | MX | mx.gcd.org. | 10 | 14400 |
| 4 | 1 | * | MX | mx.gcd.org. | 10 | 14400 |
| 5 | 1 | ns | A | 60.32.85.217 | 0 | 14400 |
| 6 | 1 | www | CNAME | gcd.jp. | 0 | 14400 |
| 7 | 1 | ddns | CNAME | gcd.jp. | 0 | 14400 |
(中略)
| 106 | 1 | senri | A | 60.32.85.220 | 0 | 50 |
| 107 | 1 | asao | A | 60.32.85.221 | 0 | 50 |
(中略)
+-----+------+-------------+-------+----------------+-----+-------+
MyDNS は listen するポートを、 「listen = 127.0.0.1:53」などといった形式で指定するが、 IP アドレスとして 0.0.0.0 を指定する (つまりインタフェースを指定せずに listen させる) ことができないので、 以下のようなパッチをあてて使っている (2行コメントアウトしただけ)。
--- src/mydns/listen.c.org 2006-01-19 05:46:47.000000000 +0900
+++ src/mydns/listen.c 2007-08-02 13:46:33.000000000 +0900
@@ -81,8 +81,8 @@
if (family == AF_INET)
{
memcpy(&addr4, address, sizeof(struct in_addr));
- if (addr4.s_addr == INADDR_ANY)
- return;
+/* if (addr4.s_addr == INADDR_ANY)
+ return; */
}
#if HAVE_IPV6
else if (family == AF_INET6)
PPPoE などでインターネットへ接続するサーバ (つまりルータ兼用サーバ) で ネームサーバを走らせようとする場合、 インタフェースを指定せず (INADDR_ANY) bind する ほうが何かと都合がよい。 にもかかわらず、 ネームサーバがインタフェース毎に bind しようとするのは何故なのだろうか。 久しく使っていないが、 BIND も そういう仕様だったと記憶している。