nss_ldap の接続タイムアウトを変える

PAM (Pluggable Authentication Modules)認証で、pam_ldap.so モジュールを使用するとき、LDAPに接続できないと、長いこと待たされる。LDAP側で管理しているユーザではなく、shadowファイルなどで認証チェックがOKとなっても、2分くらい待たされる。なんでじゃ。

PAMのsystem-authはこんな感じ。

#%PAM-1.0
auth        required      /lib/security/pam_env.so
auth        sufficient    /lib/security/pam_unix.so likeauth nullok
auth        sufficient    /lib/security/pam_ldap.so use_first_pass
auth        required      /lib/security/pam_deny.so

account required /lib/security/pam_unix.so broken_shadow account [default=bad success=ok user_unknown=ignore service_err=ignore system_err=ignore authinfo_unavail=ignore] /lib/security/pam_ldap.so account required /lib/security/pam_permit.so
password required /lib/security/pam_cracklib.so retry=3 type= password sufficient /lib/security/pam_unix.so nullok use_authtok md5 shadow password sufficient /lib/security/pam_ldap.so use_authtok password required /lib/security/pam_deny.so
session required /lib/security/pam_limits.so session required /lib/security/pam_unix.so session optional /lib/security/pam_ldap.so

ついでに、nsswitch.confも。

 passwd:     files ldap
 shadow:     files ldap
 group:      files ldap

まあ、よくある基本的なLDAP認証の設定です。これで、LDAPに登録したアカウントでの認証もできるし、無論LDAPに登録していない元からのアカウントでの認証もできる。つまり、LDAPが落ちていても、shadowファイルとかに情報があれば、認証をパスできるわけです。

ところが、実際にLDAPを落として、SSHでログインを試みると、ログイン認証ごときに尋常じゃなく待たされる。ログ(/var/log/secure)を見てみたら、こんなことに!

sshd[29791]: Accepted password for plab from xxx port 3496 ssh2
sshd[29793]: nss_ldap: failed to bind to LDAP server ldap://xxx: Can't contact LDAP server
sshd[29793]: nss_ldap: failed to bind to LDAP server ldap://xxx: Can't contact LDAP server
sshd[29793]: nss_ldap: reconnecting to LDAP server (sleeping 4 seconds)...
sshd[29793]: nss_ldap: failed to bind to LDAP server ldap://xxx: Can't contact LDAP server
sshd[29793]: nss_ldap: reconnecting to LDAP server (sleeping 8 seconds)...
sshd[29793]: nss_ldap: failed to bind to LDAP server ldap://xxx: Can't contact LDAP server
sshd[29793]: nss_ldap: reconnecting to LDAP server (sleeping 16 seconds)...
sshd[29793]: nss_ldap: failed to bind to LDAP server ldap://xxx: Can't contact LDAP server
sshd[29793]: nss_ldap: reconnecting to LDAP server (sleeping 32 seconds)...
sshd[29793]: nss_ldap: failed to bind to LDAP server ldap://xxx: Can't contact LDAP server
sshd[29793]: nss_ldap: reconnecting to LDAP server (sleeping 64 seconds)...
sshd[29793]: nss_ldap: failed to bind to LDAP server ldap://xxx: Can't contact LDAP server
sshd[29793]: nss_ldap: could not search LDAP server - Server is unavailable

どうやら認証処理うんぬんではなく、LDAPサーバに接続できるまで、時間を置きながら最大5回までチャレンジしているため、時間がかかりまくっているみたい。何らかの原因でLDAPサービスが落ちたとき、復旧でSSH接続しようとしたら2分かかりました、ってのはキツイ。もうちょっと空気読もうよ、nss_ldapさんっ!

実際、LDAPサービスの死活を判断するのに、トータル124秒も待って5回も再接続を試みる必要があるケースは少ないと思う。同じネットワークエリア内にドメインコントローラを置いたりするなら、これは大げさすぎるんじゃないか、と。

ということで、nss_ldapの再試行回数やタイムアウト時間を変更する方法。

続きを読む "nss_ldap の接続タイムアウトを変える"

リモートサーバでコマンドを実行するSSH2関数

/ php

PECLのssh2モジュールを使うと、PHPでも簡単にリモートサーバを直接操作することができるっぽいので、セキュリティうんぬんはさておき、試してみた。

PHP: Secure Shell2 関数

まずは、libssh2 のインストール。

# wget http://nchc.dl.sourceforge.net/sourceforge/libssh2/libssh2-0.17.tar.gz
# tar xvfz libssh2-0.17
# cd libssh2-0.17
# ./configure --prefix=/usr/local/
# make all install

続いて、ssh2モジュールをソースから導入。

# wget http://pecl.php.net/get/ssh2-0.10.tgz
# tar xvfz ssh2-0.10
# cd ssh2-0.10
# /usr/local/bin/phpize
# ./configure --with-ssh2=/usr/local/lib
# make

ここで、ひたすらmakeに失敗して焦る。原因は、すでに入っていた古いlibssh2を参照していたため。--with-ssh2でパスを指定したら、うまくいきました。ほっ。

ソースディレクトリ配下のmodulesの下に、ssh2.soができるので、これをphp.iniのextension_dirで指定したディレクトリにコピー。続けて、php.iniでextensionに追加する。

php.ini

; Directory in which the loadable extensions (modules) reside.
extension_dir = "/usr/local/lib/php/extensions/"

extension=ssh2.so

ssh2.soをコピー。

# cp /usr/local/src/ssh2-0.10/modules/ssh2.so "/usr/local/lib/php/extensions/

ここまでできたら、apacheを再起動。うまくインストールできていれば、phpinfoでssh2の項目がでてくる。

簡単な使い方。

$con = @ssh2_connect("server_name", 22);
ssh2_auth_password($con, 'user', 'password');
$stream = ssh2_exec($con, "ls -al | wc -l");
stream_set_blocking($stream, true);
echo fread($stream, 4096);
fclose($stream);

結果。

12

ファイル削除など、出力のないコマンドの実行結果をとるなら、こんな感じかな?

$con = @ssh2_connect("server_name", 22);
ssh2_auth_password($con, 'user', 'password');
$stream = ssh2_exec($con, "rm -rf /tmp/hoge; echo $?");
stream_set_blocking($stream, true);
echo fread($stream, 4096);
fclose($stream);

結果。

0

まあ、実際に使うことは無さそうだけど、PHPだとこういうことも簡単にできるんだなーということで、ひとり満足。もはや独自プロトコルの対応以外では、自分でソケット操作とか面倒でしてられないもん。

お疲れさまでした。

PHPで正規表現のメタ文字をエスケープする

/ php

PHPを始めて感じたのは、「PHPは痒いところに手が届いた関数が豊富」ということ。mb_strimwidth関数のように、自分でもすぐに書けそうな関数も標準装備してくれているのが助かる。そんなわけで、最近は車輪の再発明にならないように、関数を探すクセがついた。

で、正規表現のメタ文字をエスケープしてくれるような関数って無いかなーと思って、検索したけど見つからず。無念。ありそうなんだけどなあ。

ということで、適当に作ってみた。

function escapeRegexp($str)
{
    $str = str_replace('\\', '\\\\', $str);
    $str = str_replace('*', '\\*', $str);
    $str = str_replace('+', '\\+', $str);
    $str = str_replace('.', '\\.', $str);
    $str = str_replace('?', '\\?', $str);
    $str = str_replace('(', '\\(', $str);
    $str = str_replace(')', '\\)', $str);
    $str = str_replace('{', '\\{', $str);
    $str = str_replace('}', '\\}', $str);
    $str = str_replace('[', '\\[', $str);
    $str = str_replace(']', '\\]', $str);
    $str = str_replace('^', '\\^', $str);
    $str = str_replace('$', '\\$', $str);
    $str = str_replace('|', '\\|', $str);
     return $str;
}

全メタ文字が網羅されているか確認とってないけど、こんな感じで要望は満たせました。もっと効率的で確実な方法があると良いんだけど。教えて、エロい人!

Apacheをroot権限で動かす裏ワザ

Apacheはroot権限で起動したあと、セキュリティ確保のため、子プロセスを一般ユーザー(nobodyとか)で生成する。今日は、この子プロセスも、rootで起動しちゃおうという話。

CGIは通常、子プロセスの一般ユーザー権限で実行される。そのため、より高い権限が必要なファイル操作を行おうとすると、パーミッションエラーになったりする。この手の問題には、suexecで対処するのがセオリーだけど、「そもそも実行ユーザーをrootにしちゃえば、何でもできるんじゃないの?」というコペルニクス的転回があって、試してみたら、できちゃった。おおっ!

以下、その方法。


続きを読む "Apacheをroot権限で動かす裏ワザ"

chownで Unable to find uid エラー

/ php

PHPのchown関数を使ったらこんな警告が。

Warning: chown() [function.chown]: Unable to find uid for 1022

「uidが1022のユーザーなんていないよ」ってことなんだけど、実際に/etc/passwdとか見ても存在しているし、bashでchownコマンドを打つと正常に機能する。

該当処理は、こんな感じ。

public function my_chown($file, $uid, $gid)
{
    chown($file, $uid);
    chgrp($file, $gid);
}

30分ほどハマって、「もしかして、1022って名前のユーザを探してるんじゃ?」と思って調べたら、大正解。$uidと$gidがstringで渡ってきていたため、名前解決を頑張った結果だった。うわー。

var_dump($uid);

string(4) "1022"

ちゃんと判定して型キャストしてあげたら、思い通り動いてくれた。

public function my_chown($file, $uid, $gid)
{
    if (is_numeric($uid)) {
        $uid = (int) $uid;
    }
    if (is_numeric($gid)) {
        $gid = (int) $gid;
    }
    chown($file, $uid);
    chgrp($file, $gid);
}

これだから型が厳密じゃない言語ってのは…、と軽くキレそうになったけど、んなこと言っている暇があるたら、早く慣れてガリガリとアプリ開発できるように書き続けた方が、建設的だよね。ふぅ。

removeChildではまる

NodeListのノードをremoveChildで順番に消していこうとしたら、半分くらい残る。なぜ。

var tbody = document.getElementsById("hoge");
var nodelist = tbody.getElementsByTagName("tr");

var len = nodelist.length; for (var i = 0; i < len; i++) { tbody.removeChild(nodelist[i]); }

上手く説明できないけど、nodelistの数値インデックスの関係がおかしくなっている。getElementsByTagNameが返すNodeListは参照で、ノードを削除していくごとに、動的にNodeListの長さも変わっていくのかな。だから、削除していくと、「そんな数字インデックスに該当するノードは無いよー」ってなってるっぽい。

ということで、ノードの削除は、常にNodeListの先頭のものを対象に実行するとキレイに行く。

while (nodelist.length > 0) {
    tbody.removeChild(nodelist[0]);
}

仕組みが分かれば、そりゃそうだって話なんだよね。リスト操作の基本か。こういうこと考えると、Iteratorがあると便利なのかもしれない。

続きを読む "removeChildではまる"

PHPで連想配列を複数項目でソート

/ php

SQLのORDER BYみたく、きめ細やかなソートをするには、usort関数を使う。

name と price というキーに対して、それぞれ値を持つ配列があったとき、これをpriceの降順、nameの昇順に並べる例。

// 比較関数
function compare($a, $b) {
        if ($a['price'] > $b['price']) {
                return -1;
        } else if ($a['price'] < $b['price']) {
                return 1;
        } else {
                return strnatcmp($a['name'], $b['name']);
        }
}

$list = array( array( 'name' => 'apple', 'price' => 100, ), array( 'name' => 'grape', 'price' => 200, ), array( 'name' => 'orange', 'price' => 100, ), array( 'name' => 'banana', 'price' => 200, ), );
usort($list, "compare"); print_r($list);

ソート結果。

Array
(
    [0] => Array
        (
            [name] => banana
            [price] => 200
        )
    [1] => Array
        (
            [name] => grape
            [price] => 200
        )
    [2] => Array
        (
            [name] => apple
            [price] => 100
        )
    [3] => Array
        (
            [name] => orange
            [price] => 100
        )
)

usortの第2引数に、比較演算をする独自関数名を指定する。クラスのメンバ関数を指定したい場合は、配列で渡すとできるみたい。

// AnyClass::compare を指定する場合
usort($list, array("AnyClass", "compare"));

javaのComparatorインタフェースを使ったソートに近い。でも、こういうのって使い切りがほとんどだから、無名関数で指定できたりすると便利なんだけどなあ…。そういう方法もあったりするんだろうか。