FreeBSDでself-hostedなFirefox Sync 1.5

Firefox 29でFirefox Syncのバージョンが上がりました。Firefox Sync 1.5は既存のFirefox Syncと互換性がありません。さすがに既につながっているクライアントはとりあえず使えますが、新しい端末から接続しようとすると新しいバージョンのメニューしかありませんね。

さて、Firefox側が用意しているサーバーは充分安全そう(少なくとも自分で用意するよりは安全そう)だとは思いますが、Firefox Syncサーバーは自分で建てられるので、どういうわけかそういうのを自鯖(やVPS)でやりたくなってしまいますよね。というわけで建ててみるわけですが、FreeBSDだといくつか対処すべき点がありましたので記しておきます。

基本的には、Run your own Sync-1.5 Server — Mozilla Servicesに従えば(virtualenvパッケージを無事に入れることができれば)インストールできるようになっています。しかし、sqlite周りでエラーが発生します。まずはpysqlite2がないと言われますが、easy_installでpysqliteをインストールしようとすると、

src/connection.h:33:10: fatal error: 'sqlite3.h' file not found

と言ってきます。

というわけで、これらに対処した操作手順は以下のようになりました。もとよりFreeBSDに特化した内容ですので、これだけ書けば充分でしょう。なお、FreeBSD標準のcshないしtcshを使っていますので、Bシェル系の場合はactivateの操作でactivate.cshではなくactivateとしてください。

% git clone https://github.com/mozilla-services/syncserver
% cd syncserver/
% gmake build
% source local/bin/activate.csh
% setenv CFLAGS "-I/usr/local/include"
% easy_install pysqlite
% deactivate
% gmake test

あとはsyncserver.iniをいじって、 ./local/bin/pserve syncserver.ini とでもするとサーバーが起動します。このあたりも最初にリンクしたMozillaのウェブページに書いてある通りです。

(7/20追記)上記に関わらず、個人としてはFirefox Sync周りに関しては全てMozillaが用意するサーバーを利用することにしました。従って上記の内容で本当に動作するかは確かめていません。

InkscapeやDrawで大きなファイルを作って分割印刷

さて、写真のように、使えるプリンタで印刷可能なサイズより大きな用紙を使いたいこともあるでしょう。Illustratorを使っている場合には優秀なツールが附属していたような気がしますが、自宅にはそんな高価なアプリケーションソフトウェアはない場合が多いことが多いかと思います。

このような場合、適当に分割印刷したりしますが、余白の扱いがどうなるかよくわからなくて困るところです。まあ頑張ればできるのですが、手間削減のために書いておくことにしました。

LibreOfficeやInkscapeといったオープンソースなソフトウェア、あとは無償配布されているAdobe Readerを用いて分割印刷する方法について、余白周りについて確認したのでメモとしてここに記しておきます。Macで確認しましたが、Windowsでも同様だと思います。

# OSが無償じゃないじゃないか、という声はひとまず聞かなかったことにします。まあ、Linux版のAdobe Readerでも同じだと思いますが。

まず、プリンタで印刷可能な余白に余裕をもたせた上で実際に印刷可能なサイズを決定しましょう。例えば、A4は 210mm x 297mm ですので、余白が上下左右各10mmのプリンタでA0印刷(A4 x 16枚)を行いたい場合は 760mm x 1108mm が印刷可能な領域となります。「重なり」を設定したい場合は更にその分を減ずる必要がありますが、今回は余白あり、重なりなしを想定します。

InkscapeやDrawなど、ポスターの編集ないし最終的な出力で使いたいソフトウェア上で、ドキュメント設定/ページ設定から紙の大きさをカスタム(任意サイズ指定)にし、実際にサイズでのドキュメントを作成しましょう。余白は0mmにします。また、背景色は紙の色と明白に区別がつく色にしましょう(それをすることがコストその他の理由でできない場合は他の方法を検討しましょう)。

そのソフトウェア上で印刷したいものを原寸大で作成し、PDFで吐き出します。印刷したい紙より微妙に小さい謎の大きさのPDFができますね。

そのPDFをAdobe Readerで開きます。左下の「ページ設定」ボタンから正しいプリンタを選択しましょう(どうもダイアログ上部の「プリンター」と独立なようです)。その上で、ページサイズ処理で「ポスター」を選択しましょう。倍率が「100%」になっていることも確認しましょう。

予期した通りの枚数でなければなにがおかしかったか考えましょう。多分予期した通りの枚数になると思います。

この状態で印刷ボタンを押せば、原寸大での出力が行われるようです。

印刷された紙から、共通の各2辺をはさみやカッターで裁ち落とします(背景色が紙と明確に区別がつく理由があるのはこのためです)。2辺は隣り合った辺を選ぶ(平行な2辺を選んではいけない)こと、印刷した全ての紙に対して同じ2辺を選ぶこと(1ページで右と下を裁ち落としたなら2ページ以降も右と下を裁ち落とさなければなりません)に注意しましょう。

そうすると、全ての接合が必要な辺に対し、余白がある部分と余白がない部分が噛み合うようになります。あとは気合いで精度よく貼りあわせましょう。可能ならメンディングテープなどをうまく使って位置を合わせた上でクリスタルテープで裏から貼りあわせましょう。

また、当然ですが、この方法で分割印刷した場合には貼り合わせの精度の影響が出やすいので、分割印刷の境界線上に何が印刷されるかについては細心の注意を払いましょう。Adobe Readerのプレビュー画面では実際に印刷される場合のページの境目がどこに来るかを確認することができます。小さなものがそこに含まれると隙間に飲まれて認識できないほど違った形状になる可能性があります。

ところで、冒頭の写真のそれはそういうことをきちんと調べる前に印刷したものです。基本的なやりかたは似ていますが。

何はともあれ、ひとまずこれが通常のプリンタで大きな紙を印刷する簡便な手法として手っ取り早いと思います。A4で印刷してもしょっぱいなぁ、と思う時にお試しください。

Postfixにおけるメールサーバー間の暗号化

メールは平文でやりとりされるから安全な手段ではないとよく言われます。
まあ、メールという手段がそれほど安全なわけではないのは事実であるのは、今もそれほど変わってはいないように感じます。

メールサーバーとクライアントの間の、submission(メール送信)やPOP3/IMAP(メール受信)は認証を伴うのと、単にユーザーが対応すればよいので、国内大手ISPはともかくメールを主とするサービスであればフリーメールでも暗号化するのが当然となってきました。一方で、メールサーバー間のやりとりは平文でのやりとりで危険とされてきました。まあ、その認識は誤ってはいないのですが、一方でそこを暗号化する規格は今のところないと思っていました。ところがある日、メールヘッダを眺めていたら

Received: from www****.sakura.ne.jp (www****.sakura.ne.jp. [2403:********])
        by mx.google.com with ESMTPS id ******
        for <***@nhiroki.net>
        (version=TLSv1 cipher=RC4-SHA bits=128/128);
        ***, ** *** 2013 **** -0800 (PST)

なんでだいぶ前のメールのヘッダを改めてみたのかはともかく、またさくらとGMailの間のやりとりがIPv6だ!というのもともかく、

(version=TLSv1 cipher=RC4-SHA bits=128/128);

んんんっ?

これは暗号化されてやりとりしているのでは?

と思って調べてみたところ、RFC3207で暗号化してSMTPを通信する手段が規格となっており、EHLOコマンドの返答としてSTARTTLSの可否を通知、そこからTLSで暗号化して通信する手段があるとのこと。単に僕が無知だっただけか。

メールサーバー間で暗号化することができれば、メールサーバー群内はともかく、インターネットを利用した通信は基本的に暗号化され、それなりのセキュリティが保たれることになります。もちろんメールサーバー上や、構成によりリレーが多段階に渡る場合はその間のやりとりは(内部ネットワークなど盗聴の心配がないネットワークなのでしょうが)平文になる可能性がありますし、そもそも暗号化をサポートしていないメールサーバーはたくさんあるので想定する必要がありますし、当然メールサーバーの運営者が悪意を持てば盗聴はできるでしょうが、そのあたりさえ信用できればインターネットを通しても安全が保たれることになります。S/MIMEやPGPなどのend to endな暗号化と比べると劣るのは事実ですが、通常利用には利便性と安全性のバランスがとれているといえるでしょう。

というわけで、これを自鯖のPostfixでも実現したいですね。

先に書いておくと、Postfix TLS サポートというウェブページの内容をほぼ参考に自鯖に設定しました。こちらのページを読むといいでしょう。

まず準備として、SSL自体の設定はしてください。submissionを465ポートのSSLとか587ポートからのSTARTTLSで暗号化する方法はあちこちのウェブページにあるでしょう。もちろん、 submission であれば自分しか使わない場合はオレオレ鍵で充分ですが、通常メールは外から送らせるのでオレオレ鍵はダメです。Let’s Encrypt が無料で利用できる時代なので、公開サーバーでオレオレ鍵という選択肢はありませんね。一応、オレオレ鍵の場合は送信時のみで受信は平文という選択肢はあります。(2018/3/28 StartCom, Let’s Encrypt 関連情勢を反映して記事更新)

その後の設定は簡単です。main.cfに

smtpd_use_tls=yes
smtpd_tls_received_header = yes
smtp_use_tls=yes

と書きましょう。その後postfixを再起動しましょう。これで完了です。

一、二行目は受信時、三行目は送信時です。テストする場合は別々にしましょう。また、オレオレ鍵しか用意できなかった場合は三行目だけにしておきましょう。

25ポートにつないでEHLOの返答でSTARTTLSが返ってくることや、受信時にtcpdumpしてもSTARTTLSコマンドを確認した後が暗号化された謎のバイト列がくることを確認したりするといいでしょう。また、GMailは暗号化したやりとりはReceivedヘッダでわかるので送信する方はGMailに送るのもありです。

S/MIMEやPGPほどではありませんが、ローコストでそれなりのセキュリティが実現されることになります。とはいえGMailやさくらのメールボックスなどの対応メールサービスのみですが……

また、Postfix TLS サポートには特定サーバーのみポリシーを変える方法も書かれていました。安全性を高めるには、やりとりする相手のメールサーバーについて、平文で接続しないように設定するとよいでしょう。

現実的には暗号化以外受け入れない設定に誰もがするのが安全なので、個人的には、こういうのは大手どこかが「○○ヶ月後からうちは暗号化してないと受信しない」と宣言すれば随分世界中のメールが安全になる気がしますが、まあ現実そうもいかないのでしょうね。

(追記)

これを実施しても、 DNS の MX レコードは正しいことを前提としていたり、そもそも STARTTLS が可能であることは平文で署名なくやりとりしたりしているため、(例えば)接続時点で https であることを指示し、署名されているべきホスト名も URL で指定する HTTPS と同等の安全性にはなりません。

現状でメールを使う場合で確実に暗号化された手段を使うには、送信者と受信者が同じメールサーバーを使うか、必要な相手に対してポリシーを設定するか、どちらも特定の相手にしか使えないので結局はPGPかS/MIMEという結論になりますね。STARTTLSの使用可否を安全な方法で通知できる仕組みとかあったらいいんですが。まあ、暗号化がどうしても必要な用途には電子メールは使うな(or PGPやS/MIME使用)、という原則に立ち返りますね。

とはいえ、改竄を伴わない盗聴と改竄では、技術的にも盗聴されていることに気付かれるかどうかという点でも、大きくハードルが違うでしょう。そのあたりのリスクと利点をどうとるかは運用の判断となります。

(追記)この記事はいくつか執筆後の加筆・修正があります。

サーバーのネットワーク設定にご用心

みなさま、あけましておめでとうございます。

さて、新年早々大変残念なミスをやらかしたのでここに。

自宅にあるUbuntuがルーターにもなっていたのですが、先ほど自宅から外へのDNS解決ができなくなっていたので(dnsmasq、LAN内ホストの分は可能だった)ので/etc/resolv.confを見たところ127.0.0.1しかなく、DNSアドレスはPPP接続時に受け取る仕組みになっていたので

$ sudo ifconfig ppp0 down;sudo ifconfig ppp0 up

すると

Write failed: Broken pipe

沈黙しました。外からつながりません。多分中からも繋がっていないと思います。しかも、帰省していて物理的に触れないのでサーバーが外から完全に沈黙してしまったおち。不便。

こういう時(PPP接続をインターネット越しにやり直したい時)はどうやるのが正解なんでしょうか。

(2014/1/4 20:11追記) ifconfig ppp0 downしたあとpon [設定した接続先名]だったらいけたみたいです。どちらにしてもscreenには入れるべきでしたね。

ownCloudのインストール

そろそろ12月も下旬、進捗どうですか。

それはさておき、DropBoxとかと近い使い方ができて、自鯖で動かせるownCloudをFreeBSDにインストールしてみたので、それについて。

ownCloudのサーバーですが、Linuxの主要ディストリビューションの他、phpで書かれたスクリプト群なので解凍して即WWWサーバーに食わせられる圧縮書庫も配布されています。まあいずれを使う場合にも導入については公式ドキュメントを見るのがよいでしょう。

適当に解凍(し、.htaccessが有効でない状態やそもそもLinux/Apacheじゃない場合はマニュアルに従って設定を修正)すれば、パーミッションさえ合っていればとりあえず動くでしょう(この文を見てインストール作業にとりかかる前にこの記事を全文読むことをおすすめしますが)。

さて、遭遇した事象について。基本的にとりあえず現状(クライアント:v1.5.0、サーバー:6.0.0a(stable))についてです。

まず、No uploads possible – Insufficient storage · Issue #5163 · owncloud/core · GitHubにかかれているのでじきに修正が来そうな気がしますが、途中でディスク容量がNaNとか-1になり、クオータの計算ができなくなってアップロードができなくなるバグについて。現状ではこのバグを踏むことになるので、ユーザーのクオータはUnlimitedに設定しましょう。こうするとバグを回避できるようです。

また、MySQLに色々保存するような構成にするとなぜかエラーが色々でました。SQLiteを使用するとかなり現象したので、そちらの方がよさそうです。

あとは、ひとつのディレクトリ(フォルダ)に1000とか馬鹿みたいに大量のファイルがある環境では重くなるようです。これもownCloud Forums • View topic – Uploading very slowなどに掲載されていますからそのうち修正されるでしょうか。現状ではそういうディレクトリを共有するのはやめたほうがよさそうです。

また、このソフトウェア、特に日本語のウェブページだと、「VPSなどで使用する場合も、暗号化プラグインを有効にすることで解読されません!」的な説明がよくあるのですが、それは誤りとするほうが現実に近いでしょう。ストレージに保存する際にサーバサイド暗号化にするプラグインは確かにありますし有効化していますが、

  • 暗号化に使用する鍵は、ユーザーがログインした際にサーバーのセッション変数に保存される。セッション変数はphp.iniのsession.save_pathの設定のディレクトリに保存されるため、ユーザーがログイン中、あるいはログイン後にログアウトボタンを押さずに去った場合は鍵がサーバー上に残る
  • 画像表示用のキャッシュなどは平文で保存される
  • そもそもサーバー上で暗号化/復号化するので、原理的にはサーバーに侵入された場合を想定した場合を考慮すれば安全とはいえない。サーバーに侵入されないなら、おそらくそもそも安全である(もちろん両者の差はあります)

など、SparkleShareなどに見られるクライアントサイド暗号化とは性質を異にするものです。公式のドキュメンテーションの当該項目にも、これは外部ストレージを使用するプラグインを使用する場合の安全を保つためのものであり、サーバーそのものが信用できない場合の安全を保護するものではないことが書かれています。もちろん、VPSで普通に使う場合でも暗号化したほうが解読はめんどくさくなるでしょうし、セッション変数の保存先をRAMディスクに設定すれば侵入の状況によっては更に難しくなるのもまた事実でしょう。

色々書きましたが、こうやって色々なブログに書かれている記事を読みながら自分でどうにかできる人で、通常のファイルを気軽に複数端末で共有、あるいはバックアップしたい人にはよい選択肢と言えるでしょう。一方で、パスワードや秘密鍵の管理など、高いセキュリティが求められる用途には向きませんし、そうでなくともトラブルが起こった時に自分でどうにかすることができないのであればおとなしく著名なサービスを使ったほうが無難でしょう。このソフトウェアについては、今後に期待したいと考えています。

また、インストールですが、パッケージで入れるのが簡単でしょう。もっとも、FreeBSD上のApache 2.4で走るphpプログラムの権限変更(suphp)(以前の記事)で書いたように多目的のサーバーにインストールするためsuphpでユーザー権限を分離したい場合はmod_phpが使えませんから、php.iniでアップロードやPOSTの容量を修正したり、session fixation対策を行ったりといった作業は必要になるでしょう。自宅にVMがあり仮想マシンが自由に使える場合ならいいのですが、VPSで使いたい場合は色々ごちゃごちゃはいったVPSの余った容量を使用する形式でしょうから、suphpを使うのが安全と言えるでしょう。

高いセキュリティが要求される場合については、どれがいい、と書くことは難しいですが、ownCloudは向かないように思います。SparkleShareなどのようにクライアントサイドで暗号化されるアプライアンスと比べるとどうしてもセキュリティ的に劣ると言わざるを得ないでしょう。これについては各自で見つける必要がありましょう。suggestionがあればコメント欄にお書きください。

あとは悩ましいとすれば、クライアントのソフトウェアはUIを見る限り複数アカウントを一つの端末(と端末上でのアカウント)で使用することをサポートしていないように見える点でしょうか。もっとも、sftpを外部ストレージとして使用できるようですので、例えば「VPSストレージをメインとするが、信頼性が低めなものの大容量な自宅鯖もマウントする」ことも可能かと思われます。未検証ではありますが。

初期設定で全て同期し始めるのは問題ではありますが、それを一旦中止して望むディレクトリのみ改めて特定の子ディレクトリのみ共有設定することはできますから、Windows 7/8(.1)タブレットやUMPCなど、ストレージ容量が限られた端末からも必要なファイルを共有することは可能でしょう。

長々と書きましたが、なんだかんだと最後まで読んでしまったあなたはこれを導入できるでしょうから試してみてもいいのではないでしょうか。

FreeBSD上のApache 2.4で走るphpプログラムの権限変更(suphp)

表題の通り、Apache上で走るphpの権限を変える方法です。

最近だと、単目的で一つの仮想マシンを作ることが増えてきたのですが、一方で個人用でVPSをとりあえず借りて色々ごちゃごちゃと入れて使っている場合もあり、そういう場合はownCloudや重要なプロジェクトのWikiなど、扱う情報の重要度が高いプログラムだけでも権限を分けておきたいと思うことがあると思います。

suExecはバーチャルドメイン単位でしか使えませんし、そもそもphpにはそのままでは使えませんので(もちろん色々設定してsuExecを使う方法もあります)、suphpというモジュールをインストールすることにします。

ところが、suphpの0.7.2はportsにあるのですが、Apacheのバージョンが2.2以下じゃないとコンパイルできません。調べた所、いくつかパッチを当てれば動くので、手でコンパイルして入れることにしましょう。当然、portsのアップグレードでアップグレードされないので、最新版が出ているかどうかは別途自分で確認する必要があります。

さて、$ sudo su -: Compile and Install Apache-2.4.x + PHP-5.4.x + Suphp-0.7.1に、Linuxでの手順ですがApache 2.4でsuphp 0.7.1を動かす方法が書かれています。suphp 0.7.2、FreeBSDでもほぼこのまま行けましたのでこのままいきます。

基本的にはダウンロードしてパッチを当ててconfigureしてmake, make installです。途中でaprのincludeファイルをapache24ディレクトリにln -sする、あまり気分のよろしくない手順もありますが。

$ wget http://www.suphp.org/download/suphp-0.7.2.tar.gz
$ tar xzvf suphp-0.7.2.tar.gz

configure.acの修正点

AC_CONFIG_FILES([Makefile src/Makefile src/apache/Makefile src/apache2/Makefile])

の行を次のように

AC_CONFIG_FILES([Makefile src/Makefile src/apache2/Makefile])

src/Makefile.amの修正点

if COND_AP13
  MAYBE_AP = apache
endif

を削除

SUBDIRS = $(MAYBE_AP)
DIST_SUBDIRS = apache apache2

SUBDIRS = apache2
DIST_SUBDIRS = apache2

のように

以上の点を修正したら、configします。それぞれのバイナリの場所をwhichで調べて違ったら修正してください。Apacheがwww以外のユーザーで走っている時も修正してください。

$ ./configure \
--with-logfile=/var/log/suphp.log \
--with-apxs=/usr/local/bin/bin/apxs \
--with-php=/usr/local/bin/php-cgi \
--with-apr=/usr/local/bin/apr-1-config \
--with-setid-mode=paranoid \
--with-gnu-ld \
--disable-checkpath \
--with-apache-user=www

走りきったでしょうか。

続いて、includeが足りないと言われる件をどうにかします。あまり気分がよろしくないのですが、

sudo ln -s /usr/local/include/apr-1/* /usr/local/include/apache24

とかしてしまいます。もしこれなしでmakeが通るならそれに越したことはないので、先にこれなしでやってみるべきかもしれません。一応今回シンボリックリンクを貼ったファイルは全部aprで始まるようなので、完了後にapr*と指定すれば消すことはできます。

$ make
$ sudo make install

インストールされると思います。

/usr/local/etc/suphp.confを書きます。

[global]
;Path to logfile
logfile=/var/log/suphp.log

;Loglevel
loglevel=info

;User Apache is running as
webserver_user=www

;Path all scripts have to be in
;docroot=/var/www:${HOME}/public_html:/home/httpd/html/

;Path to chroot() to before executing script
;chroot=/mychroot

; Security options
allow_file_group_writeable=false
allow_file_others_writeable=false
allow_directory_group_writeable=false
allow_directory_others_writeable=false

;Check wheter script is within DOCUMENT_ROOT
check_vhost_docroot=true

;Send minor error messages to browser
errors_to_browser=false

;PATH environment variable
env_path=/bin:/usr/bin:/usr/local/bin

;Umask to set, specify in octal notation
umask=0022

; Minimum UID
min_uid=0

; Minimum GID
min_gid=0

[handlers]
application/x-httpd-php="php:/usr/local/bin/php-cgi"
x-suphp-cgi="execute:!self"

最後にApache側の設定をする……のですが、その前に、phpファイルの所有者とグループは実際に走らせるユーザーのものになっていなければならないという制約があるので、先にchown, chgrpコマンドで目的のものに変更しましょう。これはconfigureの段階で変更することもできるのですが、危険なのでやってはいけないとsuphpのドキュメンテーションのインストールのところの–with-setid-mode=MODE:のところに書いてあります。理由については触れられていませんが、.htaccessを自由に使える設定の場合そのファイルの所有者が好きな権限で走らせることができるからでしょうか。

httpd.conf内の、php5モジュールを読み込む行をコメントアウトします。

/usr/local/etc/apache24/Includes/suphp.confに以下のように書きます。

oadModule suphp_module libexec/apache24/mod_suphp.so
suPHP_Engine on
AddType application/x-httpd-php .php
suPHP_AddHandler application/x-httpd-php
suPHP_UserGroup www www

もちろん、デフォルトでwww以外のユーザー/グループで走らせたい人は変えてもよいでしょう。実際に、別のシステムアカウントを作ってそちらにすることにしました。

権限を変えてphpを走らせたいところの設定ファイルに

suPHP_UserGroup user group

を書きましょう。

また、apache2 – suphp error Directory / is not owned by admin – Server Faultに書かれているように、親ディレクトリを遡っていった時にrootでもそのユーザーでもない所有者のディレクトリがあった時に警告が出て実行できないそうです。そのユーザーのフォルダのすぐ上のフォルダはrootを所有者にしておきましょう。

余談ですが、先ほど登場したシステムユーザーの追加はFreeBSD/ユーザーの追加 – BugbearR’s Wikiを参照してください。UIDを1000未満、ログインシェルをnologin、ホームディレクトリを/nonexistent、パスワード認証なしにすればよいそうです。

これで、phpのユーザーをアプリケーションごとに走らせることができました。もちろん、別の仮想マシンを使ったりjailを利用したりするほうが安全ではありましょうが、そこまでするのも仰々しいなぁ……と思う場合にはバランスのとれた選択肢なのではないでしょうか。

# にしてもportsのコンパイラもapache24に対応してくれないかなー。

ルーターで競技プログラミング

この記事は、Competitive Programming Advent Calendar Div2013(← リンク先が閉鎖したのかスパムと化していたのでリンク削除済み)の11日目の記事です。

さて、以前にTeXで競技プログラミングは可能か?という記事を見たことがあります。TeXで競技プログラミングはジャッジサーバーさえあれば可能であろうという結論でしたが、ではルーターではどうでしょう。

というわけで、今回はルーターで競技プログラミングを行うことは可能か?について考えていきます。他の日のみなさんが書いているような有益な記事を期待している方には申し訳ありません。また、そのため、競技プログラミングの知識は必要ありません。

まずは入出力について考えましょう。ルーターで扱える形式でなければなりませんので、今回は情報のやりとりにUDPのパケット(IP(インターネット・プロトコル)上で情報をやりとりする単位)を用い、情報はそのポート番号を用いることにしましょう。ポート番号は本来、サービスの種類を識別したりするもので、パケットごとに行き先のポート番号が設定されており、それを見ながら必要なアプリケーションにパケットを渡したり、

たくさんのルーターの間でポート番号を情報としたパケットをやりとりすることで、処理が行えるかどうかについて考えます。

さて、まずはルーターとして何を使うか決めましょう。ルーターといってもそのへんで売っているようなブロードバンドルーターだとつらそうな気がしますし(可能かどうかはわかりませんが)、どちらにしても数をたくさん用意するのは大変そうに見えます。ということで、Linuxのiptablesを用います。iptablesはLinuxをルーターとして使ったことがある方なら聞いたことがあるかもしれませんが、この記事はiptablesを見たことがないことを一応前提として書くことにします(ソースコード一行一行の説明はしないので、ソースコードは無視してください)。

iptablesはLinuxカーネルの機能として存在するパケットフィルタを設定したりするためのツールですが、便宜上、パケットを制御する機能をiptablesが持つものとして扱いましょう。iptablesでは、到着したパケットの行き先のIPアドレスやポート番号を書き換え(てその後のルーティングで目的地に迎えるようにする)たり、破棄したりといったことができます。また、フラッグを記憶することができるので、例えば不正アクセスを試みようと一分間に何回もssh(遠隔操作に用いるプロトコル)のポートにアクセスしてきたりする人を弾く、といったことや、特定のポートを事前に叩くことで別のポートを開ける、といったこともできます。これはメモリ上の変数として利用できますね。

今回はiptablesのみで実現可能なもののみを行う、ただしiptablesのみで実現可能だがより短く書くために外部コマンドを使うのはありとします(iptablesの実行は初回一回走るのみで、計算時間には関係ないので……)

ではまず、環境を構築しましょう。余計なパケットが飛び交っていると色々面倒なので、専用のネットワークを用意します。今回は家のESXi上に仮想スイッチを用意し、全ての仮想マシンをつなぎました。家にESXiがある人は画面の「構成」から「ネットワーク」を選択し「ネットワークの追加」をクリックすることで追加できますし、ESXiではないものの似た機能を持つソフトウェアであれば似たような方法でできますが、家にはそういうものはないなぁ、という場合で、かつこのページの内容をどうしても再現した場合はさくらのクラウドやAmazon EC2などのクラウドサービスを利用しましょう。
ESXi上に追加された仮想ネットワーク

続いてこのネットワーク(と便宜上インターネットに繋がるネットワーク)に接続したUbuntu仮想マシンを何台か作りましょう。画像では7台ありますが、実際には5台しか使いませんでした。それぞれのメモリ割り当ては最低限で大丈夫です。

それぞれに、プライベートIPアドレスを割り当てて、このスイッチ上でパケットをやりとりできるようにしましょう。今回は、5台それぞれに192.168.150.1から192.168.150.5を割り当てました。それぞれのマシンに手動で設定しましょう。

それぞれのマシンではIPアドレスに加えて、IPの転送を許可する設定をします。Ubuntuの場合は /etc/sysctl.conf にある「net.ipv4.ip_forward = 1」のコメントアウトを外した後、rootで「sysctl -p /etc/sysctl.conf」します。

さて本題に入りましょう。

今回は問題として、Topcoder SRM 596のDiv 1の250点問題を取り上げます。問題の解説が目的ではないので詳しい解説は省略しますが、入力それぞれについて2進で表記した時に立っているビット数の合計と、それぞれについて2進で表記した時の桁数の最大値の和から1を減じたものが答えとなります。

では、解いていきましょう。

まず、答えの入出力を行うノードを決めます。192.168.150.1のマシンにしました。このマシンから問題を含むUDPのパケットを飛ばし、問題を解くことを指示するパケットを飛ばし、答えが返ってくるか試してみます。
パケットは、

$ nc -u <宛先IPアドレス> <宛先ポート>

とコマンドを書き、Enterを一回押すととりあえず飛びます。飛んだら、Ctrl+Cを押してncから抜けましょう。

パケットの送受信を確認するために、パケットを傍受する必要があります。

$ sudo tcpdump -i eth1

などとコマンドを書くことで、そのコンピュータ上でやりとりされているパケットを見ることができます。eth1の部分はマシンごとに合わせてください。

まずは、「入力それぞれ2進表記した際に立っているビットの合計」を求める事を考えましょう。これは192.168.150.2に担当させます。
2進表記した時の立っているビットの数については、今回めんどくさいので埋め込むことにしましょう。
iptablesではフラッグを叩いた回数を記憶することができるので、別のノードに一旦叩いて欲しい回数を指示するパケットを投げ、1ずつ減らしていって0になるまでそのフラッグを叩くことにします。話は少し変わりますが、この方法で繰り返し処理も実現可能なように見えますね。

「2進の桁数の最大値」については、パケットが来たら桁数に該当するフラッグを叩き、解答を求められた時にフラッグを上から見ていくことで実現できます。

最後にその二つを足すことを考えましょう。一つパケットが到着したらそのパケットのフラッグを記憶し、次に到着したらそのフラッグを元に目的の値のポートで、答えを求めているノードに投げましょう。

方針が定まりました。実際のソースコードです(面倒くさいと思うので読み飛ばしてください)。

192.168.150.2では以下のものを実行しましょう

#!/bin/bash

iptables -F
iptables -t nat -F
iptables -X

modprobe ipt_recent ip_pkt_list_tot=200

iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT

for((i=100; i>=1; i--))
do
	iptables -A PREROUTING -p udp --dport `expr $i + 32768` -m recent --name MEMORY --set -t nat -j DNAT --to 192.168.150.4:`expr $i + 32768`

	iptables -A PREROUTING -p udp --dport `expr $i + 16384` -t nat -j DNAT --to 192.168.150.4:`perl bits.pl $i 32769`

	iptables -A PREROUTING -p udp --dport $i -t nat -j DNAT --to 192.168.150.3:$i
done

for((i=100; i>=1; i--))
do
	iptables -A PREROUTING -m recent --name MEMORY --rcheck --seconds 60 --hitcount $i -p udp --dport 40000 -t nat -j DNAT --to 192.168.150.5:$i
done

だたしbits.plは

#/usr/bin/perl

my $ret = 0;

for(my $i=0; $i<20; $i++){
	if($ARGV[0]>>$i& 1){
		$ret++;
	}
}

print $ret + $ARGV[1];

192.168.150.3

#!/bin/bash

iptables -F
iptables -t nat -F
iptables -X

iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT

for((i=100; i>=1; i--))
do
	iptables -A PREROUTING -p udp --dport $i -m recent --name MEM`perl highest.pl $i 0` --set -t nat -j DNAT --to 192.168.150.2:`expr $i + 16384`
	iptables -A PREROUTING -m recent --name MEM$i --rcheck -p udp --dport 40000 -t nat -j DNAT --to 192.168.150.5:$i
	echo $i
done

ただしhighest.plは

#/usr/bin/perl

my $highest = 0;

for(my $i=0; $i<20; $i++){
	if($ARGV[0]>>$i& 1){
		$highest = $i;
	}
}

print $highest + $ARGV[1];

192.168.150.4では

#!/bin/bash

iptables -F
iptables -t nat -F
iptables -X

iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT

for((i=32868; i>=32768; i--))
do
	iptables -A PREROUTING -p udp --dport $i -t nat -j DNAT --to 192.168.150.2:`expr $i - 1`
	echo $i
done

192.168.150.5では

#!/bin/bash

iptables -F
iptables -t nat -F
iptables -X

iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT

for((i=100; i>=1; i--))
do
	iptables -A INPUT -p udp --dport 50000 -j DROP
done

for((i=100; i>=1; i--))
do
	for((j=100; j>=1; j--))
	do
		iptables -A PREROUTING -p udp --dport $i -m recent --name FIRST$j --seconds 60 --rcheck -t nat -j DNAT --to 192.168.150.1:`expr $j + $i`
	done

	echo $i
	iptables -A INPUT -p udp --dport $i -m recent --name FIRST$i --set  -j DROP
done

実行してみましょう。(18, 16, 14)を投げるので、答えは10になるはずです。仕様は省略しますが、今回の実装では解答を得るためには二箇所のノードにパケットを投げる必要があります。

nhirokinet@testvm1:~$ nc -u 192.168.150.2 18

^C
nhirokinet@testvm1:~$ nc -u 192.168.150.2 16

^C
nhirokinet@testvm1:~$ nc -u 192.168.150.2 14

^C
nhirokinet@testvm1:~$ nc -u 192.168.150.2 40000

^C
nhirokinet@testvm1:~$ nc -u 192.168.150.3 40000

^C

事前に走らせておいたtcpdumpから該当するパケットを探しましょう。

22:53:04.278019 IP 192.168.150.1.55863 > 192.168.150.1.10: UDP, length 1

お、正しい答えが返ってきましたね。

あまりまとまっていませんが、そろそろまとめに入らないと日付が変わってしまうので、まとめに入ります。

とりあえず、ルーター(iptables)のみを用いて、計算を行うプログラムを書くことができました。より多くのノードを用いることにより、またより多くのフラッグを用いることによって、また16ビット以上の数字についても前半後半に分割することによって、一応複雑な計算もできるものと思われます。

しかし、ルーターはやはりパケットのルーティングに用いるべきであり、計算に使うにはあまり実用的でないように感じます。どうしても部屋にプログラマブルな装置がルーターしかなく、その環境で計算が行いたい場合でも、ルーターでプログラムを書くよりは、ポケットコンピュータを買いに行くか手で計算したほうが実用的だと思われます。

また当然ですが、おそらく世の中にはルーターを用いて競技プログラミングを行うようなジャッジサーバーは存在しないと思われるため、これで競技プログラミングをすることは現状ではできません。誰かが将来的に作ればもしかしたらできるようになるかもしれません。

こんな記事ですが、コメントや追加実験等あれば是非コメント欄にお願いします。

MacのThunderbirdの通知先を通知センターにする

MacのThunderbirdの通知先を通知センターにするプラグインを検索して見当たらなかったのですが、Growl New Message Notificationというプラグインのgrowl通知をわずかにいじるだけで通知が通知センターにくるようにできました。Thunderbirdのデスクトップ通知と比べるとその通知をクリックしてもそのメールは開かれないという欠点はあるのですが(修正可能な気がするのですがとりあえず放置)、通知センターに統合されるメリットがあります。別プラグインとして公開できる体裁を整えるには変更しなければならない部分が多そうでめんどくさかったので、最低限書き換える方法を書いておきます。

Growl New Message Notification0.4.2現在の情報です。バージョン違いで細部や行数が異なる場合は空気を読んでください。

terminal-notifierを使うのでこちらをインストールします。

$ sudo gem install terminal-notifier

さて、Growl New Message Notificationをダウンロードします。まだインストールはしません。
これを解凍します。

$ unzip growl_new_message_notification-0.4.2-tb-mac.xpi

中のファイルの、 chrome/content/growlcommand.js を編集します。

5行目にある、実行ファイル名を指定しているところを書き換えます。

exec.initWithPath("/usr/bin/terminal-notifier");

rbenvを使用している場合などは場所が違うかもしれません。which terminal-notifierとかして調べてください。

18行目にある、引数指定(argsへの配列の代入)を書き換えます。まあterminal-notifierのオプションなのでお好みでいじってください。

var args = ["-message", message, "-sender", "org.mozilla.thunderbird", "-title", title, "-open", "open -a Thunderbird"];

最後に、プラグインのUUIDや名前が同じなのは紛らわしいので、これを入れた後にもとのGrowlプラグインを入れることはないと思いますが、この二つぐらいは変えておきましょう(手順としてではなくプラグインとして公開するならもっとちゃんと色々いじったほうがよいでしょう)。install.rdfを書き換えます。

$ uuidgen

してUUIDを得て、{}で囲んだものをidのところに書きます。以下は一例。

<em:id>{7B6A61CB-E618-4E5F-A030-2C289ECB0454}</em:id>
<em:name>Growl New Message Notification (Notification Center Version)</em:name>

最後にパッケージ化

$ zip -r growl.xpi chrome defaults install.rdf skin

あとはできたzipをThunderbirdからアドオンマネージャを開いてドラッグアンドドロップしてインストール、Thunderbirdを再起動、Thunderbirdの環境設定からデスクトップ通知を無効にしてどこかからメールを送って通知がくれば成功です。

ですが、thunderbird-binコマンドの-mailオプションで適切なURLを投げればそのメールを開いてくれるそうなので、これについてちゃんと調べてプラグインを修正すればクリックしてそのメールを開くこともできそうですね。

iptablesでLinuxをルーターに

表題の通り。といっても設定ファイルをいじりつつiptablesのスクリプトを書くだけなのですが。とりあえずの今の設定ファイルをコピペして編集したメモなので動かなかった等あればコメントお願いします。まあ、自分用にまとめただけで別に似たようなことを書いたウェブページは世にいくらでもあるのでぐぐると判ると思います。

まず、/etc/sysctl.confをいじります。

net.ipv4.ip_forward = 1

みたいなのをコメントアウト解除、なければ書き足し。

# sysctl -p /etc/sysctl.conf

してその設定を反映。次回は起動時に反映。

iptablesを実行するシェルスクリプトを書きます(実行属性を忘れない)。WAN側をppp0、LAN側をeth0で192.168.1.1/24とします。

#!/bin/sh

WAN=ppp0
LAN=eth0
WANIP=`ifconfig $WAN | awk '/inet addr:/ { sub(/addr:/, ""); ORS=""; print $2 }'` # Ubuntuのifconfigに特化した感じがあるので動かなければ書き換え、というかコメントアウトしてあったので何か動かない理由があったのかもしれません。
LANIP=192.168.1.1
LANSUBNET=192.168.1.0/24

hairpin_nat(){
        iptables -A FORWARD -i $WAN -d $1 -p $2 --dport $3 -j ACCEPT
        iptables -t nat -A PREROUTING -p $2 --dport $3 -d $WANIP -j DNAT --to $1
        iptables -t nat -A POSTROUTING -p $2 -s $LANSUBNET -d $1 --dport $3 -j SNAT --to-source $WANIP
}

# Flush & Reset
iptables -F
iptables -t nat -F
iptables -X

# Default Rule
iptables -P OUTPUT ACCEPT
iptables -P FORWARD DROP
iptables -P INPUT DROP

iptables -A INPUT -i $LAN -j ACCEPT

# Filter out packets with private IP addresses from the Internet
iptables -A INPUT -i $WAN -s 192.168.0.0/16 -j DROP
iptables -A INPUT -i $WAN -s 172.16.0.0/12 -j DROP
iptables -A INPUT -i $WAN -s 10.0.0.0/8 -j DROP
iptables -A FORWARD -i $WAN -s 192.168.0.0/16 -j DROP
iptables -A FORWARD -i $WAN -s 172.16.0.0/12 -j DROP
iptables -A FORWARD -i $WAN -s 10.0.0.0/8 -j DROP

# Established
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# FORWARD
iptables -A FORWARD -i $LAN -j ACCEPT
iptables -A FORWARD -i $WAN -m state --state ESTABLISHED,RELATED -j ACCEPT

# Loopback
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# Accept
iptables -A INPUT -p icmp --icmp-type 0 -j ACCEPT # この辺りは使うので許可してあげます。不要な場合は外します。
iptables -A INPUT -p icmp --icmp-type 8 -j ACCEPT

iptables -A INPUT -p tcp --dport 1234 -j ACCEPT # 自身で待ち受けるポート。LAN内の別のサーバーに転送したいものはここに書く必要はありません。

# SNAT (masquerade)
iptables -t nat -A POSTROUTING -o $WAN -j MASQUERADE

# Static NAT
hairpin_nat 192.168.1.2 tcp 80 # Webサーバーがある場合の例、これを必要な数だけ
hairpin_nat 192.168.1.2 tcp 443

あとはこのスクリプトをネットワーク起動後に読み込みます。Ubuntuなら/etc/network/interfacesのpppの設定のところに

auto dsl
iface dsl inet ppp
        pre-up /sbin/ifconfig eth0 up # line maintained by pppoeconf
        post-up /etc/network/iptables.up
        provider dsl

的な感じで、post-upから呼び出します。post-upのファイル名は環境に応じて変えてください。更新した場合は直接ファイルを呼び出してあげれば大丈夫です。

ファイルの中身についての説明はめんどくさいので省略しますが、DEFAULT RULEは除くとして基本的に同じchainについて複数のルールが該当する場合、先に呼び出された方のルールが適用されるはずです。ソースは忘れました。なので、「○○したいが、例外的に□□な時は△△したい」時は後者を先に書きます。

一年後、そこにはVPNと外部ネットワーク、そして攻撃してくる輩をブロックする記述で秘伝のタレ化したiptables設定ファイルが!

# 基本が自分用のメモなのでこのブログを常体で書くか敬体で書くか微妙に悩ましかったのですが、敬体を基本にしてみました。違和感ないですね。

(一部仕様を勘違いしていた部分があり随時修正しています。)

FreeBSDをインストールしたらまず行いたい作業メモ

FreeBSDはUbuntuなどと比べてそっけないので、インストールしたらまず行っておきたい作業がそれなりにあるのでメモ。これもやっとくといい、みたいなのがあればコメント欄にお願いします。

とりあえず使うパッケージの追加と設定

sudo

cd /usr/ports/security/sudo
make install clean
visudo
vi /etc/group
grpconv

残り

  • vim-lite (インストールに時間がかかる)
  • screen

VimでBackspace等を使えるように(FreeBSDだと必要でUbuntu等だと不要なところ)

set backspace=start
set encoding=utf-8
syntax on

そしてcshrcで環境変数EDITORをvimに

sudo vimでも自分のvimrcを使う

sudoでvimを触りたい時はsudoeditコマンドを使うといいらしい。この中で:shellとかしてもroot shellに出ていけない特徴があるが、これはいいことあり悪いことありなので、sudo vimした時にでも自分のvimrcを使うにはBrablc.com » How to use own vim configuration after sudo to shared accountを参考にしつつ/root/.vimrcに以下のように。

if !empty($SUDO_USER)
	set runtimepath+=〜${SUDO_USER}/.vim/bundle/vundle,〜${SUDO_USER}/.vim
	let $MYVIMRC='~'.$SUDO_USER.'/.vimrc'
	source $MYVIMRC
endif

FreeBSDのLANGをUTF-8に

FreeBSD/UTF-8化 – BugbearR’s Wiki参照

/etc/login.conf の default:\ のところに空気を読みつつ

        :charset=UTF-8:\
        :lang=en_US.UTF-8:\

を追加して

cap_mkdb /etc/login.conf