IPアドレスから国を判別する

ヘンなアクセスをしてくるIPはムカつくからiptablesでブロックしている。 whoisで、どこの国かなどを見る。 基本的に日本人以外には有用なものはないから、日本以外を全部遮断してもいいんだけど、それじゃ中国のGreatFWだもんね。
ftp.apnic.net/stats/apnic/delegated-apnic-latest


ちなみにここを見ると、どの国にどのIPアドレスが割り当てられているかはわかるようである。 日本以外をブロックするとかができるわけだ。 でも、インターネットに情報をさらしておきながら国内にアクセスを限るってのもどうかと思う。 あとIPアドレスが海外のものでも、日本人が海外の会社でサーバを借りたとか、 海外勤務の日本人が、とか、そういう場合もあるからやっぱりアドレスを管理しているISPの国を見てもあまり意味はないかもしれない。

画像アップローダの作成

画像アップローダーを作ろう。 そういうサイトはたくさんあるけど、なかなか私の使いたいものがない。 以前使っていたサイトがあって、それがとてもよかった。 まず、画像はすべてサムネイルが表示される。 サムネイルといっても、結構大きめで、それで十分楽しめるくらいの大きさである。 PCの1画面で、3、4列くらいかな。 背景は黒。各画像の下には短いコメントが表示される。 多くの場合は何もないか、ごく短い。 投稿者だけでなく閲覧者もコメントをつけられるが、そのコメントはサムネイル表示時には表示されない。

画像の表示はglobでいいかな。 まずglobで画像を表示させてみると、大きさがまちまちになる。

これをプレビュー画面では同じ大きさに統一したい。 Windowsのexplorerとかpicasaとかのように。 さて、どうやるか。 ちょっと調べるといろいろ出てくる。 いつもはとりあえずなんでもいいから動くものに食いついてきたけど、 今回はどんな方法があるのかを調べて吟味してから使おう。

Image::Magick

GD

ImgResize

画像のサムネイルを作成する。

#!/usr/bin/perl
use strict;
use Image::Magick;

print "Content-type: text/html\n";
print "\n";
my @files = glob "./images/*.png ./images/*.jpg";
foreach (@files){
&MakeThumbnails($_);
}

print '<table border="0" cellspacing="3" cellpadding="3" width=80%><tr>';
my @files = glob "./thumbnails/*.png ./thumbnails/*.jpg";
my $count =0;

foreach (@files){
if($count % 5 <1){
print "</tr><tr>";
}
print "<td><img src=\"";
print $_;
print "\"></td>";
$count++;
}

print '</tr></table>';

sub MakeThumbnails{
my ($name) = @_;
my $dir = './thumbnails';
(my $newname = $name) =~ s/\/images/\/thumbnails/;
my $img = Image::Magick->new;
my $x;
$x = $img->Read($name);
$x = $img->Resize(geometry=>"200x150");
$x = $img->Write($newname);
}


サムネイルの作成と表示を同時に実施している。 表示(リロード)するたびにサムネイルを上書きするのはムダかもしれない。 枚数が増えてくると重くなるから、ページを分けたりする必要もあるだろう。 サムネイルをクリックしたら元画像を表示するとか、コメントをつけたり削除したりできれば、 表示部分はOK。

あとは、アップロード部分か。

perlでimagemagickを使いたくて、cpanでインストールしたらエラーになった。

調べると、yumでいれればうまくいくとのこと。

yum install ImageMagick-perl


なんで・・?

セッション管理

yahoo, google, twitter, amazon... 今はどこのサイトもログインしてそのユーザ専用ページが表示されるようになっている。 前からこの仕組みをやってみたいと思っていた。 idとパスワードを入力させて、それをデータベースで参照して正しければ認証する。 実際は暗号化したり、SQLとかを使っているのだろうが、 原理だけを確認するなら、その辺は平文でテキストファイルでよい。

テキストボックス2つとボタンを作って、postさせれば認証機能は実現できる。 しかし問題はその先である。 単純に考えるなら、



  1. OKなら ok.htmlを表示し、NGなら ng.htmlを表示する。

  2. OKなら"ok"を、NGなら"ng"を引数としてauth.cgiを呼び、引数に応じて「ようこそ」「認証できません」などと表示する。

しかし問題なのは、認証の可否をどこかに維持しておかなければならないということだ。 それを「セッション管理」といって、perlにも CGI:Session というモジュールがある。 それを使えばいいのだろうが、複雑で、mySQLなどもからんでいてメンドクサイ。 もっと単純にできないだろうか? IPアドレスで管理するのはどうだろうか? いったんIDとパスワードを認証したら、そのときのuserのIPアドレスと、認証がOKであることを、ファイルに記録しておく。 サイトでは常にそのファイルを参照するようにして、認証されているかどうかに応じてページの表示を変える。 多分、「セッション管理」とか言うのも、同じようなことをしているはずだ。あとはポート番号を見たり、認証した時間を記録しておいて一定時間がたったらタイムアウトさせるとか・・・ でもあれか、natしてたりしたらIPアドレスで管理したら全部同じになっちゃうな。 セッションIDというのはHTTPプロトコルの仕組みで、それをサーバとクライアント(ブラウザ)で保持するようだ。だが、パケットキャプチャしても"session id"みたいなフィールドが見つからない。

セッション管理というのは、ステートレスであるHTTPプロトコルで状態管理をさせるための苦肉の策のようだ。セッションIDを生成して、それをクッキーを使ってクライアントに渡す。

#!/usr/bin/perl

use CGI;
use CGI::Session;

my $cgi = CGI->new;
my $session = CGI::Session->new(undef, $cgi, {Directory=>'./tmp'});
session->param('name','mysession');

print $session->header(-charset=>'UTF-8');

print   $cgi->start_html(-lang=>'ja', -encoding=>'UTF-8', -title=>'http session test'),
$cgi->p('session id: '.$session->id.'<br/>',
'name: '.$session->param('name').'<br/>'),
$cgi->end_html;


とりあえずこのようなcgiを作って実行すると、session idが表示される。F5を押すたびにidは変わる。パケットキャプチャしてみると

Set-Cookie: CGISESSID=5d6b6c1c3248aa9b399060dcef4c4e58; path=/


のように、セッションIDがサーバからクライアントに返す HTTP OKのパケットの中に入っているのがわかる。・・・・・・で?

まずはcookieからだな。 cookieというのは、サーバ側から送信するもので、ユーザが閲覧するとユーザがcookieを無効にしていない限り勝手に保存される。 cookieには名前、値、ドメイン名、パスなどが設定できる。 ユーザが保存しているcookieは、環境変数

$ENV{'HTTP_COOKIE'}


で取得できる。 たとえば「名前」=「アクセス日時」というcookieを送信しておけば、次のアクセス時に「前回のアクセスは何月何日でした」などと表示できる。

amazon.co.jpにアクセスしたら、6個のcookieが保存された。

at-acbjp
session-id
session-id-time
session-token
ubid-acbjp
x-acbjp


値を見てもなんのことやらさっぱりわからないが、 サインインしたときにアカウントと関連付けて保存しておけば、どのアカウントかわかる。 どのサイトでも「ログインしたままにする」などのチェックボックスがついているが、 それをチェックしたときはcookieを見てアカウントの認証プロセスを飛ばすのだろう。 と、こう見てくるとperlの CGI::Session モジュールがやってることはたいしたことがないように思えてきた。 ただidを発行してcookieにセットしてるだけじゃないのか?




パソコンに写真がたくさん溜まってきて、特に見ないのだが捨てるのももったいなくてとってある。 これをwebに置くことにした。 いろいろあるけど、picasaのwebアルバムに落ち着いた。 いったん上げてしまうと、フォルダの階層が作れなかったり、削除や移動が一度にできなかったりとメンドクサイが、大量ファイル操作は一度ダウンロードして操作してアップロードするなどしている。それにしても重複ファイルの多いこと・・・メニューの「整理」を選ぶと、複数選択して削除や移動ができる。天下のgoogleがそんなことくらいできないわけないよな・・・

sphinx

また新しいアドレスから sshアタックが来ていたので、iptablesに追加した。 しかしこれはイタチごっこになりそうだ。 と、誤って自分のアドレスをフィルタしてしまい、sshが固まった。 ヤバい・・・と、リモートコンソールがあることを思い出した。 しかし、なぜかアクセスできない。 と、「VNCコンソール」というものがあるのに気付いた。 よくわからないがこれだとアクセスできたのでiptablesの設定を直す・・・

denyhosts というソフトがあって一定の回数のアクセス失敗があると自動的に hosts.denyに追加してくれるものがあることを知り動かしてみたが、自分のアドレスが登録されてしまった。 過去のアクセス状況を見ているのだろうか。 それと、登録がホスト単位であること、拒否するのがsshdであることが気に食わない。 やっぱりiptablesでブロックごとドカンと跳ね除けることにした。

wikiを作ろうと思ったがsphinxというpythonでできたおもしろそうなのがあったので、 これを使ってみる。

yum install python-pip python-setuptools


をやってから、

pip-python install sphinx


をやる。 これだけでいいはずなのだが、インストール中にSyntaxErrorが出る。 最後は Successfully installed となっているので、

sphinx-quickstart


をやってみるが SyntaxError: invalid syntaxで動かない。 pythonのバージョンが古いのかと思ってupdateしてやり直したがダメ。 とりあえず、見送り。しょうがないからpukiwikiにする。 ダウンロードして解凍するだけ。 ちなみにPCにダウンロードしてFTPでアップロードした。 WEBにあるファイルをコマンドでダウンロードするにはどうすればいいのか? lynxを入れる?

sshdへの不正アクセスに悩んでいる人は多いようだが、私のように一人しか使ってないのなら、 もうsshdは止めておいて、使う時だけコンソールから入って起動したらいいんじゃないかな。もっというと全部リモートコンソールでやってしまってもいい。 とりあえずsshdできるユーザを制限。基本的にアクセス制限はデーモン単位でなくiptablesで出禁にする。・・・と、ムキになってアクセス制限をしてみたものの、 別にパスワードが割られたわけでもなし、 運用不能になるほど負荷があがったわけでもなし・・・。 止めてしまうよりもログを集めて分析したほうが面白いんじゃないかと思えてきた。

587

自宅のPCでVPSのサーバにPOP3アクセスしてメールを受信することはできているが、送信できないことに気付いた。 VPSとはいえ自分で管理するサーバを持ったときに一番注意しなければならないのは不正アクセスである。 特にSPAMの踏み台になることは絶対に避けねばならない。 だから、少なくとも誰も彼もが私のVPSサーバにSMTPアクセスできるようになっていないことは確認できた。しかし自分のPCからはアクセスしたいので、いろいろ設定しているのだがどうもうまくいかない。

そのうち、/var/log/secure に大量の不審なアクセスのログが記録されているのに気付いた。 1秒おきにユーザ名を変えてsshアクセスしているログだ。アクセス元のアドレスは数個しかなかったのでwhoisでしらべてそれらを含むブロックをrejectした。 アドレスは中国、台湾、アメリカ、タイとバラバラである。 おそらく踏み台にされているのだろう。また、アクセスの仕方が非常に乱暴なのでごく単純なスクリプトを、興味本位で試しているようなものかもしれない。

whoisといえばいつもブラウザでANSIのwhoisを使っていたが、まてよと思ってcentosのコマンドでやってみたら検索できた。

どうしてもSMTPアクセスできない。 telnet 25 もできない。 PCでキャプチャしたら synを送ったきり応答がない。 サーバ側でtcpdumpしても何もこない。 ポートが開いていないのか、と、iptablesを見ると開いている。 でも、やっぱり25が開いていないとしか思えない、googleで探すとVPSの試用期間は25番が開かない、とある、 もしかして試用期間が終わったのに設定が解除されていないのか・・・?と思う。 nmapでスキャンしてみたら、やっぱり25は開いていなかった・・・

「さくら VPS sendmail 25 」などで検索してみると、どうやら「試用期間がすぎても25番が開かない」ということがあるようだ。 再起動したら直った、再起動してもダメだからポート番号変えた、などの情報があった。 再起動は何度かしたけどもう一辺やってみる・・・ダメ。 ポートを587に変える。 以前コメントにした DAEMON_OPTIONSで使用ポートを変更できる。 Addr=127.0.0.1 は削る。

DAEMON_OPTIONS(`Port=587,Name=MTA')dnl


iptables で 587をあけてiptablesをrestart。m4をやって、sendmailをrestartする。

送信できた・・・

この野郎~ッ!

しかしお陰様で sendmailやiptablesやnmapの使い方を勉強できたよ。

javamail入門

javamail入門という面白そうな本があったので、ちょっとやってみる。 javamailとかJAFとかを入れてみるがちゃんと動かない。 いつ入れたのか定かでないSDKを消して、入れなおす。
www.oracle.com/technetwork/java/javase/downloads/jdk-7u1-download-513651.html

インストーラが起動する。 デフォルトではデモをインストールしないようになっているが、インストールする。 インストール後、ユーザー登録画面が出てくる。 ついでなので登録しておく。

pathの登録が必要。

F:\Program Files\Java\jdk1.7.0_01\bin


言い忘れたけど Windows XPです。 昔書いたhello,world的なサンプルをコンパイルしてみる。

エラーになる。 ファイル名とクラス名が同じでないといけないのを思い出して、ちゃんと同じになっているものをコンパイルして動くことを確認する。

javamailは解凍したフォルダを program files\java に移しておき、 環境変数を登録する。 CLASSPATHのところに、mail.jarのフルパスを書く。

F:\Program Files\Java¥javamail-1.4.4\mail.jar;


ファイル名まで書かなければならない。 ということはjarを入れるたびに常にclasspathを書き換える必要があるのか?メンドクサイ・・・ と思うがそれはおいておく。

demoの msgshow.javaをコンパイルする。 classpathがうまく登録されていなかったり反映していないと、エラーが大量に出る。

ちなみに、WindowsXPの環境変数の変更は、 Program Files のように間にスペースがあっても""で囲んだりする必要はない。 コマンドプロンプトを起動していた場合は、そのままでは反映しない。(setをやってみるとわかる)。 コマンドプロンプトをいったん閉じて起動し直すと反映する。ログオフや再起動の必要はない。

実行してみた。

F:\Program Files\Java\javamail-1.4.4\demo>java msgshow -T pop3 -H example.com -U hoge -P xxxxxxx
--------------------------
MESSAGE #1:
This is the message envelope
---------------------------
FROM: John Lennon <john@example.com>
REPLY TO: John Lennon <john@example.com>
TO: hoge@example.com
SUBJECT: test
SendDate: Mon Oct 24 16:10:05 JST 2011
FLAGS:
X-Mailer NOT available

F:\Program Files\Java\javamail-1.4.4\demo>


感動。

じゃあ送信をやってみようと思ったら、うまくいかない。サーバはさくらのVPSを使っていたのだが、25番ポートがどうしても開かなかった。しかたがないので587番に変えた。ところがデモは25番を使っていて、ポートの指定はできなかった。さっそく改変の必要が生じた。しかしこんなのは簡単で、argvを格納しているところに新しい引数と変数を作り、

if (port != null)
props.put("mail.smtp.port", port);


とするだけでOKだった。ちなみにいじったのは msgsend.java である。

apple礼賛

appleは衰退するなどという無責任なことを書いた後にすぐ、appleを絶賛する人を見かけた(もちろんウェブで)。そしてあらためて感じたのは、appleが好きな人のapple礼賛ぶりの熱烈さである。でも、パソコンとかミュージックプレイヤーなどというものは料理で言えば皿などの器のようなものである。食事をご馳走になった後に、ひたすら「このお皿はいいですねえ。料理が引き立ちますねぇ、この模様がすばらしいですねぇ・・・、このナイフの切れ味とフォークの刺し易さには驚嘆します」などと料理について何もいわずに食器ばかりほめたら料理を作った人はガッカリするだろう。

わたしだって電球を発明したエジソンの業績はすばらしいと思う。蓄音機も、CDも、MDも、MP3も、インターネットも、FAXも、メールも、みんなすばらしい技術である。でも、appleを礼賛する人は、ブログを書いても音楽を聴いてもゲームをしても何をしても、「Macはいいな」「appleはいいな」と言っているような印象がある。それだけならいいが、たいていWindows否定がデザートのように追加される。

先ほど書いた閉鎖性ということについても、要するにナルシシズムであって、ハードウェアからソフトウェアそしてついにはコンテンツ売買までもappleで独占して悦にいっているのである。愛好者にしてみればこんなに気持ちのよいことはないであろう。しかし、そんなものは独裁者の築いた孤立した楽園のようなものである。そういう楽園を築き維持するためにはたいてい多数の犠牲者が存在しているものである。

閉鎖的なappleは衰退する

iPhoneを使ってとても感激して、まるでペットのように肌身離さずという感じで愛用しているのだが、私はappleの繁栄はあまり長くは続かないと思う。理由は、appleが閉鎖的だからである。

好きな音楽や映画を聴いたり見たりしたくなったときに、iTunes Storeで探すとその品揃えの薄さにはいつもあきれる。iPhoneのアプリを作ってみたいと思っても、Mac OSが必要である。iPad2を買って、電子書籍も見てみたが、こちらも品揃えは薄く価格も高い。androidを始め、すでに類似品や模倣品がたくさん作られている。今はデバイスそのものしかマネされていないが、そのうちiTunesのようなシステムもマネされ始めるだろう。Macには「普通はイヤだ」という発想が見られる。「PCやWindowsはダサい」と、Mac以外のモノを露骨にバカにする態度を取る。それはユーザーにさえ見られることである。私が若い頃はMacintoshなんてとても買えたものではなかった。平気で100万円とかしていたのだ。そういう価格設定も、ビジネス上の戦略というよりは頑固な選良意識のようなものだと思う。デザインの美しさと、直感で操作できるUIはすばらしい。PCにつないでバックアップをとったりインターネットで音楽等を購入してすぐiPodで聴ける仕組みもすばらしい。だが、閉鎖的である。このままでは先がないと思う。多少みてくれがダサくても、UIが操作しづらくても、豊富なソフトが低価格で利用できるならそっちを選ぶ。

sendmail

レンタルサーバから移行した。 いろいろメンドクサイのでおぼえがきを記す。レンタルサーバで動いていたcgiがinternal server errorになることがある。 /etc/httpd/logs/error_log を見ると、
Premature end of script headers
と書いてある。 改行コードがCRLFになっているとこうなるそうだ。 改行コードを確認するには、
od -c test.cgi
とやると、改行コードが\nとか\r\nとか表示される。 \r\nになっていた場合は、以下のようにして変換する。
$ tr -d \\r <windows.txt >unix.txt
sendmail
こいつがメンドクサイ。まず、みんなが当たり前のようにやっているが前提として理解しておく必要のあること。sendmailの設定ファイルは sendmail.cf というテキストファイルである。この設定ファイルはムズカシイことで有名で、私も以前ちょっと調べてみたことがあるがさっぱりわからなかった。最近は、sendmail.mc という設定ファイルの設定ファイルみたいなものができて、それをm4 というコマンドを以下のように使って sendmail.cfに変換してくれるようになったようだ。
m4 /etc/mail/sendmail.mc > /etc/mail/sendmail.cf
sendmail.mc なら、なんとなく何をしているのかが想像がつく。そして sendmail.mcだが、コメントの書き方がちょっと変わっていて、行頭に dnl と書く。行末にもdnlがついているところがあるが、これはあってもなくてもいいようだ。
dnl #
dnl DAEMON_OPTIONS(`Port=smtp,Addr=127.0.0.1, Name=MTA')dnl
dnl #

dnl #
dnl # Also accept email sent to "localhost.localdomain" as local email.
dnl #
LOCAL_DOMAIN(`hoge.net')dnl
dnl #
今例示した下記の設定は、メール転送をローカルに限るという設定なので、外部からのメールを受信する場合はコメントアウトする。
DAEMON_OPTIONS(`Port=smtp,Addr=127.0.0.1, Name=MTA')
さて、外部からのメールの受信であるが、実はまだうまくいっていない。いろんなアカウントを作るときにメールアドレスが必要になることがあり、VPSを持っていれば実質無制限にアカウントを作れるのだが、メールが受信できなきゃしょうがないな・・・
[root@wwwXXXXXu ~]# sendmail -d0.1 -bt < /dev/null
Version 8.13.8
Compiled with: DNSMAP HESIOD HES_GETMAILHOST LDAPMAP LOG MAP_REGEX
MATCHGECOS MILTER MIME7TO8 MIME8TO7 NAMED_BIND NETINET NETINET6
NETUNIX NEWDB NIS PIPELINING SASLv2 SCANF SOCKETMAP STARTTLS
TCPWRAPPERS USERDB USE_LDAP_INIT

============ SYSTEM IDENTITY (after readcf) ============
(short domain name) $w = wwwXXXXXu
(canonical domain name) $j = wwwXXXXXu.sakura.ne.jp
(subdomain name) $m = sakura.ne.jp
(node name) $k = wwwXXXXXu.sakura.ne.jp
========================================================

ADDRESS TEST MODE (ruleset 3 NOT automatically invoked)
Enter <ruleset> <address>
多分これが原因じゃないかと思う。wwwXXXXXu というのはVPSサーバに与えられるfqdnで、これを変えて使っている。sendmail.mc にドメイン名を設定したのだが、このコマンドに反映しない。このコマンドのオプションの意味はよくわからない。後で調べる。
gmailからVPSのアカウントにメールを送ると、/var/log/maillog に以下のようなログがあるので、サーバには届いているようである。
Oct 21 17:32:58 wwwXXXXXu sendmail[9748]: p9L8WtBO009748: from=<hoge@gmail.com>, size=1182, class=0, nrcpts=1, msgid=xxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxx-cueQtTA@mail.gmail.com>, proto=ESMTP, daemon=MTA, relay=mail-bw0-f50.google.com [209.85.214.50]
Oct 21 17:32:58 wwwXXXXXu sendmail[9749]: p9L8WtBO009748: to=<hoge@example.com>, delay=00:00:00, xdelay=00:00:00, mailer=local, pri=314
56, dsn=2.0.0, stat=Sent
これは、ホスト名を逆引きして表示しているようである。 WEBブラウザで操作する「VPSのコントロールパネル」でホスト名を変更したら反映した。 このときに正引き登録が必要なので、新しくホスト名を付けた。 普段はホスト名なしでアクセスしていたのだが多分、名前は必要なんだよね? ついでに、 /etc/hosts, /etc/sysconfig/network にも設定する。 いらなかったかな? 再起動したら、コマンドプロンプトも変わった。
メールも来てた! めでたしめでたし。
rootじゃない、後から作ったアカウントでログインするのだが、そのアカウントではlsすらできない。いつもsu - をやってrootで操作していたのだがメンドクサイので、自分のアカウントの所属グループを変えることにした。 wheelにすればいいのだろうと思っていたがすでにwheelになっていた。 wheelではlsもできないのか・・・ rootのグループを見ると、admとか sysとかたくさんのグループに所属している。 rootと全く同じにする必要はないから、とりあえず admとかに入れてみたがダメだ。 そして気付いた、groupaddではひとつのグループにしか登録できないことを。 調べると vigr というコマンドがあって、これを使えばグループの設定ファイルを編集できる。 しかるべきグループに追加し、晴れて ls が実行可能となった。
所属グループの確認: id
一つのグループに所属させる場合の変更: usermod -G wheel hoge
グループ設定ファイル編集: vigr
何をしたかったかというと、アクセスログから検索語を探して表示しているcgiがあるのだが、件数がそんなにないので、見つかった時点でtweetするようにしようと思った。 すでに一つbotは稼動していて、試験的な運用なのでそれで試してもいいのだがせっかくなので新しいアカウントを取って新しいbotを作ろうと思います。
あれーまた受信できなくなった。 グループ変えたせいか?
・・・できた。ようわからん。
mail -u hoge
でhogeのメールを見る。
echo test|mail root
でrootに"test"という文字列を送る。
さて、メールが受信できるようになった。 しかし、日本語はエンコードされている。 デコードしなきゃ。 どうやるんだろう? perlでちょいちょいとやればできそうなのだが、よくわからない。情報もない。 しょうがないからpop3で受信する。 dovecotをインストール。
yum install dovecot
設定ファイルは特にいじらずに起動する。
service dovecot start
outlook expressで受信してみるがダメ。 telnet xxxx 110 でも入れない。 ping xxxx はいける。
service dovecot status
を見てみたら停止していた。 /var/log/maillog を見ると、
Oct 21 22:27:43 tal dovecot: auth(default): Unknown authentication mechanism 'dram-md5'
とあるので、設定ファイル /etc/dovecot.conf で dram-md5 と書いてあるところを探して、消して、再起動。 受信できた。こっちは簡単。 というか、sendmailもたいしたことはなかった。 でも、dnsとかiptablesとか、ある程度知ってないとワケがわからないかも。

iPad2を買った

Softbankのキャンペーンが決定打となった。要は3GモデルをWifiで使えばいいのである。WifiモデルにはGPS機能がない。購入手続きの途中でシステム障害が起きて、一旦帰宅して昼寝をし、目覚めたらちょうど電話が鳴って復旧の連絡が入った。特にiPadでなければできないことがあるわけでも、したいことがあるわけでもなかった。PCにつないでiOS5のインストールをしたら林檎マークが表示されて進まない。ときどきピヒョ!というあのUSBでつないだ時の音がするがすぐに林檎マークになる。同期か何かしているのだろうとしばらく待っていたが様子がおかしいので強制終了したら、復元が必要になった。復元が終わり、Twitterなど必須アプリを入れる。iPhoneを使い始めた時は驚きと感動の連続だったが、それほどの衝撃はない。まあ、大きなiPhoneのようなものだから当然だが。

いらなかったかな....と思い始めたがebiReaderを入れて、iPadの用途が見えた。電子書籍リーダーである。これが主要な用途となるだろう。まだコンテンツが少ないようだがこれから増えていくだろう。

...以上、iPadのローマ字キーボードを打鍵して書いた。普通のキーボードの半分くらいの速度か。いくら慣れてもこれをブラインドタッチは無理だろう。外付けも面倒だし...見たり読んだりは問題ない。ただし、ずっと持っているにはまだ重く、寝転がったり座ったりしている時にどうやって固定するかが定まっていない。座っている時は膝にのせるのがいいかな。

アクセスログ解析スクリプトの改善

アクセスログ解析スクリプト、改善した。
#! /usr/bin/perl

use strict;
use URI::Escape;
use Encode;
use DateTime;
use utf8;

my @line;
my @line2;
my @line3;
my $useragent;
my $unescaped;
my $print_level = 2;

sub print_selected{
    my ($date, $time, $host, $addr, $search_engine,
    $search_string,$useragent,$browser,$misc)=@_;

    print '<font face="georgia" size=-2><br></font>';
    print '<b>'.$search_string.'</b><br>';
    print '<font face="georgia" size=-2>';

    if($search_engine=~ /Google/){
        print ' <font color="navy"><b>'.$search_engine.'</b></font>';
    }elsif($search_engine=~ /Yahoo/){
        print ' <font color="red"><b>'.$search_engine.'</b></font>';
    }elsif($search_engine=~ /Baidu/){
        print ' <font color="orange"><b>'.$search_engine.'</b></font>';
    }else{
        print ' <font color="black"><b>'.$search_engine.'</b></font>';
    }
    $time =~ s/\[//g;
    print '<br><br></font>'."\n";
}

my $date = DateTime->now( time_zone => 'Asia/Tokyo' )->subtract(days=>1);
my $logfilename;

if($#ARGV >=0){
    my $test='cp ../../log/access_log_'.$ARGV[0].'.gz ../../log/tmp';
    system($test);

    $test='gunzip -f ../../log/tmp/*.gz';
    system($test);

    $test='mv ../../log/tmp/access_log_'.$ARGV[0].' ../../log/tmp/log.txt';
    system($test);

    $logfilename='../../log/tmp/log.txt';
}else{
    $logfilename='log.txt';
}

print "Content-type: text/html\n\n";
print "<html><head>\n";
print '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">';
print "<title>Searched Strings</title>\n";
print '
<style type="text/css">
<!--
BODY, TH, TD { font-family: "Osaka"; }
-->
</style>

'."\n";
print "</head>\n";

print "<body>\n";
#print "<a href=\"$logfilename\">$logfilename<\/a>  ";
#print "<br>print level:".$print_level."<br>";

print "<blockquote>\n";

open LOG,"<$logfilename" or die;

while(<LOG>){

    if(/googlebot\.com|Baiduspider/){
        if ($print_level > 1) {
            next;
        }
    }else{
        print "<br>";
    }

    print '<font face="georgia" size=-2>';
    print;
    print '</font>';

    @line=split;

    if(/.+(\(.+\))/){
        $useragent=$1;
    }

    if(/google/){
        @line2 = split /&q=|\?q=/, $_;
        @line3 = split /&/, $line2[1];

        $unescaped = uri_unescape($line3[0]);

        if($_ =~ /ie=SJIS/){
            $unescaped = Encode::decode('shiftjis',$unescaped);
        }

        $unescaped =~ s/\+/ /g;
        &print_selected(@line[0],@line[1],@line[2],@line[3],
        "Google",$unescaped,$useragent);

    }elsif(/yahoo.+search\?/){
        @line2 = split /p=/, $_;
        @line3 = split /&/, $line2[1];
        $unescaped = uri_unescape($line3[0]);
        $unescaped =~ s/\+/ /g;
        &print_selected(@line[0],@line[1],@line[2],@line[3],
        "Yahoo",$unescaped,$useragent);

    }elsif(/yahoofs.+search/){
        @line2 = split /p=/, $_;
        @line3 = split /&/, $line2[1];
        $unescaped = uri_unescape($line3[0]);
        $unescaped =~ s/\+/ /g;
        &print_selected(@line[0],@line[1],@line[2],@line[3],
        "Yahoofs",$unescaped,$useragent);

    }elsif(/baidu.jp\/s\?/){
        @line2 = split /wd=/, $_;
        @line3 = split /&/, $line2[1];
        $unescaped = uri_unescape($line3[0]);
        $unescaped =~ s/\+/ /g;
        &print_selected(@line[0],@line[1],@line[2],@line[3],
        "Baidu",$unescaped,$useragent);

    }elsif(/bing.+search/){
        @line2 = split /q=/, $_;
        @line3 = split /&/, $line2[1];
        $unescaped = uri_unescape($line3[0]);
        $unescaped =~ s/\+/ /g;
        &print_selected(@line[0],@line[1],@line[2],@line[3],
        "bing",$unescaped,$useragent);

    }elsif(/biglobe.+search/){
        @line2 = split /q=/, $_;
        @line3 = split /&/, $line2[1];
        $unescaped = uri_unescape($line3[0]);
        $unescaped =~ s/\+/ /g;
        &print_selected(@line[0],@line[1],@line[2],@line[3],
        "biglobe",$unescaped,$useragent);

    }elsif(/search\.goo\.ne\.jp/){
        @line2 = split /MT=/, $_;
        @line3 = split /&/, $line2[1];
        $unescaped = uri_unescape($line3[0]);
        Encode::from_to($unescaped, 'euc-jp','utf8');
        $unescaped =~ s/\+/ /g;
        &print_selected(@line[0],@line[1],@line[2],@line[3],
        "goo",$unescaped,$useragent);

    }else{
        print "<br>\n";
    }
}

close(LOG);

print "</blockquote></body></html>\n";
検索エンジンごとに個別にやっているところは、本当はまとめてしまいたいところなのだが、とりあえずはこれで。 これでもずいぶんすっきりした。

googleで、url(uri?)デコードしたした後に文字バケしたことがあった。 URLデコードのできるページで同じ文字列をデコードするとちゃんと表示される。shift-jisとかutf-8とかを判定してどのコードでも表示できるようにしているのだろう。どうやっているのかはわからない。 ログをよく見ると、

&ie=SJIS&oe=SJIS
というパラメータがついている。どういう場合にこうなるのかわからないが、あまりないので、この文字列があったら shift-jisをデコードするようにした。 最初は from_to で shiftjis から utf8 に変換したのだがなぜかうまくいかないので decode を使った。 Encodeの動作がイマイチよくわからない・・・

log解析スクリプトは、最新を上に表示したいので、print reverseを使ってログをひっくり返してから読むようにした。 そのときちょっとはまったのは、最初に

open FILE,">filename.txt";


とやって書き出すのはできるんだけど、その後にもういちどそこに書き込めないこと。 ファイルが開けない。 属性を書き換えていけたんだけど。 どうして一発目はかけるのに、その後はダメなんだろう?

perlのハナシじゃないんだけど、iPhoneでこのページを見ると字が小さすぎてしまう。useragentを見てページを作り分けるなんてことは面倒だしもっと簡単な方法があるだろうと調べた。以下のようにする。

print '<link href="../../css/bbz.css" type="text/css" rel="stylesheet">';
print '<link media="only screen and (max-device-width:480px)" href="/css/bbziphone.css" type="text/css" rel="stylesheet"/>';
print '<meta name="viewport" content="width=320, initial-scale=1.0, maximum-scale=1.0, user-scalable=no /">';


最初の行は、今まで書いてあった cssファイルを指定していたもの。その後の2行を足す。そして bbziphone.css に、iPhone用のスタイルを書く。とりあえずこれでできたけど、普通のと、iPhone用とほとんど同じで一部だけ違うっていうのが、ちょっとイヤだな。cssファイル内部でなんとかできないものかな?

splitの区切り文字

splitの区切り文字は、複数の文字からなる単語でもよい。
@array = split /http/,$string;


こうすれば "http"の前後で文字列を分割できる。 何に使うかというとアクセスログの検索文字列の抽出である。 今は、まず ?で区切って、格納した配列を見て、また区切って、などとやっているのだが、 見逃しや余計な物がくっついてエンコードに失敗したりしている。 区切り文字は1文字でないとダメだと思い込んでいたのだ。