ブログアーカイブ

暗号化APIを有効にしたSQLite3のTclバインディングをコンパイルする

コンパイルに使用したもの
ActiveTcl 8.4.19.5 http://www.activestate.com/activetcl/downloads
wxSQLite3(wxsqlite3-2.1.2.zip) http://sourceforge.net/projects/wxcode/files/Components/wxSQLite3/
SQLite3ソースコード(sqlite-autoconf-3070701.tar.gz) http://www.sqlite.org/download.html
MinGW(mingw-get-inst-20110530.exe) http://sourceforge.net/projects/mingw/files/Automated%20MinGW%20Installer/mingw-get-inst/

作業フォルダはどこでもいいですが、今回はc:\srcとします。
そこにSQLite3ソースコードとwxSQLite3を展開します。
こんな感じ。

c:\src
├─sqlite-autoconf-3070701
│ └─tea
│ ├─doc
│ ├─generic
│ ├─tclconfig
│ └─win
└─wxsqlite3-2.1.2
├─build
├─build29
├─dbadmin
│ └─images
├─docs
│ └─html
├─include
│ └─wx
├─lib
├─samples
├─sqlite3
│ ├─include
│ ├─lib
│ └─secure
│ ├─aes128
│ ├─aes256
│ └─src
│ ├─codec
│ └─codec-c
├─src
└─website

以下のフォルダ内の全てのファイルを
C:\src\wxsqlite3-2.1.2\sqlite3\secure\src\codec-c\

ここに上書きコピーします。
C:\src\sqlite-autoconf-3070701\tea\generic\

そして、以下のソースコードをテキストエディタで開きます。
C:\src\sqlite-autoconf-3070701\tea\generic\tclsqlite3.c

4行目の”sqlite3.c”を

#ifdef USE_SYSTEM_SQLITE
# include
#else
#include "sqlite3.c"
#endif

“sqlite3secure.c”に書き換える。


#ifdef USE_SYSTEM_SQLITE
# include
#else
#include "sqlite3secure.c"
#endif

これで、コンパイルの準備はできました。

さて、MinGW shellを起動しましょう。

ディレクトリを移動してコンパイルします。

cd /c/src/sqlite-autoconf-3070701/tea
$ ./configure CFLAGS="-DSQLITE_HAS_CODEC"
$ make

これで以下のファイルができました。これ単体でTclのパッケージです。
C:\src\sqlite-autoconf-3070701\tea\sqlite3771.dll

さて、テストしてみましょう。

load sqlite3771.dll Sqlite3

# create plain database file
sqlite3 pdb plain.db

# create encrypted database file
sqlite3 sdb secret.db -key password

# SQL test script
# 1. create table
# 2. populate test data
# 3. execute query
set sql {
create table users (id integer primary key autoincrement not null, name text, age integer);
insert into users (name, age) values ("山田太郎", 30);
select * from users;
}

pdb eval $sql
#=> 1 山田太郎 30
pdb close

sdb eval $sql
#=> 1 山田太郎 30
sdb close

# Re-open plain.db
sqlite3 pdb plain.db
pdb eval {
select * from users;
}
#=> 1 山田太郎 30
pdb close

# Re-open secret.db without a key
sqlite3 sdb secret.db
sdb eval {
select * from users;
}
#=> file is encrypted or is not a database
sdb close

既存のデータベースファイルを暗号化するには、rekeyするか、dumpを取得して
暗号化した新規データベースでrestoreすればOKです。
この作業には、wxSQLite3に付属するコンパイル済みのshellを使用してください。
C:\src\wxsqlite3-2.1.2\sqlite3\secure\aes256\sqlite3shell.exe

最後はちょっとはしょった説明になりましたが、できてしまえば結構簡単です。
tclsqliteの暗号化APIについてのドキュメントはないのですが、ネイティブの関数と基本的に変わりはないと思います。
間違ったコマンドを与えてやるとエラーメッセージに関数リストが出てきたり、
関数に間違った引数を与えてやることで使い方が出てきたりするので、いろいろ試してみるとよいと思います。


(tea) 1 % load sqlite3771.dll Sqlite3
(tea) 2 % sqlite3 sdb secret.db -key password
(tea) 3 % sdb aaaa
bad option "aaaa": must be authorizer, backup, busy, cache, changes, close, collate, collation_needed, commit_hook, complete, copy, enable_load_extension, errorcode, eval, exists, function, incrblob, interrupt, last_insert_rowid, nullvalue, onecolumn, profile, progress, rekey, restore, rollback_hook, status, timeout, total_changes, trace, transaction, unlock_notify, update_hook, version, or wal_hook

SQLite3にformatみたいな関数がないのでTclでやる

1-12みたいな連番文字列を01012に一括変換したかった。別のカラムに分けるのが普通でしょうが、諸事情により。
SQLiteの組み込み関数には日付の操作関数は充実してるけど、文字列操作関数は大したものがない。
それで、Tclでやることにした。

package require sqlite3
sqlite3 db ./app.db; # handle and database file

db eval {SELECT * FROM squence WHERE field LIKE "%-%"} values {
	set R-V $values(field)
	if {[scan ${R-V} %d-%d R V] == 2} {
		set RRVVV [format %02d%03d $R $V]
		db eval [format {UPDATE squence SET field="%s" WHERE id=%d} $RRVVV $values(id)]
	}
}

02012みたいなのは、クォートしないと数値とみなされて2012みたいになってしまうので注意が必要です。
LIKE以外にもGLOBやREGEXPも使えるらしい。

050-で始まる電話番号を持つ人のリストを取得するなど。
select * from users where phone_number REGEXP “^050-.+$”

Tcl/TkでMutexを使った多重起動防止

Tcl単体でやろうとすると、ロックファイルでやりなさいということになるんですけど、その場合Tclの処理系が起動するまでに、別のインスタンスが起動できてしまう可能性があるので、厳密ではないです。たとえば、

run_twice.bat
start wish app.tcl
start wish app.tcl

とかやると、2重起動してしまいます。

プロセスリストを取得して処理する方法もあります http://goo.gl/K38a 。今まではこれを使っていました。ただ、プロセスリストの取得自体が結構時間かかるので、上記よりましですが確実ではないです。

別の方法として、socketで特定ポートをバインドして、多重起動時にエラーにするという方法があります。singleton application – Tcler’s Wiki
ネットワークを使わないのにファイアウォールの例外にするか聞かれたりするのが嫌です。

Mutexを使うと、VBやC#とかでやってるみたいな厳密な多重起動防止対策ができます(Windows限定)。

twapi 2.2.3での実装

package require twapi 2.2.3
twapi::import_commands

set appname "My Application"

set handle [create_mutex -name $appname]
if {[lock_mutex $handle -wait 1] > 0} {
	tk_messageBox -icon error \
		-title "Startup error" \
		-message "Another instance is running." \
		-type ok
	exit
}
console show

twapi 3.0 正式版での実装

先日twapi3.0の正式版がリリースされました。
3.0では従来通りpackage require するか、dllを1個loadするかが選べるようになりました。
x86版はTcl8.4をサポートしています。
3.0ではAPIの仕様がいろいろ変わって、lock_mutexの返り値がsignalled, timeout, abandonedのいずれかとなっています。

load twapi-x86-3.0.29.dll
twapi::import_commands

set appname "My Application"

set handle [create_mutex -name $appname]
if {[lock_mutex $handle -wait 1] ne "signalled"} {
	tk_messageBox -icon error \
		-title "Startup error" \
		-message "Another instance is running." \
		-type ok
	exit
}
console show

先に起動してたウインドウを閉じるかユーザに聞く

そういうことをする実験です。実際にはプロセスをkillするとか、ウィンドウの存在を監視して、完全に終了するのを待つとかが必要になると思います。

package require Tk
load twapi-x86-3.0.29.dll
twapi::import_commands

set appname "My Application"

set handle [create_mutex -name $appname]
if {[lock_mutex $handle -wait 1] ne "signalled"} {
	set ans [tk_messageBox -icon error \
		-title "Startup error" \
		-message "Another instance is running. Kill it?" \
		-type yesno]
	switch $ans {
	yes {
		set hWnds [find_windows -text $appname]
		foreach hWnd $hWnds {
			close_window $hWnd
		}
	}
	no {
		exit
	}
	}
}

wm title . $appname
wm protocol . DELETE_WINDOW EXIT
proc EXIT {} {
	after 3000 exit
}

Tcl/Tkのいいところ

半年くらい前まではC#でGUIアプリケーションを作っていたけど、ここ数ヶ月は主にTcl/Tkを使っている。僕がTcl/Tkを使っている理由は、最初にまともなプログラムを書いたのがTcl/Tkからだったからだ。それ以外に必然的な理由はない。以前はプログラマを名乗るのならば、C/C++/Javaあたりは最低やっておかねばという強迫観念にとらわれていた自分だが、最近C#をやって静的型付言語の雰囲気をかじったことで、まあ、必要になったらやったらいいんじゃないかなくらいに思えてきた。

シェアの大きい言語をほとんど使いこなすことなく、Tcl/TkやRubyといったどちらかというとマイナーな言語ばかり使ってきた僕が、Tcl/Tkに客観的な評価を与えられるとは思わないが、5年間というプログラマー生活を共に過ごしてきたこの言語について、実用的観点でよいと思うことを挙げておきたいと思う。

I/Oの抽象化

ファイル、ソケット通信、シリアル通信などがチャネルというハンドルに対する共通のコマンドで操作できる。さらに、VFS(仮想ファイルシステム)といって、たとえばZIPファイルやFTPを普通のディレクトリと同じコマンドで扱えたりする。

wish.exeって便利

Tkアプリケーションで、console showとか書いとくと、アプリケーションを起動したときにシェルウィンドウが開く。ここからなんでもできてしまう。アプリケーションを走らせたまま書き換えることができる。Rubyでもできるけど、インタラクティブなシェルってコマンドライン上で動くirbしかないよね。Ruby/Tkでイベントループ走らせちゃったらもう何もできないし。

マニュアルが読みやすい

MSDNみたいな非人間的な解説じゃない。コマンドのカテゴリ分けが適切で、ひとつひとつのコマンドやオプションに必要かつ十分な説明が正確な英語で書かれている。サンプルスクリプトが少ないのが不満だったが、Tcler’s Wikiという膨大なナレッジベースを参照すればいろいろ見つかる。

実用的なライブラリが豊富

およそライブラリというものには言語の機能を組み合わせて複雑な機能を実現してくれるものと、言語ではできないことを可能にしてくれるものがある。前者のバリエーションの豊富さは言語の力を示すものだ。後者は逆かもしれないが、インタープリタ言語には必要なものだ。そしてTclはどちらの種類のライブラリも本当にたくさん揃っていると思う。Perl,PHP,Rubyみたいなパッケージシステムでteapotレポジトリというのがでてきたので、そのうち実用的になっていくことだろう。Windows向けに、ActiveXコントロールをTkに埋め込むライブラリ、COMインターフェース、Win32APIのラッパーライブラリ、DLLのエクスポート関数を利用するライブラリも存在する。

デプロイが簡単

ActiveStateのTclDevKitを使えば、Tclインタープリタ、ライブラリ、ソースコード、フォント、画像など、すべてを単一の実行ファイルに固めて配布でき、ソースコードの隠蔽もできる。僕は複数のファイルからなる合計2万行程度のスクリプトと、ロゴなどの画像、十数種類のパッケージなどを単一ファイルにラップしているが、せいぜい6MB程度にしかならない。.NET Framework 4 client profileはダウンロードするだけで41MBもある。展開したらもっと大きくなるだろう。仕組みはよく分からないが、英語版WindowsXPに日本語フォントがなくてソースコードのコメントが文字化けしていたときも、コンパイルしたTcl/TkのGUIには日本語が表示できていた。

え、これだけ?とか、どれも言語の機能じゃないんですけど、とつっこまれそうなものばかりだな。。。来年あたり8.6が出るみたいだけど、こいつはかなり進化しているらしいですよ。lambda, coroutineに期待。最近は何でも屋さんの傾向は薄れつつあり、言語自体の近代化が進んでいるように思う。JimというTclのアナザー実装はガベージコレクタを持っているらしい。

実はここでは目をつむった悪いところもたくさんあるんですけど、デプロイがここまで簡単なスクリプト言語ってほかに知らないなあ。あと、目が悪いせいか、美しいとか言われてるRubyのソースコードより、Tclのほうが美しく見えてしまうのですよね。

なんか思いついたら追加するかも。

DataGridViewをActiveXコントロールにラップしてTcl/Tkのウィンドウに埋め込む(3)

これまでの経緯
DataGridViewをActiveXコントロールにラップしてTcl/Tkのウィンドウに埋め込む(1)
DataGridViewをActiveXコントロールにラップしてTcl/Tkのウィンドウに埋め込む(2)

去年やってたoptclによるDataGridView埋め込みが、ようやく役に立つときが来た。
しかし、イベントを追加しようとしたところ、いきなり壁にぶち当たってしまった。

新たに定義したイベントにコールバックを登録できない。

続きを読む

Atermのログを解析する

昨日あたりからしょっちゅうネットが見れなくなって、NECのルータのPPPoE接続をOFF/ONすることで一時的に回復させていた。最初はルータが寿命か?と思ったけど、どうやらそうでもなさそう。

外部からのハッキングについては今まであまり調べたことがなかったんだけど、この機会に調べてみようと思い、TclでAtermのログを解析するスクリプトを書いてみた。

書いた後で気づくのもなんだけど、誰がアクセスしてきてるのか分かったところで、こっちから逆に攻撃するわけにもいかず、大して役に立たない。

強力なルータに変えたら攻撃に耐えられるようになるのだろうか?その辺よく分からない。とりあえず早まってLa Fonera 2.0nを注文してしまった。前から買うつもりだったので、「しまった」というのはわざとらしいですが。

内容的には以下のようなもの。

  1. ログを読み込んで”NAT RX Not Found : TCP”という怪しいログを探す。
  2. その行から送り元のIPアドレスを抽出する。IPアドレスのヒストグラムを作成する。
  3. IPアドレスをwhoisに投げて、ドメインの情報を取得する。
  4. IPアドレスごとに、アクセスランキング、whoisの情報を列挙する。

続きを読む