Abstract
Last Update : Fri Oct 10 00:59:58 CEST 2003
この資料は、Sambaを開発する人か、そういうことに興味がある人にとって便利かもしれない 資料を集めたものである。これは、単に、sambaのいろいろな部分の内部構造や、SMBプロトコルに ついて記述された文書の集合でしかない。これは常時発展途上である。この文書の最も最新な ものは、http://devel.samba.org/にある。
この文書はGPL(GNU General Public License)のバージョン2でライセンスされている。 ライセンス文書はSambaソースディストリビューション内に含まれている。オンラインでは http://www.fsf.org/licenses/gpl.txt で参照できる。
Table of Contents
Andrew Tridgell
Luke Leightonmailto:lkcl@switchboard.net
Paul Ashtonmailto:paul@argo.demon.co.uk
Duncan Stansfieldmailto:duncans@sco.com
Dan Shearer
Chris Hertel
David Chappellmailto:David.Chappell@mail.trincoll.edu
Steve French
Simo Sorce
Andrew Bartlett
Tim Potter
Martin Pool
Jelmer R. Vernooijmailto:jelmer@samba.org
Jelmer Vernooijmailto:jelmer@samba.org
Anthony Liguorimailto:aliguor@us.ibm.com
Jelmer Vernooijmailto:jelmer@samba.org
Alexander Bokovoymailto:ab@samba.org
Stefan Metzmachermailto:metze@samba.org
Chris Hertel
Gerald Carter
Jeremy Allisonmailto:samba@samba.org
Andrew Tridgell
Gerald Carter
Jelmer Vernooij
Table of Contents
これは、UNIX上でのSMB実装で直面するいくつかの問題について記述し、Sambaがどのように それらを実装したかを説明した短い文書である。これらはUNIX<->PC間の相互運用を 調べる人の手助けになるかもしれない。
PCへの接続機能を、UNIX上で実装している人の手助けとなるように書かれた。
SMBプロトコルはおおざっぱなユーザー名の概念しか持っていない。初期のSMBプロトコル (たとえばCOREとCOREPLUS)では、まったくユーザー名の概念がなかった。そのあとの プロトコルでもクライアントはしばしば(特にプリンターの操作において)、サーバー上で 最初にユーザー名を検証しないで運用をしていた。
UNIXのセキュリティは、ユーザー名/パスワードペアを基本としている。UNIXマシンは ある種の認証なしには、どのような実質的な操作もクライアントには許可しない。
UNIXサーバーが"共有レベル"でのセキュリティモードになっていたときに、主に問題が 発生する。これは多くのサイトで不評である、各接続済の共有に対して同じユーザーとして クライアントがサーバーに接続する事を通常強制する、"ユーザーレベル"セキュリティモードの 代替となる既定値のモードである。
"共有レベル"セキュリティでは、クライアントは通常"session setup"プロトコル中に おいて、ユーザー名を提供するが、対応するパスワードは提供しない。クライアントは 次に"tree connect"プロトコルを使ってリソースに接続し、パスワードを提供する。 問題は、PC上のユーザーは異なった場面でユーザー名とパスワードを入力することであり、 サーバーにアクセスする時に、両方を一緒にする必要があることを知らないということである。 ユーザー名は通常PCに"ログオン"するときに入力する(これはWindows for Workgroupを 仮定している)。パスワードはディスクやプリンターに接続するときに選択する。
ユーザーは、しばしばドライブの接続のためのログオンに、全く異なったユーザー名を選ぶ。 しばしば、異なったドライブに異なったユーザー名でアクセスすることも望む。UNIX サーバーは各パスワードに結びつけるための正しいユーザー名を何らかの方法で見極める 必要がある。
Sambaはこの問題をいくつかの方法を使って防ごうとする。それらは大変多くの場合、 成功する。その方法にはusername map、service%userという書式、後の認証のために session setupのユーザー名を保持してサービス名からユーザー名を判断する(directoryか user=オプション経由で)などがある。
通常使われるSMBプロトコルは、"ファイルを所有していないのでその操作はできません" という事を通知する方法を持っていない。要するに、全くファイルの所有権という概念が ないのである。
これは、ある種の興味深い問題を引き起こす。たとえば、UNIXマシンにファイルをコピー し、ファイルは誰でも書き込み可能(world writeable)だが、他のユーザーで所有されている 場合、ファイルは正しく転送されるが、間違った日付を受け取ることになる。これは、 UNIX配下ではutime()システムコールは、たとえファイルがすべてのユーザーに対して 書き込み可能だったとしても、ファイルの所有者かrootでのみ成功するという理由による。 セキュリティ上の理由により、Sambaはすべてのファイル操作を認証したユーザーの権限で 行い、rootでは行わないので、utime()が失敗する。これは"make"のようなプログラムが ファイルの時間を正しく取れないということで、共有された開発ディレクトリが不整合になる 可能性がある。
この問題に対するいくつかの解決方法があり、その中には、ユーザー名のマップと 特定の共有に対して特定のユーザー名を強制する方法などがある。
多くのSMBクライアントは送信前にパスワードを大文字にする。なぜそうなっているかの理由は 知らない。興味深いことに、WfWgは、COREPLUSよりもレベルが高いプロトコルで動いている時のみ パスワードを大文字にするので、データ入力ルーチンに責がないのは明白である。
UNIXパスワードは大文字小文字を認識する。そのため、もしもユーザーが大文字小文字を混ぜた パスワードを使う場合には問題が生じる。
Sambaは、大文字小文字を、指定された値まで変更しつつ、提供されたパスワードをSambaに 試行させる、"password level"オプションを使うか、他のマシン(通常Windows NTサーバー) 経由でこの検証をSambaに行わせることが出来る、"password server"オプションを使うことに よって、対処することが出来る。
SambaはSMBクライアントによって提供されるパスワード暗号化方法をサポートする。 Microsoftネットワークにおける暗号化パスワードの使用は、"平文と同等の" パスワードハッシュを提供することと同等であることに注意。これは、それらの パスワードハッシュを含むSambaのsmbpasswdファイルは、rootのみ読み出し可能に すべきであることが*とても重要*であることを意味する。詳細は ENCRYPTION.txtを参照のこと。
Samba 2.2以降、Sambaは他のロック方法をうまくサポートしている。この節は 時代遅れである。
DOS/Windows環境におけるロック呼び出しはunix上でのものよりも機能が豊富である。これは、 SMBのロックを実装するために、fcntl()ベースの標準UNIXロック呼び出しを使うことを選択する (Sambaのような)UNIXサーバーは、多少間に合わせで作る必要があるということである。 The locking calls available under a DOS/Windows environment are much richer than those available in unix. This means a unix server (like Samba) choosing to use the standard fcntl() based unix locking calls to implement SMB locking has to improvise a bit.
大きな1つの問題は、DOSのロックは32ビット(符号なし)の幅で行えると言うことである。 UNIXのロック呼び出しは32ビットだが、符号着きであり、31ビット幅しかない。 不幸にも、OLE2クライアントはOLEセマフォのためにロック幅を選択するためにトップ ビットを使う。
この問題に対応するために、Sambaは、適切なビットシフト操作を行うことによって、 32ビット幅を31ビット幅に圧縮する。これは動作するように見えるが、理想的ではない。 将来のバージョンでは、この問題に対処するために、分離されたSMBロックが追加される かもしれない。
多くのUNIXロックデーモンは結構バグがあり、些細なことによってクラッシュすることも 足を引っ張っている。通常、ごくわずかのUNIXプログラムしかバイトレンジロックを使わない という理由で、UNIX環境では、これらは通常ほとんど使われない。DOS/Windowsからクライアント からの非常に大量のロック要求の負荷は、ある種のシステムにおいてはデーモンを終了して しまう。
2番目の大きな問題は、ある種のクライアントから要求される"便宜的ロック(opportunistic locking)" である。もしもクライアントが便宜的ロックを要求した場合、同じファイル上に、他の誰かが 何らかの操作を行おうとしているならば、クライアントがそのロックを開放することを 告げる時、そのことを通知するためにサーバーに要求する。UNIXは便宜的ロックを簡単に実装する すべがなく、現在のSambaはそれをサポートしていない(訳注:古い)。
SMBクライアントがファイルをオープンするとき、ファイル上に設定されている特定の "拒否モード"について問い合わせをする。それらのモード(DENY_NONE, DENY_READ, DENY_WRITE, DENY_ALL, DENY_FCB と DENY_DOS)は、他の誰かが同じ時に同じファイルを使うときに許可 すべき動作を指定する。たとえば、もしもDENY_READがファイル上に設定されている場合、 読み取りのためのファイルへのオープン操作は失敗する。
UNIXは同等の概念を持っていない。これを実装するため、Sambaはファイルのinodeをベースとする ファイルのロックと、ロックディレクトリを分離するか、あるいは、共有メモリ実装を使う。 ファイルロックによる方式は、あまりかっこよくなく、処理のコストがかかりファイルリソースを 消費するが、共有メモリ実装は非常に好ましく(訳注:preferedはミススペル)、それをサポートする システムでは既定値で有効となっている。
SMBセッションは、1つのソケット上でいくつかのUIDを使って動作することが出来る。これは、 ユーザーが、異なったユーザー名で2つの共有に接続するときに起きる。これに対処するために、 UNIXサーバーは1つのプロセス内でUIDを切り替える必要がある。ある種のUNIX(たとえばSCO)では、 このようなことが出来ない。これは、そのようなUNIXでは、クライアントは単一のUIDでの処理に 制限されることを意味する。
その他の理由により、"trapdoor uid"メッセージも受け取る事があり得ることに注意。 詳細はFAQを参照のこと。
ソケット上のクライアントは、ポート番号が大きな値(>1000)の"非特権"ポート番号を使い、 サーバーへの接続は小さな値の"特権"ポートを使うという慣習がある。これは、UNIX上では、 1000よりも小さなポート番号上で、非rootなユーザーが、リッスンのためにポートを 開けないようにさせている。
ほとんどのPCベースのSMBクライアント(たとえばWfWgとWinNT)はこの習慣を完全に守っていない。 主犯はUDPのポート137でのNetBIOSの名前解決である。名前問い合わせ要求はソースポート137 から来る。これは、小さな(低位の)ポート番号で入ってくるパケットを許可しないよくある ファイアウォールの設定を使うときに問題になる。これは、それらクライアントは、低位の ポートベースのファイアウォールの反対側にあるNetBIOSネームサーバーに問い合わせできないことを 意味する。
NetBIOSノードステータスの問い合わせにおいては問題はより深刻である。WfWg、Win95と WinNT3.5のすべてが、要求元のソースポートが何であっても、ポート137上でNetBIOS ノードステータスを返す。これは両者ともポート137を使うマシン間では動作するが、 UNIXユーザーが、その種のOSでrootとして動作していない限り、ノードステータスを処理 出来ないことを意味する。答えに戻ると、ポート137はUNIXユーザーがリッスンできない。 興味深いことに、WindowsNT3.1ではこれが出来る。これは、要求中のソースポートに ノードステータスを返信する。
SMBプロトコルには数多くの"プロトコルレベル"がある。MicrosoftのOSに新しい機能が追加された たびごとに、新しい能力を"具現化"するために、SMBプロトコルの新しいプロトコルレベル中に 同等の機能を追加しているように見える。
これは、プロトコルは非常に"肥大化していて"、各ファイル操作に対して種々の方法を提供 していることを意味する。これは、SMBサーバーが複雑で肥大化してしまうことを意味する。 また、サーバーのバグをなくすことがとても困難であることも意味する。この問題で苦しむのは Sambaだけではなく、すべての呼び出しのすべての相違をサポートしないWindowsNTのような、 他のサーバーでもありえ、有効な無数のSMB呼び出しをサポートするためにMicrosoftの開発者に とっては全く持って頭痛の元である。
65種類もの(たとえばSMBreadとSMBwrite)のような"最上位の"操作がSMBプロトコル中にある。 それらのいくつかは数百ものサブ機能を含んでいる(SMBtransは少なくとも120の サブ機能を含み、たとえばそれらはDosPrintQAdd と NetSessionEnum)。それらのすべては その動作を変えるいくつかのオプションが指定できる。多くは、結果を返すのに必要と される、構造体を変更する"情報レベル"が何十種類もありえる。Sambaは2つの"最上位の" 機能を除いてすべてをサポートする。それは、SMBtransサブ機能の8つのみ(これまでの所) をサポートする。NTでさえ、全部をサポートしない。
Sambaは現在Windows95とWindowsNT3.5によって提供されている"NT LM 0.12"プロトコルまでを サポートしている。幸運なことにこのプロトコルレベルは、サーバーがサポートする、 とびきりの、新規なオプションを指定する"capabilities"フィールドを持っている。 これは、このプロトコルレベルの実装をより容易にすることを手助けしている。
また、SMBの仕様による問題もある。SMBはX/Openの仕様であるが、X/Openの書籍は理想的 からほど遠く、多くの重要な項目が欠落していて、多くを推測しなければならない。 Microsoftは最近SMBプロトコルをCIFS(Common Internet File System)と改名し、 新しい仕様を公開した。それは古いX/Openのドキュメントと比べると、とても優れているが、 引き続き文書化されていない呼び出しと機能が残っている。この仕様は、Microsoftによって 提供されているCIFS開発者メーリングリストによって活発に作業されている。
Table of Contents
この文書にはNTサーバーを使わないでNTワークステーションのログオンサービスを提供するための 情報が記述されている。これはLukeによって管理されている、 http://mailhost.cb1.com/~lkcl/cifsntdomain.txt のsgmlバージョンである。
(NTワークステーションのTCP/IP設定中で)ワークグループの代わりにドメインを選択することは 可能であり、必須である再起動後に、ユーザー名とパスワードを入力し、ドメインを選択すれば 正しくログオンできる。この文書に対する、上記作業の経験からの、何らかのフィードバック 、何らかのコメント、修正と追加事項は歓迎する。
ここで説明するパケットはNetmon.exeによって簡単に(そしておそらくそれを使うことでより よく理解でき)得られる。NETLOGON、lsarpcとsrvsvcトランザクションパイプを正しくデコード するために、使用しているシステムに一致するNetmonのバージョンを使う必要がある。 この文書は、NTサービスパック1の、それに適合したバージョンのNetmonから得られている。 これは、この文書よりもより有益であろう、注釈付きのパケットトレースが生成されることを 目的としている。
また、NTドメインログオンサービスを完全に実装するため、NTの認証の暗号化部分を 説明する文書も必要である。この文書は comp.protocols.smbから公開されている、 ntsecurity.net のからの要約とsamba digestからとその他のソースからのものを使っている。
コピーは以下にある:
http://ntbugtraq.rc.on.ca/SCRIPTS/WA.EXE?A2=ind9708;L=ntbugtraq;O=A;P=2935
http://mailhost.cb1.com/~lkcl/crypt.html
Linus Nordbergによる、 このプロトコルに関するC言語での実装は、以下にある:
http://samba.org/cgi-bin/mfs/01/digest/1997/97aug/0391.html
http://mailhost.cb1.com/~lkcl/crypt.txt
デバッグ情報の提供のためにはNT workstationのCheck Buildバージョンも必要であり、 NETLOGON中で完全なデバッグを有効にする。これは以下のようにして、REG_SZ レジストリーキーを 0x1ffffff に設定することで行える:
HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters
不正にレジストリを編集すると、使用しているマシンを動作不能にさせる 事になる。このプロトコルの不正な実装になる。上記の"Liability:"を参照のこと。
ネットワーク上の各パケットはAPI呼び出しにおいて固有のオリジンを持っていることを 心にとめておくこと。そのため、その定義がほかの所で便利に文書化されている、 構造体や列挙型かもしれない。
この文書は、決して完全でも正式でもない。以下のようものだけではなく、 抜けているところがある:
ユーザー名からRID(やその他(へのマッピング。
ユーザーIDとグループIDが何であるかということ。
種々の、特定の定数または列挙型の正確な意味と定義。
ワークステーションがドメインのメンバーになるときの、返されるエラーコードと エラーコードの使用法(後で説明)。失敗時に返されるエラーコードは、ワークステーション上で すでにドメインのメンバーになっているという表示を行わせる。
ワークステーションにそのパスワードを変更させることが出来るようにする、 NetrServerPasswordSetコマンドの暗号化の側面。このパスワードは長期でのセッションキー を生成するのに使われる[このコマンドを拒否するのも可能で、その場合は既定値の ワークステーションパスワードを保持する]。
cket Traces from Netmonitor (Service Pack 1 and above) |
ul Ashton and Luke Leighton's other "NT Domain" doc. |
FS documentation - cifs6.txt |
FS documentation - cifsrap2.txt |
Paul Ashton: loads of work with Net Monitor; understanding the NT authentication system; reference implementation of the NT domain support on which this document is originally based. |
Duncan Stansfield: low-level analysis of MSRPC Pipes. |
Linus Nordberg: producing c-code from Paul's crypto spec. |
Windows Sourcer development team |
SMBトランザクションパイプ中で、いくつかの"構造体"がここで説明されていて、 開始時に4バイト境界で整列されたSMBヘッダーがある。正確に"構造体"を整列させる 必要性は、正確には知られても、文書化されてもいない。
UDP NETLOGON Mailslot中で、いくつかの"構造体"がここで説明されていて、開始時に、 mailslotの先頭に2バイト境界で整列されている。
Domain SIDはS-revision-version-auth1-auth2...authNという形式である。たとえば、 Domain SID is of the format S-revision-version-auth1-auth2...authN. S-1-5-123-456-789-123-456である。ここで、5はサブバージョンでもありえる。
参照する文字バッファーが文字を含むなら、文書化されていないバッファーポインターは0以外で なければならない。それが正確に何であるかは不明である。0x0000 0002は、バッファーが 存在することを指示するためのトリックを行っているように見える。もしも、バッファー ポインターがNULLの場合、構造体が参照するものはSMBデータストリームへ(あるいは から) 置かれない。これは、経験的に得られたものであり、例えば、バッファーポインターがNULLの、 LSA SAMログオンレスポンスパケットのユーザー情報はデータストリーム中には挿入されない。 いろいろと推測することは出来るが、バッファーポインターの配列が正確になんなのかは 未知である。
構造体(コンテナ)の配列は、カウンタとポインターを持つように見える。もしもカウンタが ゼロならば、ポインターもゼロである。SMBデータストリームにさらなるデータは存在しない。 もしも、カウンタがゼロでない場合、ポインターもゼロではない。ポインターの直後には また、コンテナのサブ構造体の配列が続くカウンタが来る。カウンタは最後のサブ構造体 の後に3回目のカウンタが来る。
msrpcパケットヘッダー中のコマンド番号
0x00
0x02
0x0B
0x0C
num of sub-authorities in domain SID
SID revision number
num of sub-authorities in domain SID
6 bytes for domain SID - Identifier Authority.
domain SID sub-authorities
注意:ドメイン SID はどこかで文書化されている。
length of unicode string
max length of unicode string
4 - undocumented.
length of unicode string
null-terminated string of unicode characters.
padding to get unicode string 4-byte aligned with the start of the SMB header.
max length of unicode string
0 - undocumented
length of unicode string
string of uncode characters
0x18 - length (in bytes) including the length field.
0 - root directory (pointer)
0 - object name (pointer)
0 - attributes (undocumented)
0 - security descriptior (pointer)
0 - security quality of service
5 - SID type
0 - undocumented
domain SID unicode string header
domain SID unicode string
注意: 文字列の長さを指定示すために、どちらを使うかについて、 UNICODE文字列ヘッダーとUNICODE文字列自身との間で競合がある。これは解決されねばならないだろう。
注意: SIDタイプは、たとえば別名、既定のグループ等などを指示する。これは どこかで文書化されている。
5 - well-known SID. 1 - user SID (see ShowACLs)
5 - undocumented
domain RID
0 - domain index out of above reference domains
注意: ログオンサーバー名は、2つの'\'で始まり、大文字である。
注意: アカウント名は、LSAリクエストチャレンジからのログオンクライアント名で、最後に$があり、大文字である。
undocumented buffer pointer
logon server unicode string
account name unicode string
sec_chan - security channel type
logon client machine unicode string
注意: ログオンサーバー名は2つの'\'で始まり、大文字である。
undocumented buffer pointer
logon server unicode string
undocumented buffer pointer
logon client machine unicode string
注意: 要求中にこの構造体が使われた時にはいつでも、引き続く認証検査で使われるため、 受け取ったクライアントで計算された認証情報のコピーを取る必要がある。仮定された意図は、認証された 要求/応答の痕跡を維持することである。
client and server names
???? padding, for 4-byte alignment with SMB header.
pointer to client credentials.
client-calculated credentials + client time
注意: 要求中にこの構造体が使われた時にはいつでも、引き続く認証検査で使われるため、 受け取ったクライアントで計算された認証情報のコピーを取る必要がある。仮定された意図は、認証された 要求/応答の痕跡を維持することである。
logon account info
client-calculated credentials + client time
ptr_id_info_1
domain name unicode header
param control
logon ID
user name unicode header
workgroup name unicode header
arc4 LM OWF Password
arc4 NT OWF Password
domain name unicode string
user name unicode string
workstation name unicode string
注意: たぶん、戻される認証情報は、おそらく認証チェインが脆弱でない事を、サーバーが検査するためのものである。
client identification/authentication info
pointer to return credentials.
return credentials - ignored.
logon level
switch value
switch (switch_value) case 1: { ID_INFO_1 id_info_1; }
undocumented buffer pointer.
num referenced domains?
undocumented domain name buffer pointer.
32 - max number of entries
4 - num referenced domains?
domain name unicode string header
referenced domain unicode string headers
domain name unicode string
referenced domain SIDs
??? padding to get 4-byte alignment with start of SMB header
domain name string length * 2
domain name string length * 2
undocumented domain name string buffer pointer
undocumented domain SID string buffer pointer
domain name (unicode string)
domain SID
注意: 16バイトのユーザーセッションキーが何のためかを知ることはすてきであろう。
logon time
logoff time
kickoff time
password last set time
password can change time
password must change time
username unicode string header
user's full name unicode string header
logon script unicode string header
profile path unicode string header
home directory unicode string header
home directory drive unicode string header
logon count
bad password count
User ID
Group ID
num groups
undocumented buffer pointer to groups.
user flags
user session key
logon server unicode string header
logon domain unicode string header
undocumented logon domain id pointer
40 undocumented padding bytes. future expansion?
0 - num_other_sids?
NULL - undocumented pointer to other domain SIDs.
username unicode string
user's full name unicode string
logon script unicode string
profile path unicode string
home directory unicode string
home directory drive unicode string
num groups
group info
logon server unicode string
logon domain unicode string
domain SID
other domain SIDs?
注意: cifsrap2.txtの section5, page 10を参照。
0 for shi1_type indicates a Disk. |
1 for shi1_type indicates a Print Queue. |
2 for shi1_type indicates a Device. |
3 for shi1_type indicates an IPC pipe. |
0x8000 0000 (top bit set in shi1_type) indicates a hidden share. |
shi1_netname - pointer to net name
shi1_type - type of share. 0 - undocumented.
shi1_remark - pointer to comment.
shi1_netname - unicode string of net name
shi1_remark - unicode string of comment.
share container with 0 entries:
0 - EntriesRead
0 - Buffer
share container with > 0 entries:
EntriesRead
non-zero - Buffer
EntriesRead
share entry pointers
share entry strings
padding to get unicode string 4-byte aligned with start of the SMB header.
EntriesRead
0 - padding
注意: cifs6.txt section 6.4を参照 - その中で説明されているフィールドはここで補足する。 例えば、以下でリストされたタイプは6.4.1で説明されているfServerTypeと同じである。
0x00000001 All workstations
0x00000002 All servers
0x00000004 Any server running with SQL server
0x00000008 Primary domain controller
0x00000010 Backup domain controller
0x00000020 Server running the timesource service
0x00000040 Apple File Protocol servers
0x00000080 Novell servers
0x00000100 Domain Member
0x00000200 Server sharing print queue
0x00000400 Server running dialin service.
0x00000800 Xenix server
0x00001000 NT server
0x00002000 Server running Windows for
0x00008000 Windows NT non DC server
0x00010000 Server that can run the browser service
0x00020000 Backup browser server
0x00040000 Master browser server
0x00080000 Domain Master Browser server
0x40000000 Enumerate only entries marked "local"
0x80000000 Enumerate Domains. The pszServer and pszDomain parameters must be NULL.
500 - platform_id
pointer to name
5 - major version
4 - minor version
type (SV_TYPE_... bit field)
pointer to comment
sv101_name - unicode string of server name
sv_101_comment - unicode string of server comment.
padding to get unicode string 4-byte aligned with start of the SMB header.
名前付きパイプ上のSMBトランザクションの詳細はcifs6.txtを参照。
MSRPCは\PIPE\
という名前のSMBトランザクションパイプ上で行われる。
たとえば、パイプ名\PIPE\srvsvc
でSMBopenXを送信することで、
まず初めに16ビットのファイルハンドルを得ておかねばならない。そうすると次に
SMB Transを実行でき、処理が終了したら、ファイルハンドル上でSMBCloseを送る必要がある。
Trans Requestは(これについては知られていないが)1つのUINT16ではなく2つのUINT16 セットアップパラメーターと、MSRPCヘッダーを含むために十分なUINT8データパラメーターと MSRPCデータを送る必要がある。最初のUINT16セットアップパラメーターはRPCを指定するために 0x0026か、Set Named Pipe Handle Stateを指示するために、0x0001のどちらかでなければ ならない。2番目のUINT16パラメーターは、上記で得られた、パイプ用のファイルハンドルで なければならない。
Trans Request中の、0x0026 (RPC pipe)であるAPIコマンド用のデータセクションは、RPCデータが 後に来るRPCヘッダーである。0x0001のAPIコマンド(Set Named Pipe Handle state)のための データセクションは、2バイトである。その2バイトの中に現れるものは、0x00 0x43のみである。 The Data section for an API Command of 0x0026 (RPC pipe) in the Trans Request is the RPC Header, followed by the RPC Data. The Data section for an API Command of 0x0001 (Set Named Pipe Handle state) is two bytes. The only value seen for these two bytes is 0x00 0x43.
MSRPC ResponseはMSPRCヘッダー、MSRPCデータとMSRPCフッター(訳注:tail)がある、標準 SMB Transレスポンス内で、レスポンスデータとして送信される。
Trans Requestが少なくとも2バイト境界(おそらく4バイト)に配置される必要があるだろうと いうことは、推測される。これは、SMBに対して標準的な慣習である。また、MSRPCヘッダーと MSRPCデータの間での4バイト単位境界を含む、MSRPCヘッダーの4バイト単位境界とは独立している。 It is suspected that the Trans Requests will need to be at least 2-byte aligned (probably 4-byte). This is standard practice for SMBs. It is also independent of the observed 4-byte alignments with the start of the MSRPC header, including the 4-byte alignment between the MSRPC header and the MSRPC data.
最初に、IPC$共有に対してSMBtconX接続が行われる。コネクションは、平文ではなく、 暗号化したパスワードを使って行わなければならない。次に、SMBOpenXがパイプ上で行われる。 次に、Set Named Pipe Handle Stateが、APCコマンドを受け取れるようにパイプがなった後に 送られる必要がある。最後に、SMBcloseが送られる。
解決されること:
lkcl/01nov97において、RPCパイプのためのNULL文字で終端している\PIPE\名の後に2バイトが 存在するように見える。今のところ分かっている値は以下の通り:
initial SMBopenX request: RPC API command 0x26 params: "\\PIPE\\lsarpc" 0x65 0x63; 0x72 0x70; 0x44 0x65; "\\PIPE\\srvsvc" 0x73 0x76; 0x4E 0x00; 0x5C 0x43;
[Duncan Stansfieldによる作業を受け取った後、この節は書き直される予定]
興味深い注意:もしも、パックされたデータを0x0100 0000と設定した場合、 すべての4バイトと2バイトワードの順番は逆転する!
NTLSAとNETLOGON名前付きパイプのおのおのの開始は、以下で始まる:
reply same as request (0x05)
reply same as request (0x00)
one of the MSRPC_Type enums
reply same as request (0x00 for Bind, 0x03 for Request)
reply same as request (0x00000010)
the length of the data section of the SMB trans packet
call identifier. (e.g. 0x00149594)
the remainder of the packet depending on the "type"
インタフェースには番号が付けられている。同じパイプ名srvsvc上で1つより多い インタフェースを使う場面にはまだ出会ったことがない。
abstract (0x4B324FC8, 0x01D31670, 0x475A7812, 0x88E16EBF, 0x00000003) transfer (0x8A885D04, 0x11C91CEB, 0x0008E89F, 0x6048102B, 0x00000002)
"タイプ"がレスポンスヘッダーのBINDであった場合、ヘッダーの後のパケットの残りの "タイプ"は、BindAckであるべきである。
maximum transmission fragment size (0x1630)
max receive fragment size (0x1630)
associated group id (0x0)
the number of elements (0x1)
presentation context identifier (0x0)
the number of syntaxes (has always been 1?)(0x1)
4-byte alignment padding, against SMB header
num and vers. of interface client is using
num and vers. of interface to use for replies
length of the string including null terminator
the string above in single byte, null terminated form
リプライパケット中でヘッダーの後に配置されるレスポンス
same as request
same as request
zero
the address string, as described earlier
4-byte alignment padding, against SMB header
the number of results (0x01)
4-byte alignment padding, against SMB header
result (0x00 = accept)
reason (0x00 = no reason specified)
the transfer syntax from the request
すべての他のパケットのためのヘッダーの後のパケットの残り
the size of the stub data in bytes
presentation context identifier (0x0)
operation number (0x15)
a packet dependent on the pipe name (probably the interface) and the op number)
RPCバインドは、あるRPCパイプ(例えば\PIPE\lsarpc)と"transfer syntax"(RPC_Iface 構造体を参照)を関連づける手続きである。これを行う目的は不明である。
Note: The RPC_ResBind SMB Transact request is sent with two uint16 setup parameters. The first is 0x0026; the second is the file handle returned by the SMBopenX Transact response.
Note: The RPC_ResBind members maxtsize, maxrsize and assocgid are the same in the response as the same members in the RPC_ReqBind. The RPC_ResBind member transfersyntax is the same in the response as the
Note: The RPC_ResBind response member secondaddr contains the name of what is presumed to be the service behind the RPC pipe. The mapping identified so far is:
RPC_ResBind response:
"\\PIPE\\ntsvcs"
"\\PIPE\\lsass"
"\\PIPE\\lsass"
"\\PIPE\\wksvcs"
"\\PIPE\\NETLOGON"
注意: バインド要求とバインド承認の両方におけるRPC_Packet fraglength メンバーは、RPC_Packet headerを含めて、RPCデータ全体の長さを含まなければならない。
Request:
RPC_Packet |
RPC_ReqBind |
Response:
RPC_Packet |
RPC_ResBind |
このパイプ上で取られる動作の順番は以下の通り:
IPC$ share (SMBtconX)への接続を確立する。暗号化パスワードを使用。 |
"\\PIPE\\lsarpc"という名前でRPCパイプを開く。ファイルハンドルを格納する。 |
ファイルハンドルを使い、Named Pipe Handle stateを0x4300に設定する。 |
LSA Open Policy要求を送る。ポリシーハンドルを格納する。 |
ポリシーハンドルを使い、LSA Query Info Policy要求などを送る。 |
ポリシーハンドルを使い、LSA Closeを送る。 |
IPC$共有をクローズする。 |
このパイプの定義のための、問い合わせの識別子は以下の通り:
0x2c
0x07
0x0d
0xff
0xfe
0xfd
0x00
注意: ポリシーハンドルはどんな好みのものでもあり得る。
buffer pointer
server name - unicode string starting with two '\'s
object attributes
1 - desired access
注意: レスポンス中のinfo classはリクエスト中のものと同じである必要がある。
注意: レスポンス中のnum_entriesはリクエスト中のnum_entriesと同じでなければならない。
LSA policy handle
num_entries
undocumented domain SID buffer pointer
undocumented domain name buffer pointer
DOM_SID[num_entries] domain SIDs to be looked up.
completely undocumented 16 bytes.
注意: レスポンス中のnum_entriesはリクエスト中のnum_entriesと同じでなければならない。
LSA policy handle
num_entries
num_entries
undocumented domain SID buffer pointer
undocumented domain name buffer pointer
names to be looked up.
undocumented bytes - falsely translated SID structure?
このパイプ上の動作の順番は以下の通り:
IPC$ 共有 (SMBtconX)に対して接続を確立する。暗号化パスワードを使用。 |
"\\PIPE\\NETLOGON"という名前でRPCパイプを開く。ファイルハンドルを格納する。 |
ファイルハンドルを使い、Named Pipe Handle stateを0x4300に設定する。 |
クライアント用のチャレンジデータを作成する。LSA Request Challengeを送信する。Server Challengeを格納する。 |
セッションキーを計算する。LSA Auth 2 Challengeを送信する。Auth2 Challengeを格納する。 |
Client Credsを計算して検証する。LSA Srv PW Setを送信する。Server Credsを計算、検証する。 |
Client Credsを計算して検証する。LSA Srv Logonを送信する。Server Credsを計算、検証する。 |
Client Credsを計算して検証する。LSA Srv Logoffを送信する。Server Credsを計算、検証する。 |
IPC$共有をクローズする。 |
このパイプの定義のための、問い合わせ識別子は以下の通り
0x04
0x06
0x02
0x03
0x0f
0x0e
注意: ログオンサーバー名は2つの'\'で始まり、大文字である。
注意: ログオンクライアントはマシンであり、ユーザーではない。
注意: チャレンジが発行されたものに対する最初のLanManager パスワードハッシュは、(小文字の)マシン名それ自身である。あとで、これを変更するであろう 呼び出し(LSA Server Password Set)が発行されるであろう。これらの呼び出しを抑制すると、 常時同じパスワードで扱うことが出来るようになる(すなわち、小文字でのマシン名のLM#)。
undocumented buffer pointer
logon server unicode string
logon client unicode string
client challenge
注意: リクエストとレスポンスの間で、クライアントの認証情報を計算し、 クライアントが計算した認証情報と比較する(この手続きは以前に受け取ったクライアントの 認証情報を使う)。
注意: レスポンス中のneg_flagsはリクエスト中のものと同じである。
注意: そのあとの認証パケット中で使われるという理由で、ここで受け取った クライアントが計算した認証情報のコピーを取る必要がある。
client identification info
client-calculated credentials
padding to 4-byte align with start of SMB header.
neg_flags - negotiated flags (usual value is 0x0000 01ff)
注意: 新しいパスワードはキーを生成するために古いパスワードを使って DES暗号化を使うことが推測される。
注意: リクエストとレスポンスの間で、クライアント認証情報を計算し、 クライアントが計算した認証情報と比較する(この手続きは以前に受け取ったクライアントの 認証情報を使う)。
注意: サーバーの認証情報はクライアントが計算した認証情報と、クライアント時間+1秒から構築される。
注意: そのあとの認証パケット中で使われるという理由で、ここで受け取った クライアントが計算した認証情報のコピーを取る必要がある。
注意: valid_user は、もしもユーザー名とパスワードハッシュが、要求されたドメインに対して 有効ならば、Trueである。
undocumented buffer pointer
server credentials. server time stamp appears to be ignored.
if (valid_user) { UINT16 3 - switch value indicating USER_INFO structure. VOID* non-zero - pointer to USER_INFO structure USER_INFO user logon information UINT32 1 - Authoritative response; 0 - Non-Auth? return 0 - indicates success } else { UINT16 0 - switch value. value to indicate no user presumed. VOID* 0x0000 0000 - indicates no USER_INFO structure. UINT32 1 - Authoritative response; 0 - Non-Auth? return 0xC000 0064 - NT_STATUS_NO_SUCH_USER. }
注意: mailslotsはレスポンスmailslotを含み、それは送信されるべきレスポンスである。 ターゲットのNetBIOS名はREQUEST_NAME<20>であり、ここで、REQUEST_NAME はリクエストを送信したマシンの名前である。
注意: レスポンス中のNTversion, LMNTtoken, LM20tokenはリクエスト中で与えられたものと同じである。
0x0007 - Query for PDC
machine name
response mailslot
padding to 2-byte align with start of mailslot.
machine name
NTversion
LMNTtoken
LM20token
注意: レスポンス中のマシン名は先頭に2つの'\'が付く。
注意: レスポンス中のNTversion, LMNTtoken, LM20tokenはリクエスト中のものと同じである。
注意: レスポンス中のユーザー名はおそらくリクエスト中のものと同じである。
0x0012 - SAM Logon
request count
machine name
user name
response mailslot
alloweable account
domain SID size
domain SID, of sid_size bytes.
???? padding to 4? 2? -byte align with start of mailslot.
NTversion
LMNTtoken
LM20token
このパイプのための定義で、クエリの識別は以下の通り:
0x0f
0x15
注意: レスポンス中のスイッチの値と共有レベルはリクエスト中のものとおそらく同じである。
注意: cifsrap2.txt (section 5)はここで、限定的に手助けとなるかもしれない。
pointer (to server name?)
server name
padding to get unicode string 4-byte aligned with the start of the SMB header.
share level
switch value
pointer to SHARE_INFO_1_CTR
share info with 0 entries
preferred maximum length (0xffff ffff)
Intel byte ordered addition of corresponding 4 byte words in arrays A1 and A2
DES ECB encryption of 8 byte data D using 7 byte key K
Lan man hash
NT hash
md4(machine_password) == md4(lsadump $machine.acc) == pwdump(machine$) (initially) == md4(lmowf(unicode(machine)))
ARC4 encryption of data D of length Ld with key K of length Lk
subset of v from bytes m to n, optionally padded with zeroes to length l
E(K[7..7,7],E(K[0..6],D)) computes a credential
4 byte current time
8 byte client and server challenges Rc,Rs: 8 byte client and server credentials
C->S ReqChal,Cc S->C Cs
C & S compute session key Ks = E(PW[9..15],E(PW[0..6],Add(Cc,Cs)))
C: Rc = Cred(Ks,Cc) C->S Authenticate,Rc S: Rs = Cred(Ks,Cs), assert(Rc == Cred(Ks,Cc)) S->C Rs C: assert(Rs == Cred(Ks,Cs))
ドメインへの参加において、クライアントはオプションとしてそのパスワードを変更しようと し、ドメインコントローラーはレジストリの設定に依存して、その更新を抑制するかもしれない。 これは以降毎週毎に起きるであろう。
C: Tc = Time(), Rc' = Cred(Ks,Rc+Tc) C->S ServerPasswordSet,Rc',Tc,arc4(Ks[0..7,16],lmowf(randompassword()) C: Rc = Cred(Ks,Rc+Tc+1) S: assert(Rc' == Cred(Ks,Rc+Tc)), Ts = Time() S: Rs' = Cred(Ks,Rs+Tc+1) S->C Rs',Ts C: assert(Rs' == Cred(Ks,Rs+Tc+1)) S: Rs = Rs'
ユーザー: パスワードがPのユーザーUはドメインにログオンしようとする(ワークステーションと ドメインのような付随的なデータは省略される)
C: Tc = Time(), Rc' = Cred(Ks,Rc+Tc) C->S NetLogonSamLogon,Rc',Tc,U,arc4(Ks[0..7,16],16,ntowf(P),16), arc4(Ks[0..7,16],16,lmowf(P),16) S: assert(Rc' == Cred(Ks,Rc+Tc)) assert(passwords match those in SAM) S: Ts = Time()
S->C Cred(Ks,Cred(Ks,Rc+Tc+1)),userinfo(logon script,UID,SIDs,etc) C: assert(Rs == Cred(Ks,Cred(Rc+Tc+1)) C: Rc = Cred(Ks,Rc+Tc+1)
最初のドメインへの参加において、マシンパスワードがよく知られた値であるように、 セッションキーは、ネットワーク上で盗聴している誰にでも、計算することが出来た。 マシンがリブートするまで、パスワードと同等の、NTとLMの一方向関数による暗号化 パスワードを、このセッションキーは使うであろう。このマシンが2回目にリブートする 前にログインする任意のユーザーは、パスワードと同等のものがばれてしまうであろう。 もちろん、新しいマシンのパスワードはとにかくこの時点でばれてしまう。
ログオンスクリプト、プロファイルパスと、SIDのような戻り情報は、TCPチェックサム以外の 何かによって保護されるように見えない。
サーバーのタイムスタンプは無視されるように見える。
クライアントは、それに対する使用法が分からない、SamLogonリクエスト中で ReturnAuthenticatorを送る。しかし、その時間はサーバーによって戻されるタイムスタンプ として使われる。
パスワードのOMFは解読できる暗号でネットワーク上に送信すべきでない。SAM中のowf値を 使う同じ機能でサーバーが計算するARC4(Ks,md4(owf))を使って送るべきである。
SIDとRIDはどこかで詳細に説明がある。
SIDはNTセキュリティID(DOM_SID構造参照)である。これらは以下の形式を取る:
revision-NN-SubAuth1-SubAuth2-SubAuth3... |
revision-0xNNNNNNNNNNNN-SubAuth1-SubAuth2-SubAuth3... |
現在、SIDのリビジョンは1である。 Sub-Authoritiesは相対ID(RID)として知られている。
S-1-0-0
S-1-1-0
S-1-2-0
S-1-3-0
S-1-3-1
S-1-3-2
S-1-3-3
S-1-4
RIDはsub-authorityであり、SIDの一部か、グループRIDの場合は、DOM_GID構造の一部であり、 USER_INFO_1構造ではLSA SAMログオンレスポンスの中である。 A RID is a sub-authority value, as part of either a SID, or in the case of Group RIDs, part of the DOM_GID structure, in the USER_INFO_1 structure, in the LSA SAM Logon response.
Table of Contents
Table of Contents
この文書は、内部的にSambaがどのように動くかについての一般的な概要を説明している。 Samba Teamは、非常に汚いSMBとCIFSプロトコルによって押しつけられる、優雅さ、セキュリティと 制約の間で、最も良い妥協点であるモデルを見いだそうとした。
また、以下のようなよく聞かれる質問のいくつかに答えようともしている:
UNIX上で動かすときにSambaは安全か?xyzプラットフォームでは? root特権についての問題は?
Sambaのいくつかの部分中でのマルチスレッドの賛否
なぜ、名前解決、WINSとブラウジングに関して分離されたプロセスがあるのか?
人々は時々、一様にスレッドを良いものとして推奨する。それらは推奨する人にとっては とても良いものであるが、smbdに取ってはまったく不適当である。nmbdは別の問題で、 マルチスレッドそれ自身はとても良いことである。
手短に言うと、smbdはマルチスレッド化されておらず、UNIX配下での別のサーバー(例えば、 書いている時点においてはSyntax)はこの方法を取っていて、とても大きな性能上の問題を 抱えていて、頑丈ではない。nmbdもスレッド化されていないが、これは、35以上もの プラットフォームにまたがって、コードの整合性を取り、ポータブルにするということが、 不可能だからという理由である(この欠点は同じくsmbdをスレッド化することにも当てはまる)。
より長い期間において、smbdをマルチスレッドにしない、とても良い理由が存在している。 マルチスレッドはSambaをより遅くし、スケーラビリティを減少させ、移植性をなくし、 とても不安定にさせる。実際、各接続に対して独立したプロセスを使用していて、それは Sambaの最も大きな利点の1つである。
スレッド化したsmbdから発生するいくつかの問題は以下の通り:
プロセスの代わりにスレッドを作成するだけでなく、スレッド固有で存在しなければならない 場合、すべての変数について管理を行わなければならない(現在それらはグローバルである)。
もしもある1つのスレッドが異常終了すると(例えば、セグメンテーションフォルト)、 すべてのスレッドが異常終了する。信頼性を損ねることになる。
使用している多くのシステムコールがブロックしている。多くのシステムコールで ブロックしないか同等のことをするものは、用意されていないか、うまく使えない (そして遅い)。そのため、ある1つのスレッドでブロックしている間は、すべての クライアントは待たされることになる。ある共有が遅いNFSファイルシステムで、 その他が早いものだと仮定してみた場合、すべてのクライアントが、NFSのスピードに 抑制されてしまう。
異なったスレッド中で異なったuidとして動作できない。これは、すべてのSMBパケット上で uid/gidをスイッチしなければならないと言うことを意味する。それはとてつもなく遅い。
プロセスあたりのファイルディスクリプタ制限は、制限されたクライアントの数のみを サポートできることを意味する。
プロセスに対してfcntl()のコンテキストでロッキングを行うようにスレッドに対して システムロッキングを使うことが出来ない。
これは理想ではあるが、ポータビリティの要求により実現するのは難しい。
Andrew はANSI-Cの範囲のみを使って(setjmpとlongjmpを使って)nmbd用のテスト用 スレッドライブラリを加工とした。残念なことに、いくつかのOSでは、スタック上の 現在のアドレスよりも浅い位置のアドレスを呼び出すためにlongjmpを制限することに よって、うまくいかなかった(見たところでは、AIXがそうである)。これは、真に ポータブルなスレッドライブラリを不可能にしている。そのため、現在対応している すべてのプラットフォームに対して、nmbdのコードをスレッドあり、なしの両方用意 しなければならず、スレッドの本来の目的はコードをきれいにすることであるが、 それを得られない(スレッドが物事を早くするというのは神話である。スレッドは 再帰のようなものであり、物事をきれいにするが、他の何らかの方法によって、いつでも それはもっと早くすることもできる)。
Chrisは、スレッド対独立したプロセス(対他の方法?)を要約する汎用的なデザインを設計 しようとし、いくつかの汎用APCを通して、それらをアクセス可能にした。これは、 プロトコルによるデータ共有要求(現在のパケットに将来のパケットが依存するなど)という 理由でうまく動かなかった。少なくとも、コードは動いたが、非常にぎこちなく、 その上、fork()タイプモデルは、UNIX上では決して動かなかった(nmbdに対して、 それが動くOSはあるのだろうか?)。
fork()は安っぽいが、受信したすべてのUDPパケット上で処理を行うのに、十分 安っぽいわけではない。プロセスのプールを持つことは可能であるが、プロセス間で、 (複雑な構造体中で)共有された巨大な量のデータのために、プログラムをきれいにする ことは、ひどく難しい。各プラットフォームが共有メモリシステムを持つ事を あてにすることは出来ない。
もともと、Andrewは、途方もなくスタックを使い、全くもってデバッグ作業を混乱させる、 マルチスレッド環境をシミュレートするための再帰を使った。Luke Leightonは各パケット上で ステート情報を保持する問い合わせシステムを使うように書き直した。最初のバージョンは、 すべての待機状態ステートによって使われる単一の構造を使っていた。この構造の初期化は、 引数を追加することで行われ、機能が開発するに従い、だんだん複雑化していった。 そのため、より高位の関数と、ユーザーが定義したメモリブロックを指定するポインターによって 置き換えられた。これは突然物事をより簡単にした:非常に多数の関数は静的に作成され、 モジュール化された。これはNTカーネルで使われているものと同じ原理であり、単一の プロセス中ではあるが、スレッドと同じような効果が成し遂げられた。
次に、Jeremyはnmbdを書き直した。nmbd中のパケットデータはネットワーク上のものではない。 この形式は、処理に対してとても従順なものであるが、まだ他のパケットの内容を保持していた。 nameserv.h中の"struct packet_struct"を参照のこと。そこにはすべての詳細があるが、 ネットワーク上のメッセージについては記述がない。これは、ブラウジングとWINSサポートの ための、ディスクかメモリベースのデータベース中で理想的に使えるようにする。
Table of Contents
デバッグログファイルの文法は以下の通り:
>debugfile< :== { >debugmsg< } >debugmsg< :== >debughdr< '\n' >debugtext< >debughdr< :== '[' TIME ',' LEVEL ']' FILE ':' [FUNCTION] '(' LINE ')' >debugtext< :== { >debugline< } >debugline< :== TEXT '\n'
TEXTは改行文字を含まない文字列である。
LEVEL はメッセージのDEBUGレベルである(0から10の範囲の整数値)。
TIME はタイムスタンプである。
FILE はでバグメッセージが生成されたファイルの名前である。
FUNCTIONはデバッグメッセージが生成された関数である。
LINEはメッセージが生成されたデバッグ文の行番号である。
基本的に、それが意味するところは以下の通り:
デバッグログファイルはデバッグメッセージで構成される。
核でバッグメッセージはヘッダーとテキストから構成される。ヘッダーは改行によってテキストと 分離されている。
ヘッダーはタイムスタンプで始まり、括弧の中にデバッグレベルが続く。メッセージによって 生成されたファイル名、関数名、と行番号がそのあとに続く。ファイル名はコロンで終了し、 関数名は行番号を含む括弧で終了する。コンパイラに依存するが、関数名は無いかもしれない (これは、広く実装はされていない__FUNCTION__マクロによって生成される、dangit)。
メッセージテキストは0かそれ以上の行から構成され、おのおのは改行で終端する。
出力の例:
[1998/08/03 12:55:25, 1] nmbd.c:(659) Netbios nameserver version 1.9.19-prealpha started. Copyright Andrew Tridgell 1994-1997 [1998/08/03 12:55:25, 3] loadparm.c:(763) Initializing global parameters
上記の例において関数名はヘッダー行には表示されていないことに注意。これは、上記の例は SGI Indyによって生成され、SGIコンパイラは__FUNCTION__マクロをサポートしていないという 理由による。
DEBUG()マクロの使用は変わっていない。DEBUG()は2つのパラメーターをとる。 最初のものはメッセージレベルであり、2番目のものはDebug1()関数を呼び出すための 関数呼び出しの実体である。
これは混乱させる。
若干の手助けとなるかもしれない例は以下の通り。もしも
printf( "This is a %s message.\n", "debug" );
と書いて結果を標準出力に送りたいのであれば、 to send the output to stdout, then you would write
DEBUG( 0, ( "This is a %s message.\n", "debug" ) );
と書くことで、結果はデバッグファイルに送られる。すべての通常のprintf() フォーマットのエスケープ処理はちゃんと動作する。
上記の例において、DEBUGメッセージレベルは0に設定されていることに注意。メッセージレベル 0は常時出力される。基本的に、もしもメッセージレベルがグローバル値DEBUGLEVEL以下の場合、 DEBUG文は処理される。
上記の例の出力は以下のようなものになる:
[1998/07/30 16:00:51, 0] file.c:function(128) This is a debug message.
おのおののDEBUG()呼び出しは、以前の呼び出しによって生成された出力の行末が、 "\n"で終わっていないならば、新しいヘッダーを作成する。デバッグファイルへの出力は、 改行が来るたびごとにフラッシュされるフォーマットバッファーを通して渡される。 もしも、DEBUG()が呼ばれたときにバッファーがからでなければ、新しい入力は単に 追加される。
...しかし、これは全くその場しのぎである。DEBUG()が分割された行を書き込むのに 使われると言う理由で、適切な位置に置かれる。以下は今説明したことに関する、 この種の簡単な例である:
DEBUG( 0, ("The test returned " ) ); if( test() ) DEBUG(0, ("True") ); else DEBUG(0, ("False") ); DEBUG(0, (".\n") );
フォーマッティングバッファーなしでは、出力(test()がtrueを返すことを仮定)は以下のようになる:
[1998/07/30 16:00:51, 0] file.c:function(256) The test returned [1998/07/30 16:00:51, 0] file.c:function(258) True [1998/07/30 16:00:51, 0] file.c:function(261) .
これは使い物にはならない。フォーマットバッファーというその場しのぎの方法は、 この問題を解決する。
上記で説明されている行が壊れてしまう問題への、その場しのぎの解決方法への追加として、 もっときれいな解決方法がある。DEBUGADD()マクロは決してヘッダーを生成しない。フォーマット バッファーがからであったとしても、これは現在のデバッグメッセージに対して新しいテキストを 追加する。DEBUGADD()マクロの文法はDEBUG()マクロのものと同じである。
DEBUG( 0, ("This is the first line.\n" ) ); DEBUGADD( 0, ("This is the second line.\nThis is the third line.\n" ) );
これは以下を生成する。
[1998/07/30 16:00:51, 0] file.c:function(512) This is the first line. This is the second line. This is the third line.
DEBUG()マクロにおける問題の1つは、DEBUG()行が長すぎる傾向にあると言うことである。 nmbd_sendannounce.cからの以下の例について考えてみよう:
DEBUG(3,("send_local_master_announcement: type %x for name %s on subnet %s for workgroup %s\n", type, global_myname, subrec->subnet_name, work->work_group));
この問題の1つの解は、DEBUG()とDEBUGADD()を以下のように使って行を分割することである:
DEBUG( 3, ( "send_local_master_announcement: " ) ); DEBUGADD( 3, ( "type %x for name %s ", type, global_myname ) ); DEBUGADD( 3, ( "on subnet %s ", subrec->subnet_name ) ); DEBUGADD( 3, ( "for workgroup %s\n", work->work_group ) );
同様だが、もっとすてきな方法は、DEBUGLVL()マクロを使う方法である。 このマクロは、メッセージレベルがグローバル値DEBUGLEVEL以下の場合に真を返すので:
if( DEBUGLVL( 3 ) ) { dbgtext( "send_local_master_announcement: " ); dbgtext( "type %x for name %s ", type, global_myname ); dbgtext( "on subnet %s ", subrec->subnet_name ); dbgtext( "for workgroup %s\n", work->work_group ); }
と書ける(dbgtext()関数は以下で説明する)。
この方式ではいくつかの利点がある:
値の評価が1回のみ実行される。
変数を、DEBUGLVL()ブロック内でのみ使われるスタックの外側に配置できる。
デバッグ出力にのみ該当する処理はDEBUGLVL()ブロック内に配置できる。
この関数はフォーマットバッファー経由でデバッグファイル(と可能であればsyslog)に デバッグメッセージテキストを出力する。関数はprintf()やDebug1(0のような可変引数を取る。 入力はvslprintf()関数を使ってバッファー中に格納され、format_debug_text()に渡される。 もしもDEBUGLVL()を使っているならば、おそらくdbgtext()を使ってメッセージ本体を出力 しているだろう。
これは、デバッグメッセージヘッダーを出力する関数である。ヘッダーはフォーマットバッファー 経由では処理されない。また、もしも、フォーマットバッファーがからでない場合、dbghdr() の呼び出しは何らの出力も行わないことに注意。より詳細についてはdbghdr()中のコメントを 参照。
この関数は直接呼ばれることはない。DEBUG()とDEBUGADD()によって使われる。
Table of Contents
この章では、Samba3.0以降におけるSamba内部での文字セットの取り扱いについて言及する。
以前のバージョンのSambaは、とてもその場しのぎの文字セット処理を行っていた。点在する コードはDOSのコードページへ、あるいはコードページから特定の文字列を変換するのに、 数多くの呼び出しが使われていた。問題は、特定のchar*がdosのコードページかUNIXの コードページかを確認するすべがないということである。これは、一般的な場合での取り扱いが ない、特定の場合に対応する事を試みる悪夢のようなコードになってしまう。
新しいシステムは以下のように動作する:
Samba内部におけるすべてのchar*文字列は"unix"文字列である。smb.conf中の "unix charset"オプションによって定義される文字セット中にはマルチバイト 文字列が存在する。
unix文字列用の1バイト固定文字セットは存在しないが、使われている任意の文字セットは 以下の性質を必要としている:
行終端を除いて、NULL文字が含まれてはならない
Cの文字列と互換がある7ビット文字でなければならず、そのため、C用の 定数文字列か文字は、選択された文字セット中で同等の文字列とバイト単位で 全く同じである。
文字列を大文字化あるいは小文字化した場合、もはやオリジナルの文字列と 同じではなくなる。
クライアントから送信されてきたすべての文字を扱えねばならない。
例えば、UTF-8は良くできていて、ほとんどのマルチバイトのアジアの文字セットに 対応するが、UCS2は、内部にNULLが含まれているため、UNIX文字列としては使うことが できない。
ネットワーク上に送信しようとするためのバッファー中に文字列を書き込む必要がある場合、 あるいは、クライアントの文字セットと互換のある文字セット形式での文字列を必要とする 場合、pull_またはpush_関数を使う必要がある。pull_関数は送信バッファーから(マルチバイト) UNIX文字列中に変換する。push_関数は文字列を書き込みバッファーに書く。
理解する必要がある2つの主要なpull_とpush_関数は、pull_stringとpush_stringである。 これらの関数は文字列が入っているSMBパケットの開始位置をポイントすべきである、 ベースポインターを取る。関数は、パケットがユニコードパケットとしてマーク されているかを自動的に決定するため、パケット中のフラグフィールドをチェックし、 このフラグに基づいて、文字列に対してユニコードを使うかどうかを決める。また、 STR_UNICODEかSTR_ASCIIを使うことによって、この決定を強制的に行わせてもよい。 smbd/とlibsmb/中での使用のために、適切な最初の引数を付けてpull_/push_関数を 呼び出すclistr_とsrvstr_ラッパー関数がある。
また、特定の文字列がASCIIかユニコードか分かっている場合、pull_ascii/ pull_ucs2かpush_ascii/push_ucs2関数を呼び出しても良い。たとえば pull_ascii_pstring()のような、特定の共通の引数を持つpush_/pull_関数を 呼び出すcharcnv.c中に、数多くの他の便利な関数もある。
覚えておかなければならない最も大きな問題は、Samba中の内部(UNIX)文字列は、 マルチバイト文字を含むかもしれないということである。これは、文字が常時1バイト であることを仮定できないと言うことを意味する。しばしばこれは文字列をucs2に 変換しなければならないということであり、いくつかの(表面上)単純な作業を行うために、 再度逆変換する必要があるということである。どのようにこれを行うかの例は、 strchr_m()関数を見ること。これはとても遅いことが分かっていて、結局、これを 高速化したが、現在、スピードよりも正確さを求めている。
すべてのlp_関数は現在UNIX文字列を返す。パラメーター上の特別な"DOS"フラグはなくなった。
すべてのvfs関数はUNIX文字列を取る。それに渡すときに変換してはならない。
この節では、byteorder.h内で定義されているマクロについて説明する。それらのマクロは Sambaのコード内で広範囲に使われている。
バッファーbuf内で、符号なしshort(16ビット)ビッグエンディアン整数値のオフセット位置を、 valという値に設定する。これは"USHORT"として参照される。
この節では、LANマネージャRPCコールを行わせるのに必要な関数について説明する。 この情報はSambaコードの調査とLANマネージャ 2.0APIのドキュメントに依存している。 これは完全に信頼できるものではない。
call_api(int prcnt, int drcnt, int mprcnt, int mdrcnt, char *param, char *data, char **rparam, char **rdata);
この関数はclient.c内で定義されている。リモートAPIを呼び出すためにSMBトランザクションで 使われる。
パラメーターは以下の通り:
prcnt: the number of bytes of parameters begin sent.
drcnt: the number of bytes of data begin sent.
mprcnt: the maximum number of bytes of parameters which should be returned
mdrcnt: the maximum number of bytes of data which should be returned
param: a pointer to the parameters to be sent.
data: a pointer to the data to be sent.
rparam: a pointer to a pointer which will be set to point to the returned parameters. The caller of call_api() must deallocate this memory.
rdata: a pointer to a pointer which will be set to point to the returned data. The caller of call_api() must deallocate this memory.
パラメーターブロック中で、現れる順番で送らなければならないパラメーターがある:
符号なし16ビット整数値。SSVAL()を使ってこの値を設定すべきである。この数値が どこで説明されているかは分からない。
LANマネージャのドキュメント中で定義されてるようなAPI関数のためのパラメーターを 説明するASCIIZ文字列。最初のパラメーターはサーバー名で、省略されている。この文字列は マニュアル中で説明されているようなAPI関数に基づいていて、実際に渡されるデータではない。
戻されるべきデータ構造を説明するASCIIZ文字列。
関数呼び出し中で現れる任意のパラメーターで、LANマネージャAPIドキュメント中の、 "Server"の後、"uLevel"パラメーターまでを含むように定義されている。 Any parameters which appear in the function call, as defined in the LAN Manager API documentation, after the "Server" and up to and including the "uLevel" parameters.
符号なし16ビット整数値で、戻されるデータ構造体配列を受け取るために使われる バッファーの大きさをバイト単位で与える。これはおそらくmdrcntと同じであるべきである。 この値はSSVAL()を使って設定されるべきである。
戻されるべき副構造体を説明するASCIIZ文字列。もしも副構造体が適用されなければ、 この文字列の長さは0である。
client.c中のコードは常時データなしでcall_api()を呼び出す。0以外の長さを持つデータ バッファーがいつ送られるかは不明である。
戻されるパラメーター(rparamによって指示される)の、現れる順番は以下の通り:
符号なし16ビット整数値で、API関数の戻りコードを含んでいる。この値はSVAL() を使って読まれるべきである。
戻されたデータ中のポインターによる、調整すべき調整量。この値はSVAL()を使って読まれる べきである。基本的に戻されたバッファーの開始アドレスは、それに追加される戻された ポインター値を持ち、戻されたデータバッファー中の現在のオフセットを得るために、それから この値が引かれる。 An adjustment which tells the amount by which pointers in the returned data should be adjusted. This value should be read with SVAL(). Basically, the address of the start of the returned data buffer should have the returned pointer value added to it and then have this value subtracted from it in order to obtain the currect offset into the returned data buffer.
戻された構造体配列中の要素の数。時々、これは戻されたバイト数である可能性もある。
calll_api()から戻ったとき、rparamは戻りパラメーターを指示する。それがある場合、 最初のものは戻り値である。API呼び出しが成功した場合、それは0である。これは、 "SVAL(rparam,0)"として読まれる。
2番目のパラメーターは"SVAL(rparam,2)"として読むことが出来る。戻されたデータバッファーの ベースアドレスがサーバー上で構築された時に何だったかを指示する16ビットオフセットである。 これは使用の前にポインターを正しくするのに使われるべきである。 use.
戻されたデータバッファーには戻されたデータ構造体配列が含まれている。すべてのポインターは 使用する前に調整しなければならないことに注意。client.c中の関数fix_char_ptr()は、 この目的のために使える。
3番目のパラメーター("SVAL(rparam,4)"として読むことが出来る)は、戻されたデータの量を 指示するか、十分なバッファー空間がある場合、戻すことが出来るデータのようなものである。
特定のデータ構造は、コード文字を含むASCIIZ文字列を使って記述される。以下は コード文字である:
W バイト単位のリトルエンディアン符号なし整数
N そのあとに続く副構造体の数
D 4バイトのリトルエンディアン符号なし整数
B バイト(そのあとに続くASCII数値として表現された、任意のカウントが続く)
z 4バイトのNULLで終端する文字列へのオフセット
l 4バイトの非文字列ユーザーデータへのオフセット
b データへのオフセット(そのあとに、ASCII数値として表現されるカウントが続く)
r 戻されるデータバッファーへのポインター???
L 戻されるデータバッファーのバイト長???
h 有効な情報のバイト数???
Sambaにコードを追加したい場合は...
Sambaのためのコードを書こうとしているプログラマーが直面する難題の一つは、プロジェクト中で
最も活躍している人によって使われているコーディングの習慣を理解することである。それらの
習慣はほとんど説明がなく、ポータビリティ、安定性、あるいはコードの一貫性などを
改善するのに役立っている。このドキュメントは、現時点での、Sambaプロジェクト上で
使われている、重要なコーディングの慣習のいくつかについて説明しようと試みている。
コーディングの慣習は長い期間には少し変わり、わかりにくい移植性の考慮点について
より多く学習するように、進歩していく。すでにある
samba/source/internals.doc
と
samba/source/architecture.doc
というドキュメントは補足情報を
提供する。
コーディングスタイルに付いて、おおざっぱに関連した質問は、非常に個人的で、 このドキュメントはそのような主題について言及はしないが、Sambaソース中では 8桁ごとのタブが好まれるように見受けられることは言っておこう。もしも、コーディング スタイルについてのトピックに興味があるのであれば、2つの、しばしば引用される 文書は以下の通りである:
http://lxr.linux.no/source/Documentation/CodingStyle
http://www.fsf.org/prep/standards_toc.html
しかし、Samba内でのコーディングスタイルはコードを投稿する多くの異なったプログラマーが いるという理由で変化に富んでいることに注意。
以下は、Sambaに新しいコードを追加するときに使うべきいくつかの考慮点である。 まず最初に、そして、最も重要な、覚えておいてほしい点は以下の通り:
移植性は、デ・ファクトで、存在する、実際の世界でのCIFS/SMB実装のような、 ネットワークの互換性が、関数を追加するときに一番考慮することである。 Sambaがコンパイルできるプラットフォームは多数あり、存在するSambaコード中で 起動されないライブラリ関数への呼び出しを追加するときには注意すること。 また、数多くの異なった、SambaがサポートしようとするSMB/CIFSクライアントがあり、 SNIA CIFS技術参照(あるいは最新のMicrosoftリファレンス文書あるいは SMB標準についてのX/Open書籍)を、すべてが完全に準拠していないことにも注意。
以下はその他の助言である:
テキスト表示のために、printfの代わりにd_printfを使う 理由:翻訳された言語のテキストへの置き換えを有効にする。
freeの代わりに SAFE_FREEを使う 理由: nullポインターをトラップすることを減らす。
bzeroを使わないで、memsetあるいはZERO_STRUCT と ZERO_STRUCTPマクロを使う 理由: これは POSIX ではない
strcpyとstrlenを使わない(safe_*という同等品を使う) 理由:バッファーオーバーランによるトラップを防ぐため
getopt_longを使わないでその代わりにpopt関数を使う 理由:互換性
明示的に、parmが入力のみの場合、関数中に渡すparmはconst識別子を追加する (若干議論があるところではあるが、constは#definedで処理できる)
argとしてva_listに渡すときか、あるものを他に割り当てるときには、 VA_COPY()マクロを使うこと 理由:いくつかのプラットフォーム上では、va_listは、各関数中で初期化しなければならない 構造体であり、そうでない場合にはセグメンテーションフォルトになり得る。
スレッドを使わない 理由:移植性(archtecture.docを参照)
Cファイル中の新しいヘッダーを明示的にインクルードしない - 新しいヘッダーファイルは include.hに1回だけ追加することによりインクルードすべきである。 理由:一貫性
明示的に関数をexternしない(それらはproto.h内に"make proto"によって自動生成される) 理由:一貫性
SMBをアンパックするときにはエンディアン依存性がない安全なマクロを使う (byteorder.hとinternals.docを参照) 理由:すべての人がIntelチップを使うとは限らない
文字セットハンドリングのユニコード実装に注意(internals.docを参照)。 pull_* と push_* と convert_string を参照。 理由:国際化
英語のみであることを仮定しないこと 理由:同上
in/outパラメーターを使わないようにする(関数は、戻りデータに対し、 入力パラメーターを上書きする) 理由:安定性の問題を引き起こすため
著作権記述が正しいかを確認し、Tridgeが書いていないコードにその名前を追加しないこと。 もしもコードを書いていないのであれば、GPLなSambaのコードの残りと共存できるように すること。
バイトデータによって指定された長さのためのDATA_BLOBの使用を考えること。 理由:安定性
関数のようにデータベース用のtdbを利用すること。 理由:一貫性
直接SAM_ACCOUNT構造体にアクセスしないこと。pdb_get...() と pdb_set...()関数経由で それにアクセスすべきである。 理由:安定性、一貫性
passdbに対してパスワードを直接チェックせず、常時check_password()インタフェースを 使うこと。 理由:長期間における互換性
可能なところでは、asprintfの代わりにpstringsとfstringsを使うことを試みること。
//のような C++コメントの代わりに通常のCのコメント / * を使うこと。C++のコメント形式が C99の標準の一部だとしても、いくつかの古いCコンパイラはそれを受け付けない。
コードの要点を説明するAPI関数と構造体、使用法と特別な条件や結果についての ドキュメントを書くようにすること。2つの*印 / ** で始まるコメントでそれらを マークすると、このファイル中からDoxygenによって拾い出せるように出来る。
範囲を小さくするように心がけること。これは、可能なときにはいつでも関数/変数を 静的にさせるということになる。固有の名前空間を汚染させることは望んでいない。 各モジュールは、外部から参照できる関数や変数を最小限にすべきである。
一箇所に隔離された特定のコードの範囲を指示するために、関数ポインターを使うこと。 特定の1まとまりの機能を数多くの場所に分散してしまうことを望んでいない - これは、障害を引き起こしやすくコードのメンテナンスが難しくなるからである。 その代わり、インタフェースを設計し、特定の機能の実装への関数ポインターを含む テーブルを使う。これはコマンドインタプリタのためには特に重要である。
他の誰かがコードを追加することと、そのコードを維持することのようなことが 何であるかについて注意深く考えること。
上記のような助言は単純であるが、その情報は新しいコード上でされる型どおりのやり直しを 手助けするかもしれない。前述の一覧は新しいサポートルーチンとして定期的に変更され、 マクロが追加される事が期待される。
以下は、Sambaのソースコードを変更することとそれをSambaのメインブランチに 組み込むことに興味がある場合に便利かもしれない、いくつかのTIPSと注意事項である。
Sambaにコードを寄贈するために、最新のソースを得ておく必要がある。 Samba HOWTOコレクションの付録に記載されているGIT(訳注:オリジナルはCVS) からソースコードを検索する。
大量の変更を行うときには、Sambaチームのメンバーと相談してほしい。 Sambaコードのある部分は、1人またはそれ以上の'担当' - すなわち 大半のコードを書き維持しているSamba開発者がいる。
この方法は余計な時間と、他の誰かが同じ事を作業しているか、 作成した実装が正しいものではないという理由で、Sambaのメインブランチに 入れられない、何らかの作業を行うのを防ぐことが出来る。
Sambaツリーへのパッチはunified diff形式にすべきである。
たとえば、diff -u
によって生成されたファイルである。
もしもCVSから検索したSambaのコピーを修正しているならば、
cvs diff -u
を実行することによりそれらの変更の
差分を生成することが簡単にできる。
他の場所から単純にコピーして、動くまで、 それを変更してはならない。コードはきれいで論理的である必要がある。 重複したコードは拒否される。
パッチをテストすること。提供されたパッチを、Samba Teamの 誰かが評価する前にしばらく時間がかかるかもしれないので、 提供されたパッチが、再びレビューサイクルを通過する事が必要な時 より前に長い時間がかかるかもしれない。
1つの大きなdiffファイル中に分離されたパッチを入れないこと。 これは、読解とテストが困難になる。誰かが問題を抱えているものと混合する という理由で、コミットされた良いパッチを得られないというリスクに なるかもしれない。
coding-suggestionsの章で推奨されているような Sambaのコーディング形式に従うようにすること。
Sambaにおける、バグに対するバグ修正は、バグの説明の所 にあるような、Sambaの バグジラシステム を使って投稿すべきである。
新規機能のパッチが何をするかについての説明と共に、パッチを Samba-technicalメーリングリスト に投稿し、可能であれば、変更するコードの部分の、 Sambaチームメンバーの'担当'(のだれか)にも送ること。 Sambaチームメンバーは常時忙しいので、皆が、'他の誰かの1人にそれを処理させる' 傾向がある。もしも1週間経っても誰も反応しないのであれば、Sambaチーム のだれかから反応があるまで、再送すること。
Sambaチームの誰かはパッチを確認し、コミットするか、なぜそうしなかったかについての コメントを出すだろう。後者の場合、パッチを修正し、パッチが 受け付けられるまで再度提出することが出来る。
新しいモジュールシステムは以下の利点を有する:
透過的な、静的および動的モジュールのローディング(サブシステムがモジュールについて 知る必要がない) |
構成時に、静的および動的モジュールを簡単に選べる |
安定モジューのパフォーマンスを向上するための"preload modules"オプション |
ややこしい#define stuff anymoreが不要 |
すべてのバックエンドがプラグインとして提供されている(pdb_ldapとpdp_tdbを含む) |
Samba内のいくつかのサブシステムは、異なったバックエンドを使う。それらのバックエンドは、 Sambaに静的にリンクするか、プラグインとして提供されているかのどちらかである。 サブシステムは、モジュールそれ自身を登録する事を許可する関数をもっている。例えば、 passdbサブシステムでは以下の通り:
NTSTATUS smb_register_passdb(int version, const char *name, pdb_init_function init);
この関数は、モジュールそれ自身を登録するために初期化関数によって呼ばれる。
モジュールシステムは、各サブシステムの静的モジュールのための初期化関数の一覧に従う。
例えば、(include/config.h
から)現在は以下のようになっている:
/* Static init functions */ #define static_init_pdb { pdb_mysql_init(); pdb_ldap_init(); pdb_smbpasswd_init(); pdb_tdbsam_init(); pdb_guest_init();}
これらの関数はサブシステムが使われる前に呼ばれるべきである。それは、 サブシステムが初期化されるか、最初に使われるときに終了すべきである。
サブシステムが特定のバックエンドを必要とする場合、それがすでに登録されているかを 調べるべきである。もしもバックエンドがまだ登録されていなかった場合、サブシステムは、 smb_probe_module(char *subsystem, char *backend)を呼び出すべきである。 この関数は、指定されたパス($LIBDIR/subsystem/backend.so)から正しいモジュールを ロードしようとする。もしも'backend'の最初の文字がスラッシュの場合には、 smb_probe_module()は'backend'で指定された絶対パスからモジュールをロードしようとする。
smb_probe_module()の実行後、サブシステムは、モジュールがすでに登録されたか どうかをチェックすべきである。
各モジュールは初期化関数をもっている。Sambaに含まれるモジュールでは、
この名前は
'subsystem
_backend
_init'
である。(決して組み込まれないが、モジュールとしてのみ提供される)外部モジュールでは、
この名前は常時init_module'である(モジュールがSambaに含まれる場合、
configureシステムは#define subsystem_backend_init() init_module()を追加する)。
これらの関数のプロトタイプは以下の通り:
NTSTATUS init_module(void);
この関数は1回またはそれ以上登録関数を呼び出すべきである。関数は 成功時にはNT_STATUS_OKか、NT_STATUS_UNSUCCESSFULか、失敗時に便利に使える nt error codeを返すべきである。
たとえばpdb_ldap_init()は以下を含む:
NTSTATUS pdb_ldap_init(void) { smb_register_passdb(PASSDB_INTERFACE_VERSION, "ldapsam", pdb_init_ldapsam); smb_register_passdb(PASSDB_INTERFACE_VERSION, "ldapsam_nua", pdb_init_ldapsam_nua); return NT_STATUS_OK; }
configure.in中のいくつかのマクロは、システムが正しく動くために必要な、種々の define文とsubstを生成する。既定値によって構築されるべきすべてのモジュールは、 既定値で'default_modules'変数に追加されなければならない。例えば、もしもldap が見つかった場合、pdb_ldapはこの変数に追加される。
condigure.inの最下部に、各モジュールのためにSMB_MODULE()が、各サブシステム用に SMB_SUBSYSTEM()が呼ばれなければならない。
文法:
SMB_MODULE(subsystem
_backend
,object files
,plugin name
,subsystem name
,static_action
,shared_action
) SMB_SUBSYSTEM(subsystem
,depfile
)
特定のサブシステム用のdepfileは、モジュール中に静的に構築されるための 初期化モジュールを呼び出すファイルである。
Makefile.in中の@SUBSYSTEM_MODULES@
は構築されるプラグインの名前に置き換わる。
すべての.cファイルに対し、'modules_clean'というmakeのターゲット中に./configureが
再構築されることで変更できるdefineを含むようにしておくこと。実際に、これは、
static_init_subsystem;
コールを含むすべてのcファイルは、
再構築する必要がある。
現在、configure.in中に、SMB_MODULE_PROVIVES()と呼ばれるコマンドがある。 これは複数のものを登録するモジュールのために使われる。これは将来なくなる予定 なので、調査(probe)のために使うべきではない。
Table of Contents
この文書は、どのようにSamba3.0用の新しいRPC着脱可能なモジュール機能を使えるように するかについて説明している。このアーキテクチャは、メインのCVSブランチから分かれて 作業されることをRPCパイプについて許可しているSambaの維持管理性を向上させることを 付加する。RPMアーキテクチャはまた、サードパーティベンダに対して、プラグインを通して、 Sambaに対して機能を追加する事も認めている。
RPC呼び出しがsmbdに送られる時、smbdは、もしもその呼び出しを内部的に処理する方法が
無い場合、それを処理するための、librpc_<pipename>.so
という名前の共有ライブラリをロードしようとする。例えば、LSA呼び出しは
librpc_lsass.so
によって処理される。これらの共有ライブラリは、
<sambaroot>/lib/rpc
中に配置されるべきである。
smbdは次に共有ライブラリ内のinit_module関数を呼び出そうとする。詳細はモジュールの
章を参照のこと。
init_module関数内で、ライブラリはrpc_pipe_register_commands()を呼び出すべきである。 この関数は以下の引数を取る:
NTSTATUS rpc_pipe_register_commands(int version, const char *clnt, const char *srv, const struct api_struct *cmds, int size);
RPCインタフェースのバージョン番号。この引数に対しては、 SMB_RPC_INTERFACE_VERSIONという定義を使うこと。
名前付きパイプのクライアント名
名前付きパイプのサーバー名
RPC ordinal番号を関数呼び出しにマップするapi構造体のリスト
cmd中に含まれるapi_structsの数
どのようにこのライブラリを使うかについての小さな例については、 rpc_server/srv_reg.c と rpc_server/srv_reg_nt.cを参照。
Table of Contents
Samba開発のほとんどが、POSIX互換なOSを使っている間、 NTファイルシステムの文法を適用する場合に、POSIXによって要求されるものよりも、 ファイルシステムに対してのもののほうがより明確である。Samba 2.2以降、すべての 操作に関連するファイルシステムは、NTFS文法を変換するために必要とされる追加の関数と、 POSIXの両方でモデル化された後の仮想ファイルシステム(VFS)のための、概要レイヤ を通して行われる。
この概要レイヤは、現在通常のPOSIXファイルシステムが持っているものよりもたくさんの機能を 提供している。それらすべてが使用している特定のファイルシステムに対して実装すべきであるという ことは要求されない。しかし、それらの機能が有効な時、SambaはCIFSクライアントに対して それを公告し、それらは、それらの機能に遭遇する時、更に多くの追加機能を期待する ことを予測するかもしれないという事を意味するWindowsクライアントの場合、とアプリケーション によって使われるかもしれない。これらには、実行時にVFSモジュールを動的にロードするための 基盤を提供することにより処理が出来ることとSambaのコアを変更なしに、この降雪を扱うことを 許可するための実際的な理由がある。 This abstraction layer now provides more features than a regular POSIX file system could fill in. It is not required that all of them should be implemented by your particular file system. However, when those features are available, Samba would advertize them to a CIFS client and they might be used by an application and in case of Windows client that might mean a client expects even more additional functionality when it encounters those features. There is a practical reason to allow handling of this snowfall without modifying the Samba core and it is fulfilled by providing an infrastructure to dynamically load VFS modules at run time.
各VFSモジュールは数多くのVFS動作を実装できる。それがそれを行う方法とは 無関係であり、たった2つの事が実際には重要である:特定の実装が他のモジュールの 実装と協調することを必要としているか否かかということと、モジュールが、それが動作中の 文脈で特有である追加情報を格納することを必要とするか否かということである。 複数のVFSモジュールは同じ時にロードでき、異なったパラメーターで同じVFSモジュールの、 いくつかのインスタンスをロードすることも可能である。
VFSモジュールには3つの主要なコンポーネントがある:
初期化関数 これは、実装された操作を 登録するためにモジュールをロードする間に呼ばれる。
オペレーションテーブル 静的に定義されたモジュール関数とVFSレイヤ操作の間でのマッピングを提供する。
モジュール関数 これは実際の操作を行う。
この構造がVFSサブシステムに対して最初に適用された間、ローダブルモジュールを サポートするすべてのSamba3サブシステムにまたがって、現在共通的に使われる。実際、ある1つの モジュールは異なったオペレーションテーブルを分離された 初期化関数を通して公開することで、異なったサブシステムに対しての 数多くのインタフェースを提供できる。
初期化関数は、Samba動作時にモジュールを登録するのに使われる。 Sambaの内部構造体とAPIは、日時が経つにつれ変化し、各リリースバージョンは、VFSの開発作業の ために増加したVFSインタフェースバージョンを持つか、任意のSambaの構造体が、バイナリ 非互換の方式で変更される。VFSモジュールがコンパイルされるとき、Samba環境のVFS インタフェースバージョンはモジュールのバイナリオブジェクト中に埋め込まれ、それは モジュールのロード時にSambaコアによってチェックされる。もしも、モジュールによって 通知されるVFSインタフェース番号が、Sambaコアが認識するものと一致しない場合、バージョンの 不一致が検出され、(変更された)Samba構造体をアクセスするときに、メモリ破壊の可能性を 防ぐために、モジュールはロードされない。
それ故、初期化関数はVFS登録関数 smb_register_vfs()
に
3つのパラメーターを渡す。
インタフェースバージョン番号,
SMB_VFS_INTERFACE_VERSION
という定数として,
モジュール名, Sambaコアが認識する、そして
オペレーションテーブル。
オペレーションテーブルは、モジュール内のどの関数が、特定の VFS操作に対応しているかと、どのようにそれらの関数を、残りのVFSサブシステムと協調 させるかを定義する。各オペレーションは以下の方法で実行できる:
transparent,これは、オペレーションが
上書きされている間、その固有の動作の前後に、モジュールは引き続き以前の
処理を呼び出すだろう。このモードは
SMB_VFS_LAYER_TRANSPARENT
という定数によって指示される;
opaque, 一連の動作を終了させている処理用。
たとえば、非POSIXファイルシステム、あるいは、たとえば個人向け
オーディオコレクションのためのデータベースのような、全くファイルシステムでないもの
上でのPOSIX動作を実装するのに使われる。このモードでは、定数
SMB_VFS_LAYER_OPAQUE
を使う。
splitter, ある種のファイルシステム動作が、
以前の実行を透過的に呼び出すために追加される中で、終了する時の方法。これは通常、
それを呼び出す側に戻す前に、その呼び出しの結果をマングリングすることを伴う。
このモードは定数SMB_VFS_LAYER_SPLITTER
を選択する。
loggerは何も変更せず、何らの追加VFS操作も行わない。
loggerモジュールが動作するとき、動作についての情報が
外部機能(あるいはSambaの固有のデバッグツール)を使ってどこかに記録されるが、
それはVFSレイヤではない。このタイプの動作を記述するためには
SMB_VFS_LAYER_LOGGER
定数を使う。
正反対に、scannerモジュールは、システムに渡される
データを処理している間、他のVFS操作を呼び出す。このタイプの操作は、定数
SMB_VFS_LAYER_SCANNER
によって指示される。
基本的に、transparent, opaqueと loggerという3つのタイプがある。Splitterと scannerは、開発者を混乱させるかもしれない(そして、我々の 経験が示すように、彼らは全く混乱している)が、この分割はモジュールの動作の性質を よりよく提示するはずである。これまでの所、開発されたほとんどのモジュールは、 transparentを伴いopaqueが一般的な、3つのタイプのどれかである。 Most of modules developed so far are either one of those three fundamental types with transparent and opaque being prevalent.
各VFS操作は、操作の呼び出しを簡単にさせるための、vfs_op_type、関数ポインターと
vfs_ops構造体中のハンドルポインターと、treeマクロを持つ(
include/vfs.h
と
include/vfs_macros.h
を参照のこと)。
typedef enum _vfs_op_type { SMB_VFS_OP_NOOP = -1, ... /* File operations */ SMB_VFS_OP_OPEN, SMB_VFS_OP_CLOSE, SMB_VFS_OP_READ, SMB_VFS_OP_WRITE, SMB_VFS_OP_LSEEK, SMB_VFS_OP_SENDFILE, ... SMB_VFS_OP_LAST } vfs_op_type;
この構造体は関数とすべての操作のためのハンドルポインターを含む。
struct vfs_ops { struct vfs_fn_pointers { ... /* File operations */ int (*open)(struct vfs_handle_struct *handle, struct connection_struct *conn, const char *fname, int flags, mode_t mode); int (*close)(struct vfs_handle_struct *handle, struct files_struct *fsp, int fd); ssize_t (*read)(struct vfs_handle_struct *handle, struct files_struct *fsp, int fd, void *data, size_t n); ssize_t (*write)(struct vfs_handle_struct *handle, struct files_struct *fsp, int fd, const void *data, size_t n); SMB_OFF_T (*lseek)(struct vfs_handle_struct *handle, struct files_struct *fsp, int fd, SMB_OFF_T offset, int whence); ssize_t (*sendfile)(struct vfs_handle_struct *handle, int tofd, files_struct *fsp, int fromfd, const DATA_BLOB *header, SMB_OFF_T offset, size_t count); ... } ops; struct vfs_handles_pointers { ... /* File operations */ struct vfs_handle_struct *open; struct vfs_handle_struct *close; struct vfs_handle_struct *read; struct vfs_handle_struct *write; struct vfs_handle_struct *lseek; struct vfs_handle_struct *sendfile; ... } handles; };
このマクロは任意のvfs操作を呼び出すために*使われるべきである*。 直接conn->vfs.ops.*を呼び出しては*いけない!!!*
... /* File operations */ #define SMB_VFS_OPEN(conn, fname, flags, mode) \ ((conn)->vfs.ops.open((conn)->vfs.handles.open,\ (conn), (fname), (flags), (mode))) #define SMB_VFS_CLOSE(fsp, fd) \ ((fsp)->conn->vfs.ops.close(\ (fsp)->conn->vfs.handles.close, (fsp), (fd))) #define SMB_VFS_READ(fsp, fd, data, n) \ ((fsp)->conn->vfs.ops.read(\ (fsp)->conn->vfs.handles.read,\ (fsp), (fd), (data), (n))) #define SMB_VFS_WRITE(fsp, fd, data, n) \ ((fsp)->conn->vfs.ops.write(\ (fsp)->conn->vfs.handles.write,\ (fsp), (fd), (data), (n))) #define SMB_VFS_LSEEK(fsp, fd, offset, whence) \ ((fsp)->conn->vfs.ops.lseek(\ (fsp)->conn->vfs.handles.lseek,\ (fsp), (fd), (offset), (whence))) #define SMB_VFS_SENDFILE(tofd, fsp, fromfd, header, offset, count) \ ((fsp)->conn->vfs.ops.sendfile(\ (fsp)->conn->vfs.handles.sendfile,\ (tofd), (fsp), (fromfd), (header), (offset), (count))) ...
これらの値は、複数のVFSモジュールとの接続用に、conn->vfsとconn->vfs_opaque構造体を 構築するときに、VFSサブシステムによって使われる。内部的にSambaはこのプロセスで、opaqueと transparentレイヤのみを区別する。他のタイプは、よりよい診断機能を提供するために使われる。
ほとんどのモジュールはtransparentレイヤを提供する。opaqueレイヤは、実際のファイルシステム コール(DBベースのVFSのような)を実装するモジュール用である。例えば、既定値の、Sambaに 組み込まれているPOSIX VFSはopaque VFSモジュールである。
他のレイヤタイプ(logger, splitter, scanner)は異なったtransparencyの段階を提供するためと、 診断VFSモジュールの動作のために設計された。
各モジュールは、1つのレイヤは1つの操作毎に使われる、同じ時に提供されるいくつかの レイヤを実装できる。
typedef enum _vfs_op_layer { SMB_VFS_LAYER_NOOP = -1, /* - For using in VFS module to indicate end of array */ /* of operations description */ SMB_VFS_LAYER_OPAQUE = 0, /* - Final level, does not call anything beyond itself */ SMB_VFS_LAYER_TRANSPARENT, /* - Normal operation, calls underlying layer after */ /* possibly changing passed data */ SMB_VFS_LAYER_LOGGER, /* - Logs data, calls underlying layer, logging may not */ /* use Samba VFS */ SMB_VFS_LAYER_SPLITTER, /* - Splits operation, calls underlying layer _and_ own facility, */ /* then combines result */ SMB_VFS_LAYER_SCANNER /* - Checks data and possibly initiates additional */ /* file activity like logging to files _inside_ samba VFS */ } vfs_op_layer;
各SambaモジュールはVFSモジュールで、それがSambaに静的にリンクされるならば、
NTSTATUS vfs_example_init(void);
関数か、 共有モジュールならば、
NTSTATUS init_module(void);
関数を持つべきである。
これは、モジュール内部の静的でない関数のみがそうすべきである。グローバル 変数は同様に静的であるべきである!
モジュールはその関数を
NTSTATUS smb_register_vfs(int version, const char *name, vfs_op_tuple *vfs_op_tuples);
関数経由で登録すべきである。
はSMB_VFS_INTERFACE_VERSIONで設定されるべきである。
これは、このモジュールを使うために、vfs objects
パラメーター中で一覧表示する事が出来る名前である。
これは、vfs_op_tupleの配列である(vfs_op_tuplesは、詳細が以下で説明される)。
モジュールが提供することを望む各操作のために、vfs_op_tuple入れつん中にエントリが ある。
typedef struct _vfs_op_tuple { void* op; vfs_op_type type; vfs_op_layer layer; } vfs_op_tuple;
指定した関数への関数ポインター。
どの操作を関数が提供するかを指定するための、関数のvfs_op_type。
どこで関数が操作するかのvfs_op_layer。
簡単な例:
static vfs_op_tuple example_op_tuples[] = { {SMB_VFS_OP(example_connect), SMB_VFS_OP_CONNECT, SMB_VFS_LAYER_TRANSPARENT}, {SMB_VFS_OP(example_disconnect), SMB_VFS_OP_DISCONNECT, SMB_VFS_LAYER_TRANSPARENT}, {SMB_VFS_OP(example_rename), SMB_VFS_OP_RENAME, SMB_VFS_LAYER_OPAQUE}, /* This indicates the end of the array */ {SMB_VFS_OP(NULL), SMB_VFS_OP_NOOP, SMB_VFS_LAYER_NOOP} }; NTSTATUS init_module(void) { return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "example", example_op_tuples); }
各VFS関数の最初のパラメーターは、vfs_handle_structモジュールへのポインターである。
typedef struct vfs_handle_struct { struct vfs_handle_struct *next, *prev; const char *param; struct vfs_ops vfs_next; struct connection_struct *conn; void *data; void (*free_data)(void **data); } vfs_handle_struct;
これはvfs objects
パラメーター中で指定されるモジュール
パラメーターである。
すなわち、'vfs objects = example:test'用パラメーターは、"test"であろう。
このvfs_ops構造体には、次のモジュール操作を呼び出すための情報が含まれている。 次のモジュール操作を呼ぶためにSMB_VFS_NEXT_*マクロを使い、直接handle->vfs_next.ops.*に アクセスしないこと!
これは、ハンドルが帰属するconnection_structに戻るためのポインターである。
モジュール固有データを保持するためのポインターである。 handle->conn->mem_ctx TALLOC_CTX上で接続が有効な間、データをallocできる。 しかし、自分自身でメモリ管理を行う必要もある。
これは、モジュール固有データをフリーにする関数へのポインターである。 TALLOC_CTX handle->conn->mem_ctx上で固有データをtallocした場合、この関数ポインターは NULLに設定することが出来る。
固有データを扱うための、いくつかの有益なマクロ。
#define SMB_VFS_HANDLE_GET_DATA(handle, datap, type, ret) { \ if (!(handle)||((datap=(type *)(handle)->data)==NULL)) { \ DEBUG(0,("%s() failed to get vfs_handle->data!\n",FUNCTION_MACRO)); \ ret; \ } \ } #define SMB_VFS_HANDLE_SET_DATA(handle, datap, free_fn, type, ret) { \ if (!(handle)) { \ DEBUG(0,("%s() failed to set handle->data!\n",FUNCTION_MACRO)); \ ret; \ } else { \ if ((handle)->free_data) { \ (handle)->free_data(&(handle)->data); \ } \ (handle)->data = (void *)datap; \ (handle)->free_data = free_fn; \ } \ } #define SMB_VFS_HANDLE_FREE_DATA(handle) { \ if ((handle) && (handle)->free_data) { \ (handle)->free_data(&(handle)->data); \ } \ }
SMB_VFS_LAYER_TRANSPARENT関数がSMB_VFS_LAYER_OPAQUE関数を呼び出せる方式。
これを行う最も簡単な方法は、SMB_VFS_OPAQUE_*マクロを使うことである。
... /* File operations */ #define SMB_VFS_OPAQUE_OPEN(conn, fname, flags, mode) \ ((conn)->vfs_opaque.ops.open(\ (conn)->vfs_opaque.handles.open,\ (conn), (fname), (flags), (mode))) #define SMB_VFS_OPAQUE_CLOSE(fsp, fd) \ ((fsp)->conn->vfs_opaque.ops.close(\ (fsp)->conn->vfs_opaque.handles.close,\ (fsp), (fd))) #define SMB_VFS_OPAQUE_READ(fsp, fd, data, n) \ ((fsp)->conn->vfs_opaque.ops.read(\ (fsp)->conn->vfs_opaque.handles.read,\ (fsp), (fd), (data), (n))) #define SMB_VFS_OPAQUE_WRITE(fsp, fd, data, n) \ ((fsp)->conn->vfs_opaque.ops.write(\ (fsp)->conn->vfs_opaque.handles.write,\ (fsp), (fd), (data), (n))) #define SMB_VFS_OPAQUE_LSEEK(fsp, fd, offset, whence) \ ((fsp)->conn->vfs_opaque.ops.lseek(\ (fsp)->conn->vfs_opaque.handles.lseek,\ (fsp), (fd), (offset), (whence))) #define SMB_VFS_OPAQUE_SENDFILE(tofd, fsp, fromfd, header, offset, count) \ ((fsp)->conn->vfs_opaque.ops.sendfile(\ (fsp)->conn->vfs_opaque.handles.sendfile,\ (tofd), (fsp), (fromfd), (header), (offset), (count))) ...
SMB_VFS_LAYER_TRANSPARENT関数が次のモジュール関数を呼び出せる方式。
これを行う最も簡単な方法は、SMB_VFS_NEXT_*マクロを使うことである。
... /* File operations */ #define SMB_VFS_NEXT_OPEN(handle, conn, fname, flags, mode) \ ((handle)->vfs_next.ops.open(\ (handle)->vfs_next.handles.open,\ (conn), (fname), (flags), (mode))) #define SMB_VFS_NEXT_CLOSE(handle, fsp, fd) \ ((handle)->vfs_next.ops.close(\ (handle)->vfs_next.handles.close,\ (fsp), (fd))) #define SMB_VFS_NEXT_READ(handle, fsp, fd, data, n) \ ((handle)->vfs_next.ops.read(\ (handle)->vfs_next.handles.read,\ (fsp), (fd), (data), (n))) #define SMB_VFS_NEXT_WRITE(handle, fsp, fd, data, n) \ ((handle)->vfs_next.ops.write(\ (handle)->vfs_next.handles.write,\ (fsp), (fd), (data), (n))) #define SMB_VFS_NEXT_LSEEK(handle, fsp, fd, offset, whence) \ ((handle)->vfs_next.ops.lseek(\ (handle)->vfs_next.handles.lseek,\ (fsp), (fd), (offset), (whence))) #define SMB_VFS_NEXT_SENDFILE(handle, tofd, fsp, fromfd, header, offset, count) \ ((handle)->vfs_next.ops.sendfile(\ (handle)->vfs_next.handles.sendfile,\ (tofd), (fsp), (fromfd), (header), (offset), (count))) ...
すべてのvfs操作関数に最初のパラメーターとして"vfs_handle_struct *handle, " を追加する。例えば、 example_connect(connection_struct *conn, const char *service, const char *user); -> example_connect(vfs_handle_struct *handle, connection_struct *conn, const char *service, const char *user);
"default_vfs_ops."を"smb_vfs_next_"で置き換える。たとえば、 default_vfs_ops.connect(conn, service, user); -> smb_vfs_next_connect(conn, service, user);
すべての"smb_vfs_next_*"関数を大文字化する。たとえば、 e.g. smb_vfs_next_connect(conn, service, user); -> SMB_VFS_NEXT_CONNECT(conn, service, user);
すべてのSMB_VFS_NEXT_*()呼び出しに対して最初のパラメーターとして"handle, "を 追加する。たとえば、 SMB_VFS_NEXT_CONNECT(conn, service, user); -> SMB_VFS_NEXT_CONNECT(handle, conn, service, user);
(2.2.*モジュール用のみ) 古いstruct vfs_ops example_opsをvfs_op_tuple example_op_tuples[]配列に変換する。たとえば、
struct vfs_ops example_ops = { /* Disk operations */ example_connect, /* connect */ example_disconnect, /* disconnect */ NULL, /* disk free * /* Directory operations */ NULL, /* opendir */ NULL, /* readdir */ NULL, /* mkdir */ NULL, /* rmdir */ NULL, /* closedir */ /* File operations */ NULL, /* open */ NULL, /* close */ NULL, /* read */ NULL, /* write */ NULL, /* lseek */ NULL, /* sendfile */ NULL, /* rename */ NULL, /* fsync */ example_stat, /* stat */ example_fstat, /* fstat */ example_lstat, /* lstat */ NULL, /* unlink */ NULL, /* chmod */ NULL, /* fchmod */ NULL, /* chown */ NULL, /* fchown */ NULL, /* chdir */ NULL, /* getwd */ NULL, /* utime */ NULL, /* ftruncate */ NULL, /* lock */ NULL, /* symlink */ NULL, /* readlink */ NULL, /* link */ NULL, /* mknod */ NULL, /* realpath */ NULL, /* fget_nt_acl */ NULL, /* get_nt_acl */ NULL, /* fset_nt_acl */ NULL, /* set_nt_acl */ NULL, /* chmod_acl */ NULL, /* fchmod_acl */ NULL, /* sys_acl_get_entry */ NULL, /* sys_acl_get_tag_type */ NULL, /* sys_acl_get_permset */ NULL, /* sys_acl_get_qualifier */ NULL, /* sys_acl_get_file */ NULL, /* sys_acl_get_fd */ NULL, /* sys_acl_clear_perms */ NULL, /* sys_acl_add_perm */ NULL, /* sys_acl_to_text */ NULL, /* sys_acl_init */ NULL, /* sys_acl_create_entry */ NULL, /* sys_acl_set_tag_type */ NULL, /* sys_acl_set_qualifier */ NULL, /* sys_acl_set_permset */ NULL, /* sys_acl_valid */ NULL, /* sys_acl_set_file */ NULL, /* sys_acl_set_fd */ NULL, /* sys_acl_delete_def_file */ NULL, /* sys_acl_get_perm */ NULL, /* sys_acl_free_text */ NULL, /* sys_acl_free_acl */ NULL /* sys_acl_free_qualifier */ };
->
static vfs_op_tuple example_op_tuples[] = { {SMB_VFS_OP(example_connect), SMB_VFS_OP_CONNECT, SMB_VFS_LAYER_TRANSPARENT}, {SMB_VFS_OP(example_disconnect), SMB_VFS_OP_DISCONNECT, SMB_VFS_LAYER_TRANSPARENT}, {SMB_VFS_OP(example_fstat), SMB_VFS_OP_FSTAT, SMB_VFS_LAYER_TRANSPARENT}, {SMB_VFS_OP(example_stat), SMB_VFS_OP_STAT, SMB_VFS_LAYER_TRANSPARENT}, {SMB_VFS_OP(example_lstat), SMB_VFS_OP_LSTAT, SMB_VFS_LAYER_TRANSPARENT}, {SMB_VFS_OP(NULL), SMB_VFS_OP_NOOP, SMB_VFS_LAYER_NOOP} };
example_op_tuples[]配列をファイルの最後に移動する。
ファイルの最後にinit_module()関数を追加する。たとえば、
NTSTATUS init_module(void) { return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,"example",example_op_tuples); }
使用しているvfs_init()関数がvfs_ops構造体を準備するだけ以上か、smb_vfs_handle_struct 構造体を記憶しているかを調べる。
もしもそうでなければ、vfs_init()関数を取り除くことが出来る。 |
もしもそうならば、コードをexample_connect()操作に移すか、init_module()に移すかを決める。 そして次に、vfs_init()を取り除く。たとえば、debugクラスの登録は、init_module()に行くべきで あり、固有データの割り当てはexample_connect()に行くべきである。 |
(3.0alpha*モジュールのみ) 使用しているvfs_done()関数が必要とされるコードを含んでいるかを確認する。
もしもそうでなければ、vfs_done()関数を取り除くことが出来る。 |
もしもそうならば、コードをexample_disconnect()操作に移動出来るかを決める。 それ以外の場合、smb_register_exit_event()と一緒にSMB_EXIT_EVENTを登録する。たとえば、 固有データを開放する事はexample_disconnect()に行くべきである。 |
何らかのグローバル変数が残っているかを確認する。 もしもコネクションベース上にこのデータを持つ方がよいかどうかを決める。
もしもそうでない場合、存在しているものを取り去る(すなわち、固有デバッグクラス用の変数とする)。 |
もしもそうであれば、データすべてを構造体に入れる。接続単位ベース上でそのような構造体を 指定するためにhandle->dataを使うことが出来る。 |
すなわち、もしも以下ような構造体をもっているような場合:
struct example_privates { char *some_string; int db_connection; };
これを行う最初の方法は以下の通り:
static int example_connect(vfs_handle_struct *handle, connection_struct *conn, const char *service, const char* user) { struct example_privates *data = NULL; /* alloc our private data */ data = (struct example_privates *)talloc_zero(conn->mem_ctx, sizeof(struct example_privates)); if (!data) { DEBUG(0,("talloc_zero() failed\n")); return -1; } /* init out private data */ data->some_string = talloc_strdup(conn->mem_ctx,"test"); if (!data->some_string) { DEBUG(0,("talloc_strdup() failed\n")); return -1; } data->db_connection = open_db_conn(); /* and now store the private data pointer in handle->data * we don't need to specify a free_function here because * we use the connection TALLOC context. * (return -1 if something failed.) */ VFS_HANDLE_SET_DATA(handle, data, NULL, struct example_privates, return -1); return SMB_VFS_NEXT_CONNECT(handle,conn,service,user); } static int example_close(vfs_handle_struct *handle, files_struct *fsp, int fd) { struct example_privates *data = NULL; /* get the pointer to our private data * return -1 if something failed */ SMB_VFS_HANDLE_GET_DATA(handle, data, struct example_privates, return -1); /* do something here...*/ DEBUG(0,("some_string: %s\n",data->some_string)); return SMB_VFS_NEXT_CLOSE(handle, fsp, fd); }
これを行う次の方法は以下の通り:
static void free_example_privates(void **datap) { struct example_privates *data = (struct example_privates *)*datap; SAFE_FREE(data->some_string); SAFE_FREE(data); *datap = NULL; return; } static int example_connect(vfs_handle_struct *handle, connection_struct *conn, const char *service, const char* user) { struct example_privates *data = NULL; /* alloc our private data */ data = (struct example_privates *)malloc(sizeof(struct example_privates)); if (!data) { DEBUG(0,("malloc() failed\n")); return -1; } /* init out private data */ data->some_string = strdup("test"); if (!data->some_string) { DEBUG(0,("strdup() failed\n")); return -1; } data->db_connection = open_db_conn(); /* and now store the private data pointer in handle->data * we need to specify a free_function because we used malloc() and strdup(). * (return -1 if something failed.) */ SMB_VFS_HANDLE_SET_DATA(handle, data, free_example_privates, struct example_privates, return -1); return SMB_VFS_NEXT_CONNECT(handle,conn,service,user); } static int example_close(vfs_handle_struct *handle, files_struct *fsp, int fd) { struct example_privates *data = NULL; /* get the pointer to our private data * return -1 if something failed */ SMB_VFS_HANDLE_GET_DATA(handle, data, struct example_privates, return -1); /* do something here...*/ DEBUG(0,("some_string: %s\n",data->some_string)); return SMB_VFS_NEXT_CLOSE(handle, fsp, fd); }
サードパーティモジュールを構築することを簡単にするため、configure.in,(configure)、
install.shとMakefile.inをモジュールと一緒に提供することは有用であろう。
(examples/VFS
中の例を一見してみること。)
configurationスクリプトは、Sambaソースツリーへのパスを指定するために、
--with-samba-source
を受け付ける。また、コンパイラにたくさんの
警告を出させるための、--enable-developer
も受け付ける。
あなたのモジュール用に、このconfigure.in
と
Makefile.in
スクリプトを拡張できる。
コンパイルとテスト...
./configure ... |
make |
Try to fix all compiler warnings |
make |
Testing, Testing, Testing ... |
以下のような関数を書くことはしない:
static int example_close(vfs_handle_struct *handle, files_struct *fsp, int fd) { return SMB_VFS_NEXT_CLOSE(handle, fsp, fd); }
真に必要とする関数のみをオーバーロードすること!
もしも、既定値のSamba opaque関数よりもよりよいバージョンを実装したいならば (たとえば、特別なファイルシステムに対するdisk_free()関数のようなもの)、 その特定の関数をオーバーロードすることは問題ない。
もしもデータベースファイルシステムあるいはposixファイルシステムから異なる 何かを実装したい場合、すべてのvfs操作をオーバーロードするようにすること!!!
使用するファイルシステムがサポートしない機能は、以下のようなものでオーバーロードすべきである: たとえば、リードオンリファイルシステムなど。
static int example_rename(vfs_handle_struct *handle, connection_struct *conn, char *oldname, char *newname) { DEBUG(10,("function rename() not allowed on vfs 'example'\n")); errno = ENOSYS; return -1; }
Table of Contents
基本的に、ファイルは行単位で処理される。構文解析ルーチン(param.c)によって認識される 4つのタイプの行がある。
空白行 - 空白のみを含む行。
コメント行 - セミコロンかシャープ記号で始まる行(';' か '#')。
セクションヘッダー行 - 左大括弧で始まる行('[')。
パラメーター行 - その他の文字で始まる行。 (既定値の行タイプ)
最初の2つはもっぱら構文解釈ルーチンによって無視される。残りの2つのタイプは、 以下を解釈する。
- セクション名
- パラメーター名
- パラメーター値
パラメーターローダー(loadparm.c)にトークンのみが渡される。パラメーター名と値はそれぞれを 等号('=')で分離される。
空白は改行文字('\n')以外、isspace()関数(ctype(3C)参照)によって、認識されるすべての 文字として定義される。行末を識別するという理由で、改行は除外される。
構文解析ルーチンは行の先頭からの空白を読み飛ばす。
セクションとパラメーター名は内部に空白を含むことが出来る。名前の中にあるすべての空白は、 単一の空白文字に圧縮される。
パラメーター値内部の空白は、それらすべてが削除されるキャリッジリターン文字('\r')を除き、 そのまま保持される。
名前と値の先頭およびそのあとの空白は削除される。
流しセクションヘッダーとパラメーター行は、バックスラッシュ文字('\\')を使って、 複数の行に継続しても良い。継続行は、空白とコメント行に対しては無視される。
もしも、セクションヘッダー内か、パラメーター行上にある最後の(非空白)文字は、バックスラッシュ 文字であり、次に、次の行が、構文解析ルーチンによって、(論理的に)現在の行に対して 結合される。例は以下の通り:
param name = parameter value string \ with line continuation.
は以下と同値である
param name = parameter value string with line continuation.
単語'string'の後に5つの空白があり、最初の行中の'stging'と'\\'の間に1つの空白があり、 さらに、2番目の行の'with'の前に4つの空白があることに注意(もちろん、 インデントをカウントしている)。
継続行文字は空白行とコメントの最後では無視される。セクションとパラメーター行*のみで* 解釈される。
以下の例に注意:
param name = parameter value string \ \ with line continuation.
真ん中の行は、最初の行の行に結合されるため、空白行としては*処理されない*。 結果は以下のようになる。
param name = parameter value string with line continuation.
コメントラインに対してもこのことは成立する。
param name = parameter value string \ ; comment \ with a comment.
これは以下のようになる:
param name = parameter value string ; comment with a comment.
セクションヘッダー行状に置いて、閉じ大括弧(']')は終端文字として考慮され、行の残りの 部分は無視される。以下のような例
[ section name ] garbage \ param name = value
は下記のように解釈される。
[section name] param name = value
smb.confファイルの文法は以下のようになる:
<file> :== { <section> } EOF <section> :== <section header> { <parameter line> } <section header> :== '[' NAME ']' <parameter line> :== NAME '=' VALUE NL
基本的に、これの意味は以下のようになる
ファイルは0個以上のセクションからなり、EOF(それを知っているので)によって 終端する。
セクションは0行以上のパラメーターが続くセクションヘッダーからなる。
セクションヘッダーは左大括弧により定義され、右大括弧により閉じる。囲まれた 名前がセクションを識別する。
パラメーター行は名前と値に分割される。行中の*最初の*等号は、名前と値を分割する。 値は改行文字(NL = '\n')によって終端する。
Table of Contents
現在のSambaのコードベースは、NetBIOS名の登録と解決のための共通の名前空間を共有する、 グループになっているWINSサーバーを使う能力を持っている。正式なパラメーターの文法は 以下の通り。
WINS_SERVER_PARAM = SERVER [ SEPARATOR SERVER_LIST ] WINS_SERVER_PARAM = "wins server" SERVER = ADDR[:TAG] ADDR = ip_addr | fqdn TAG = string SEPARATOR = comma | \s+ SERVER_LIST = SERVER [ SEPARATOR SERVER_LIST ]
有効なwinsサーバーの設定例は以下の通り。
[global] wins server = 192.168.1.2 192.168.1.3
リスト中のSERVER用にTAGが定義されていないイベント中では、smbdは"*"という、 既定値のタグを割り当てる。TAGはNetBIOS名前空間を一緒に共有するサーバーをグループにする のに使われる。起動時から、nmbdはNetBIOS名の値を、各タグづけられたグループ中に1つのサーバーを 登録しようと試みる。
WINSサーバーを一緒にグループ化するためのタグの使い方の例は以下にある。タグ中の インタフェース名の使用は、単に慣習的なものによるもので、技術的な要求ではないことに 注意。
[global] wins server = 192.168.1.2:eth0 192.168.1.3:eth0 192.168.2.2:eth1
この設定を使い、nmbd はサーバーのNetBIOS名を、各グループ中で1つのWINSサーバーに 登録しようと試みる。"eth0"グループは2つのサーバーを持つという理由で、 2番目のサーバーは登録(あるいは名前解決)要求が、そのグループの最初のサーバーに対して タイムアウトになった時にのみ使われる。
NetBIOS名前解決は名前登録と同じようなパターンで処理を行う。WINS経由でNetBIOS名の 解決を行うとき、smbdと他のSambaプログラムは、どこからか最短で肯定応答が返るまでか、 名前問い合わせ要求に対してすべてのタグされたグループのサーバーから否定応答が返るまで、 タグされたグループ中の、単一のWINSサーバーに問い合わせを試みる。もしも、特定のWINS サーバーに対して、問い合わせのタイムアウトが発生した場合、そのサーバーは、その先また タイムアウトが起きないようにダウンしているとマークされ、WINSグループ内の次のサーバーに 接続する。一度停止とマークされると、Sambaは10分間は名前登録/解決の問い合わせを、 そのサーバーに対して行おうとはしない。
Table of Contents
Samba用の、LanManager と Windows NT互換のパスワード暗号化は、 LanManager または Windows NT サーバーと正確に同じ方法で、ユーザーの接続を 検証することが出来るようになっている。
この文書は、どのようにSMB パスワードの暗号化アルゴリズムが動作し、 それを使うことを選択する場合に、何が問題化について記述している。この文書を 特に、セキュリティと"PROS and CONS"節を注意深く読むべきである。
LanManagerの暗号化はUNIパスワードの暗号化と若干似ている。サーバーは、 ユーザーのパスワードをハッシュ化した値を含むファイルを使う。これは、ユーザーからの 平文パスワートを得て、それを大文字化し、14文字で打ち切るか、NULL文字を14文字に なるまで埋めることのどちらかを行って作成する。この14バイトの値は、'特別な' 8バイトの値に暗号化されるため、2つの56ビットDESキーとして使われ、サーバーと クライアントによって保存される16バイトの値に整形される。これは、 "ハッシュ化されたパスワード"として知られる。
Windows NTの暗号化は非常に高品質のメカニズムで、ユニコード版の、 ユーザーのパスワード上で、MD4ハッシュを行うことから成り立っている。これはまた、 可逆変換不可能な16バイトハッシュ値である。
クライアント(LanManager, Windows for WorkGroups, Windows 95 か Windows NT)がSambaドライブ(かSambaリソース)をマウントしようとした時、 最初に接続を要求し、クライアントとサーバーが使うプロトコルを調整する。 この要求のリプライ中で、Sambaサーバーは8バイトのランダム値を生成し添付する。 これは、"チャレンジ"と呼ばれ、リプライが返ってくることに備えてSambaサーバー中に 保存される。チャレンジはすべてのクライアント接続毎に異なる。
クライアントは次にハッシュ化されたパスワード(上記で説明されている 16バイト値)を使い、5つのNULL文字を追加し、3つの56ビットDESキーとし、 それは、おのおのチャレンジの8バイト値を暗号化するのに使われ、"レスポンス" と呼ばれる24バイトの値に整形する。
SMB呼び出しSMBsessionsetupX(ユーザーレベルセキュリティが選択された場合)中か、 STBtconX呼び出し(共有レベルセキュリティが選択された場合)中で、24バイトの レスポンスはクライアントによってSambaサーバーに戻される。Windows NTプロトコル レベル用に、上記の計算が、ユーザーのパスワードのハッシュ両方ととレスポンス両方に 対して行われ、2つの24バイト値がSMB呼び出しに対して戻される。
Sambaサーバーは次に上記の計算を、サーバー内に保存されている16バイトの
ハッシュ化されたパスワード(後述するsmbpasswd
から読んで)、
ネゴシエートプロトコルの返答から得たチャレンジ値を使って再度行う。それは
次に、計算した24バイトの値がクライアントから返されたものと一致するかを
確認する。
もしも、それらの値が性格に一致するならば、クライアントが正しいパスワード (か、16バイトのハッシュ化された値 - 以下のセキュリティに関する注意を参照) を使っていて、その結果アクセスは許可される。もしもそうでない場合、 クライアントは正しいパスワードを使っておらず、アクセスは拒否される。
Sambaサーバーはユーザーの平文パスワードを受け取ったり、格納することは 決してないことに注意。それに由来する16バイトのハッシュ値のみである。また、 平文パスワードまたは16バイトのハッシュ値はネットワーク上を決して流れない。 そのため、セキュリティは向上する。
Sambaが上記のプロトコルに参加するために、それがユーザー名と等価ならば、
16バイトのハッシュ値を検索できる必要がある。残念ながら、UNIXパスワードの
値は1方向ハッシュ関数による値なので(すなわち、UNIXのハッシュとして与えられた、
ユーザーの平文パスワードを検索することは不可能である)、この16バイト値を含む
分離されたパスワードファイルを用意しなければならない。2つのパスワードファイルに
起因する、同期ずれ問題を最小限にするために、UNIXの
/etc/passwd
と smbpasswd
、
ユーティリティmksmbpasswd.sh
がUNIXの
/etc/passwd
からsmbpasswdファイルを生成するために
提供されている。
使用している/etc/passwd
ファイルからsmbpasswd
ファイルを生成するために、以下のコマンドを使用する:
$
cat /etc/passwd | mksmbpasswd.sh
> /usr/local/samba/private/smbpasswd
もしも、NISを使っているシステム上ならば、以下を使用する
$
ypcat passwd | mksmbpasswd.sh
> /usr/local/samba/private/smbpasswd
mksmbpasswd.sh
プログラムは、Sambaソース
ディレクトリ中にある。既定値では、smbpasswdファイルは下記にある:
/usr/local/samba/private/smbpasswd
/usr/local/samba/private/
ディレクトリの
所有者は、rootに設定すべきであり、それのアクセス許可設定は、0500
(chmod 500 /usr/local/samba/private
)に設定すべきである。
同じように、privateディレクトリ内のsmbpasswdファイルはrootが所有
すべきであり、それのアクセス許可設定は、0600
(chmod 600 smbpasswd
)にすべきである。
smbpasswdファイルの形式は以下の通り(ここでは行を折り返している。 smbpasswdファイル内では、1つのエントリ毎に1行となっている):
username:uid:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX: [Account type]:LCT-<last-change-time>:Long name
Although only the username
,
uid
,
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
,
[Account type
]と
last-change-time
セクションのみが重要であり、Sambaソースコード
内ではそれらのみが参照される。
XXXセクション中に、32の'X'文字の間に2つの':'文字があることが、 特に重要で、smbpasswdとSambaのコードは、 ':'の文字の間に32文字がない任意のエントリについては認証に失敗する。 最初のXXXセクションはLanman passwordハッシュで、2番目はWindows NT 用である。
passwordファイルが生成された時、すべてのユーザーは32個の'X'文字を含む パスワードエントリを持つ。既定値で、これはこのユーザーに対して、何らの アクセスも許可しない。ユーザーがパスワードを設定すると、'X'文字は32バイトの ASCII16進数値(0-9,A-F)に変わる。これは、ユーザーのパスワードの、 16バイトハッシュ値を表現したものである。
(推奨しないが)ユーザーがパスワードを持たないようにするためには、viを
使ってこのファイルを編集し、最初の11文字をASCII文字列
"NO PASSWORD"
(引用符は勘定に入れない)に置き換える。
たとえば、ユーザーbobのパスワードをなくすには、彼のパスワードファイルの エントリを以下のようにする:
bob:100:NO PASSWORDXXXXXXXXXXXXXXXXXXXXX:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX: [U ]:LCT-00000000:Bob's full name:/bobhome:/bobshell
もしも、自分自身でsmbpasswdコマンドを使ってパスワードを設定することを認めて
いるならば、(推奨しないが)ユーザーがパスワードを再設定する時に、以前の
パスワードを入力しなくてすむよう、初期値としてユーザーに NO PASSWORD を設定しても
よいだろう。これを行うためには、smbpasswd
プログラムは
そのユーザーがパスワードなしで動くために、smbd
デーモンに
接続できる必要がある。これを行うには以下のような設定を
null passwords = yes
smb.confファイルの[global]セクションに(これが、なぜ上記の記述が 推奨されないかの理由である)記述する。出来れば、最初に各ユーザーの初期パスワードを 設定すれば、使用するサーバー上でこれを有効にしなくてすむ。
注意 : このファイルはとても注意深く保護すべきである。
誰でもがこのファイルにアクセスできると言うことは、(プロトコルについて
十分な知識がなくても)使用するSMBサーバーへのアクセス権を得ることが出来ることに
なるからである。ファイルはそのため通常のUNIX/etc/passwd
ファイルよりもより機密性が高い。
Table of Contents
このファイルは、どのようにSamba上でのシステムコールトレースを行い、何が問題かを 調べる事について説明している。これは気弱な人のためのものではなく、もしもこれを 読んでいるならば、あなたはおそらくせっぱ詰まっているはずだ。
実際、上記で言っているようには、物事はそれほどひどくはなく、ただ出力が汚いだけである :-)
ビジネスの話に戻ろう。UNIXシステムのとても大きな利点の1つは、プログラムが行っている すべてのシステムコールをモニターする事が出来るシステムトレースユーティリティを、ほとんど すべてのシステムがもっていると言うことである。これは特にデバッグのために使われ、また、 なぜ期待していたよりも遅くしか動かないかという問題を調べる手助けにもなる。特に、 何らかの特別なコンパイルオプションなしに、システムトレースを使うことが出来る。
システムトレースユーティリティは異なったシステムでは異なった名前で呼ばれる。Linux システム上では、straceと呼ばれる。SunOS 4では、traceと呼ばれる。SVR4ベースのシステム では(solarisを含む)、trussと呼ばれる。多くのBSDシステムではktraceと呼ばれる。
最初に行うべき事は、使用しているシステム上でのシステムコールトレースユーティリティの マニュアルページを読むことである。以下の議論の中で、straceのみがポータブルなシステム トレースユーティリティ(多くのUNIXマシンで自由に使えるものがある)で、かつ、最も良い ある種の機能をもっているので、これをstraceと呼ぶ。
次に、いくつかの簡単なコマンド上でstraceを使ってみる。例えば、
strace ls
か strace echo hello
などである。
これが大量の出力を出すことに注意。これは、プログラムが出すすべてのシステムコールに対する 引数とその結果を表示する。システムコールを出さないプログラムは非常に例外的で、そのため 大量の出力が出る。共有ライブラリなどをロードする事を表示することなどの、大量の"前処理" を生成することにも注意。これは無視すること(それが問題にならない限り!)
例えば、strace echo hello
中の、真に重要な行に対する出力は以下の
通りである:
write(1, "hello\n", 6) = 6
残りすべてはプログラムを動かすための準備である。
OK,これでstraceになれたはずである。これをSambaに使うためには、動作しているsmbd
デーモンに対してstraceする必要がある。それを使うための方法は、まず最初にWindows PC
からSambaサーバーにログオンし、次に、そのクライアントが接続しているプロセスIDを、
smbstatusを使って調べ、rootでstrace -p PID
として、そのプロセスに
接続する。通常後で見るために、このコマンドからのstderr出力をファイルにリダイレクトする。
例えば、もしもcsh風のシェルを使っているならば、
strace -f -p 3872 >& strace.out
かあるいはsh風のシェルを使っているならば:
strace -f -p 3872 > strace.out 2>&1
とする。
"-f"オプションに注意。これは、いくつかのシステムのみで有効であり、現在のプロセス だけではなく、フォークした任意のチャイルドプロセスのトレースも取れるようにする。 これは、"print command"がうまくいかない時に引き起こされる印刷時の問題を見つけるのに とても役に立つ。
一度アタッチすると、問題を引き起こしているクライアント上でのすべての動作を見ることが でき、smbdが発行しているすべてのシステムコールをとらえられる。 Once you are attached you then can do whatever it is on the client that is causing problems and you will capture all the system calls that smbd makes.
次に、どのように結果を解釈したらよいだろうか?。一般的に、問題が発生した時に現れる だろうと思われる、分かっている文字列を、出力全体を通して検索する。例えば、もしも、 ファイルに対するアクセス許可の問題があった場合、straceの出力からそのファイル名を 検索し、その周りの行を眺めてみる。もう一つのテクニックとしては、ファイルディスクリプタの 番号の一致を捜し、それがオープンしてクローズするまでに何があったかを"順に追っていく"。
これを元にして、自分で考えていく必要がある。ここで何を捜すかと言うことのヒントを与える
ために、/dev/null
がすべてのユーザーに対して書き込み可能でない場合、
Sambaで印刷が失敗する場合の、strace の結果の一部を提示しよう:
[pid 28268] open("/dev/null", O_RDWR) = -1 EACCES (Permission denied) [pid 28268] open("/dev/null", O_WRONLY) = -1 EACCES (Permission denied)
プロセスは最初に/dev/null
を読み書き可能で開こうとし、次に、
リードオンリで開こうとする。両方とも失敗する。これは、/dev/null
が
適切なアクセス許可をもっていないことを意味する。
Table of Contents
Sambaは7つの機能に対する関数ポインターテーブルを使う。関数のプロトタイプは
printing.h
中で記述されるprintif
構造体中で
定義されている。
retrieve the contents of a print queue
pause the print queue
resume a paused print queue
delete a job from the queue
pause a job in the print queue
result a paused print job in the queue
submit a job to the print queue
現在、2つの印刷バックエンドの実装のみが定義されている。
標準UNIX印刷サブシステムと共に動作するための汎用的な機能セット
CUPS固有の機能セット(これはCUPSライブラリがコンパイル時に存在 する場合にのみ有効となる)。
Sambaはパフォーマンス向上のために、"lpq command"からの出力を定期的にキャッシュする 機能を提供する。このキャッシュ時間は秒単位で設定可能である。明らかに、キャッシュ時間が より長いと、それだけsmbdはlpqへのコピーを行う回数が減る。しかし、クライアントに見せる 印刷キューの内容の正確さは、減少するだろう。
現在オープンしている印刷キューTDBのリストは、tdb_print_db structuresのリストを 調べることによって見つけることが出来る(printing.c中のprint_db_head参照)。1つのキュー TDBはラッパー関数printing.c:get_print_db_byname()を使うことによってオープンされる。 関数は、すべての有効なファイルディスクリプタを使い果たしてしまうことから、大きな 印刷サーバーを防ぐための効果がある、MAX_PRINT_DBS_OPENよりも数多く開かない事をsmbdに 行わせる。もしもキューTDBのオープン数がMAX_PRINT_DBS_OPENの制限を超過した場合、 smbdはオープンTDBのリストを管理するためMRU(most recently used)アルゴリズムを使う。
印刷ジョブを印刷キューTDB中に投入できるための2つの方法がある。最初のものはTDB中に ジョブ情報を直接挿入するWindowsクライアントからジョブを投入する方法である。2番目の 方法は存在している"lpq command"を実行することで、印刷ジョブをピックアップする方法である。
/* included from printing.h */ struct printjob { pid_t pid; /* which process launched the job */ int sysjob; /* the system (lp) job number */ int fd; /* file descriptor of open file if open */ time_t starttime; /* when the job started spooling */ int status; /* the status of this job */ size_t size; /* the size of the job so far */ int page_count; /* then number of pages so far */ BOOL spooled; /* has it been sent to the spooler yet? */ BOOL smbjob; /* set if the job is a SMB job */ fstring filename; /* the filename used to spool the file */ fstring jobname; /* the job name given to us by the client */ fstring user; /* the user who started the job */ fstring queuename; /* service number of printer for this job */ NT_DEVICEMODE *nt_devmode; };
printjob 構造体の現在の状態には、"lpq command"から戻されるUNIXジョブIDと、 Windows ジョブID(PRINT_MAX_JOBIDによって32ビットごとに区切られる) のためのフィールドが含まれている。印刷ジョブが、キューTDB中に存在するジョブに 一致しない"lpq command"によって戻された場合、上記の32ビットジョブID <*vance doesn't know what word is missing here*>がlpqによって報告される ID用にUNIX_JOB_STARTを追加して生成される。
32ビットWindowsジョブIDを16ビットlanman印刷ジョブIDに一致させるために、smbdは 古いlanmanクライアントのために、前半部分の適切な数値を一致させるためのメモリTDBを使う。
印刷キューを更新する時、smbdは以下の手順を実行する
(print.c:print_queue_update()
を参照):
もしも、他のsmbdが現在キューの内容を更新中かどうかを、
LOCK/
に
格納されているpidをチェックすることで調べる。もしもそうであれば、
TDBの更新をしない。printer_name
TDB中のmutexエントリをロックし、今のプロセス固有のpidを格納する。 それがうまくいったかを調べ、そうでなければ失敗する。
新しいキャッシュ内容の表示のために、更新したタイムスタンプを 格納する。
"lpq command"経由でキューの一覧を検索する。
foreach job in the queue { ジョブがUNIXジョブならば、新しいエントリを作成する; ジョブがWindowsベースのジョブIDを持つならば、 { ジョブIDでレコードを検索する; もしも検索が失敗したら それをUNIXジョブとして扱う; そうでなければ ジョブステータスのみを更新する } }
lpqの一覧中に無い、TDB中のジョブを削除する。
TDB中に印刷キューステータスを格納する。
再度キャッシュされたタイムスタンプを更新する。
これは、Windowsクライアントに返されるTDBの内容であり、"lpq command"からの実際の一覧とは 違うことに注意。
printjob構造体の一部として格納されるNT_DEVICEMODEは、印刷ジョブに関連づけられる 非標準のDeviceModeへのポインターを格納するのに使われる。ポインターは、クライアントが OpenPrinterEx()呼び出し中でDevice Modeを含める時にnull以外になり、その同じハンドルで 印刷のために引き続いてジョブを投入する。もしもクライアントがOpenPrinterEx()要求に 対してDevice Modeを含めないのであれば、nt_devmodeフィールドはNULLであり、ジョブは 既定値で、それに関連づけられるプリンターのデバイスモードを持つ。
非標準のDevice Modeのみが印刷キューTDB中にプリントジョブと一緒に格納される。それ以外は、 Device Modeは、クライアントがGetJob(level == 2)要求を発行した時、プリンターオブジェクト から得られたものである。
Windows NT+クライアントで動作している場合、プリンターサーバーに対してRPCを使って、 クライアントに、特定のプリンターと印刷ジョブ属性の、非同期な変更通知イベント を送ることが出来る。これは、クライアントが指定したプリンターに対して新しいジョブが キューに追加されたことか、プリンターのドライバーが変更されたかを知る必要がある時に 便利である。これは、プリンターオブジェクトのなめの新しいChangeID上をベースとした キャッシュ更新とは完全に無関係に行われることに注意。
変更通知を実装するために使われる基本的なRPCの使用範囲は以下の通り
RemoteFindFirstPrinterChangeNotifyEx ( RFFPCN )
RemoteFindNextPrinterChangeNotifyEx ( RFNPCN )
FindClosePrinterChangeNotify( FCPCN )
ReplyOpenPrinter
ReplyClosePrinter
RouteRefreshPrinterChangeNotify ( RRPCN )
1つの追加RPCがサーバーに対して有効であるが、それはWindows spoolerサービスでは決して 使われない:
RouteReplyPrinter()
それらすべてのRFC用のopnumはinclude/rpc_spoolss.hで定義されている。
Windows NT印刷サーバーはクライアントに印刷通知イベントを送る変わった方法を使っている。 新しい変更通知ハンドルの登録プロセスは、以下のようになる。'C'はクライアントで、 'S'はサーバーである。すべてのエラー状態は省略されている。
C: 標準OpenPrinterEx()呼び出しを経由して、プリンターかプリンターサーバーへの ハンドルを得る。 S: オブジェクトへの有効なハンドルを返す。 C: (a)モニターへの変更イベントのための1まとまりのフラグか、(b)モニターへの イベント情報を含むPRINTER_NOTIFY_OPTIONS構造体のどちらかと共に、 以前に入手したハンドルと共にRFFPCN要求を送る。Windows spoolerは (b)の使用のみを観察している。 S: <*他の間違った単語*>はクライアントに対する新しいTCPセッションを開き (そのため、すべての印刷クライアントはCIFSサーバーであるように要求する)、 クライアントに対して、ReplyOpenPrinter()要求を送信する。 C: クライアントは、イベント通知メッセージを送るために使われる事が出来る、 プリンターハンドルを返す。 S: サーバーはRFFPCN要求に対して成功を返す。 C: Windows spoolerは、すべての、モニターしている属性の現在値を取得するために、 RFNPCNを使うRFFPCNをフォローする。 S: サーバーはSPOOL_NOTIFY_INFO_DATA構造体(SPOOL_NOTIFY_INFO構造体を含む)を 返す。 C: もしも、変更通知ハンドルがFCPCN要求経由でクライアントにより常に開放されるならば、 サーバーは最初にクライアントに対してReplyClosePrinter()要求を返す。しかし、 クライアントからのこの性質の要求は、しばしば以前の通知イベントがサーバーによって、 正しく整理されなかったか、データの一部分が間違っていたかを示すものである。 S: サーバーは内部の変更通知ハンドル(POLICY_HND)をクローズし、そのプリンターかジョブに 対する変更通知イベントは、その後クライアントに送らないようになる。
Sambaによってサポートされている、現在の通知イベントのリストは、srv_spoolss_nt.c中の 内部テーブルを捜すことで見つけることが出来る。
printer_notify_table[]
job_notify_table[]
モニターされているイベントが発生した時、smbdは変更についてそれ自身にメッセージを送る。 送信されるイベントのリストは、目セージの送信によって、TDBの利用状態が高負荷になる ことを防ぐためにsmbdプロセスによってキューされ、内部メッセージは、smbdがアイドル 状態の時に(printing/notify.cとsend_spoolss_notify2_msg()関数と print_notify_send_messages()関数を参照)送信される。
変更状態を、接続されているクライアントに送るかどうかの決定は、実際に通知を送る ルーチンによって行われる(srv_spoolss_nt.c:recieve_notify2_message()を参照)。
複数のプリンターの複数の変更の一覧を受け取ることが可能という理由で、通知イベントは プリンター名の区分けによって分離される必要がある。これにより、ReplyOpenPrinter()経由で 得られるプリンターハンドルによる、単一のRPCで、複数の変更イベントを送れるようになる。
実際の変更通知はRRPCN要求RPCを使って実行される。このパケットは以下を含む
変更が起こったところのクライアントのスプーラとして登録された プリンターハンドル
クライアントからの最後のRFNPCN要求の一部として送られる 変更の生の値(change_low value)
イベント情報を持つSPOOL_NOTIFY_INFOコンテナ
SPOOL_NOTIFY_INFO
は以下を含む:
バージョンとフラグ欄はあらかじめ定義され、変更すべきでない
count欄はSPOOL_NOTIFY_INFO_DATA配列内のエントリ数である。
SPOOL_NOTIFY_INFO_DATA
は以下を含む:
typeは、このイベントがプリンターからか、あるいは印刷ジョブからかを 定義する
fieldは、イベントを識別するフラグである
notify_data unionには新しい属性値が含まれている。
enc_typeは、整列しているかいないかの構造体の大きさを定義する。
(a) プリンターハンドル上のプリンターイベントに対しては、idは0でなければならない。 (b)idは、プリンタージョブ上のイベントのジョブidでなければならない。 (c)idは、通知のためのプリントサーバーハンドルを使う時にRFNPCNに対する応答パケット中で 使われるプリンターインデックスの番号と一致しなければならない。Sambaは現在、 もしも通知ハンドルが登録されてから、サービスのリストが、変更された場合、変わっているかも しれないプリンターのsnumを使う。
サイズは(a)UNICODEでの文字列の長さか、(b)セキュリティディスクリプタの バイト値か(c)データ値の場合は0である。
Table of Contents
Table of Contents
是非是非source/VERSION
中にベンダバージョンサフィックスと番号を
設定し、使用するパッケージのバージョニングを含めるために
source/script/mkvesion.sh
を呼び出してほしい。ベンダバージョンを
作成するための関数を設定することがそこでは出来る。これにより、個別に構築したSambaシステム
(しばしばパッチを当てたパッケージ)から標準のSambaシステムを区別するのがとても容易になる。
例えば、良いバージョンの例は以下の通り:
Version 2.999+3.0.alpha21-5 for Debian
Samba3 はプラグインとしてSambaの一部を構築することをサポートしている。これは、たとえば、 ldapまたはmysqlサポートを別パッケージにすることが可能となり、その結果、通常のSamba パッケージがLDAPやmysqlに依存しないようにすることが出来る。Sambaの多くの部分をプラグイン として構築するためには、以下のように行う:
The option --with-shared-modules
は、たとえばidmap_XXX and vfs_XXX
のような特定のモジュールをサポートするために維持されている。例えば、
--with-shared-modules=idmap_ad
である。公式のリリース中で
サポートしないように、configure
コマンドにこのパラメーターを使用する。
./configure --with-shared-modules=rpc,vfs,auth,pdb,charset