Apache2.2でリモートアドレスベースでの制限 "または" SSLクライアント証明書による認証を行う
現行でリモートアドレスベースでのアクセス制限を行っているサーバがあるんだけど、新たにSSLクライアント証明書をインストールした端末にもアクセスを許可したいという話があった。ちょっとトリッキーだけどなんかうまくいったので記録。むしろ情報求む。
クライアントに証明書を要求する
まず、アクセス時にクライアントに証明書を要求するわけだけど、リモートアドレスベースで許可されている場合には証明書は不要なので、ここでは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
これでリモートアドレスベースで登録されているクライアントと、クライアント証明書がインストールされているクライアントからのアクセスを許可できた(一応。)
穴が無いかとかもう少し調べてみる。
文字列テンプレートを型安全にする
パーサとテンプレートエンジンって丁度動きが逆だよなあと前から思ってたんだけど、てことはパーサコンビネータっぽい感じで文字列テンプレート書けないかと思った。
一応見た感じそれっぽく出来たけど、なんか格好悪い実装になってしまった。真性の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ではないのは、この辺と関係ありそう。
というようなこと考えてたら、解説があったのでリンクしておく。中身まだあんまり読めてないけど。
『一般意思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、とりあえずデータを非定形のまま格納し、後で並列に実行されるジョブエンジンなどによって情報を抽出するという、処理のタイミングの違いがある。
差異の和としての一般意思にふさわしいのは、整形されたリレーショナルデータベースのレコードではなく、差異を差異のまま格納したビッグデータではないかと思う。おそらくこの本で「データベース」と表現されているものもそれに近いのだろう。
つまり、ビッグデータというのはHadoopに突っ込めば何か自動的に統計データが出てくるのではなく、仮説を当てはめることで初めて意味のあるデータを得ることが出来る。誰かがデータを「分析」し「診断」するフェーズが存在する。
ビッグデータを「診断」する分析家が居て、分析結果を投げ返す事で、診断と症状のキャッチボールがおこなわれ、初めて欲望と呼ばれるような物が出てくるのではないか。
当然だけど、この場合フィードバックループが回っている事が重要で診断結果の妥当性とかはどうでもいいです。正確かというより、説得力があり、かつ相手をびっくりさせるような結果が大事じゃないかな。(というかループがある事によって事後的に正しい結果とされるに過ぎない。)
まとめ
エンジニアがこの本を読む場合に「データベース」とか「集合知」というキーワードに惑わされがちだと思うけど、情動のフィードバックループを回す事で政治にダイナミズムを取り戻す、という部分が大事だと思うので、実現の難しい遠い未来のことと思わずに、どんどん面白いサービスを作ってリリースしたりすればいいと思う。
- 作者: 東浩紀
- 出版社/メーカー: 講談社
- 発売日: 2011/11/22
- メディア: 新書
- 購入: 14人 クリック: 581回
- この商品を含むブログ (164件) を見る
使用しているファイルの一覧を取得する
プロセスが使用しているファイルの一覧を取得する。
「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
一番下の奴はこのプログラム自体を動かしているアプリケーションなので見えている。
プロセスの引数を取得する
前回の続き。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