Ruby で ClamAV のコントロール
いま急激に温泉欲が高まっている suzuki です。
みなさん、Clam Anti Virus はご存知でしょうか?
オープンソースで開発が続けられている、ウイルス対策ソフトウェアです。
「オープンソースのウイルス対策ソフトウェアなんて大丈夫なの?」と思う方もいらっしゃるかも知れませんが、アップルの Mac OS X Server にも採用されている信頼のおけるステキなソフトウェアです。
今回は、この Clam Anti Virus(以下 ClamAV)を Ruby でコントロールしてみます。
ClamAV には、単体で動く clamscan コマンドと、デーモンとして clamd を起動しておき、そのデーモンと通信しつつウイルススキャンを行なう clamdscan コマンドが存在します。ここでは、この clamdscan に代替する部分を Ruby で作成してみました。
プログラムは、clamd デーモンとの通信を行なうライブラリ部分と、ファイルを読み込んでウイルスを発見した時の処理を行なうフロント部分とに分けてみました。
まずはライブラリ部分です。ちょっと手を抜いていて、clamd デーモンが localhost のデフォルトポート(3310)で動いていることを期待しています。なお、もし、ここに書いたサンプルを実行してみる場合には「clamd.rb」の名前で保存してください。
#!/bin/env ruby # # Clam Anti Virus Daemon 'clamd' control class # class Clamd require 'socket' ERROR_NOT_CONNECT = "Can not connect clamd daemon. perhaps, clamd is down ?" def initialize(host='127.0.0.1',port=3310) @host = host @port = port # check clamd is active ? begin result = ping if result != 'PONG' raise IOError.new(ERROR_NOT_CONNECT) end end end # Check the daemon's state (should reply with "PONG") def ping() result = execute('PING') result.chomp end # Print program and database versions. def version() result = execute('VERSION') result.chomp end # Reload the databases. def reload() result = execute('RELOAD') result.chomp! if result == "RELOADING" true else false end end # Perform a clean exit. def shutdown() result = execute('SHUTDOWN') result.chomp! end # Scan file or directory (recursively) with archive support enabled # (a full path is required). def scan(path) found = {} result = execute("SCAN #{path}") if result found = parse_result(result) end end # Scan file or directory (recursively) with archive and special # file support disabled (a full path is required). def rawscan(path) found = {} result = execute("RAWSCAN #{path}") found = parse_result(result) end # Scan file or directory (recursively) with archive support enabled # and don't stop the scanning when a virus is found. def contscan(path) found = {} result = execute("CONTSCAN #{path}") if result found = parse_result(result) end end # Scan file in a standard way or scan directory (recursively) using # multiple threads (to make the scanning faster on SMP machines). def multiscan(path) result = execute("MULTISCAN #{path}") if result found = parse_result(result) end end # Scan stream: clamd will return a new port number you should # connect to and send data to scan. def stream() execute("STREAM") end # Start/end a clamd session - you can do multiple commands per TCP # session (WARNING: due to the clamd implementation the RELOAD # command will break the session). def session() execute("SESSION") end def end() execute("END") end ############################################################## # private method ############################################################## private def execute(command) begin s = TCPSocket.open(@host,@port) if ! s raise IOError.new(ERROR_NOT_CONNECT) end end s.write(command) ret = '' while s.gets ret += $_ end s.close return ret end def parse_result(result) found = {} tmp = result.split("\n") while t = tmp.shift if /(.+):\s+([^:]+)\s+FOUND$/ =~ t file = $1 virus = $2 found[file] = virus end end found end end
やっていることは簡単で、socket を使って clamd デーモンと通信しているだけです。通信内容は、ClamAV のプロトコルに沿っています。
実は Ruby には、ClamAV を使う為の clamavr というライブラリが既に存在しますが、今回は前述のプロトコルを試してみたくて、自作しました。
次にフロント部分です。こちらはもっと手抜きで、スキャンするディレクトリは SCAN_DIR に指定したところ固定です。また、ウイルスを発見しても標準出力へ表示するだけです。
#!/bin/env ruby require 'clamd.rb' SCAN_DIR = '/usr/share/doc/clamav-0.94.2/test' c = Clamd.new() result = c.multiscan(SCAN_DIR) if result.size > 0 print 'WARNING: Virus Found !!!' + "\n" result.each do |file, type| print file + ':' + type + "\n" end else print 'OK: No Virus Found' + "\n" end
実際に上記の clamd.rb を使う時には、ウイルスの有無を syslog へ書き出したり、メールを出したりと言った処理を付け加えるのが良いと思います。
「ウイルスに感染したファイルをサーバ上に置かせない」為の対策を取るのが、まず第一だとは思いますが、「置かれていないことを確認する」手段を念のため用意するのも良いのではないでしょうか。
ところで、このライブラリを作っていた時に気がついたのですが、clamd デーモンは、クライアントプログラム側から SHUTDOWN リクエストを送ると、見事に停止してくれます。クライアントプログラムとは言わず、telnet コマンドで次のように打ち込んでも停止してくれます。
$ telnet 127.0.0.1 3310
Trying 127.0.0.1...
Connected to localhost (127.0.0.1).
Escape character is '^]'.
SHUTDOWN
Connection closed by foreign host.
これって root ユーザではなく、一般ユーザでも終了できちゃいます。個人的には、ちょっと気持ち悪いんですが、何か回避方法ってあるのでしょうか?
ディスカッション
コメント一覧
まだ、コメントがありません