Programming」カテゴリーアーカイブ

プログラミングの話題

暗号化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

デフォルトではAES128コーデックが使われます。
試してませんが、AES256にしたければ、以下のようにすればよいと思います。

$ ./configure CFLAGS="-DSQLITE_HAS_CODEC -DCODEC_TYPE=CODEC_TYPE_AES256"

wxSQLite3 AES128ならGUIツールのSQLite2009 Proが対応していますが、AES256に対応しているツールはなさそうなので、セキュリティの強化以外の理由でAES256を選択する理由はないと思います。

これで以下のファイルができました。これ単体でTclのパッケージです。

C:\src\sqlite-autoconf-3070701\tea\sqlite3771.dll

直接tclshからloadするか、pkgIndex.tclとともにTcl/libにインストールしてpackage requireすることもできます。

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

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\aes128\sqlite3shell.exe

追記:Tclからもできます。

load sqlite3771.dll Sqlite3
# Encrypt plain database
sqlite3 pdb plain.db
pdb rekey "password"
pdb close

# Reopen as plain database
sqlite3 pdb plain.db
pdb eval {
	select * from users;
}
#=>; file is encrypted or is not a database

# Decript secret database
sqlite3 sdb plain.db -key password
sdb rekey ""; # specify null string as encryption key
sdb close

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

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

(tea) 1 % load sqlite3771.dll Sqlite3
(tea) 2 % sqlite3 sdb secret.db -key password
(tea) 3 % sdb ?
bad option "?": 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

なお、.NET Frameworkから使えるADOプロバイダとしてSystem.Data.SQLite.dllが暗号化に対応していますが、これはRSA-MS Cryptだそうです。名前しか分かりませんがとりあえずAESとは別物です。SQLite Encryption Extension($2,000)は4つのコーデックがコンパイル時に選択できるようです。

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

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

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

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

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

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

Tcl単体でやろうとすると、ロックファイルでやりなさいということになるんですけど、その場合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)]
	}
}

とかやると、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
}

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

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

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

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

続きを読む

C#とSQLiteの日時データ型とパフォーマンス

1年ほど前に手がけた仕事では、C#+Windows.Forms+Entity Framework+System.Data.SQLite という組み合わせでアプリケーションを作成しました。

次に始まる開発でもこの組み合わせにするつもりですが、いろいろとやりにくかった点をまとめておきたいと思います。まずは、SQLiteの日時カラムについて。

続きを読む

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の情報を列挙する。

続きを読む

本日の災難RubyとPostgreSQL

2年前にリリースしたRubyのシステムが突然止まった。しかも部分的に。

UDPのパケットを待ち受けていろいろやるプログラムでLinuxのデーモンとして動作するサーバー。

デバッグログを見てみると、こんなエラーが。
Rubyの組み込みライブラリであるlogger.rbとそれが利用してるmonitor.rbで例外が起こっている。
安易に考えれば、writeメソッド内をbegin-rescue-endでくくってしまえばなんとかなりそうだ、と思ったけど、ちゃんと調べてからにする事にした。
まあ、たぶん1年に一回起こるか起こらないかの事でしょう、いずれ対策を考えます、などと言って現場を後にした。
帰ってきて「monitor.rb ThreadError thread killed」などとググってみると、まさにそのバグが見つかった。
上のは4ヶ月前で、予想通り例外処理による一時しのぎ。そしてつい2日前に根本的解決がコミットされていたようだ。
まさか、こんなバグが昔からあったとは。rescueしたあと原因をログにはいてたりするところでおきたら致命的ではないか。
もっともこのプログラムがスレッドを頻繁に使用しているから起こったのであって、Railsではスレッドを多用しないし、
そもそもproduction環境で大量にログを吐くってこともないだろうからめったに遭遇しないのかもしれない。
とりあえず、小さな修正なので、Rubyをアップグレードしなくてもパッチを当てるだけでよさそう、これで解決!と思った矢先。
「また止まったんだけど。」
ここ2年で動き続けたシステムが1日に2回止まった。
これは偶然じゃなく、システムが何らかの限界を訴えているに違いない。
現場に急行してfreeしてみると、なんと2GBの物理メモリの空き容量が50MBしかない。
しかし、topで見てもそんなにメモリ食ってるプロセスは見当たらない。
何が悪いのか分からなかったが、再起動すると1.3GBくらい空きが出た。
いろいろ見ても、PostgreSQLくらいしか疑うところがない。
履歴テーブルが140GB近くになってるのだけど、このテーブルから100件ほどSELECTすると、
一気に1GBくらいメモリが増大することが分かった。
後はGCでぎりぎり頑張ってるという状態が続く。
postgresql.confを見てみると、なんとautovacuum=offになってた。CentOS5.2のPostgreSQLは8.1で、デフォルトではAUTO VACUUMされない。
24時間365日動かさないといけないのに、このテーブルにはBLOBの画像データも含んでいて簡単にバックアップもできないし、VACUUMも何時間、あるいは何日かかるか予想できない。
その間、システムのレスポンスは確実に落ちる。ドキュメントによればオプションなしのVACUUM中は読み書きが許可されるとあるが、しばらくCPUを占有されると、本来の動作が割り込んできた1回目のレスポンスが極端に落ちる。
さてどうしよう。
  1. VACUUMって部分的にできるの?できるなら毎晩ちょっとずつ実行するようにする。
  2. 代替機を用意し、その間にVACUUMを済ます。代替機にたまった履歴は破棄。
  3. システム完全停止。VACUUM FULL実行。終わるまで待ってもらう。
いずれにしても大変だ。
こういう蓄積するデータについてはデータベースではなくファイルで管理するしかないのだろうか。
--追記
メモリのfree領域がどんどん減っていくのは正常な挙動らしい。Linuxではバッファやキャッシュを利用してディスクIOの負担を減らしており、この領域はプロセスに占有されているとはみなさないらしい。
そのため、以下のようになっていても53.6MBしか空きがない!とあせる必要はないということのようだ。
[root@localhost ~]# free
	total         used        free          shared     buffers     cached
Mem:	2075444    2021844      53600           0       21848     1702788
-/+ buffers/cache:	297208    1778236
Swap:	2031608          112    2031496
いまいちRubyのエラー頻度が増えた理由との関連性があるのかよく分からないけど、VACUUMしてないことは今回のトラブルにおいてそれほど重要ではなかったということだと思う。しかし、UPDATEやDELETEしてできた領域が再利用できないのは問題なので、今後はなんとかしないといけないと思う。なにかの機会に8.4に上げるかのう。
--追記
VACUUM FULLというのは、PostgreSQL7.1以前のVACUUMで、今はこれをしないで済むように定期的なVACUUMをやるようにしなさいとのこと。
でもVACUUMがいくらテーブルロックをしなくても、確実に負荷は増すので、パーティショニングといってテーブルを分割する仕組みを利用するのがよさそう。パーティションテーブルの名前を年月からつけておき、1年以上前の月のテーブルは丸ごとDROPすれば不要領域も削除される。真のメンテナンスフリーが実現可能?
このあたりの仕組みは、最近のPostgreSQLはかなり機能アップしてきているみたいです。逆に言うとLinuxに標準で含まれるパッケージを利用してる場合はかなり使い勝手の悪さを強いられているということでしょう。
24時間365日メンテナンスフリーなんて、小さな規模では考えなくてもできるものだと思い込んでましたが、それはかなり大変なことで、データベースに詳しくない人でもそれなりにできる道が開けたというのはつい今しがた、というところなのかもしれません。
Railsならデータベースを意識する必要なし、本番環境で入れ替えたりもできちゃう、なんてのは、やはり宣伝効果を狙った機能ということになるのでしょうね。

C#のプロパティに初期化の方法があればいいのにと思った

C#のプロパティは宣言と一緒に初期値指定することはできない。フィールドと組み合わせるか、コンストラクタで初期化するとかしないといけない。

class Person
{
    string _Name = "名無し"; // フィールドでやるか
    public string Name
    {
        get
        {
            return _Name;
        }
        set
        {
            _Name = value;
        }
    }
    public Person()
    {
        Name = "名無し"; // コンストラクタでやるか
    }
}

これまでは値をセットしたときにイベントを起こすとか、設定ファイルと連動させるときとかに使ったことがあったくらい。

もしかしたら次期バージョンではできるのかなと思って、「C# プロパティ 初期 .net framework 4.0」とかで調べてみるけど、無さそうですね。

そのかわりちょっと興味をひく記事を見つけました。

[C#]自動プロパティの必要性
[C#]自動プロパティの必要性(その2)

出水さんという方のコメント「アセンブリ公開するのがわかっていれば、もともとフィールドでは置かないですしね」

なるほど、先日からCOMを使っているので意味分かります。インターフェースにフィールドは置けないんですよね。メンバ変数みたいなのをCOMに公開したければパブリックプロパティを使う必要があります。

インターフェースに初期化データを置くというのも変な気がするので、その辺で初期化は導入されないのかもしれないですね。詳しいことは知りませんが。

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

 

前回に引き続き、イベントの定義をやってみました。

DataGridViewのボタンセルが押されたら、そのセルの行・列番号を引数にして、Tclのプロシージャをコールバックするということがしたい。

ソリューション一式: SimpleDgv.zip

まず、メソッドと別にインターフェースを用意して、InterfaceTypeをComInterfaceType.InterfaceIsIDispatchにします。

[Guid("1AE7D7D7-02EF-4d70-B7F5-71CE046FAEA9"), ComVisible(true), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ISimpleDgvDispatch
{
    void ButtonClick(int col, int row);
}

そして、クラスの属性にComSourceInterfaces(“ISimpleDgvDispatch”)を追加し、クラスにButtonClickイベントを定義します。

[Guid("29E1BC35-88D3-47f0-997D-B889CA25E135"), ComVisible(true), ClassInterface(ClassInterfaceType.None), ComSourceInterfaces(typeof(ISimpleDgvDispatch))]
public partial class SimpleDgv : UserControl, ISimpleDgvInteface
{
    public delegate void ButtonClickDelegate(int col, int row);
    public event ButtonClickDelegate ButtonClick;

    protected void OnButtonClick(int col, int row)
    {
        if (ButtonClick != null)
        {
            ButtonClick(col, row);
        }
    }
}

ビルドするとCOMとして公開されました。

Tcl側のコード

proc ButtonClick {col row} {
    tk_messageBox -type ok -message "Callback Col=$col, Row=$row"
}
optcl::bind $dgvObj ButtonClick ButtonClick

 

さあ、どうでしょう?

エラー全文

イベントを実行したC#側で例外が発生しました。例外テキストからはSystem.RuntimeType.InvokeDispMethodに渡されるcultureが不明なものであったということだろうと解釈できますが、だからといってどうすればよいというのでしょう?

DISP_E_UNKNOWNLCIDとはなんぞや?ググっても文字通りの説明しか出てきません。

AssemblyInfo.csでCultureInfoを指定したりもしてみたが、何も変わらない。

これで丸1日試行錯誤したものの、全く原因も解決法も分からず途方にくれました。

どうしよう。引っ込みがつかない。


2009/9/8 -- 追記: 成功

.Net Framework フォーラムで質問したらjzkeyさんが回答をくれました。

http://social.msdn.microsoft.com/Forums/ja-JP/netfxgeneralja/thread/46e0c5a2-0a89-472e-b4d6-0cd7399e1300

動くようになったoptclのバイナリとソースを置いておきます。

http://yyamasak.drivehq.com/devel/src/tcl/lib/optcl3010t-bin.zip
http://yyamasak.drivehq.com/devel/src/tcl/lib/optcl3010t-src.zip

optclがビルドできるようになったので、とりあえずBase64でのやり取りで妥協したマルチバイト引数の件も調べれば何とかなるかもしれないという希望が出てきました。