NetBiosUtils.cs 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. /* Copyright (C) 2014-2020 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 NetBiosSuffix GetSuffixFromMSNetBiosName(string netBiosName)
  43. {
  44. if (netBiosName.Length != 16)
  45. {
  46. throw new ArgumentException("Invalid MS NetBIOS name");
  47. }
  48. return (NetBiosSuffix)netBiosName[15];
  49. }
  50. public static byte[] EncodeName(string name, NetBiosSuffix suffix, string scopeID)
  51. {
  52. string netBiosName = GetMSNetBiosName(name, suffix);
  53. return EncodeName(netBiosName, scopeID);
  54. }
  55. /// <param name="name">NetBIOS name</param>
  56. /// <param name="scopeID">dot-separated labels, formatted per DNS naming rules</param>
  57. public static byte[] EncodeName(string netBiosName, string scopeID)
  58. {
  59. string domainName = FirstLevelEncoding(netBiosName, scopeID);
  60. return SecondLevelEncoding(domainName);
  61. }
  62. // The conversion of a NetBIOS name to a format complying with DNS "best practices".
  63. // NetBIOS names may contain characters which are not considered valid for use in DNS names,
  64. // yet RFC 1001 and RFC 1002 attempted to map the NetBIOS name space into the DNS name space.
  65. // To work around this conflict, NetBIOS names are encoded by splitting each byte of the name
  66. // into two nibbles and then adding the value of 'A' (0x41).
  67. // Thus, the '&' character (0x26) would be encoded as "CG".
  68. // NetBIOS names are usually padded with spaces before being encoded.
  69. /// <param name="name">NetBIOS name</param>
  70. /// <param name="scopeID">dot-separated labels, formatted per DNS naming rules</param>
  71. public static string FirstLevelEncoding(string netBiosName, string scopeID)
  72. {
  73. // RFC 1001: NetBIOS names as seen across the client interface to NetBIOS are exactly 16 bytes long
  74. if (netBiosName.Length != 16)
  75. {
  76. throw new ArgumentException("Invalid MS NetBIOS name");
  77. }
  78. StringBuilder builder = new StringBuilder();
  79. for (int index = 0; index < netBiosName.Length; index++)
  80. {
  81. byte c = (byte)netBiosName[index];
  82. byte high = (byte)(0x41 + (c >> 4));
  83. byte low = (byte)(0x41 + (c & 0x0F));
  84. builder.Append((char)high);
  85. builder.Append((char)low);
  86. }
  87. if (scopeID.Length > 0)
  88. {
  89. builder.Append(".");
  90. builder.Append(scopeID);
  91. }
  92. return builder.ToString();
  93. }
  94. // Domain names messages are expressed in terms of a sequence
  95. // of labels. Each label is represented as a one octet length
  96. // field followed by that number of octets. Since every domain
  97. // name ends with the null label of the root, a compressed
  98. // domain name is terminated by a length byte of zero
  99. /// <summary>
  100. /// The on-the-wire format of an NBT name. The encoding scheme replaces the familiar dot characters
  101. /// used in DNS names with a byte containing the length of the next label.
  102. /// </summary>
  103. public static byte[] SecondLevelEncoding(string domainName)
  104. {
  105. string[] labels = domainName.Split('.');
  106. int length = 1; // null terminator
  107. for (int index = 0; index < labels.Length; index++)
  108. {
  109. length += 1 + labels[index].Length;
  110. if (labels[index].Length > 63)
  111. {
  112. throw new ArgumentException("Invalid NetBIOS label length");
  113. }
  114. }
  115. byte[] result = new byte[length];
  116. int offset = 0;
  117. foreach(string label in labels)
  118. {
  119. result[offset] = (byte)label.Length;
  120. offset++;
  121. ByteWriter.WriteAnsiString(result, offset, label, label.Length);
  122. offset += label.Length;
  123. }
  124. result[offset] = 0; // null termination
  125. return result;
  126. }
  127. public static string DecodeName(byte[] buffer, ref int offset)
  128. {
  129. string domainName = SecondLevelDecoding(buffer, ref offset);
  130. string name = domainName.Split('.')[0];
  131. return FirstLevelDecoding(name);
  132. }
  133. public static string SecondLevelDecoding(byte[] buffer, ref int offset)
  134. {
  135. StringBuilder builder = new StringBuilder();
  136. byte labelLength = ByteReader.ReadByte(buffer, ref offset);
  137. while (labelLength > 0)
  138. {
  139. if (builder.Length > 0)
  140. {
  141. builder.Append(".");
  142. }
  143. // The high order two bits of the length field must be zero
  144. if (labelLength > 63)
  145. {
  146. throw new ArgumentException("Invalid NetBIOS label length");
  147. }
  148. string label = ByteReader.ReadAnsiString(buffer, offset, labelLength);
  149. builder.Append(label);
  150. offset += labelLength;
  151. labelLength = ByteReader.ReadByte(buffer, ref offset);
  152. }
  153. return builder.ToString();
  154. }
  155. public static string FirstLevelDecoding(string name)
  156. {
  157. StringBuilder builder = new StringBuilder();
  158. for(int index = 0; index < name.Length; index += 2)
  159. {
  160. byte c0 = (byte)name[index];
  161. byte c1 = (byte)name[index + 1];
  162. byte high = (byte)(((c0 - 0x41) & 0xF) << 4);
  163. byte low = (byte)((c1 - 0x41) & 0xF);
  164. byte c = (byte)(high | low);
  165. builder.Append((char)c);
  166. }
  167. return builder.ToString();
  168. }
  169. public static void WriteNamePointer(byte[] buffer, ref int offset, int nameOffset)
  170. {
  171. WriteNamePointer(buffer, offset, nameOffset);
  172. offset += 2;
  173. }
  174. /// <summary>
  175. /// Will write a 2 bytes pointer to a name
  176. /// Note: NetBIOS implementations can only use label string pointers in Name Service packets
  177. /// </summary>
  178. public static void WriteNamePointer(byte[] buffer, int offset, int nameOffset)
  179. {
  180. ushort pointer = (ushort)(0xC000 | (nameOffset & 0x3FFF));
  181. BigEndianWriter.WriteUInt16(buffer, offset, pointer);
  182. }
  183. public static void WriteNamePointer(Stream stream, int nameOffset)
  184. {
  185. ushort pointer = (ushort)(0xC000 | (nameOffset & 0x3FFF));
  186. BigEndianWriter.WriteUInt16(stream, pointer);
  187. }
  188. }
  189. }