Android 4.0 で Apache SNI 対応

追記
びっくりしたので思わず記事にしてしまったが、ApacheのSNIはAndroidブラウザで使えない - isherの日記 のコメントにもあるように、Honeycomb から対応していたらしい。
今頃気付いたよ…


対応っつーか、普通に接続できた。
hadacchi が使っている CM9 on NOOK Color (CM9 は、当時より新しい版) で試してみたところ、SNI を使っているサイトを普通に表示できた。

先日書いた、SSL 対応の設定 を当サイトでは施しており(word press は SSL 対応に手間がかかるので放置中)、www.hadacchi.com (SSL) へアクセスしてもらえば、Windows Vista 以降、または Firefox や Opera 利用の場合には問題なく表示されるはず。
SNI 非対応の Win XP IE8 とかでアクセスすると、証明書エラーが表示された挙句に、Forbidden が返るはず。

さて、Android は、2.3 までは SNI 対応しておらず、Win XP と同様に Forbidden が返っていた。
しかし、ICS が対応しているのか、CM9 が対応しているのか、少なくとも私のタブレットでは上記 URL へアクセスして、正しくページが表示される。

というお話。

StartCom の SSL 証明書を使う時の備忘

登録後のログインには、クライアント証明書が必要。
Apache サーバーに SSL 証明書をインストールした後、CAの証明書等を同じく設定してやらないと Firefox でエラーが出る。
ドメイン管理者でないと証明書を発行できない。サブドメインの管理者でもダメ。

オレオレ証明書

概要:SSL通信をするためだけに証明書を作る場合、オレオレ証明書で十分だと思うので、作った。
作成にあたっては、ここを参考にした。
Apache/SSL自己証明書の作成とmod sslの設定 - maruko2 Note.

注意:VirtualHostを使っている場合、SNI対応のブラウザでないと正しくアクセスできない。

  1. opensslをインストールする
  2. 証明書を作成する
    1. 秘密鍵を作成する。
      openssl genrsa -aes256 2048 > server.key
      秘密鍵は、パスフレーズで保護する。
      server.key が秘密鍵。
    2. 秘密鍵を使って、署名要求ファイルを作成する。
      openssl req -new -key server.key > server.csr
      秘密鍵のパスフレーズを求められるので、入力する。
      証明書へ埋め込みたい情報の入力が求められるので、入力する。
      とりわけ、Common Name は使いたい web site のホスト・ドメイン名と一致させておくこと。
      server.csrが署名要求ファイル。
    3. 署名要求ファイルからデジタル証明書を作成する。
      第三者機関を使う場合、この工程を当該機関にさせるらしい。
      オレオレ証明書の場合、署名要求ファイルの作成に使った秘密鍵で証明書を作成する。
      openssl x509 -in server.csr -days 365 -req -signkey server.key > server.crt
      秘密鍵のパスフレーズを求められるので、入力する。
      server.crt がデジタル証明書。
  3. apache で SSL を有効化する。
    1. VirtualHost を使う場合は、設定する。
      /etc/apache2/ports.conf へ NameVirtualHost *:443 を追記。
      sites-enabled で使っているディレクティブの、対象 VirtualHost の設定をコピペし、*:80 の部分を *:443 に書き換え。
      下記を、DocumentRoot とかと同じく、ディレクティブの外に追記。

      SSLEngine on
      SSLCertificateFile /path/server.crt
      SSLCertificateKeyFile /path/server.key
      
    2. SSL を有効化。
      a2enmod ssl
      でモジュールを読み込ませ、apache2 の再起動で完了。
  4. 設置場所にもよるけど、上記で作った全ファイルは chmod 400 * とかしといた方がいいと思う。
    400 なのに apache サーバーが読み込みできているのは、謎。

で、せっかくなんで、StartSSL™ Certificates & Public Key Infrastructure でデジタル証明書を作った。
ここは、フリーで証明書発行できるくせに、Firefox に登録されているという稀有なルート証明機関。
ここでの発行にも手間がかかるが、いい加減眠いので、メモはこのへんで終了。
このサイトへ、https でアクセスしてもらえば、見えるはず。

注意:

  1. SNI 未対応のブラウザでは見られない。IEとか。(Vista 以降の OS 上で IE8 以降を使えば見られるはずが、何故か我が家の IE9 では不可だった。不思議だ。)
    IEは、TLS 1.1、TLS1.2を有効にすればOK。
  2. WordPress は、テーマやプラグインに SSL 未対応のものが多くあり、SSL で表示するはずのページ上に SSL を使わない URL 表記のソースが出力されてしまうことから、エラーが出る。
    add_filter とかで http を https に書き換えたり、相対パスに変換したりすれば良いのだが、面倒くさいので本サイトではやってない。

自鯖のwebサーバ化

我が家のLAN
我が家のLAN

我が家では、web 公開にあたり、DMZ を設けたので、もはや左の構成では動作していないが、まぁ参考まで。

下記は、自鯖の公開にあたり修正中のメモ。

php 動かす。

a2enmod php5

ドメインで飛んできた時に、ユーザーディレクトリを DocumentRoot にする

具体的には、http://www.hadacchi.com/ で、/home/user/public_html を DocumentRoot として動作させる
VirtualHost または VirtualDocumentRoot あたりで Alias するらしい
ただし、DNS で引けないとダメ。

DNS サーバを立てる。

bind9 の設定

…前職では、職場のローカル DNS の管理してたのになぁ; さっぱり覚えとらん
master ファイルの名前の付け方は、自由にできるらしい。
最近は named.conf から import するのが主流っぽい。

bash は糞面倒くさいので、zsh 入れる。

ローカルの .zsh/.zshrc が動かないので改行コードを置換する。
vi しかエディタいれてなくて、tr は苦手なので vi で置換する。
vi::何かを改行に置換 - そらまめ備忘録 で C-v C-m で ^M を入力できることを知る。

named-checkconf が通っても、普通に起動しないことがあるので注意。例えばファイル名の指定ミスとか。

zone ファイルは、1 ファイル内に複数 zone を記載してもいいはずなのに、うまく動かなかったので、ちゃんと zone 毎に分ける。

例えば、192.168.1.1 に DNS もやってくれる R があったとして、192.168.1.2 = ns.hadacchi.com なる DNS を立てる時、
named.conf.local では次のように記載し、

zone "hadacchi.com" {
    type master
    file "hadacchi.db"
}
zone "1.168.192.in-addr.arpa" {
    type master
    file "1.168.192.db"
}

各 zone ファイルでは、

$TTL 86400
$ORIGIN hadacchi.com.  ; @ means $ORIGIN
@       IN      SOA     ns.hadacchi.com.       test.hadacchi.com.    (
        2012032701 ; serial
        86400      ; refresh
        1200       ; retry
        2419200    ; expire
        1200 )     ; minimum
        IN      NS      ns          ; NS is name server which defined as A record
        IN      A       192.168.1.2 ; short for above label
ns      IN      A       192.168.1.2 ; A record
www     IN      CNAME   ns          ; CNAME is alias

とか

$TTL 86400
$ORIGIN 1.168.192.in-addr.arpa.
@       IN      SOA     ns.hadacchi.com.       test.hadacchi.com     (
        2012032701 ; serial
        86400      ; refresh
        1200       ; retry
        2419200    ; expire
        1200 )     ; minimum
2       IN      PTR     ns.hadacchi.com.

とかいう感じ。

キャッシュは、named.conf.options で

options {
        directory "/var/cache/named";

        forwarders {
                192.168.1.1;
        };

        auth-nxdomain no;    # conform to RFC1035
        listen-on-v6 { any; };
};

とか。

PC に LAN 内の DNS を参照させる。

まず、自分で立てた DNS 自身が外のエントリよりも自分の持つエントリを優先するように設定。
DHCP を動かしている場合、DNS 設定も一緒に配信されてしまうので、クライアント側で弾く。
Ubuntu の場合、/etc/dhcp/etc/dhcp3 の下に
dhclient.conf というファイルがあるので、その中のrequest 行でDNSに関する記述をコメントアウトする。

request subnet-mask, broadcast-address, time-offset, routers,
#       domain-name, domain-name-servers, domain-search, host-name,
        netbios-name-servers, netbios-scope, interface-mtu,
        rfc3442-classless-static-routes, ntp-servers,
        dhcp6.domain-search, dhcp6.fqdn,
        dhcp6.name-servers, dhcp6.sntp-servers;

で、/etc/resolv.conf を書き換える

domain hadacchi.com
search hadacchi.com
nameserver 192.168.x.x
nameserver 192.168.x.1 # 元々の記述

それから、networking を restart

/etc/init.d/networking restart

これで、名前を引けるようになる。

自分の PC が参照する DNS をサーバにすれば、その PC からアクセスした時だけ hadacchi.com をローカルサーバに飛ばすことができるので、テスト用に良い。

VirtualHost の設定。

VirtualDocumentRoot と違って、静的に書くことになる。
しかし、他人に鯖を貸すことはあったとしても、web I/F で自動受付とかはやらないと思うので、これでいこう。
/etc/apache2/sites-available にある default を コピーして、hadacchi とでもしておく。
hadacchi を編集して、ドメイン名とドキュメントルートを追記する。

# Other diretive

<VirtualHost *:80>
    ServerAdmin mail@domain.com
    DocumentRoot /home/user/path
    ServerName subdomain.domain.com
</VirtualHost>

これで、subdomain.domain.com でアクセスされた時には、/home/user/path をドキュメントルートとして表示し、他のアクセスでは /var/www など元のドキュメントルートを表示されるよう設定できる。
この時、追加した内容を元の記述より上に記載すると、IP アドレスでアクセスされた時に subdomain 用のドキュメントルートが表示されるので注意。

www を外向けに空ける。

FTPS を外に向けて空ける

切れたセッションに再接続するために、screen を入れようと検索していたら、tmux を知る。

Terminal Multiplexer(tmux)の紹介|サイバーエージェント 公式エンジニアブログ
とか、
時代はGNU screenからtmuxへ - Dマイナー志向
が詳しい。上のリンクの設定では、 h の設定が重複しているので注意。

会社でも使えそうな、Windows 上での仮想デスクトップソフトを発見した。

Windowsに仮想デスクトップを設定、拡張機能も便利「VirtuaWin 4.3」
で紹介されている、VirtuaWin - Virtual Desktops for Windows が便利。
最初は、窓の杜 - 【REVIEW】WindowsのデスクトップをAndroid風に操作できるようにする「Blacksmith」で紹介されているソフトを試してみた。
このソフト、ランチャもついているのは良いのだが…
hotkey の変更ができないことと、一部のアプリケーションが全仮想デスクトップ上に残ることが不便なため、使うのを止めた。
今のところ、上の VirtuaWin は、Windows 7 SP1 では、問題なく動作しているので、しばらくこれを使うつもり。

妻のおばあちゃんがくれた、かえりちりめんがすげー美味い。貪り食っていたら、妻からキレられた。でもうまいし。

LAN-W300N/RU2 でチャネルボンディングを有効化

LAN-W300N/R(親機)側の上級者向け設定画面で「チャンネル幅」を「Auto 20/40 MHZ」に設定すれば良いのは分かっていたのだが,
なぜかセットでついてきた LAN-W300N/U2(子機)で接続ができなかった.
無線設定の「帯域」を「2.4 GHz (N)」に設定すれば,接続ができるようになった.

別件として,以前からPCを使用中にスマフォンを起動させると頻繁に無線リンクが切れる減少が起こっていたのだが,
どうも「2.4 GHz (B+G)」や「2.4 GHz (B+G+N)」を選択していたのが原因だったようだ.
今回,この設定を「2.4 GHz (N)」に変更したことで,チャネルボンディングの有効化をしても
無線リンクが確立できるようになった.
全くの偶然だった.

LAN-W300N/R は,配下に 11g 端末が発生すると,11n から 11g に切り替えるようであり,
この動作が起こるであろうタイミングで,一定期間,子機が接続できなくなっているように思える.
原因は色々想像できるが,無線APにログが残らず調査自体が面倒そうなので,それ以上は調査していない.
多分,IP レイヤより下で問題が起こってんじゃないかと思うが,スペアナなど持ってもなければ使ったこともないので,さっぱり手が出ない.

FTPサーバでディレクトリ一覧を表示させない

ProFTPDの場合。
.ftpaccess を表示させたくないディレクトリへ作成。
ファイルの中身はこんな感じ。

<Limit DIRS>
    DenyAll
</Limit>

ただしこの設定を行なうと、親フォルダからそのディレクトリ自体を表示できなくなる。
Windows 標準の FTP クライアントの場合、
cd -- 不可
put -- 可能
具体的には、
put hogehoge.txt invisible_dir/hogehoge.txt
のように名前を直接指定して転送すれば、転送は可能。

公的SSL証明書は,お高い

会社から,よく PC の設定など自分のメモを見るために,自分の blog へアクセスすることがある.
メモを会社から投稿することもある.
でも,ちょっと情報セキュリティに気を使う会社なら投稿内容の監視くらいはしていそうなので,
SSL 通信で投稿内容を暗号化したいなーとか思ったのでした.

SSL の証明書って,たっけーのね…
最大手 VeriSign の場合,最安で 85,050 円とか,泣ける.
で,安いとこないかなーと探したら,こんなトコがありました → サイフにやさしいSSL証明書
2,880 円~ とか.びっみょー.
オレオレ証明書もいいけど,どうせなら第三者機関の証明書が欲しい…という人にとっては, 240円/月はお手頃なのだろうか.

spammer counter v3

IP アドレスでアクセスを禁止している spammer が,その後どれだけアクセスを続けているかをカウントする php コードを,今回は MySQL を使って実装した.
まず最初に注意点. ErrorDocument 403 で php ファイルへ飛ばす場合,その php ファイルへのアクセスは可能とすること.
さもなくば, 403 の転送で無限ループが発生する.
具体的には,.htaccess へ下記を追記.hogehoge.phpの部分は,設置したphpファイル名に変更すること.

<Files hogehoge.php>
allow from all
</Files>

spammer のアクセスが集中している時など,どうしても
spammer counter v2
では,ファイル書き込みに失敗するようだ.
その結果,度々ファイルがクリアされてしまっていた.

んで,よく考えたらこのサイトでは MySQL を使う blog が2つも動いていることを思い出したので,
DB で実装することにした.
とても楽チン.

そのコードは,こんな感じ.

<?php
header('HTTP/1.1 403 Forbidden');
header('Content-Type: text/html; charset=iso-8859-1');
?>
<!DOCTYPE HTML PUBLIC '-//IETF//DTD HTML 2.0//EN'>
<HTML><HEAD>
<TITLE>403 Forbidden</TITLE>
</HEAD><BODY>
<H1>Forbidden</H1>
<?php printf('You don't have permission to access %s\non this server.',
             htmlentities(strip_tags($_SERVER['REQUEST_URI']))); ?><P>
<HR>
<ADDRESS><?php
$e = explode(' ',$_SERVER['SERVER_SOFTWARE']);
printf('%s Server at %s Port %d',$e[0],$_ENV['SERVER_NAME'],$_ENV['SERVER_PORT']);
?></ADDRESS>
</BODY></HTML>
<?php
// MySQL
$mySqlHost = 'xxx.xxx.xxx';
$dbname    = 'database_name';
$user      = 'user_name';
$password  = 'password';
$table     = 'spammer_counter';

// connecting
$mobj = mysql_connect($mySqlHost,$user,$password);
if ($mobj == FALSE) { die('broken');}

// count IP addr
$addr = $_SERVER['REMOTE_ADDR'];

mysql_select_db($dbname,$mobj);
$done=mysql_query('insert into '.$table.' (ipaddr,number) values (''.$addr.'',1) '
                  .'on duplicate key update number=number+1;'); 
mysql_close($mobj);
if ($done == FALSE) { die('no response...');}
?>

欠点は,データを見るのに DB を叩く必要があること.  
それは嫌なので,viewer も作った.
</p>
<p class="pre">
<?xml version='1.0' encoding='Shift_JIS'?>
<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN'
    'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>
<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='ja' lang='ja'>
 <head><title>spammer viewer</title></head>
 </body>
 <h1>spammer viewer</h1>
 <table style='border: black solid 1px;'>
  <tr><th>IP addr</th><th># of access</th></tr>
<?php
// MySQL
$mySqlHost = 'xxx.xxx.xxx';
$dbname    = 'database_name';
$user      = 'user_name';
$password  = 'password';
$table     = 'spammer_counter';

// connecting
$mobj = mysql_connect($mySqlHost,$user,$password);
if ($mobj == FALSE) { die('broken');}

mysql_select_db($dbname,$mobj);

$done=mysql_query('select * from '.$table.';');
mysql_close($mobj);
if ($done == FALSE) { die('no response...');}

while ( $row = mysql_fetch_row($done)) {
  printf('  <tr><td>%s</td><td style=\'text-align:right;\'>%d</td></tr>\n',$row[0],$row[1]);
}

?>
 </table>
 </body>
</html>

spammer counter

アクセス禁止したスパマーが,その後どれだけアクセスしているかカウントする.

以前,スパム増加という記事でも触れたが,
本サイトではアクセス禁止リストにあるホストからのアクセスを禁止している.
ところが,似たようなIPからのアクセスが絶えず続いており,どんどんホストを追加している状態である.
このままではアクセス禁止リストが延々と伸びていってしまうので,特にアクセスの多いアドレス空間については,
適当なサブネットマスクでもってアク禁とすることにしたい.

ところが,一度アクセスを禁止してしまうと,その後はどの程度のアクセスがあるのか分からない.
アク禁リストのアップデートをするための情報として,アク禁を解除しないまま,アクセス数を把握する方法を考えた.

ずばり,403 エラードキュメントをphpにして,アクセス元をカウントアップする作戦である.
そして普通に誤った操作をした人に疑問を与えないよう,403を忠実に再現したい.
ということで,こんなコードを生成してみた.

<?php
header("HTTP/1.1 403 Forbidden");
header("Content-Type: text/html; charset=iso-8859-1");
?>
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<HTML><HEAD>
<TITLE>403 Forbidden</TITLE>
</HEAD><BODY>
<H1>Forbidden</H1>
<?php printf("You don't have permission to access %s\non this server.",
             htmlentities(strip_tags($_SERVER['REQUEST_URI']))); ?><P>
<HR>
<ADDRESS><?php
$e = explode(' ',$_SERVER['SERVER_SOFTWARE']);
printf("%s Server at %s Port %d",$e[0],$_ENV['SERVER_NAME'],$_ENV['SERVER_PORT']);
?></ADDRESS>
</BODY></HTML>
<?php
// count IP addr
$addr = $_SERVER['REMOTE_ADDR'];
$fobj = fopen("counter.dat","r");
if (flock($fobj, LOCK_EX)) {
    $oobj = fopen("counter.tmp","w");
    $flag=false;
    for(;!feof($fobj);)
    {
        if ($flag) { fwrite($oobj,fgets($fobj));}
        else {
            $s=fgetcsv($fobj);
            if (count($s)<2) { break;}
            if ($s[0] === $addr) {
                $s[1]=(int)$s[1]+1;
                $flag=true;
            }
            fputcsv($oobj,$s);
        }
    }
    if (!$flag) { fprintf($oobj,"%s,1\n",$addr);}
    fclose($fobj);
    fclose($oobj);
    copy("counter.tmp","counter.dat");
} else {
    fclose($fobj);
}
?>

ファイルロックなんて,何年か振りに実装した.
普通に php のオンラインドキュメントにあるようなファイルロックにしていないのは,
各行毎に読み込み/書き込みをしたかったから.
読み込み用ファイルを排他ロックして,読み込めた人だけ処理が進むようにしてみた.
そんな処理が問題になるほど膨大なログが溜まる頃には,レンタルサーバのスペースを
食い潰してアカウント消されていそうだけどね…

んで,アク禁で ErrorDocument 403 へ飛ばす場合の注意.
403 で飛ばす先の php ファイルへのアクセスは可能とすること.
さもなくば, 403 の転送で無限ループが発生する.

<Files hogehoge.php>
allow from all
</Files>

とかでOK.

余談だが, ErrorDocument で http://~~ と URI 指定すると,
別サーバへの転送と同様に処理されてしまうらしく,呼び出し元が引けなくなるので注意.

AutoFollowersBlocker

GAE 上で動作し, cron で呼び出されて, twitter アカウントの follower を監視し,
プロファイルの文字列によって自動ブロッキングをするスクリプトを作成した.
きっかけは,昔むかし,山のように spammer からフォローされまくって,鬱陶しかったため.
当時の記事はこちら → twitterのspammerのfollowが多い

で,作ってみたのが, twitter を OAuth 認証でアクセスするためのクラス twit_oauth.py
そして,呼び出し元の AFBsample.py
いずれも分量が多いので,ソース自体はリンク先を参照されたい.

また,GAEで使うためのyamlファイルもついでに貼り付けしておく.

application: afb-sample
version: 10
runtime: python
api_version: 1

handlers:
# handling mode
- url: /cron
  script: AFBsample.py
  login: admin
# viewer mode
- url: /.*
  script: AFBsample.py
cron:
- description: polling job
  url: /cron
  schedule: every 5 minutes

さてこれで,次の4つを GAE へ放り込めば動く.
GAE の使い方については,web を参照されたいが,以前に hadacchi が調べたメモもある → twitterのspammerのfollowが多い
これでも,多少は参考になるかも知れない.

  • AFBsample.py
  • twit_oauth.py
  • app.yaml
  • cron.yaml

さて,今回のハマりポイントは,呼出 URL による処理の振り分けだった.

handlers:
# handling mode
- url: /cron
  script: AFBsample.py
  login: admin
# viewer mode
- url: /.*
  script: AFBsample.py
application = webapp.WSGIApplication([
    ('/cron', AutoFollowerBlocker),
    ('/', AFBview)
    ])

def main():
    wsgiref.handlers.CGIHandler().run(application)

当初は app.yaml の url 行を修正すれば呼び出し URL に応じて処理を振り分けできると思っていたが,
呼び出される側の python スクリプト内の webapp.WSGIApplication() の意味に気付いていなかった.
こいつの引数は,長さ 2 のタプルのリストであるが,タプルの第 1 要素は呼び出しURLになっていて,
ここでも呼び出し URL に応じて処理クラスを振り分けできる.
きちんと GAE の処理を読んでいないが,多分 app.yaml による振り分けは login 行におけるパーミッション制御や,
スクリプトファイルを分けることにより,クロスサイトスクリプティングみたいなアタックに対してある程度の
対処をしたい場合などに使うのが良いのかも知れない.

今回の構成では,
http://pjname.appspot.com/ へのアクセスで前回の実行結果を表示し,
http://pjname.appspot.com/cron へのアクセスでフィルタを手動実行する.

他の工夫としては,運用面で使い易くするため,自動 BLOCK 後に手動で UNBLOCK したユーザは対象から除外するようにしたり,
AND/OR 検索で重複マッチを走らせないようにしたり,
空フィールドに対して None が返却されるので事前に置換しておいたり,(デバッグの時にはjsonファイルを直接食わせる関係から,
マッチ関数内にもNoneの処理は残しているが…)
といった小細工は少ししてみた.

今後の課題としては,プロファイルを 1 call で 100 id 分まで抜ける API を見落としていたことが
分かったので,この API へ対応させた上で, follower が 10000 を越えるユーザでも TwitterAPI の
呼出制限にかからないよう初期化する実装を行なうことがある.
と難しく言ってみたものの,既チェック ID をパスするロジックなので, API の回数制限にかかる手前で
ループを break させてしまえば良いだけである.

参考にした web site は, Python アプリケーション設定 - Google App Engine - Google Code などの GAE のオンラインドキュメントと,
REST API Resources | Twitter Developers などの Twitter のオンラインドキュメントに加え,
前回までの記事 twitterでspammerのfollowを自動block 及び twitterのspammerのfollowが多い 内で参照している各ページである.