WordPress からのほぼすべての通知を XMPP で送信するプラグイン wp_mail to XMPP

先日、WordPress のフォーラムの話題に触発されて、「コメントが来たことをメール以外で知らせる方法」という記事を書きました。

そのあともう少し一般的に考えてみました。WordPress から多くのメールが届きますが、そのほとんどは単なる「通知」で返信を要しないものです。つまりメールである必要はないのです。ということは、コメントがついた時だけでなく、WordPress からのすべてのメールを XMPP にしてもいいのではないか、と思いました。メールと XMPP は形式がとてもよく似ており、そのまま宛先を変えるだけでいいのではないか、と。

探してみたら、同じ目的の XMPP sender というものが既にありました。しかし中を見てみると古くて、メンテナンスもされていないようです。また、完全に wp_mail() を置き換えているので、条件によっては XMPP ではなくメールで送りたいと思っても応用が効きません。

WordPress からの通知は、更新・ユーザー追加・コメントなど発生源は種々あっても最終的には wp-includes/pluggable.php の中の wp_mail() によってメール発信が行われています。[1] wp_mail() を見てみますと、

function wp_mail( $to, $subject, $message, ...) {
	extract( apply_filters( 'wp_mail', compact( 'to', 'subject', 'message', 'headers', 'attachments' ) ) );
		...
		$phpmailer->Send();
}

という構造になっています。冒頭のフック ‘wp_mail’ は、たぶん送信先の追加や、subject(件名)や message(内容)に一律に何か加工するなどを想定されていると思いますが、その中で XMPP 送信を行ってしまえばいいと考えて、次のようなプラグインを作りました。

wp_mail to XMPP
Send almost all notifications via XMPP instead of email

https://github.com/mako09/wp-mail2xmpp にも置いてあります。

前の記事「コメントが来たことをメール以外で知らせる方法」でざざっと書いて示したプラグインは、その機能が今回のプラグインに完全に含まれますので、忘れてください。

なおこのプラグインは、別のプラグイン XMPP Enabled を利用していますので、それを先にインストールしてください。

きっかけとなったフォーラムの話題のように、複数の投稿作成者がいて誰かが投稿したことを他の作成者たちが知りたい、という場合には、投稿を通知するプラグイン(公式ディレクトリで “post notification” で検索すれば、いろいろ見つかります)、たとえば New Post Notification などをインストールしておけばいいでしょう。そのメール通知も今回のプラグインで XMPP に振り替えます。

wp_mail to XMPP の中身

このプラグインは2つのフックを備えています。

abort_xmpp_sender は、XMPP送信処理を中止するためのものです。たとえば「コンタクトフォームからの送信は、JID の有無に関わらず、メールで送信する」としたい場合、件名やヘッダなどで判定するフィルターを追加すれば、実現できます。

email_to_jid は、メールアドレスから JID に変換するフィルターを呼び出します。デフォルトでは、そのサイトに登録されているユーザーかつそのユーザーがJIDを設定している場合のみ、JID を返すフィルターを設定しています。たとえばコメントをつけた人が入力したメールアドレスのように、JID を見つけることができなければ、その人には XMPP 通知を出しません。このフックにさらにフィルターを追加して、たとえば JID があっても XMPP 通知を出さない人を設定する、あるいは逆に、JID が見つからない場合は管理者の JID に XMPP 通知を出す、などを実現できます。

これらの後に、JID があれば XMPP 通知を、JID がなければメール通知を行います。XMPP に送られる通知では、subject(件名)と message(内容)をそのまま用い、headers(ヘッダー)と attachments(添付)は送信せず無視しています。

オプションで、XMPP 通知を行ったユーザーにもメール通知を行うように設定することもできます。

おわりに

はじめてプラグインを公式に公開しようとしたら、実際に動作するコードの部分を書く時間の10倍くらいを、その周辺のことに費やしました。名前を決めること、コメントや readme を英語で書くこと、依存する別のプラグイン XMPP Enabled を国際化・日本語化して作者に連絡して取り込んでもらうこと……。いやはや、いい経験になりました。それでもコード自体にも英語にも自信がありません。おかしなところを見つけて知らせてもらえたらうれしいです。

  1. ただし Jetpack購読(subscription)は、実際には WordPress.com がメールを送信しているため、これに該当しません。

コメントが来たことをメール以外で知らせる方法

WordPress のフォーラムに次のような話題がありました。

「コメントが来たことを知らせる方法をメール以外でなにかほしい」

現在、ワードプレスを数人で管理し、記事を書いています。

私たちの環境では、ディスカッション設定の自分宛のメール通知のところにある「コメントが投稿された時」にチェックを入れています。

(中略)

メールに気づく派も気づかない派も、各ユーザーがメールの通知以外でコメントに気づくことが出来るようにしたいと思っています。

そこで私は、以前に書いた「Jabber と WordPress」を示しながら、ただし XMPP サーバーを確保できないと難しいので一般的ではないと回答しました。

さて質問文をよく読むと、

  • 投稿にコメントがついたことの通知を受け取るのは、その投稿の作者のみでよい

で済みそうです。一度の通知の送り先は1人でよい、ということであれば、先に示したややこしい仕組みは必要ありません。

やはりインスタントメッセンジャー(IM) の XMPP を利用した通知を行うことにします。

管理者の作業

公式プラグインのサイトから XMPP Enabled というプラグインをインストールします。

さらに次のプラグインをインストールします。これは XMPP Enabled と同じ作者による、投稿のあった際に XMPP で通知する Juick Crossposter というプラグインを、コメントの場合に置き換えて作り直してみたものです。

また管理者は次の節の脚注の作業も必要に応じて行ってください。

【追記】 これを元に、一般化して、プラグインを公開しました。「WordPress からのほぼすべての通知を XMPP で送信するプラグイン wp_mail to XMPP」を参照してください。

各投稿者の作業

先の質問では複数の投稿作成者がいるようです。そこでそれぞれが通知を受け取るためには、各人の PC やスマートフォンなどに XMPP クライアントを用意してもらいます。また、各人に XMPP のアカウント(JID)を取得してもらいます。WordPress.comのアカウントを取得すれば自動的に (アカウント)@im.wordpress.com が JID になるのでそれを利用できます。

さて投稿しているブログサイトで、各人は自分のプロフィールのページにある「Jabber/Google Talk」の欄に取得した JID を入力しておきます [1][2]

これで設定は完了です。コメントがつくと、そのコメントがつけられた元の投稿の作者に XMPP で通知が行きます。

  1. 「Google Talk」とありますが、2013年末現在、Google Talk はサービスがなくなり(Google+ ハングアウトに吸収され)、他の XMPP ネットワークと連絡できなくなりました。そのため Gmail アカウントはここでは利用できません。
  2. なお、つい最近の WordPress を新たにインストールして始めた場合には、この「Jabber/Google Talk」欄がないかもしれません。管理者は、たとえばこのページなどを参考に(そこでは悲しいことにJabber項目の削除として紹介されていますが、ちょうどその逆の操作です)、jabber というフィールド名の項目を追加してください。

昨年のクリスマスのおもちゃ Zometool

たまには子どものおもちゃのことなど。

昨年のクリスマス、4歳のスウちゃん(仮名)の元に届いたプレゼントは Zometool (ゾムツール)。その中の「ゾムツール Creator 1」というキットでした。

家ではダイヤブロック(知人の子からのおさがり)、保育園ではレゴが大好きなスウちゃん。といって同じものを増やすだけでは面白くない、と父である私はサンタクロースの代理人として悩みました。

布団に入ってつらつら考えるうちに、そうだ、組み立ておもちゃでもいま遊んでいるのはボリューム系(中身の詰まったブロックタイプ)だから、逆に骨格系というのはどうだろう、と思いつきました。平面の多角形や立体の正多面体も作れるものがいいぞ、とこのとき頭にあったのは遠い学生時代に見た分子模型です。ノードになる玉を棒でつなぐようなものです。でも小さな子どもでも楽しめるんだろうか、多くの多角形や多面体を作ることができるようにするには、様々な角度に対応できないとならないぞ、するとノードの接続穴はどうなっていればいいだろう、いっそスポンジ玉のように自由に抜き差しできるものなんだろうか、棒もいろんな長さの種類がないといけないな……などと考えながら眠りに落ちました。

[pe2-image src=”http://lh4.ggpht.com/-UQXvI4jgCOw/Uc0l29TTqaI/AAAAAAAACIc/5qYvaSSq7Mg/s144-c-o/DSC_5303_1.JPG” href=”https://picasaweb.google.com/101047809611356552039/20130627#5894409113396554146″ caption=”しゃぼん膜” type=”image” alt=”DSC_5303_1.JPG” ]翌日、あれこれとキーワードを試しながら検索しているうちに見つけたのが Zometool でした。これは私の浅はかな考えをあっさりと超えたすばらしいシステム(ノードは斜方二十・十二面体、棒の長さは黄金比など)でした。これを考えた人はすごいし、これを子どものおもちゃにしているところがさらにすごい。4歳の子どもにはちょっと高度すぎるかなと思いつつも、私のほうがもうすっかり欲しくなってしまいました。

1年前でドル円のレートもまだ有利だったこともあり、アメリカの Amazon で、送料を加えても日本での価格よりずいぶん安く買うことができました。

スウちゃんはたいへん気に入り、付録の作例(PDF)マニュアル(PDF)をまねてさっそくいろいろな形を作りました。そのうち勝手に別の形のものを作っては壊して遊んでいます。夏にはマニュアルにもあるように、石鹸液に浸けて膜を張らせたりして遊びました。

年齢のいかない子どもにとってちょっと難しいのが、いったん挿した棒を抜くことです。このときに棒の挿す部分を折ってしまいがちです。特に赤(断面が5角形)が弱いようです。それが欠点といえば欠点。あとは価格がやや高いこと。

別のおもちゃなどに興味が移って最近でこそ Zometool の出番は減っていますが、数年経って、あるいはそれこそ幾何や分子構造の知識を得る頃にも、また楽しめるのではないかと思っています。

著作権表示の年を自動的に付ける

最近 twitter に流れてくる話を見ているうちに、ふとその隣の記事を読みました。「年末年始恒例、WordPressの著作権表示更新作業をやっと自動化した」。そうか、「年末年始恒例」なのか。

大前提。著作権表示はただの飾りなので、ほんとうはどうでもいいです。

この記事では年号表示を (開始年)-(いま) としています。そもそも著作権表示の全部が要らないのですが、そのなかでもさらに不要と言われている後ろの年、この方法だと「見ているとき」になってしまいますよね。最後の記事を公開したのが2013年のうちで、見る人が見たのが2014年になってからだったら -2014 になってしまいます。やはりここは「公開されたとき」じゃないかと思うのです。

そういえば数年前に別の方の「WordPressで時差分ずれない著作権年号表示の仕方」という、実に細かな心配りの記事を見たときにも、「気にするのはそこじゃないのでは?」と思ったものです。

もう長いこと前に WordPress 1.5 対応の「boxy but gold」というテーマの footer.php にあったコードを見て、これはいいやと思ってそれを改良して、私は次のようにしていました。ただの飾りなのでほんとうはどうでもいいのですが。

<?php
/**
 * Boxy Copyright
 * Provides a dated copyright mark.
 *
 * Author: Kaf Oseo
 * Author URL: http://szub.net/
 *         Copyright (c) 2005, Kaf Oseo (http://szub.net)
 *         Boxy But Gold theme released under the GNU Public License (GPL)
 *         http://www.gnu.org/licenses/gpl.txt
 * Author: Mako N
 * Author URL: http://pasero.net/~mako/
 *         Copyright (c) 2007, Mako N
 */
function get_boxy_copyright($userid=1) {
	global $wpdb, $m, $post, $year;

	if(!$userid || $userid == 0) {
		$copy_owner = get_bloginfo('name'); /* use blog name for copyright owner */
	} else {
		$blog_owner = get_userdata($userid);
		$copy_owner = $blog_owner->user_firstname . ' ' . $blog_owner->user_lastname;
	}

	$first_post_date = @$wpdb->get_var("SELECT post_date from $wpdb->posts where post_status = 'publish' ORDER BY post_date ASC LIMIT 1");
	$last_post_date = @$wpdb->get_var("SELECT post_date from $wpdb->posts where post_status = 'publish' ORDER BY post_date DESC LIMIT 1");
	if(is_single() || is_page()) :
		$copydate = substr($post->post_date, 0, 4);
	else :
		if($m) :
			$copydate = substr($m, 0, 4);
		elseif($year) :
			$copydate = $year;
		else :
			if(substr($first_post_date, 0, 4) == substr($last_post_date, 0, 4))
				$copydate = substr($first_post_date, 0, 4);
			else
				$copydate = substr($first_post_date, 0, 4) . '-' . substr($last_post_date, 0, 4);
		endif;
	endif;
	return ( __('Copyright&#169;') . $copydate . ' ' . $copy_owner);
}
?>

きれいじゃないけど、まあいいや。元作者のページは制限がかかっているようだし、このテーマもいまは手に入らないかもしれません。これも元は後ろの年が単に date('Y') になっていたのですが、それを最新記事の公開時にするなどを付け足したり、適当な名前の関数にしたのは私なので、ふだんは書きませんけど著作権がらみの話でもあることだし、責任の所在を明らかにするためにも自分の名前を入れておきます。

  • 投稿の単独表示や固定ページでは (公開年) になる
  • 年別アーカイブや月別アーカイブでも (公開年) になる
  • その他の複数投稿が表示される場面では (最古投稿の公開年)-(最新投稿の公開年) になる
  • 年の後ろは、作者の名前(またはブログ名)になる

というものです。ここしばらく付けていませんでしたが、この投稿を書くのでこのブログにも一番下に付けてみました。

何度も言いますが、ただの飾りなのでほんとうはどうでもいいです。

WWW 20周年(自分の中で)

今年は World Wide Web、20周年(3回目)だそうですが、自分の歴史の中でもちょうど20周年です。節目でもあるので、覚えていることを書いておこうと思います。

インターネット歴史年表で1993年を見ますと、NCSA Mosaic が公開された年です。何しろこれから WWW という時代ですから、情報元は雑誌(UNIX MAGAZINE)と fjニュースグループでしたが、身近にいた人から直接聞いて、Mosaic の動いているさまを見せてもらったのが大きなきっかけでした。早速 Mosaic を手元のマシン(Sun SPARC Classic を使っていました)にインストールして、はじめて WWW に触れたのは1993年のちょうどいまくらいの季節だったと思います。確か匿名FTPサーバーというのが日本にいくつかあって(Archie というサービスで目的のソフトを探す)、そこからダウンロードしたんだと思います。いま思えば回線はかなり細かったはずです。

それで国立がんセンターの gopher サイトで気象衛星ひまわりの画像を見たり、NTT の「日本のWWWサーバー」(そう、その頃は日本中の「全リスト」が一覧できるくらいの数しか存在しなかったのです)を見ては新しいサイトを見にいったりしていました。

何しろこの年に HTML 1.0 が出たばかりだし、本もなにもありません。Mosaic には現在のブラウザと同様に「View Source」ができたので、簡単なページのソースを見て、真似しながら書いてみたりしたものです。<P> は段落の最後につけて、ほとんど改行という意味だったし(閉じタグ</p>はまだ存在していなかった)、箇条書きの<LI>などにも閉じタグはありませんでした。それにこのように大文字で書くのが習わしでした。それから自分のマシンに CERN HTTPd や NCSA HTTPd をインストールして試したりしていました。SunOS は当時たいへんポピュラーだったので、インストールは苦もなくできたのです。

年が明けて1994年、冬季オリンピックの公式ページが公開されていると知り、会期中よく見ていたのを覚えています。ノルディック複合などで日本勢の活躍が期待された大会で、いち早く結果を知っては周囲の人に見せびらかしていました。

個人がインターネットに接続できる状況(それもダイヤルアップ)になるのは、それからしばらく後でした。

オチもなにもありません。ただの思い出話でした。

テーマ Tewnty Fourteen の日本語化(非公式)

WordPress のバージョンが 3.7 になりました。インストールしてみたところ、次期デフォルトテーマの Twenty Fourteen が含まれていました。ただしバージョンは 0.1 なので、まだ完成形ではないのでしょう。

(追記)すみません。RCまでは含まれていたのですが、正式な3.7には含まれなくなっていました。重ねてインストールしていたので混乱していました。まあ、いいや。そのうちどこかで役立ててください。(追記終わり)

翻訳の作業は、公式には GlotPress で行われるのですが、バージョンが若すぎて、まだここに登場していません。当然、日本語パッケージにも翻訳は含まれていません。

そこで非公式の日本語リソースを個人的に作成しましたので、公開します。

中身は twentyfourteen-ja.po と twentyfourteen-ja.mo だけです。適当な場所 (wp-content/languages/themes/ の下とか)に 置いてください。

大部分は Twenty Thirteen のリソースを取り込み、それから変更されたり追加された分を訳しました。自分でもまだちゃんと使っていないので、どこに表示されるものかも確認しないままやったものもあり、おかしなところがあるかもしれません。

とにかくまだ Twenty Fourteen 自体が発展途上で固まっていないこと、今回公開するものは WordPress 3.7 パッケージに含まれる 0.1 に対応するものであること、元の文もどんどん変更されるかもしれませんし、あくまで非公式な作業なので訳語も変わりうること、やっつけでやったのでまだおかしな訳があるかもしれないこと(“Featured Content” の訳語ってどうしましょうね)、などをご了承の上、ご自由にお使いください。

wp-login.php への攻撃に対処する

サーバーにログインして作業しようとしたら非常に反応が悪く、どうしたことかと見てみると(普段と違って、コマンドを入力してから応答があるまで数十秒も待たされるのでヒヤヒヤしましたが)、大量の apache プロセスが走っていて、ログには wp-login.php への大量のアクセスが記録されていました。最近よく耳にする wp-login.php へのブルートフォースアタックです。

1つの IP アドレスから5,6回のアクセスがあり、すぐに(あるいは同時に)別の IP アドレスからのアクセスがあります。結局、2時間ほどのあいだに数千回のアクセスがあり、その後沈静化しました。

特に対策らしい対策をしていなかったので、パスワードの強度のみで耐えたことになりますが、実際目の当たりにすると気持ちのいいものではありません。それにどちらかというとサーバーに対する高負荷のほうが心配です。

そこでいまさらではありますが、少し手を打つことにしました。

強いパスワードにする

今回行った対策ではありませんが、そこそこ強いパスワードにしていたので、攻撃に気づくまでの30分間ほどはこのことだけで侵入を防ぎました。

.htaccess で制限する

攻撃に気づいてすぐ、 .htaccess に次のように書いて wp-login.php へのアクセスを制限しました。これは効果てきめんで、すぐにサーバーの負荷は通常の範囲まで下がりました。

<Files wp-login.php>
Order deny,allow
Deny from all
Allow from xxx.xxx.xxx.xxx
</Files>

前述のように攻撃側の IP アドレスは絞り切れないので、Deny に列挙することができません。

さて当面はこれでしのいだとしても、正当に管理画面にアクセスするのが複数の人間で、それぞれの接続元の IP アドレスが固定していなかったりすると、Allow に列挙するのもやっかいです。

WordPress のロックダウン

IP アドレスでふるい分けられないとすれば、wp-login.php のところで何らかの制限をかける方法が考えられます。たとえば Force Email Login は、中心となる機能は「ログインをユーザー名ではなくメールアドレスで」という点なのですが、そのほかに

  • ログインに失敗すると、ログイン処理に対して10秒間 wp_die() を発火させて膨大な量のブルートフォースアタックによるサーバー負荷を軽減します。

ということも行います。

こちらの問題のサイトでは「ログインをユーザー名ではなくメールアドレスで」という機能は必要ないので(というか、ようやく「ユーザー名」が何かを理解するくらいの人たちにもう一度ログイン方法を周知徹底するのが面倒すぎるので)、その部分を削ぎ落とすことにしました。

ただ、これだと誰か(攻撃者)がログインに失敗するとその後10秒間は誰も(正当なユーザーも)ログインできません。たとえば5秒に1回、1週間にわたってログインを失敗し続けるという、もはや「ブルートフォース」とは呼べないようなぬるい攻撃(?)で、そのあいだ正当なユーザーのログインを妨害することができるという欠点があります。

やはり「IP アドレスごとに」という機能をつけざるを得ないのかもしれません。そこで、Limit Login Attempts in WordPress … のようなものにすることにしました。これだと、同一 IP アドレスから1秒間にn回のログインの失敗があったらその後m秒間、その IP アドレスからのアクセスを禁止します[1]。ユーザー名/パスワードを試みる回数は、IP アドレスをチェックしないものよりは増えますが、何も対策しないよりはずいぶん減らせるでしょう。

さて、たとえば1秒間に2か所からそれぞれ2回、3回の攻撃があった場合の対応は、この対策を行わない場合、

  • wp-login.php にアクセス → データベースでユーザー名とパスワードを調べる → 失敗
  • wp-login.php にアクセス → データベースでユーザー名とパスワードを調べる → 失敗
  • wp-login.php にアクセス → データベースでユーザー名とパスワードを調べる → 失敗
  • wp-login.php にアクセス → データベースでユーザー名とパスワードを調べる → 失敗
  • wp-login.php にアクセス → データベースでユーザー名とパスワードを調べる → 失敗

となっていたわけですが、これが

  • wp-login.php にアクセス → データベースで IP アドレスごとの失敗回数を調べる → データベースでユーザー名とパスワードを調べる → 失敗
  • wp-login.php にアクセス → データベースで IP アドレスごとの失敗回数を調べる → 失敗(403)
  • wp-login.php にアクセス → データベースで IP アドレスごとの失敗回数を調べる → データベースでユーザー名とパスワードを調べる → 失敗
  • wp-login.php にアクセス → データベースで IP アドレスごとの失敗回数を調べる → 失敗(403)
  • wp-login.php にアクセス → データベースで IP アドレスごとの失敗回数を調べる → 失敗(403)

となります。いずれにせよデータベースにはアクセスするわけで、1回の試行の負荷はどれほど違い、全体での負荷はどれほど違ってくるのでしょうか。そこまでは調べていません。

apache のモジュールで制限する

データベースへのアクセス前に制限できれば負荷を小さくできるのは明らかなので、前述の .htaccess によるものに代わる、apache のモジュールはないか探してみました。

mod_bw は回数制限ではない、mod_dosdetector は特定ページ(wp-login.php)のみに限定できない、mod_dosblock は IP アドレスごとではない……と、どれも一長一短のようです。ModSecurity だと要求にかなっていそうですが、これだけのためには機能が豊富すぎてルールが難しく、まだ理解できていません。

mod_evasive というのは、IP アドレスごとのアクセス回数を制限できますが特定ページの指定はできないようで、 mod_dosdetector と同じです。しばらくのあいだ、これを試してみることにしました。


管理するいくつかのサイトで、実情に合わせて上述の対策のいずれかをとることにしました。

  1. 実際は、1回の失敗ですぐ制限をかけるようにするため、このページのコードを少し修正しました。