読者です 読者をやめる 読者になる 読者になる

seri::diary

プログラミングのこととかポエムとか

Windows環境でPHP OpenIDを使う場合の注意点

ハマった PHP OpenID

先日よりPHP OpenID Libraryの使い方を調べていたが、
CentOS上では上手くサンプルが動いたのでwindowsでもやってみた。
んで、案の定見事にハマったので対策をまとめておく。


/dev/urandomが読み込めなくてエラー

PHP OpenIDライブラリのインストール方法等は別を見てもらうとして(ぇ、
サンプルをドキュメントルートに置いていきなり実行した所以下のWarningが出た。

ファイル名:OpenID/CryptUtil.php

Define Auth_OpenID_RAND_SOURCE as null to continue with an insecure random number generator

どうやら『Auth_OpenID_RAND_SOURC』という定数の値がnullですよーということらしい。
因みにエラーメッセージを返してきた該当箇所は以下のコードの中程にあるtrigger_error関数。

<?php

//(前略)

   static $f = null;
   $bytes = '';
   if ($f === null) {
       if (Auth_OpenID_RAND_SOURCE === null) {
           $f = false;
       } else {
           $f = @fopen(Auth_OpenID_RAND_SOURCE, "r");
           if ($f === false) {
               $msg = 'Define Auth_OpenID_RAND_SOURCE as null to ' .
                      ' continue with an insecure random number generator.';
               trigger_error($msg, E_USER_ERROR); 
           }
       }
    }

    if ($f === false) {
        // pseudorandom used
        $bytes = '';
        for ($i = 0; $i < $num_bytes; $i += 4) {
            $bytes .= pack('L', mt_rand());
         }
         $bytes = substr($bytes, 0, $num_bytes);
      } else {
          $bytes = fread($f, $num_bytes);
      }
   
    return $bytes;

因みにこの関数が呼ばれる前に
以下の処理が必ずコールされている。

<?php

//(前略)

if (!defined('Auth_OpenID_RAND_SOURCE')) {
 
   define('Auth_OpenID_RAND_SOURCE', '/dev/urandom');
}

処理順としては

  1. 定数「Auth_OpenID_RAND_SOURCE」に/dev/urandomというファイルパスの文字列を代入
  2. /dev/urandomの中身の乱数を読み込む
  3. もし乱数を読み込めなかったら生成し、読み込めていればそのまま別の変数に代入
  4. 乱数が入った変数を返して終了

ってな感じのようだ。

そこまで難しい話ではないのだが、windowsには/dev/urandomなんてあるワケねーのに
堂々とそんな処理をいれているのが問題。

ググったところ*1、仕方ないので以下のようにコードを修正するのが対応するのが通例らしい。

<?php

//(前略)

if (!defined('Auth_OpenID_RAND_SOURCE')) {
    //***ここから追加分***//
    if (@is_readable('/dev/urandom')) {
        define('Auth_OpenID_RAND_SOURCE', '/dev/urandom');
    } else {
        define('Auth_OpenID_RAND_SOURCE', NULL);
    }
    //***追加分ここまで***

    //既存の定数定義処理をコメントアウト
    //define('Auth_OpenID_RAND_SOURCE', '/dev/urandom');
}

ほー。is_readableなんて関数があったのね。
これでfalseだったらNULLを突っ込んでエラー処理になる部分を回避してしまうと。

一応これで逃げられるけど、すぐ直せるんだからgit hubのマスターも直して欲しいところ。

header関数使うところで「すでに出力してるんじゃボケ」と怒られる

ファイル名:try_auth.php(※サンプルコードの一部なのでライブラリの中身ではない)
L.66

header("Location: ".$redirect_url);

において、

Warning: Cannot modify header information - headers already sent by (output started at D:\php\includes\Auth\Yadis\Manager.php:416)

というエラーが出た。

なんて事はなく、PHPのマニュアルとか教本によく書いてある
「header()を使う前にechoとかputで出力をしてはいけませんよ」
という規約違反みたいなモンである。

しかし、上記エラーメッセージの「すでにここでheaderを吐いてますよ」と示されている該当箇所のYadis\Manager.php:416を元に追ってみても、出力系の処理はしてないように見える。


じゃあなんじゃい???
とググりまくったところ、以下の記事を発見。

PEAR::AUTH

php.iniのoutput_bufferingをOnにすることで回避できるとな?
そもそもoutput_bufferingとは??


私の環境のoutput_bufferingの値は以下のようになっていた。

output_buffering = 4096

php.netには、output_bufferingについて以下のように説明がある。

このディレクティブを 'On' と設定することにより、全てのファイルに 関して出力バッファリングを有効にすることができます。 特定の大きさにバッファの大きさを制限したい場合、このディレクティブの 値として 'On' の代わりに最大バイト数(例:output_buffering=4096) を使用することができます。

つまり、今の設定は最大で4096Byteまでバッファ出来るが、それ超えると
(恐らく)headerが勝手に作成されてしまうということのようだ。

で、以下のように変更

output_buffering = On

Onにすると上限無くbufferを使用可能に出来る。
(余談だが「Onにする時は気をつけて使ってね」とご丁寧にphp.iniのコメント欄に書いてある)

この設定に変えたところこの問題についても回避することができた。
ちなみに、問題なく動作したCentOS環境上のoutput_bufferingは4096のままで正常に動作した。
この辺はもしかしたらphpのOS依存の差異によるものかも知れないが、
何となく釈然としない気はする。
windowsとlinuxの改行コードの解釈の仕方の違いかも知れないな。



2011 8/18追記

サンプルコードの

*1:[http://blog.fkoji.com/2008/10302059.html:title=PHP OpenID Library を使ってみた]
[http://go4it.seesaa.net/article/73726670.html:title=PHP:Warning: Cannot modify header information - headers already sent by ...の解決策]