2006年07月21日

ディレクティブの処理と設定値の利用 (apache module 開発事初め その3)

はてなブックマークに登録

今回は前回の記事で予告した通り,Apache の(いくつかのタイプの)モジュールが動作するべきか否かをどうやって判断するか,というお話です.タイトルは「ディレクティブの処理」となっていますが,モジュールがディレクティブを処理することと今回のテーマは密接に結びついています.

モジュールが,というよりも厳密には各種 hook が,呼び出された際に処理をするべきか否かの判断は,大体の場合そのモジュール用の設定ディレクティブが設定ファイル(httpd.conf)にあるか否かで行います.handler の場合は前々回の記事でも出てきたように,汎用の handler 指定用のディレクティブがあります.(hook ではないですが) filter も,Input/Output filter のための設定用ディレクティブがそれぞれあります.もちろん,必ずこれらのディレクティブを使わなければならない,という訳ではありません.handler や filter の指定はそれを実装しているモジュール以外からでも指定できます.
今回のモジュールが登録する hook はアクセス制御用のものですので,汎用の設定ディレクティブはありません.ですので,専用のディレクティブを登録して,設定ファイル中にそのディレクティブがあれば処理をする形にします.このディレクティブでは,先のコードの RATE マクロで規定していた,アクセスを許可する確率を設定することにします.
前回のコードにディレクティブ処理関連のコードを追加したものを次に示します.またファイルとしてこちらに置いていますので,試される方はご自由にお使い下さい.

  1  #include "httpd.h"
  2  #include "http_config.h"
  3  #include "http_protocol.h"
  4  #include "ap_config.h"
  5
  6  #include "http_log.h"
  7  #include "apr_time.h"
  8  #include <stdlib.h>
  9
 10  module AP_MODULE_DECLARE_DATA denyrandom_module;
 11
 12  static int denyrandom(request_rec *r){
 13      int msec = apr_time_msec(r->request_time);
 14      int *rate = (int *)ap_get_module_config(r->per_dir_config, &denyrandom_module);
 15
 16      if(*rate < 1)
 17          return DECLINED;
 18
 19      if(msec % *rate){
 20          ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "deny: msec = %d", msec);
 21          return HTTP_FORBIDDEN;
 22      }
 23
 24      ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "allow: msec = %d", msec);
 25      return OK;
 26  }
 27
 28  static void *create_dir_config(apr_pool_t *p, char *dir){
 29      int *rate = (int *)apr_palloc(p, sizeof(int));
 30      *rate = 0;
 31      return (void *)rate;
 32  }
 33
 34  static const char *set_rate(cmd_parms *cmd, void *_rate, const char *s_rate){
 35      int *rate = (int *)_rate;
 36
 37      *rate = atoi(s_rate);
 38
 39      if(*rate < 1)
 40          return "specify natural number for rate";
 41
 42      return NULL;
 43  }
 44
 45  static const command_rec cmds[] = {
 46      AP_INIT_TAKE1("SetAllowrate",
 47                    set_rate,
 48                    NULL,
 49                    OR_LIMIT,
 50                    "specify deny access rate"),
 51      {NULL}
 52  };
 53
 54  static void denyrandom_register_hooks(apr_pool_t *p)
 55  {
 56      ap_hook_access_checker(denyrandom, NULL, NULL, APR_HOOK_MIDDLE);
 57  }
 58
 59  /* Dispatch list for API hooks */
 60  module AP_MODULE_DECLARE_DATA denyrandom_module = {
 61      STANDARD20_MODULE_STUFF,
 62      create_dir_config,         /* create per-dir    config structures */
 63      NULL,                      /* merge  per-dir    config structures */
 64      NULL,                      /* create per-server config structures */
 65      NULL,                      /* merge  per-server config structures */
 66      cmds,                      /* table of config file commands       */
 67      denyrandom_register_hooks  /* register hooks                      */
 68  }; 
変更点は,ディレクティブの処理用の関数(set_rate())と設定データの保持用の領域を確保するための関数(create_dir_config()),独自のディレクティブを Apache に登録するための配列(cmds[])の定義が追加になったのと,hook 関数内でディレクティブによって設定された値を参照するコードが増えた点です.それぞれ説明していきます.

まず cmds[](45行目) に関して.これはこのモジュールが処理するディレクティブを Apache に登録するための配列です.AP_INIT_XXX() というマクロを使ってこのモジュール用のディレクティブ名とそれを処理する関数などを登録します.ご覧のように NULL ターミネートします.XXX の部分はディレクティブが取る引数のパターンによって何種類か定義されています.AP_INIT_TAKE1() の場合,ディレクティブは引数を一つだけ取ることができます.他の AP_INIT_XXX() に関しては長くなるので別の記事でまとめることにします.
AP_INIT_TAKE1() の引数の意味は次のようになっています.

 AP_INIT_TAKE1(directive, func, info, where, help)
directive
設定ファイル中で用いるディレクティブの名前.char *型.大文字小文字は無視されます.
func
ディレクティブ処理用の関数を指定すします.関数の型は,AP_INIT_XXX の種類によって変わります.AP_INIT_TAKE1()の場合については set_rate() の説明の中で述べます.
info
func() を呼び出す時に渡す情報.void *型.特に渡すものがなければ NULL を指定します.
where
このディレクティブが現れてよい場所の指定.int型.指定できる値の種類は次に述べます.
help
このディレクティブに関する説明.char *型.使用時にフォーマットが間違っていた場合に表示されます.
where で指定できる値は次のようになっています.
OR_LIMIT
httpd.conf内の <Directory><Location> の中か, AllowOverride Limit が指定されていれば .htaccess の中.このタイプのディレクティブの例として,AllowDeny があります.
OR_OPTIONS
httpd.conf の中か,AllowOverride Options が指定されていれば .htaccess の中.このタイプのディレクティブの例として,XBitHackCheckSpelling があります.
OR_FILEINFO
httpd.conf の中か,AllowOverride FileInfo が指定されていれば .htaccess の中.このタイプのディレクティブの例として,SetEnvIfHeader があります.
OR_AUTHCFG
httpd.conf 内の <Directory><Location> の中か,AllowOverride AuthConfig が指定されていれば .htaccess の中.このタイプのディレクティブの例として,AuthNameAuthDigestFile があります.
OR_INDEXES
httpd.conf の中か,AllowOverride Indexes が指定されていれば .htaccess の中.
OR_ALL
OR_XXX の条件の内いずれかを満たしている箇所.
ACCESS_CONF
httpd.conf 内の <Directory><Location> の中.
RSRC_CONF
httpd.conf 内の <Directory> 及び <Location> の外.
EXEC_ON_READ
httpd.conf の内容を実質的に変更するようなディレクティブの場合指定します.このタイプのディレクティブの例として,IFModuleIfVersion があります.
今回のモジュールは AllowDeny と同じ OR_LIMIT を使っています.この cmds[] 配列自体は,66行目denyrandom_module を通して Apache に登録されます.

次に,ディレクティブで設定する値を保持するための領域を確保するための create_dir_config() 関数を見てみます.名前からも分かるとおり,ディレクトリ毎の設定保持用の領域を確保するためのものです.この関数自体は,62行目denyrandom_module を通して Apache に登録されます.引数は apr_pool_t 型のポインタと char 型のポインタをもらいます.前者に関しては後述します.後者はどのディレクトリ用の設定領域を確保するかを示す文字列が渡されます.具体的には <Directory><Location> ディレクティブの引数の文字列がそのまま渡されます.さてこの関数で確保する設定保持用の領域ですが,大概のモジュールでは複数の値を保持するために設定データの保持用の構造体を定義して,1つまたは複数のディレクティブで設定された値をその構造体に保存します.けれども今回のモジュールでは整数値1つを保持しておけばいいので,構造体は定義せずに int 型の変数一つで済ませています.設定保持用のデータ領域はヒープから確保しますが,malloc を使うのではなく apr_palloc() という関数を使っています().
apr_palloc() の第一引数は apr_pool_t型で,create_dir_config() の第一引数として渡されたものを使います.apr_pool_t 型の変数はモジュール内の関数が Apache から呼び出される際に直接的或いは間接的に渡されます.それを apr_palloc() に渡して確保したメモリは,それぞれの場面に適したライフサイクルを持ちます.ですので,確保したメモリを明示的に解放する必要はありません.
データ領域が確保できればそれを 0 に初期化して,そのデータ領域のポインタを呼び出し元に返します.余談ですが apr_palloc() には apr_pcalloc() という亜種があって,これは確保した領域を 0 で初期化してくれます.ですので,実はこの関数の処理は

 return apr_pcalloc(p, sizeof(int));
だけで済んだりします.

既に何度か apr_xxx という名前の関数や型が出てきましたが,これらは Apache 2.0 から導入された Apache Portable Runtime ライブラリに属するものです.Apache のソースでは,srclib ディレクトリ以下にあります.独立して配布もされています.これは Apache 1.x 系のソースの中から OS の違いを吸収するためのライブラリ層をまとめて独立化したものです.どのような機能を提供しているのか興味がある方はサイトにあるドキュメントをご覧下さい.

create_dir_config() 関数で確保した領域に具体的な設定をセットするのはディレクティブ処理関数である set_rate() の役割です.この関数の型は次のようになっています.これは AP_INIT_TAKE1() マクロで登録する関数が備えているべき型になります.

 static const char *set_rate(cmd_parms *cmd, void *_rate, const char *s_rate);
cmd は,この関数が処理する際に必要となる様々な変数が詰まった cmd_parms 構造体の変数です.cmd_parms 構造体のメンバには,前述の apr_pool_t 型のポインタ(cmd->pool)や,処理するディレクティブが記述されたファイルの情報を表すメンバがあります.AP_INIT_TAKE1() マクロでディレクティブを登録するときにセットした info 引数は,この構造体の info メンバ変数(cmd->info) に入っています.
_rate には create_dir_config() で確保したデータ領域のポインタが渡されます.void * 型のままでは扱いにくいので,int * 型の変数 rate にキャストしています(35行目).
s_rateSetAllowrate ディレクティブへの引数が,文字列の形で渡されます.これを整数値に変換して rate 変数に入れます(37行目).変換結果が自然数でなければ(39行目)エラーにします.この関数の返値としてエラーメッセージの文字列を返す(40行目)と Apache は設定エラーと判断して ErrorLog にそのメッセージを出力します.問題がなければ NULL を返します(42行目).

以上がディレクティブの処理のためのコードになります.ディレクティブで設定した値を使用するのは,hook や filter になります.このモジュールでは denyrandom() 関数です.前回のコードに対して新たに追加したのは 14 〜 17行目の 4行です.
まず設定データを ap_get_module_config() 関数を使って取り出します(14行目).この関数への第一引数は設定を取り出す先を指定します.create_dir_config() 関数で作成した設定データを取得するには,request_rec 構造体の per_dir_config メンバを指定します.第二引数には取り出すためのマーカとして denyrandom_module 変数のポインタを渡します.(この変数の定義はこの関数の後ろでしているので,コンパイル時にワーニングが出ないように,宣言をこの関数よりも前でしています(10行目).)
さて,16行目が今回の本題である "このモジュール(hook)が動作すべきか否か" の判断文になります.set_rate() 関数でディレクティブで設定された値を保存するときに,値が自然数でなければ=1未満であればエラーにしました.ですので *rate の値は set_rate() で処理されていれば必ず 1 以上になっているはずです.逆に言えば *rate の値が 1 未満であるということは set_rate() で処理されていない = SetAllowrate ディレクティブが設定されなかった,ということです.
もう少し詳しく説明すると,denyrandom() 関数は hook として Apache からリクエストがある度に呼び出されます.Apache が hook を呼び出すにあたって,そのリクエストに対して httpd.conf で記述された設定がかき集められます.例えば,SetAllowrate ディレクティブが次のような形で設定されていたとします.

 <Location "/hoge/">
   SetAllowrate 3
 </Location>
この場合,http://localhost/hoge/ へのアクセスは "SetAllowrate 3" で設定された値が 14行目で取り出されますが,http://localhost/fuga/ へのアクセスは SetAllowrate ディレクティブの設定が特に無いため,デフォルトの設定値が 14行目で取り出されます.このデフォルトの設定値というのも,ちゃんと create_dir_config() 関数を呼び出して作成されたものです.しかし set_rate() 関数は(SetAllowrate ディレクティブは設定されてないので)呼び出されていません.create_dir_config() では初期値として 0 を設定しています.set_rate() では値として必ず 1 以上を設定します.ですので,(*rate < 1) という条件文で,set_rate() が呼び出されたか否かが判断できます.逆に言うと,ディレクティブ処理関数では必ず初期値以外の値を設定する必要があります.設定保持変数が構造体の場合でしたら,メンバ変数のいずれかに必ず初期値以外の値を設定するようにします.そうしないとディレクティブが現れたか否か = hook 処理を行って良いか否かが判断付かなくなります.
hook 処理するべきでない場合,返す値は OK でも HTTP_FORBIDDEN でもなく DECLINED という値になります.これは,この hook はこのリクエストに対しては何も処理しない,と Apache に告げることを意味します.

参考文献

klab_gijutsu2 at 21:22│Comments(7)TrackBack(0)apache | 開発

トラックバックURL

この記事へのコメント

1. Posted by NCSA   2006年07月22日 03:09
DECLINEDの場合は次のhookは実行されるのでしょうか?
同じように、OKやHTTP_FORBIDDENの場合はどうなんでしょうか・・・
2. Posted by かつみ   2006年07月24日 11:38
NCSA さん,コメントありがとうございます.

hook が DECLINED か OK を返した場合は,次の hook が実行されます.HTTP_FORBIDDEN 等のエラーを示す値を返した場合は,次の hook は実行されずにエラー処理が走ります.
ちなみに,エラーを示さずに次の(同種の) hook を実行したくない場合は,DONE という値を返します.
3. Posted by hiroyuki oyama   2006年07月29日 04:54
厳密にはAP_IMPLEMENT_HOOK_RUN_FIRSTなhookとAP_IMPLEMENT_HOOK_RUN_ALLなhookで動作が違いますよね。

AP_IMPLEMENT_HOOK_RUN_ALLなhook(例えばfixups, access_checker等)ではreturn OK/DECLINEDの何れを返しても対象hookに登録されている全ての関数が実行されますが、AP_IMPLEMENT_HOOK_RUN_FIRSTなhook(例えばtranslate_name,auth_checker等)ではreturn OKを返した時点で対象hookの処理は終了して、次のフェーズのhookの処理にうつります。

って、このへん簡潔に説明するのって難しいですよね。私はこの辺は表にして説明するようにしてます。
6. Posted by かつみ   2006年07月31日 11:19
hiroyuki oyama さん,コメントありがとうございます m(_ _)m
小山さんからコメントいただけるとは光栄です(^^

御指摘頂いた点に関しまして,自分の中でもちゃんと区別できてなかったので,改めてまとめて記事にさせていただきます.
7. Posted by かつみ   2006年08月07日 20:06
遅くなりましたが,hook に関してhttp://dsas.blog.klab.org/archives/50626863.html の記事にまとめてみました.
9. Posted by perisy   2007年08月23日 10:32
参考にさせていただいております。

サンプルソースのファイル版は、
ディレクティブ名がSetDenyrateで登録されている為、
http.confの設定も変える必要があるようなので報告させていただきます。
10. Posted by かつみ   2007年08月23日 12:18
ご指摘ありがとうございます>perisyさん.
他にも細々と記事中のソースと,ファイルの内容が異なっている点がありましたので,改めて記事中のソースのもので置き換えておきました.(間違った版を上げてしまってたようです… お恥ずかしい.)

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔   
 
 
 
Blog内検索
Archives
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
最新コメント