2015年5月18日月曜日

linuxで稼働する操作コマンド記録ツール


linux上で操作されたオペレーションをログとして日時と共に記録するツール(logrec)を作成した。
telnet、ssh等で別サーバへログインしていっても、そこでの操作記録はすべてログイン元サーバで一元的に保存される。
対象は入力デバイスからの操作のみであり、コマンドの結果は記録しないものとする。


(参考)
Cによるselectを利用した簡易プログラム
コマンドライン引数を取得するCのサンプルコード
【review】 Unix/Linux プログラミング 理論と実践



◆ コード
logrec.cの一つだけである。
全コードはgithubを参照のこと。
https://github.com/alpha-netzilla/logrec
※linuxで使われるscriptというコマンドのコードを改造している。


◆ 使い方
1. ビルド
$ gcc -lutil logrec.c -o logrec

2. 実行
引数は結果を保存するファイルである。
$ ./logrec /var/logrec/some_user

3. 記録
コマンドを適当に打ち操作を記録させる。
当然コマンドは何でもよい。
$ ls
hoge piyo
$ ssh example.com
example.com's password:***
Last login:...
$ ls
foo bar

4. 確認
ツールが起動した旨と、実行したコマンドのみが日時とともに記録されていることを確認する。
$ cat /var/logrec/some_user
yyyy-mm-dd 00:00:00 logrec started
yyyy-mm-dd 00:00:05 $ ls
yyyy-mm-dd 00:00:10 $ ssh example.com
yyyy-mm-dd 00:00:15 $ ls

ファイルへの結果は即フラッシュさせないで、バッファリングして出力させている。
そのため、数コマンドでは結果を確認できないかもしれない。
何文字以上で書き込むかはOSに任せている。

5. 停止
Ctrl + D



◆ 動作ロジック
コード内に適宜コメントを残しているため、ポイントだけを記載しておく。

(ポイント1)
目的の実現のために、仮想端末(pty)を利用している。
仮想端末は、物理的な端末の入出力を任意のプログラムでエミュレーションする仕組みである。

ツール内で親がforkしその後にその子がさらにforkして子プロセスを生む。
それぞれの役割を記す。

・親:parent
端末から入力を読み込んで、pty(master)へ書き出すプロセスである。

・子:child1
pty(master)から入力を読み込み、端末に書き出すプロセスである。

・子:child2(child1から呼ばれる)
shellを起動し、制御端末になるプロセスである。
ただし、このプロセスをセッションリーダにするため親子関係は断ち切らせる。
pty(slave)からの入出力用ファイルディスクリプタをこのプロセスが受け持つ。


inとoutの流れを見る際にptyは、masterとslaveが2つではじめて仮想端末の役割を果たすことを注意しておきたい。
例えば、pty(master)に入った通信は必ずpty(slave)へ出力され、
pty(master)からの出力はpty(slave)からの入力が対象となる。


概略図を書いてみる。

+--------+
| client |
+--------+
  |   ^
  |   |
  |   +-----------------+
  v                     |
+--------+   fork   +--------+    fork     +---------+
| parent | -------> | child1 | ----------> |  child2 |
+--------+          +--------+             | ->shell |
    |                    ^                 +---------+
    |                    |                      ^
    |                    |                      |
    |                    |                      |
    |                    |                      |
    |             + -----|----------------------v---------+
    |             | +-------------+        +------------+ |
    +-------------> | pty(master) | <----> | pty(slave) | |
                  | +-------------+        +------------+ |
                  +---------------------------------------+


sshでも同様の仕組みが使われている。
なじみが深いリモートログインの仕組みで役割を読み替えると分かりやすいだろう。

・client: sshクライアント
parent: sshd
child1: なし(forkは一回だけである)
child2: shell

logrec内でchild1プロセスをforkした理由は、
ここで操作ログを取得するロジックをはさみたかったためである。


(ポイント2)
実行されたコマンド単位に、その先頭に日時を付与したい。
そこで改行時を終了時として、コマンドの終わりと判断する。

parentプロセスからの入力をchild1に記録させるが、
両プロセス間で日時の付与するタイミングをどのように知らせて、連携をとればよいだろうか。

いくつか手段はあるが、今回は共有メモリを利用することとした。
プロセス同士はメモリ空間を共有しないのだが、共有するための専用の空間がある。
改行がくれば、そのことを知らせるフラグを立て、日時を追加する。



(参考)
http://rachid.koucha.free.fr/tech_corner/pty_pdip.html
http://www.glamenv-septzen.net/view/563
http://www.glamenv-septzen.net/view/854
http://alpha-netzilla.blogspot.com/2014/12/expect.html