Mail::GnuPG ハメられました。

Mail::GnuPGはひじょーーーーにできが悪い。単純に俺が悪いだけなのかもしれないけれど。とてもハメられましたよ。
今回の要件は、httpsで受け付けるメールフォームから送信するメールをGnuPGで暗号化し、ユーザの入力から実際の受け取り手まで全ての経路で暗号化を行いたい、というもの。
メールをGnuPGで暗号化、ということで至極安直にMail::GnuPGの使用を決めたんだけど、こいつがとっても曲者だったのですよ。

GnuPG::Interface
IO::Handle
MIME::Entity
File::Temp
MIME::Parser
Mail::Address 

Mail::GnuPGは上記のモジュールを必要としている。これはどういうことかというと…

  • GnuPG::Interface でGnuPG暗号化
  • MIME::Entity で GnuPGで暗号化した結果をMIME形式のメールにする
  • MIME::Parser はメールを復号化する時に使う(ので今回はあまり関係ない)
  • その他のモジュールは裏方なので説明は省く。

以上から、Mail::GnuPGの中の人がやっている仕事は

  • GnuPG::Interfaceで内容を暗号化し
  • MIME::EntityでMIME形式に整える

となる。

「そんぐらい自分で書けるじゃん」と考えるか「それでも車輪の再発明はヤだ」と考えるかは人それぞれかもしんないけど、Mail::GnuPGはたったそれしきの事をうまくヤれない奴だった。(嘘だと思うならヤってみてくさいよ。そして願わくば俺の浅さを指摘して欲しい)

========new
  Create a new Mail::GnuPG instance.

 Arguments:
   Paramhash...

   key    => gpg key id
   keydir => gpg configuration/key directory
   passphrase => primary key password

   # FIXME: we need more things here, maybe primary key id.

これはドキュメントのMail::GnuPGインスタンスを作るには、という項目からの引用。"FIXME"で始まる文一つとっても「まだまだ」感が強い。

さらにさらに、今回の事件はコレだけじゃなかった。

どーやら Perl5.6.0 なんていうバージョンはもうダメなんですかね。GnuPG::Interface は、Class:MethodMakerというモジュールが必要で、こいつがコンパイル通らないったらなかったよ!!あーちきしょう!
何をどうやっても通らないので、別で入れてあった Perl5.6.1で動かすことにして、こちらでは何なくインストールできた。Perlはコーディングする分にはバージョンを気にする事はさほどない(と思う)けど、こういうモジュール類を入れる時にはけっこうあると思う。ハマっちゃうのって俺だけ?それともv5.8.6入れるべき?(いやそれはない。と信じたい。)

結果、どうしたかと言うとですね。

一瞬だけ /bin/sh で書きなおすか、なんて思ってしまった事はさておき(POSTデータのデコードが面倒な事に気が付いて思いとどまったもんで)、やっぱりPerlで作ったんだけれども、Mail::GnuPGは使わずに

#!/usr/bin/perl
use Jcode;              # 日本語文字コード変換
use MIME::Entity;       # MIMEメール取り扱い
use GnuPG::Interface;   # GnuPGインタフェース
use GnuPG::Handles      # GnuPG I/Oハンドラ
my $mess = MIME::Entity->build(
  Type=>'Multipart/Encrypted; protocol="application/pgp-encrypted"',
  From=>"$FROM",
  To=>"$TO",
  Subject=>"$SUBJ",
  Encoding=>"7bit");
$mess->attach(Data=>"$version",
  Type=>'application/pgp-encrypted',
  Encoding=>"7bit");
$mess->attach(Data=>"$message",
  Type=>'Application/Octet-Stream',
  Encoding=>"7bit");
open(MAIL, "|$sendmail $rcptto") || die
$mess->print(\*MAIL);
close(MAIL);
# $mess->print(\*STDOUT);   #just for debugging.

だいたいこういう感じで自分でMIMEオブジェクトを作ったよ。上記サンプル中の $message には、だいたい以下の要領で作った暗号化済みテキストをぶち込む感じ。

my $gpg = GnuPG::Interface->new();
$gpg->options->hash_init(armor=>1,homedir=>'/path/to/.gnupg');
$gpg->options->push_recipients("$rcptto");
$gpg->options->meta_interactive(0);
my ($si,$so,$se,$sh,$lh,$ph)=
  (IO::Handler->new(),IO::Handler->new(),IO::Handler->new(),
  IO::Handler->new(),IO::Handler->new(),IO::Handler->new());
my $handles = GnuPG::Handles->new(
  stdin=>$si, stdout=>$so, stderr=>$se,
  status=>$sh, logger=>$lh, passphrase=>$ph);
my $pid = $gpg->encrypt(handles=>$handles);
print $si "$mes_plain";
close($si);
my @enc = <$so>;
close($so);
close($se); close($sh); close($lh); close($ph);
$message = join('',@enc);

そんなにHandleをnew()しなくても、と俺も思うんだけれど、開いてどこかに割り当てておかないと勝手にstdinとかstdoutにされちゃって困ったので、これで良いことにしました。もー眠いし。

↑の続き。

どうやら、世の中のGnuPG対応のMUAは"Multipart/Encrypted"なメールよりも、SinglePartで本文のところにペロリと貼られたブツのほうが扱いが上手らしい。よく知らないけどね。PGPとかGnuPGとか自分で使うつもりはあんまりないし。
で、結局Mail::GnuPGはおろかMIME::Entityすら不要になった。処理が簡単になるのは良い事だし、他の誰が見ても(そして未来の俺が見ても)だいたい一目でわかるコードになったのでラッキー。一件落着。