カテゴリー: IRC

PHPでIRCのログ収集を行うbotを作成

IRCで発言を逃さない為に・・・

弊社では作業環境としてMacBookProを使用している人も多いのですが、ノートパソコンだとしばらくするとスリープになってしまい、ネットワークも切れるため、IRCサーバーからログアウトした状態になり、その間の発言ログは見る事ができない状態になります。

単にログを収集するbotを簡単に作れないかと思い調べてみました。

PHPにこだわらないのであればIRCproxyの「tiarra」等で上記の問題は対応でき、完璧なツールです、が今回はスルーです。(tiarraについてはカヤックさんのブログがとてもわかりやすかったです。)
stone を使って tiarra を SSL 化する方法

PEAR::Net_SmartIRC を使ってPHPで作る

PEARにてばっちりなIRC用のPHPクラスがあったのでこちらを使いました。

pear install Net_SmartIRC

とりあえず作成したPHPソースの全文です。
88. PEAR::Net_SmartIRCではじめるIRC Bot入門
を参考にしました。

logbot.php
[code]
<?php

include_once(‘Net/SmartIRC.php‘);

define(“LOG_PATH”, “/path/to/irc-logs.txt”); //ログの保存先
$irc_host = “localhost“; //IRCサーバー
$irc_port = “6667”; // IRCサーバーのポート
$irc_name = “bot“; // 名前
$irc_channel = “#hogehoge”; // チャンネル名

class mybot
{
function welcome(&$irc, &$data)
{
// welcome
$irc->message(SMARTIRC_TYPE_NOTICE, $data->channel, $data->nick.’さん : ようこそー。’);
}

        function url(&amp;$irc,&amp;$data)
{
preg_match_all('|http://\w+(?:-\w+)*(?:\.\w+(?:-\w+)*)+(?::\d+)?(?:[/\?][\w%&amp;=~\-\+/;\.\?]*(?:#[^]*)?)?|', $data-&gt;message, $match);
$url=$match[0][0];
$urldata = file_get_contents($url);
$urldata = mb_convert_encoding($urldata, "UTF-8", "auto" );
preg_match( "/<title>(.*?)/i", $urldata, $matches);
$irc-&gt;message(SMARTIRC_TYPE_NOTICE, $data-&gt;channel,$matches[1] . ' - ' . $url );
}
function getlog(&amp;$irc,&amp;$data)
{
$data-&gt;message;
$fp = fopen(LOG_PATH, "a+");
fwrite($fp, date("Y-m-d H:i:s") . ' - (' . $data-&gt;nick . ') - ' . "$data-&gt;message \n");
fclose($fp);
}

}

$bot = &new mybot();
$irc = &new Net_SmartIRC();
//$irc->setDebug(SMARTIRC_DEBUG_ALL);
$irc->setUseSockets(TRUE);
$irc->registerActionhandler(SMARTIRC_TYPE_CHANNEL, ‘(?:^|[\s ]+)*1;
$irc->listen();
$irc->disconnect();

[/code]

簡単な解説

詳細なドキュメントはこちらです。
http://pear.php.net/package/Net_SmartIRC/docs/latest/Net_SmartIRC/Net_SmartIRC_base.html

URLを含む発言があったら、そのページのタイトルを取得して発言させる

[code]
$irc->registerActionhandler(SMARTIRC_TYPE_CHANNEL, ‘(?:^|[\s ]+)((?:https?|ftp):\/\/[^\s ]+)’, $bot, ‘url’);
[/code]

これはログの保存とは関係ないですが・・・
registerActionhandlerの第一引数でSMARTIRC_TYPE_CHANNELを選ぶ事で発言を監視し、第二引数の正規表現にマッチした場合に特定の動作を行わせる事ができます。
上記の例だと発言にURLが含まれていた場合にmybotクラスのurl関数を実行します。
url関数はURLに接続してtitleタグを取得しNOTICEとしてIRCでタイトルとURLを発言します。
こんな感じになります ↓

ログを収集する

[code]
$irc->registerActionhandler(SMARTIRC_TYPE_CHANNEL, ‘.*’, $bot, ‘getlog’);
[/code]
こちらがメインのログを収集する処理。
さきほどと同じく発言を監視しますがすべての発言にマッチするような正規表現となっています。
今回はgetlog関数を実行し、時間と発言者と発言内容をファイルに書きだす処理になってます。

JOINしたユーザーにwelcomeメッセージをとばす

[code]
$irc->registerActionhandler(SMARTIRC_TYPE_JOIN, ‘.*’, $bot, ‘welcome’);
[/code]
こちらはお遊び。第一引数をSMARTIRC_TYPE_JOINとするとユーザーがJOINしてきた時になんらかの動作を行わせる事ができます。
今回はチャンネルにJOINしてきた方へbotは「ようこそー」と発言させています。

実行

バックグラウンドで実行しておきます。

php logbot.php &amp;

このときIRCサーバーに正常にログインし、指定されたチャンネルへbot君がJOINしていれば成功。

おまけ

あとは書き出されたログをPHPとかで表示してやればOKです。
ドキュメントルート以下におけばブラウザから参照できますね。

logview.php

[code]
<!DOCTYPE html>
<html lang=”ja”>
<head>
<meta content=”text/html; charset=utf-8” http-equiv=”Content-Type”>
<title>IRC Log
</head>
<body>
<p>

<?php
$lines = file(‘/path/to/irc-logs.txt’);
foreach ($lines as $line_num => $line) {
echo nl2br(htmlspecialchars($line));
}
?>

</p>
</body>
</html>
[/code]

*1:?:https?|ftp):\/\/[^\s ]+)’, $bot, ‘url’);
$irc->registerActionhandler(SMARTIRC_TYPE_CHANNEL, ‘.‘, $bot, ‘getlog’);
$irc->registerActionhandler(SMARTIRC_TYPE_JOIN, ‘.
‘, $bot, ‘welcome’);
$irc->connect($irc_host, $irc_port);
$irc->login($irc_name, $irc_name, 0, $irc_name, $irc_pass);
$irc->join(array($irc_channel

stoneを使ってIRCのSSL暗号化を行う

stone は、アプリケーションレベルの TCP & UDP リピーターです。
stone – http://www.gcd.org/sengoku/stone/Welcome.ja.html

IRCサーバーはSSL暗号化に対応しているものが少なく、データがすべて平文でやりとりされてしまう。
stoneは任意のポートからポートへトンネルをつくる事が出来るアプリケーションなんですが、
stoneがSSLに対応している為、こちらを使ってIRCSSL対応を行います。

これはポートを待ち受けるアプリケーションであればどのアプリケーションでも暗号化可能かと思います。

イメージしにくいかもしれないので以下がイメージ図。

IRCサーバーは6667で待ち受けているアプリケーション。
stoneを使う事で6668ポートと6667ポートを中継し、クライアントは6668ポートに繋ぐ事でIRCへ接続できる。
またstoneはSSLに対応している為、6668ポートはSSL暗号化を利用する事ができる。

stoneのインストール

すこしハマったので詳細を記述します。

[code]
cd /usr/local/src
wget http://www.gcd.org/sengoku/stone/stone-2.3e.tar.gz
tar zxvf stone-2.3e.tar.gz
make linuxssl
[/code]

こんなエラーが出てしまった

[code]
# make linuxssl
make TARGET=linux ssl_stone LIBS=”-ldl”
make[1]: Entering directory /usr/local/src/stone-2.3d-2.3.2.7'
make FLAGS="-DUSE_POP -DUSE_SSL " LIBS="-ldl -lssl -lcrypto" linux
make[2]: Entering directory
/usr/local/src/stone-2.3d-2.3.2.7′
make FLAGS=”-O -Wall -DCPP=’\”/usr/bin/cpp -traditional\”‘ -DPTHREAD -DUNIX_DAEMON -DPRCTL -DSO_ORIGINAL_DST=80 -DUSE_EPOLL -DUSE_POP -DUSE_SSL ” LIBS=”-lpthread -ldl -lssl -lcrypto” stone
make[3]: Entering directory /usr/local/src/stone-2.3d-2.3.2.7'
cc -O -Wall -DCPP='"/usr/bin/cpp -traditional"' -DPTHREAD -DUNIX_DAEMON -DPRCTL -DSO_ORIGINAL_DST=80 -DUSE_EPOLL -DUSE_POP -DUSE_SSL -o stone stone.c -lpthread -ldl -lssl -lcrypto
stone.c: In function ‘saDup’:
stone.c:1558: warning: cast from pointer to integer of different size
stone.c: In function ‘sendPairUDPbuf’:
stone.c:3023: warning: cast from pointer to integer of different size
stone.c: In function ‘freePair’:
stone.c:3644: warning: cast from pointer to integer of different size
stone.c: In function ‘doconnect’:
stone.c:3883: warning: cast from pointer to integer of different size
stone.c: In function ‘acceptPair’:
stone.c:4072: warning: cast from pointer to integer of different size
stone.c: In function ‘strnUser’:
stone.c:4509: error: dereferencing pointer to incomplete type
stone.c:4524: error: dereferencing pointer to incomplete type
stone.c:4536: error: dereferencing pointer to incomplete type
stone.c:4551: error: dereferencing pointer to incomplete type
stone.c:4557: error: dereferencing pointer to incomplete type
stone.c: In function ‘proto2fdset’:
stone.c:6239: warning: cast from pointer to integer of different size
stone.c:6300: warning: cast from pointer to integer of different size
stone.c: In function ‘dispatch’:
stone.c:6916: warning: cast from pointer to integer of different size
stone.c: In function ‘newMatch’:
stone.c:7070: warning: cast from pointer to integer of different size
stone.c: In function ‘freeMatch’:
stone.c:7084: warning: cast from pointer to integer of different size
stone.c: In function ‘initialize’:
stone.c:10178: warning: cast from pointer to integer of different size
make[3]: *** [stone] Error 1
make[3]: Leaving directory
/usr/local/src/stone-2.3d-2.3.2.7′
make[2]: [linux] Error 2
make[2]: Leaving directory /usr/local/src/stone-2.3d-2.3.2.7'
make[1]: *** [ssl_stone] Error 2
make[1]: Leaving directory
/usr/local/src/stone-2.3d-2.3.2.7′
make:
[linuxssl] Error 2
[/code]

Debian Squeezeで発生。Lennyでは出ませんでした。
どうやらglibc 2.8 以降では、この構造体の定義を得るためには機能検査マクロ _GNU_SOURCE を定義しなければならないようです。
解決方法としては、Makefileの以下の記述部分に、-D_GNU_SOURCEを追加することで正常にコンパイルできるようになりました。
(参考) http://typex2.wordpress.com/2009/10/25/stone%E3%82%92linux%E3%81%A7%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%AB%E3%81%99%E3%82%8B/

[code]
linux:
$(MAKE) FLAGS=”-O -Wall -DCPP=’\”/usr/bin/cpp -traditional\”‘ -DPTHREAD -DUNIX_DAEMON -DPRCTL -DSO_ORIGINAL_DST=80 -DUSE_EPOLL $(FLAGS)” LIBS=”-lpthread $(LIBS)” stone

linux:
$(MAKE) FLAGS=”-O -Wall -DCPP=’\”/usr/bin/cpp -traditional\”‘ -DPTHREAD -DUNIX_DAEMON -DPRCTL -DSO_ORIGINAL_DST=80 -DUSE_EPOLL -D_GNU_SOURCE $(FLAGS)” LIBS=”-lpthread $(LIBS)” stone
[/code]

再度実行。

[code]
# make linuxssl
make TARGET=linux ssl_stone LIBS=”-ldl”
make[1]: Entering directory /usr/local/src/stone-2.3d-2.3.2.7'
make FLAGS="-DUSE_POP -DUSE_SSL " LIBS="-ldl -lssl -lcrypto" linux
make[2]: Entering directory
/usr/local/src/stone-2.3d-2.3.2.7′
make FLAGS=”-O -Wall -DCPP=’\”/usr/bin/cpp -traditional\”‘ -DPTHREAD -DUNIX_DAEMON -DPRCTL -DSO_ORIGINAL_DST=80 -DUSE_EPOLL -D_GNU_SOURCE -DUSE_POP -DUSE_SSL ” LIBS=”-lpthread -ldl -lssl -lcrypto” stone
make[3]: Entering directory /usr/local/src/stone-2.3d-2.3.2.7'
cc -O -Wall -DCPP='"/usr/bin/cpp -traditional"' -DPTHREAD -DUNIX_DAEMON -DPRCTL -DSO_ORIGINAL_DST=80 -DUSE_EPOLL -D_GNU_SOURCE -DUSE_POP -DUSE_SSL -o stone stone.c -lpthread -ldl -lssl -lcrypto
stone.c: In function ‘saDup’:
stone.c:1558: warning: cast from pointer to integer of different size
stone.c: In function ‘sendPairUDPbuf’:
stone.c:3023: warning: cast from pointer to integer of different size
stone.c: In function ‘freePair’:
stone.c:3644: warning: cast from pointer to integer of different size
stone.c: In function ‘doconnect’:
stone.c:3883: warning: cast from pointer to integer of different size
stone.c: In function ‘acceptPair’:
stone.c:4072: warning: cast from pointer to integer of different size
stone.c: In function ‘proto2fdset’:
stone.c:6239: warning: cast from pointer to integer of different size
stone.c:6300: warning: cast from pointer to integer of different size
stone.c: In function ‘dispatch’:
stone.c:6916: warning: cast from pointer to integer of different size
stone.c: In function ‘newMatch’:
stone.c:7070: warning: cast from pointer to integer of different size
stone.c: In function ‘freeMatch’:
stone.c:7084: warning: cast from pointer to integer of different size
stone.c: In function ‘initialize’:
stone.c:10178: warning: cast from pointer to integer of different size
make[3]: Leaving directory
/usr/local/src/stone-2.3d-2.3.2.7′
make[2]: Leaving directory /usr/local/src/stone-2.3d-2.3.2.7'
make[1]: Leaving directory
/usr/local/src/stone-2.3d-2.3.2.7′
[/code]

なんか警告は出てますが無事コンパイル完了しました。

パスが通る位置にコンパイル済のstoneを移動
[code]
cp -rfpa stone /usr/local/bin/
[/code]

動作テスト

動いてるポートへ別ポートから接続できるかテスト。(6668から6667へ接続例)
[code]
stone localhost:6667 6668
Mar 27 12:00:18.903642 139940579038976 start (2.3e) [21488]
Mar 27 12:00:18.904514 139940579038976 stone 3: 127.0.0.1:ircd <- 0.0.0.0:6668
stone localhost:6667 6668
[/code]

これでircサーバーに6667でも6668でも接続が可能になる。
次は暗号化して待ち受けテスト

[code]
stone localhost:6667 6668/ssl
Mar 27 12:19:26.297086 140086834108160 start (2.3e) [23145]
Mar 27 12:19:26.297835 140086834108160 SSL_CTX_use_PrivateKey_file(/usr/lib/ssl/certs/stone.pem) error:02001002:system library:fopen:No such file or directory
[/code]

証明書がないというエラーが出て立ち上がらない。
どうやらデフォルトで/usr/lib/ssl/certs/stone.pemにある証明書&keyを利用するようなのでここに証明書を作成する。(要openssl)

[code]
openssl req -new -outform pem -out stone.cert -newkey rsa:2048 -keyout stone.key -nodes -rand ./rand.pat -x509 -batch -days 3560
cat stone.cert stone.key > stone.pem
cp stone.pem /usr/lib/ssl/certs/
[/code]

2048bitで3560(10年)使用できる鍵を作成。
普段使用している証明書と鍵でも
証明書ファイル 鍵ファイル の順番のファイルを作ればOKです。
改めてSSL接続テスト。

[code]
stone localhost:6667 6668/ssl
Mar 27 12:34:04.695961 140533134583552 start (2.3e) [24813]
Mar 27 12:34:04.697198 140533134583552 stone 3: 127.0.0.1:ircd <- 0.0.0.0:6668/ssl
[/code]

6668ポートにSSL接続できる事が確認できます。
これでSSLでの待ち受けもOKです。

起動

こんな感じで起動
[code]
stone localhost:6667 6668/ssl >& /dev/null &
[/code]
Linuxファイアウォール設定でircの6667ポートを閉じて6668をOPENにする。
あとはIRCクライアント側(LimeChatとか)の設定で 6668ポートにSSLで接続できるようになります。

備考

今のところ僕の環境では問題ないですが、クリティカルな環境ではstoneが落ちたりするのを防ぐ為に
プロセス監視を行うかdaemontoolsで監視するなどの設定が必要だと思います。

IRCサーバーの構築

なにかと使えるIRCサーバーの構築手順です

ircd-hybridのインストール

[code]
apt-get install ircd-hybrid
[/code]

設定

デフォルトの設定ファイルは修正箇所がとても多いので、
サンプルで用意されているsimple.confを使用する。

[code]
mv /etc/ircd-hybrid/ircd.conf /etc/ircd-hybrid/ircd.conf_bk
cp -rfpa /usr/share/doc/ircd-hybrid/examples/simple.conf /etc/ircd-hybrid/ircd.conf
[/code]

設定開始
[code]
vi /etc/ircd-hybrid/ircd.conf
[/code]

一つづつ項目を編集します。

[code]
serverinfo {
name = “hogehoge.com”;
sid = “392”;
description = “IRC Server”;
hub = no;
};
[/code]
-nameを適当な名前に
-sidはサーバーのID?日本は392 と決まっているらしいので392に
 よくわかってません。

[code]
administrator {
description = “IRC Server”;
name = “hoge“;
email = “hoge@hogehoge.com”;
};
[/code]

-適当に任意の名前に修正した

[code]
class {
name = “users”;
ping_time = 90;
number_per_ip = 0;
max_number = 200;
sendq = 100000;
};

class {
name = “opers”;
ping_time = 90;
number_per_ip = 0;
max_number = 10;
sendq = 500000;
};
[/code]

-変更なし

[code]
auth {
user = “@“;
class = “users”;
password = “md5化したパスワード”
encrypted = yes;
};
[/code]

-password行を追加。
-passwordはmd5化したパスワードを入力
md5暗号化したパスワードの作成方法はあとで記述します。

[code]
operator {
name = “hoge“;
user = “@.hogehoge.com”;
password = “md5化したパスワード”;
encrypted = yes;
class = “opers”;
};
[/code]

-nameなどを適当に設定。
-passwordはmd5化したパスワードを入力。
md5暗号化したパスワードの作成方法はしたへ。

その他はデフォルト設定。

MD5で暗号化した文字列の作成方法

[code]

/usr/bin/mkpasswd

password : MD5化したい文字列(パスワード)を入力
hdTabFnEY7//2 ←MD5化されたパスワードが出力されるのでこぴぺ
[/code]

ircd-hybridの起動

[code]
/etc/init.d/ircd-hybrid start
[/code]

ポートを空ける

ポートが閉じている場合は6667ポートを空ける
(デフォルトでは6667ポートを使用する)

テスト

以上で構築は終了です。
クライアントから接続のテストを行ってみましょう。

© SEEDS Co.,Ltd.