Title

2013年12月の投稿一覧

iOS向けSMBライブラリを実装する(その3)~NetBIOS名の解決

Windowsで使われる、NetBIOSまわりを頑張ってみます。

やりたいことは、Windowsコンピュータ名(NETBIOS名)からIPアドレスを取得することです。

NetBIOSはとにかく不可解です。
本当に不可解なんですが、歴史的背景があるので仕方がありません。
どのくらい古いかというと1980年代くらいなので仕様書もイマイチ読みづらく、後の改変も多数あるので本当にわかりづりです。

まずNetBIOSはTCP/IPとは別物です。
現在ではプロトコル部分をNetBEUI、インターフェイス部分をNetBIOSと呼ぶようですが、元はインターフェイス(API)からプロトコルまですべてひっくるめた規格でした。

NetBEUIは現在では使われることは無くなりましたが、NetBIOSのインターフェイスだけは利用され続けているというわけです。
NetBIOSをTCP/IPで利用できるようにしたものがNetBIOS over TCP/IPです。(TCP/IPとか言いながらUDP/IPも多用します)
これ時代もWindows3.x時代のものなので何かと理解しづらいです。

さて、関係ありそうな、NetBIOS over TCP (NBT) Extensions NetBIOS over TCP (NBT) Extensions[MS-NBTE]をまず見てみると
名前解決についてはRFC1002を見るように書いてあります。

RFC1002の内容は「NetBIOS-over-TCP」となっています。
日付は March 1987・・・。
読んでみると・・・なんか意味不明・・ですががんばって読む。

とりあえずパケットのフォーマットはこう

                       1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |         NAME_TRN_ID           | OPCODE  |   NM_FLAGS  | RCODE |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          QDCOUNT              |           ANCOUNT             |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          NSCOUNT              |           ARCOUNT             |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

で、名前解決のリクエストはこういう固定値を入れる

4.2.12.  NAME QUERY REQUEST

                        1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |         NAME_TRN_ID           |0|  0x0  |0|0|1|0|0 0|B|  0x0  |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          0x0001               |           0x0000              |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          0x0000               |           0x0000              |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   /                         QUESTION_NAME                         /
   /                                                               /
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           NB (0x0020)         |        IN (0x0001)            |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Bはブロードキャストフラグなので、1で固定みたいです。
なのでフラグ部分は0x0110。

QUESTION_NAMEの部分はRFC 883を使ってコード化しろって書いてます! 
これがまだ何故??というやり方で頭に???浮かびっぱなしです。

とりあえずNETBIOS名は15文字です。本来は16文字でしたが、最後の1バイトを別の用途に割り当てたため15文字。
15文字に満たない場合はスペース(0x20)で埋めます。

16文字目はサフィックスで種別を表すのですが、
0x00だとワークステーション サービス
0x20だとファイル サーバー サービス
0x1bだとドメイン マスタ ブラウザ
・・・などなどを表すようですが、、
これはもともとのLAN Managerの仕様ではなくMicrosoftの拡張仕様です。
どうやら0x00はクライアント、0x20がサーバサービスを示すようなのでここでは0x20が正解だと思われます。(実際にはどちらも名前登録されていて、反応もしてくれるので良いのですが)

で、肝心のNETBIOS名の格納方法です。
最初の1バイトは0x20を指定します。0x20=長さが32byteの意味で、NETBIOS名の場合は32byte固定と書いてあるので必ず0x20を指定。

NEBIOS名+サフィックスの16byteを以下の手順で符号化して32Byteにします。
最後にNULL(0x00)くっつけるので、長さを表す先頭の0x20と合わせて34byteになります。
これがQUESTION_NAMEになります。

NETBIOS名の符号化は、
まず文字コードを4バイト単位にわけます。
空白文字(0x20)であれば0x2と0x0です。
0x0をA、0x1をB、0x2をC・・・という感じで文字を割り当てます。
0x20であれば「CA」になります。

つまりNETBIOS名が「FRED」であれば「EGFCEFEECACACACACACACACACACACACA」になります。

さて、これをUDPでブロードキャストすれば該当ホストがレスポンスを返してくれます。
iOSで1024番以下のポートをbindできるのか?とちょっと不安でしたが、やってみたら大丈夫でした。

レスポンスの内容は以下のとおり

4.2.13.  POSITIVE NAME QUERY RESPONSE

                        1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |         NAME_TRN_ID           |1|  0x0  |1|T|1|?|0 0|0|  0x0  |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          0x0000               |           0x0001              |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          0x0000               |           0x0000              |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   /                            RR_NAME                            /
   /                                                               /
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           NB (0x0020)         |         IN (0x0001)           |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                              TTL                              |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           RDLENGTH            |                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               |
   |                                                               |
   /                       ADDR_ENTRY ARRAY                        /
   /                                                               /
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

TTLより前はリクエストと同じ。
ADDR_ENTRY ARRAYにIPアドレスが入ります。
(複数ある場合は複数入ります。パケットの送信アドレスを見るか、自分のネットワークマスクと較べてどれを利用するか決めます)

実際には名前の解放パケットとかも飛んでくるようですが(それを見てキャッシュから削除する)、iOSでそれを監視し続けるのは非現実的なので無視します。

なお、IPv6の場合はLLMNR(Link-Local Multicast Name Resolution)を使う必要があります。
今回はIPv4だけでいいのでそっちはやりません。
(そのくらいの知識がある人はIPアドレスでわかるでしょうし・・・)

さて、これでコンピュータ名(NETBIOS名)を解決できるようになりました。
まぁ、なったところでどう使うかは悩ましいところなんですけどね。

iOSでSMBライブラリを実装してみる(その2)~NTLM認証

Windows関連情報は運用を目的にしたものが多く、コーディング例はあんまり聞かないのでメモ的に書いておきます。

SMBの認証にはLM認証、NTLMv1認証、NTLMv2認証、Kerberos認証のいずれかを使います。
Kerberos認証は鍵交換でActiveDirectly環境想定のものなので除外。

LM認証はNTLMv1認証はだいぶ古いにもかかわらずつい最近まで使われていましたがWindows7からはデフォルト無効になっているようなのでこちらも使わない方針で行きます。
ようするに面倒なのでNTLMv2だけ対応します。
あと、まず非対応ということはあり得ないので、Unicodeだけ対応します。

さて、NTLMは「NT LAN Manager Authentication Protocol」のことらしいです。
Microsoftの仕様書名は[MS-NLMP]です。

基本的には認証情報をバイト列に並べているだけなので仕様書を読みながら実装すればなんとか出来ます。
SMB/CIFSがアライメントやら謎のpaddingやらとても実装しにくいのに比べると可変長データをpayloadにまとめているので少しだけ気楽です。

クライアント側で生成する必要があるのは、NEGOTIATE_MESSAGEとAUTHENTICATE_MESSAGEです。
前者は本当に必要な情報を連結してバイト列にしているだけなので楽勝。それでも実際実装してみると一発ではなかなか思い通りにならないのですが・・・。

後者のAUTHENTICATE_MESSAGEもほとんど同じですが、NEGOTIATE_MESSAGEに対してサーバから返されたCHALLENGE_MESSAGEに含まれる情報を使ってチャレンジレスポンスを計算する必要があります。
それがNTLMv2なわけですね。
AUTHENTICATE_MESSAGEのうち、計算が必要なのはNTLMv2 Responseというフィールドです。

といっても、使うのはmd4とmd5だけなんですね。
試行錯誤の結果正しい結果を出せるようになったのでメモ。

入力
・ドメイン(大文字のみ,UTF16LittileEndian)
・ユーザ名(大文字のみ,UTF16LittileEndian)
・パスワード(UTF16LittileEndian)
※ドメインとユーザー名は全て大文字に変換しておく
※文字列のNULLターミネーションは不要(含めない)

サーバから(CHALLENGE_MESSAGEの内容)
・Server challenge (8byte)
・TargetInfo (可変長)

(クライアント側で)生成するもの
・タイムスタンプ(8byte)
・Client challenge (8byte乱数)

1.パスワードのmd4ハッシュを求める
2.ユーザ名とドメイン名を連結する(例:「user」+[Domain]→「USERDOMAIN」 )
※ここを先入観でDOMAIN+USERの順番だと思い込んでハマりました・・・
3.2に対して、1を鍵として、hmac_md5を計算する
4.以下を連結する(これをblobと言うらしい)
  署名(固定値=0x01010000)
  4byteの予約領域その1(0x00000000)
  タイムスタンプ(8byte)
  Client challenge(8byte)
  4byteの予約領域その2(0x00000000)
  TargetInfo
  4byteの予約領域その3(0x00000000)
5.Server challengeと4を連結する
6.5に対して、3を鍵として、hmac_md5を計算する
7.6と4を連結する

7で出来たものがNTLMv2レスポンスになります。
ややこしいですね。

MD4はRFC 1320
MD5はRFC 1321
HMACは RFC 2104に規定されています。
なお、それぞれC言語コードが附属しています。
たいして計算量も無いので最適化も不要でしょうから、そのまま使ってもOKかと思います。ただしライセンス表記が必要です。
探せばそのあたりをクリアしたコードも見つかります。
(僕はmd4はそのまま使いました。md5は以前準備したコードがあったのでそっちを利用)

基本的に、SMBと同じく全てリトルエンディアンが使われます。
普通ネットワークバイトオーダーと言えばビッグエンディアンなんですけどね。
最初はエンディアン気にしてコード書いてましたが、どうせiPhoneもリトルエンディアンだし途中から面倒になってリトルエンディアン環境想定で作りました。

さて、これでSMBサーバと認証ができるようになりました。

次回はWindowsファイル共有に使うNETBIOS名(ホスト名)をNetBIOS over TCP/IPを使って名前解決(IPアドレスを取得する)をやってみたいと思います。
まぁ、IPアドレスでアクセスすれば良いので不要な気もしますが。。

欲を言えばBROWSERプロトコルを使ってネットワーク上のNETBIOSホスト名を自動収集したいところですが、、まぁNASとかはBonjourに対応してるから不要な気もします。

2015-05-18 修正 challengeのスペルが違うというお恥ずかしいミスがありましたのでこっそり修正。

夜更かししないためのライフハック

昔実践していたことがあるのが、ひとりサマータイムもどき。
自分だけ時計を1時間か2時間ずらして生活します。(時計を進めます)

そうすると時計が午前2時とか3時とかになるまで起きていても平気です。(気分ですよ気分)
実際には午前0時頃なわけですが、さすがに(表示が)1時、2時になると寝ようかなーという気分になるわけです。

これはこれでよいのですが、昼間は他の人と時間がずれていて何かと不便です。

といわけで、帳尻合わせて夜だけ時計を進める歪みタイムゾーンを考えてみました。

・午前6時から午後6時までは普通の時計と同じです。
・午後6時からちょっと加速して、午後10時になるころに表示は24時(0時)になります。
・午後10時から午前2時までは表示は2時間進んだまま、通常と同じ速度で表示が進みます。
・午前2時から午前6時の間に2時間の進みを帳尻合わせします。

<図解>

とりあえずみんな笑顔でハッピーということで「スマイルタイム」と名づけました。
(というかいろいろ検索したんだけどわかりやすくて取れるドメインがこれでした)

http://smiletime.info/ <スマイルタイム>

↑思いついたらすぐになんかしてみたくなるので、説明のためのサイト作りました。全てパブリックドメイン的に扱って構わないので是非お試しください。普及活動します(弱々しく)
 寝る時間だけではなく、仕事もはやく終えるようになったらいいですよね。
 iPhone時計アプリとかもそのうち作ってみる。

iOS上でSMB/CIFS(Windowsファイル共有)をゼロから実装してみる

何故か全くライブラリが見つからないSMB/CIFS。
ググってみても、「SMBのiOSライブラリないの?(そもそもObj-cに限定しなくても無い)」「sambaがGPL、ぐぬぬぬ・・・」という記事しか見当たらない。組込み向けの製品はいくつか見つかりましたが、たぶん個人で買えるようなものじゃない。
きっと実装は厄介なんだろうなと見る気もしてなかったですが、風邪でダウンしていたので、その時に一度気合を入れてテストしてみました。

なお、tangoというOSSがあるようですが、うまく動かない。
恐らく認証関連が古いせいで最近のWindowsでは動かないぽい。
extended security flagが立っていると即座にエラー返すようなのできっとダメだろう、ってことでtangoは諦める。

SMBはSMB1とSMB2があって、SMB2は大きなバッファが扱えて高速LANで効率がいいようですが、NASとかで対応していないものもあるかもしれないし、iPhoneのWifiからではほとんど速度は変わらない(と思う)のでまずSMB1にトライ。(SMB1の仕様書読んでいるうちに、ファイルオフセット指定が32bitしかなく、2G超えるファイルダメじゃん!って気づいたけど気づかなかったことにします:→追記で訂正)

悪名高いMicrosoftの仕様書を読み解くわけですが、SMBは魑魅魍魎な方言やら実はこうでしたー! 残念! みたいな実装があって苦しむと噂には聞いております。

さて、さて、
仕様書読むと案外単純な通信です。
これはいける。

流れは、
・ネゴシエーション
・セッションリクエスト(認証)
・ツリー接続、一覧取得、(ここでパイプ、RPCの仕様書を読まされる(涙))
・ファイル一覧の取得 (ワイルドカード*でFindFileする。ルートディレクトリだと失敗するのでちょっと変える必要ある
・データの転送(実は簡単な命令っぽい)

という感じで、バイナリデータなのでC言語では扱いやすい(はず・・)

しかしセッションリクエストで早速ハマる。

[MS-CIFS]306ページ、2.2.4.53 SMB_COM_SESSION_SETUP_ANDX (0x73)

This command was introduced in the LAN Manager 1.0 dialect.
The formats of the request and response messages have changed since the command was first defined. The CIFS format, as defined for the NT LAN Manager dialect, is presented here. This format MUST be used when the NT LAN Manager dialect has been negotiated.
(後略)

このコマンドはLAN Manager1.0のときの(方言で?)定義された。
このコマンドが最初に定義されてから、リクエストと応答メッセージのフォーマットが変わったよ。

うーん。LAN Manager1.0はIBMの頃の仕様でもう使われていないので、NT LMを使うみたいね。
Wordデータは26 bytesなので、WordCoundは13になるはず??? よくわからん

他の通信をパケットキャプチャしてみるとWordCountが12=24Byteしかない。1バイトどこいったー?

試しにきにせず26Byteで送ってみたら見事エラーでございます・・・。

と思ったら別のファイルの[MS-SMB]の方の53ページにextended security flagを使う時にはSecurityBlobLengthを使えって書いてあった。・・・といった具合に仕様書読むにもコツが必要な模様。

とりあえずWindows7とNAS相手にツリー一覧取得までは出来たので、頑張ればなんとかなるかなー。
どうも一番面倒なのはツリー一覧取得するところだったっぽい。
もう少しちゃんとエラー処理とか、ネゴシエーションやらないと実用的には使えないと思うので作業量は多そうですが。

時間かかる要素その2は、SMB通信に使うNTLMの暗号化をどうするか。
とりあえず外部ライブラリ使って認証が通ることは確認しましたが、ここも自前で実装するとなると読まなければならない仕様書がまた増えて面倒くさい。
まぁ、こちらの方が内容はある程度クリアなので実装することは出来そうですが、まっとうにやったら時間かかりそうです。まっとうじゃない方法探します。

NTLMもUIWebViewでは使えるので、API公開してくれたらいいのに・・・。

ComicGlassへはうまく出来たら実装します。

追記:

だいぶ分かってきました。
上記記述にいくつか間違いがあったので一応訂正。

ファイルのリードについては見ていた資料が古かったようで、新しい資料にはOffsetの上位バイトのフィールドがありました。
(SMB_COM_READ_ANDX==0xeE最後の4バイト,Optinal)
よって2GBを超えるファイルもオフセット付で扱えます。
またTimeoutがTimeout_or_MaxCountHighになっており、ファイルを読む場合はMaxCountHighとして扱うように変わってました。
ネゴシエーションの時に対応非対応を設定できるようです。
(しかしWindowsXPの通信見てみると使ってない・・・)

ときおり出てくるDataOffsetとかの意味がわかって受信データのpaddingサイズ不要が要らないことがわかりました。(DataOffsetでデータの先頭がわかる)

カレンダー

2013年12月
« 11月   1月 »
 1
2345678
9101112131415
16171819202122
23242526272829
3031  

▲Pagetop