WWW」カテゴリーアーカイブ

Zend Framework と Smarty

Smarty-2.26 を Zend Framework の View として使っていた (参考: Zend_View_Interface を使用したテンプレート) けれど、Smarty-3.1.3 にしたら、Smarty API の命名規則が変わったり、細部の挙動が異なっているなどして、そのままでは動かなくなってしまった。

修正のためいろいろ調べていたのだけれど、Zend のドキュメントの例って、あくまでも Zend_View_Interface の使い方の例として Smarty が持ち出されているだけで、Smarty の使い方の例というわけでもないのだ。

でも、Zend 公式に載ってるから、みんなそれをベースに修正して使ってるのだろう。

で、読んでるうちに、ベースの方向性の違いが気になってきて、いろいろと修正を試みていたのだが、
まあ、これでいいんじゃね? ってのができたのでメモしておく。

基本的な使い方は従来のものと同じ。大きな違いは、Zend_View_Interface の implement)でなく、Zend_View_Abstract の extends とし、Zend_View_Abstract で利用できそうな機構はなるべくそのまま利用するようにしているところ。

だから、従来比で Zend 多め、Smarty 少なめの依存度になっている。たとえば、Smarty::template_dir は設定せず、Zend 側のほうで用意してくれる script path (絶対パス) をテンプレートのパスとして使う、など。

ちなみに、Zend も Smarty もぜんぜん使い込んでないので、両者のいろいろな機能に対応していない箇所は多いとは思うが、そういうのはその都度修正して動くようにすればよいので。方向性としてはこんなもんかな、と思っている。

<?php
class Zend_View_Smarty extends Zend_View_Abstract
{
/**
* Smarty object
* @var Smarty
*/
protected $_smarty;
/**
* コンストラクタ
*
* @param string $tmplPath
* @param array $extraParams
* @return void
*/
public function __construct($config = array(), $extraParams = array())
{
require_once 'Smarty/Smarty.class.php';
$this->_smarty = new Smarty();
// Smarty::template_dir は設定しない
// Smarty::display() には Zend 側で生成したテンプレートの
// フルパスを渡すように $this->_script() で制御する
// Zend_View_Abstract::__construct() の実行
$config = (null === $config) ? array() : $config;
parent::__construct($config);
foreach ($extraParams as $key => $value) {
switch ($key) {
case 'plugins_dir':
$dirs = $this->_smarty->$key;
array_unshift($dirs, $value);
$this->_smarty->$key = $dirs;
break;
default:
$this->_smarty->$key = $value;
}
}
$this->_smarty->assignByRef('view', $this);
}
/**
* テンプレートエンジンオブジェクトを返します
*
* @return Smarty
*/
public function getEngine()
{
return $this->_smarty;
}
/**
* 変数をテンプレートに代入します
*
* @param string $key 変数名
* @param mixed $val 変数の値
* @return void
*/
public function __set($key, $val)
{
$this->_smarty->assign($key, $val);
}
/**
* empty() や isset() のテストが動作するようにします
*
* @param string $key
* @return boolean
*/
public function __isset($key)
{
return (null !== $this->_smarty->getTemplateVars($key));
}
/**
* オブジェクトのプロパティに対して unset() が動作するようにします
*
* @param string $key
* @return void
*/
public function __unset($key)
{
$this->_smarty->clearAssign($key);
}
/**
* 変数をテンプレートに代入します
*
* 指定したキーを指定した値に設定します。あるいは、
* キー => 値 形式の配列で一括設定します
*
* @see __set()
* @param string|array $spec 使用する代入方式 (キー、あるいは キー => 値 の配列)
* @param mixed $value (オプション) 名前を指定して代入する場合は、ここで値を指定します
* @return void
*/
public function assign($spec, $value = null)
{
if (is_array($spec)) {
$this->_smarty->assign($spec);
return;
}
$this->_smarty->assign($spec, $value);
}
/**
* Return list of all assigned variables
*
* Returns all public properties of the object. Reflection is not used
* here as testing reflection properties for visibility is buggy.
*
* @return array
*/
public function getVars()
{
return $this->_smarty->getTemplateVars();
}
/**
* 代入済みのすべての変数を削除します
*
* Zend_View に {@link assign()} やプロパティ
* ({@link __get()}/{@link __set()}) で代入された変数をすべて削除します
*
* @return void
*/
public function clearVars()
{
$this->_smarty->clearAllAssign();
}
/**
* Finds a view script from the available directories.
*
* @param string $name The base name of the script.
* @return void
*/
protected function _script($name)
{
// $name に対するフルパスを取得
$script_path = parent::_script($name);
// Smarty::compile_dir 内に置かれるコンパイル済みのテンプレートの
// 衝突回避のために Smarty::compile_id にディレクトリ名を設定する
$this->_smarty->compile_id = dirname($script_path);
return $script_path;
}
/**
* Includes the view script in a scope with only public $this variables.
*
* @param string The view script to execute.
*/
protected function _run()
{
// render() から呼び出される
// 出力は Zend 側でバッファされ、フィルタに渡されるので
// Smarty::fetch() ではなく Smarty::display() を使用する
return $this->_smarty->display(func_get_arg(0));
}
}
<?php
require_once "ZendViewSmarty.php";
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initView()
{
// application.ini から設定を読み出す準備
$options = new Zend_Config($this->getOptions());
// view.view.viewSuffix を取得
$viewSuffix = $options->view->viewSuffix;
// view.smarty.* の設定を配列で取得
$extraParams = $options->view->smarty->toArray();
$viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('ViewRenderer');
$savedViewSuffix = $viewRenderer->getViewSuffix();
// 初期設定された Smarty オブジェクトを保持する
// Zend_View_Smarty オブジェクトのインスタンスを作成
// 1番目の引数は Zend_View_Abstract に渡す設定内容を表す
$view = new Zend_View_Smarty(null, $extraParams);
$viewRenderer->setView($view)
->setViewSuffix($viewSuffix);
}
}

application.ini の関連するところ

[production]
(略)
view.viewSuffix = "tpl"
view.smarty.compile_dir = APPLICATION_PATH "/../../smarty/templates_c"
view.smarty.plugins_dir = APPLICATION_PATH "/../../smarty/plugins"
(略)

サーバの引越し(1)

固定IPを維持するのが経済的に負担になってきたので、レンタルサーバに引越しすることにした。

この文章を書いている時点で、引越し作業の大半は終わっているわけだが、今後同様の作業が必要になった場合、もしくは同様の作業を行うとしている人に、何らかの参考になるかもしれないので、これから数回に分けて、引越しにともなう作業とかをメモしておこうと思う。

ちなみに作業順に並べると、おおむね下のようになる。記事をこの順で書くかどうかは未定。

  1. メールサーバ。IMAPサーバの確保。既存メールの移動。
  2. Webサーバの引越し。レンタルサーバへ。
  3. DNSサーバ。プライマリをどこへ置くか。
  4. 自宅サーバ(動的IP割り当てになる予定)へのアクセス確保。Dynamic DNS。

失効リスト (CRL)

失効リスト (CRL) が機能するためには、証明書に CRL Distribution Points が記載されていなければならないらしい。これは、失効リストの配布元を示す情報で、OpenSSL では openssl.cnf に crlDistributionPoints の設定をすることで、発行される証明書に CRL Distribution Points の情報が埋め込まれる。

問題はどこに記述するかだが……。とりあえず、[usr_cert] セクションにでも書いておくことにしよう。

(略)
[ user_cert ]
(略)
crlDistributionPoints = URI:http://www.uconst.org/CA/uconst-ca.crl
(略)

URI: の後に、crl のファイルを配布する URL を記述する。結果は次の通り。

$ ./CA.sh -sign
$ openssl x509 -noout -text -in newcert.pem
Certificate:
(略)
X509v3 extensions:
(略)
X509v3 CRL Distribution Points:
Full Name:
URI:http://www.uconst.org/CA/uconst-ca.crl
(略)

それで、失効リストを公開して、失効した証明書をサーバに設定して Firefox 3.6.3 で接続すると、普段と変りない。 Internet Exploler 6 (IE6) だと、「このサイトのセキュリティ証明書の取り消し情報は、使用できません。続行しますか?」などとでてくる。少なくとも IE6 では途中まではうまく行ってるようだ。

IE6 は、CRL の署名がオレオレ認証局になっているのが気に入らないらしい。オレオレ認証局の証明書をルート証明書のリストに追加してやると、「このサイトのセキュリティ証明書は、取り消されています。このサイトは信頼するべきではありません。」と出てくる。これがまさに期待通りの挙動だ。

とはいえ、オレオレ認証局の証明書をインストールするのは、はばかられるので、IE6 で CRL の動作が確認できたという結論をもって、インストールしたオレオレ認証局証明書をアンインストールした。

Firefox 3.6 では、どうするのだろう? 調べてみると、Online Certificate Status Protocol(OCSP)
というものを使うらしい。openssl ocsp で、OCSP のサーバを動かすことができるようだけれど、ど、実際動かしてみたらエラーが出た。エラーメッセージからはソケットをバインドするのに失敗しているように見えるけど、それがエラーの原因かどうかはよく分からない。

$  openssl ocsp -index index.txt -CA cacert.pem -rsigner cacert.pem -port 8888
Error setting up accept BIO
3148073508:error:02006016:system library:bind:Invalid argument:/u7/NetBSD/src/crypto/dist/openssl/crypto/bio/b_sock.c:759:port='8888'
3148073508:error:20069075:BIO routines:BIO_get_accept_socket:unable to bind socket:/u7/NetBSD/src/crypto/dist/openssl/crypto/bio/b_sock.c:761:
(追記)

どうも NetBSD 5.0.2 付属の OpenSSL (OpenSSL 0.9.9-dev 09 May 2008) のバグらしい。
FreeBSD 8.0 付属の OpenSSL 0.9.8k 25 Mar 2009 だとちゃんと動く。OpenSSL 本家のサイトから
OpenSSL 1.0.0a を持ってきてコンパイルしたら動作した。

(追記2)

動いたと思ったら、IPv6 アドレスの ::1 にしかバインドしてくれない。-url とかでホストを指定しても無視されてるっぽい。

オレオレ証明書による SSL サイトの構築

オレオレ証明書でサイトの SSL を有効にしてみました。
そのうちにどっか格安の証明書を入手しようかと思ってます。
鍵の作成(パスフレーズによる暗号化はしない)

openssl genrsa -out private/key.pem 1024

openssl.conf を作成

[ req ]
default_bits = 1024
default_keyfile = privkey.pem
distinguished_name = req_distinguished_name
dirstring_type = nobmp
[ req_distinguished_name ]
countryName = Country Name (2 letter code)
countryName_default = JP
countryName_min = 2
countryName_max = 2
stateOrProvinceName = State or Province Name (full name)
stateOrProvinceName_default = Tokyo
localityName = City or Locality Name (eg, city)
localityName_default = Shibuya-ku
organizationalUnitName = Organizational Unit Name (eg, section)
commonName = Common Name (eg, YOUR name)
commonName_max = 64
emailAddress = Email Address
emailAddress_max = 40

CSR(サイト証明書を発行するためのリクエスト)の作成

openssl req -config openssl.conf -new -key private/key.pem -out csr.pem

CSRの確認

openssl req -noout -text -in csr.pem

(自分で署名した)サーバー証明書の作成

openssl x509 -days 365 -in csr.pem -out certs/cert.pem -req -signkey private/key.pem

サイト証明書の確認

openssl x509 -noout -text -in certs/cert.pem

apache の ssl.conf で指定したファイルに保存

cp private/key.pem (httpd_conf_dir)/ssl.key/server.key
cp certs/cert.pem (httpd_conf_dir)/ssl.crt/server.crt

rc.conf を修正

-apache=YES
+apache=YES apache_start=”startssl”

apache の再起動

/etc/rc.d/apache restart

Apache の digest 認証

Apache で Digest 認証を行う場合、認証のためのパスワードデータベースは htdigest によって作成します。そのデータベースは username:realm:passwd-digest となっていますが、passwd-digest の部分は username:realm:passwd の MD5 によるハッシュ値です。
ここで疑問が生じました。Digest 認証といえばチャレンジ-レスポンスによる認証で、サーバーは生のパスワードを知らないと認証できなかったはずです。CRAM-MD5 ではたしかそうだったはずです。Apache はパスワードのハッシュ値しか持たずにどうやって認証するんでしょうか?
ということで調べてみました。
HTTP の Digest 認証で通信経路を流れるのは先の passwd-digest を基にチャレンジ-レスポンスによる認証を行います。したがって、passwd-digest 自身がチャレンジ-レスポンスの際の生のパスワードに相当しますが、通信経路を流れるのは passwd-digest とサーバのチャレンジから生成したハッシュ値なので、通信の傍受に対して Basic 認証より安全であるというのは確かなようです。
しかし、サーバに保存されている passwd-digest の値が漏洩した場合は Digest 認証は生のパスワードが漏洩した場合と同様に、成りすましに対して無防備になります。これはチャレンジ-レスポンスによる認証方式一般に言えることなので仕方がありませんが、これでは生のパスワードを直接保存するのではなく、ハッシュ値を保存してそれを使用するようにしている理由がわかりません。
その理由は RFC2617 にありました。

それは、あるダイジェスト認証パスワードファイルが危険に晒された場合、同じユーザ名やパスワードを持つその他のものを危険に晒す事はない (しかし総当たり攻撃には晒される) という事を意味する。

パスワードファイルが漏洩した場合に総当り攻撃に晒されるというのは Basic 認証のパスワードファイルでも同じです。パスワードファイルが漏洩したときの HTTP の認証に対する成りすましの危険度は Digest 認証の方が高いようですが、総合的に見て Basic 認証より Digest 認証の方が安全だということなのでしょう。
参考 URL