dovecotでCRAM-MD5認証を使えるようにする


1年ほど前から自宅サーバpop3/imapサーバをdovecotにしているのだが、
こいつの認証方式が長い間plainになっていたのを今日発見したので
暇を見て暗号化される認証方式にチェンジすることにした。
ちなみにAPOPでも良かったのだが、qpopperを導入していた時にそっちは試してしまったので
この際だからCRAM-MD5で。……と言っても別段難しい作業はない。


今回はdovecot.conf内でpassdbにpasswd-fileを使うように設定し、
そいつのためのpasswd-fileを作成してやれば設定は完了だ。
また、これに関する情報は全て公式のwikiに載っているので
情報を探し回る必要もない。postfix同様この辺の情報がすっきりしているのは非常に助かる。


まずCRAM-MD5用のpasswdファイルを作成する作業を行う。
公式のWikiによると……

This file is compatible with a normal /etc/passwd file, and a password file used by libpam-pwdfile PAM plugin. It's in the following format:

user:password:uid:gid:(gecos):home:(shell):extra_fields

For a password database it's enough to have only the user and password fields. For a user database, you need to set also uid, gid and preferably also home (see VirtualUsers). (gecos) and (shell) fields are unused by Dovecot.

The password field can be in four formats:

password: Assume CRYPT password scheme.

{SCHEME}password: The password is in the given scheme.

password[13]: libpam-passwd file compatible format for CRYPT scheme.

password[34]: libpam-passwd file compatible format for MD5 scheme.

extra_fields is a space-separated list of key=value pairs which can be used to set various passdb settings and userdb settings. Keys which begin with a userdb_ prefix are used for userdb, others are used for passdb. So for example if you wish to override mail_location setting for one user, use userdb_mail=mbox:~/mail.

Empty lines and lines beginning with '#' character are ignored.

http://wiki.dovecot.org/AuthDatabase/PasswdFile

……と言うことなので、これを踏まえてまずはCRAM-MD5で暗号化されたパスワード文字列を生成する。
dovecotpwというコマンドで暗号化が可能なので……

# dovecotpw -s CRAM-MD5
Enter new password:
Retype new password:
{CRAM-MD5}9bde2235538debe003(適当。実際はもっと長い)

暗号化した文字列を取得する。蛇足だがデフォルトではHMAC-MD5で暗号化を行うらしい。
で、この暗号化した文字列を先ほど引用したサイトの内容を踏まえて……

user:{CRAM-MD5}9bde2235538debe003(適当。実際はもっと長い):1001:1001::/home/user

こんな感じで/usr/local/etc/dvc_passwdとしてエディタで作成。
ちなみに今回はバーチャルユーザではなくUNIXアカウントでCRAM-MD5を使えるようにするため
uid・gid・homeは/etc/passwdの該当アカウントの情報を丸ごと頂くことにした。
これでpasswd-fileの作成は完了。


さて、後は設定ファイルを書き換えて再起動するだけだ。
/usr/local/etc/dovecot.confをエディタで開いて……

auth default {
  # Space separated list of wanted authentication mechanisms:
  #   plain login digest-md5 cram-md5 ntlm rpa apop anonymous gssapi
  # NOTE: See also disable_plaintext_auth setting.
  #mechanisms = plain
  mechanisms = cram-md5 plain

 ...

  # passwd-like file with specified location
  # 
  passdb passwd-file {
    # Path for passwd-file
    args = /usr/local/etc/dvc_passwd
  }

こんな感じに編集してdovecotを再起動。
後はメーラ側の認証方式をCRAM-MD5にして接続を確認、設定作業は完了だ。


こんなに簡単に済むんならもっと早いうちにCRAM-MD5を使うようにすれば良かった……

バックアップHDDの復旧を試みる

自宅サーバのコンテンツバックアップとして補完していたバックアップHDDから
久しぶりにデータを拾い上げようと試みたところ、見事に大変な事態になってしまった。


今回はHDDをUSBで接続する変換コネクタを使って接続を試みたのだけど、
電源を入れてUSBケーブルをマシンに接続した途端

bkserv kernel: umass0:  on uhub4
bkserv kernel: umass0: BBB reset failed, TIMEOUT
bkserv kernel: umass0: BBB bulk-in clear stall failed, TIMEOUT
bkserv kernel: umass0: BBB reset failed, IOERROR
bkserv kernel: umass0: BBB bulk-in clear stall failed, IOERROR
bkserv kernel: umass0: BBB bulk-out clear stall failed, IOERROR
bkserv kernel: umass0: BBB reset failed, IOERROR
bkserv kernel: umass0: BBB bulk-in clear stall failed, IOERROR
bkserv kernel: umass0: BBB bulk-out clear stall failed, IOERROR
bkserv kernel: umass0: BBB reset failed, IOERROR
bkserv kernel: umass0: BBB bulk-in clear stall failed, IOERROR
bkserv kernel: umass0: BBB bulk-out clear stall failed, IOERROR
bkserv kernel: umass0: BBB reset failed, IOERROR
bkserv kernel: umass0: BBB bulk-in clear stall failed, IOERROR
bkserv kernel: umass0: BBB bulk-out clear stall failed, IOERROR

この始末だ。おまけにHDDはかこん、かこんとヘッドが引っ掛かっているような嫌な音を立てている……これはまずい!
慌てて電源を落として原因を模索する作業に入った。
こいつはHDD死亡のお知らせですか!?


とりあえず今回問題となっているHDDがMaxtor製だったので電力不足から疑ってみることに。
Maxtorはやたら電力を食う上に比較的容易に死ぬ印象が強いHDDだったので
電源関連の問題が解決すれば何とかなるのではないか……と淡い希望を抱きながら作業開始。
自宅にあったいくつかのACアダプタの中から最初に使っていたものよりも高品質なアダプタと交換して接続を試みた結果、
とりあえず接続は出来るようになった。まずは第一段階クリア……いきなりヒヤヒヤさせられるメンテである。


さて、いよいよ接続したHDDをマウントする……のだが、

# mount /dev/da0s1d /mnt
/dev/da0s1d: Invalid Argument

#

引数が違うとのたまわれてしまった。当然そんなことは無いはずなので、これはつまり……参ったぞマウントできない。
もうこうなるといくつかセクタが破損している可能性も視野に入れないといけない。こんな時はとりあえずfsck……

 is not a file system superblock
 SEARCH FOR ALTERNATE SUPER-BLOCK FAILED. YOU MUST USE THE
 -b OPTION TO FSCK TO SPECIFY THE LOCATION OF AN ALTERNATE
 SUPER-BLOCK TO SUPPLY NEEDED INFORMATION; SEE fsck(8).

(シングルモードで作業したので手元にあるメモから引用)

……あれ?「-bオプションを使ってalternate super-blockを指定しないとダメっすよ」だなんて
もしかしてファイルシステム破壊されてます?これ。そしてもしかしなくてもかなりやばい事態に?
普通のHDDならここで大分諦めモードなのだが、今回は訳が違う。
なんたってうちのサーバの貴重なコンテンツが入ったHDDなのだ。自分で作成したスクリプトやらCGIやらもここに入っているのだ。
いや自分で作成したスクリプトの方は何とかならないでもないのだが、コンテンツに飛ばれるのはかなりよろしくない。
というわけで早速google先生にお伺いを立てる。最近先生に頼り切りだ……
google先生が居なくなったら僕は一体どうなってしまうのかと思いたくなるほどだ。いやーまいったまいった。


閑話休題。先生曰く、

そこで、alternate superblock を利用して fsck を実行すると
復旧できるかなと思い、次のコマンドを実行しました。

# fsck_ufs -b 160 /dev/da2s1f
Alternate super block location: 160
** /dev/da2s1f
** Last Mounted on
** Phase 1 - Check Blocks and Sizes
** Phase 2 - Check Pathnames
** Phase 3 - Check Connectivity
** Phase 4 - Check Reference Counts
** Phase 5 - Check Cyl groups
SUMMARY BLK COUNT(S) WRONG IN SUPERBLK
SALVAGE? [yn] y

17034 files, 16174610 used, 17215823 free (2551 frags, 2151659 blocks, 0.0%
fragmentation)

UPDATE STANDARD SUPERBLOCK? [yn] y


***** FILE SYSTEM WAS MODIFIED *****

どうやら無事、ファイルシステムを復旧できました。
# alternate superblock を使った復旧は、
# alternate superblock の値を知らないとできないんですね。
# newfs 実行時に表示されるのですが、メモしてなかったので…。

http://www.mail-archive.com/freebsd-users-jp@jp.freebsd.org/msg02508.html

との事だそうだ。google先生もそうだが、コミュニティの情報には本当に感謝してもし足りない。
引用元の情報を提供してくださった方、ありがとうございます。
というわけで、引用元の手順に則って問題のHDDのsuperblockのブロック値を取得して再度fsck_ufsを実行。

# fsck_ufs -b 160 /dev/da0s1d

(メモにコマンドしか書いて無かった……)

これでファイルシステムの復旧は完了。あとは通常通りマウント出来るか確認して……

# mount /dev/da0s1d /mnt
#

どうやら無事にマウント出来た模様。
データが拾えれば儲けものだと思っていた一連の作業だが、
よもやファイルシステム単位でデータが復旧出来るとは!こいつは業務にも応用出来そうだ。

Perl5.8.9のsuidperlにバグがあるらしい

自宅サーバのハードウェアグレードアップをしたついでにFreeBSD7.1を新規インストールした。
FreeBSD7.1のPortsに載っかってるPerlは5.8.9だったのだが*1
こいつをインストールした後suidperlを使っているスクリプトを走らせてみたところ

suidperl needs (suid) fd script


といったエラーログと共にスクリプトがたたき落とされるようになった。
こういうときはGoogle先生の出番である……件の文言で調べてみた結果、どうやらバグがあるらしい事が解った。

> in perl.c in release 5.8.9, line 3727 is if (*suidscript) {
> should be if (*suidscript != 1) {

suidscript is a bool* so comparison with 1 is not sound

Elsewhere in patch 33168, C is replaced by C so I suspect the test is just inverted
if (*suidscript) {
should be if (!*suidscript) {

http://www.gossamer-threads.com/lists/perl/porters/234548


どうやら実行しようとしているスクリプトがsuidなスクリプトかどうかのフラグ判定を間違えているらしい。
早速/usr/ports/lang/perl5.8/work/perl-5.8.9/perl.cをこの情報に従って書き換えて再インストールを試みることにした。
これにて正常通り稼働……している……と思う。もう少し使い込んでから結論を出す予定。

*1:6系では5.8.8_1だった。最近5.8.9に更新されたらしい

自作CGIを改造してポッドキャスト対応にする

表題の通り。以前趣味で作成した3gpやらそれ以外のCODECでエンコードされた動画を
別の形式に変換してダウンロードさせるスクリプトを作成したのだが、
今回はそれをポッドキャスト対応にすべく色々と試行錯誤を繰り返すことにした。

方法としては幾つかあるが、今回はPlaggerを使ってみることにした。
折角Perlをある程度使いこなせるようになったし、業務にも応用する機会が来れば良いなあという
淡い期待を胸に抱きながらPlagger自宅サーバに導入。

Podcastってのは要はRSSなので、別にPlaggerに頼らなくてもXML::RSSモジュールを使えば
直接Podcastを配信することは可能なのだが、今回はPlaggerの試用も兼ねているので
Plaggerのサンプルレシピの中にあった「RSSPodcastに変換する」設定を使ってみることに。
つまりPodcast化する前のRSS自体をXML::RSSで作成して、それをPlaggerPodcast化するわけだ。
ま……まどろっこしい!


というわけで、まずはRSSの生成・更新から。
CPANにあった解説に基づいて以下のようなサンプルコードを作成してみた。

#!/usr/bin/perl

use XML::RSS;
use Jcode;

my $GETDIR = "/var/html/data/video";
my $PREFIX_URL = "http://www.example.jp/data/video";

my $rss;
my %item;
my @files;
my @sorted_files;
my @sorted_keys;
my @reversed_keys;
my $i;

$rss = new XML::RSS;

if (-f "/var/html/tmp_video/feeds/rss.xml") {
    goto update_rss;
}

$rss->channel (
    title => "test rss",
    link => "http://www.example.jp/",
    language => "ja",
    description => "Summary for test rss",
);

opendir DIR, "$GETDIR" or die;
@files = grep { $_ =~ /.*\.3gp$/ } readdir DIR;
closedir DIR;

@sorted_files = sort @files;

for ($i = 0; defined $sorted_files[$i]; $i++) {
    my $disptitle = "テストコンテンツ$i";
    $item{"$disptitle"} = "$PREFIX_URL/$sorted_files[$i]";
}

@sorted_keys = sort keys %item;
@reversed_keys = reverse @sorted_keys;

foreach (@reversed_keys) {
    my $key = $_;
    $dispkey = jcode($key)->utf8;
    $rss->add_item(
        title => "$dispkey",
        link => "$item{$key}",
    );
}

open FILE, "> /var/html/tmp_video/feeds/rss.xml" or die;
print FILE $rss->as_string();
close FILE;

update_rss:
    $rss->parsefile("/var/html/tmp_video/feeds/rss.xml");
    $rss->add_item(
	title => "テストコンテンツ(追加)",
	link => "http://www.example.jp/data/video/test4.3gp",
	mode => "insert",
    );

    $rss->save("/var/html/tmp_video/feeds/rss.xml");

exit 0;


RSSの生成は実は今回が初なのだけれど、
こうやって高々60行かそこらのスクリプトで十分通用するものが作れるところがPerlのいいところだと思う。
Perl Hacksが実質モジュール解説の書籍になっているのも頷ける話だ……

閑話休題。あとはこのスクリプトで作成したRSSPlaggerを使ってPodcast化すればよいわけだ。
Plagger側のconfig.yamlも載せようかどうか迷ったけど、
サンプルのpodcast.yamlを微調整しただけなのであえて載せないことにする。

あとはこれを動画変換スクリプトに組み込めば完成……だがそこで一つ問題が発生した。
RSSの更新時に新しく登録した項目のタイトルが文字化けを起こしてしまうのだ。

散々調べまくって資料を確認した結果、どうやらXML::RSSの関連モジュールであるXML::Parserが
読み込んだRSSのエントリを自動的にUTF8で読み込むためらしい。
さらに困ったことにJcodeモジュールで日本語をUTF8にエンコードしても文字化けが解消されることは無かった。

……で、色々調べてみたところ(個人的には)意外な事実が発覚した。
詳細なところはこちらにあるが、
つまりこういうことらしい。

Perl 5.8.x では、内部的には、文字列は Unicode で扱われます。 内部的に扱われている Unicode 文字列には、 UTF8フラグというものがつくみたいです。
(中略)
外部から読み込んだ文字列や、ソースコードに書いた"ほげ"とかいう文字列は、 UTF8フラグがついていません(use encoding "..." しない場合)。

http://www.rwds.net/kuroita/program/Perl_unicode.html#flg


なんということだ!そんなこと聞いてないぞ!
……ということは、つまり外部から入ってきた文字列にUTF8フラグを付けてやればXML::Parserが正しく解釈してくれるということだろう。
というわけで早速上記の引用元の記事を参考にさっきのコードに手を入れてみる。

update_rss:
    use Encode;
    $rss->parsefile("/var/html/tmp_video/feeds/rss.xml");
    ## utf8フラグを立てるためにEncode::decodeを使う。
    #
    my $titlename = Encode::decode("euc-jp","テストコンテンツ(追加)");
    $rss->add_item(
	title => $titlename,
	link => "http://www.example.jp/data/video/test4.3gp",
	mode => "insert",
    );

    open FILE, "> /var/html/tmp_video/feeds/rss.xml" or die;
    binmode(STDOUT, "utf8");
    print FILE jcode($rss->as_string)->utf8;
    close FILE;

exit 0;


こんな感じ。これで文字化けすることなくRSSを更新することが出来た。

プロバイダ乗り換えにつきOP25B関連の設定を変更する

これまでISPはDTIを利用していたが、先週の日曜日から訳あって@niftyへと乗り換えることになった。
DTIではOP25Bに関して自宅サーバ側でとりわけ設定が必要になっていなかったが*1
nifty側ではどうやら明確な設定が必要なようなので、サポートのサイトから情報を検索して設定を施すことにした。


で、ちょこちょこと検索して出てきたページによると……

メールサーバーを構築し、その送信サーバーからメールを送信した場合には、Outbound Port25 Blockingの影響を受けます。

対応方法として、@niftyの送信サーバーを中継するようにメールサーバーの設定を変更してください。なお、設定方法につきましては、サーバーの管理者へご確認ください。

http://faq.nifty.com/nft5795/web2210/faq/detail.asp?Option=1&FAQID=402&baID=6&NodeID=317&DispNodeID=0&Text=%u30C0%u30A4%u30CA%u30DF%u30C3%u30AFDNS&Field=1&KW=1&KWAnd=0&AspPage=LST&strkind=9&Page=0&Rows=10&NB=/


……だそうだ。いや、その「管理者」が僕だっつーの!!(笑
これだと全く手がかりを得られないので、仕方なくGoogle先生にお伺いを立てる事に。偉大なるGoogle先生曰く、

 対応策として、メールサーバーの設定変更を行い、@niftyの送信サーバーを中継するようにしてください。なお、本件に関する設定方法等はサポートしておりません。

 ・サーバー名 : smtp.nifty.com
 ・sendmail : SMART_HOST,MAIL_HUB
 ・Postfix : transport
 ・qmail : smtproutes

http://209.85.175.104/search?q=cache:B-nshgb5MFQJ:www.nifty.com/support/madoguchi/op25b/op25b_qa_ans13.htm+nifty+op25b+transport&hl=ja&ct=clnk&cd=1&client=opera/


……とのこと。なるほど、自宅サーバで使っているMTAはPostfixなのでどうやら
/usr/local/etc/postfix/transportの中にsmtp.nifty.comへとリレーする記述を追加すればいいらしい。

 .my.domain     local:
 *               smtp:smtp.nifty.com


上記のようにぱぱっと書き換えてpostmapで更新、対応完了。


っていうかGoogle先生にお伺いを立てて出てきたページもniftyのサポートページじゃないか!
何でこっちがヒットするようにしておかないんだnifty……

*1:精々Submissionポートへの対応くらい

日記投稿CGIの続き

前回の投稿時に考えついていた事柄を一通り試行してみた。今回試行したのは

  • はてな記法を一旦HTMLに変換した後文章の部分だけを抽出する方法
  • はてな記法を投稿したい文体の平文に変換する方法

の2つだ。どちらも基本コンセプトははてな記法を変換するという点で同一である。
……が、試行してみた結果意外に面倒なことが発覚した。
前者にはLIタグ←→・の関係に代表されるような
「タグで表現するけどテキストでも特に明記した書き方がある」ような記法を
用いた場合に生じる不十分なテキスト変換問題があり、
後者にははてな記法を部分的にでもテキストに変換できるようなモジュールがないために
完全自前でやらなければならないためチェック工程が死ぬほど面倒な事になっている……
という問題がある。だいいちはてな記法にははてな固有の機能を利用するようなものもあるし、
mixi日記側はHTMLタグを事実上使用できないので、例えばAタグを使用するような記事は
mixi日記に投稿する際には文面を大幅に変えなければならない。*1


いずれにせよ、両者の間に容易い共通点を発見できない以上
これまで通り手動でむにゃむにゃやった方が気楽に済みそうだ。


なかなか楽しそうな企画だと思ったんだけどなー……残念!

*1:少なくともヘルプやmixiプレミアムの詳細から推定した結果そう感じた

日記書き込み用のCGIを作ってみよう(模索中)

毎回日記を投降する際にmixiの日記とはてなダイアリーの両方に
同じ記事を投稿しているわけだが、これの作業がなかなかに面倒臭い。
はてなダイアリー側では部分的ではあるが「はてな記法」を使っているからだ。
【サーバ運用】のカテゴリに入るような記事でははてな記法を使ってコマンドの文法や
出力結果等々を記入しているし、
【プログラミング】のカテゴリに入るような記事ではソースコードを記入する際に
はてな記法を使わないと表示される記事がぐだぐだになってしまう。
そのため、mixiに投稿する記事とはてなダイアリーに投稿する記事を一元入力出来るように
CGIを組もうかと模索してみた。ちなみにPerlを使う予定だ。


まずは通常のテキストとはてな記法を使ったテキストの差を埋めるために
一旦htmlに変換してから投稿する方法だ。
CPANText::Hatenaというモジュールがあるので理論的には実装可能である。
……が、よくよく考えてみたらmixi側で日記を投稿する際に
htmlタグを細かく使えるわけではないのでこの方法は無理だろう。*1


となると次に思いつく手段ははてな記法を使わない本文をはてな記法に変換して
そいつをはてなダイアリーの方にPOSTしてやること……だけど、これもよくよく考えたら
目の前に出てきた文字列をはてな記法として解釈するのかただのテキストとして解釈するのかを
どこで判別するかが問題になる。
何でもかんでもはてな記法だとして読ませる訳にもいかないからだ。


……ん?でも待てよ?
はてな側では目の前の文字列がはてな記法だと解釈できればはてな記法になってしまうのだから、
とりあえずはてな記法で書くだけ書いてしまって、mixiへ投稿する側のルーチンでは
はてな記法だと想定される文字はすっ飛ばせばよいのかな?
或いは上で既に述べた「htmlに変換して投稿する」をもじって
htmlに変換した文字列をHtml::Parserでパースしてからmixiに投稿するという手が
使えなくもないような……


多分はてな記法だと想定される文字はすっ飛ばしてしまう手法の方が手間が掛からなさそうだ。
引き続き模索中……

*1:どっかで統一した方が楽なんだからこの方法がベストだったんだけどなぁ……