[轉]應用SMB/CIFS協議


第一節 本文的目的
   Microsoft公開了CIFS協議的所有細節,這使得我們可以了解這個協議並且編寫基於這個協議的應用程序。 SMB/CIFS協議在Windows系統中的被廣泛的應用,這要求我們對這個協議應該有所了解,下面文字就我的一點實際經驗與大家進行交流,如果有錯誤的地方,真誠的希望得到大家的指正,我的Email:ilsy@whitecell.org。
第二節 什么是SMB/CIFS協議?
   CIFS(Common Internet File System)是開放的跨平台的,其實現是基於SMB(Server Message Block)協議的,使用戶可以使用這個協議方便的向支持SMB協議的網絡服務器請求文件和打印服務。 
   在不同的操作系統上,都有相應的SMB協議的實現,包括我們知道的Samba、Windows的不同版本等,而應用CIFS協議都可以與其通訊,CIFS是一個開放的SMB版本。可以從http://msdn.microsoft.com/downloads /default.asp?url=/downloads/sample.asp?url=/msdn-files/027/001/902 /msdncompositedoc.xml下載CIFS詳細的說明文檔或者看最新的Platform SDK文檔。
   
   在所有的SMB數據報中都會包含下面的數據頭:
   typedef unsigned char UCHAR;                // 8  unsigned bits
   typedef unsigned short USHORT;              // 16 unsigned bits
   typedef unsigned long ULONG;                // 32 unsigned bits
   typedef struct {
       ULONG LowPart;
       LONG HighPart;
   } LARGE_INTEGER;                            // 64 bits of data
   //  結構根據不同的SMB請求會有不同的變化
   typedef struct  {
       UCHAR Protocol[4];                      // 協議的名字'SMB', 前面放一個0xFF
       UCHAR Command;                          // 請求的命令類型
       union {
           struct {
               UCHAR ErrorClass;               // Error class
               UCHAR Reserved;                 // Reserved for future use
               USHORT Error;                   // Error code
           } DosError;
           ULONG Status;                       // 32- bit 錯誤代碼
       } Status;
       UCHAR Flags;                            // 標記
       USHORT Flags2;                          // 擴展標記
       union {
           USHORT Pad[6];                      // Ensure section is 12 bytes long
           struct {
               USHORT PidHigh;                 // High part of PID
               UCHAR SecuritySignature[8];     // reserved for security
         } Extra;
       };
       USHORT Tid;                             // Tree 標識, 用來識別資源
       USHORT Pid;                             // Caller's process id
       USHORT Uid;                             // 用來識別用戶
       USHORT Mid;                             // multiplex id
       UCHAR  WordCount;                       // Count of parameter words
       USHORT ParameterWords[ WordCount ];     // The parameter words
       USHORT ByteCount;                       // Count of bytes
       UCHAR  Buffer[ ByteCount ];             // The bytes
   } SMB_HEADER;
   
   而每一個SMB數據報都會包含這樣的一個NetBIOS會話頭:
   
   //  NBT_HEADER 結構
   typedef struct  {
        struct  {
                u_char  packetType;            // 數據報類型,在使用SMB數據報時,這個字段的值是0x00
                u_char  packetFlags;           // 這個字段總是為0x00
                u_short packetLen;             // 在SMB數據報中,為SMB數據報的總長度,但不包含這個結構的長度
                } s;
   } NBT_HEADER;
   
   實際上,一個SMB數據報是這樣構成的: NBT Header + SMB Header + SMB Command Header + SMB Data
第三節 建立一個空會話
   SMB可以運行在TCP/IP等協議之上,如在我們常用的系統Windows系列操作系統上,如果要在TCP/IP上使用SMB協議,那么,你需要支持NetBIOS名字的傳輸。NETBIOS名字用來在網絡上鑒別一台提供SMB服務計算機,使用SMB協議前,首先應該用NetBIOS名字去鑒別一台提供SMB服務的計算機。
   我們可以用下面的代碼去取得一台計算機的NetBios名字:
   
   #include 
   #include 
   #include 
   #define xmalloc(s)   (char *)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY, (s)) //  分配內存
   #define xfree(p)     (char *)HeapFree (GetProcessHeap(),0, (p))                //  釋放內存
   //  NBT_NAME_HEADER 結構
   typedef struct  {
 USHORT ID;
 USHORT Flags;
 USHORT Questions;
 USHORT Answer;
 USHORT Authority;
 USHORT Additional;
   }NBT_NAME_HEADER;
   //  NBT_NAME_ANSWER 結構
   typedef struct  {
 char   Name_[34];
 USHORT Type;
        USHORT Class;
 ULONG  TTL;
 USHORT DataLen;
 UCHAR  Number;
 char   Name[16];
   }NBT_NAME_ANSWER;
   //  NetBIOS 名字查詢字符串
   unsigned char NetBiosQuery[] = 
      "/x20/x43/x4B/x41/x41/x41/x41/x41/x41/x41/x41/x41/x41/x41/x41/x41"
      "/x41/x41/x41/x41/x41/x41/x41/x41/x41/x41/x41/x41/x41/x41/x41/x41"
      "/x41/x00/x00/x21/x00/x01";
   int GET_NetBiosName(char * IpAddress, char * szNetBiosName, int timeout)
   {
      SOCKET             csock;
      NBT_NAME_HEADER    NbtNameHeader;
      NBT_NAME_ANSWER    NbtNameAnswer;
      struct sockaddr_in dest, from;
      int                fromlen = sizeof(from);
      int                iRet,iTimeout;
      char *             SendBuf;
      csock = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
      if(csock == SOCKET_ERROR)return NULL;
      //  建立UDP端口,查詢一台計算機的NetBIOS名字要通過UDP的137端口查詢
      
      iTimeout = timeout;
      iRet = setsockopt(csock,SOL_SOCKET,SO_SNDTIMEO,(char*)&iTimeout,sizeof(iTimeout));
      if(csock == SOCKET_ERROR)
      {
            closesocket(csock);
            return 1;
      }
      iTimeout = timeout;
      iRet = setsockopt(csock,SOL_SOCKET,SO_RCVTIMEO,(char*)&iTimeout,sizeof(iTimeout));
      if(csock == SOCKET_ERROR)
      {
            closesocket(csock);
            return 1;
      }
      //  建立超時
      
      dest.sin_family      = AF_INET;
      dest.sin_port        = htons(137);
      dest.sin_addr.s_addr = inet_addr(IpAddress);
      //  填充sockaddr_in結構
       
      memset(&NbtNameHeader, 0, sizeof(NBT_NAME_HEADER));
      NbtNameHeader.ID         = htons(0x01F8);
      NbtNameHeader.Flags      = htons(0x0010);
      NbtNameHeader.Questions  = htons(0x0001);
      NbtNameHeader.Answer     = 0;
      NbtNameHeader.Additional = 0;
      NbtNameHeader.Authority  = 0;
      //  填充NBT_NAME_HEADER結構
       
      SendBuf = xmalloc(sizeof(NBT_NAME_HEADER) + sizeof(NetBiosQuery));
      if(!SendBuf)
      {
            closesocket(csock);
            return 1;
      }
      memcpy(SendBuf, &NbtNameHeader, sizeof(NBT_NAME_HEADER));
      memcpy(SendBuf+sizeof(NBT_NAME_HEADER), NetBiosQuery, sizeof(NetBiosQuery));
      //  建立發送緩沖區,復制NBT_NAME_HEADER結構和NetBiosQuery到發送緩沖區
      
      iRet = sendto(csock, SendBuf, sizeof(NBT_NAME_HEADER) + sizeof(NetBiosQuery)-1, 0, (const sockaddr *)&dest, sizeof(dest));
      if(iRet <= 0)
      {
            closesocket(csock);
            xfree(SendBuf);
            return 1;
      }
      //  發送數據
      
      memset(&from, 0, sizeof(from));
      memset(&NbtNameAnswer, 0, sizeof(NBT_NAME_ANSWER));
      xfree(SendBuf);
      SendBuf = xmalloc(1024);
      if(!SendBuf)
      {
            closesocket(csock);
            return 1;
      }
      iRet = recvfrom(csock, SendBuf, 1024, 0, (sockaddr *)&from, &fromlen);
      if(iRet < (sizeof(NBT_NAME_ANSWER) + sizeof(NBT_NAME_HEADER)))
      {
            closesocket(csock);
            xfree(SendBuf);
            return 1;
      }
      //  接收數據
      
      memcpy(&NbtNameAnswer, SendBuf + sizeof(NBT_NAME_HEADER), sizeof(NBT_NAME_ANSWER));
      //  復制接收的數據到NBT_NAME_ANSWER結構
      strncpy(szNetBiosName, NbtNameAnswer.Name, sizeof(NbtNameAnswer.Name));
      //  復制接受到的NetBIOS名字到變量szNetBiosName
      
      closesocket(csock);
      xfree(SendBuf);
      return 0;
   }
   
 與一台計算機建立空連接一般需要下面這些步驟:
   
   1 - 客戶端發送已經編碼的服務器的NetBIOS名字,如果服務端確認,發送經確認NetBIOS數據
報給客戶端,客戶端在確認NetBIOS名字后才能繼續用SMB協議與服務端交互。
       NetBIOS 名字在Windows系統下總是大寫的,長度為16字節,它編碼后為32字節,是這樣編碼的:
       例如名字PPSP,不足16字節后面會補充空格,16進制為0x50 0x50 0x53 0x50 0x20 0x20...,系統
   把每一個字節分為4位一組,變換后成為 0x5 0x0 0x5 0x0 0x5 0x3 0x5 0x0 0x2 0x0 0x2 0x0...,然后
   把每一組數據都加上 ACSII碼'A'的值(0x41),最后,NetBIOS名字被編碼為32字節。實際中的編碼后的緩沖區向下面這樣:  
   00000030                                   46 41 46 41 46       ...D.FAFAF
   00000040  44 46 41 43 41 43 41 43 41 43 41 43 41 43 41 43 DFACACACACACACAC
   00000050  41 43 41 43 41 43 41 43 41 43 41 00 20 46 45 45 ACACACACACA..FEE
   00000060  46 46 44 46 45 43 41 43 41 43 41 43 41 43 41 43 FFDFECACACACACAC
   00000070  41 43 41 43 41 43 41 43 41 43 41 41 41 00       ACACACACACAAA.  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM