Apache2.2でリモートアドレスベースでの制限 "または" SSLクライアント証明書による認証を行う

現行でリモートアドレスベースでのアクセス制限を行っているサーバがあるんだけど、新たにSSLクライアント証明書をインストールした端末にもアクセスを許可したいという話があった。ちょっとトリッキーだけどなんかうまくいったので記録。むしろ情報求む。

証明書の発行とApacheの設定

この辺参照。

以下、サーバのSSLCACertificateFileなどの設定と、ブラウザへのクライアント証明書のインストールが終わった状態として話を進める。*1

クライアントに証明書を要求する

まず、アクセス時にクライアントに証明書を要求するわけだけど、リモートアドレスベースで許可されている場合には証明書は不要なので、ここではSSLVerifyClient optionalを指定する。

##
## SSL Virtual Host Context
##
<VirtualHost _default_:443>
...
  <Directory "/var/www/html_ssl">
    Options -Indexes FollowSymLinks
    AllowOverride All
  </Directory>

  SSLVerifyClient optional
  SSLVerifyDepth 1
</VirtualHost>

今回はSSLVerifyClient optionalをバーチャルホストまたはサーバレベルで設定する必要がある。DirectoryやLocationの中ではダメ。

Allow設定に追加する

SSL認証が成功した場合、SSL_CLIENT_VERIFYというヘッダに"SUCCESS"という文字列が設定されるので、それで条件判定した結果をAllowに追加する。

order deny,allow
Deny from all

# IPアドレスベースでのAllowが並んでいる
Allow from XXX.XXX.XX.XXX
...

# SSL_CLIENT_VERIFYがSUCCESSの場合に認証済みとする ※これは動かない!!
SetEnvIf SSL_CLIENT_VERIFY "^SUCCESS$" clcertified
Allow from env=clcertified

と思いきやこれは動かない。デフォルトでSetEnvIfで使えるヘッダは標準的なHTTPヘッダに限られていて、SSL_CLIENT_VERIFYヘッダを参照することはできないようだ。

てことは他の方法で環境変数セットするしかない。が、いろいろやってみるがうまくいかない。

# RewriteRuleで環境変数にセットしてやる ※これは動かない!!
RewriteEngine on
RewriteCond     %{SSL:SSL_CLIENT_VERIFY} "^SUCCESS$"
RewriteRule .* - [E=clcertified:SUCCESS,L]

どうやら、Allow from env=で使えるのはSetEnvIfで設定した環境変数だけらしい(cf: 「Allow | Turboflash's Blog」。) が、RewriteRuleはSetEnvIfより後に評価されるので、RewriteRuleの処理結果をSetEnvIfに伝えることもできない。

困っていたら、RequestHeaderディレクティブにearlyというキーワードを発見した。これを使ってみる。

##
## SSL Virtual Host Context
##
<VirtualHost _default_:443>
...
  SSLVerifyClient optional
  SSLVerifyDepth 1
  RequestHeader set SSL_CLIENT_VERIFY_early "%{SSL_CLIENT_VERIFY}s" early
</VirtualHost>

早期処理の設定はメインサーバかバーチャルホストコンテキストに書かないとダメらしい。

なんと、RequestHeader setでセットしたヘッダはSetEnvIfで使える!これで認証済みかどうかの判定をAllowディレクティブに伝えられる。

# IPアドレスベースでのAllowが並んでいる
Allow from XXX.XXX.XX.XXX
...
# SSL_CLIENT_VERIFY_earlyがSUCCESSの場合に認証済みとする
SetEnvIf SSL_CLIENT_VERIFY_early "^SUCCESS$" clcertified
Allow from env=clcertified

これでリモートアドレスベースで登録されているクライアントと、クライアント証明書がインストールされているクライアントからのアクセスを許可できた(一応。)


穴が無いかとかもう少し調べてみる。

*1:ちなみにサーバのSSL証明書のCA局とクライアント証明書のCA局は別でも大丈夫みたい。つまりサーバはVerisignとかで署名貰って、クライアント証明書はオレオレCA局でも認証はできる。

文字列テンプレートを型安全にする

パーサとテンプレートエンジンって丁度動きが逆だよなあと前から思ってたんだけど、てことはパーサコンビネータっぽい感じで文字列テンプレート書けないかと思った。
一応見た感じそれっぽく出来たけど、なんか格好悪い実装になってしまった。真性のScalaユーザならもっと格好よく書けると思う。

使い方

import Formatter._
import Formatters._

// 挨拶
val greeting = "Hello, " #: stringFormat #: "!"

println(greeting("terazzo"))        // "Hello, terazzo!"と出力される
println(greeting(100))              // これはコンパイルエラー


// 現在の為替
val currencyFormat = "1.0 USD = " #: decimalFormat("#,###.00") #: " JPY [" #: dateFormat("yyyy-MM-dd HH:mm")  #: "]"

println(currencyFormat(80.9558)(new java.util.Date))    // "1.0 USD = 80.96 JPY [2012-04-17 00:55]"のように出力
println(Function.uncurried(currencyFormat.toFunc)(80.9558, new java.util.Date)) // カレーがお嫌いなら

// 下の二つのように型や引数の数が合わない場合はコンパイルエラー
println(currencyFormat("80.9558")(new java.util.Date))
println(Function.uncurried(currencyFormat.toFunc)(80.9558, new java.util.Date, "hoge")) 

文字列と(型ごとに定義された)フォーマッタを「#:」で繋いで行くと関数が出来るので、中に埋める値を引数にして呼び出すと合成した文字列を出力する。

ソースコード

まず型ごとのフォーマッタを定義。まあ今回は使う分だけ。

trait Formatter[A] {
 def format(a:A):String
}
object Formatter {
  def stringFormat = new Formatter[String] {
    def format(s:String):String = s
  }
  def dateFormat(fmt:String) = new Formatter[java.util.Date] {
    def format(d:java.util.Date):String = new java.text.SimpleDateFormat(fmt).format(d)
  }
  def decimalFormat(fmt:String) = new Formatter[Double] {
    def format(d:Double):String = new java.text.DecimalFormat(fmt).format(d)
  }
}

Formatterを合成できるように型のリストを作る

sealed trait FormattersBase {
  def prefixed(s:String):this.type
  type ToFunc
  def toFunc:ToFunc
}
final case class Formatters[H, T <: FormattersBase](head : Formatter[H], tail : T, acc:String = "") extends FormattersBase {
  def #:(s : String) = prefixed(s)
  def #:[F](f : Formatter[F]) = Formatters(f, this)
  def prefixed(s:String) = Formatters(head, tail, s + acc).asInstanceOf[this.type]

  type ToFunc = H => T#ToFunc
  def toFunc = (h:H) => tail.prefixed(acc + head.format(h)).toFunc

  def apply(h:H) = toFunc(h)
}

final case class FormattersNil(acc:String = "") extends FormattersBase {
  def #:(s : String) = prefixed(s)
  def #:[F](f : Formatter[F]) = Formatters(f, this)
  def prefixed(s:String) = FormattersNil(s + acc).asInstanceOf[this.type]

  type ToFunc = String
  def toFunc = acc
}

object Formatters {
  implicit def asFormatters[T](f:Formatter[T]) = f #: FormattersNil("")
  implicit def asFormattersNil(s:String) = FormattersNil(s)
}

「#:」のように「:」で終わるメソッドの場合右結合にしてくれるので、一番最後の要素から順に結合されて行く。
例えば「"Hello, " #: stringFormat #: "!"」の場合、("!".#:(stringFormat)).#:("Hello, ")のような順番になる。
最後が文字列の場合asFormatterNil()で、最後がFormatterの場合はasFormatters()でFormattersに変換される。
あとはFormatterが来るたびにリンクを繋いで行く。文字列の場合は引数が要らないのでaccに足して行く。
適用時は、toFuncで再帰的に関数を合成して行く。(toFuncの型がToFuncになるようにしている。)
この辺りは「http://dl.dropbox.com/u/261418/Apocalisp/ja/type_level_programming_in_scala/index.html」を見よう見まねで書いたけど正直良く分かってない。HListを使って書くべきだったかも。
asInstanceOfを使っているのもダサいが上手く消せなかった。同じ型じゃないの?あと#:の実装も重複してるように見えるけど親traitに持って行こうとしても上手くいかない。

感想

最初の予定ではもうちょっと格好よくApplicativeスタイルのパーサコンビネータに対応するような形で実装できて、あー丁度逆になってるよねってところから数学的な理解を深めて行くような感じにする予定だったけど、全然中途半端になってしまった。圏力が足りない……

もうちょっと勉強してから再チャレンジする。

Blocksをメソッドとして追加する

imp_implementationWithBlock()という、BlockからIMPを作り出す関数がiOS 4.3 or Lionから使えるようになったみたいなので、これを使って実行時にインスタンスメソッドを追加する実験

例えば、モーダルウィンドウをクローズした時に自動的にstopModalするコードは下のようになる。簡単。*1

/* CLANG_ENABLE_OBJC_ARC = YES */
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import <objc/runtime.h>
...
    NSApplication *application = [NSApplication sharedApplication];
    
    BOOL (^windowShouldClose)(id, id) = ^(id self, id sender) {
        NSLog(@"windowShouldClose:%@", sender);
        if ([application modalWindow] == self) {
            [application stopModal];
        }
        return YES;
    };
    // Block to IMP
    IMP imp_windowShouldClose = imp_implementationWithBlock((__bridge void*) windowShouldClose);
    // add IMP to class as Method
    class_addMethod([NSWindow class], @selector(windowShouldClose:), imp_windowShouldClose, "B@:@");
...

Blockの引数は、第一引数がid型(selfが渡される)で、第二引数以降がメソッドの引数の列になる。戻り値の型はメソッドの戻り値の型を指定する。この場合、windowShouldClose:はid型の引数を取り、戻り値の型はBOOLなので、「BOOL (^)(id, id)」になるようにBlockを定義する。
class_addMethod()の最後の引数は、メソッドのシグネチャ文字列(NSMethodSignatureの+signatureWithObjCTypes:の引数と同じ)で、「戻り値がBOOL(="c")で引数がid(="@")」なので、"c@:@"となる*2。以下も参照。

既存のメソッドの実装を置き換えることも出来る。置き換え元の実装を呼び出しやすくするために「Blockを作るBlock」を渡せるようにしてみる。

void
replaceMethodWithBlock(Class cls, SEL sel, id (^blockGen)(SEL, IMP))
{
    IMP originalImplementation = NULL;
    Method originalMethod = class_getInstanceMethod(cls, sel);
    if (!originalMethod) {
        [NSException raise:NSInvalidArgumentException format:@"method not found:%s", sel];
    }
    originalImplementation = method_getImplementation(originalMethod);
    id block = blockGen(sel, originalImplementation);
    if (!block) {
        return;
    }
    IMP imp = imp_implementationWithBlock((__bridge void*) block);
    method_setImplementation(originalMethod, imp);
}

これを使って、例えばNSWindowのmouseDown:の前後にログを吐くようにする。

    replaceMethodWithBlock([NSWindow class], @selector(mouseDown:), 
        // block to generate block for method.
        (id) ^(SEL selector, IMP originalImplimentation) {
            // block for method.
            void (^block)(id, NSEvent*) = ^(id self, NSEvent *theEvent) {
                NSLog(@"[BEFORE] mouseDown:%@", theEvent);
                originalImplimentation(self, selector, theEvent);
                NSLog(@"[AFTER] mouseDown:");
            };
            return block;
       });

ちょっとAOPっぽい感じ。

なんかちょっとメモリ管理回りが不安だけど。[block copy]とかやんなくて良くなったのかな?

おまけ: 関数の型について

IMPの型はヘッダによるとこう

typedef id 			(*IMP)(id, SEL, ...); 

一方Blockの関数部分はこんな感じ(cf: http://d.hatena.ne.jp/terazzo/20101103/1288807194)

id (struct __block_literal_1 *, ...)

引数の数だけで言えばBlock側はSELが足りない。メソッド用Blockの第二引数がSELではないのは、この辺と関係ありそう。

というようなこと考えてたら、解説があったのでリンクしておく。中身まだあんまり読めてないけど。

*1:但し全NSWindowに適用されるので危険だけど

*2:当初BOOL(="B")としていましたがコメントでツッコミがあったので訂正しました

『一般意思2.0』読んだ

話題になってたので読んだよ。この本(や「一般意思2.0」という言葉)についてはいろいろな人が言及していて、とりもなおさずそれはこの本が良い本だっていう証拠だと思う。

それを聴いたり読んだりした人が、自分も何かを語らずには居られなくなるような言葉に価値がある。

個人的に、人間の存在意義は発展的であることにあると思っていて、閉塞よりも開放が、硬直的であることよりも柔軟であることが、不自由よりも自由であることが、好ましいと思っている。*1 そういう意味で、この本の内容も受け取られ方も、とても良い感じだと思う。

ちょっとプログラマとして補足できそうなところがあったので要点を抜粋して感想を書いてみた。自分はただの平凡なプログラマであって現代思想とか社会学の専門家とかじゃないので、まあ参考程度に(それこそ「データベース」上の1データとして)読んでくれたらいいと思う。

簡単なまとめ

箇条書きで書いてみるよ。内容を理解するには本を読んで下さい。読まずに済ませるためのまとめではない。

  • 「一般意志」は、ルソーの『社会契約論』に出てくる概念
    • 「一般意志」とは、個人の意志(特殊意志)の集合体である共同体の意志である
    • 「全体意志」(特殊意志の単なる総和)と「一般意志」(特殊意志の差異の和)は違う
    • 「一般意志」は常に正しく、常に公共の利益に向かう。政府は一般意志の執行のための機関。
    • 「一般意志」が現れるには、個人の熟慮があればよく、コミュニケーションは無い方が良い(部分的結社の禁止)
    • 「一般意志」は人間の秩序ではなく事物の秩序に従う
    • 「一般意志」を正しく掴むには、超人間的な存在(天才)が必要
  • 「一般意思2.0」は、総記録社会において、個人の欲望を記録・蓄積したデータベース
    • 総記録社会における記録は、無意識的に自動的におこなわれる
    • データベースを数学的に処理し可視化する(集合知)ことで、一般意思2.0にアクセスできる
  • 現代社会はあまりにも複雑化しているため、熟議によって公共性を形作るやり方での政治は限界が来ている
  • 新しい政府(政府2.0)は、熟議とデータベースが互いに補いあうことで成立する
    • 熟議 - 理性 - 意識的 - 人間的
    • データベース - 情念 - 無意識的 - 動物的
    • データベース(大衆の欲望)への単なる従属(ポピュリズム)ではない。
      • 熟議が無意識に対立するが、無意識が熟議に対する制約条件にもなっている
      • 熟議を無意識に晒し、また無意識を熟議に反映するフィードバックを作る
    • 集合知によって得られるのは無意識であり、条文や政策を作るのには向いていない
      • 専門家が熟議により立てた政策に、無意識は専ら「ツッコミ」をおこなう

著者ご本人の解説

Twitterで著者が解説していたので引用しておく。議論の文脈などは「臨床とドゥルーズ&ガタリ / ラカン的欲望と『一般意志2.0』 - 精神科医@schizoophrenie と東浩紀 - Togetter」を参照。

エンジニアがこの本を読むと「データベース」とか「集合知」という字面に引っ張られて、ITの素人がとんちんかんなことを言っているように思うかもしれない。この補足が無ければ自分もそう思っていたかもしれない。

「転移関係があれば欲望を発見できる」と「アルゴリズムは必要ない」は解説が要ると思う。

会話ソフト「ELIZA」

ELIZAというソフトウェアについて、プログラマなら名前ぐらいは聞いた事があると思う。Wikipediaにも項目がある

ELIZAについて紹介しているページがあったので引用する。

人工知能研究の黎明期だった1960年代中期にジョセフ・ワイゼンバウムによって作られたプログラムです。ELIZAは、擬似的に人間との対話を行うことができました。ロジャー派の精神分析法を用いる精神医として振る舞い、その結果多くの人間がELIZAが実際の人間であると信じて疑わず、さらには実際にカウンセリングによって癒されたと感じる人間まで出現したのです。

ホームページ移転のお知らせ - Yahoo!ジオシティーズ

ELIZAは精神科医によるカウンセリングをエミュレートするプログラムではなく、人間が入力した単語に反応するだけの、いわゆる「人工無脳」の類いである。にも関わらず、「癒されたと感じる人間」が出てくるのはなぜか。会話をおこなっていた人間がELIZAを実際の精神科医と信じていたから、転移が生じていたからと考えられる。逆に転移さえあれば複雑なアルゴリズムがなくても、実際に人間を癒したりするようなフィードバックループを駆動させる事が出来る。*2

一般意思2.0というのは、高度なテクノロジーで「大衆の本当に望んでいること」を抽出する人工知能的な物と考えるよりは、大掛かりなELIZAのような物と思った方がいいんじゃないだろうか。もしアルゴリズムが求められるとしても、正確な統計処理のようなものよりも、フィードバックループをうまく駆動させ続けるような仕組み(入力されたコメントがリアルタイムに右から左に流れるようなキャッチーなUIとか)が必要ではないかな。

それよりも、むしろどうやって転移をおこさせるのかが良く分からない。その場合の「知を想定された主体」は一体どこにあるのか。データベースシステム自体なのか、それを利用する一人の政治家なのか。

一般意志2.0としての「やりましょう」

一時期よく見られたTwitterの名物的なイベントとして、一般のユーザがソフトバンク社に対する要望をTwitter上のメッセージとしてソフトバンク社長の孫さんに送ると、それが孫社長の目にとまり、「やりましょう」という返事とともに要望が実現されるというのがあった。

この「やりましょう」はとても一般意志2.0的だと思う。

直訴メッセージを孫社長が一つずつ吟味し、その場で意思決定をしているとは思えない。おそらく、意思決定のプロセスは社内の「熟議」によって行われているのではないかと思う(あくまで想像です。) ただ、直訴メッセージが何かユーザの雰囲気のようなものを(つまり「大衆の欲望」を)伝えているというのは十分考えられる。これはまさに「熟議と欲望が互いに補いあうことで成立する」と言えないだろうか。

ここで一つのポイントだと思うのは、孫社長が個々のメッセージに対してリプライ(非公式RT)を返しているということだ。「皆様のご意見で○○が多かったので」ではない、つまり集計結果に対してではない。あくまで個々のメッセージに対する返答であり、それによって直訴をするユーザと孫社長の間に転移関係が生じるのではないだろうかと思う。「誰が知っている」かといえば孫社長であり、「誰の欲望について知っている」かといえばメッセージを送った「わたし」であるという関係である。「どこかの誰か」ではなく、「わたしたち」でもない。

統計処理によって抽出されたものは「欲望」足り得るのかというのが疑問である。統計処理によって「差異の和」というより均された「単なる和」になってしまわないかというのもある。

ビッグデータと一般意思2.0

総記録社会における個人の無意識的な行動の記録、といえば、最近だと「ビッグデータ」が連想される事が多いだろう。バズワード化して乱用されているけど、概ね「大量」で「非定形」で「リアルタイム性の高い」データのことをビッグデータと呼んでいる。

ビッグデータは、いわゆる「データベース」という言葉で連想されるRDBMSではなく、Hadoopなどの分散ストレージに格納される事が多い。RDBMSとの大きな違いは、RDBMSはデータ構造(スキーマ)が決まっている為、あらかじめ決められたフォーマットに整形して格納するのに対して*3、とりあえずデータを非定形のまま格納し、後で並列に実行されるジョブエンジンなどによって情報を抽出するという、処理のタイミングの違いがある。

差異の和としての一般意思にふさわしいのは、整形されたリレーショナルデータベースのレコードではなく、差異を差異のまま格納したビッグデータではないかと思う。おそらくこの本で「データベース」と表現されているものもそれに近いのだろう。

ビッグデータの専門家のtweetを引用する。

つまり、ビッグデータというのはHadoopに突っ込めば何か自動的に統計データが出てくるのではなく、仮説を当てはめることで初めて意味のあるデータを得ることが出来る。誰かがデータを「分析」し「診断」するフェーズが存在する。

ビッグデータを「診断」する分析家が居て、分析結果を投げ返す事で、診断と症状のキャッチボールがおこなわれ、初めて欲望と呼ばれるような物が出てくるのではないか。

当然だけど、この場合フィードバックループが回っている事が重要で診断結果の妥当性とかはどうでもいいです。正確かというより、説得力があり、かつ相手をびっくりさせるような結果が大事じゃないかな。(というかループがある事によって事後的に正しい結果とされるに過ぎない。)

まとめ

エンジニアがこの本を読む場合に「データベース」とか「集合知」というキーワードに惑わされがちだと思うけど、情動のフィードバックループを回す事で政治にダイナミズムを取り戻す、という部分が大事だと思うので、実現の難しい遠い未来のことと思わずに、どんどん面白いサービスを作ってリリースしたりすればいいと思う。


一般意志2.0 ルソー、フロイト、グーグル

一般意志2.0 ルソー、フロイト、グーグル

*1:思想家じゃないので自分の価値観を根拠付けたりしないけど、あえて身体でたとえて言うと、関節がしなやかである方が怪我とかもしにくいし健康的だろう。

*2:まあこれは大いに異論はあると思う

*3:RDBMSでもとりあえずBLOBで生データを突っ込むというのは可能だし実際やってるけど

使用しているファイルの一覧を取得する

プロセスが使用しているファイルの一覧を取得する。
c - on iOS/iPhone: "Too many open files": need to list open files (like lsof) - Stack Overflow」によると、fcntlでfdを舐めてパスを取れば良いっぽい。

#include <sys/param.h>
#include <sys/fcntl.h>

void
show_openfiles()
{
    int flags;
    int fd;
    char buf[MAXPATHLEN + 1] ;
    
    for (fd = 0; fd < OPEN_MAX; fd++) {
        errno = 0;
        flags = fcntl(fd, F_GETPATH, buf);
        if (flags != -1) {
            printf("File Descriptor %d in use for: %s\n", fd, buf) ;
        }
    }
}

なんか適当にファイルを開いた状態で読んでみる。

    id filehandle = [NSFileHandle fileHandleForReadingAtPath:@"/etc/services"];
    show_openfiles();
...

出力

File Descriptor 0 in use for: /dev/null
File Descriptor 1 in use for: /dev/ttys000
File Descriptor 2 in use for: /dev/ttys000
File Descriptor 3 in use for: /private/etc/services

0〜2は標準入力、標準出力、標準エラー出力。/dev/ttys...になっているのはソケットの模様。


sysctlでKERN_FILEを取れないかやってみた。

#include <sys/sysctl.h>
#include <sys/file.h>

/* file types */
typedef enum {
    DTYPE_VNODE = 1,	/* file */
    DTYPE_SOCKET,	/* communications endpoint */
    DTYPE_PSXSHM,	/* POSIX Shared memory */
    DTYPE_PSXSEM,	/* POSIX Semaphores */
    DTYPE_KQUEUE,	/* kqueue */
    DTYPE_PIPE,         /* pipe */
    DTYPE_FSEVENTS,	/* fsevents */
    DTYPE_MAX
} file_type_t;
#define DTYPE_NAME { \
    "*UNKNOWN*", \
    "DTYPE_VNODE", \
    "DTYPE_SOCKET", \
    "DTYPE_PSXSHM", \
    "DTYPE_PSXSEM", \
    "DTYPE_KQUEUE", \
    "DTYPE_PIPE", \
    "DTYPE_FSEVENTS" \
}

LIST_HEAD(filelist, extern_file);

void
show_kernfiles(void)
{
    int count[DTYPE_MAX];
    for (int i = 0; i < DTYPE_MAX; i++) count[i] = 0;
    const char *dtype_name[] = DTYPE_NAME;

    size_t size;
    if (sysctlbyname("kern.file", NULL, &size, NULL, 0) == -1) {
        perror("sysctl: kern.file");
        return;
    }
    char *buffer = malloc(size);
    if (sysctlbyname("kern.file", buffer, &size, NULL, 0) == -1) {
        perror("sysctl: kern.file");
        return;
    }

    char *ptr = buffer;

    struct filelist filehead = *(struct filelist *)ptr;
    ptr += sizeof(struct filelist);

    struct extern_file *fp;
    for (fp = filehead.lh_first; fp != 0; fp = fp->f_list.le_next){
        fp = (struct extern_file *)ptr;
        printf("fp = %p\n", fp);
        printf("f_list.le_next = %p\n", fp->f_list.le_next);
        printf("f_flag = %x\n", fp->f_flag);
        printf("f_type = %d\n", fp->f_type);
        printf("f_count = %d\n", fp->f_count);
        printf("f_msgcount = %d\n", fp->f_msgcount);
        printf("f_cred = %p\n", fp->f_cred);
        printf("f_ops = %p\n", fp->f_ops);
        printf("f_offset = %lld\n", fp->f_offset);
        printf("f_data = %p\n", fp->f_data);
        if (fp->f_type >= DTYPE_VNODE && fp->f_type < DTYPE_MAX) {
            count[fp->f_type]++;;
        } else {
            printf("unknown type = %d\n", fp->f_type);
            count[0]++;;
        }
        ptr += sizeof(struct extern_file);
    }
    free(buffer);
    printf("\nSummary:\n");
    for (int i = 0; i < DTYPE_MAX; i++) {
        printf("\t%s:\t%d\n", dtype_name[i], count[i]);
    }
}

file_type_tは定義が見つからなかったからxnuのソースコードの新しそうな奴から持って来た。
取得したデータの中身はsys/sysctl.hによると「struct: file entries」らしいのだが、具体的には何か分からないし、データの中身を見ると36バイト周期っぽかったので多分struct extern_fileと思う。
struct extern_fileはLIST_ENTRYでリンク構造になっているのでLIST_HEADで先頭のポインタの型を作ってやる。
但し、リンクの中身はカーネル空間のアドレス(0x80000000以降)で見れないので、そっちは使わずに構造体のサイズ分をポインタを進めている。


出力:

fp = 0xe07de04
f_list.le_next = 0x9880a900
f_flag = 1
f_type = 1
f_count = 41
f_msgcount = 0
f_cred = 0x80b52f80
f_ops = 0x802c4168
f_offset = 0
f_data = 0x81e875c8
...
fp = 0xe080df8
f_list.le_next = 0x0
f_flag = 1
f_type = 1
f_count = 28
f_msgcount = 0
f_cred = 0x80b52f80
f_ops = 0x802c4168
f_offset = 0
f_data = 0x81e875c8

Summary:
	*UNKNOWN*:	0
	DTYPE_VNODE:	144
	DTYPE_SOCKET:	145
	DTYPE_PSXSHM:	0
	DTYPE_PSXSEM:	0
	DTYPE_KQUEUE:	36
	DTYPE_PIPE:	16
	DTYPE_FSEVENTS:	1

VNODEというのがファイルのようで、実際にf_dataの中身にアクセス出来ればパス等も取れそうだけど、檻の中からカーネルタスクのメモリに自由にアクセスする方法がないので詳細は見られないっぽい。

kern.vnodeも読んでみたけど、こっちはEINVALになって内容は読めなかった。

ファイルの一覧を取得する

NSFileManagerの-enumeratorAtPath:を呼ぶと、特定ディレクトリの下を深さ優先でトラバースするNSDirectoryEnumeratorオブジェクトを返してくれるので、それで全ファイルエントリを取得出来るようだ。

但し、iTunesで買ったアプリなどは見えない*1。他にも見えないファイルがあるようだが、パーミッションの問題ではないようだ。別の方法で見せないようにしているっぽい。

/* CLANG_ENABLE_OBJC_ARC = YES */

#define TYPE_CHAR(fileType) (\
      fileType == NSFileTypeDirectory         ? 'd' \
    : fileType == NSFileTypeSymbolicLink      ? 'l' \
    : fileType == NSFileTypeSocket            ? 's' \
    : fileType == NSFileTypeCharacterSpecial  ? 'c' \
    : fileType == NSFileTypeBlockSpecial      ? 'b' \
    : fileType == NSFileTypeUnknown           ? '?' \
    :                                           '-' \
)

#define WHO_OWNER   2
#define WHO_GROUP   1
#define WHO_OTHER   0

/* non-zero if the file is readable */
#define READABLE(mode, who)   (mode >> (who * 3 + 2) & 1)
/* non-zero if the file is writable */
#define WRITABLE(mode, who)   (mode >> (who * 3 + 1) & 1)
/* non-zero if the file is executable or the directory is searchable */
#define EXECUTABLE(mode, who) (mode >> (who * 3 + 0) & 1)
/* non-zero if set-x-ID or sticky bit is set */
#define SBIT_SET(mode, who)   (mode >> (who + 9) & 1)

#define SBIT_CHAR(mode, who) (\
    who == WHO_OTHER \
        ? (EXECUTABLE(mode, who) ? 't' : 'T') \
        : (EXECUTABLE(mode, who) ? 's' : 'S') \
)\

#define MODE_CHARS(mode, who) \
    READABLE(mode, who) ? 'r' :'-', \
    WRITABLE(mode, who) ? 'w' :'-', \
    SBIT_SET(mode, who) \
       ? SBIT_CHAR(mode, who) \
       : EXECUTABLE(mode, who) ? 'x' :'-'

void
show_files(NSString *directoryPath)
{
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
    [dateFormat setDateFormat:@"MM dd HH:mm:ss yyyy"];

    NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtPath:directoryPath];
    NSString *filePath;
    while (filePath = [enumerator nextObject]) {
        NSString *fullPath = [directoryPath stringByAppendingPathComponent:filePath];

        NSDictionary *attrs = [enumerator fileAttributes];
        NSString *fileType = [attrs objectForKey:NSFileType];
        short mode = [[attrs objectForKey:NSFilePosixPermissions] shortValue];
        NSString *owner = [attrs objectForKey:NSFileOwnerAccountName];
        NSString *group = [attrs objectForKey:NSFileGroupOwnerAccountName];
        NSDate *modificationDate = [attrs objectForKey:NSFileModificationDate];
        
        unsigned long long fileSize = [[attrs objectForKey:NSFileSize] unsignedLongLongValue];

        printf("%c" "%c%c%c" "%c%c%c" "%c%c%c" " %-8s %-8s %10qu %s %s\n",
                TYPE_CHAR(fileType),
                MODE_CHARS(mode, WHO_OWNER), MODE_CHARS(mode, WHO_GROUP), MODE_CHARS(mode, WHO_OTHER),
                [owner UTF8String], [group UTF8String], fileSize,
                [[dateFormat stringFromDate:modificationDate] UTF8String],
                [fullPath fileSystemRepresentation]);
    }
}

show_files(@"/Developer/usr/bin/")してみた。

-rwxrwxr-x root     admin         14496 02 02 16:20:17 2012 /Developer/usr/bin/chudRemoteCtrl
-rwxrwxr-x root     admin        152032 08 10 14:05:50 2011 /Developer/usr/bin/debugserver
-rwxrwxr-x root     admin         20176 02 02 16:20:33 2012 /Developer/usr/bin/hwprefs
-rwxrwxr-x root     admin        106016 01 16 12:09:06 2012 /Developer/usr/bin/reg
-rwxrwxr-x root     admin         18064 02 05 07:10:09 2012 /Developer/usr/bin/ScreenShotr
-rwxrwxr-x root     admin        117360 02 02 16:20:29 2012 /Developer/usr/bin/shark

他のディレクトリも含め、setuidしてるファイルは見つからなかった。
ソケットやシンボリックリンクはそれなりに見つかった。


拡張属性を調べるには、xattr.hにある関数を使えば良いっぽい。

#include <sys/types.h>
#include <sys/xattr.h>
...
        ssize_t bufsize = listxattr([fullPath fileSystemRepresentation], NULL, 0,0);
        if (bufsize > 0){
            char *buf = malloc(bufsize);
            listxattr([fullPath fileSystemRepresentation], buf, bufsize,0);
            char *ptr = buf;
            while(ptr <  buf + bufsize){
                ssize_t attrsize = getxattr([fullPath fileSystemRepresentation], ptr, NULL, 0, 0, 0);
                printf("\t%s\t%ld\n", ptr, attrsize);
                ptr += strlen(ptr) + 1;
            }
            free(buf);
        }
...

拡張属性が付いているファイルは一つしか見つからなかった。

-rw-r--r-- mobile   mobile         5662 01 18 13:58:01 2012 /private/var/mobile/Media/PhotoData/MISC/PreviewWellImage.tiff
	com.apple.assetsd.thumbnailCameraPreviewImageAssetID	58


ファイルの中身は普通にFoundationのクラスでアクセス出来る。

        NSData *data = [NSData dataWithContentsOfFile:@"/Developer/usr/bin/debugserver"];
        NSLog(@"data = %@", data);

適当に一つiPad2からLion上に持って来てotoolで中身をみたら、ARM_V7用バイナリだった。

daphne:tmp terazzo$ otool -h debugserver
debugserver:
Mach header
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
 0xfeedface      12          9  0x00          2    19       2000 0x00210085

/usr/include/mach/machine.h によると

#define CPU_TYPE_ARM            ((cpu_type_t) 12)
#define CPU_SUBTYPE_ARM_V7              ((cpu_subtype_t) 9)

追記: パーミッションのとこのAssert

ちょっと書いてた。関数の中から呼ぶときはNSAssertじゃなくてNSCAssertを使う。

#define ASSERT_MODE(mode, who, str) \
    NSCAssert2(([[NSString stringWithFormat:@"%c%c%c", MODE_CHARS(mode, who)] isEqualToString:str]), \
         @"'%@' expected but was '%@'", str, ([NSString stringWithFormat:@"%c%c%c", MODE_CHARS(mode, who)]))

void
assert_mode()
{
    ASSERT_MODE(00000, WHO_OWNER, @"---");
    ASSERT_MODE(00100, WHO_OWNER, @"--x");
    ASSERT_MODE(00200, WHO_OWNER, @"-w-");
    ASSERT_MODE(00300, WHO_OWNER, @"-wx");
    ASSERT_MODE(00400, WHO_OWNER, @"r--");
    ASSERT_MODE(00500, WHO_OWNER, @"r-x");
    ASSERT_MODE(00600, WHO_OWNER, @"rw-");
    ASSERT_MODE(00700, WHO_OWNER, @"rwx");

    ASSERT_MODE(04000, WHO_OWNER, @"--S");
    ASSERT_MODE(04100, WHO_OWNER, @"--s");
    ASSERT_MODE(04200, WHO_OWNER, @"-wS");
    ASSERT_MODE(04300, WHO_OWNER, @"-ws");
    ASSERT_MODE(04400, WHO_OWNER, @"r-S");
    ASSERT_MODE(04500, WHO_OWNER, @"r-s");
    ASSERT_MODE(04600, WHO_OWNER, @"rwS");
    ASSERT_MODE(04700, WHO_OWNER, @"rws");
    
    ASSERT_MODE(01000, WHO_OTHER, @"--T");
    ASSERT_MODE(01001, WHO_OTHER, @"--t");
    ASSERT_MODE(01002, WHO_OTHER, @"-wT");
    ASSERT_MODE(01003, WHO_OTHER, @"-wt");
    ASSERT_MODE(01004, WHO_OTHER, @"r-T");
    ASSERT_MODE(01005, WHO_OTHER, @"r-t");
    ASSERT_MODE(01006, WHO_OTHER, @"rwT");
    ASSERT_MODE(01007, WHO_OTHER, @"rwt");
}

追記2: 見えないディレクト

readdirで存在は確認できるがstatするとEPERMが戻ってくる。サンドボックス外のディレクトリについてはそういう風になるようだ。

#include <sys/param.h>
#include <sys/stat.h>
#include <dirent.h>
...
        const char base_dir[] = "/var/mobile/Applications/";
        DIR *dir = opendir(base_dir);
        if (dir) {
            char path[MAXPATHLEN + 1];
            struct dirent *dirent;
            struct stat st;
            while ((dirent = readdir(dir)) != NULL) {
                if (strlen(base_dir) + strlen(dirent->d_name) > MAXPATHLEN) continue;
                if (!strcmp(dirent->d_name, ".") || !strcmp(dirent->d_name, "..")) continue; 
                strcpy(path, base_dir);
                strcat(path, dirent->d_name);
                printf("file: \t%s\n", dirent->d_name);
                if (stat(path, &st) == -1) {
                    perror("\t*** stat failed");
                    continue;
                }
                
                printf("\tmode:%0.6o\n", st.st_mode);
            }
            closedir(dir);
        }
file: 	4335AC47-9753-4D5E-BF9F-52507E510B8C
	*** stat failed: Operation not permitted
file: 	AD45441A-D37B-4B6A-8A46-3DEC149E0C54
	*** stat failed: Operation not permitted
file: 	BCAB6052-D9F7-4F52-9496-5F102EDC93B0
	mode:040755

一番下の奴はこのプログラム自体を動かしているアプリケーションなので見えている。

*1:前エントリの「/var/mobile/Applications/4335AC47-9753-4D5E-BF9F-52507E510B8C/iBooks.app」などは見えなかった

プロセスの引数を取得する

前回の続き。KERN_PROCARGS2で引数の情報も取得出来た。但し自分(mobile)がオーナーのプロセスのみ。rootで動ければ全部取れるかも。

KERN_PROCARGSの方も試してみたけどiOS上ではうまく動かない(エミュレータなら環境変数まで取れたけど。)

KERN_PROCARGS2はバッファの先頭に引数の数(argc)が入っていて、あとは引数が文字列で('\0'区切りで)入っている。信用してprintf/strlenで中身を手繰っている。

参考: 「blog: pawo: 実行中のアプリケーションの引数リストを取得する (2)

#include <sys/sysctl.h>
#include <pwd.h>

#define ARRAY_SIZE(a) (sizeof (a) / sizeof ((a)[0]))

void
show_processes()
{
    int mib_proc[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0}; // all processes
    struct kinfo_proc *procs;
    size_t buffersize = 0;
    int index, count;
    
    // get required buffer size for procs
    if (sysctl(mib_proc, ARRAY_SIZE(mib_proc), NULL, &buffersize, NULL, 0) < 0) {
        // failure calling sysctl()
        return;
    }
    procs = (struct kinfo_proc *)malloc(buffersize);
    // get procs info
    if (sysctl(mib_proc, ARRAY_SIZE(mib_proc), procs, &buffersize, NULL, 0) < 0) {
        // failure calling sysctl()
        free(procs);
        return;
    }
    count = buffersize / sizeof(struct kinfo_proc);

    // get required buffer size for arguments
    int mibargmax[] = { CTL_KERN, KERN_ARGMAX};
    int size_argmax = 0;
    size_t size = sizeof(size_argmax);
    if(sysctl(mibargmax, ARRAY_SIZE(mibargmax), &size_argmax, &size, NULL, 0) == -1) {
        // failure calling sysctl()
        free(procs);
        return;
    }
    char *arguments = malloc(size_argmax + 1);
    
    printf("UID             PID   PPID COMMAND\n");
    for (index = 0; index < count; index++) {
        uid_t p_uid = procs[index].kp_eproc.e_pcred.p_ruid;
        char *username = user_from_uid(p_uid, 0);
        pid_t p_pid = procs[index].kp_proc.p_pid;
        pid_t e_ppid = procs[index].kp_eproc.e_ppid;
        char *p_comm = procs[index].kp_proc.p_comm;
        printf("%-14s %5d %5d", username, p_pid, e_ppid);
        
        int mib[] = { CTL_KERN, KERN_PROCARGS2, p_pid};
        size_t arguments_size = size_argmax;
        if (sysctl(mib, ARRAY_SIZE(mib), arguments, &arguments_size, NULL, 0) != -1) {
            arguments[arguments_size] = '\0';
            int number_of_arguments;
            memcpy(&number_of_arguments, arguments, sizeof(number_of_arguments));
            char *arg_ptr = arguments + sizeof(number_of_arguments);
            while (number_of_arguments--) {
                printf(" %s", arg_ptr);
                arg_ptr += strlen(arg_ptr) + 1;
            }
            printf("\n");
        } else {
            printf(" %s\n", p_comm);
        }
    }
    free(arguments);
    free(procs);
    return;
}
UID             PID   PPID COMMAND
mobile         19193     1 /System/Library/CoreServices/CFNetworkAgent
root           19192     1 amfid
mobile         19191 19187 /var/mobile/Applications/BCAB6052-D9F7-4F52-9496-5F102EDC93B0/True3DMaze4i.app/True3DMaze4i
mobile         19190     1 /System/Library/PrivateFrameworks/iTunesStore.framework/Support/itunesstored
mobile         19187 19176 /Developer/usr/bin/debugserver 
mobile         19186 19176 /usr/libexec/springboardservicesrelay
mobile         19182     1 /usr/libexec/lsd
mobile         19178     1 /usr/libexec/installd  
root           19176     1 lockbot
_securityd     19175     1 securityd
mobile         19165     1 /usr/libexec/atc
mobile         19144     1 /usr/libexec/syslog_relay 
mobile         19143     1 /usr/libexec/notification_proxy
mobile         19139     1 /usr/libexec/notification_proxy
mobile         19138     1 /usr/libexec/notification_proxy
mobile         19130     1 /usr/libexec/afcd   /usr/libexec/afcd --lockdown -d
mobile         19117     1 /usr/libexec/ptpd  
mobile         13372     1 /Developer/usr/bin/debugserver 
mobile          7645     1 /System/Library/PrivateFrameworks/GeoServices.framework/geod
mobile          6662     1 /var/mobile/Applications/4335AC47-9753-4D5E-BF9F-52507E510B8C/iBooks.app/iBooks
mobile          6640     1 /Applications/MobileSafari.app/MobileSafari
mobile          6635     1 /System/Library/PrivateFrameworks/Ubiquity.framework/Versions/A/Support/ubd
root            6634     1 filecoordination
mobile          5744     1 /Applications/MobileMail.app/MobileMail
mobile          5579     1 /Applications/MobileSlideShow.app/MobileSlideShow
mobile          5394     1 /Applications/MobileStore.app/MobileStore
mobile          5390     1 /Applications/AppStore.app/AppStore
root             502     1 SCHelper
mobile           488     1 /Applications/Preferences.app/Preferences
mobile           321     1 /System/Library/Frameworks/AssetsLibrary.framework/Support/assetsd
mobile           145     1 /Applications/MobilePhone.app/MobilePhone
mobile           141     1 /usr/sbin/absinthed.K93
mobile            82     1 /System/Library/PrivateFrameworks/DataAccess.framework/Support/dataaccessd
mobile            81     1 /usr/sbin/aosnotifyd
root              70     1 networkd
mobile            67     1 /System/Library/CoreServices/SpringBoard.app/SpringBoard
mobile            63     1 /usr/sbin/BTServer
mobile            58     1 /System/Library/PrivateFrameworks/AggregateDictionary.framework/Support/aggregated
mobile            57     1 /System/Library/PrivateFrameworks/ApplePushService.framework/apsd
mobile            52     1 /usr/sbin/fairplayd.K93
root              51     1 fseventsd
mobile            49     1 /System/Library/PrivateFrameworks/IAP.framework/Support/iapd
mobile            48     1 /System/Library/PrivateFrameworks/IMCore.framework/imagent.app/imagent
root              46     1 locationd
_mdnsresponder    45     1 mDNSResponder
mobile            44     1 /System/Library/PrivateFrameworks/MediaRemote.framework/Support/mediaremoted
mobile            43     1 /usr/sbin/mediaserverd
root              27     1 wifid
root              23     1 powerd
root              21     1 lockdownd
_wireless         19     1 CommCenterClassi
root              16     1 syslogd
root              14     1 configd
root              13     1 notifyd
root              12     1 UserEventAgent
root               1     0 launchd
root               0     0 kernel_task