NetBiosUtils.cs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. /* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  2. *
  3. * You can redistribute this program and/or modify it under the terms of
  4. * the GNU Lesser Public License as published by the Free Software Foundation,
  5. * either version 3 of the License, or (at your option) any later version.
  6. */
  7. using System;
  8. using System.Collections.Generic;
  9. using System.IO;
  10. using System.Text;
  11. using Utilities;
  12. namespace SMBLibrary.NetBios
  13. {
  14. public class NetBiosUtils
  15. {
  16. /// <summary>
  17. /// The NetBIOS naming convention allows for 16 characters in a NetBIOS name.
  18. /// Microsoft, however, limits NetBIOS names to 15 characters and uses the 16th character as a NetBIOS suffix
  19. /// See http://support.microsoft.com/kb/163409/en-us
  20. /// </summary>
  21. public static string GetMSNetBiosName(string name, NetBiosSuffix suffix)
  22. {
  23. if (name.Length > 15)
  24. {
  25. name = name.Substring(0, 15);
  26. }
  27. else if (name.Length < 15)
  28. {
  29. name = name.PadRight(15);
  30. }
  31. return name + (char)suffix;
  32. }
  33. public static string GetNameFromMSNetBiosName(string netBiosName)
  34. {
  35. if (netBiosName.Length != 16)
  36. {
  37. throw new ArgumentException("Invalid MS NetBIOS name");
  38. }
  39. netBiosName = netBiosName.Substring(0, 15);
  40. return netBiosName.TrimEnd(' ');
  41. }
  42. public static byte[] EncodeName(string name, NetBiosSuffix suffix, string scopeID)
  43. {
  44. string netBiosName = GetMSNetBiosName(name, suffix);
  45. return EncodeName(netBiosName, scopeID);
  46. }
  47. /// <param name="name">NetBIOS name</param>
  48. /// <param name="scopeID">dot-separated labels, formatted per DNS naming rules</param>
  49. public static byte[] EncodeName(string netBiosName, string scopeID)
  50. {
  51. string domainName = FirstLevelEncoding(netBiosName, scopeID);
  52. return SecondLevelEncoding(domainName);
  53. }
  54. // The conversion of a NetBIOS name to a format complying with DNS "best practices".
  55. // NetBIOS names may contain characters which are not considered valid for use in DNS names,
  56. // yet RFC 1001 and RFC 1002 attempted to map the NetBIOS name space into the DNS name space.
  57. // To work around this conflict, NetBIOS names are encoded by splitting each byte of the name
  58. // into two nibbles and then adding the value of 'A' (0x41).
  59. // Thus, the '&' character (0x26) would be encoded as "CG".
  60. // NetBIOS names are usually padded with spaces before being encoded.
  61. /// <param name="name">NetBIOS name</param>
  62. /// <param name="scopeID">dot-separated labels, formatted per DNS naming rules</param>
  63. public static string FirstLevelEncoding(string netBiosName, string scopeID)
  64. {
  65. // RFC 1001: NetBIOS names as seen across the client interface to NetBIOS are exactly 16 bytes long
  66. if (netBiosName.Length != 16)
  67. {
  68. throw new ArgumentException("Invalid MS NetBIOS name");
  69. }
  70. StringBuilder builder = new StringBuilder();
  71. for (int index = 0; index < netBiosName.Length; index++)
  72. {
  73. byte c = (byte)netBiosName[index];
  74. byte high = (byte)(0x41 + (c >> 4));
  75. byte low = (byte)(0x41 + (c & 0x0F));
  76. builder.Append((char)high);
  77. builder.Append((char)low);
  78. }
  79. if (scopeID.Length > 0)
  80. {
  81. builder.Append(".");
  82. builder.Append(scopeID);
  83. }
  84. return builder.ToString();
  85. }
  86. // Domain names messages are expressed in terms of a sequence
  87. // of labels. Each label is represented as a one octet length
  88. // field followed by that number of octets. Since every domain
  89. // name ends with the null label of the root, a compressed
  90. // domain name is terminated by a length byte of zero
  91. /// <summary>
  92. /// The on-the-wire format of an NBT name. The encoding scheme replaces the familiar dot characters
  93. /// used in DNS names with a byte containing the length of the next label.
  94. /// </summary>
  95. public static byte[] SecondLevelEncoding(string domainName)
  96. {
  97. string[] labels = domainName.Split('.');
  98. int length = 1; // null terminator
  99. for (int index = 0; index < labels.Length; index++)
  100. {
  101. length += 1 + labels[index].Length;
  102. if (labels[index].Length > 63)
  103. {
  104. throw new ArgumentException("Invalid NetBIOS label length");
  105. }
  106. }
  107. byte[] result = new byte[length];
  108. int offset = 0;
  109. foreach(string label in labels)
  110. {
  111. result[offset] = (byte)label.Length;
  112. offset++;
  113. ByteWriter.WriteAnsiString(result, offset, label, label.Length);
  114. offset += label.Length;
  115. }
  116. result[offset] = 0; // null termination
  117. return result;
  118. }
  119. public static string DecodeName(byte[] buffer, ref int offset)
  120. {
  121. string domainName = SecondLevelDecoding(buffer, ref offset);
  122. string name = domainName.Split('.')[0];
  123. return FirstLevelDecoding(name);
  124. }
  125. public static string SecondLevelDecoding(byte[] buffer, ref int offset)
  126. {
  127. StringBuilder builder = new StringBuilder();
  128. byte labelLength = ByteReader.ReadByte(buffer, ref offset);
  129. while (labelLength > 0)
  130. {
  131. if (builder.Length > 0)
  132. {
  133. builder.Append(".");
  134. }
  135. // The high order two bits of the length field must be zero
  136. if (labelLength > 63)
  137. {
  138. throw new ArgumentException("Invalid NetBIOS label length");
  139. }
  140. string label = ByteReader.ReadAnsiString(buffer, offset, labelLength);
  141. builder.Append(label);
  142. offset += labelLength;
  143. labelLength = ByteReader.ReadByte(buffer, ref offset);
  144. }
  145. return builder.ToString();
  146. }
  147. public static string FirstLevelDecoding(string name)
  148. {
  149. StringBuilder builder = new StringBuilder();
  150. for(int index = 0; index < name.Length; index += 2)
  151. {
  152. byte c0 = (byte)name[index];
  153. byte c1 = (byte)name[index + 1];
  154. byte high = (byte)(((c0 - 0x41) & 0xF) << 4);
  155. byte low = (byte)((c1 - 0x41) & 0xF);
  156. byte c = (byte)(high | low);
  157. builder.Append((char)c);
  158. }
  159. return builder.ToString();
  160. }
  161. public static void WriteNamePointer(byte[] buffer, ref int offset, int nameOffset)
  162. {
  163. WriteNamePointer(buffer, offset, nameOffset);
  164. offset += 2;
  165. }
  166. /// <summary>
  167. /// Will write a 2 bytes pointer to a name
  168. /// Note: NetBIOS implementations can only use label string pointers in Name Service packets
  169. /// </summary>
  170. public static void WriteNamePointer(byte[] buffer, int offset, int nameOffset)
  171. {
  172. ushort pointer = (ushort)(0xC000 | (nameOffset & 0x3FFF));
  173. BigEndianWriter.WriteUInt16(buffer, offset, pointer);
  174. }
  175. public static void WriteNamePointer(Stream stream, int nameOffset)
  176. {
  177. ushort pointer = (ushort)(0xC000 | (nameOffset & 0x3FFF));
  178. BigEndianWriter.WriteUInt16(stream, pointer);
  179. }
  180. }
  181. }