読者です 読者をやめる 読者になる 読者になる

MySQL ソケットピア証明書認証プラグイン

MySQL

全然知らなかったんですが、MySQLに「ソケットピア証明書認証プラグイン」というのがあるのを知りました。

http://dev.mysql.com/doc/refman/5.6/ja/socket-authentication-plugin.html

これを使うとOSのログインユーザーと同じ名前のMySQLユーザーであれば、パスワード無しでMySQLに接続することができます。ただしローカルホストでUNIXソケット経由での接続のみ。

試してみます。

% mysql -uroot
mysql> INSTALL PLUGIN auth_socket SONAME 'auth_socket.so';
mysql> CREATE USER tommy@localhost IDENTIFIED WITH auth_socket;
% whoami
tommy
% mysql
mysql> SELECT USER();
+-----------------+
| USER()          |
+-----------------+
| tommy@localhost |
+-----------------+

どうやら 5.5.10 からあるようです。今までずっとMySQLのユーザーとOSのユーザーは無関係だと思ってたのでちょっと驚きました。

Twitter からの文字化けメール

Twitter を名乗るメールアドレスからメールが届いたのですが、Subjectが文字化けしていました。

Subject: ã¨ã¿ãŸã¾ã•ã²ã‚さん、Twitterからの感謝のメッセージです。

いまのTwitterがあるのは、皆さんのおかげです。
10年前の今日、ひとつのツイートからすべてが始まりました。 
そのときから、Twitterは驚くべきことを皆さんと一緒に行いました。<BR> この瞬間にも何百万人の人々がつながり、それぞれの思いを自由に語り、<BR> かつては想像もできなかったインパクトを世界中に与えています。
Twitterの誕生日に、皆さんにあらためて感謝いたします。<BR> これまで支えてくださり、ありがとうございます!<BR> そして次の10年に向けてもよろしくお願いいたします。

Subject が文字化けしていることだけでなく、本文中に「<BR>」がそのまま入っていたり、本文中のリンク先が Twitter ではなく、http://cl.S7.exct.net/ というサイトへのリンクになっているので、かなり怪しい雰囲気なのですが、変なメール配信サイトを使ってるだけで多分本物だと個人的には思ってます。知りませんけど。

まあ本物かフィッシングメールかはどうでも良くて、本題は文字化けです。

「◯◯さん、Twitterからの感謝のメッセージです」という文面から、文字化け部分は名前だと推測できます。

メールの生ヘッダを見てみます。

Subject: =?UTF-8?B?w6PCgcKow6PCgcK/w6PCgcW4w6PCgcK+w6PCgeKAosOjwoHCssOj?=
 =?UTF-8?B?4oCawo3jgZXjgpPjgIFUd2l0dGVy44GL44KJ44Gu5oSf6Kyd44Gu44Oh44OD?=
 =?UTF-8?B?44K744O844K444Gn44GZ44CC?=

MIMEデコードして文字化けしている部分のバイト列を見てみます。

$ ruby -e 'p "w6PCgcKow6PCgcK/w6PCgcW4w6PCgcK+w6PCgeKAosOjwoHCssOj4oCawo3=".unpack("m")[0]'
"\xC3\xA3\xC2\x81\xC2\xA8\xC3\xA3\xC2\x81\xC2\xBF\xC3\xA3\xC2\x81\xC5\xB8\xC3\xA3\xC2\x81\xC2\xBE\xC3\xA3\xC2\x81\xE2\x80\xA2\xC3\xA3\xC2\x81\xC2\xB2\xC3\xA3\xE2\x80\x9A\xC2\x8D"

「C2 xx」や「C3 xx」が繰り返されているのは、たいてい Latin-1(ISO8859-1) を UTF-8 に変換した時のバイト列です。

元々何か別のエンコーディング文字列(おそらく UTF-8)を Latin-1 とみなして UTF-8 に変換してしまったものと思われます。

ということで、逆に変換してみます。

$ ruby -e 'p "w6PCgcKow6PCgcK/w6PCgcW4w6PCgcK+w6PCgeKAosOjwoHCssOj4oCawo3=".unpack("m")[0].encode("iso8859-1", "utf-8", undef: :replace)'
"\xE3\x81\xA8\xE3\x81\xBF\xE3\x81?\xE3\x81\xBE\xE3\x81?\xE3\x81\xB2\xE3?\x8D"

UTF-8 ぽいバイトの並びになりました。このバイト列を文字で表すと「とみ???ま???ひ???」となりました。 当初の推測通り私の名前「とみたまさひろ」が文字化けしていたようです。

「とみたまさひろ」をそのまま UTF-8 バイト列で表すと次のようになります。

$ ruby -e 'p "とみたまさひろ".b'
"\xE3\x81\xA8\xE3\x81\xBF\xE3\x81\x9F\xE3\x81\xBE\xE3\x81\x95\xE3\x81\xB2\xE3\x82\x8D"

9バイト目の\x9F, 15バイト目の\x95, 20バイト目の\x82 の文字の部分がうまく変換できなかったようですが、これの原因はわかりませんでした。

天下の Twitter さんなんだから、まともなメール配信システムを使って欲しいところです。

flock(LOCK_EX) で EBADF

Linux NFS Ruby

ちょっとハマったのでメモ。

ファイルを flock() で排他的にロックするために次のようにすると成功します。

% ruby -e 'File.open("hoge").flock(File::LOCK_EX); puts "OK"'
OK

が、NFS 上で同じことをやると失敗します。

% ruby -e 'File.open("hoge").flock(File::LOCK_EX); puts "OK"'
-e:1:in `flock': Bad file descriptor @ rb_file_flock - hoge (Errno::EBADF)
    from -e:1:in `<main>'

7年前に書いたんですが、Linux は NFS ファイルシステムに対して flock() すると fcntl(F_SETLK) を使います。

で、fcntl(F_SETLK)flock() と異なり、排他ロックをするにはファイルを書き込み可のモードでオープンしておかないといけません。

通常のローカルファイルシステムでも読み込み専用でオープンしたファイルに fcntl(F_SETLK) を使って排他ロックを行えば同じエラーが発生します。

% ruby -rfcntl -e 'File.open("hoge").fcntl(Fcntl::F_SETLK, [Fcntl::F_WRLCK, 0].pack("ss"))'
-e:1:in `fcntl': Bad file descriptor @ rb_fcntl - hoge (Errno::EBADF)
    from -e:1:in `<main>'

書き込みできるモードでオープンしておけばエラーにはなりません。

% ruby -rfcntl -e 'File.open("hoge", "r+").fcntl(Fcntl::F_SETLK, [Fcntl::F_WRLCK, 0].pack("ss")); puts "OK"'
OK

つまり、NFS 上でも書き込みできるモードでファイルをオープンしておけば flock() で排他制御できます。

% ruby -e 'File.open("hoge", "r+").flock(File::LOCK_EX); puts "OK"'
OK

ということで、flock(LOCK_EX) する場合は書き込みできるモードでファイルをオープンしておいた方が無難というお話でした。

Postfix 3.1 の新機能 / JSON形式キュー表示と配送流量制御

Postfix

Postfix 3.1 がリリースされました

個人的に気になった新機能は

  • JSON-format Postfix queue listing.
  • Destination-independent delivery rate delay

の2つです。

JSON形式キュー表示

今までは mailq や postqueue -p コマンドで次のような表示がされていました。

~% postqueue -p
-Queue ID-  --Size-- ----Arrival Time---- -Sender/Recipient-------
8A9AE6EF        275 Sun Mar  6 23:47:06  sender@example.com
                                                          (deferred transport)
                                         rcpt1@example.net
                                         rcpt2@example.net

94AC010A!       298 Sun Mar  6 23:46:33  sender@example.com
                                                          (deferred transport)
                                         rcpt@example.net

-- 0 Kbytes in 2 Requests.

人が読むには良いのですが、プログラムで処理するにはちょっと難しい形式でした。

これが JSON 形式で出力できるようになりました。postqueue -j コマンドを使用します。

~% postqueue -j
{"queue_name": "deferred", "queue_id": "8A9AE6EF", "arrival_time": 1457275626, "message_size": 275, "sender": "sender@example.com", "recipients": [{"address": "rcpt1@example.net", "delay_reason": "deferred transport"}, {"address": "rcpt2@example.net", "delay_reason": "deferred transport"}]}
{"queue_name": "hold", "queue_id": "94AC010A", "arrival_time": 1457275593, "message_size": 298, "sender": "sender@example.com", "recipients": [{"address": "rcpt@example.net", "delay_reason": "deferred transport"}]}

キュー毎に JSON 形式でリストされます。時刻はUNIX時刻(1970-01-01 00:00:00 UTC からの経過秒数)で表され、受信者は配列形式で表されます。プログラムで処理するのも簡単そうです。

配送流量制御

今まで Postfix は配送時の流量を制御することはできませんでした。相手が受け付けるだけメールを送信していました。

3.1 では明にメッセージ配送を遅延させることができるようになりました。

たとえば default_transport_rate_delay = 3s を設定すれば、キューに入ってから配送を開始するまで3秒間待つようになります。

smtp_transport_rate_delay = 3s とすれば smtp 配送だけに効果があります。

transport_maps と master.cf の設定をうまく使えば特定のドメイン宛だけ遅延させることもできると思います。

次は default_transport_rate_delay = 3s とした状態で、メールをほぼ同時に5通送った時の配送ログです。 約3秒間隔で配送されていることがわかります。

Mar  7 00:31:03 x220 postfix/smtp[29564]: 10D0610A: to=<tmtms@example.com>, relay=mx.example.com[49.212.128.207]:25, delay=3.8, delays=0.08/3.4/0.14/0.17, dsn=2.0.0, status=sent (250 2.0.0 Ok: queued as AA13B9FCBF)
Mar  7 00:31:03 x220 postfix/qmgr[29553]: 10D0610A: removed
Mar  7 00:31:07 x220 postfix/smtp[29564]: 6AD316EF: to=<tmtms@example.com>, relay=mx.example.com[49.212.128.207]:25, delay=6.7, delays=0.09/6.3/0.14/0.19, dsn=2.0.0, status=sent (250 2.0.0 Ok: queued as 046759FCBF)
Mar  7 00:31:07 x220 postfix/qmgr[29553]: 6AD316EF: removed
Mar  7 00:31:10 x220 postfix/smtp[29598]: BD2BC1A0E: to=<tmtms@example.com>, relay=mx.example.com[49.212.128.207]:25, delay=9.7, delays=0.12/9.3/0.14/0.17, dsn=2.0.0, status=sent (250 2.0.0 Ok: queued as 522C19FCBF)
Mar  7 00:31:10 x220 postfix/qmgr[29553]: BD2BC1A0E: removed
Mar  7 00:31:13 x220 postfix/smtp[29564]: 164231AA5: to=<tmtms@example.com>, relay=mx.example.com[49.212.128.207]:25, delay=13, delays=0.12/12/0.14/0.17, dsn=2.0.0, status=sent (250 2.0.0 Ok: queued as 9E3459FCBF)
Mar  7 00:31:13 x220 postfix/qmgr[29553]: 164231AA5: removed
Mar  7 00:31:17 x220 postfix/smtp[29598]: 642CC1CE2: to=<tmtms@example.com>, relay=mx.example.com[49.212.128.207]:25, delay=16, delays=0.12/15/0.14/0.18, dsn=2.0.0, status=sent (250 2.0.0 Ok: queued as EA5DD9FCBF)
Mar  7 00:31:17 x220 postfix/qmgr[29553]: 642CC1CE2: removed

Crystalの文字エンコーディング

Crystal

2/16 に Crystal 0.12 がリリースされました。

0.12 では Crystal に文字エンコーディング変換の仕組みが導入されました。

Ruby と同じような感じで String#encodeIO#set_encoding 等が追加されました。

といっても Crystal の文字エンコーディングは Ruby とは異なり UTF-8 固定です。そのためString#encode で変換後のデータは String ではなく Slice です。UTF-8 への変換でも同じです。

"ほげ".encode("cp932") #=> [130, 217, 130, 176]
"ほげ".encode("utf-8") #=> [227, 129, 187, 227, 129, 146]

UTF-8 以外のエンコーディングで書かれたファイルに読み書きするには、File.open にエンコーディングを指定します。

% od -tx1 hoge.txt        # hoge.txt には CP932 で「ほげ」と書かれている
0000000 82 d9 82 b0 0a
0000005
File.open("hoge.txt", encoding: "cp932").gets  #=> UTF-8 で"ほげ\n"
File.open("fuga.txt", "w", encoding: "cp932") do |f|
  f.puts "ふが"
end
% od -tx1 fuga.txt        # fuga.txt には CP932 で「ふが」と書かれる
0000000 82 d3 82 aa 0a
0000005

Ruby はエンコーディング変換を自前で行っていますが、Crystal はエンコーディングの変換に iconv を使用しているため、使用できるエンコーディングは環境に依存します。

たとえば、手元の環境(Ubuntu 15.10)の iconv には CP50221(日本でよく ISO-2022-JP と詐称してメールで使われるエンコーディング)がなかったので、メールを扱うのにはちょっと厳しい感じです。

文字化けメールその2

スパムメール…というかフィッシングメールが送られてきたのですが、文字化けしていました。

文字化けメール研究家としては(ry

メーラーで見ると Subject と本文が次のようになっていました。

From: 【りそな_y行】 <mp@resona-gr.co.jp>
Subject: 本人_J_^サ_`ビス

文字化けとしてはちょっと珍しい感じでなので、ソースを見てみます。

From: =?GB2312?B?ob6k6qS9pMrjedDQob8=?= <mp@resona-gr.co.jp>
Subject: =?GB2312?B?sb7Iy9VK1F6ltalgpdOluQ==?=

これを素直にデコードすると、「【りそな〓y行】」「本人〓J〓^サ〓`ビス」となります。「〓」は変換に失敗した部分です。

charset が GB2312 となっています。GB2312 は中国で使われている文字エンコーディングですが、GB2312 と名乗りつつ実際には GB2312 ではなく GBK であることが多い…というか普通のようです。

日本で流通しているメールが ISO-2022-JP と名乗りつつ「①」や「㈱」や半角カナ文字が使われていて、実際には CP50221 であることと似ていますね。 メールの charset が正しく使われてないのは日本だけではないようです。

ということで、charset を GBK にしてみます。

From: =?GBK?B?ob6k6qS9pMrjedDQob8=?= <mp@resona-gr.co.jp>
Subject: =?GBK?B?sb7Iy9VK1F6ltalgpdOluQ==?=

これをデコードするとちゃんと「【りそな銀行】」「本人認証サービス」となりました。

GB2312(GBK) なんかを使わずに UTF-8 にしておけば文字化けもしなかったでしょうに。中国の詐欺メール業者ももうちょっと頑張れ。

文字化けメール

DELL にユーザー登録したらメールが送られてきたんですが、そのメールが文字化けしてました。

文字化けメール研究家としては解析せざるを得ません。

メーラーで見ると Subject が次のようになっていました。

デルアカウントに$4EPO?$$$?$@$-!"$"$j$,$H$&$4$6$$$^$9!

メールのソースを見ると次のようになっていました。

Subject: =?iso-2022-jp?Q?=1B=24B=25G=25k=25=22=25=2B=25=26=25s=25H=24K?=
 =?iso-2022-jp?Q?=244EPO=3F=24=24=24=3F=24=40=24=2D=21=22=24=22=24j=24?=
 =?iso-2022-jp?Q?=2C=24H=24=26=244=246=24=24=24=5E=249=1B=28B=21?=

この Subject の Q エンコーディング文字列をデコードすると次のようになります。(\e は ESC (0x1B))

\e$B%G%k%"%+%&%s%H$K
$4EPO?$$$?$@$-!"$"$j$
,$H$&$4$6$$$^$9\e(B!

この3行を連結すると、ISO-2022-JP で「デルアカウントにご登録いただき、ありがとうございます!」となります。 つまり文字を構成するデータとしては正しいです。

ではなんで文字化けしているかというと、この Subject の記述は2つの点で間違っているためです:

その1

ISO-2022-JP は ASCII と日本語をエスケープシーケンスによって切り替えるエンコーディングです。\e$B で日本語に切り替え、\e(B で ASCII に切り替えます。 MIME エンコーディングではエンコーディングデータの文字セットは ASCII であると定められています。 1行目の先頭は \e$B なので、以降のデータは日本語になりますが、2行目と3行目のエンコーディング文字列には \e$B がないため、ASCII のままです。 そのため、「$4EPO?$$$?$@$-!"$"$j$〜」という文字列が ASCII のまま表れています。

その2

では、2行目、3行目が \e$B で始まっていたらよいかというとまだダメです。 2行目の末尾の $ は「が」の1バイト目、3行目の先頭の , は「が」の2バイト目で、1文字が別のエンコーディングデータに分かれてしまっています。 MIME エンコーディングは文字単位で行う必要があり、文字が分かれてはいけません。

というわけで、メールヘッダのエンコーディングは複雑です。早く RFC6530 が普及して UTF-8 文字がそのままヘッダに書ける世の中になればいいですね。

(その2に続く)