1# $Id: netbios.py 23 2006-11-08 15:45:33Z dugsong $ 2# -*- coding: utf-8 -*- 3"""Network Basic Input/Output System.""" 4from __future__ import absolute_import 5 6import struct 7 8from . import dpkt 9from . import dns 10from .compat import compat_ord 11 12 13def encode_name(name): 14 """ 15 Return the NetBIOS first-level encoded name. 16 17 14.1. FIRST LEVEL ENCODING 18 19 The first level representation consists of two parts: 20 21 - NetBIOS name 22 - NetBIOS scope identifier 23 24 The 16 byte NetBIOS name is mapped into a 32 byte wide field using a 25 reversible, half-ASCII, biased encoding. Each half-octet of the 26 NetBIOS name is encoded into one byte of the 32 byte field. The 27 first half octet is encoded into the first byte, the second half- 28 octet into the second byte, etc. 29 30 Each 4-bit, half-octet of the NetBIOS name is treated as an 8-bit, 31 right-adjusted, zero-filled binary number. This number is added to 32 value of the ASCII character 'A' (hexidecimal 41). The resulting 8- 33 bit number is stored in the appropriate byte. The following diagram 34 demonstrates this procedure: 35 36 37 0 1 2 3 4 5 6 7 38 +-+-+-+-+-+-+-+-+ 39 |a b c d|w x y z| ORIGINAL BYTE 40 +-+-+-+-+-+-+-+-+ 41 | | 42 +--------+ +--------+ 43 | | SPLIT THE NIBBLES 44 v v 45 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 46 +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ 47 |0 0 0 0 a b c d| |0 0 0 0 w x y z| 48 +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ 49 | | 50 + + ADD 'A' 51 | | 52 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 53 +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ 54 |0 1 0 0 0 0 0 1| |0 1 0 0 0 0 0 1| 55 +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ 56 57 This encoding results in a NetBIOS name being represented as a 58 sequence of 32 ASCII, upper-case characters from the set 59 {A,B,C...N,O,P}. 60 61 The NetBIOS scope identifier is a valid domain name (without a 62 leading dot). 63 64 An ASCII dot (2E hexidecimal) and the scope identifier are appended 65 to the encoded form of the NetBIOS name, the result forming a valid 66 domain name. 67 """ 68 l_ = [] 69 for c in struct.pack('16s', name.encode()): 70 c = compat_ord(c) 71 l_.append(chr((c >> 4) + 0x41)) 72 l_.append(chr((c & 0xf) + 0x41)) 73 return ''.join(l_) 74 75 76def decode_name(nbname): 77 """ 78 Return the NetBIOS first-level decoded nbname. 79 80 """ 81 if len(nbname) != 32: 82 return nbname 83 84 l_ = [] 85 for i in range(0, 32, 2): 86 l_.append( 87 chr( 88 ((ord(nbname[i]) - 0x41) << 4) | 89 ((ord(nbname[i + 1]) - 0x41) & 0xf) 90 ) 91 ) 92 return ''.join(l_).split('\x00', 1)[0] 93 94 95# RR types 96NS_A = 0x01 # IP address 97NS_NS = 0x02 # Name Server 98NS_NULL = 0x0A # NULL 99NS_NB = 0x20 # NetBIOS general Name Service 100NS_NBSTAT = 0x21 # NetBIOS NODE STATUS 101 102# RR classes 103NS_IN = 1 104 105# NBSTAT name flags 106NS_NAME_G = 0x8000 # group name (as opposed to unique) 107NS_NAME_DRG = 0x1000 # deregister 108NS_NAME_CNF = 0x0800 # conflict 109NS_NAME_ACT = 0x0400 # active 110NS_NAME_PRM = 0x0200 # permanent 111 112# NBSTAT service names 113nbstat_svcs = { 114 # (service, unique): list of ordered (name prefix, service name) tuples 115 (0x00, 0): [('', 'Domain Name')], 116 (0x00, 1): [('IS~', 'IIS'), ('', 'Workstation Service')], 117 (0x01, 0): [('__MSBROWSE__', 'Master Browser')], 118 (0x01, 1): [('', 'Messenger Service')], 119 (0x03, 1): [('', 'Messenger Service')], 120 (0x06, 1): [('', 'RAS Server Service')], 121 (0x1B, 1): [('', 'Domain Master Browser')], 122 (0x1C, 0): [('INet~Services', 'IIS'), ('', 'Domain Controllers')], 123 (0x1D, 1): [('', 'Master Browser')], 124 (0x1E, 0): [('', 'Browser Service Elections')], 125 (0x1F, 1): [('', 'NetDDE Service')], 126 (0x20, 1): [('Forte_$ND800ZA', 'DCA IrmaLan Gateway Server Service'), 127 ('', 'File Server Service')], 128 (0x21, 1): [('', 'RAS Client Service')], 129 (0x22, 1): [('', 'Microsoft Exchange Interchange(MSMail Connector)')], 130 (0x23, 1): [('', 'Microsoft Exchange Store')], 131 (0x24, 1): [('', 'Microsoft Exchange Directory')], 132 (0x2B, 1): [('', 'Lotus Notes Server Service')], 133 (0x2F, 0): [('IRISMULTICAST', 'Lotus Notes')], 134 (0x30, 1): [('', 'Modem Sharing Server Service')], 135 (0x31, 1): [('', 'Modem Sharing Client Service')], 136 (0x33, 0): [('IRISNAMESERVER', 'Lotus Notes')], 137 (0x43, 1): [('', 'SMS Clients Remote Control')], 138 (0x44, 1): [('', 'SMS Administrators Remote Control Tool')], 139 (0x45, 1): [('', 'SMS Clients Remote Chat')], 140 (0x46, 1): [('', 'SMS Clients Remote Transfer')], 141 (0x4C, 1): [('', 'DEC Pathworks TCPIP service on Windows NT')], 142 (0x52, 1): [('', 'DEC Pathworks TCPIP service on Windows NT')], 143 (0x87, 1): [('', 'Microsoft Exchange MTA')], 144 (0x6A, 1): [('', 'Microsoft Exchange IMC')], 145 (0xBE, 1): [('', 'Network Monitor Agent')], 146 (0xBF, 1): [('', 'Network Monitor Application')] 147} 148 149 150def node_to_service_name(name_service_flags): 151 name, service, flags = name_service_flags 152 try: 153 unique = int(flags & NS_NAME_G == 0) 154 for namepfx, svcname in nbstat_svcs[(service, unique)]: 155 if name.startswith(namepfx): 156 return svcname 157 except KeyError: 158 pass 159 return '' 160 161 162class NS(dns.DNS): 163 """ 164 NetBIOS Name Service. 165 166 RFC1002: https://tools.ietf.org/html/rfc1002 167 """ 168 169 class Q(dns.DNS.Q): 170 pass 171 172 class RR(dns.DNS.RR): 173 """NetBIOS resource record. 174 175 RFC1001: 14. REPRESENTATION OF NETBIOS NAMES 176 177 NetBIOS names as seen across the client interface to NetBIOS are 178 exactly 16 bytes long. Within the NetBIOS-over-TCP protocols, a 179 longer representation is used. 180 181 There are two levels of encoding. The first level maps a NetBIOS 182 name into a domain system name. The second level maps the domain 183 system name into the "compressed" representation required for 184 interaction with the domain name system. 185 186 Except in one packet, the second level representation is the only 187 NetBIOS name representation used in NetBIOS-over-TCP packet formats. 188 The exception is the RDATA field of a NODE STATUS RESPONSE packet. 189 """ 190 191 _node_name_struct = struct.Struct('>15s B H') 192 _node_name_len = _node_name_struct.size 193 194 def unpack_rdata(self, buf, off): 195 if self.type == NS_A: 196 self.ip = self.rdata 197 elif self.type == NS_NBSTAT: 198 num_names = compat_ord(self.rdata[0]) 199 200 self.nodenames = [ 201 self._node_name_struct.unpack_from( 202 self.rdata, 1+idx*self._node_name_len 203 ) for idx in range(num_names) 204 ] 205 # XXX - skip stats 206 207 208class Session(dpkt.Packet): 209 """NetBIOS Session Service.""" 210 __hdr__ = ( 211 ('type', 'B', 0), 212 ('flags', 'B', 0), 213 ('len', 'H', 0) 214 ) 215 216 217SSN_MESSAGE = 0 218SSN_REQUEST = 1 219SSN_POSITIVE = 2 220SSN_NEGATIVE = 3 221SSN_RETARGET = 4 222SSN_KEEPALIVE = 5 223 224 225class Datagram(dpkt.Packet): 226 """NetBIOS Datagram Service.""" 227 __hdr__ = ( 228 ('type', 'B', 0), 229 ('flags', 'B', 0), 230 ('id', 'H', 0), 231 ('src', 'I', 0), 232 ('sport', 'H', 0), 233 ('len', 'H', 0), 234 ('off', 'H', 0) 235 ) 236 237 238DGRAM_UNIQUE = 0x10 239DGRAM_GROUP = 0x11 240DGRAM_BROADCAST = 0x12 241DGRAM_ERROR = 0x13 242DGRAM_QUERY = 0x14 243DGRAM_POSITIVE = 0x15 244DGRAM_NEGATIVE = 0x16 245 246 247def test_encode_name(): 248 assert encode_name('The NetBIOS name') == 'FEGIGFCAEOGFHEECEJEPFDCAGOGBGNGF' 249 # rfc1002 250 assert encode_name('FRED ') == 'EGFCEFEECACACACACACACACACACACACA' 251 # https://github.com/kbandla/dpkt/issues/458 252 assert encode_name('*') == 'CKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' 253 254 255def test_decode_name(): 256 assert decode_name('FEGIGFCAEOGFHEECEJEPFDCAGOGBGNGF') == 'The NetBIOS name' 257 # original botched example from rfc1001 258 assert decode_name('FEGHGFCAEOGFHEECEJEPFDCAHEGBGNGF') == 'Tge NetBIOS tame' 259 assert decode_name('CKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') == '*' 260 261 # decode a name which is not 32 chars long 262 assert decode_name('CKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABB') == 'CKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABB' 263 264 265def test_node_to_service_name(): 266 svcname = node_to_service_name(("ISS", 0x00, 0x0800)) 267 assert svcname == "Workstation Service" 268 269 270def test_node_to_service_name_keyerror(): 271 svcname = node_to_service_name(("ISS", 0xff, 0x0800)) 272 assert svcname == "" 273 274 275def test_rr(): 276 import pytest 277 from binascii import unhexlify 278 279 rr = NS.RR() 280 with pytest.raises(NotImplementedError): 281 len(rr) 282 283 buf = unhexlify(''.join([ 284 '01', # A record 285 '0001', # DNS_IN 286 '00000000', # TTL 287 '0000', # rlen 288 ])) 289 rr.unpack_rdata(buf, 0) 290 assert rr.ip == rr.rdata 291 292 293def test_rr_nbstat(): 294 from binascii import unhexlify 295 buf = unhexlify(''.join([ 296 '41' * 1025, # Name 297 '0033', # NS_NBSTAT 298 '0001', # DNS_IN 299 '00000000', # TTL 300 '0004', # rlen 301 ])) 302 rdata = ( 303 b'\x02' # NUM_NAMES 304 b'ABCDEFGHIJKLMNO\x2f\x01\x02' 305 b'PQRSTUVWXYZABCD\x43\x03\x04' 306 ) 307 rr = NS.RR( 308 type=NS_NBSTAT, 309 rdata=rdata, 310 ) 311 312 assert rr.type == NS_NBSTAT 313 rr.unpack_rdata(buf, 0) 314 assert rr.nodenames == [ 315 (b'ABCDEFGHIJKLMNO', 0x2f, 0x0102), 316 (b'PQRSTUVWXYZABCD', 0x43, 0x0304), 317 ] 318 319 320def test_ns(): 321 from binascii import unhexlify 322 ns = NS() 323 correct = unhexlify( 324 '0000' 325 '0100' 326 '0000000000000000' 327 ) 328 assert bytes(ns) == correct 329