では、問題の認証に入る。リスト5(auth_node.c、10ページ目にも掲載)がそのサンプルだ。このサンプルでは92行目よりmain関数が始まっており、120行目のdsOpenDirNode関数までは先ほどまでのサンプルと同じだ(図8)。

図8: auth_node.c(ヘッダおよびmain関数部分)

図8: auth_node.c(ヘッダおよびmain関数部分)

123行目では、readpassphrase関数を用いてユーザからパスワードの入力を取得している。この関数はOpenBSD由来のもので、従来のgetpass関数のような文字数制限やセキュリティ上の問題点が少ない、より安全なパスワード入力手段を提供している。

127行目で認証を行う関数DoTinyAuthを呼び出し、その結果であるisAuthOK変数やresultMsg変数を表示、終了している。

DoTinyAuth関数は18行目から始まっている。中心となるのは66行目のdsDoDirNodeAuth関数で、この関数がDirectoryServiceへの認証リクエストやその他認証を伴うリクエストを送付する関数となっている。認証情報はauthBufferという変数(バッファ)に格納されており、それを構築するのが42行目から63行目までの部分だ(図9)。

図9: auth_node.c(DoTinyAuth関数部分)

図9: auth_node.c(DoTinyAuth関数部分)

authBufferはtDataBuffer型の構造体へのポインタであり、この構造体はバッファの先頭ポインタとその長さの2つの情報をまとめたものだ。このバッファに対して、dsDoDirNodeAuthは呼び出す認証方式に応じた適切な順序で「4バイトのデータ長」「実際のデータ」の繰り返しでデータをパッキングすることを要求している。認証方式はdsDoDirNodeAuthの第2引数で指定される。ここでは、kDSStdAuthNodeNativeClearTextOKという定数であり、そのnodeにとって自然な(デフォルトの)認証方式を利用、それがクリアテキスト形式でも許可するというものだ。この方式では「ユーザ名」「パスワード」という順番でauthBufferを構築すればよいため、42行目からはそれを行っている。

Leopardでは新たにdsFillAuthBuffer関数ができたため、適切な順序で可変長引数に値を記述すれば、バッファの中身を適切にセットしてくれる。しかし、Tigerやそれ以前にはそんな便利な関数がなかったため、52行目からにあるようにmemcpyを使って値をバッファにコピーしていかなければならない。また、最後の61行目にあるように、実際のデータサイズに合わせてバッファの長さを設定をしないと、dsDoDirNodeAuthはバッファを正しく読み取れず、エラーとなってしまうので注意しよう。

このような方式になった背景としては、DirctoryServiceがMach IPCを使っている点が挙げられるだろう。Mach IPCでは、通信内容を送り手から受け手のメモリ空間にコピーするinboundな通信だけではなく、メモリの割り当てを操作することで送り手と受け手の間で通信内容の載ったメモリを共有、メモリコピーを最低限にとどめるoutboundな通信をサポートしている(メモリはCopy On Writeの状態で共有されるため、どちらかのプロセスがそのメモリに書き込みを行い、データを変えようとした瞬間に必要分のメモリのコピーが発生する)。

Mac OS Xのカット & ペーストがどんなサイズのデータをコピーしてペーストしても迅速に動作するのは、このoutboundの通信による恩恵が大きい。一方、この恩恵を最大限に受けようとするならば、ちまちまと小さなデータを用意するのではなく、1つの大きなバッファにパックしたほうがいいのは分かるのだが、しかし、こうしたパック処理をAPIの呼び出し側にいちいち行わせるのはいかがなものか。実際、この部分でのミスはなかなか分かりにくいバグや問題になり、DirectoryService APIの使い勝手を落としている。dsFillAuthBufferのような関数を提供するのではなく、そもそもそれを組み込んだ形で、内部で自動的にバッファを作成しMach IPCを送る形でAPIを制定したほうが、よっぽどましだったのではないかと思えてならない。

なお、認証の結果はdsDoDirNodeAuthの戻り値で分かるが、それ以外に付加的な情報がある場合は、respBufferに格納されて返ってくる。もちろん、respBufferに情報が入りきらない場合は、末尾のコンテクスト情報を利用して呼び直すことになる。

本来、dsDoDirNodeAuthは認証のためのAPIであり、プラグイン特有のAPIなどはdsDoPlugInCustomCall関数を用いて呼び出すべきなのであるが、認証に絡む微妙な処理についてはdsDoDirNodeAuthの認証方式を拡張することで対応している。例えば、管理者による任意のユーザへのパスワードの設定についてはkDSStdAuthSetPasswdを用い、ユーザが自分自身のパスワードを変更したい場合はkDSStdAuthChangePasswdを認証方式に設定し、適切なバッファを用意することで行われる。

またこうした認証から外れた認証方式の中には、値を取得するものが存在する。例えばkDSStdAuthGetMethodsForUserは、ユーザが使用できる認証方式のリストを取得する、dsDoDirNodeAuthの認証方式であるが、この値は先の respBufferの中に、やはり「4バイトの長さ」「実際データ」の繰り返しの形で渡される。

それぞれに適当なAPIを用意すべきだと思われてならないが、どうもDirectoryServiceのAPIの制定者にはこうしたCという言語の持つ"バッドノウハウ"にはまった人間がいるらしい。旧Mac OS時代のプログラミングが「使うは天国、作るは地獄」と評されたのも、こうした複雑な構造体を重ね合わせ、一方で構造体の内部を読み書きし、一方で改変してはならないデータとして扱ったり、バイトデータやAPIに妙な多義性を持たせたりといったバッドノウハウの跋扈にある。Carbon化の一環でそうした構造体への直接アクセスは避けられるようになり、アクセス関数を利用するのが一般的になったはず、またCocoaやCore Foundationのようなより抽象度の高い、オブジェクト指向の考え方を取り入れたはずである。しかし、ことDirectoryServiceに関してはそうした正しい考え方が見えず、Toolbox時代から何も変わってない状況が存在している。これがQuickTimeのように旧Mac OSから引き継いだAPIならまだ納得がいくのだが、DirectoryServiceはMac OS Xになってから作られたAPIであるにもかかわらず、こうしたばかばかしい状態ができあがっている。

実際、DirectoryServiceを使うプログラマはその上により抽象度の高いライブラリをかぶせる、バッドノウハウに対する"グッドラッパー"を作るのが一般的だ。Appleですら、PrivateFrameworkとしてCore FoundationでラップしたCFOpenDirectory.frameworkやObjective-Cでラップ下OpenDirectory.frameworkを用意している。

筆者はPanther時代からTiger時代にかけてこのAPIを利用したシステムを作成、メンテナンスしていたが、当時からこうした不便なAPIと隠されたラッパーというダブルスタンダード状態であり、不満を募らせていた。Leopardではこうした事態も改善されるかと期待していたのだが、まったくもって残念な限りである。